Surge 中文白皮书 (草稿第四部分,TLS,HTTPS 与 MITM)
本章详细讲解了 TLS 和 HTTPS 的关系,TLS 的作用,以及怎样进行 MITM。本文只阐述方法、流程和高层抽象概念,尽量不涉及具体的算法和技术实现。
TLS 和 HTTPS 的关系
现代计算机网络体系结构的设计采用的是分层思想,HTTP 是基于 TCP 协议层的应用层协议。
TCP 层所提供的核心功能是可靠传输,上层应用不必担心数据包的构造、拆分、乱序、丢包等实现问题,当使用 TCP 协议与目标主机通信,即有了一个可靠的双向的流式数据通道。
HTTP 协议基于 TCP 协议的基础上,定义了更详细的数据传输标准,将数据流抽象成了会话制,客户端发送一个请求,服务端回应一个响应,一一对应,更方便使用。
HTTPS 则是在 TCP 层和 HTTP 层之间,又插入了一个 TLS 层,TLS 层可以使 TCP 层的数据流得到加密和安全保护。所有基于 TCP 协议的上层协议都可以靠这种方式获得保护,而不必对协议自身进行调整。(如 SMTP)
TLS 的作用
一般认为 TLS 层的作用就是数据加密,实际上 TLS 层承载了全面的安全特性,至少包括:
- 私密性(Confidentiality):即数据加密,哪怕攻击者从链路上自始至终地获取了完整的数据流,也无法解密出原始数据。
- 真实性(Authenticity):可以确认目标主机身份,比如当访问 example.com 时,即使整个物理网络遭到了劫持,也能保证访问的是 example.com 的主机而非其他冒牌主机。
- 数据完整性(Integrity):保证数据不可被修改,链路上有攻击者对数据流进行了修改一定会引发错误。
数据加密和完整性保护
简单来说,TLS 握手阶段通过非对称加密或密钥协商交换算法生成一个对称加密密钥,后续传输依靠该密钥进行加密和完整性保护,关于 TLS 如何进行数据加密和完整性保护的具体算法细节不在本章的探讨范围,可以查阅相关文献。
确认目标主机身份
讲解 TLS 如何确认目标主机身份前,需要补充一点简单的密码学知识:非对称加密。我们这里不去描述非对称加密的具体数学原理,仅简单描述非对称加密的用法。
非对称加密
对称加密,指的是加密与解密用的是同一个密钥。非对称加密,顾名思义,指的是加密与解密用的是不同的密钥,我们把用来加密的密钥称为公钥(Public Key),用来解密的密钥称为私钥(Private Key),当然只有该公钥配套的私钥才能去解密由该公钥加密的内容,这对配套的公钥与私钥称为密钥对(Key Pair)。密钥对满足:
- 不可能由公钥直接计算出私钥。
- 暴力破解的算力需求不现实,在密钥长度符合要求的前提下,完全不用担心被暴力破解的可能。
除了用于加解密,密钥对还可以用于签名(Signing),对于一段内容,可以用私钥生成一段签名(一般称作数字签名),公钥可以验证签名是否是由私钥所生成,以此确定该内容是由私钥持有者所认可的内容。同时保证了内容无法被篡改,只要内容有变化数字签名验证一定会失败。
最常见和著名的非对称加密算法是 RSA 算法,也还有其他的非对称加密算法。
X.509 证书链
接下来我们简略了解一下什么是证书,证书其实就是一堆键值对(Key-Value)构成的数据体,不同的用途时会有不同的字段内容。
- 一份证书对应着一个密钥对,公钥是证书的一部分,而私钥由证书的拥有者私密保存。
- 证书可以由另一个证书所签发,即该证书的内容中包含了来自上级签发证书的数字签名和上级签发者的信息。
- 上级签发者的证书可以由上上级签发者所签发,构成证书链,一般 TLS 使用的是 3 级证书链,我们把最高级签发者称作根证书(或叫 CA 证书,Certificate Authority),中间的证书称作中级证书(Intermediate Certificate),最末端的证书称作服务器证书(Server Certificate)
- 操作系统预置了很多 CA 证书,表示操作系统信任这些 CA 证书,中级证书通过出示 CA 证书的数字签名表示受到 CA 证书信任,服务器证书出示中级证书的数字签名表示受到中级证书信任,构成了信任链。
那么这个证书信任关系是怎样对应的真实世界的身份验证呢?通常来说,CA 证书的维护机构需要符合特定的安全审计,守规守法的运作,由他们再去挑选一些代理机构,授予中级证书,当有域名的持有者希望能获得一份表明自己身份的证书时,代理机构先验证申请者的身份(如通过域名的联系人邮箱),然后向申请者颁发证书。
上述体系即为 X.509 证书链的简略描述。
- 证书颁发的过程中,通常由申请者先产生密钥对,然后将公钥封装为 CSR (Certificate Signing Request),然后发送给代理机构,代理机构确认身份后返回带有其中级证书数字签名的证书,证书的字段中包含申请者的域名。代理机构并不知道该证书的私钥。
- 操作系统会随着系统更新,根据业务需求不断的调整内置的根证书库。
- 有的软件会选择自己维护根证书库,忽略系统的根证书库,如 Firefox。
- 上述提到的最末端的证书更准确的名称是 End-entity Certificate 或 Leaf Certificate,由于本文主要讲述的是服务端证书的验证,为避免混淆故直接写作服务器证书(Server Certificate)。
TLS 握手
拥有上述基础知识后,我们可以开始讲解 TLS 是怎样确认目标主机身份的了。来看一下 TLS 握手的具体流程:
- 客户端通过 SNI,明文告知服务端正在访问 example.com,请提供相应的证书。
- 服务端确认自己拥有 example.com 的证书,向客户端提供自己的服务器证书和所有中级证书。
- 客户端收到证书,确认证书的 Common Name(或者 SAN 字段)字段包含 example.com。
- 确认验证该证书的证书链可被根证书库所信任。
现在我们已经能确认服务器提供的证书,确实是 example.com 的一个真实可信的证书了,唯一还需要确认的是,服务端拥有该证书的私钥。
- 客户端随机生成一段内容,使用服务器证书的公钥加密后发给服务端。服务端用私钥解密后可通过该内容计算出后续传输阶段的对称加密密钥,称作会话密钥。
如果服务端没有该证书的私钥,那么则不可能计算出正确的会话密钥,也就无法继续和客户端的通讯。
(上述流程有所简化,且根据 TLS 版本与加密方式不同可能略有不一致,可阅读 https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/ 了解更多)
通过上述步骤,客户端终于和 example.com 建立了一个安全信道。可以开始后续的 HTTP 内容传输。
MITM 攻击
在解释清楚了 TLS 是怎样保证连接安全之后,我们想要解释怎样通过中间人攻击(MITM,Man-In-The-Middle)解密 TLS 流量就太简单了。
中间人攻击顾名思义,就是在客户端 A 和服务端 B 之间,插入一个中间人 C,以此截取明文内容。具体做法是劫持 A 往 B 的连接到 C 上,让 A 以为自己在和 B 通讯,而实际上是在和 C 通讯,C 再建立和 B 的连接,作为中间人在 A 和 B 间转发内容。
要实施 MITM 有两个条件:
- 有能力劫持 A 的网络,一般代理服务或软件、VPN、ISP、Wi-Fi 提供者等链路拥有者可以轻松实现。(恶意软件也可以通过对操作系统 hook 实现,但是如果恶意软件已获得如此高的权限,也不必再通过 MITM 去解密流量,直接读取对应软件内存即可)
- 需要突破 TLS 的目标主机身份机制。
根据前文的描述,因为是我们自己处于研究目的想要进行 MITM,所以突破的方式很简单,在系统的根证书库中插入一份自己拥有私钥的证书即可。
Surge 的 MITM 流程
我们再来完整的看一下 Surge 进行 MITM 的流程。
- 用户配置 MITM 功能,Surge 在本地生成密钥对,产生一个根证书并安装进系统证书库。开启了 example.com 的 MITM 功能
- 收到向 example.com:433 的 CONNECT 请求,进入 MITM 模式,直接向客户端表示已完成到服务器的 TCP 握手。
- 客户端开始 TLS 握手,发出 ClientHello 消息。
- Surge 立刻生成一份 example.com 的服务器证书,并由配置的根证书进行签名,以该证书和客户端完成握手。
- 客户端进行 HTTP 层通讯,发送真实 HTTP 请求。
- Surge 收到请求后,按照第二章的描述对请求进行修改,再判定出口策略。使用对应策略向真实的 example.com 发起连接并完成 TLS 握手,转发该请求。
不同的软件在进行 MITM 时有一个细节会有所不同,Surge 是在与客户端握手时,直接凭空生成了一份符合需要的新服务器证书。另一些软件的策略是,先暂缓与客户端的握手,立刻开始与服务端的握手,在完成服务端握手拿到服务端证书后,修改其公钥与签发者信息并用自己的根证书重新签名,再用这个证书与客户端完成握手。
第二种方案的好处是,这样客户端拿到的证书与真实服务器的差异非常小,可以解决一些少见的兼容性问题。Surge 之所以采用第一种方案,是因为 Surge 的规则系统允许以 HTTP 层的特征作为判断依据选择出口策略(如 URL),所以必须先完成握手获得 HTTP 层请求后,才可以去与真实目标服务器建立连接。
公开的根证书
实践中我们发现有些 MITM 工具软件为了减少工作量,没有提供本地生成根证书的功能。取而代之的是直接预置了一份根证书和证书的私钥。
这种做法是很不安全的,如果用户系统信任了该证书,一旦网络遭遇劫持,攻击者便可以利用这份公共证书的私钥进行 MITM 攻击解密流量。
请务必在本地生成自己独特的根证书,并妥善保存私钥。
对抗 MITM 攻击
作为软件开发者,如果不想自己的流量可被 MITM 工具所解密,需要进行 MITM 防御。
方法其实并不复杂,只需要抛弃 X.509 证书链验证逻辑,使用自己的逻辑进行验证即可。X.509 的诞生意义是为了服务浏览器,当用户访问某一个网站时,除了这个网站的域名外,是一无所知的。所以需要依赖证书链对证书的合法性进行验证。但是对于 App 来说并不存在这个限制,在 TLS 握手阶段,直接判断服务器证书的公钥是否为特定值即可,这样 MITM 工具便不可能绕过身份验证,也就无法解密流量。
实践中除了比对服务器证书的公钥,还有很多做法,这里不再展开。如果 App 进行 MITM 防御,想要继续进行 MITM,则必须使用越狱设备修改程序二进制或注入运行时代码,突破自定义的证书验证流程。
TLS 的其他细节
我们顺便再补充一些与 TLS 相关的细节。
常见的 HTTPS 错误
我们经常在浏览中看到的「不安全」错误,就是上述验证过程中由于各个环节失败所产生的错误,一般有:
- 名称不符:服务器证书的许可域名和正在访问的 URL 并不匹配。
- 证书过期:证书是一定会被设置有效期的,一般为一年,出现该错误一般说明网站维护人忘记更新证书,或者用户设置了错误的系统时间。
- 根证书不可信:说明服务器提供的证书链的根证书并没去在系统的证书库中。
如果是在一个可信网络下(如家庭宽带),遇到上述错误,通常是网站管理员的配置失误造成的,如果在公共网络中遇到以上错误,那么需要加倍小心,可能遇到了劫持。
SNI
之前我们在 TLS 握手中提到了 SNI,这里详细解释下 SNI 的作用。
首先我们需要知道,假设服务器有一个 IP 地址 11.22.33.44,且有多个域名 exampleA.com 和 exmapleB.com 都指向这个 IP,当客户端向服务端发起 TCP 连接时,服务端并不知道客户端是通过 IP 还是 exampleA.com 或者 exmapleB.com 进行访问的,因为 TCP 的元数据中仅包含了 IP 地址,域名在被客户端用来查询获取到 IP 后,完全不参与后续的 TCP 环节。
这在 HTTP 协议的实践中遇到了一个问题,由于 IP 地址的稀缺性,我们有时希望同一个 IP 地址(或者说同一台服务器),能够根据访问者访问的域名,提供不同的内容(即虚拟主机)。为了解决这个问题,浏览器会在发出 HTTP 请求时,在请求头中加上 Host 字段,内容为 URL 中的主机名(域名)部分。这样服务器便可通过 Host 字段区分访问的站点以返回不同内容。
HTTPS 也遇到了同样的问题,TLS 握手时需要根据访问的域名的不同,使用不同的服务器证书。由于 Host 的内容存在于握手后的加密传输中,如果握手都无法完成那么自然无法通过该字段解决问题。所以 TLS 在握手时,客户端(浏览器)会以明文发送客户端所访问的域名的,即 SNI,供服务器选择证书。
但这可能导致隐私泄露,使得链路拥有者可以知道用户访问的网站的域名。但由于用户所访问的 IP 地址是一定能被获知的,所以访问的域名的泄露的影响有多大其实并不好说。
另外对于如果 TLS 客户端是浏览器,由于 TLS 的 SNI 和 HTTP 的 Host 都是 URL 的主机名,所以这两者一定是一致的,但是对于其他 TLS 客户端却并不一定一致。比如 Surge 就支持自定义 TLS 握手的 SNI 内容。
前向安全(Forward Secrecy)
之前在讲解 TLS 握手时有提到,TLS 后续对称加密的所使用的会话密钥,是由服务器证书的私钥加密的随机数据计算而得的。
那么,如果攻击者保存了通讯的密文,假如未来某天服务器的私钥泄漏了,或者计算机科学的发展使得暴力破解私钥成为可能,那么就可以通过私钥解密出会话密钥,从而完全解密保存下来的密文。
为了解决这个缺陷,现在所使用的 TLS 协议在握手时会更复杂一点,不再简单地使用静态的非对称密钥对去传递会话密钥,转而使用密钥协商算法去生成临时的会话密钥。比如目前常见的 DHE 算法。
这里简单描述一下 DHE 的用法:
- 服务端每次都为新连接随机生成一个密钥对:服务端私钥和服务端公钥。
- 客户端每次都为新连接随机生成一个密钥对:客户端私钥和客户端公钥。
- 客户端和服务端通过交换公钥。
- 客户端根据服务端公钥、客户端私钥和客户端公钥,通过算法计算出结果 1。
- 服务端根据客户端公钥、服务端私钥和服务端公钥,通过算法计算出结果 2。
- 算法保证了结果 1 和结果 2 一定是相等的,使用该结果进一步加工即可得到会话密钥。
- 服务端私钥、客户端私钥、结果 1、结果 2、会话密钥均只存在于两端的内存中,连接结束后,这些数据将被彻底抛弃不可恢复。
在这样的密钥交换机制下,由于攻击者仅能保存公开交换的客户端公钥和服务端公钥,任何人都无法再计算出用于会话密钥。
此项特性即称为前向安全(Forward Secrecy),也叫做完美前向安全(Perfect Forward Secrecy)。TLS 在握手时会根据客户端和服务器情况自动选择是否使用具有前向安全的密钥交换算法。
TLS Cipher Suite
综上所述,TLS 协议在握手阶段,服务器和客户端需要协商出数个算法
- TLS 协议版本
- 密钥交换算法
- 签名算法
- 对称加密算法
- 哈希算法
协商的方法其实很简单,客户端先告状服务端自己上述 5 个项目所支持的组合,服务端再按照自己所支持的组合,选择尽可能安全的一个结果告知客户端。这个组合称为 TLS Cipher Suite。
以目前最常见的几个 TLS Cipher Suite 举例:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:这是一个 TLS 1.2 版本下的组合,密钥交换算法为 ECDHE,签名算法为 RSA,对称加密算法为 AES-128-GCM,哈希算法为 SHA256。
- TLS_AES_256_GCM_SHA384:这是一个 TLS 1.3 版本下的组合,TLS 1.3 标准只支持使用 ECDHE 算法进行密钥交换所以不需要协商,同时由于所使用的加密算法均为 AEAD 算法已自带完整性保护,不再需要进行单独的完整性保护,签名算法也不用再协商。组合中只包含对称加密算法为 AES-256-GCM,哈希算法为 SHA384。
如果你使用了一个基于 TLS 的代理协议,可以在 Surge 的 Dashboard/Recent Requests 的备注中,看到代理连接所协商出的 Cipher Suite。