Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTTPS Proxy Implementation (SSLStream)

Tags:

c#

proxy

https

ssl

I've written a console application that acts as a proxy server. Now I like to implement SSL as well. Do not like to decrypt any traffic. Just like a normal https proxy. I'm not sure how I should go on.

var host = text.Remove(0, connectText.Length + 1);
var hostIndex = host.IndexOf(" ", StringComparison.Ordinal);
var hostEntry = host.Remove(hostIndex).Split(new []{":"}, StringSplitOptions.None);
requestClient.Connect(hostEntry[0], Convert.ToInt32(hostEntry[1]));
requestStream = requestClient.GetStream();
var sslStream = new SslStream(requestStream, false, (x1,x2,x3,x4) => true);
sslStream.AuthenticateAsClient(hostEntry[0]);
const string sslResponse = "HTTP/1.0 200 Connection established\r\n\r\n";
var sslResponseBytes = Encoding.UTF8.GetBytes(sslResponse);
proxyStream.Write(sslResponseBytes, 0, sslResponseBytes.Length);
proxyStream.Flush();

Should I directly write everything into sslStream? What about the browser connection proxyClient. Did I need to wrap the stream as well or can I just write everything directly into proxyStream? Should I use AuthenticateAsServer and pass somehow the certificate from AuthenticateAsClient?

  1. IE issues a CONNECT request to my proxy
  2. my proxy sees that it's a CONNECT request and gets the IP: port of the destination (eg, www.hotmail.com:443)
  3. my proxy creates a new TCP connection to www.hotmail.com:443
  4. my proxy gets a SslStream from this destination and calls AuthenticateAsClient - this gives my proxy a secure connection to the Hotmail side of things
  5. my proxy then sends an "HTTP/1.0 200" message back to the browser to say that the CONNECT was successful
  6. my proxy then gets a SslStream from the browser connection and calls AuthenticateAsServer - gives my proxy a secure connection to the browser side of things

I saw this, but how AuthenticateAsServer without a fake certificate. Can I just write that like in my normal streams or should i consider something?


static void Main(string[] args)
{
    var tcpServer = new TcpListener(IPAddress.Parse("127.0.0.1"), 8080);
    tcpServer.Start();
    while (true)
    {
        var proxyClient = tcpServer.AcceptTcpClient();
        var requestClient = new TcpClient();
        var proxyStream = proxyClient.GetStream();
        NetworkStream requestStream = null;
        var bytes = new byte[proxyClient.ReceiveBufferSize];
        var hostHeaderAvailable = 0;
        int count;

        while (proxyStream.DataAvailable)
        {
            count = proxyStream.Read(bytes, 0, bytes.Length);
            if (hostHeaderAvailable == 0)
            {
                var text = Encoding.UTF8.GetString(bytes);
                const string connectText = "connect";
                const string hostText = "Host: ";
                //HTTPS NOT FULLY IMPLEMENTED YET
                if (text.ToLower().StartsWith(connectText))
                {
                    var host = text.Remove(0, connectText.Length + 1);
                    var hostIndex = host.IndexOf(" ", StringComparison.Ordinal);
                    var hostEntry = host.Remove(hostIndex).Split(new []{":"}, StringSplitOptions.None);
                    requestClient.Connect(hostEntry[0], Convert.ToInt32(hostEntry[1]));
                    requestStream = requestClient.GetStream();
                    var sslStream = new SslStream(requestStream, false, (x1,x2,x3,x4) => true);
                    sslStream.AuthenticateAsClient(hostEntry[0]);
                    const string sslResponse = "HTTP/1.0 200 Connection established\r\n\r\n";
                    var sslResponseBytes = Encoding.UTF8.GetBytes(sslResponse);
                    proxyStream.Write(sslResponseBytes, 0, sslResponseBytes.Length);
                    proxyStream.Flush();
                }
                //HTTP WORKS LIKE A CHARM
                else {
                    var hostIndex = text.IndexOf(hostText, StringComparison.Ordinal);
                    if (hostIndex < 0)
                        continue;
                    var host = text.Remove(0, hostIndex + hostText.Length);
                    hostIndex = host.IndexOf("\n", StringComparison.Ordinal);
                    if (hostIndex < 0)
                        continue;
                    host = host.Remove(hostIndex).Replace("\r", "");
                    requestClient.Connect(host, 80);
                    requestStream = requestClient.GetStream();
                }
            }
            hostHeaderAvailable++;
            if (requestClient.Connected) {
                requestStream.Write(bytes, 0, count);
            }
        }

        if (!requestClient.Connected) {
            proxyStream.Close();
            proxyClient.Close();
            continue;
        }

        var timeout = 0;
        while (!requestStream.DataAvailable) {
            if (timeout > 12)
                break;
            Thread.Sleep(500);
            timeout++;
        }

        while (requestStream.DataAvailable)
        {
            count = requestStream.Read(bytes, 0, bytes.Length);
            proxyStream.Write(bytes, 0, count);
        }
        proxyStream.Close();
        proxyClient.Close();
    }
}
like image 577
MR.ABC Avatar asked Jun 12 '14 22:06

MR.ABC


1 Answers

IE issues a CONNECT request to my proxy My proxy sees that its a CONNECT request and gets the ip:port of the destination (eg, www.hotmail.com:443) My proxy creates a new TCP connection to www.hotmail.com:443

All correct so far.

My proxy gets an SslStream from this destination and calls AuthenticateAsClient - this gives my proxy a secure connection to the hotmail side of things

No. Your proxy should use the plain-text connection you already have.

My proxy then sends an "HTTP/1.0 200" message back to the browser to say that the CONNECT was successful.

Correct. Or else if you got a connection failure you send back an appropriate HTTP failure response.

My proxy then gets an SslStream from the browser connection and calls AuthenticateAsServer - gives my proxy a secure connection to the browser side of things

No. Your proxy continues to use the plaintext connection to the browser.

how AuthenticateAsServer without fake certificate?

You don't have to do it at all.

At this point the browser and the upstream server are ready to execute the SSL handshake. But as you said you don't want to sniff the contents, you have no need to be an SSL endpoint yourself. All you have to do now is copy bytes in both directions simultaneously. The endpoints will SSL-handshake just as though you weren't there.

like image 186
user207421 Avatar answered Oct 08 '22 06:10

user207421