在握手里携带一个公开的密钥交换值,和客户端登录并不是同一件事。前者为连接建立密码学状态;后者说明客户端是谁,而且只有在服务器主动要求、交换本身也支持时才会发生。把前者当成后者,会让人对 VPN 究竟隐藏了什么、没有隐藏什么得出错误结论。先读消息,再判断你实际观察到的是四种性质中的哪一种。

抓包里看起来很像的两个对象

TLS 1.3 客户端可以在首个飞行中就贡献密钥交换材料,却不提交任何证书。这份贡献按设计就是公开的,因为服务器需要它来派生共享密钥。客户端证书是另一条消息,只在服务器请求之后才发送。它承载服务器随后验证的身份,而双向认证是服务器作出的决定,不是密码套件隐含的性质。应用身份完全是第三件事:登录表单、承载令牌、Cookie。这些都不会出现在握手里,而且都能在更换网络路径后继续存在。

Object in playWhat it establishes
Key-exchange shareCryptographic state for this connection
Server certificateWhich server the client reached
Client certificateClient authentication, when requested
Application sessionAccount identity inside the application

这四行彼此独立。一次连接可以具备第一、第二行,跳过第三行,却仍在 Cookie 头里携带第四行。

判断抓到的握手能证明什么

仅凭握手,观察者能得知协商的版本、密码套件、服务器证书和公开的密钥交换值。这足以给客户端实现取指纹,并看出联系了哪台服务器。它给不出的是解密连接所需的私有状态,其保密性依靠的是临时交换而非公开的那部分。它也提供不了账户身份,那本来就从未出现在握手里。

所以诚实的结论很窄:这次交换中没有提交客户端证书。这不是说客户端没发送任何可关联的东西,更不是说会话没有携带任何标识符。

读消息,而不是读密码名称

直接问服务器要求什么,而不是从密码套件去推断。包含证书请求的 TLS 1.3 握手在记录里看得见,它缺席也同样看得见。

openssl s_client -connect app.example.net:443 -tls1_3 -msg < /dev/null 2>&1 | grep -i -E 'key_share|CertificateRequest'

然后配置好客户端证书再重复一次并比较。第二次运行应当显示证书被发送并被接受;第一次应当什么都不显示,这正是争论所涉及的具体差别。

openssl s_client -connect app.example.net:443 -tls1_3 -cert client.pem -key client.key -quiet

如果你只需要普通的浏览场景,客户端的详细输出就足以看到协议版本、验证结果以及是否提供了客户端证书。从不发送证书请求的服务器不可能收到过一张。

会话恢复在哪里改变了答案

TLS 1.3 的恢复使用与秘密状态绑定的票据,因此后续连接可以不做完整证书交换就完成。这减少了观察者看到的内容,也改变了记录能证明的东西。它不会在原本没有身份的地方造出身份。恢复的会话仍属于同一个应用会话,该会话里的任何 Cookie 或令牌仍然是真正要紧的标识符。

这就是为什么停在“没有客户端证书”的隐私审查会错过有趣的部分。该问的是哪一方握有稳定标识符,而不是某次握手缺了哪条消息。

记录四项性质,而不是一个结论

把密钥协商、服务器验证、客户端认证和应用身份写成四行,分别附上证据。这份清单能经受协议版本变化,也让关于隐私的论证可被别人核查。然后再补上网络路径的贡献。VPN 改变的是服务所看到的源地址;它并不会移除应用早已持有的登录、Cookie 或设备指纹。

把连接协商的版本和证书请求的结果一起记下来,因为评审者要复现你的结论,需要的正是这一对,而不必自己做抓包。

相关文章