Skip to content

Commit 82feb68

Browse files
authored
Don't close connection on MacOS when getting client certificate (#46365)
1 parent f0af112 commit 82feb68

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

src/Servers/Kestrel/Core/src/Internal/TlsConnectionFeature.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ public int KeyExchangeStrength
129129
await _sslStream.NegotiateClientCertificateAsync(cancellationToken);
130130
#pragma warning restore CA1416 // Validate platform compatibility
131131
}
132+
catch (PlatformNotSupportedException)
133+
{
134+
// NegotiateClientCertificateAsync might not be supported on all platforms.
135+
// Don't attempt to recover by creating a new connection. Instead, just throw error directly to the app.
136+
throw;
137+
}
132138
catch
133139
{
134140
// We can't tell which exceptions are fatal or recoverable. Consider them all recoverable only given a new connection

src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,49 @@ void ConfigureListenOptions(ListenOptions listenOptions)
570570
await AssertConnectionResult(stream, true);
571571
}
572572

573+
[ConditionalFact]
574+
[TlsAlpnSupported]
575+
[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.Linux, SkipReason = "MacOS only test.")]
576+
public async Task CanRenegotiateForClientCertificate_MacOS_PlatformNotSupportedException()
577+
{
578+
void ConfigureListenOptions(ListenOptions listenOptions)
579+
{
580+
listenOptions.Protocols = HttpProtocols.Http1;
581+
listenOptions.UseHttps(options =>
582+
{
583+
options.ServerCertificate = _x509Certificate2;
584+
options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
585+
options.AllowAnyClientCertificate();
586+
});
587+
}
588+
589+
await using var server = new TestServer(async context =>
590+
{
591+
var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
592+
Assert.NotNull(tlsFeature);
593+
Assert.Null(tlsFeature.ClientCertificate);
594+
Assert.Null(context.Connection.ClientCertificate);
595+
596+
await Assert.ThrowsAsync<PlatformNotSupportedException>(() => context.Connection.GetClientCertificateAsync());
597+
598+
var lifetimeNotificationFeature = context.Features.Get<IConnectionLifetimeNotificationFeature>();
599+
Assert.False(
600+
lifetimeNotificationFeature.ConnectionClosedRequested.IsCancellationRequested,
601+
"GetClientCertificateAsync shouldn't cause the connection to be closed.");
602+
603+
await context.Response.WriteAsync("hello world");
604+
}, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
605+
606+
using var connection = server.CreateConnection();
607+
// SslStream is used to ensure the certificate is actually passed to the server
608+
// HttpClient might not send the certificate because it is invalid or it doesn't match any
609+
// of the certificate authorities sent by the server in the SSL handshake.
610+
// Use a random host name to avoid the TLS session resumption cache.
611+
var stream = OpenSslStreamWithCert(connection.Stream);
612+
await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
613+
await AssertConnectionResult(stream, true);
614+
}
615+
573616
[Fact]
574617
public async Task Renegotiate_ServerOptionsSelectionCallback_NotSupported()
575618
{

0 commit comments

Comments
 (0)