diff --git a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs index af52396a20..8aa67fc8e9 100644 --- a/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs +++ b/src/Servers/Kestrel/Core/ref/Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs @@ -301,6 +301,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https public Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode ClientCertificateMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Func ClientCertificateValidation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.TimeSpan HandshakeTimeout { get { throw null; } set { } } + public System.Action OnAuthenticate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Security.Cryptography.X509Certificates.X509Certificate2 ServerCertificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Func ServerCertificateSelector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Security.Authentication.SslProtocols SslProtocols { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } diff --git a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs index b4b075209b..baf7785773 100644 --- a/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs +++ b/src/Servers/Kestrel/Core/src/HttpsConnectionAdapterOptions.cs @@ -75,6 +75,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https /// public bool CheckCertificateRevocation { get; set; } + /// + /// Provides direct configuration of the on a per-connection basis. + /// This is called after all of the other settings have already been applied. + /// + public Action OnAuthenticate { get; set; } + /// /// Specifies the maximum amount of time allowed for the TLS/SSL handshake. This must be positive and finite. /// @@ -90,8 +96,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https _handshakeTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue; } } - - // For testing - internal Action OnHandshakeStarted; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionAdapter.cs b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionAdapter.cs index 7b43cb37ea..4f42d53c5d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionAdapter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpsConnectionAdapter.cs @@ -127,8 +127,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal var timeoutFeature = context.Features.Get(); timeoutFeature.SetTimeout(_options.HandshakeTimeout); - _options.OnHandshakeStarted?.Invoke(); - try { // Adapt to the SslStream signature @@ -170,6 +168,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); } + _options.OnAuthenticate?.Invoke(context.ConnectionContext, sslOptions); + await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); } catch (OperationCanceledException) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs index efd1d56722..30847af463 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs @@ -326,7 +326,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests listenOptions.UseHttps(o => { o.ServerCertificate = new X509Certificate2(TestResources.GetTestCertificate()); - o.OnHandshakeStarted = () => handshakeStartedTcs.SetResult(null); + o.OnAuthenticate = (_, __) => handshakeStartedTcs.SetResult(null); handshakeTimeout = o.HandshakeTimeout; }); @@ -380,6 +380,81 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel); } + [Fact] + public async Task OnAuthenticate_SeesOtherSettings() + { + var loggerProvider = new HandshakeErrorLoggerProvider(); + LoggerFactory.AddProvider(loggerProvider); + + var testCert = TestResources.GetTestCertificate(); + var onAuthenticateCalled = false; + + await using (var server = new TestServer(context => Task.CompletedTask, + new TestServiceContext(LoggerFactory), + listenOptions => + { + listenOptions.UseHttps(httpsOptions => + { + httpsOptions.ServerCertificate = testCert; + httpsOptions.OnAuthenticate = (connectionContext, authOptions) => + { + Assert.Same(testCert, authOptions.ServerCertificate); + onAuthenticateCalled = true; + }; + }); + })) + { + using (var connection = server.CreateConnection()) + using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true)) + { + await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null, + enabledSslProtocols: SslProtocols.None, + checkCertificateRevocation: false); + } + } + + Assert.True(onAuthenticateCalled, "onAuthenticateCalled"); + } + + [Fact] + public async Task OnAuthenticate_CanSetSettings() + { + var loggerProvider = new HandshakeErrorLoggerProvider(); + LoggerFactory.AddProvider(loggerProvider); + + var testCert = TestResources.GetTestCertificate(); + var onAuthenticateCalled = false; + + await using (var server = new TestServer(context => Task.CompletedTask, + new TestServiceContext(LoggerFactory), + listenOptions => + { + listenOptions.UseHttps(httpsOptions => + { + httpsOptions.ServerCertificateSelector = (_, __) => throw new NotImplementedException(); + httpsOptions.OnAuthenticate = (connectionContext, authOptions) => + { + Assert.Null(authOptions.ServerCertificate); + Assert.NotNull(authOptions.ServerCertificateSelectionCallback); + authOptions.ServerCertificate = testCert; + authOptions.ServerCertificateSelectionCallback = null; + onAuthenticateCalled = true; + }; + }); + })) + { + using (var connection = server.CreateConnection()) + using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true)) + { + await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null, + enabledSslProtocols: SslProtocols.None, + checkCertificateRevocation: false); + } + } + + Assert.True(onAuthenticateCalled, "onAuthenticateCalled"); + } + private class HandshakeErrorLoggerProvider : ILoggerProvider { public HttpsConnectionFilterLogger FilterLogger { get; } = new HttpsConnectionFilterLogger();