diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Https/HttpsConnectionFilter.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Https/HttpsConnectionFilter.cs index 0992d229e5..f8efc1abbb 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Https/HttpsConnectionFilter.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Https/HttpsConnectionFilter.cs @@ -40,7 +40,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https if (string.Equals(context.Address.Scheme, "https", StringComparison.OrdinalIgnoreCase)) { - X509Certificate2 clientCertificate = null; SslStream sslStream; if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { @@ -66,16 +65,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https } } - X509Certificate2 certificate2 = certificate as X509Certificate2; + var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { -#if NETSTANDARD1_3 - // conversion X509Certificate to X509Certificate2 not supported - // https://github.com/dotnet/corefx/issues/4510 return false; -#else - certificate2 = new X509Certificate2(certificate); -#endif } if (_options.ClientCertificateValidation != null) @@ -86,7 +79,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https } } - clientCertificate = certificate2; return true; }); await sslStream.AuthenticateAsServerAsync(_options.ServerCertificate, clientCertificateRequired: true, @@ -98,6 +90,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https { previousPrepareRequest?.Invoke(features); + var clientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); if (clientCertificate != null) { features.Set(new TlsConnectionFeature { ClientCertificate = clientCertificate }); @@ -108,5 +101,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https context.Connection = sslStream; } } + + private X509Certificate2 ConvertToX509Certificate2(X509Certificate certificate) + { + if (certificate == null) + { + return null; + } + + X509Certificate2 certificate2 = certificate as X509Certificate2; + if (certificate2 != null) + { + return certificate2; + } + +#if NETSTANDARD1_3 + // conversion X509Certificate to X509Certificate2 not supported + // https://github.com/dotnet/corefx/issues/4510 + return null; +#else + return new X509Certificate2(certificate); +#endif + } } } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/HttpsConnectionFilterTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/HttpsConnectionFilterTests.cs index c0da2cbc5b..50b89a0416 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/HttpsConnectionFilterTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/HttpsConnectionFilterTests.cs @@ -347,6 +347,69 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { #if NET451 ServicePointManager.ServerCertificateValidationCallback -= validationCallback; +#endif + } + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "This test currently fails on Mono because of an issue with SslStream (https://github.com/aspnet/KestrelHttpServer/issues/240).")] + public async Task CertificatePassedToHttpContextIsNotDisposed() + { + RemoteCertificateValidationCallback validationCallback = + (sender, cert, chain, sslPolicyErrors) => true; + + try + { +#if NET451 + ServicePointManager.ServerCertificateValidationCallback += validationCallback; +#endif + + var serviceContext = new TestServiceContext(new HttpsConnectionFilter( + new HttpsConnectionFilterOptions + { + ServerCertificate = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword"), + ClientCertificateMode = ClientCertificateMode.RequireCertificate, + ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true + }, + new NoOpConnectionFilter()) + ); + + RequestDelegate app = context => + { + var tlsFeature = context.Features.Get(); + Assert.NotNull(tlsFeature); + Assert.NotNull(tlsFeature.ClientCertificate); + Assert.NotNull(context.Connection.ClientCertificate); + Assert.NotNull(context.Connection.ClientCertificate.PublicKey); + return context.Response.WriteAsync("hello world"); + }; + + using (var server = new TestServer(app, serviceContext, "https://localhost:0/")) + { + // SslStream is used to ensure the certificate is actually passed to the server + // HttpClient might not send the certificate because it is invalid or it doesn't match any + // of the certificate authorities sent by the server in the SSL handshake. + using (var client = new TcpClient()) + { + await client.ConnectAsync("127.0.0.1", server.Port); + + var stream = new SslStream(client.GetStream(), false, (sender, certificate, chain, errors) => true, + (sender, host, certificates, certificate, issuers) => new X509Certificate2(@"TestResources/testCert.pfx", "testPassword")); + await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false); + + var request = Encoding.UTF8.GetBytes("GET / HTTP/1.0\r\n\r\n"); + await stream.WriteAsync(request, 0, request.Length); + + var reader = new StreamReader(stream); + var line = await reader.ReadLineAsync(); + Assert.Equal("HTTP/1.1 200 OK", line); + } + } + } + finally + { +#if NET451 + ServicePointManager.ServerCertificateValidationCallback -= validationCallback; #endif } }