public async Task OnConnectionAsync(ConnectionContext context) { await Task.Yield(); bool certificateRequired; if (context.Features.Get <ITlsConnectionFeature>() != null) { await _next(context); return; } var feature = new Core.Internal.TlsConnectionFeature(); context.Features.Set <ITlsConnectionFeature>(feature); context.Features.Set <ITlsHandshakeFeature>(feature); var memoryPool = context.Features.Get <IMemoryPoolFeature>()?.MemoryPool; var inputPipeOptions = new StreamPipeReaderOptions ( pool: memoryPool, bufferSize: memoryPool.GetMinimumSegmentSize(), minimumReadSize: memoryPool.GetMinimumAllocSize(), leaveOpen: true ); var outputPipeOptions = new StreamPipeWriterOptions ( pool: memoryPool, leaveOpen: true ); SslDuplexPipe sslDuplexPipe = null; if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); certificateRequired = false; } else { sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions, s => new SslStream(s, 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; } var sslStream = sslDuplexPipe.Stream; using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) { try { // Adapt to the SslStream signature ServerCertificateSelectionCallback selector = null; if (_serverCertificateSelector != null) { selector = (sender, name) => { feature.HostName = name; context.Features.Set(sslStream); var cert = _serverCertificateSelector(context, 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 <SslApplicationProtocol>() }; // 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); } _options.OnAuthenticate?.Invoke(context, sslOptions); KestrelEventSource.Log.TlsHandshakeStart(context, sslOptions); await sslStream.AuthenticateAsServerAsync(sslOptions, cancellationTokeSource.Token); } catch (OperationCanceledException) { KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId); KestrelEventSource.Log.TlsHandshakeStop(context, null); _logger.AuthenticationTimedOut(); await sslStream.DisposeAsync(); return; } catch (IOException ex) { KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId); KestrelEventSource.Log.TlsHandshakeStop(context, null); _logger.AuthenticationFailed(ex); await sslStream.DisposeAsync(); return; } catch (AuthenticationException ex) { KestrelEventSource.Log.TlsHandshakeFailed(context.ConnectionId); KestrelEventSource.Log.TlsHandshakeStop(context, null); _logger.AuthenticationFailed(ex); await sslStream.DisposeAsync(); return; } } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set <ITlsApplicationProtocolFeature>(feature); 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; KestrelEventSource.Log.TlsHandshakeStop(context, feature); _logger.HttpsConnectionEstablished(context.ConnectionId, sslStream.SslProtocol); var originalTransport = context.Transport; try { context.Transport = sslDuplexPipe; // Disposing the stream will dispose the sslDuplexPipe await using (sslStream) await using (sslDuplexPipe) { await _next(context); // Dispose the inner stream (SslDuplexPipe) before disposing the SslStream // as the duplex pipe can hit an ODE as it still may be writing. } } finally { // Restore the original so that it gets closed appropriately context.Transport = originalTransport; } }
private async Task InnerOnConnectionAsync(ConnectionContext context) { SslStream sslStream; bool certificateRequired; var feature = new Core.Internal.TlsConnectionFeature(); context.Features.Set <ITlsConnectionFeature>(feature); context.Features.Set <ITlsHandshakeFeature>(feature); // TODO: Handle the cases where this can be null var memoryPoolFeature = context.Features.Get <IMemoryPoolFeature>(); var inputPipeOptions = new PipeOptions ( pool: memoryPoolFeature.MemoryPool, readerScheduler: _options.Scheduler, writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: _options.MaxInputBufferSize ?? 0, resumeWriterThreshold: _options.MaxInputBufferSize / 2 ?? 0, useSynchronizationContext: false, minimumSegmentSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize() ); var outputPipeOptions = new PipeOptions ( pool: memoryPoolFeature.MemoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: _options.MaxOutputBufferSize ?? 0, resumeWriterThreshold: _options.MaxOutputBufferSize / 2 ?? 0, useSynchronizationContext: false, minimumSegmentSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize() ); // TODO: eventually make SslDuplexStream : Stream, IDuplexPipe to avoid RawStream allocation and pipe allocations var adaptedPipeline = new AdaptedPipeline(context.Transport, new Pipe(inputPipeOptions), new Pipe(outputPipeOptions), _logger, memoryPoolFeature.MemoryPool.GetMinimumAllocSize()); var transportStream = adaptedPipeline.TransportStream; 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.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { try { // Adapt to the SslStream signature ServerCertificateSelectionCallback selector = null; if (_serverCertificateSelector != null) { selector = (sender, name) => { context.Features.Set(sslStream); var cert = _serverCertificateSelector(context, 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 <SslApplicationProtocol>() }; // 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); } _options.OnAuthenticate?.Invoke(context, sslOptions); await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); } 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; } } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set <ITlsApplicationProtocolFeature>(feature); 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 original = context.Transport; try { context.Transport = adaptedPipeline; using (sslStream) { try { adaptedPipeline.RunAsync(sslStream); await _next(context); } finally { await adaptedPipeline.CompleteAsync(); } } } finally { // Restore the original so that it gets closed appropriately context.Transport = original; } }