HTTP 加密代理

2019-07-08 ⏳2.4分钟(1.0千字)

HTTP 代理在企业中有着应泛的应用。但 HTTP 代理信令是明文传输的,非常不利于商业秘密和个人隐私的保护。本文介绍一种使用 TLS 技术对 HTTP 实行加密保护的方法。

对于明文 HTTP 请求,代理通信的流程如下:

Agent                         Proxy                          Site
  |                             |                              |
  | <1> tcp dial to proxy:8080  |                              |
  |<--------------------------->|                              |
  |    <2> GET / HTTP/1.1       |                              |
  |        Host: baidu.com      | <3> tcp dial to baidu.com:80 |
  |---------------------------->|<---------------------------->|
  |                             |   <4> GET / HTTP/1.1         |
  |                             |       Host: baidu.com        |
  |                             |----------------------------->|
  |                             |   <5> HTTP/1.1 200 OK        |
  |   <4> HTTP/1.1 200 OK       |<-----------------------------|
  |<----------------------------|                              |
  |                             |                              |

如是你要访问 HTTPS 站点,则需遵循另外一套流程:

Agent                         Proxy                          Site
  |                             |                              |
  | <1> tcp dial to proxy:8080  |                              |
  |<--------------------------->|                              |
  |  <2> CONNECT baidu.com:443  |                              |
  |---------------------------->| <3> tcp dial to baidu.com:443|
  |   <4> HTTP/1.1 200 OK       |<---------------------------->|
  |<----------------------------|                              |
  |                             |                              |
  |          https data         |         https data           |
  |<--------------------------->|<---------------------------->|
  |                             |                              |

接下来代理服务器会不断将客户端发来数据转发给目标服务器,并把目标服务器的发来的数据转发给客户端。

那为什么 HTTP 请求和 HTTPS 请求的代理通信流程不一样呢?因为对于明文的 HTTP 请求,代理服务器可以通过解析请求内容获取目标服务器的域名和端口,进而发起 TCP 连接。可对于 HTTPS 请求,客户端在发送 HTTP 请求之前要发送 TLS 握手数据。Proxy 收到 TLS 握手数据后是不知道要把这些数据转发给哪台服务器的。所以,协议要求客户端要先发送一个 CONNECT 请求来告诉代理服务器目标服务器的域名和端口。

大家注意,对于 HTTP 请求,Agent 和 Proxy 之间的通信中全明文的;对于 HTTPS 请求,Agent 的 CONNECT 请求也是通过明文发送给 Proxy 的。因为是明文协议,所以存在安全风险。

我们知道,HTTPS 就是使用 TLS 对明文 HTTP 协议进行的加密的。那我们可不可以使用 TLS 对 Agent 和 Proxy 之间的通信进行加密呢?理论上是可行的,但现实中却不可行,因为改造现有系统来支持 TLS 版 HTTP 代理的工作量太大了。那就没有其他办法了吗?有,那就是引入二级代理,其代理通信流程如下:

Agent                       Local Proxy                  Remote Proxy                      Site
  |                             |                             |                              |
  | <1> tcp dial to proxy:8080  |                             |                              |
  |<--------------------------->|                             |                              |
  |    <2> GET / HTTP/1.1       |                             |                              |
  |        Host: baidu.com      |                             |                              |
  |---------------------------->|   <3> tls handshake         |                              |
  |                             |<--------------------------->|                              |
  |                           ~=================================~                            |
  |                             |  <4> CONNECT baidu.com:80   |                              |
  |                             |---------------------------->|                              |
  |                             |                             | <5> tcp dial to baidu.com:80 |
  |                             |  <6> HTTP/1.1 200 OK        |<---------------------------->|
  |                             |<----------------------------|                              |
  |                             |   <7> GET / HTTP/1.1        |                              |
  |                             |       Host: baidu.com       |   <8> GET / HTTP/1.1         |
  |                             |---------------------------->|       Host: baidu.com        |
  |                             |                             |----------------------------->|
  |                             |                             |   <9> HTTP/1.1 200 OK        |
  |                             |   <10> HTTP/1.1 200 OK      |<-----------------------------|
  |                             |<----------------------------|                              |
  |                           ~=================================~                            |
  |   <11> HTTP/1.1 200 OK      |                             |                              |
  |<----------------------------|                             |                              |
  |                             |                             |                              |

对于 HTTPS 请求,通信流程则稍微简单一些:

Agent                       Local Proxy                  Remote Proxy                      Site
  |                             |                             |                              |
  | <1> tcp dial to proxy:8080  |                             |                              |
  |<--------------------------->|                             |                              |
  |  <2> CONNECT baidu.com:443  |                             |                              |
  |---------------------------->|   <3> tls handshake         |                              |
  |                             |<--------------------------->|                              |
  |                           ~=================================~                            |
  |                             | <4> CONNECT baidu.com:443   |                              |
  |                             |---------------------------->|                              |
  |                             |                             | <5> tcp dial to baidu.com:443|
  |                             |  <6> HTTP/1.1 200 OK        |<---------------------------->|
  |   <7> HTTP/1.1 200 OK       |<----------------------------|                              |
  |<----------------------------|                             |                              |
  |           https data        |         https data          |         https data           |
  |<--------------------------->|<--------------------------->|<---------------------------->|
  |                           ~=================================~                            |
  |                             |                             |                              |

这里的核心是引入 Local ProxyRemote Proxy 两台代理服务器。对 Agent 来说,Local Proxy 就是一台普通的代理服务器。Local Proxy 收到代理请求后不会直接连接目标服务器,而是先跟 Remote Proxy 建立 TLS 连接,并使用 CONNECT 请求将目标服务器的信息发送给 Remote Proxy,Remote Proxy 收到 CONNECT 请求后执行代理连辑。Local Proxy 既是 Agent 的 Proxy,又是 Remote Proxy 的 Agent,具有双重人格。Local Proxy 和 Remote Proxy 之间的通信使用 TLS 加密,保证不被恶意用户窃取。

理好思路,编码实现就很容易了。我自己使用 Go 语言的实现已经发布到了 GitHub 上,供大家参考。https://github.com/taoso/tlstunnel

最后,TLS 加密 HTTP 代理的缺点。

第一,TLS 通信需要 SSL 证书。SSL 证书一般只签发给域名,所以还需要注册一个域名。好在我们可以去 https://www.freenom.com 注册一年期免费域名,然后使用 https://letsencrypt.org/ 签发三月期证书,所以这个问题不大。

第二,就是 TLS 握手效率的问题。TLS v1.2 握手流程确实很繁琐,至少要四次通信才能完成握手,延迟的确很大。但是,TLS v1.3 对握手流程做了大幅优化,只需两次通信即可,大大缩短了握手延迟。所以,只要能使用 TLS v1.3,这个问题也不大。