Exemple #1
0
        public async Task SslStream_StreamToStream_Alpn_NonMatchingProtocols_Fail()
        {
            TcpListener listener = new TcpListener(IPAddress.Loopback, 0);

            try
            {
                listener.Start();
                using (TcpClient client = new TcpClient())
                {
                    Task <TcpClient> serverTask = listener.AcceptTcpClientAsync();
                    await client.ConnectAsync(IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port);

                    using (TcpClient server = await serverTask)
                        using (SslStream serverStream = new SslStream(server.GetStream(), leaveInnerStreamOpen: false))
                            using (SslStream clientStream = new SslStream(client.GetStream(), leaveInnerStreamOpen: false))
                                using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate())
                                {
                                    SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions
                                    {
                                        ApplicationProtocols = new List <SslApplicationProtocol> {
                                            SslApplicationProtocol.Http2
                                        },
                                        ServerCertificate = certificate,
                                    };
                                    SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions
                                    {
                                        ApplicationProtocols = new List <SslApplicationProtocol> {
                                            SslApplicationProtocol.Http11
                                        },
                                        RemoteCertificateValidationCallback = AllowAnyServerCertificate,
                                        TargetHost = certificate.GetNameInfo(X509NameType.SimpleName, false),
                                    };

                                    // Test alpn failure only on platforms that supports ALPN.
                                    if (BackendSupportsAlpn)
                                    {
                                        Task t1 = Assert.ThrowsAsync <IOException>(() => clientStream.AuthenticateAsClientAsync(clientOptions, CancellationToken.None));
                                        try
                                        {
                                            await serverStream.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);

                                            Assert.True(false, "AuthenticationException was not thrown.");
                                        }
                                        catch (AuthenticationException) { server.Dispose(); }

                                        await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1);
                                    }
                                    else
                                    {
                                        Task t1 = clientStream.AuthenticateAsClientAsync(clientOptions, CancellationToken.None);
                                        Task t2 = serverStream.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);

                                        await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);

                                        Assert.Equal(default(SslApplicationProtocol), clientStream.NegotiatedApplicationProtocol);
                                        Assert.Equal(default(SslApplicationProtocol), serverStream.NegotiatedApplicationProtocol);
                                    }
                                }
                }
            }
            finally
            {
                listener.Stop();
            }
        }
Exemple #2
0
        public async Task SslStream_TargetHostName_Succeeds(bool useEmptyName)
        {
            string tagetName = useEmptyName ? string.Empty : Guid.NewGuid().ToString("N");

            (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
            using (clientStream)
                using (serverStream)
                    using (var client = new SslStream(clientStream))
                        using (var server = new SslStream(serverStream))
                            using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate())
                            {
                                // It should be empty before handshake.
                                Assert.Equal(string.Empty, client.TargetHostName);
                                Assert.Equal(string.Empty, server.TargetHostName);

                                SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions()
                                {
                                    TargetHost = tagetName
                                };
                                clientOptions.RemoteCertificateValidationCallback =
                                    (sender, certificate, chain, sslPolicyErrors) =>
                                {
                                    SslStream stream = (SslStream)sender;
                                    if (useEmptyName)
                                    {
                                        Assert.Equal('?', stream.TargetHostName[0]);
                                    }
                                    else
                                    {
                                        Assert.Equal(tagetName, stream.TargetHostName);
                                    }

                                    return(true);
                                };

                                SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions();
                                serverOptions.ServerCertificateSelectionCallback =
                                    (sender, name) =>
                                {
                                    SslStream stream = (SslStream)sender;
                                    if (useEmptyName)
                                    {
                                        Assert.Equal('?', stream.TargetHostName[0]);
                                    }
                                    else
                                    {
                                        Assert.Equal(tagetName, stream.TargetHostName);
                                    }

                                    return(certificate);
                                };

                                await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
                                    client.AuthenticateAsClientAsync(clientOptions),
                                    server.AuthenticateAsServerAsync(serverOptions));

                                if (useEmptyName)
                                {
                                    Assert.Equal('?', client.TargetHostName[0]);
                                    Assert.Equal('?', server.TargetHostName[0]);
                                }
                                else
                                {
                                    Assert.Equal(tagetName, client.TargetHostName);
                                    Assert.Equal(tagetName, server.TargetHostName);
                                }
                            }
        }
        /// <summary>
        ///     This is called when client is aware of proxy
        ///     So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
        /// </summary>
        /// <param name="endPoint">The explicit endpoint.</param>
        /// <param name="clientConnection">The client connection.</param>
        /// <returns>The task.</returns>
        private async Task handleClient(ExplicitProxyEndPoint endPoint, TcpClientConnection clientConnection)
        {
            var cancellationTokenSource = new CancellationTokenSource();
            var cancellationToken       = cancellationTokenSource.Token;

            var clientStream       = new CustomBufferedStream(clientConnection.GetStream(), BufferPool, BufferSize);
            var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize);

            Task <TcpServerConnection> prefetchConnectionTask = null;
            bool closeServerConnection = false;
            bool calledRequestHandler  = false;

            SslStream sslStream = null;

            try
            {
                string connectHostname = null;
                TunnelConnectSessionEventArgs connectArgs = null;

                // Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
                if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, BufferSize, cancellationToken) == 1)
                {
                    // read the first line HTTP command
                    string httpCmd = await clientStream.ReadLineAsync(cancellationToken);

                    if (string.IsNullOrEmpty(httpCmd))
                    {
                        return;
                    }

                    Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version);

                    var httpRemoteUri = new Uri("http://" + httpUrl);
                    connectHostname = httpRemoteUri.Host;

                    var connectRequest = new ConnectRequest
                    {
                        RequestUri  = httpRemoteUri,
                        OriginalUrl = httpUrl,
                        HttpVersion = version
                    };

                    await HeaderParser.ReadHeaders(clientStream, connectRequest.Headers, cancellationToken);

                    connectArgs = new TunnelConnectSessionEventArgs(this, endPoint, connectRequest,
                                                                    cancellationTokenSource);
                    connectArgs.ProxyClient.Connection   = clientConnection;
                    connectArgs.ProxyClient.ClientStream = clientStream;

                    await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc);

                    // filter out excluded host names
                    bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl;

                    if (connectArgs.DenyConnect)
                    {
                        if (connectArgs.HttpClient.Response.StatusCode == 0)
                        {
                            connectArgs.HttpClient.Response = new Response
                            {
                                HttpVersion       = HttpHeader.Version11,
                                StatusCode        = (int)HttpStatusCode.Forbidden,
                                StatusDescription = "Forbidden"
                            };
                        }

                        // send the response
                        await clientStreamWriter.WriteResponseAsync(connectArgs.HttpClient.Response,
                                                                    cancellationToken : cancellationToken);

                        return;
                    }

                    if (await checkAuthorization(connectArgs) == false)
                    {
                        await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc);

                        // send the response
                        await clientStreamWriter.WriteResponseAsync(connectArgs.HttpClient.Response,
                                                                    cancellationToken : cancellationToken);

                        return;
                    }

                    // write back successful CONNECT response
                    var response = ConnectResponse.CreateSuccessfulConnectResponse(version);

                    // Set ContentLength explicitly to properly handle HTTP 1.0
                    response.ContentLength = 0;
                    response.Headers.FixProxyHeaders();
                    connectArgs.HttpClient.Response = response;

                    await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken);

                    var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken);

                    bool isClientHello = clientHelloInfo != null;
                    if (isClientHello)
                    {
                        connectRequest.TunnelType      = TunnelType.Https;
                        connectRequest.ClientHelloInfo = clientHelloInfo;
                    }

                    await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc, isClientHello);

                    if (decryptSsl && isClientHello)
                    {
                        connectRequest.RequestUri = new Uri("https://" + httpUrl);

                        bool http2Supported = false;

                        var alpn = clientHelloInfo.GetAlpn();
                        if (alpn != null && alpn.Contains(SslApplicationProtocol.Http2))
                        {
                            // test server HTTP/2 support
                            try
                            {
                                // todo: this is a hack, because Titanium does not support HTTP protocol changing currently
                                var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs,
                                                                                                isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList,
                                                                                                noCache : true, cancellationToken : cancellationToken);

                                http2Supported = connection.NegotiatedApplicationProtocol ==
                                                 SslApplicationProtocol.Http2;
                                //release connection back to pool instead of closing when connection pool is enabled.
                                await tcpConnectionFactory.Release(connection, true);
                            }
                            catch (Exception)
                            {
                                // ignore
                            }
                        }

                        if (EnableTcpServerConnectionPrefetch)
                        {
                            IPAddress[] ipAddresses = null;
                            try
                            {
                                //make sure the host can be resolved before creating the prefetch task
                                ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host);
                            }
                            catch (SocketException) { }

                            if (ipAddresses != null && ipAddresses.Length > 0)
                            {
                                //don't pass cancellation token here
                                //it could cause floating server connections when client exits
                                prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs,
                                                                                                  isConnect: true, applicationProtocols: null, noCache: false,
                                                                                                  cancellationToken: CancellationToken.None);
                            }
                        }

                        X509Certificate2 certificate = null;
                        try
                        {
                            sslStream = new SslStream(clientStream, false);

                            string certName = HttpHelper.GetWildCardDomainName(connectHostname);
                            certificate = endPoint.GenericCertificate ??
                                          await CertificateManager.CreateServerCertificate(certName);

                            // Successfully managed to authenticate the client using the fake certificate
                            var options = new SslServerAuthenticationOptions();
                            if (EnableHttp2 && http2Supported)
                            {
                                options.ApplicationProtocols = clientHelloInfo.GetAlpn();
                                if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0)
                                {
                                    options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
                                }
                            }

                            options.ServerCertificate              = certificate;
                            options.ClientCertificateRequired      = false;
                            options.EnabledSslProtocols            = SupportedSslProtocols;
                            options.CertificateRevocationCheckMode = X509RevocationMode.NoCheck;
                            await sslStream.AuthenticateAsServerAsync(options, cancellationToken);

#if NETCOREAPP2_1
                            clientConnection.NegotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif

                            // HTTPS server created - we can now decrypt the client's traffic
                            clientStream       = new CustomBufferedStream(sslStream, BufferPool, BufferSize);
                            clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize);
                        }
                        catch (Exception e)
                        {
                            var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false);
                            throw new ProxyConnectException(
                                      $"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs);
                        }

                        if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, BufferSize, cancellationToken) == -1)
                        {
                            decryptSsl = false;
                        }

                        if (!decryptSsl)
                        {
                            await tcpConnectionFactory.Release(prefetchConnectionTask, true);

                            prefetchConnectionTask = null;
                        }
                    }

                    if (cancellationTokenSource.IsCancellationRequested)
                    {
                        throw new Exception("Session was terminated by user.");
                    }

                    // Hostname is excluded or it is not an HTTPS connect
                    if (!decryptSsl || !isClientHello)
                    {
                        if (!isClientHello)
                        {
                            connectRequest.TunnelType = TunnelType.Websocket;
                        }

                        // create new connection to server.
                        // If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then
                        // this connection should not be HTTPS.
                        var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs,
                                                                                        isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList,
                                                                                        noCache : true, cancellationToken : cancellationToken);

                        try
                        {
                            if (isClientHello)
                            {
                                int available = clientStream.Available;
                                if (available > 0)
                                {
                                    // send the buffered data
                                    var data = BufferPool.GetBuffer(BufferSize);

                                    try
                                    {
                                        await clientStream.ReadAsync(data, 0, available, cancellationToken);

                                        // clientStream.Available should be at most BufferSize because it is using the same buffer size
                                        await connection.StreamWriter.WriteAsync(data, 0, available, true, cancellationToken);
                                    }
                                    finally
                                    {
                                        BufferPool.ReturnBuffer(data);
                                    }
                                }

                                var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream, BufferPool, cancellationToken);

                                ((ConnectResponse)connectArgs.HttpClient.Response).ServerHelloInfo = serverHelloInfo;
                            }

                            await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool, BufferSize,
                                                    (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
                                                    (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
                                                    connectArgs.CancellationTokenSource, ExceptionFunc);
                        }
                        finally
                        {
                            await tcpConnectionFactory.Release(connection, true);
                        }

                        return;
                    }
                }

                if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream, BufferPool, BufferSize, cancellationToken) == 1)
                {
                    // todo
                    string httpCmd = await clientStream.ReadLineAsync(cancellationToken);

                    if (httpCmd == "PRI * HTTP/2.0")
                    {
                        connectArgs.HttpClient.ConnectRequest.TunnelType = TunnelType.Http2;

                        // HTTP/2 Connection Preface
                        string line = await clientStream.ReadLineAsync(cancellationToken);

                        if (line != string.Empty)
                        {
                            throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
                        }

                        line = await clientStream.ReadLineAsync(cancellationToken);

                        if (line != "SM")
                        {
                            throw new Exception($"HTTP/2 Protocol violation. 'SM' expected, '{line}' received");
                        }

                        line = await clientStream.ReadLineAsync(cancellationToken);

                        if (line != string.Empty)
                        {
                            throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
                        }

                        var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs,
                                                                                        isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList,
                                                                                        noCache : true, cancellationToken : cancellationToken);

                        try
                        {
                            await connection.StreamWriter.WriteLineAsync("PRI * HTTP/2.0", cancellationToken);

                            await connection.StreamWriter.WriteLineAsync(cancellationToken);

                            await connection.StreamWriter.WriteLineAsync("SM", cancellationToken);

                            await connection.StreamWriter.WriteLineAsync(cancellationToken);

#if NETCOREAPP2_1
                            await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize,
                                                        (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
                                                        (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
                                                        () => new SessionEventArgs(this, endPoint, cancellationTokenSource)
                            {
                                ProxyClient = { Connection = clientConnection },
                                HttpClient  = { ConnectRequest = connectArgs?.HttpClient.ConnectRequest },
                                UserData    = connectArgs?.UserData
                            },
                                                        async args => { await invokeBeforeRequest(args); },
                                                        async args => { await invokeBeforeResponse(args); },
                                                        connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc);
#endif
                        }
                        finally
                        {
                            await tcpConnectionFactory.Release(connection, true);
                        }
                    }
                }

                calledRequestHandler = true;
                // Now create the request
                await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter,
                                               cancellationTokenSource, connectHostname, connectArgs, prefetchConnectionTask);
            }
            catch (ProxyException e)
            {
                closeServerConnection = true;
                onException(clientStream, e);
            }
            catch (IOException e)
            {
                closeServerConnection = true;
                onException(clientStream, new Exception("Connection was aborted", e));
            }
            catch (SocketException e)
            {
                closeServerConnection = true;
                onException(clientStream, new Exception("Could not connect", e));
            }
            catch (Exception e)
            {
                closeServerConnection = true;
                onException(clientStream, new Exception("Error occured in whilst handling the client", e));
            }
            finally
            {
                if (!calledRequestHandler)
                {
                    await tcpConnectionFactory.Release(prefetchConnectionTask, closeServerConnection);
                }

                sslStream?.Dispose();
                clientStream.Dispose();

                if (!cancellationTokenSource.IsCancellationRequested)
                {
                    cancellationTokenSource.Cancel();
                }
            }
        }
Exemple #4
0
    public SniOptionsSelector(
        string endpointName,
        Dictionary <string, SniConfig> sniDictionary,
        ICertificateConfigLoader certifcateConfigLoader,
        HttpsConnectionAdapterOptions fallbackHttpsOptions,
        HttpProtocols fallbackHttpProtocols,
        ILogger <HttpsConnectionMiddleware> logger)
    {
        _endpointName = endpointName;

        _fallbackServerCertificateSelector = fallbackHttpsOptions.ServerCertificateSelector;
        _onAuthenticateCallback            = fallbackHttpsOptions.OnAuthenticate;

        foreach (var(name, sniConfig) in sniDictionary)
        {
            var sslOptions = new SslServerAuthenticationOptions
            {
                ServerCertificate              = certifcateConfigLoader.LoadCertificate(sniConfig.Certificate, $"{endpointName}:Sni:{name}"),
                EnabledSslProtocols            = sniConfig.SslProtocols ?? fallbackHttpsOptions.SslProtocols,
                CertificateRevocationCheckMode = fallbackHttpsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
            };

            if (sslOptions.ServerCertificate is null)
            {
                if (fallbackHttpsOptions.ServerCertificate is null && _fallbackServerCertificateSelector is null)
                {
                    throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
                }

                if (_fallbackServerCertificateSelector is null)
                {
                    // Cache the fallback ServerCertificate since there's no fallback ServerCertificateSelector taking precedence.
                    sslOptions.ServerCertificate = fallbackHttpsOptions.ServerCertificate;
                }
            }

            if (sslOptions.ServerCertificate != null)
            {
                // This might be do blocking IO but it'll resolve the certificate chain up front before any connections are
                // made to the server
                sslOptions.ServerCertificateContext = SslStreamCertificateContext.Create((X509Certificate2)sslOptions.ServerCertificate, additionalCertificates: null);
            }

            if (!certifcateConfigLoader.IsTestMock && sslOptions.ServerCertificate is X509Certificate2 cert2)
            {
                HttpsConnectionMiddleware.EnsureCertificateIsAllowedForServerAuth(cert2);
            }

            var clientCertificateMode = sniConfig.ClientCertificateMode ?? fallbackHttpsOptions.ClientCertificateMode;

            if (clientCertificateMode != ClientCertificateMode.NoCertificate)
            {
                sslOptions.ClientCertificateRequired = clientCertificateMode == ClientCertificateMode.AllowCertificate ||
                                                       clientCertificateMode == ClientCertificateMode.RequireCertificate;
                sslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
                                                                 HttpsConnectionMiddleware.RemoteCertificateValidationCallback(
                    clientCertificateMode, fallbackHttpsOptions.ClientCertificateValidation, certificate, chain, sslPolicyErrors);
            }

            var httpProtocols = sniConfig.Protocols ?? fallbackHttpProtocols;
            httpProtocols = HttpsConnectionMiddleware.ValidateAndNormalizeHttpProtocols(httpProtocols, logger);
            HttpsConnectionMiddleware.ConfigureAlpn(sslOptions, httpProtocols);

            var sniOptions = new SniOptions(sslOptions, httpProtocols, clientCertificateMode);

            if (name.Equals(WildcardHost, StringComparison.Ordinal))
            {
                _wildcardOptions = sniOptions;
            }
            else if (name.StartsWith(WildcardPrefix, StringComparison.Ordinal))
            {
                // Only slice off 1 character, the `*`. We want to match the leading `.` also.
                _wildcardPrefixOptions.Add(name.Substring(1), sniOptions);
            }
            else
            {
                _exactNameOptions.Add(name, sniOptions);
            }
        }
    }
        private async Task <SslServerAuthenticationOptions> OptionsTask(SslServerAuthenticationOptions value)
        {
            await Task.Yield();

            return(value);
        }
Exemple #6
0
 /// <summary>
 /// Create a QUIC listener.
 /// </summary>
 /// <param name="listenEndPoint">The local endpoint to listen on.</param>
 /// <param name="sslServerAuthenticationOptions">TLS options for the listener.</param>
 public QuicListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
     : this(QuicImplementationProviders.Default, listenEndPoint, sslServerAuthenticationOptions)
 {
 }
Exemple #7
0
        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;
            }
        }
    public void CloneSslOptionsClonesAllProperties()
    {
        var propertyNames = typeof(SslServerAuthenticationOptions).GetProperties().Select(property => property.Name).ToList();

        CipherSuitesPolicy cipherSuitesPolicy = null;

        if (!OperatingSystem.IsWindows())
        {
            try
            {
                // The CipherSuitesPolicy ctor throws a PlatformNotSupportedException on Windows.
                cipherSuitesPolicy = new CipherSuitesPolicy(new[] { TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 });
            }
            catch (PlatformNotSupportedException)
            {
                // The CipherSuitesPolicy ctor throws a PlatformNotSupportedException on Ubuntu 16.04.
                // I don't know exactly which other distros/versions throw PNEs, but it isn't super relevant to this test,
                // so let's just swallow this exception.
            }
        }

        // Set options properties to non-default values to verify they're copied.
        var options = new SslServerAuthenticationOptions
        {
            // Defaults to true
            AllowRenegotiation = false,
            // Defaults to null
            ApplicationProtocols = new List <SslApplicationProtocol> {
                SslApplicationProtocol.Http2
            },
            // Defaults to X509RevocationMode.NoCheck
            CertificateRevocationCheckMode = X509RevocationMode.Offline,
            // Defaults to null
            CipherSuitesPolicy = cipherSuitesPolicy,
            // Defaults to false
            ClientCertificateRequired = true,
            // Defaults to SslProtocols.None
#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
            EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls11,
#pragma warning restore SYSLIB0039
#pragma warning disable SYSLIB0040 // EncryptionPolicy.NoEncryption is obsolete
            // Defaults to EncryptionPolicy.RequireEncryption
            EncryptionPolicy = EncryptionPolicy.NoEncryption,
#pragma warning restore SYSLIB0040
            // Defaults to null
            RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
            // Defaults to null
            ServerCertificate = new X509Certificate2(Array.Empty <byte>()),
            // Defaults to null
            ServerCertificateContext = SslStreamCertificateContext.Create(_x509Certificate2, additionalCertificates: null, offline: true),
            // Defaults to null
            ServerCertificateSelectionCallback = (sender, serverName) => null,
        };

        var clonedOptions = SniOptionsSelector.CloneSslOptions(options);

        Assert.NotSame(options, clonedOptions);

        Assert.Equal(options.AllowRenegotiation, clonedOptions.AllowRenegotiation);
        Assert.True(propertyNames.Remove(nameof(options.AllowRenegotiation)));

        // Ensure the List<SslApplicationProtocol> is also cloned since it could be modified by a user callback.
        Assert.NotSame(options.ApplicationProtocols, clonedOptions.ApplicationProtocols);
        Assert.Equal(Assert.Single(options.ApplicationProtocols), Assert.Single(clonedOptions.ApplicationProtocols));
        Assert.True(propertyNames.Remove(nameof(options.ApplicationProtocols)));

        Assert.Equal(options.CertificateRevocationCheckMode, clonedOptions.CertificateRevocationCheckMode);
        Assert.True(propertyNames.Remove(nameof(options.CertificateRevocationCheckMode)));

        Assert.Same(options.CipherSuitesPolicy, clonedOptions.CipherSuitesPolicy);
        Assert.True(propertyNames.Remove(nameof(options.CipherSuitesPolicy)));

        Assert.Equal(options.ClientCertificateRequired, clonedOptions.ClientCertificateRequired);
        Assert.True(propertyNames.Remove(nameof(options.ClientCertificateRequired)));

        Assert.Equal(options.EnabledSslProtocols, clonedOptions.EnabledSslProtocols);
        Assert.True(propertyNames.Remove(nameof(options.EnabledSslProtocols)));

        Assert.Equal(options.EncryptionPolicy, clonedOptions.EncryptionPolicy);
        Assert.True(propertyNames.Remove(nameof(options.EncryptionPolicy)));

        Assert.Same(options.RemoteCertificateValidationCallback, clonedOptions.RemoteCertificateValidationCallback);
        Assert.True(propertyNames.Remove(nameof(options.RemoteCertificateValidationCallback)));

        // Technically the ServerCertificate could be reset/reimported, but I'm hoping this is uncommon. Trying to clone the certificate and/or context seems risky.
        Assert.Same(options.ServerCertificate, clonedOptions.ServerCertificate);
        Assert.True(propertyNames.Remove(nameof(options.ServerCertificate)));

        Assert.Same(options.ServerCertificateContext, clonedOptions.ServerCertificateContext);
        Assert.True(propertyNames.Remove(nameof(options.ServerCertificateContext)));

        Assert.Same(options.ServerCertificateSelectionCallback, clonedOptions.ServerCertificateSelectionCallback);
        Assert.True(propertyNames.Remove(nameof(options.ServerCertificateSelectionCallback)));

        // Ensure we've checked every property. When new properties get added, we'll have to update this test along with the CloneSslOptions implementation.
        Assert.Empty(propertyNames);
    }
        private async Task <IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterContext context)
        {
            SslStream sslStream;
            bool      certificateRequired;
            var       feature = new TlsConnectionFeature();

            context.Features.Set <ITlsConnectionFeature>(feature);

            if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate)
            {
                sslStream           = new SslStream(context.ConnectionStream);
                certificateRequired = false;
            }
            else
            {
                sslStream = new SslStream(context.ConnectionStream,
                                          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 timeoutFeature = context.Features.Get <IConnectionTimeoutFeature>();

            timeoutFeature.SetTimeout(_options.HandshakeTimeout);

            try
            {
#if NETCOREAPP2_1
                var sslOptions = new SslServerAuthenticationOptions()
                {
                    ServerCertificate              = _serverCertificate,
                    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);
                }

                if ((_options.HttpProtocols & HttpProtocols.Http1) != 0)
                {
                    sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11);
                }

                await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None);
#else
                await sslStream.AuthenticateAsServerAsync(_serverCertificate, certificateRequired,
                                                          _options.SslProtocols, _options.CheckCertificateRevocation);
#endif
            }
            catch (OperationCanceledException)
            {
                _logger?.LogInformation(2, CoreStrings.AuthenticationTimedOut);
                sslStream.Dispose();
                return(_closedAdaptedConnection);
            }
            catch (IOException ex)
            {
                _logger?.LogInformation(1, ex, CoreStrings.AuthenticationFailed);
                sslStream.Dispose();
                return(_closedAdaptedConnection);
            }
            finally
            {
                timeoutFeature.CancelTimeout();
            }

#if NETCOREAPP2_1
            feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol;
            context.Features.Set <ITlsApplicationProtocolFeature>(feature);
#endif
            feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate);

            return(new HttpsAdaptedConnection(sslStream));
        }
        public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
        {
            using (X509Certificate2 clientCert = Configuration.Certificates.GetClientCertificate())
                using (X509Certificate2 serverCert = Configuration.Certificates.GetServerCertificate())
                {
                    // Values used to populate client options
                    bool clientAllowRenegotiation = false;
                    List <SslApplicationProtocol> clientAppProtocols = new List <SslApplicationProtocol> {
                        SslApplicationProtocol.Http11
                    };
                    X509RevocationMode        clientRevocation   = X509RevocationMode.NoCheck;
                    X509CertificateCollection clientCertificates = new X509CertificateCollection()
                    {
                        clientCert
                    };
                    SslProtocols     clientSslProtocols = SslProtocols.Tls12;
                    EncryptionPolicy clientEncryption   = EncryptionPolicy.RequireEncryption;
                    LocalCertificateSelectionCallback   clientLocalCallback  = new LocalCertificateSelectionCallback(delegate { return(null); });
                    RemoteCertificateValidationCallback clientRemoteCallback = new RemoteCertificateValidationCallback(delegate { return(true); });
                    string clientHost = serverCert.GetNameInfo(X509NameType.SimpleName, false);

                    // Values used to populate server options
                    bool serverAllowRenegotiation = true;
                    List <SslApplicationProtocol> serverAppProtocols = new List <SslApplicationProtocol> {
                        SslApplicationProtocol.Http11, SslApplicationProtocol.Http2
                    };
                    X509RevocationMode serverRevocation = X509RevocationMode.NoCheck;
                    bool serverCertRequired             = false;
#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
                    SslProtocols serverSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12;
#pragma warning restore SYSLIB0039
#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
                    EncryptionPolicy serverEncryption = EncryptionPolicy.AllowNoEncryption;
#pragma warning restore SYSLIB0040
                    RemoteCertificateValidationCallback serverRemoteCallback = new RemoteCertificateValidationCallback(delegate { return(true); });
                    SslStreamCertificateContext         certificateContext   = SslStreamCertificateContext.Create(serverCert, null, false);

                    (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams();
                    using (var client = new SslStream(stream1))
                        using (var server = new SslStream(stream2))
                        {
                            // Create client options
                            var clientOptions = new SslClientAuthenticationOptions
                            {
                                AllowRenegotiation             = clientAllowRenegotiation,
                                ApplicationProtocols           = clientAppProtocols,
                                CertificateRevocationCheckMode = clientRevocation,
                                ClientCertificates             = clientCertificates,
                                EnabledSslProtocols            = clientSslProtocols,
                                EncryptionPolicy = clientEncryption,
                                LocalCertificateSelectionCallback   = clientLocalCallback,
                                RemoteCertificateValidationCallback = clientRemoteCallback,
                                TargetHost = clientHost
                            };

                            // Create server options
                            var serverOptions = new SslServerAuthenticationOptions
                            {
                                AllowRenegotiation             = serverAllowRenegotiation,
                                ApplicationProtocols           = serverAppProtocols,
                                CertificateRevocationCheckMode = serverRevocation,
                                ClientCertificateRequired      = serverCertRequired,
                                EnabledSslProtocols            = serverSslProtocols,
                                EncryptionPolicy = serverEncryption,
                                RemoteCertificateValidationCallback = serverRemoteCallback,
                                ServerCertificate        = serverCert,
                                ServerCertificateContext = certificateContext,
                            };

                            // Authenticate
                            Task clientTask = client.AuthenticateAsClientAsync(TestAuthenticateAsync, clientOptions);
                            Task serverTask = server.AuthenticateAsServerAsync(TestAuthenticateAsync, serverOptions);
                            await new[] { clientTask, serverTask }.WhenAllOrAnyFailed();

                            // Validate that client options are unchanged
                            Assert.Equal(clientAllowRenegotiation, clientOptions.AllowRenegotiation);
                            Assert.Same(clientAppProtocols, clientOptions.ApplicationProtocols);
                            Assert.Equal(1, clientOptions.ApplicationProtocols.Count);
                            Assert.Equal(clientRevocation, clientOptions.CertificateRevocationCheckMode);
                            Assert.Same(clientCertificates, clientOptions.ClientCertificates);
                            Assert.Contains(clientCert, clientOptions.ClientCertificates.Cast <X509Certificate2>());
                            Assert.Equal(clientSslProtocols, clientOptions.EnabledSslProtocols);
                            Assert.Equal(clientEncryption, clientOptions.EncryptionPolicy);
                            Assert.Same(clientLocalCallback, clientOptions.LocalCertificateSelectionCallback);
                            Assert.Same(clientRemoteCallback, clientOptions.RemoteCertificateValidationCallback);
                            Assert.Same(clientHost, clientOptions.TargetHost);

                            // Validate that server options are unchanged
                            Assert.Equal(serverAllowRenegotiation, serverOptions.AllowRenegotiation);
                            Assert.Same(serverAppProtocols, serverOptions.ApplicationProtocols);
                            Assert.Equal(2, serverOptions.ApplicationProtocols.Count);
                            Assert.Equal(clientRevocation, serverOptions.CertificateRevocationCheckMode);
                            Assert.Equal(serverCertRequired, serverOptions.ClientCertificateRequired);
                            Assert.Equal(serverSslProtocols, serverOptions.EnabledSslProtocols);
                            Assert.Equal(serverEncryption, serverOptions.EncryptionPolicy);
                            Assert.Same(serverRemoteCallback, serverOptions.RemoteCertificateValidationCallback);
                            Assert.Same(serverCert, serverOptions.ServerCertificate);
                            Assert.Same(certificateContext, serverOptions.ServerCertificateContext);
                        }
                }
        }
Exemple #11
0
        private async Task InnerOnConnectionAsync(ConnectionContext context)
        {
            bool certificateRequired;
            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))
                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);
                        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;
            }
        }
        public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndPoint endpoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
        {
            if (!QuicImplementationProviders.Default.IsSupported)
            {
                throw new NotSupportedException("QUIC is not supported or enabled on this platform. See https://aka.ms/aspnet/kestrel/http3reqs for details.");
            }

            _log     = log;
            _context = new QuicTransportContext(_log, options);
            var quicListenerOptions = new QuicListenerOptions();

            var listenEndPoint = endpoint as IPEndPoint;

            if (listenEndPoint == null)
            {
                throw new InvalidOperationException($"QUIC doesn't support listening on the configured endpoint type. Expected {nameof(IPEndPoint)} but got {endpoint.GetType().Name}.");
            }

            // Workaround for issue in System.Net.Quic
            // https://github.com/dotnet/runtime/issues/57241
            if (listenEndPoint.Address.Equals(IPAddress.Any) && listenEndPoint.Address != IPAddress.Any)
            {
                listenEndPoint = new IPEndPoint(IPAddress.Any, listenEndPoint.Port);
            }
            if (listenEndPoint.Address.Equals(IPAddress.IPv6Any) && listenEndPoint.Address != IPAddress.IPv6Any)
            {
                listenEndPoint = new IPEndPoint(IPAddress.IPv6Any, listenEndPoint.Port);
            }

            quicListenerOptions.ServerAuthenticationOptions = sslServerAuthenticationOptions;
            quicListenerOptions.ListenEndPoint           = listenEndPoint;
            quicListenerOptions.IdleTimeout              = options.IdleTimeout;
            quicListenerOptions.MaxBidirectionalStreams  = options.MaxBidirectionalStreamCount;
            quicListenerOptions.MaxUnidirectionalStreams = options.MaxUnidirectionalStreamCount;
            quicListenerOptions.ListenBacklog            = options.Backlog;

            _listener = new QuicListener(quicListenerOptions);

            // Listener endpoint will resolve an ephemeral port, e.g. 127.0.0.1:0, into the actual port.
            EndPoint = _listener.ListenEndPoint;
        }
Exemple #13
0
 internal abstract QuicListenerProvider CreateListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions);
Exemple #14
0
        private async Task InnerOnConnectionAsync(ConnectionContext context)
        {
            bool certificateRequired;
            var  feature = new 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.RemoteCertificateMode == RemoteCertificateMode.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.RemoteCertificateMode != RemoteCertificateMode.RequireCertificate);
                    }

                    if (_options.RemoteCertificateValidation == null)
                    {
                        if (sslPolicyErrors != SslPolicyErrors.None)
                        {
                            return(false);
                        }
                    }

                    var certificate2 = ConvertToX509Certificate2(certificate);
                    if (certificate2 == null)
                    {
                        return(false);
                    }

                    if (_options.RemoteCertificateValidation != null)
                    {
                        if (!_options.RemoteCertificateValidation(certificate2, chain, sslPolicyErrors))
                        {
                            return(false);
                        }
                    }

                    return(true);
                }));

                certificateRequired = true;
            }

            var sslStream = sslDuplexPipe.Stream;

            using (var cancellationTokeSource = new CancellationTokenSource(Debugger.IsAttached ? Timeout.InfiniteTimeSpan : _options.HandshakeTimeout))
            {
                try
                {
                    // Adapt to the SslStream signature
                    ServerCertificateSelectionCallback selector = null;
                    if (_certificateSelector != null)
                    {
                        selector = (sender, name) =>
                        {
                            context.Features.Set(sslStream);
                            var cert = _certificateSelector(context, name);
                            if (cert != null)
                            {
                                EnsureCertificateIsAllowedForServerAuth(cert);
                            }
                            return(cert);
                        };
                    }

                    var sslOptions = new SslServerAuthenticationOptions
                    {
                        ServerCertificate = _certificate,
                        ServerCertificateSelectionCallback = selector,
                        ClientCertificateRequired          = certificateRequired,
                        EnabledSslProtocols            = _options.SslProtocols,
                        CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
                        ApplicationProtocols           = new List <SslApplicationProtocol>()
                    };

                    _options.OnAuthenticateAsServer?.Invoke(context, sslOptions);

                    await sslStream.AuthenticateAsServerAsync(sslOptions, cancellationTokeSource.Token).ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    _logger?.LogDebug(2, "Authentication timed out");
                    await sslStream.DisposeAsync().ConfigureAwait(false);

                    return;
                }
                catch (Exception ex) when(ex is IOException || ex is AuthenticationException)
                {
                    _logger?.LogDebug(1, ex, "Authentication failed");
                    await sslStream.DisposeAsync().ConfigureAwait(false);

                    return;
                }
            }

            feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol;
            context.Features.Set <ITlsApplicationProtocolFeature>(feature);
            feature.LocalCertificate     = ConvertToX509Certificate2(sslStream.LocalCertificate);
            feature.RemoteCertificate    = 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).ConfigureAwait(false);

                        // 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;
            }
        }
Exemple #15
0
        private async Task DoHandshakeWithOptions(SslStream clientSslStream, SslStream serverSslStream, SslClientAuthenticationOptions clientOptions, SslServerAuthenticationOptions serverOptions)
        {
            using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate())
            {
                clientOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate;
                clientOptions.TargetHost        = certificate.GetNameInfo(X509NameType.SimpleName, false);
                serverOptions.ServerCertificate = certificate;

                Task t1 = clientSslStream.AuthenticateAsClientAsync(clientOptions, CancellationToken.None);
                Task t2 = serverSslStream.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);

                await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);
            }
        }
        private bool DoHandshakeWithOptions(SslStream clientSslStream, SslStream serverSslStream, SslClientAuthenticationOptions clientOptions, SslServerAuthenticationOptions serverOptions)
        {
            using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate())
            {
                clientOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate;
                clientOptions.TargetHost        = certificate.GetNameInfo(X509NameType.SimpleName, false);
                serverOptions.ServerCertificate = certificate;

                Task t1 = clientSslStream.AuthenticateAsClientAsync(clientOptions, CancellationToken.None);
                Task t2 = serverSslStream.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);

                return(Task.WaitAll(new[] { t1, t2 }, TestConfiguration.PassingTestTimeoutMilliseconds));
            }
        }
Exemple #17
0
        /// <summary>
        ///     This is called when client is aware of proxy
        ///     So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
        /// </summary>
        /// <param name="endPoint">The explicit endpoint.</param>
        /// <param name="clientConnection">The client connection.</param>
        /// <returns>The task.</returns>
        internal async Task handleClient(ExplicitProxyEndPoint endPoint, RequestStateBase state)
        {
            TcpClientConnection clientConnection = state.ClientConnection;
            var cancellationTokenSource          = new CancellationTokenSource();
            var cancellationToken = cancellationTokenSource.Token;

            var clientStream = new HttpClientStream(clientConnection, clientConnection.GetStream(), BufferPool);

            Task <TcpServerConnection>?prefetchConnectionTask = null;
            bool closeServerConnection = false;
            bool calledRequestHandler  = false;

            SslStream?sslStream = null;

            try
            {
                TunnelConnectSessionEventArgs?connectArgs = null;

                var method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken);

                if (clientStream.IsClosed)
                {
                    return;
                }

                // Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
                if (method == KnownMethod.Connect)
                {
                    // read the first line HTTP command
                    var requestLine = await clientStream.ReadRequestLine(cancellationToken);

                    if (requestLine.IsEmpty())
                    {
                        return;
                    }

                    var connectRequest = new ConnectRequest(requestLine.RequestUri)
                    {
                        RequestUriString8 = requestLine.RequestUri,
                        HttpVersion       = requestLine.Version
                    };

                    await HeaderParser.ReadHeaders(clientStream, connectRequest.Headers, cancellationToken);

                    connectArgs = new TunnelConnectSessionEventArgs(state, endPoint, connectRequest,
                                                                    clientConnection, clientStream, cancellationTokenSource);
                    clientStream.DataRead  += (o, args) => connectArgs.OnDataSent(args.Buffer, args.Offset, args.Count);
                    clientStream.DataWrite += (o, args) => connectArgs.OnDataReceived(args.Buffer, args.Offset, args.Count);

                    await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc);

                    // filter out excluded host names
                    bool decryptSsl  = endPoint.DecryptSsl && connectArgs.DecryptSsl;
                    bool sendRawData = !decryptSsl;

                    if (connectArgs.DenyConnect)
                    {
                        if (connectArgs.HttpClient.Response.StatusCode == 0)
                        {
                            connectArgs.HttpClient.Response = new Response
                            {
                                HttpVersion       = HttpHeader.Version11,
                                StatusCode        = (int)HttpStatusCode.Forbidden,
                                StatusDescription = "Forbidden"
                            };
                        }

                        // send the response
                        await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken);

                        return;
                    }

                    if (await checkAuthorization(connectArgs) == false)
                    {
                        await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc);

                        // send the response
                        await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken);

                        return;
                    }

                    // write back successful CONNECT response
                    var response = ConnectResponse.CreateSuccessfulConnectResponse(requestLine.Version);

                    // Set ContentLength explicitly to properly handle HTTP 1.0
                    response.ContentLength = 0;
                    response.Headers.FixProxyHeaders();
                    connectArgs.HttpClient.Response = response;

                    await clientStream.WriteResponseAsync(response, cancellationToken);

                    var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken);

                    if (clientStream.IsClosed)
                    {
                        return;
                    }

                    bool isClientHello = clientHelloInfo != null;
                    if (clientHelloInfo != null)
                    {
                        connectRequest.TunnelType      = TunnelType.Https;
                        connectRequest.ClientHelloInfo = clientHelloInfo;
                    }

                    await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc, isClientHello);

                    if (decryptSsl && clientHelloInfo != null)
                    {
                        connectRequest.IsHttps = true; // todo: move this line to the previous "if"
                        clientStream.Connection.SslProtocol = clientHelloInfo.SslProtocol;

                        bool http2Supported = false;

                        if (EnableHttp2)
                        {
                            var alpn = clientHelloInfo.GetAlpn();
                            if (alpn != null && alpn.Contains(SslApplicationProtocol.Http2))
                            {
                                // test server HTTP/2 support
                                try
                                {
                                    // todo: this is a hack, because Titanium does not support HTTP protocol changing currently
                                    var connection = await tcpConnectionFactory.GetServerConnection(state,
                                                                                                    true, SslExtensions.Http2ProtocolAsList,
                                                                                                    true, cancellationToken);

                                    http2Supported = connection.NegotiatedApplicationProtocol ==
                                                     SslApplicationProtocol.Http2;

                                    // release connection back to pool instead of closing when connection pool is enabled.
                                    await tcpConnectionFactory.Release(connection, true);
                                }
                                catch (Exception)
                                {
                                    // ignore
                                }
                            }
                        }

                        if (EnableTcpServerConnectionPrefetch)
                        {
                            IPAddress[]? ipAddresses = null;
                            try
                            {
                                // make sure the host can be resolved before creating the prefetch task
                                ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host);
                            }
                            catch (SocketException) { }

                            if (ipAddresses != null && ipAddresses.Length > 0)
                            {
                                // don't pass cancellation token here
                                // it could cause floating server connections when client exits
                                prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(state,
                                                                                                  true, null, false,
                                                                                                  CancellationToken.None);
                            }
                        }

                        string connectHostname = requestLine.RequestUri.GetString();
                        int    idx             = connectHostname.IndexOf(":");
                        if (idx >= 0)
                        {
                            connectHostname = connectHostname.Substring(0, idx);
                        }

                        X509Certificate2?certificate = null;
                        try
                        {
                            sslStream = new SslStream(clientStream, false);

                            string certName = HttpHelper.GetWildCardDomainName(connectHostname);
                            certificate = endPoint.GenericCertificate ??
                                          await CertificateManager.CreateServerCertificate(certName);

                            // Successfully managed to authenticate the client using the fake certificate
                            var options = new SslServerAuthenticationOptions();
                            if (EnableHttp2 && http2Supported)
                            {
                                options.ApplicationProtocols = clientHelloInfo.GetAlpn();
                                if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0)
                                {
                                    options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
                                }
                            }

                            options.ServerCertificate              = certificate;
                            options.ClientCertificateRequired      = false;
                            options.EnabledSslProtocols            = SupportedSslProtocols;
                            options.CertificateRevocationCheckMode = X509RevocationMode.NoCheck;
                            await sslStream.AuthenticateAsServerAsync(options, cancellationToken);

#if NETSTANDARD2_1
                            clientStream.Connection.NegotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif

                            // HTTPS server created - we can now decrypt the client's traffic
                            clientStream = new HttpClientStream(clientStream.Connection, sslStream, BufferPool);
                            sslStream    = null; // clientStream was created, no need to keep SSL stream reference

                            clientStream.DataRead  += (o, args) => connectArgs.OnDecryptedDataSent(args.Buffer, args.Offset, args.Count);
                            clientStream.DataWrite += (o, args) => connectArgs.OnDecryptedDataReceived(args.Buffer, args.Offset, args.Count);
                        }
                        catch (Exception e)
                        {
                            var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false);
                            throw new ProxyConnectException(
                                      $"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs);
                        }

                        method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken);

                        if (clientStream.IsClosed)
                        {
                            return;
                        }

                        if (method == KnownMethod.Invalid)
                        {
                            sendRawData = true;
                            await tcpConnectionFactory.Release(prefetchConnectionTask, true);

                            prefetchConnectionTask = null;
                        }
                    }
                    else if (clientHelloInfo == null)
                    {
                        method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken);

                        if (clientStream.IsClosed)
                        {
                            return;
                        }
                    }

                    if (cancellationTokenSource.IsCancellationRequested)
                    {
                        throw new Exception("Session was terminated by user.");
                    }

                    if (method == KnownMethod.Invalid)
                    {
                        sendRawData = true;
                    }

                    // Hostname is excluded or it is not an HTTPS connect
                    if (sendRawData)
                    {
                        // create new connection to server.
                        // If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then
                        // this connection should not be HTTPS.
                        var connection = await tcpConnectionFactory.GetServerConnection(state,
                                                                                        true, SslExtensions.Http2ProtocolAsList,
                                                                                        true, cancellationToken);

                        try
                        {
                            if (isClientHello)
                            {
                                int available = clientStream.Available;
                                if (available > 0)
                                {
                                    // send the buffered data
                                    var data = BufferPool.GetBuffer();

                                    try
                                    {
                                        // clientStream.Available should be at most BufferSize because it is using the same buffer size
                                        int read = await clientStream.ReadAsync(data, 0, available, cancellationToken);

                                        if (read != available)
                                        {
                                            throw new Exception("Internal error.");
                                        }

                                        await connection.Stream.WriteAsync(data, 0, available, true, cancellationToken);
                                    }
                                    finally
                                    {
                                        BufferPool.ReturnBuffer(data);
                                    }
                                }

                                var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream, BufferPool, cancellationToken);

                                ((ConnectResponse)connectArgs.HttpClient.Response).ServerHelloInfo = serverHelloInfo;
                            }

                            if (!clientStream.IsClosed && !connection.Stream.IsClosed)
                            {
                                await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool,
                                                        null, null, connectArgs.CancellationTokenSource, ExceptionFunc);
                            }
                        }
                        finally
                        {
                            await tcpConnectionFactory.Release(connection, true);
                        }

                        return;
                    }
                }

                if (connectArgs != null && method == KnownMethod.Pri)
                {
                    // todo
                    string?httpCmd = await clientStream.ReadLineAsync(cancellationToken);

                    if (httpCmd == "PRI * HTTP/2.0")
                    {
                        connectArgs.HttpClient.ConnectRequest !.TunnelType = TunnelType.Http2;

                        // HTTP/2 Connection Preface
                        string?line = await clientStream.ReadLineAsync(cancellationToken);

                        if (line != string.Empty)
                        {
                            throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
                        }

                        line = await clientStream.ReadLineAsync(cancellationToken);

                        if (line != "SM")
                        {
                            throw new Exception($"HTTP/2 Protocol violation. 'SM' expected, '{line}' received");
                        }

                        line = await clientStream.ReadLineAsync(cancellationToken);

                        if (line != string.Empty)
                        {
                            throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
                        }

                        var connection = await tcpConnectionFactory.GetServerConnection(state,
                                                                                        true, SslExtensions.Http2ProtocolAsList,
                                                                                        true, cancellationToken);

                        try
                        {
#if NETSTANDARD2_1
                            var connectionPreface = new ReadOnlyMemory <byte>(Http2Helper.ConnectionPreface);
                            await connection.Stream.WriteAsync(connectionPreface, cancellationToken);

                            await Http2Helper.SendHttp2(clientStream, connection.Stream,
                                                        () => new SessionEventArgs(state, endPoint, clientStream, connectArgs?.HttpClient.ConnectRequest, cancellationTokenSource)
                            {
                                UserData = connectArgs?.UserData
                            },
                                                        async args => { await onBeforeRequest(args); },
                                                        async args => { await onBeforeResponse(args); },
                                                        connectArgs.CancellationTokenSource, clientStream.Connection.Id, ExceptionFunc);
#endif
                        }
                        finally
                        {
                            await tcpConnectionFactory.Release(connection, true);
                        }
                    }
                }

                calledRequestHandler = true;

                // Now create the request
                await handleHttpSessionRequest(endPoint, state, clientStream, cancellationTokenSource, connectArgs, prefetchConnectionTask);
            }
            catch (ProxyException e)
            {
                closeServerConnection = true;
                onException(clientStream, e);
            }
            catch (IOException e)
            {
                closeServerConnection = true;
                onException(clientStream, new Exception("Connection was aborted", e));
            }
            catch (SocketException e)
            {
                closeServerConnection = true;
                onException(clientStream, new Exception("Could not connect", e));
            }
            catch (Exception e)
            {
                closeServerConnection = true;
                onException(clientStream, new Exception("Error occured in whilst handling the client", e));
            }
            finally
            {
                if (!calledRequestHandler)
                {
                    await tcpConnectionFactory.Release(prefetchConnectionTask, closeServerConnection);
                }

                sslStream?.Dispose();
                clientStream.Dispose();
            }
        }
Exemple #18
0
        public override async Task <GenericLoopbackConnection> EstablishGenericConnectionAsync()
        {
            Socket socket = await _listenSocket.AcceptAsync().ConfigureAwait(false);

            Stream stream = new NetworkStream(socket, ownsSocket: true);

            var options = new GenericLoopbackOptions()
            {
                Address       = _options.Address,
                SslProtocols  = _options.SslProtocols,
                UseSsl        = false,
                ListenBacklog = _options.ListenBacklog
            };

            GenericLoopbackConnection connection = null;

            try
            {
                if (_options.UseSsl)
                {
                    var sslStream = new SslStream(stream, false, delegate { return(true); });

                    using (X509Certificate2 cert = Configuration.Certificates.GetServerCertificate())
                    {
                        SslServerAuthenticationOptions sslOptions = new SslServerAuthenticationOptions();

                        sslOptions.EnabledSslProtocols  = _options.SslProtocols;
                        sslOptions.ApplicationProtocols = _options.SslApplicationProtocols;
                        sslOptions.ServerCertificate    = cert;

                        await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None).ConfigureAwait(false);
                    }

                    stream = sslStream;
                    if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2)
                    {
                        // Do not pass original options so the CreateConnectionAsync won't try to do ALPN again.
                        return(connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false));
                    }
                    if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http11 ||
                        sslStream.NegotiatedApplicationProtocol == default)
                    {
                        // Do not pass original options so the CreateConnectionAsync won't try to do ALPN again.
                        return(connection = await Http11LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false));
                    }
                    else
                    {
                        throw new Exception($"Unsupported negotiated protocol {sslStream.NegotiatedApplicationProtocol}");
                    }
                }

                if (_options.ClearTextVersion == HttpVersion.Version11)
                {
                    return(connection = await Http11LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false));
                }
                else if (_options.ClearTextVersion == HttpVersion.Version20)
                {
                    return(connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false));
                }
                else
                {
                    throw new Exception($"Invalid ClearTextVersion={_options.ClearTextVersion} specified");
                }
            }
            catch
            {
                if (connection is not null)
                {
                    await connection.DisposeAsync();

                    connection = null;
                }
                stream.Dispose();
                throw;
            }
            finally
            {
                if (connection != null)
                {
                    await connection.InitializeConnectionAsync().ConfigureAwait(false);
                }
            }
        }
Exemple #19
0
 // !!! TEMPORARY: Remove or make internal before shipping
 public QuicListener(QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
     : this(implementationProvider, new QuicListenerOptions() { ListenEndPoint = listenEndPoint, ServerAuthenticationOptions = sslServerAuthenticationOptions })
 {
 }
Exemple #20
0
        private async Task <IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterContext context)
        {
            SslStream sslStream;
            bool      certificateRequired;
            var       feature = new TlsConnectionFeature();

            context.Features.Set <ITlsConnectionFeature>(feature);
            context.Features.Set <ITlsHandshakeFeature>(feature);

            if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate)
            {
                sslStream           = new SslStream(context.ConnectionStream);
                certificateRequired = false;
            }
            else
            {
                sslStream = new SslStream(context.ConnectionStream,
                                          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 timeoutFeature = context.Features.Get <IConnectionTimeoutFeature>();

            timeoutFeature.SetTimeout(_options.HandshakeTimeout);

            _options.OnHandshakeStarted?.Invoke();

            try
            {
#if NETCOREAPP2_1
                // Adapt to the SslStream signature
                ServerCertificateSelectionCallback selector = null;
                if (_serverCertificateSelector != null)
                {
                    selector = (sender, name) =>
                    {
                        context.Features.Set(sslStream);
                        var cert = _serverCertificateSelector(context.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 <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);
                }

                await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None);
#elif NETSTANDARD2_0 // No ALPN support
                var serverCert = _serverCertificate;
                if (_serverCertificateSelector != null)
                {
                    context.Features.Set(sslStream);
                    serverCert = _serverCertificateSelector(context.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(_closedAdaptedConnection);
            }
            catch (Exception ex) when(ex is IOException || ex is AuthenticationException)
            {
                _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
                sslStream.Dispose();
                return(_closedAdaptedConnection);
            }
            finally
            {
                timeoutFeature.CancelTimeout();
            }

#if NETCOREAPP2_1
            feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol;
            context.Features.Set <ITlsApplicationProtocolFeature>(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;

            return(new HttpsAdaptedConnection(sslStream));
        }
Exemple #21
0
 /// <summary>
 ///     Sets the <see cref="ListenerProvider" /> on the <see cref="MinimalServer" /> to one that provides
 ///     <see cref="SslListener" />s with <see cref="TcpConnectionListener" />s, using the given
 ///     <see cref="SslServerAuthenticationOptions" />.
 /// </summary>
 /// <param name="server">the server</param>
 /// <param name="serverAuthenticationOptions">
 ///     SSL Server Authentication Options to be passed to the
 ///     <see cref="SslListener" /> constructor
 /// </param>
 public static void AddSsl(this MinimalServer server, SslServerAuthenticationOptions serverAuthenticationOptions)
 {
     server.ListenerProvider = endpoint =>
                               new SslListener(new TcpConnectionListener(endpoint), serverAuthenticationOptions);
 }
Exemple #22
0
 public SniOptions(SslServerAuthenticationOptions sslOptions, HttpProtocols httpProtocols, ClientCertificateMode clientCertificateMode)
 {
     SslOptions            = sslOptions;
     HttpProtocols         = httpProtocols;
     ClientCertificateMode = clientCertificateMode;
 }