diff --git a/src/Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs b/src/Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs index eb65aac25..e73eccb9d 100644 --- a/src/Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs +++ b/src/Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.IO.Pipelines; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; @@ -12,16 +13,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal { - public class AdaptedPipeline : IDuplexPipe + public class AdaptedPipeline : IDuplexPipe, IDisposable { private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2; private readonly IDuplexPipe _transport; - public AdaptedPipeline(IDuplexPipe transport, - Pipe inputPipe, + public AdaptedPipeline(Pipe inputPipe, Pipe outputPipe, - IKestrelTrace log) + ILogger log, + IDuplexPipe transport) { _transport = transport; Input = inputPipe; @@ -33,7 +34,7 @@ public AdaptedPipeline(IDuplexPipe transport, public Pipe Output { get; } - public IKestrelTrace Log { get; } + public ILogger Log { get; } PipeReader IDuplexPipe.Input => Input.Reader; @@ -64,6 +65,11 @@ private async Task WriteOutputAsync(Stream stream) try { + if (result.IsCanceled) + { + break; + } + if (buffer.IsEmpty) { if (result.IsCompleted) @@ -111,7 +117,11 @@ private async Task WriteOutputAsync(Stream stream) finally { Output.Reader.Complete(); + _transport.Output.Complete(); + + // Cancel any pending flushes due to back-pressure + Input.Writer.CancelPendingFlush(); } } @@ -149,7 +159,7 @@ private async Task ReadInputAsync(Stream stream) var result = await Input.Writer.FlushAsync(); - if (result.IsCompleted) + if (result.IsCompleted || result.IsCanceled) { break; } @@ -163,10 +173,20 @@ private async Task ReadInputAsync(Stream stream) finally { Input.Writer.Complete(error); + // The application could have ended the input pipe so complete // the transport pipe as well _transport.Input.Complete(); + + // Cancel any pending reads from the application + Output.Reader.CancelPendingRead(); } } + + public void Dispose() + { + Input.Reader.Complete(); + Output.Writer.Complete(); + } } } diff --git a/src/Kestrel.Core/Adapter/Internal/RawStream.cs b/src/Kestrel.Core/Adapter/Internal/RawStream.cs index 137b87ea0..aab0c7296 100644 --- a/src/Kestrel.Core/Adapter/Internal/RawStream.cs +++ b/src/Kestrel.Core/Adapter/Internal/RawStream.cs @@ -61,18 +61,18 @@ public override int Read(byte[] buffer, int offset, int count) { // ValueTask uses .GetAwaiter().GetResult() if necessary // https://github.com/dotnet/corefx/blob/f9da3b4af08214764a51b2331f3595ffaf162abe/src/System.Threading.Tasks.Extensions/src/System/Threading/Tasks/ValueTask.cs#L156 - return ReadAsyncInternal(new Memory(buffer, offset, count)).Result; + return ReadAsyncInternal(new Memory(buffer, offset, count), default).Result; } public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - return ReadAsyncInternal(new Memory(buffer, offset, count)).AsTask(); + return ReadAsyncInternal(new Memory(buffer, offset, count), cancellationToken).AsTask(); } #if NETCOREAPP2_1 public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) { - return ReadAsyncInternal(destination); + return ReadAsyncInternal(destination, cancellationToken); } #elif NETSTANDARD2_0 #else @@ -115,11 +115,11 @@ public override Task FlushAsync(CancellationToken cancellationToken) return WriteAsync(null, 0, 0, cancellationToken); } - private async ValueTask ReadAsyncInternal(Memory destination) + private async ValueTask ReadAsyncInternal(Memory destination, CancellationToken cancellationToken) { while (true) { - var result = await _input.ReadAsync(); + var result = await _input.ReadAsync(cancellationToken); var readableBuffer = result.Buffer; try { diff --git a/src/Kestrel.Core/HttpsConnectionAdapterOptions.cs b/src/Kestrel.Core/HttpsConnectionAdapterOptions.cs index 2e2059f96..ab7e472e3 100644 --- a/src/Kestrel.Core/HttpsConnectionAdapterOptions.cs +++ b/src/Kestrel.Core/HttpsConnectionAdapterOptions.cs @@ -2,12 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO.Pipelines; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Https { @@ -75,6 +77,12 @@ public HttpsConnectionAdapterOptions() /// public bool CheckCertificateRevocation { get; set; } + internal PipeScheduler Scheduler { get; set; } = PipeScheduler.ThreadPool; + + internal long? MaxInputBufferSize { get; set; } + + internal long? MaxOutputBufferSize { get; set; } + /// /// Specifies the maximum amount of time allowed for the TLS/SSL handshake. This must be positive and finite. /// diff --git a/src/Kestrel.Core/Internal/AddressBinder.cs b/src/Kestrel.Core/Internal/AddressBinder.cs index 67ff4f110..5228d3335 100644 --- a/src/Kestrel.Core/Internal/AddressBinder.cs +++ b/src/Kestrel.Core/Internal/AddressBinder.cs @@ -170,8 +170,7 @@ public async Task BindAsync(AddressBindContext context) var httpsDefault = ParseAddress(Constants.DefaultServerHttpsAddress, out https); context.ServerOptions.ApplyEndpointDefaults(httpsDefault); - if (httpsDefault.ConnectionAdapters.Any(f => f.IsHttps) - || httpsDefault.TryUseHttps()) + if (httpsDefault.IsTls || httpsDefault.TryUseHttps()) { await httpsDefault.BindAsync(context).ConfigureAwait(false); context.Logger.LogDebug(CoreStrings.BindingToDefaultAddresses, @@ -254,7 +253,7 @@ public virtual async Task BindAsync(AddressBindContext context) var options = ParseAddress(address, out var https); context.ServerOptions.ApplyEndpointDefaults(options); - if (https && !options.ConnectionAdapters.Any(f => f.IsHttps)) + if (https && !options.IsTls) { options.UseHttps(); } diff --git a/src/Kestrel.Core/Internal/Http/Http1Connection.cs b/src/Kestrel.Core/Internal/Http/Http1Connection.cs index 5a4e1d68b..ec68873dc 100644 --- a/src/Kestrel.Core/Internal/Http/Http1Connection.cs +++ b/src/Kestrel.Core/Internal/Http/Http1Connection.cs @@ -71,7 +71,7 @@ public void Abort(ConnectionAbortedException abortReason) } // Abort output prior to calling OnIOCompleted() to give the transport the chance to complete the input - // with the correct error and message. + // with the correct error and message. Output.Abort(abortReason); OnInputOrOutputCompleted(); diff --git a/src/Kestrel.Core/Internal/HttpConnection.cs b/src/Kestrel.Core/Internal/HttpConnection.cs index d860d36ba..5f3d959bf 100644 --- a/src/Kestrel.Core/Internal/HttpConnection.cs +++ b/src/Kestrel.Core/Internal/HttpConnection.cs @@ -110,10 +110,10 @@ public async Task ProcessRequestsAsync(IHttpApplication http if (_context.ConnectionAdapters.Count > 0) { - adaptedPipeline = new AdaptedPipeline(_adaptedTransport, - new Pipe(AdaptedInputPipeOptions), + adaptedPipeline = new AdaptedPipeline(new Pipe(AdaptedInputPipeOptions), new Pipe(AdaptedOutputPipeOptions), - Log); + Log, + _adaptedTransport); _adaptedTransport = adaptedPipeline; } @@ -639,8 +639,12 @@ private void CloseUninitializedConnection(ConnectionAbortedException abortReason _context.ConnectionContext.Abort(abortReason); - _adaptedTransport.Input.Complete(); - _adaptedTransport.Output.Complete(); + // Back compat + if (_context.ConnectionAdapters.Count > 0) + { + _adaptedTransport.Input.Complete(); + _adaptedTransport.Output.Complete(); + } } private enum ProtocolSelectionState diff --git a/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs b/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs index 83bca0899..53d0fdb8c 100644 --- a/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs +++ b/src/Kestrel.Core/Internal/HttpConnectionMiddleware.cs @@ -33,8 +33,6 @@ public HttpConnectionMiddleware(IList adapters, ServiceConte public Task OnConnectionAsync(ConnectionContext connectionContext) { - // We need the transport feature so that we can cancel the output reader that the transport is using - // This is a bit of a hack but it preserves the existing semantics var memoryPoolFeature = connectionContext.Features.Get(); var httpConnectionContext = new HttpConnectionContext diff --git a/src/Kestrel.Core/Internal/HttpsConnectionMiddleware.cs b/src/Kestrel.Core/Internal/HttpsConnectionMiddleware.cs new file mode 100644 index 000000000..8439c6cfb --- /dev/null +++ b/src/Kestrel.Core/Internal/HttpsConnectionMiddleware.cs @@ -0,0 +1,291 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal +{ + public class HttpsConnectionMiddleware + { + private readonly ConnectionDelegate _next; + private readonly HttpsConnectionAdapterOptions _options; + private readonly X509Certificate2 _serverCertificate; + private readonly Func _serverCertificateSelector; + + private readonly ILogger _logger; + + public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options) + : this(next, options, loggerFactory: null) + { + } + + public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, ILoggerFactory loggerFactory) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + _next = next; + // capture the certificate now so it can't be switched after validation + _serverCertificate = options.ServerCertificate; + _serverCertificateSelector = options.ServerCertificateSelector; + if (_serverCertificate == null && _serverCertificateSelector == null) + { + throw new ArgumentException(CoreStrings.ServerCertificateRequired, nameof(options)); + } + + // If a selector is provided then ignore the cert, it may be a default cert. + if (_serverCertificateSelector != null) + { + // SslStream doesn't allow both. + _serverCertificate = null; + } + else + { + EnsureCertificateIsAllowedForServerAuth(_serverCertificate); + } + + _options = options; + _logger = loggerFactory?.CreateLogger(nameof(HttpsConnectionMiddleware)); + } + + public async Task OnConnectionAsync(ConnectionContext connectionContext) + { + SslStream sslStream; + bool certificateRequired; + var feature = new TlsConnectionFeature(); + connectionContext.Features.Set(feature); + connectionContext.Features.Set(feature); + + var transportStream = new RawStream(connectionContext.Transport.Input, connectionContext.Transport.Output); + + if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) + { + sslStream = new SslStream(transportStream); + certificateRequired = false; + } + else + { + sslStream = new SslStream(transportStream, + leaveInnerStreamOpen: false, + userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => + { + if (certificate == null) + { + return _options.ClientCertificateMode != ClientCertificateMode.RequireCertificate; + } + + if (_options.ClientCertificateValidation == null) + { + if (sslPolicyErrors != SslPolicyErrors.None) + { + return false; + } + } + + var certificate2 = ConvertToX509Certificate2(certificate); + if (certificate2 == null) + { + return false; + } + + if (_options.ClientCertificateValidation != null) + { + if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors)) + { + return false; + } + } + + return true; + }); + + certificateRequired = true; + } + + using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) + using (cancellationTokeSource.Token.Register(state => ((ConnectionContext)state).Abort(), connectionContext)) + { + _options.OnHandshakeStarted?.Invoke(); + + try + { +#if NETCOREAPP2_1 + // Adapt to the SslStream signature + ServerCertificateSelectionCallback selector = null; + if (_serverCertificateSelector != null) + { + selector = (sender, name) => + { + connectionContext.Features.Set(sslStream); + var cert = _serverCertificateSelector(connectionContext, name); + if (cert != null) + { + EnsureCertificateIsAllowedForServerAuth(cert); + } + return cert; + }; + } + + var sslOptions = new SslServerAuthenticationOptions() + { + ServerCertificate = _serverCertificate, + ServerCertificateSelectionCallback = selector, + ClientCertificateRequired = certificateRequired, + EnabledSslProtocols = _options.SslProtocols, + CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, + ApplicationProtocols = new List() + }; + + // This is order sensitive + if ((_options.HttpProtocols & HttpProtocols.Http2) != 0) + { + sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); + // https://tools.ietf.org/html/rfc7540#section-9.2.1 + sslOptions.AllowRenegotiation = false; + } + + if ((_options.HttpProtocols & HttpProtocols.Http1) != 0) + { + sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); + } + + await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); +#elif NETSTANDARD2_0 // No ALPN support + var serverCert = _serverCertificate; + if (_serverCertificateSelector != null) + { + connectionContext.Features.Set(sslStream); + serverCert = _serverCertificateSelector(connectionContext, null); + if (serverCert != null) + { + EnsureCertificateIsAllowedForServerAuth(serverCert); + } + } + await sslStream.AuthenticateAsServerAsync(serverCert, certificateRequired, + _options.SslProtocols, _options.CheckCertificateRevocation); +#else +#error TFMs need to be updated +#endif + } + catch (OperationCanceledException) + { + _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); + sslStream.Dispose(); + return; + } + catch (Exception ex) when (ex is IOException || ex is AuthenticationException) + { + _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); + sslStream.Dispose(); + return; + } + } + +#if NETCOREAPP2_1 + feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; + connectionContext.Features.Set(feature); +#elif NETSTANDARD2_0 // No ALPN support +#else +#error TFMs need to be updated +#endif + feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); + feature.CipherAlgorithm = sslStream.CipherAlgorithm; + feature.CipherStrength = sslStream.CipherStrength; + feature.HashAlgorithm = sslStream.HashAlgorithm; + feature.HashStrength = sslStream.HashStrength; + feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; + feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; + feature.Protocol = sslStream.SslProtocol; + + var memoryPoolFeature = connectionContext.Features.Get(); + + var inputPipeOptions = new PipeOptions + ( + pool: memoryPoolFeature.MemoryPool, + readerScheduler: _options.Scheduler, + writerScheduler: PipeScheduler.Inline, + pauseWriterThreshold: _options.MaxInputBufferSize ?? 0, + resumeWriterThreshold: _options.MaxInputBufferSize ?? 0, + useSynchronizationContext: false, + minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + ); + + var outputPipeOptions = new PipeOptions + ( + pool: memoryPoolFeature.MemoryPool, + readerScheduler: PipeScheduler.Inline, + writerScheduler: PipeScheduler.Inline, + pauseWriterThreshold: _options.MaxOutputBufferSize ?? 0, + resumeWriterThreshold: _options.MaxOutputBufferSize ?? 0, + useSynchronizationContext: false, + minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize + ); + + var original = connectionContext.Transport; + + try + { + var adaptedPipeline = new AdaptedPipeline(new Pipe(inputPipeOptions), new Pipe(outputPipeOptions), _logger, original); + connectionContext.Transport = adaptedPipeline; + + using (adaptedPipeline) + using (sslStream) + { + var task = adaptedPipeline.RunAsync(sslStream); + + await _next(connectionContext); + + await task; + } + } + finally + { + // Restore the original so that it gets closed appropriately + connectionContext.Transport = original; + } + } + + private static void EnsureCertificateIsAllowedForServerAuth(X509Certificate2 certificate) + { + if (!CertificateLoader.IsCertificateAllowedForServerAuth(certificate)) + { + throw new InvalidOperationException(CoreStrings.FormatInvalidServerCertificateEku(certificate.Thumbprint)); + } + } + + private static X509Certificate2 ConvertToX509Certificate2(X509Certificate certificate) + { + if (certificate == null) + { + return null; + } + + if (certificate is X509Certificate2 cert2) + { + return cert2; + } + + return new X509Certificate2(certificate); + } + } +} diff --git a/src/Kestrel.Core/KestrelConfigurationLoader.cs b/src/Kestrel.Core/KestrelConfigurationLoader.cs index 60204443f..6d06cce41 100644 --- a/src/Kestrel.Core/KestrelConfigurationLoader.cs +++ b/src/Kestrel.Core/KestrelConfigurationLoader.cs @@ -237,7 +237,7 @@ public void Load() } // EndpointDefaults or configureEndpoint may have added an https adapter. - if (https && !listenOptions.ConnectionAdapters.Any(f => f.IsHttps)) + if (https && !listenOptions.IsTls) { if (httpsOptions.ServerCertificate == null && httpsOptions.ServerCertificateSelector == null) { diff --git a/src/Kestrel.Core/KestrelServer.cs b/src/Kestrel.Core/KestrelServer.cs index e90188a6e..c2ddf9674 100644 --- a/src/Kestrel.Core/KestrelServer.cs +++ b/src/Kestrel.Core/KestrelServer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO.Pipelines; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; @@ -141,6 +142,11 @@ public async Task StartAsync(IHttpApplication application, C async Task OnBind(ListenOptions endpoint) { + if (endpoint.ConnectionAdapters.Any(c => c.IsHttps)) + { + endpoint.IsTls = true; + } + // Add the HTTP middleware as the terminal connection middleware endpoint.UseHttpServer(endpoint.ConnectionAdapters, ServiceContext, application, endpoint.Protocols); diff --git a/src/Kestrel.Core/ListenOptions.cs b/src/Kestrel.Core/ListenOptions.cs index b3e4ec21c..7da77d2b2 100644 --- a/src/Kestrel.Core/ListenOptions.cs +++ b/src/Kestrel.Core/ListenOptions.cs @@ -123,6 +123,22 @@ public FileHandleType HandleType /// public bool NoDelay { get; set; } = true; + internal string Scheme + { + get + { + if (IsHttp) + { + return IsTls ? "https" : "http"; + } + return "tcp"; + } + } + + public bool IsTls { get; set; } + + public bool IsHttp { get; set; } = true; + /// /// The protocols enabled on this endpoint. /// @@ -149,18 +165,14 @@ public FileHandleType HandleType /// internal virtual string GetDisplayName() { - var scheme = ConnectionAdapters.Any(f => f.IsHttps) - ? "https" - : "http"; - switch (Type) { case ListenType.IPEndPoint: - return $"{scheme}://{IPEndPoint}"; + return $"{Scheme}://{IPEndPoint}"; case ListenType.SocketPath: - return $"{scheme}://unix:{SocketPath}"; + return $"{Scheme}://unix:{SocketPath}"; case ListenType.FileHandle: - return $"{scheme}://"; + return $"{Scheme}://"; default: throw new InvalidOperationException(); } diff --git a/src/Kestrel.Core/ListenOptionsHttpsExtensions.cs b/src/Kestrel.Core/ListenOptionsHttpsExtensions.cs index 80c6eda3d..09232e129 100644 --- a/src/Kestrel.Core/ListenOptionsHttpsExtensions.cs +++ b/src/Kestrel.Core/ListenOptionsHttpsExtensions.cs @@ -3,12 +3,15 @@ using System; using System.IO; +using System.IO.Pipelines; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.Hosting { @@ -24,7 +27,7 @@ public static class ListenOptionsHttpsExtensions /// The to configure. /// The . public static ListenOptions UseHttps(this ListenOptions listenOptions) => listenOptions.UseHttps(_ => { }); - + /// /// Configure Kestrel to use HTTPS. /// @@ -210,10 +213,24 @@ internal static bool TryUseHttps(this ListenOptions listenOptions) /// The . public static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions) { - var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService(); + var loggerFactory = listenOptions.KestrelServerOptions?.ApplicationServices.GetRequiredService() ?? NullLoggerFactory.Instance; // Set the list of protocols from listen options httpsOptions.HttpProtocols = listenOptions.Protocols; - listenOptions.ConnectionAdapters.Add(new HttpsConnectionAdapter(httpsOptions, loggerFactory)); + httpsOptions.MaxInputBufferSize = listenOptions.KestrelServerOptions?.Limits.MaxRequestBufferSize; + httpsOptions.MaxOutputBufferSize = listenOptions.KestrelServerOptions?.Limits.MaxResponseBufferSize; + + if (listenOptions.KestrelServerOptions?.ApplicationSchedulingMode == SchedulingMode.Inline) + { + httpsOptions.Scheduler = PipeScheduler.Inline; + } + + listenOptions.IsTls = true; + + listenOptions.Use(next => + { + var middleware = new HttpsConnectionMiddleware(next, httpsOptions, loggerFactory); + return middleware.OnConnectionAsync; + }); return listenOptions; } } diff --git a/src/Kestrel.Core/LocalhostListenOptions.cs b/src/Kestrel.Core/LocalhostListenOptions.cs index ee28dce63..c68a84fd1 100644 --- a/src/Kestrel.Core/LocalhostListenOptions.cs +++ b/src/Kestrel.Core/LocalhostListenOptions.cs @@ -28,11 +28,7 @@ internal LocalhostListenOptions(int port) /// internal override string GetDisplayName() { - var scheme = ConnectionAdapters.Any(f => f.IsHttps) - ? "https" - : "http"; - - return $"{scheme}://localhost:{IPEndPoint.Port}"; + return $"{Scheme}://localhost:{IPEndPoint.Port}"; } internal override async Task BindAsync(AddressBindContext context) @@ -80,6 +76,7 @@ internal ListenOptions Clone(IPAddress address) KestrelServerOptions = KestrelServerOptions, NoDelay = NoDelay, Protocols = Protocols, + IsTls = IsTls }; options._middleware.AddRange(_middleware); diff --git a/test/Kestrel.Core.Tests/KestrelServerTests.cs b/test/Kestrel.Core.Tests/KestrelServerTests.cs index 5ac0fbd5c..a3684bc70 100644 --- a/test/Kestrel.Core.Tests/KestrelServerTests.cs +++ b/test/Kestrel.Core.Tests/KestrelServerTests.cs @@ -58,7 +58,7 @@ public void StartWithHttpsAddressConfiguresHttpsEndpoints() StartDummyApplication(server); Assert.True(server.Options.ListenOptions.Any()); - Assert.Contains(server.Options.ListenOptions[0].ConnectionAdapters, adapter => adapter.IsHttps); + Assert.True(server.Options.ListenOptions[0].IsTls); } } diff --git a/test/Kestrel.InMemory.FunctionalTests/HttpsConnectionAdapterTests.cs b/test/Kestrel.InMemory.FunctionalTests/HttpsConnectionAdapterTests.cs index 572e717b8..d6ffc005c 100644 --- a/test/Kestrel.InMemory.FunctionalTests/HttpsConnectionAdapterTests.cs +++ b/test/Kestrel.InMemory.FunctionalTests/HttpsConnectionAdapterTests.cs @@ -13,6 +13,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -33,15 +34,12 @@ public class HttpsConnectionAdapterTests : LoggedTest [Fact] public async Task CanReadAndWriteWithHttpsConnectionAdapter() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }) - } - }; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }); + } - using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), listenOptions)) + using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { var result = await server.HttpClientSlim.PostAsync($"https://localhost:{server.Port}/", new FormUrlEncodedContent(new[] { @@ -56,13 +54,10 @@ public async Task CanReadAndWriteWithHttpsConnectionAdapter() [Fact] public async Task HandshakeDetailsAreAvailable() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }) - } - }; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }); + } using (var server = new TestServer(context => { @@ -77,7 +72,7 @@ public async Task HandshakeDetailsAreAvailable() Assert.True(tlsFeature.KeyExchangeStrength >= 0, "KeyExchangeStrength"); // May be 0 on mac return context.Response.WriteAsync("hello world"); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false); Assert.Equal("hello world", result); @@ -87,17 +82,12 @@ public async Task HandshakeDetailsAreAvailable() [Fact] public async Task RequireCertificateFailsWhenNoCertificate() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.RequireCertificate - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = ClientCertificateMode.RequireCertificate + }); using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), listenOptions)) @@ -110,17 +100,14 @@ await Assert.ThrowsAnyAsync( [Fact] public async Task AllowCertificateContinuesWhenNoCertificate() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.AllowCertificate - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = ClientCertificateMode.AllowCertificate + }); + } using (var server = new TestServer(context => { @@ -128,7 +115,7 @@ public async Task AllowCertificateContinuesWhenNoCertificate() Assert.NotNull(tlsFeature); Assert.Null(tlsFeature.ClientCertificate); return context.Response.WriteAsync("hello world"); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false); Assert.Equal("hello world", result); @@ -138,7 +125,7 @@ public async Task AllowCertificateContinuesWhenNoCertificate() [Fact] public void ThrowsWhenNoServerCertificateIsProvided() { - Assert.Throws(() => new HttpsConnectionAdapter( + Assert.Throws(() => new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions()) ); } @@ -146,15 +133,12 @@ public void ThrowsWhenNoServerCertificateIsProvided() [Fact] public async Task UsesProvidedServerCertificate() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }) - } - }; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }); + } - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -172,30 +156,28 @@ public async Task UsesProvidedServerCertificate() public async Task UsesProvidedServerCertificateSelector() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificateSelector = (connection, name) => { - ServerCertificateSelector = (connection, name) => - { - Assert.NotNull(connection); - Assert.NotNull(connection.Features.Get()); + Assert.NotNull(connection); + Assert.NotNull(connection.Features.Get()); #if NETCOREAPP2_2 - Assert.Equal("localhost", name); + Assert.Equal("localhost", name); #elif NET461 - Assert.Null(name); + Assert.Null(name); #else #error TFMs need to be updated #endif - selectorCalled++; - return _x509Certificate2; - } - }) - } - }; - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + selectorCalled++; + return _x509Certificate2; + } + }); + } + + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -214,34 +196,32 @@ public async Task UsesProvidedServerCertificateSelector() public async Task UsesProvidedServerCertificateSelectorEachTime() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificateSelector = (connection, name) => { - ServerCertificateSelector = (connection, name) => - { - Assert.NotNull(connection); - Assert.NotNull(connection.Features.Get()); + Assert.NotNull(connection); + Assert.NotNull(connection.Features.Get()); #if NETCOREAPP2_2 - Assert.Equal("localhost", name); + Assert.Equal("localhost", name); #elif NET461 - Assert.Null(name); + Assert.Null(name); #else #error TFMs need to be updated #endif - selectorCalled++; - if (selectorCalled == 1) - { - return _x509Certificate2; - } - return _x509Certificate2NoExt; + selectorCalled++; + if (selectorCalled == 1) + { + return _x509Certificate2; } - }) - } - }; - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + return _x509Certificate2NoExt; + } + }); + } + + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -270,21 +250,19 @@ public async Task UsesProvidedServerCertificateSelectorEachTime() public async Task UsesProvidedServerCertificateSelectorValidatesEkus() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificateSelector = (features, name) => { - ServerCertificateSelector = (features, name) => - { - selectorCalled++; - return TestResources.GetTestCertificate("eku.code_signing.pfx"); - } - }) - } - }; - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + selectorCalled++; + return TestResources.GetTestCertificate("eku.code_signing.pfx"); + } + }); + } + + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -303,31 +281,29 @@ await Assert.ThrowsAsync(() => public async Task UsesProvidedServerCertificateSelectorOverridesServerCertificate() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificate = _x509Certificate2NoExt, + ServerCertificateSelector = (connection, name) => { - ServerCertificate = _x509Certificate2NoExt, - ServerCertificateSelector = (connection, name) => - { - Assert.NotNull(connection); - Assert.NotNull(connection.Features.Get()); + Assert.NotNull(connection); + Assert.NotNull(connection.Features.Get()); #if NETCOREAPP2_2 - Assert.Equal("localhost", name); + Assert.Equal("localhost", name); #elif NET461 - Assert.Null(name); + Assert.Null(name); #else #error TFMs need to be updated #endif - selectorCalled++; - return _x509Certificate2; - } - }) - } - }; - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + selectorCalled++; + return _x509Certificate2; + } + }); + } + + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -346,21 +322,19 @@ public async Task UsesProvidedServerCertificateSelectorOverridesServerCertificat public async Task UsesProvidedServerCertificateSelectorFailsIfYouReturnNull() { var selectorCalled = 0; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificateSelector = (features, name) => { - ServerCertificateSelector = (features, name) => - { - selectorCalled++; - return null; - } - }) - } - }; - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + selectorCalled++; + return null; + } + }); + } + + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -380,19 +354,16 @@ await Assert.ThrowsAsync(() => [InlineData(HttpProtocols.Http1AndHttp2)] // Make sure Http/1.1 doesn't regress with Http/2 enabled. public async Task CertificatePassedToHttpContext(HttpProtocols httpProtocols) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - Protocols = httpProtocols, - ConnectionAdapters = + listenOptions.Protocols = httpProtocols; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.RequireCertificate, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = ClientCertificateMode.RequireCertificate, + ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true + }); + } using (var server = new TestServer(context => { @@ -401,7 +372,7 @@ public async Task CertificatePassedToHttpContext(HttpProtocols httpProtocols) Assert.NotNull(tlsFeature.ClientCertificate); Assert.NotNull(context.Connection.ClientCertificate); return context.Response.WriteAsync("hello world"); - }, new TestServiceContext(LoggerFactory), listenOptions)) + }, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -418,15 +389,12 @@ public async Task CertificatePassedToHttpContext(HttpProtocols httpProtocols) [Fact] public async Task HttpsSchemePassedToRequestFeature() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }) - } - }; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 }); + } - using (var server = new TestServer(context => context.Response.WriteAsync(context.Request.Scheme), new TestServiceContext(LoggerFactory), listenOptions)) + using (var server = new TestServer(context => context.Response.WriteAsync(context.Request.Scheme), new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false); Assert.Equal("https", result); @@ -436,20 +404,17 @@ public async Task HttpsSchemePassedToRequestFeature() [Fact] public async Task DoesNotSupportTls10() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.RequireCertificate, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = ClientCertificateMode.RequireCertificate, + ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true + }); + } - using (var server = new TestServer(context => context.Response.WriteAsync("hello world"), new TestServiceContext(LoggerFactory), listenOptions)) + using (var server = new TestServer(context => context.Response.WriteAsync("hello world"), new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { // 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 @@ -469,26 +434,23 @@ public async Task DoesNotSupportTls10() public async Task ClientCertificateValidationGetsCalledWithNotNullParameters(ClientCertificateMode mode) { var clientCertificateValidationCalled = false; - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + ServerCertificate = _x509Certificate2, + ClientCertificateMode = mode, + ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = mode, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => - { - clientCertificateValidationCalled = true; - Assert.NotNull(certificate); - Assert.NotNull(chain); - return true; - } - }) - } - }; + clientCertificateValidationCalled = true; + Assert.NotNull(certificate); + Assert.NotNull(chain); + return true; + } + }); + } - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -505,20 +467,17 @@ public async Task ClientCertificateValidationGetsCalledWithNotNullParameters(Cli [InlineData(ClientCertificateMode.RequireCertificate)] public async Task ValidationFailureRejectsConnection(ClientCertificateMode mode) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = mode, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => false - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = mode, + ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => false + }); + } - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -534,19 +493,16 @@ public async Task ValidationFailureRejectsConnection(ClientCertificateMode mode) [InlineData(ClientCertificateMode.RequireCertificate)] public async Task RejectsConnectionOnSslPolicyErrorsWhenNoValidation(ClientCertificateMode mode) { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = mode - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = mode + }); + } - using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), listenOptions)) + using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { @@ -560,18 +516,15 @@ public async Task RejectsConnectionOnSslPolicyErrorsWhenNoValidation(ClientCerti [Fact] public async Task CertificatePassedToHttpContextIsNotDisposed() { - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions - { - ServerCertificate = _x509Certificate2, - ClientCertificateMode = ClientCertificateMode.RequireCertificate, - ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true - }) - } - }; + ServerCertificate = _x509Certificate2, + ClientCertificateMode = ClientCertificateMode.RequireCertificate, + ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => true + }); + } RequestDelegate app = context => { @@ -583,7 +536,7 @@ public async Task CertificatePassedToHttpContextIsNotDisposed() return context.Response.WriteAsync("hello world"); }; - using (var server = new TestServer(app, new TestServiceContext(LoggerFactory), listenOptions)) + using (var server = new TestServer(app, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) { // 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 @@ -606,7 +559,7 @@ public void AcceptsCertificateWithoutExtensions(string testCertName) var cert = new X509Certificate2(certPath, "testPassword"); Assert.Empty(cert.Extensions.OfType()); - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, }); @@ -624,7 +577,7 @@ public void ValidatesEnhancedKeyUsageOnCertificate(string testCertName) var eku = Assert.Single(cert.Extensions.OfType()); Assert.NotEmpty(eku.EnhancedKeyUsages); - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, }); @@ -643,7 +596,7 @@ public void ThrowsForCertificatesMissingServerEku(string testCertName) Assert.NotEmpty(eku.EnhancedKeyUsages); var ex = Assert.Throws(() => - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions + new HttpsConnectionMiddleware(context => Task.CompletedTask, new HttpsConnectionAdapterOptions { ServerCertificate = cert, })); diff --git a/test/Kestrel.InMemory.FunctionalTests/HttpsTests.cs b/test/Kestrel.InMemory.FunctionalTests/HttpsTests.cs index 1f3039abb..89a78c0ef 100644 --- a/test/Kestrel.InMemory.FunctionalTests/HttpsTests.cs +++ b/test/Kestrel.InMemory.FunctionalTests/HttpsTests.cs @@ -385,7 +385,7 @@ private class HandshakeErrorLoggerProvider : ILoggerProvider public ILogger CreateLogger(string categoryName) { - if (categoryName == nameof(HttpsConnectionAdapter)) + if (categoryName == nameof(HttpsConnectionMiddleware)) { return FilterLogger; } diff --git a/test/Kestrel.Tests/KestrelConfigurationBuilderTests.cs b/test/Kestrel.Tests/KestrelConfigurationBuilderTests.cs index 33caeb1d3..e0ba2f9f6 100644 --- a/test/Kestrel.Tests/KestrelConfigurationBuilderTests.cs +++ b/test/Kestrel.Tests/KestrelConfigurationBuilderTests.cs @@ -164,8 +164,8 @@ public void ConfigureDefaultsAppliesToNewConfigureEndpoints() Assert.True(ran1); Assert.True(ran2); - Assert.NotNull(serverOptions.ListenOptions[0].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault()); - Assert.Null(serverOptions.ListenOptions[1].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault()); + Assert.True(serverOptions.ListenOptions[0].IsTls); + Assert.False(serverOptions.ListenOptions[1].IsTls); } [Fact] @@ -209,8 +209,8 @@ public void ConfigureEndpointDefaultCanEnableHttps() Assert.True(ran2); // You only get Https once per endpoint. - Assert.NotNull(serverOptions.ListenOptions[0].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault()); - Assert.NotNull(serverOptions.ListenOptions[1].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault()); + Assert.True(serverOptions.ListenOptions[0].IsTls); + Assert.True(serverOptions.ListenOptions[1].IsTls); } [Fact] diff --git a/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs b/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs index bc61229b9..bb74b3a57 100644 --- a/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs +++ b/test/Kestrel.Transport.FunctionalTests/ResponseTests.cs @@ -579,13 +579,10 @@ public async Task HttpsConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate testContext.InitializeHeartbeat(); - var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + void ConfigureListenOptions(ListenOptions listenOptions) { - ConnectionAdapters = - { - new HttpsConnectionAdapter(new HttpsConnectionAdapterOptions { ServerCertificate = certificate }) - } - }; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = certificate }); + } using (var server = new TestServer(async context => { @@ -608,7 +605,7 @@ public async Task HttpsConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate appFuncCompleted.SetResult(null); throw; } - }, testContext, listenOptions)) + }, testContext, ConfigureListenOptions)) { using (var connection = server.CreateConnection()) { diff --git a/test/Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs b/test/Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs index 2d906fc2c..c7dc4b77d 100644 --- a/test/Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs @@ -302,11 +302,7 @@ private static Uri GetUri(ListenOptions options) throw new InvalidOperationException($"Could not determine a proper URI for options with Type {options.Type}"); } - var scheme = options.ConnectionAdapters.Any(f => f.IsHttps) - ? "https" - : "http"; - - return new Uri($"{scheme}://{options.IPEndPoint}"); + return new Uri($"{options.Scheme}://{options.IPEndPoint}"); } private class ConnectionBuilder : IConnectionBuilder diff --git a/test/shared/TransportTestHelpers/TestServer.cs b/test/shared/TransportTestHelpers/TestServer.cs index df7388812..6c411e22a 100644 --- a/test/shared/TransportTestHelpers/TestServer.cs +++ b/test/shared/TransportTestHelpers/TestServer.cs @@ -41,14 +41,23 @@ public TestServer(RequestDelegate app, TestServiceContext context) } public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions) - : this(app, context, listenOptions, _ => { }) + : this(app, context, options => options.ListenOptions.Add(listenOptions), _ => { }) { } - public TestServer(RequestDelegate app, TestServiceContext context, ListenOptions listenOptions, Action configureServices) - : this(app, context, options => options.ListenOptions.Add(listenOptions), configureServices) + public TestServer(RequestDelegate app, TestServiceContext context, Action configureListenOptions) + : this(app, context, options => + { + var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + { + KestrelServerOptions = options + }; + configureListenOptions(listenOptions); + options.ListenOptions.Add(listenOptions); + }, _ => { }) { } + public TestServer(RequestDelegate app, TestServiceContext context, Action configureKestrel) : this(app, context, configureKestrel, _ => { }) { @@ -121,5 +130,11 @@ public void Dispose() { _host.Dispose(); } + + private static void SetupListenOptions(KestrelServerOptions options, ListenOptions listenOptions) + { + listenOptions.KestrelServerOptions = options; + options.ListenOptions.Add(listenOptions); + } } }