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?
- IE issues a CONNECT request to my proxy
- my proxy sees that it's 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
- my proxy gets a SslStream from this destination and calls AuthenticateAsClient - this gives my proxy a secure connection to the Hotmail side of things
- my proxy then sends an "HTTP/1.0 200" message back to the browser to say that the CONNECT was successful
- 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();
}
}
Adding to the answer from EJP, the proxy simply opens a tunnel between the end server and the client after a successful CONNECT request. The client however, needs to upgrade the socket to HTTPS and continue sending data through the same socket which was used in the CONNECT request. The proxy on the other hand just needs to maintain this tunnel between the end server and client acting as a simple simple transparent proxy just copying encrypted data from one socket to another.
Check this doc for more details about the tunnel
All correct so far.
No. Your proxy should use the plain-text connection you already have.
Correct. Or else if you got a connection failure you send back an appropriate HTTP failure response.
No. Your proxy continues to use the plaintext connection to the browser.
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.