diff --git a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs
index 48b6629d0762..f13540fa579c 100644
--- a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs
+++ b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs
@@ -77,12 +77,6 @@ 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/ListenOptionsHttpsExtensions.cs b/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs
index 2ea73a318584..32bd1dd59889 100644
--- a/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs
+++ b/src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs
@@ -166,6 +166,9 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, Action
{
- // Set the list of protocols from listen options
- httpsOptions.HttpProtocols = listenOptions.Protocols;
- var middleware = new HttpsConnectionMiddleware(next, httpsOptions, loggerFactory, metrics);
+ var middleware = new HttpsConnectionMiddleware(next, httpsOptions, listenOptions.Protocols, loggerFactory, metrics);
return middleware.OnConnectionAsync;
});
diff --git a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs
index 3488024f2294..bd51152f3548 100644
--- a/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs
+++ b/src/Servers/Kestrel/Core/src/Middleware/HttpsConnectionMiddleware.cs
@@ -43,17 +43,19 @@ 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;
+
+ // Internal for testing
+ internal readonly HttpProtocols _httpProtocols;
// Pool for cancellation tokens that cancel the handshake
private readonly CancellationTokenSourcePool _ctsPool = new();
- public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, KestrelMetrics metrics)
- : this(next, options, loggerFactory: NullLoggerFactory.Instance, metrics: metrics)
+ public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, HttpProtocols httpProtocols, KestrelMetrics metrics)
+ : this(next, options, httpProtocols, loggerFactory: NullLoggerFactory.Instance, metrics: metrics)
{
}
- public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, ILoggerFactory loggerFactory, KestrelMetrics metrics)
+ public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, HttpProtocols httpProtocols, ILoggerFactory loggerFactory, KestrelMetrics metrics)
{
ArgumentNullException.ThrowIfNull(options);
@@ -74,7 +76,7 @@ public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapter
//_sslStreamFactory = s => new SslStream(s);
_options = options;
- _options.HttpProtocols = ValidateAndNormalizeHttpProtocols(_options.HttpProtocols, _logger);
+ _httpProtocols = ValidateAndNormalizeHttpProtocols(httpProtocols, _logger);
// capture the certificate now so it can't be switched after validation
_serverCertificate = options.ServerCertificate;
@@ -331,7 +333,7 @@ private Task DoOptionsBasedHandshakeAsync(ConnectionContext context, SslStream s
CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
};
- ConfigureAlpn(sslOptions, _options.HttpProtocols);
+ ConfigureAlpn(sslOptions, _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 965f1fe841f3..1979300aa144 100644
--- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs
+++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs
@@ -402,7 +402,156 @@ public void ConfigureEndpoint_RecoverFromBadPassword()
void CheckListenOptions(X509Certificate2 expectedCert)
{
var listenOptions = Assert.Single(serverOptions.ConfigurationBackedListenOptions);
- Assert.Equal(expectedCert.SerialNumber, listenOptions.HttpsOptions.ServerCertificate.SerialNumber);
+ Assert.Equal(expectedCert.SerialNumber, listenOptions.HttpsOptions!.ServerCertificate.SerialNumber);
+ }
+ }
+
+ [Fact]
+ public void LoadDevelopmentCertificate_LoadBeforeUseHttps()
+ {
+ 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.ConfigurationLoader.DefaultCertificate);
+
+ serverOptions.ConfigurationLoader.Load();
+
+ Assert.NotNull(serverOptions.ConfigurationLoader.DefaultCertificate);
+ Assert.Equal(serverOptions.ConfigurationLoader.DefaultCertificate.SerialNumber, certificate.SerialNumber);
+
+ var ran1 = false;
+ serverOptions.ListenAnyIP(4545, listenOptions =>
+ {
+ ran1 = true;
+ listenOptions.UseHttps();
+ });
+ Assert.True(ran1);
+
+ var listenOptions = serverOptions.CodeBackedListenOptions.Single();
+ listenOptions.Build();
+ Assert.Equal(listenOptions.HttpsOptions.ServerCertificate?.SerialNumber, certificate.SerialNumber);
+ }
+ finally
+ {
+ if (File.Exists(GetCertificatePath()))
+ {
+ File.Delete(GetCertificatePath());
+ }
+ }
+ }
+
+ [Fact]
+ public void LoadDevelopmentCertificate_UseHttpsBeforeLoad()
+ {
+ 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.ConfigurationLoader.DefaultCertificate);
+
+ var ran1 = false;
+ serverOptions.ListenAnyIP(4545, listenOptions =>
+ {
+ ran1 = true;
+ listenOptions.UseHttps();
+ });
+ Assert.True(ran1);
+
+ // Use Https triggers a load, so the default cert is already set
+ Assert.NotNull(serverOptions.ConfigurationLoader.DefaultCertificate);
+ Assert.Equal(serverOptions.ConfigurationLoader.DefaultCertificate.SerialNumber, certificate.SerialNumber);
+
+ // This Load is a no-op (tested elsewhere)
+ serverOptions.ConfigurationLoader.Load();
+
+ var listenOptions = serverOptions.CodeBackedListenOptions.Single();
+ listenOptions.Build();
+ Assert.Equal(listenOptions.HttpsOptions.ServerCertificate?.SerialNumber, certificate.SerialNumber);
+ }
+ finally
+ {
+ if (File.Exists(GetCertificatePath()))
+ {
+ File.Delete(GetCertificatePath());
+ }
+ }
+ }
+
+ [Fact]
+ public void LoadDevelopmentCertificate_UseHttpsBeforeConfigure()
+ {
+ 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 defaultCertificate = TestResources.GetTestCertificate();
+ Assert.NotEqual(certificate.SerialNumber, defaultCertificate.SerialNumber);
+ serverOptions.TestOverrideDefaultCertificate = defaultCertificate;
+
+ 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.ConfigurationLoader.DefaultCertificate);
+
+ serverOptions.ConfigurationLoader.Load();
+
+ Assert.NotNull(serverOptions.ConfigurationLoader.DefaultCertificate);
+ Assert.Equal(serverOptions.ConfigurationLoader.DefaultCertificate.SerialNumber, certificate.SerialNumber);
+
+ var listenOptions = serverOptions.CodeBackedListenOptions.Single();
+ listenOptions.Build();
+ // In a perfect world, it would match certificate.SerialNumber, but there's no way for an eager UseHttps
+ // to do that before Configure is called.
+ Assert.Equal(listenOptions.HttpsOptions.ServerCertificate?.SerialNumber, defaultCertificate.SerialNumber);
+ }
+ finally
+ {
+ if (File.Exists(GetCertificatePath()))
+ {
+ File.Delete(GetCertificatePath());
+ }
}
}
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs
index f51b31631c4a..3362f78150ea 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs
@@ -264,7 +264,7 @@ void ConfigureListenOptions(ListenOptions listenOptions)
[Fact]
public void ThrowsWhenNoServerCertificateIsProvided()
{
- Assert.Throws(() => CreateMiddleware(new HttpsConnectionAdapterOptions()));
+ Assert.Throws(() => CreateMiddleware(new HttpsConnectionAdapterOptions(), ListenOptions.DefaultHttpProtocols));
}
[Fact]
@@ -1318,7 +1318,8 @@ public void ValidatesEnhancedKeyUsageOnCertificate(string testCertName)
CreateMiddleware(new HttpsConnectionAdapterOptions
{
ServerCertificate = cert,
- });
+ },
+ ListenOptions.DefaultHttpProtocols);
}
[Theory]
@@ -1337,7 +1338,8 @@ public void ThrowsForCertificatesMissingServerEku(string testCertName)
CreateMiddleware(new HttpsConnectionAdapterOptions
{
ServerCertificate = cert,
- }));
+ },
+ ListenOptions.DefaultHttpProtocols));
Assert.Equal(CoreStrings.FormatInvalidServerCertificateEku(cert.Thumbprint), ex.Message);
}
@@ -1357,6 +1359,7 @@ public void LogsForCertificateMissingSubjectAlternativeName(string testCertName)
{
ServerCertificate = cert,
},
+ ListenOptions.DefaultHttpProtocols,
testLogger);
Assert.Single(testLogger.Messages.Where(log => log.EventId == 9));
@@ -1404,11 +1407,10 @@ public void Http1AndHttp2DowngradeToHttp1ForHttpsOnIncompatibleWindowsVersions()
var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
{
ServerCertificate = _x509Certificate2,
- HttpProtocols = HttpProtocols.Http1AndHttp2
};
- CreateMiddleware(httpConnectionAdapterOptions);
+ var middleware = CreateMiddleware(httpConnectionAdapterOptions, HttpProtocols.Http1AndHttp2);
- Assert.Equal(HttpProtocols.Http1, httpConnectionAdapterOptions.HttpProtocols);
+ Assert.Equal(HttpProtocols.Http1, middleware._httpProtocols);
}
[ConditionalFact]
@@ -1419,11 +1421,10 @@ public void Http1AndHttp2DoesNotDowngradeOnCompatibleWindowsVersions()
var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
{
ServerCertificate = _x509Certificate2,
- HttpProtocols = HttpProtocols.Http1AndHttp2
};
- CreateMiddleware(httpConnectionAdapterOptions);
+ var middleware = CreateMiddleware(httpConnectionAdapterOptions, HttpProtocols.Http1AndHttp2);
- Assert.Equal(HttpProtocols.Http1AndHttp2, httpConnectionAdapterOptions.HttpProtocols);
+ Assert.Equal(HttpProtocols.Http1AndHttp2, middleware._httpProtocols);
}
[ConditionalFact]
@@ -1434,10 +1435,9 @@ public void Http2ThrowsOnIncompatibleWindowsVersions()
var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
{
ServerCertificate = _x509Certificate2,
- HttpProtocols = HttpProtocols.Http2
};
- Assert.Throws(() => CreateMiddleware(httpConnectionAdapterOptions));
+ Assert.Throws(() => CreateMiddleware(httpConnectionAdapterOptions, HttpProtocols.Http2));
}
[ConditionalFact]
@@ -1448,11 +1448,10 @@ public void Http2DoesNotThrowOnCompatibleWindowsVersions()
var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
{
ServerCertificate = _x509Certificate2,
- HttpProtocols = HttpProtocols.Http2
};
// Does not throw
- CreateMiddleware(httpConnectionAdapterOptions);
+ CreateMiddleware(httpConnectionAdapterOptions, HttpProtocols.Http2);
}
private static HttpsConnectionMiddleware CreateMiddleware(X509Certificate2 serverCertificate)
@@ -1460,18 +1459,19 @@ private static HttpsConnectionMiddleware CreateMiddleware(X509Certificate2 serve
return CreateMiddleware(new HttpsConnectionAdapterOptions
{
ServerCertificate = serverCertificate,
- });
+ },
+ ListenOptions.DefaultHttpProtocols);
}
- private static HttpsConnectionMiddleware CreateMiddleware(HttpsConnectionAdapterOptions options, TestApplicationErrorLogger testLogger = null)
+ private static HttpsConnectionMiddleware CreateMiddleware(HttpsConnectionAdapterOptions options, HttpProtocols httpProtocols, TestApplicationErrorLogger testLogger = null)
{
var loggerFactory = testLogger is null ? (ILoggerFactory)NullLoggerFactory.Instance : new LoggerFactory(new[] { new KestrelTestLoggerProvider(testLogger) });
- return new HttpsConnectionMiddleware(context => Task.CompletedTask, options, loggerFactory, new KestrelMetrics(new TestMeterFactory()));
+ return new HttpsConnectionMiddleware(context => Task.CompletedTask, options, httpProtocols, loggerFactory, new KestrelMetrics(new TestMeterFactory()));
}
- private static HttpsConnectionMiddleware CreateMiddleware(HttpsConnectionAdapterOptions options)
+ private static HttpsConnectionMiddleware CreateMiddleware(HttpsConnectionAdapterOptions options, HttpProtocols httpProtocols)
{
- return new HttpsConnectionMiddleware(context => Task.CompletedTask, options, new KestrelMetrics(new TestMeterFactory()));
+ return new HttpsConnectionMiddleware(context => Task.CompletedTask, options, httpProtocols, new KestrelMetrics(new TestMeterFactory()));
}
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 819def76e5a2..f0f223373bf4 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs
@@ -64,14 +64,18 @@ public void UseHttpsDefaultsToDefaultCert()
Assert.False(serverOptions.IsDevelopmentCertificateLoaded);
+ var ranUseHttpsAction = false;
serverOptions.ListenLocalhost(5001, options =>
{
options.UseHttps(opt =>
{
// The default cert is applied after UseHttps.
Assert.Null(opt.ServerCertificate);
+ ranUseHttpsAction = true;
});
});
+
+ Assert.True(ranUseHttpsAction);
Assert.False(serverOptions.IsDevelopmentCertificateLoaded);
}
@@ -117,14 +121,19 @@ 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;
});
});
+
+ Assert.True(ranUseHttpsAction);
+
// Never lazy loaded
Assert.False(serverOptions.IsDevelopmentCertificateLoaded);
Assert.Null(serverOptions.DevelopmentCertificate);
@@ -144,6 +153,7 @@ public void ConfigureCertSelectorNeverLoadsDefaultCert()
};
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
+ var ranUseHttpsAction = false;
serverOptions.ListenLocalhost(5000, options =>
{
options.UseHttps(opt =>
@@ -151,8 +161,12 @@ public void ConfigureCertSelectorNeverLoadsDefaultCert()
Assert.Null(opt.ServerCertificate);
Assert.NotNull(opt.ServerCertificateSelector);
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode);
+ ranUseHttpsAction = true;
});
});
+
+ Assert.True(ranUseHttpsAction);
+
// Never lazy loaded
Assert.False(serverOptions.IsDevelopmentCertificateLoaded);
Assert.Null(serverOptions.DevelopmentCertificate);
diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3TlsTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3TlsTests.cs
index 8efd8e2789bd..a03253369e84 100644
--- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3TlsTests.cs
+++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3TlsTests.cs
@@ -5,6 +5,7 @@
using System.Net.Http;
using System.Net.Quic;
using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
@@ -25,6 +26,7 @@ public class Http3TlsTests : LoggedTest
[MsQuicSupported]
public async Task ServerCertificateSelector_Invoked()
{
+ var serverCertificateSelectorActionCalled = false;
var builder = CreateHostBuilder(async context =>
{
await context.Response.WriteAsync("Hello World");
@@ -37,6 +39,7 @@ 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();
@@ -61,6 +64,8 @@ public async Task ServerCertificateSelector_Invoked()
Assert.Equal(HttpVersion.Version30, response.Version);
Assert.Equal("Hello World", result);
+ Assert.True(serverCertificateSelectorActionCalled);
+
await host.StopAsync().DefaultTimeout();
}
@@ -422,6 +427,93 @@ public void UseKestrelCore_ConfigurationBased(bool useQuic)
Assert.Throws(host.Run);
}
+ [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);