HTTPS代理实现(SSLStream)

时间:2014-06-12 22:41:49

标签: c# ssl https proxy

我编写了一个充当代理服务器的控制台应用程序。现在我也想实现SSL。不喜欢解密任何流量。就像普通的https代理一样。我不确定我该怎么做。

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();

我应该直接将所有内容写入sslStream吗?浏览器连接proxyClient怎么样?我是否还需要包装流,或者我可以直接将所有内容写入proxyStream?我应该使用AuthenticateAsServer并以某种方式从AuthenticateAsClient传递证书吗?

  1. IE向我的代理发出CONNECT请求
  2. 我的代理看到它是一个CONNECT请求并获取IP:端口 目的地(例如,www.hotmail.com:443)
  3. 我的代理创建了一个到www.hotmail.com:443
  4. 的新TCP连接
  5. 我的代理从此目的地获取SslStream并调用AuthenticateAsClient - 这为我的代理提供了与Hotmail方面的安全连接
  6. 我的代理然后将“HTTP / 1.0 200”消息发送回浏览器,表示CONNECT成功
  7. 我的代理然后从浏览器连接获取SslStream并调用AuthenticateAsServer - 为我的代理提供与浏览器端的安全连接
  8. 我看到了这个,但AuthenticateAsServer如何没有假证书。我可以像在普通的流中那样写这个或者我应该考虑一下吗?


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

2 个答案:

答案 0 :(得分:5)

  

IE向我的代理发出CONNECT请求   我的代理看到它的CONNECT请求并获取目标的ip:端口(例如,www.hotmail.com:443)   我的代理创建了一个到www.hotmail.com:443的新TCP连接

到目前为止一切正确。

  

我的代理从此目的地获取SslStream并调用AuthenticateAsClient - 这为我的代理提供了与Hotmail方面的安全连接

没有。您的代理应使用您已有的纯文本连接。

  

我的代理然后发送&#34; HTTP / 1.0 200&#34;回到浏览器的消息说CONNECT成功了。

正确。否则,如果连接失败,则会发回适当的HTTP失败响应。

  

我的代理然后从浏览器连接获取SslStream并调用AuthenticateAsServer - 为我的代理提供与浏览器端的安全连接

没有。您的代理继续使用与浏览器的纯文本连接。

  

AuthenticateAsServer如何没有假证书?

你根本不必这样做。

此时,浏览器和上游服务器已准备好执行SSL握手。但正如您所说,您不想嗅探内容,您无需自己成为SSL端点。您现在要做的就是同时复制两个方向的字节。端点将进行SSL握手,就像您不在那里一样。

答案 1 :(得分:1)

添加到EJP的答案,代理只是在成功的CONNECT请求后打开终端服务器和客户端之间的隧道。但是,客户端需要将套接字升级到HTTPS并继续通过CONNECT请求中使用的同一套接字发送数据。另一方面,代理只需要在端服务器和客户端之间维护这个隧道,作为一个简单的简单透明代理,只需将加密数据从一个套接字复制到另一个套接字。

检查此doc以获取有关隧道的更多详细信息