Exemplo n.º 1
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"></param>
        /// <param name="tcpClient"></param>
        /// <returns></returns>
        private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
        {
            bool disposed = false;

            var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);

            var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
            var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);

            Uri httpRemoteUri;

            try
            {
                //read the first line HTTP command
                string httpCmd = await clientStreamReader.ReadLineAsync();

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

                string  httpMethod;
                string  httpUrl;
                Version version;
                Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version);

                httpRemoteUri = httpMethod == "CONNECT" ? new Uri("http://" + httpUrl) : new Uri(httpUrl);

                //filter out excluded host names
                bool excluded = false;

                if (endPoint.ExcludedHttpsHostNameRegex != null)
                {
                    excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
                }

                if (endPoint.IncludedHttpsHostNameRegex != null)
                {
                    excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
                }

                ConnectRequest connectRequest = null;

                //Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
                if (httpMethod == "CONNECT")
                {
                    connectRequest = new ConnectRequest
                    {
                        RequestUri  = httpRemoteUri,
                        OriginalUrl = httpUrl,
                        HttpVersion = version,
                    };

                    await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers);

                    var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest);
                    connectArgs.ProxyClient.TcpClient    = tcpClient;
                    connectArgs.ProxyClient.ClientStream = clientStream;

                    if (TunnelConnectRequest != null)
                    {
                        await TunnelConnectRequest.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
                    }

                    if (await CheckAuthorization(clientStreamWriter, connectArgs) == false)
                    {
                        if (TunnelConnectResponse != null)
                        {
                            await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
                        }

                        return;
                    }

                    //write back successfull CONNECT response
                    connectArgs.WebSession.Response = ConnectResponse.CreateSuccessfullConnectResponse(version);
                    await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response);

                    var clientHelloInfo = await SslTools.PeekClientHello(clientStream);

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

                    if (TunnelConnectResponse != null)
                    {
                        connectArgs.IsHttpsConnect = isClientHello;
                        await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
                    }

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

                        SslStream sslStream = null;

                        try
                        {
                            sslStream = new SslStream(clientStream);

                            string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host);

                            var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false);

                            //Successfully managed to authenticate the client using the fake certificate
                            await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);

                            //HTTPS server created - we can now decrypt the client's traffic
                            clientStream = new CustomBufferedStream(sslStream, BufferSize);

                            clientStreamReader.Dispose();
                            clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
                            clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
                        }
                        catch
                        {
                            sslStream?.Dispose();
                            return;
                        }

                        //Now read the actual HTTPS request line
                        httpCmd = await clientStreamReader.ReadLineAsync();
                    }
                    //Hostname is excluded or it is not an HTTPS connect
                    else
                    {
                        //create new connection
                        using (var connection = await GetServerConnection(connectArgs, true))
                        {
                            try
                            {
                                if (isClientHello)
                                {
                                    if (clientStream.Available > 0)
                                    {
                                        //send the buffered data
                                        var data = new byte[clientStream.Available];
                                        await clientStream.ReadAsync(data, 0, data.Length);

                                        await connection.Stream.WriteAsync(data, 0, data.Length);

                                        await connection.Stream.FlushAsync();
                                    }

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

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

                                await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
                                                        (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
                            }
                            finally
                            {
                                UpdateServerConnectionCount(false);
                            }
                        }

                        return;
                    }
                }

                //Now create the request
                disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
                                                          httpRemoteUri.Scheme == UriSchemeHttps?httpRemoteUri.Host : null, endPoint, connectRequest);
            }
            catch (Exception e)
            {
                ExceptionFunc(new Exception("Error whilst authorizing request", e));
            }
            finally
            {
                if (!disposed)
                {
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                }
            }
        }
Exemplo n.º 2
0
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (called)
            {
                stream.Write(buffer, offset, count);
                return;
            }

            called = true;
            var ms = new MemoryStream(buffer, offset, count);

            //this can be non async, because reads from a memory stream
            var cts         = new CancellationTokenSource();
            var clientHello = SslTools.PeekClientHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).Result;

            if (clientHello != null)
            {
                // 0x00 0x10: ALPN identifier
                // 0x00 0x0e: length of ALPN data
                // 0x00 0x0c: length of ALPN data again:)
                var dataToAdd = new byte[]
                {
                    0x0, 0x10, 0x0, 0xE, 0x0, 0xC,
                    2, (byte)'h', (byte)'2',
                    8, (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)'/', (byte)'1', (byte)'.', (byte)'1'
                };

                int newByteCount = clientHello.Extensions == null ? dataToAdd.Length + 2 : dataToAdd.Length;
                var buffer2      = new byte[buffer.Length + newByteCount];

                for (int i = 0; i < buffer.Length; i++)
                {
                    buffer2[i] = buffer[i];
                }

                //this is a hacky solution, but works
                int length = (buffer[offset + 3] << 8) + buffer[offset + 4];
                length += newByteCount;
                buffer2[offset + 3] = (byte)(length >> 8);
                buffer2[offset + 4] = (byte)length;

                length  = (buffer[offset + 6] << 16) + (buffer[offset + 7] << 8) + buffer[offset + 8];
                length += newByteCount;
                buffer2[offset + 6] = (byte)(length >> 16);
                buffer2[offset + 7] = (byte)(length >> 8);
                buffer2[offset + 8] = (byte)length;

                int pos    = offset + clientHello.EntensionsStartPosition;
                int endPos = offset + clientHello.ClientHelloLength;
                if (clientHello.Extensions != null)
                {
                    // update ALPN length
                    length           = (buffer[pos] << 8) + buffer[pos + 1];
                    length          += newByteCount;
                    buffer2[pos]     = (byte)(length >> 8);
                    buffer2[pos + 1] = (byte)length;
                }
                else
                {
                    // add ALPN length
                    length           = dataToAdd.Length;
                    buffer2[pos]     = (byte)(length >> 8);
                    buffer2[pos + 1] = (byte)length;
                    endPos          += 2;
                }

                for (int i = 0; i < dataToAdd.Length; i++)
                {
                    buffer2[endPos + i] = dataToAdd[i];
                }

                // copy the reamining data if any
                for (int i = clientHello.ClientHelloLength; i < count; i++)
                {
                    buffer2[offset + newByteCount + i] = buffer[offset + i];
                }

                buffer = buffer2;
                count += newByteCount;
            }

            stream.Write(buffer, offset, count);
        }
        /// <summary>
        ///     This is called when this proxy acts as a reverse proxy (like a real http server).
        ///     So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
        /// </summary>
        /// <param name="endPoint">The transparent endpoint.</param>
        /// <param name="clientConnection">The client connection.</param>
        /// <returns></returns>
        private async Task handleClient(TransparentProxyEndPoint 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);

            SslStream sslStream = null;

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

                bool   isHttps       = clientHelloInfo != null;
                string httpsHostName = null;

                if (isHttps)
                {
                    httpsHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName;

                    var args = new BeforeSslAuthenticateEventArgs(cancellationTokenSource)
                    {
                        SniHostName = httpsHostName
                    };

                    await endPoint.InvokeBeforeSslAuthenticate(this, args, ExceptionFunc);

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

                    if (endPoint.DecryptSsl && args.DecryptSsl)
                    {
                        //do client authentication using certificate
                        X509Certificate2 certificate = null;
                        try
                        {
                            sslStream = new SslStream(clientStream, false);

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

                            // Successfully managed to authenticate the client using the certificate
                            await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);

                            // 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);
                            var session  = new SessionEventArgs(this, endPoint, cancellationTokenSource)
                            {
                                ProxyClient = { Connection = clientConnection },
                                HttpClient  = { ConnectRequest = null }
                            };
                            throw new ProxyConnectException(
                                      $"Couldn't authenticate host '{httpsHostName}' with certificate '{certname}'.", e, session);
                        }
                    }
                    else
                    {
                        var connection = await tcpConnectionFactory.GetServerConnection(httpsHostName, endPoint.Port,
                                                                                        httpVersion : null, isHttps : false, applicationProtocols : null,
                                                                                        isConnect : true, proxyServer : this, session : null, upStreamEndPoint : UpStreamEndPoint,
                                                                                        externalProxy : UpStreamHttpsProxy, noCache : true, cancellationToken : cancellationToken);

                        try
                        {
                            CustomBufferedStream serverStream = null;
                            int available = clientStream.Available;

                            if (available > 0)
                            {
                                // send the buffered data
                                var data = BufferPool.GetBuffer(BufferSize);
                                try
                                {
                                    // clientStream.Available should be at most BufferSize because it is using the same buffer size
                                    await clientStream.ReadAsync(data, 0, available, cancellationToken);

                                    serverStream = connection.Stream;
                                    await serverStream.WriteAsync(data, 0, available, cancellationToken);

                                    await serverStream.FlushAsync(cancellationToken);
                                }
                                finally
                                {
                                    BufferPool.ReturnBuffer(data);
                                }
                            }

                            await TcpHelper.SendRaw(clientStream, serverStream, BufferPool, BufferSize,
                                                    null, null, cancellationTokenSource, ExceptionFunc);
                        }
                        finally
                        {
                            await tcpConnectionFactory.Release(connection, true);
                        }

                        return;
                    }
                }
                // HTTPS server created - we can now decrypt the client's traffic
                // Now create the request
                await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter,
                                               cancellationTokenSource, isHttps?httpsHostName : null, null, null);
            }
            catch (ProxyException e)
            {
                onException(clientStream, e);
            }
            catch (IOException e)
            {
                onException(clientStream, new Exception("Connection was aborted", e));
            }
            catch (SocketException e)
            {
                onException(clientStream, new Exception("Could not connect", e));
            }
            catch (Exception e)
            {
                onException(clientStream, new Exception("Error occured in whilst handling the client", e));
            }
            finally
            {
                sslStream?.Dispose();
                clientStream.Dispose();

                if (!cancellationTokenSource.IsCancellationRequested)
                {
                    cancellationTokenSource.Cancel();
                }
            }
        }
Exemplo n.º 4
0
        private async Task handleClient(TransparentBaseProxyEndPoint endPoint, TcpClientConnection clientConnection,
                                        int port, CancellationTokenSource cancellationTokenSource, CancellationToken cancellationToken)
        {
            bool isHttps      = false;
            var  clientStream = new HttpClientStream(clientConnection, clientConnection.GetStream(), BufferPool, cancellationToken);

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

                if (clientHelloInfo != null)
                {
                    var httpsHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName;

                    var args = new BeforeSslAuthenticateEventArgs(clientConnection, cancellationTokenSource, httpsHostName);

                    await endPoint.InvokeBeforeSslAuthenticate(this, args, ExceptionFunc);

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

                    if (endPoint.DecryptSsl && args.DecryptSsl)
                    {
                        clientStream.Connection.SslProtocol = clientHelloInfo.SslProtocol;

                        // do client authentication using certificate
                        X509Certificate2?certificate = null;
                        SslStream?       sslStream   = null;
                        try
                        {
                            sslStream = new SslStream(clientStream, false);

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

                            // Successfully managed to authenticate the client using the certificate
                            await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls12, false);

                            // HTTPS server created - we can now decrypt the client's traffic
                            clientStream = new HttpClientStream(clientStream.Connection, sslStream, BufferPool, cancellationToken);
                            sslStream    = null; // clientStream was created, no need to keep SSL stream reference
                            isHttps      = true;
                        }
                        catch (Exception e)
                        {
                            sslStream?.Dispose();

                            var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false);
                            var session  = new SessionEventArgs(this, endPoint, clientStream, null, cancellationTokenSource);
                            throw new ProxyConnectException(
                                      $"Couldn't authenticate host '{httpsHostName}' with certificate '{certName}'.", e, session);
                        }
                    }
                    else
                    {
                        var sessionArgs = new SessionEventArgs(this, endPoint, clientStream, null, cancellationTokenSource);
                        var connection  = await tcpConnectionFactory.GetServerConnection(this, httpsHostName, port,
                                                                                         HttpHeader.VersionUnknown, false, null,
                                                                                         true, sessionArgs, UpStreamEndPoint,
                                                                                         UpStreamHttpsProxy, true, cancellationToken);

                        try
                        {
                            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
                                    await clientStream.ReadAsync(data, 0, available, cancellationToken);

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

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

                        return;
                    }
                }

                // HTTPS server created - we can now decrypt the client's traffic
                // Now create the request
                await handleHttpSessionRequest(endPoint, clientStream, cancellationTokenSource, isHttps : isHttps);
            }
            catch (ProxyException e)
            {
                onException(clientStream, e);
            }
            catch (IOException e)
            {
                onException(clientStream, new Exception("Connection was aborted", e));
            }
            catch (SocketException e)
            {
                onException(clientStream, new Exception("Could not connect", e));
            }
            catch (Exception e)
            {
                onException(clientStream, new Exception("Error occured in whilst handling the client", e));
            }
            finally
            {
                clientStream.Dispose();
            }
        }
Exemplo n.º 5
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>
        private async Task handleClient(ExplicitProxyEndPoint endPoint, TcpClientConnection clientConnection)
        {
            var cancellationTokenSource = new CancellationTokenSource();
            var cancellationToken       = cancellationTokenSource.Token;

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

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

            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(this, endPoint, connectRequest, 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(this, connectArgs,
                                                                                                    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(this, connectArgs,
                                                                                                  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;
                        SslStream?       sslStream   = 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, cancellationToken);
                            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)
                        {
                            sslStream?.Dispose();

                            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(this, connectArgs,
                                                                                        true, null,
                                                                                        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(this, connectArgs,
                                                                                        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(this, 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);
                        }
                    }
                }

                var prefetchTask = prefetchConnectionTask;
                prefetchConnectionTask = null;

                // Now create the request
                await handleHttpSessionRequest(endPoint, clientStream, cancellationTokenSource, connectArgs, prefetchTask);
            }
            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 (!cancellationTokenSource.IsCancellationRequested)
                {
                    cancellationTokenSource.Cancel();
                }

                await tcpConnectionFactory.Release(prefetchConnectionTask, closeServerConnection);

                clientStream.Dispose();
            }
        }
Exemplo n.º 6
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>
        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) == 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.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc);

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

                        return;
                    }

                    // write back successfull CONNECT response
                    var response = ConnectResponse.CreateSuccessfullConnectResponse(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.ClientHelloInfo = clientHelloInfo;
                    }

                    await endPoint.InvokeBeforeTunnectConnectResponse(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
                            // 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 intead of closing when connection pool is enabled.
                            await tcpConnectionFactory.Release(connection, true);
                        }

                        if (EnableTcpServerConnectionPrefetch)
                        {
                            //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, true);

                            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 (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) == -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)
                    {
                        // 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 sbould 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) == 1)
                {
                    // todo
                    string httpCmd = await clientStream.ReadLineAsync(cancellationToken);

                    if (httpCmd == "PRI * HTTP/2.0")
                    {
                        // 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); },
                                                        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();
                }
            }
        }
Exemplo n.º 7
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"></param>
        /// <param name="tcpClient"></param>
        /// <returns></returns>
        private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
        {
            var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);

            var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
            var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);

            try
            {
                string connectHostname = null;

                ConnectRequest connectRequest = null;

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

                    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;

                    //filter out excluded host names
                    bool excluded = false;

                    if (endPoint.BeforeTunnelConnect != null)
                    {
                        excluded = await endPoint.BeforeTunnelConnect(connectHostname);
                    }

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

                    await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers);

                    var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest, ExceptionFunc);
                    connectArgs.ProxyClient.TcpClient    = tcpClient;
                    connectArgs.ProxyClient.ClientStream = clientStream;

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


                    if (await CheckAuthorization(clientStreamWriter, connectArgs) == false)
                    {
                        await endPoint.InvokeTunnectConnectResponse(this, connectArgs, ExceptionFunc);

                        return;
                    }

                    //write back successfull CONNECT response
                    var response = ConnectResponse.CreateSuccessfullConnectResponse(version);
                    response.Headers.FixProxyHeaders();
                    connectArgs.WebSession.Response = response;

                    await clientStreamWriter.WriteResponseAsync(response);

                    var clientHelloInfo = await SslTools.PeekClientHello(clientStream);

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

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

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

                        SslStream sslStream = null;

                        try
                        {
                            sslStream = new SslStream(clientStream);

                            string certName = HttpHelper.GetWildCardDomainName(connectHostname);

                            var certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateCertificateAsync(certName);

                            //Successfully managed to authenticate the client using the fake certificate
                            await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);

                            //HTTPS server created - we can now decrypt the client's traffic
                            clientStream = new CustomBufferedStream(sslStream, BufferSize);

                            clientStreamReader.Dispose();
                            clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
                            clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
                        }
                        catch
                        {
                            sslStream?.Dispose();
                            return;
                        }

                        if (await HttpHelper.IsConnectMethod(clientStream) == -1)
                        {
                            // It can be for example some Google (Cloude Messaging for Chrome) magic
                            excluded = true;
                        }
                    }

                    //Hostname is excluded or it is not an HTTPS connect
                    if (excluded || !isClientHello)
                    {
                        //create new connection
                        using (var connection = await GetServerConnection(connectArgs, true))
                        {
                            if (isClientHello)
                            {
                                int available = clientStream.Available;
                                if (available > 0)
                                {
                                    //send the buffered data
                                    var data = BufferPool.GetBuffer(BufferSize);

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

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

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

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

                            await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
                                                    (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
                                                    (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
                                                    ExceptionFunc);
                        }

                        return;
                    }
                }

                //Now create the request
                await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, connectHostname, endPoint, connectRequest);
            }
            catch (ProxyHttpException e)
            {
                ExceptionFunc(e);
            }
            catch (IOException e)
            {
                ExceptionFunc(new Exception("Connection was aborted", e));
            }
            catch (SocketException e)
            {
                ExceptionFunc(new Exception("Could not connect", e));
            }
            catch (Exception e)
            {
                ExceptionFunc(new Exception("Error occured in whilst handling the client", e));
            }
            finally
            {
                clientStreamReader.Dispose();
                clientStream.Dispose();
            }
        }
        private async Task <IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterContext context)
        {
            // We start off by handing the connection stream off to a library that can do a peek read
            // (which is really just doing buffering tricks, not an actual peek read).
            var yourClientStream = new CustomBufferedStream(context.ConnectionStream, 4096);

            // We then use the same lib to parse the "peeked" data and extract the SNI hostname.
            var clientSslHelloInfo = await SslTools.PeekClientHello(yourClientStream);

            switch (clientSslHelloInfo != null)
            {
            case true:
            {
                string sniHost = clientSslHelloInfo.Extensions?.FirstOrDefault(x => x.Name == "server_name")?.Data;

                if (string.IsNullOrEmpty(sniHost) || string.IsNullOrWhiteSpace(sniHost))
                {
                    LoggerProxy.Default.Error("Failed to extract SNI hostname.");
                    return(s_closedConnection);
                }

                try
                {
                    var sslStream = new SslStream(yourClientStream, true,
                                                  (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) =>
                        {
                            // TODO - Handle client certificates. They should be pushed to the
                            // upstream connection eventually.
                            if (certificate != null)
                            {
                                LoggerProxy.Default.Info("CLIENT CERTIFICATE AVAILABLE!!!!!!!!!!!!!");
                            }

                            return(true);
                        }
                                                  );

                    // Spoof a cert for the extracted SNI hostname.
                    var spoofedCert = m_certStore.GetSpoofedCertificateForHost(sniHost);

                    try
                    {
                        // Try to handshake.
                        await sslStream.AuthenticateAsServerAsync(spoofedCert, false, s_allowedTlsProtocols, false);
                    }
                    catch (OperationCanceledException oe)
                    {
                        LoggerProxy.Default.Error("Failed to complete client TLS handshake because the operation was cancelled.");

                        LoggerProxy.Default.Error(oe);

                        sslStream.Dispose();
                        return(s_closedConnection);
                    }
                    catch (IOException ex)
                    {
                        LoggerProxy.Default.Error("Failed to complete client TLS handshake because of IO exception.");

                        LoggerProxy.Default.Error(ex);

                        sslStream.Dispose();
                        return(s_closedConnection);
                    }

                    // Always set the feature even though the cert might be null
                    context.Features.Set <ITlsConnectionFeature>(new TlsConnectionFeature
                        {
                            ClientCertificate = sslStream.RemoteCertificate != null ? sslStream.RemoteCertificate.ToV2Certificate() : null
                        });

                    return(new HttpsAdaptedConnection(sslStream));
                }
                catch (Exception err)
                {
                    LoggerProxy.Default.Error("Failed to complete client TLS handshake because of unknown exception.");

                    LoggerProxy.Default.Error(err);
                }

                return(s_closedConnection);
            }

            default:
            {
                return(s_closedConnection);
            }
            }
        }
        /// <summary>
        ///     This is called when this proxy acts as a reverse proxy (like a real http server).
        ///     So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
        /// </summary>
        /// <param name="endPoint">The transparent endpoint.</param>
        /// <param name="clientConnection">The client connection.</param>
        /// <returns></returns>
        private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClientConnection clientConnection)
        {
            var cancellationTokenSource = new CancellationTokenSource();
            var cancellationToken       = cancellationTokenSource.Token;

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

            var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);

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

                bool   isHttps       = clientHelloInfo != null;
                string httpsHostName = null;

                if (isHttps)
                {
                    httpsHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName;

                    var args = new BeforeSslAuthenticateEventArgs(cancellationTokenSource)
                    {
                        SniHostName = httpsHostName
                    };

                    await endPoint.InvokeBeforeSslAuthenticate(this, args, ExceptionFunc);

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

                    if (endPoint.DecryptSsl && args.DecryptSsl)
                    {
                        SslStream sslStream = null;

                        try
                        {
                            sslStream = new SslStream(clientStream);

                            string certName    = HttpHelper.GetWildCardDomainName(httpsHostName);
                            var    certificate = await CertificateManager.CreateCertificateAsync(certName);

                            // Successfully managed to authenticate the client using the fake certificate
                            await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);

                            // HTTPS server created - we can now decrypt the client's traffic
                            clientStream = new CustomBufferedStream(sslStream, BufferSize);

                            clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
                        }
                        catch (Exception e)
                        {
                            sslStream?.Dispose();
                            throw new ProxyConnectException(
                                      $"Could'nt authenticate client '{httpsHostName}' with fake certificate.", e, null);
                        }
                    }
                    else
                    {
                        // create new connection
                        var connection = new TcpClient(UpStreamEndPoint);
                        await connection.ConnectAsync(httpsHostName, endPoint.Port);

                        connection.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
                        connection.SendTimeout    = ConnectionTimeOutSeconds * 1000;

                        using (connection)
                        {
                            var serverStream = connection.GetStream();

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

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

                                    await serverStream.WriteAsync(data, 0, available, cancellationToken);

                                    await serverStream.FlushAsync(cancellationToken);
                                }
                                finally
                                {
                                    BufferPool.ReturnBuffer(data);
                                }
                            }

                            ////var serverHelloInfo = await SslTools.PeekServerHello(serverStream);

                            await TcpHelper.SendRaw(clientStream, serverStream, BufferSize,
                                                    null, null, cancellationTokenSource, ExceptionFunc);
                        }
                    }
                }

                // HTTPS server created - we can now decrypt the client's traffic
                // Now create the request
                await HandleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter,
                                               cancellationTokenSource, isHttps?httpsHostName : null, null);
            }
            catch (ProxyException e)
            {
                OnException(clientStream, e);
            }
            catch (IOException e)
            {
                OnException(clientStream, new Exception("Connection was aborted", e));
            }
            catch (SocketException e)
            {
                OnException(clientStream, new Exception("Could not connect", e));
            }
            catch (Exception e)
            {
                OnException(clientStream, new Exception("Error occured in whilst handling the client", e));
            }
            finally
            {
                clientStream.Dispose();
                if (!cancellationTokenSource.IsCancellationRequested)
                {
                    cancellationTokenSource.Cancel();
                }
            }
        }