Esempio n. 1
0
        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) =>
                        {
                            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, cancellationTokeSource.Token);
                }
                catch (OperationCanceledException)
                {
                    _logger.LogDebug(2, CoreStrings.AuthenticationTimedOut);
                    await sslStream.DisposeAsync();

                    return;
                }
                catch (IOException ex)
                {
                    _logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
                    await sslStream.DisposeAsync();

                    return;
                }
                catch (AuthenticationException ex)
                {
                    if (_serverCertificate == null ||
                        !CertificateManager.IsHttpsDevelopmentCertificate(_serverCertificate) ||
                        CertificateManager.CheckDeveloperCertificateKey(_serverCertificate))
                    {
                        _logger.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
                    }
                    else
                    {
                        _logger.LogError(3, ex, CoreStrings.BadDeveloperCertificateState);
                    }

                    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;

            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;
            }
        }