REALITY 的实现原理
REALITY 是一个基于 GO 最新版本 TLS 库的协议,它能够在不需要域名的情况下,使用他人网站的域名与自己的客户端进行 TLS 握手,并在中间人对 REALITY 服务端进行探测时,向中间人展示他人网站真正的证书,即对中间人表现为流量转发,中间人得到的是他人真正的证书,而不是伪造的证书。
TLS 1.3 是如何握手的
想知道 REALITY 是如何工作的,首先需要了解 TLS 1.3 是如何握手的。下面是 RFC 8446 中关于 TLS 1.3 握手的流程图:
RFC 8446 TLS August 2018
Figure 1 below shows the basic full TLS handshake:
Client Server
Key ^ ClientHello
Exch | + key_share*
| + signature_algorithms*
| + psk_key_exchange_modes*
v + pre_shared_key* -------->
ServerHello ^ Key
+ key_share* | Exch
+ pre_shared_key* v
{EncryptedExtensions} ^ Server
{CertificateRequest*} v Params
{Certificate*} ^
{CertificateVerify*} | Auth
{Finished} v
<-------- [Application Data*]
^ {Certificate*}
Auth | {CertificateVerify*}
v {Finished} -------->
[Application Data] <-------> [Application Data]
+ Indicates noteworthy extensions sent in the
previously noted message.
* Indicates optional or situation-dependent
messages/extensions that are not always sent.
{} Indicates messages protected using keys
derived from a [sender]_handshake_traffic_secret.
[] Indicates messages protected using keys
derived from [sender]_application_traffic_secret_N.
Figure 1: Message Flow for Full TLS Handshake
可以发现 TLS 1.3 握手的过程是 1 RTT(即一个来回),相比 TLS 1.2 减少了一个 RTT。而握手过程分 3 个重要阶段:
- 密钥交换(Key Exchange)
- 服务端参数(Server Parameters)
- 认证(Authentication)
密钥交换
建立共享的密钥材料并选择加密参数。此阶段之后的所有内容都将被加密
因此,如果存在中间人,中间人能得到的信息非常有限,最主要的就是 SNI。
服务端参数
建立其他握手参数(例如,客户端是否经过认证,应用层协议支持等)。
认证
认证服务器(以及可选的客户端认证),并提供密钥确认和握手完整性。
而 TLS 1.3 的握手过程中,密钥交换阶段是最重要的。在密钥交换阶段,客户端发送 ClientHello 消息,该消息包含一个随机数(ClientHello.random);它提供的协议版本;一系列对称密码/HKDF哈希对;"key_share" 扩展中的一组 Diffie-Hellman 密钥共享,或者 "pre_shared_key" 扩展中的一组预共享密钥标签,或者两者都有;可能还有其他扩展。为了与中间件兼容,可能还存在其他字段和/或消息。
服务器处理 ClientHello 并确定连接的适当加密参数。然后,它用自己的 ServerHello 进行响应,该响应指示协商的连接参数。 ClientHello 和 ServerHello 的组合决定了共享密钥。如果正在使用 (EC)DHE 密钥建立,那么 ServerHello 将包含一个 "key_share" 扩展,其中包含服务器的临时 Diffie-Hellman(阅读本文需要了解DH密钥交换) 共享;服务器的共享必须与客户端的某个共享在同一组。如果正在使用 PSK 密钥建立,那么 ServerHello 将包含一个 "pre_shared_key" 扩展,指示选择了客户端提供的哪个 PSK。请注意,实现可以同时使用 (EC)DHE 和 PSK,在这种情况下,两个扩展都将被提供。
REALITY 是如何工作的
REALITY 中的密钥
-
Auth Key 是由客户端临时生成的 ECDHE(一种基于椭圆曲线的 DH 密钥交换协议,也是 DHKE) 密钥对和服务端配置中的 REALITY 密钥对进行 X25519 算法计算得到的,而不是使用 TLS 1.3 握手中的密钥交换得到的。而 Auth Key 用来加密和解密藏在 ClientHello sessionId 中的 shortId,同时还被用来"签名" REALITY 生成的自签证书。
-
客户端临时生成的 ECDHE 密钥对既用来生成 Auth Key,又用来与服务端临时生成的 ECDHE 密钥对进行 TLS 1.3 中的 key share。
-
服务端临时生成的 ECDHE 密钥对仅用来与客户端临时生成的 ECDHE 密钥对进行 TLS 1.3 中的 key share。
Client
REALITY 客户端需要预先在配置中存储 REALITY 服务端的公钥,并指定与服务端约定好伪装的域名,以及自己的 shortId,该 shortId 应该在服务端的配置中存在。
与正常客户端的区别是,REALITY 客户端会在 ClientHello 中携带 sessionId,虽然 sessionId 在 TLS 1.3 中已经不再需要,并可以为空,但为了向后兼容,sessionId 是可以存在的,REALITY 客户端会将 shortId 藏在 sessionId 中,并通过预先在配置文件中设定的 REALITY 服务器的 PUBLIC KEY 与 REALITY 客户端自己临时生成的 ECDHE 私钥进行 DHKE 得到 Auth Key,并使用 Auth Key 结合 AEAD 加密算法对 sessionId 进行加密。
Server
在服务端,REALITY 与正常 GO TLS 库的区别在于,REALITY 会检查 ClientHello,并且在满足指定条件时才能与 REALITY 服务端本身握手,否则流量将会被导入指定的他人的域名。
- 它会查看客户端指定的 SNI 是否在自己预先配置的列表中,如果在列表中,那么客户端才能够继续与 REALITY 服务端本身进行握手。
- 它会使用与客户端加密 sessionId 同样的方式来解密 ClientHello 中携带的 sessionId,如果解密失败,那么说明客户端不是受信任的 REALITY 客户端,REALITY 服务端此时会将流量导入指定的他人的域名。
- 它会检查客户端发送的 shortId 是否在服务端的配置文件中给出,只有客户端的 shortId 在服务端配置中被包含,客户端才能够继续与 REALITY 服务端本身进行握手。
因此,REALITY 服务端能够精准识别出客户端是否是受信任的 REALITY 客户端,而中间人对 REALITY 服务端进行主动探测时,REALITY 服务端会将流量导入指定的他人的域名,使得展现给中间人的是真实的他人网站的证书,而不是 REALITY 服务端本身的证书。
Server 的证书
REALITY 服务端本身的证书是可以自签的(在实现中是通过生成一份临时自签证书,并将域名指定为与客户端约定好的他人的域名),因为 REALITY 服务端本身的证书并不会被中间人使用,中间人只会得到他人网站的证书,而不是 REALITY 服务端本身的证书。
Server 会在自签的证书中添加一个 HMAC 值,而这个 HMAC 值是由 Auth Key 和 服务端临时生成的 ECDH 公钥计算得到的。
客户端如何识别证书
因为服务端在它自签证书中添加的签名是由 Auth Key 和 服务端临时生成的 ECDH 公钥计算得到的,而这两个客户端也都持有,如果两个 HMAC 值相同,那么客户端就能够确认当前握手的服务端是受信任的 REALITY 服务端,而不是中间人。这就是 REALITY 文档中所说的 REALITY 客户端能够精准识别临时可信的证书。
REALITY 客户端应当收到由“临时认证密钥”签发的“临时可信证书”,但以下三种情况会收到目标网站的真证书:
- REALITY 服务端拒绝了客户端的 Client Hello,流量被导入目标网站
- 客户端的 Client Hello 被中间人重定向至目标网站
- 中间人攻击,可能是目标网站帮忙,也可能是证书链攻击
REALITY 客户端可以完美区分临时可信证书、真证书、无效证书,并决定下一步动作:
收到临时可信证书时,连接可用,一切如常 收到真证书时,进入爬虫模式 收到无效证书时,TLS alert,断开连接