diff --git a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs index 1605d0a9ec9a..8435bfa998a6 100644 --- a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs +++ b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs @@ -72,6 +72,12 @@ public HttpsConnectionAdapterOptions() /// public SslProtocols SslProtocols { get; set; } + /// + /// The protocols enabled on this endpoint. + /// + /// Defaults to HTTP/1.x only. + internal HttpProtocols HttpProtocols { get; set; } + /// /// Specifies whether the certificate revocation list is checked during authentication. /// diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs index 87d2869f52e1..3760612366e5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs @@ -77,7 +77,7 @@ public async Task BindAsync(EndPoint endPoint, MultiplexedConnectionDe // The QUIC transport will check if TlsConnectionCallbackOptions is missing. if (listenOptions.HttpsOptions != null) { - var sslServerAuthenticationOptions = HttpsConnectionMiddleware.CreateHttp3Options(listenOptions.HttpsOptions.Value); + var sslServerAuthenticationOptions = HttpsConnectionMiddleware.CreateHttp3Options(listenOptions.HttpsOptions); features.Set(new TlsConnectionCallbackOptions { ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols ?? new List { SslApplicationProtocol.Http3 }, diff --git a/src/Servers/Kestrel/Core/src/ListenOptions.cs b/src/Servers/Kestrel/Core/src/ListenOptions.cs index dba524d66aaf..b8aaf3c75d72 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptions.cs @@ -140,8 +140,7 @@ internal string Scheme } internal bool IsTls { get; set; } - /// Should not be inspected until the configuration has been loaded. - internal Lazy? HttpsOptions { get; set; } + internal HttpsConnectionAdapterOptions? HttpsOptions { get; set; } internal TlsHandshakeCallbackOptions? HttpsCallbackOptions { get; set; } /// diff --git a/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs b/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs index 7f2744146ce8..175b8bed8163 100644 --- a/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs +++ b/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs @@ -163,24 +163,33 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, Action(() => + var options = new HttpsConnectionAdapterOptions(); + listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options); + configureOptions(options); + listenOptions.KestrelServerOptions.ApplyDefaultCert(options); + + if (options.ServerCertificate == null && options.ServerCertificateSelector == null) { - // We defer configuration of the https options until build time so that the IConfiguration will be available. - // This is particularly important in docker containers, where the docker tools use IConfiguration to tell - // us where the development certificates have been mounted. + throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound); + } - var options = new HttpsConnectionAdapterOptions(); - listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options); - configureOptions(options); - listenOptions.KestrelServerOptions.ApplyDefaultCert(options); + return listenOptions.UseHttps(options); + } - if (options.ServerCertificate == null && options.ServerCertificateSelector == null) - { - throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound); - } + // Use Https if a default cert is available + internal static bool TryUseHttps(this ListenOptions listenOptions) + { + var options = new HttpsConnectionAdapterOptions(); + listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options); + listenOptions.KestrelServerOptions.ApplyDefaultCert(options); - return options; - })); + if (options.ServerCertificate == null && options.ServerCertificateSelector == null) + { + return false; + } + + listenOptions.UseHttps(options); + return true; } /// @@ -192,28 +201,16 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, ActionThe . public static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions) { - return listenOptions.UseHttps(new Lazy(httpsOptions)); - } + var loggerFactory = listenOptions.KestrelServerOptions?.ApplicationServices.GetRequiredService() ?? NullLoggerFactory.Instance; - /// - /// Configure Kestrel to use HTTPS. This does not use default certificates or other defaults specified via config or - /// . - /// - /// The to configure. - /// Options to configure HTTPS. - /// The . - private static ListenOptions UseHttps(this ListenOptions listenOptions, Lazy lazyHttpsOptions) - { listenOptions.IsTls = true; - listenOptions.HttpsOptions = lazyHttpsOptions; + listenOptions.HttpsOptions = httpsOptions; - // NB: This lambda will only be invoked if either HTTP/1.* or HTTP/2 is being used listenOptions.Use(next => { - // Evaluate the HttpsConnectionAdapterOptions, now that the configuration, if any, has been loaded - var httpsOptions = listenOptions.HttpsOptions.Value; - var loggerFactory = listenOptions.KestrelServerOptions?.ApplicationServices.GetRequiredService() ?? NullLoggerFactory.Instance; - var middleware = new HttpsConnectionMiddleware(next, httpsOptions, listenOptions.Protocols, loggerFactory); + // Set the list of protocols from listen options + httpsOptions.HttpProtocols = listenOptions.Protocols; + var middleware = new HttpsConnectionMiddleware(next, httpsOptions, loggerFactory); return middleware.OnConnectionAsync; }); @@ -273,7 +270,6 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, TlsHandsh listenOptions.IsTls = true; listenOptions.HttpsCallbackOptions = callbackOptions; - // NB: This lambda will only be invoked if either HTTP/1.* or HTTP/2 is being used listenOptions.Use(next => { // Set the list of protocols from listen options. diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs index 901818c2e164..16abe279d3e8 100644 --- a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs +++ b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs @@ -33,9 +33,6 @@ internal sealed class HttpsConnectionMiddleware private readonly ILogger _logger; private readonly Func _sslStreamFactory; - // Internal for testing - internal readonly HttpProtocols _httpProtocols; - // The following fields are only set by HttpsConnectionAdapterOptions ctor. private readonly HttpsConnectionAdapterOptions? _options; private readonly SslStreamCertificateContext? _serverCertificateContext; @@ -45,16 +42,17 @@ internal sealed class HttpsConnectionMiddleware // The following fields are only set by TlsHandshakeCallbackOptions ctor. private readonly Func>? _tlsCallbackOptions; private readonly object? _tlsCallbackOptionsState; + private readonly HttpProtocols _httpProtocols; // Pool for cancellation tokens that cancel the handshake private readonly CancellationTokenSourcePool _ctsPool = new(); - public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, HttpProtocols httpProtocols) - : this(next, options, httpProtocols, loggerFactory: NullLoggerFactory.Instance) + public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options) + : this(next, options, loggerFactory: NullLoggerFactory.Instance) { } - public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, HttpProtocols httpProtocols, ILoggerFactory loggerFactory) + public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(options); @@ -74,7 +72,7 @@ public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapter //_sslStreamFactory = s => new SslStream(s); _options = options; - _httpProtocols = ValidateAndNormalizeHttpProtocols(httpProtocols, _logger); + _options.HttpProtocols = ValidateAndNormalizeHttpProtocols(_options.HttpProtocols, _logger); // capture the certificate now so it can't be switched after validation _serverCertificate = options.ServerCertificate; @@ -322,7 +320,7 @@ private Task DoOptionsBasedHandshakeAsync(ConnectionContext context, SslStream s CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, }; - ConfigureAlpn(sslOptions, _httpProtocols); + ConfigureAlpn(sslOptions, _options.HttpProtocols); _options.OnAuthenticate?.Invoke(context, sslOptions); diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs index 53b707743058..36683b4c81c0 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs @@ -252,104 +252,6 @@ public void ConfigureEndpointDevelopmentCertificateGetsLoadedWhenPresent() } } - [Fact] - public void LoadDevelopmentCertificate_ConfigureFirst() - { - try - { - var serverOptions = CreateServerOptions(); - var certificate = new X509Certificate2(TestResources.GetCertPath("aspnetdevcert.pfx"), "testPassword", X509KeyStorageFlags.Exportable); - var bytes = certificate.Export(X509ContentType.Pkcs12, "1234"); - var path = GetCertificatePath(); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - File.WriteAllBytes(path, bytes); - - var config = new ConfigurationBuilder().AddInMemoryCollection(new[] - { - new KeyValuePair("Certificates:Development:Password", "1234"), - }).Build(); - - serverOptions.Configure(config); - - Assert.Null(serverOptions.DefaultCertificate); - - serverOptions.ConfigurationLoader.Load(); - - Assert.NotNull(serverOptions.DefaultCertificate); - Assert.Equal(serverOptions.DefaultCertificate.SerialNumber, certificate.SerialNumber); - - var ran1 = false; - serverOptions.ListenAnyIP(4545, listenOptions => - { - ran1 = true; - listenOptions.UseHttps(); - }); - Assert.True(ran1); - - var listenOptions = serverOptions.CodeBackedListenOptions.Single(); - Assert.False(listenOptions.HttpsOptions.IsValueCreated); - listenOptions.Build(); - Assert.True(listenOptions.HttpsOptions.IsValueCreated); - Assert.Equal(listenOptions.HttpsOptions.Value.ServerCertificate?.SerialNumber, certificate.SerialNumber); - } - finally - { - if (File.Exists(GetCertificatePath())) - { - File.Delete(GetCertificatePath()); - } - } - } - - [Fact] - public void LoadDevelopmentCertificate_UseHttpsFirst() - { - try - { - var serverOptions = CreateServerOptions(); - var certificate = new X509Certificate2(TestResources.GetCertPath("aspnetdevcert.pfx"), "testPassword", X509KeyStorageFlags.Exportable); - var bytes = certificate.Export(X509ContentType.Pkcs12, "1234"); - var path = GetCertificatePath(); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - File.WriteAllBytes(path, bytes); - - var ran1 = false; - serverOptions.ListenAnyIP(4545, listenOptions => - { - ran1 = true; - listenOptions.UseHttps(); - }); - Assert.True(ran1); - - var config = new ConfigurationBuilder().AddInMemoryCollection(new[] - { - new KeyValuePair("Certificates:Development:Password", "1234"), - }).Build(); - - serverOptions.Configure(config); - - Assert.Null(serverOptions.DefaultCertificate); - - serverOptions.ConfigurationLoader.Load(); - - Assert.NotNull(serverOptions.DefaultCertificate); - Assert.Equal(serverOptions.DefaultCertificate.SerialNumber, certificate.SerialNumber); - - var listenOptions = serverOptions.CodeBackedListenOptions.Single(); - Assert.False(listenOptions.HttpsOptions.IsValueCreated); - listenOptions.Build(); - Assert.True(listenOptions.HttpsOptions.IsValueCreated); - Assert.Equal(listenOptions.HttpsOptions.Value.ServerCertificate?.SerialNumber, certificate.SerialNumber); - } - finally - { - if (File.Exists(GetCertificatePath())) - { - File.Delete(GetCertificatePath()); - } - } - } - [Fact] public void ConfigureEndpoint_ThrowsWhen_The_PasswordIsMissing() { @@ -828,8 +730,6 @@ public void EndpointConfigureSection_CanSetSslProtocol() }); }); - _ = serverOptions.CodeBackedListenOptions.Single().HttpsOptions.Value; // Force evaluation - Assert.True(ranDefault); Assert.True(ran1); Assert.True(ran2); @@ -965,8 +865,6 @@ public void EndpointConfigureSection_CanSetClientCertificateMode() }); }); - _ = serverOptions.CodeBackedListenOptions.Single().HttpsOptions.Value; // Force evaluation - Assert.True(ranDefault); Assert.True(ran1); Assert.True(ran2); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs index cdb836028951..68929b4f167a 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs @@ -228,8 +228,7 @@ void ConfigureListenOptions(ListenOptions listenOptions) public void ThrowsWhenNoServerCertificateIsProvided() { Assert.Throws(() => new HttpsConnectionMiddleware(context => Task.CompletedTask, - new HttpsConnectionAdapterOptions(), - ListenOptions.DefaultHttpProtocols) + new HttpsConnectionAdapterOptions()) ); } @@ -1269,8 +1268,7 @@ public void AcceptsCertificateWithoutExtensions(string testCertName) new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, - }, - ListenOptions.DefaultHttpProtocols); + }); } [Theory] @@ -1288,8 +1286,7 @@ public void ValidatesEnhancedKeyUsageOnCertificate(string testCertName) new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, - }, - ListenOptions.DefaultHttpProtocols); + }); } [Theory] @@ -1308,7 +1305,7 @@ public void ThrowsForCertificatesMissingServerEku(string testCertName) new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, - }, ListenOptions.DefaultHttpProtocols)); + })); Assert.Equal(CoreStrings.FormatInvalidServerCertificateEku(cert.Thumbprint), ex.Message); } @@ -1355,10 +1352,11 @@ public void Http1AndHttp2DowngradeToHttp1ForHttpsOnIncompatibleWindowsVersions() var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2, + HttpProtocols = HttpProtocols.Http1AndHttp2 }; - var middleware = new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions, HttpProtocols.Http1AndHttp2); + new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions); - Assert.Equal(HttpProtocols.Http1, middleware._httpProtocols); + Assert.Equal(HttpProtocols.Http1, httpConnectionAdapterOptions.HttpProtocols); } [ConditionalFact] @@ -1369,10 +1367,11 @@ public void Http1AndHttp2DoesNotDowngradeOnCompatibleWindowsVersions() var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2, + HttpProtocols = HttpProtocols.Http1AndHttp2 }; - var middleware = new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions, HttpProtocols.Http1AndHttp2); + new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions); - Assert.Equal(HttpProtocols.Http1AndHttp2, middleware._httpProtocols); + Assert.Equal(HttpProtocols.Http1AndHttp2, httpConnectionAdapterOptions.HttpProtocols); } [ConditionalFact] @@ -1383,9 +1382,10 @@ public void Http2ThrowsOnIncompatibleWindowsVersions() var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2, + HttpProtocols = HttpProtocols.Http2 }; - Assert.Throws(() => new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions, HttpProtocols.Http2)); + Assert.Throws(() => new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions)); } [ConditionalFact] @@ -1396,10 +1396,11 @@ public void Http2DoesNotThrowOnCompatibleWindowsVersions() var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2, + HttpProtocols = HttpProtocols.Http2 }; // Does not throw - new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions, HttpProtocols.Http2); + new HttpsConnectionMiddleware(context => Task.CompletedTask, httpConnectionAdapterOptions); } private static async Task App(HttpContext httpContext) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs index fb599ec4870f..c78429ea1e9a 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs @@ -53,18 +53,14 @@ public void UseHttpsDefaultsToDefaultCert() Assert.False(serverOptions.IsDevCertLoaded); - var ranUseHttpsAction = false; serverOptions.ListenLocalhost(5001, options => { options.UseHttps(opt => { // The default cert is applied after UseHttps. Assert.Null(opt.ServerCertificate); - ranUseHttpsAction = true; }); }); - _ = serverOptions.CodeBackedListenOptions[1].HttpsOptions.Value; // Force evaluation - Assert.True(ranUseHttpsAction); Assert.False(serverOptions.IsDevCertLoaded); } @@ -110,20 +106,14 @@ public void ConfigureHttpsDefaultsNeverLoadsDefaultCert() options.ServerCertificate = _x509Certificate2; options.ClientCertificateMode = ClientCertificateMode.RequireCertificate; }); - var ranUseHttpsAction = false; serverOptions.ListenLocalhost(5000, options => { options.UseHttps(opt => { Assert.Equal(_x509Certificate2, opt.ServerCertificate); Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode); - ranUseHttpsAction = true; }); }); - - _ = serverOptions.CodeBackedListenOptions.Single().HttpsOptions.Value; // Force evaluation - Assert.True(ranUseHttpsAction); - // Never lazy loaded Assert.False(serverOptions.IsDevCertLoaded); Assert.Null(serverOptions.DefaultCertificate); @@ -143,7 +133,6 @@ public void ConfigureCertSelectorNeverLoadsDefaultCert() }; options.ClientCertificateMode = ClientCertificateMode.RequireCertificate; }); - var ranUseHttpsAction = false; serverOptions.ListenLocalhost(5000, options => { options.UseHttps(opt => @@ -151,13 +140,8 @@ public void ConfigureCertSelectorNeverLoadsDefaultCert() Assert.Null(opt.ServerCertificate); Assert.NotNull(opt.ServerCertificateSelector); Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode); - ranUseHttpsAction = true; }); }); - - _ = serverOptions.CodeBackedListenOptions.Single().HttpsOptions.Value; // Force evaluation - Assert.True(ranUseHttpsAction); - // Never lazy loaded Assert.False(serverOptions.IsDevCertLoaded); Assert.Null(serverOptions.DefaultCertificate); diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3TlsTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3TlsTests.cs index f3fb01d8e725..6226bf1660c0 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3TlsTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3TlsTests.cs @@ -5,14 +5,11 @@ using System.Net.Http; using System.Net.Quic; using System.Net.Security; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Testing; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Xunit; @@ -26,7 +23,6 @@ public class Http3TlsTests : LoggedTest [MsQuicSupported] public async Task ServerCertificateSelector_Invoked() { - var serverCertificateSelectorActionCalled = false; var builder = CreateHostBuilder(async context => { await context.Response.WriteAsync("Hello World"); @@ -39,7 +35,6 @@ public async Task ServerCertificateSelector_Invoked() { httpsOptions.ServerCertificateSelector = (context, host) => { - serverCertificateSelectorActionCalled = true; Assert.Null(context); // The context isn't available durring the quic handshake. Assert.Equal("testhost", host); return TestResources.GetTestCertificate(); @@ -64,8 +59,6 @@ public async Task ServerCertificateSelector_Invoked() Assert.Equal(HttpVersion.Version30, response.Version); Assert.Equal("Hello World", result); - Assert.True(serverCertificateSelectorActionCalled); - await host.StopAsync().DefaultTimeout(); } @@ -336,93 +329,6 @@ public async Task TlsHandshakeCallbackOptions_Invoked() await host.StopAsync().DefaultTimeout(); } - [ConditionalFact] - [MsQuicSupported] - public async Task LoadDevelopmentCertificateViaConfiguration() - { - var expectedCertificate = new X509Certificate2(TestResources.GetCertPath("aspnetdevcert.pfx"), "testPassword", X509KeyStorageFlags.Exportable); - var bytes = expectedCertificate.Export(X509ContentType.Pkcs12, "1234"); - var path = GetCertificatePath(); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - File.WriteAllBytes(path, bytes); - - var config = new ConfigurationBuilder().AddInMemoryCollection(new[] - { - new KeyValuePair("Certificates:Development:Password", "1234"), - }).Build(); - - var ranConfigureKestrelAction = false; - var ranUseHttpsAction = false; - var hostBuilder = CreateHostBuilder(async context => - { - await context.Response.WriteAsync("Hello World"); - }, configureKestrel: kestrelOptions => - { - ranConfigureKestrelAction = true; - kestrelOptions.Configure(config); - - kestrelOptions.ListenAnyIP(0, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http3; - listenOptions.UseHttps(_ => - { - ranUseHttpsAction = true; - }); - }); - }); - - Assert.False(ranConfigureKestrelAction); - Assert.False(ranUseHttpsAction); - - using var host = hostBuilder.Build(); - await host.StartAsync().DefaultTimeout(); - - Assert.True(ranConfigureKestrelAction); - Assert.True(ranUseHttpsAction); - - var request = new HttpRequestMessage(HttpMethod.Get, $"https://127.0.0.1:{host.GetPort()}/"); - request.Version = HttpVersion.Version30; - request.VersionPolicy = HttpVersionPolicy.RequestVersionExact; - request.Headers.Host = "testhost"; - - var ranCertificateValidation = false; - var httpHandler = new SocketsHttpHandler(); - httpHandler.SslOptions = new SslClientAuthenticationOptions - { - RemoteCertificateValidationCallback = (object _sender, X509Certificate actualCertificate, X509Chain _chain, SslPolicyErrors _sslPolicyErrors) => - { - ranCertificateValidation = true; - Assert.Equal(expectedCertificate.GetSerialNumberString(), actualCertificate.GetSerialNumberString()); - return true; - }, - TargetHost = "targethost", - }; - using var client = new HttpMessageInvoker(httpHandler); - - var response = await client.SendAsync(request, CancellationToken.None).DefaultTimeout(); - response.EnsureSuccessStatusCode(); - var result = await response.Content.ReadAsStringAsync(); - Assert.Equal(HttpVersion.Version30, response.Version); - Assert.Equal("Hello World", result); - - Assert.True(ranCertificateValidation); - - await host.StopAsync().DefaultTimeout(); - } - - /// - /// This is something of a hack - we should actually be calling - /// . - /// - private static string GetCertificatePath() - { - var appData = Environment.GetEnvironmentVariable("APPDATA"); - var home = Environment.GetEnvironmentVariable("HOME"); - var basePath = appData != null ? Path.Combine(appData, "ASP.NET", "https") : null; - basePath = basePath ?? (home != null ? Path.Combine(home, ".aspnet", "https") : null); - return Path.Combine(basePath, $"{typeof(Http3TlsTests).Assembly.GetName().Name}.pfx"); - } - private IHostBuilder CreateHostBuilder(RequestDelegate requestDelegate, HttpProtocols? protocol = null, Action configureKestrel = null) { return HttpHelpers.CreateHostBuilder(AddTestLogging, requestDelegate, protocol, configureKestrel);