WSS 隧道的加密止于 TLS 终止处,而不是应用
wss:// 地址只能证明链路上有一段 TLS。载荷是否另有自己的加密,取决于客户端在里面放了什么,以及由谁来终止那段 TLS。
装在安全 WebSocket 里的隧道,只有客户端放进去的那一层加密,不会更多。wss:// 方案说明 TLS 保护的是单跳上的一条连接,却完全没有说明 WebSocket 消息内的字节是否另有保护。而正是这个区别,决定了当中间某一环终止 TLS 时,谁能读到你的流量。
两层看起来像一层
安全的 WebSocket 就是在其之上完成 WebSocket 握手的 TLS,RFC 6455 在连接与安全两节里写得很清楚。TLS 层完成对服务端的认证并加密字节流,随后由 WebSocket 层把应用决定发送的内容分帧。
这是两个彼此独立的决定,而只在整个路径上画一把锁的示意图会把第二个藏起来。先问:谁终止了 TLS 连接。再问:客户端在每条消息里放了什么。部署的其余结论都从这两个答案推出。
设计 A:传输层是唯一的加密
在更简单的设计里,客户端取出原始 IP 包,把它们包进 WebSocket 消息。内部没有任何加密,所以持有 TLS 会话的组件读到的就是明文载荷。当这个组件就是你自己的隧道服务器时,这正是预期的终点,设计也自洽,因为服务器本来就会看到流量。
一旦路径上坐着检查代理,并用客户端信任的证书终止 TLS,情况就变成另一种安排。这个代理现在持有与服务器相同的明文,再多的 WebSocket 分帧也改变不了。客户端的信任库早在隧道建立之前就决定了这个结果,因为它选择接受该名称下的那张证书。代理还能看到请求的主机名和每条消息的大小,这已足以在不做任何进一步解密的情况下,为大量活动建立指纹。
设计 B:载荷自带密钥
在第二种设计里,客户端用带自有对端身份和密钥交换的 VPN 协议加密并认证自己的数据包,再把结果装进 WebSocket 消息。此时终止外层 TLS 暴露的是密文,而不是流量。代理能看到消息大小与时间,载荷里的其他东西它看不到。这暴露面小得多,但仍属于值得在评审中点名的元数据,而不是可以挥手带过的细节。
内层协议自身的端点仍然能看到明文,这既是这套安排的意义,也是它的边界。如果端点上的某个组件被授权在加密之前或解密之后读取数据,多出来的那一层并不能约束它。密钥以及处理密钥的软件,仍然是有意义的边界。
VPN 标签、眼熟的客户端图标或营销页面都不能证明这种设计存在。读懂载荷格式才能。
追踪明文究竟出现在哪里
URL 方案只给你一个事实,其余的要从接口上取。按顺序跑下面的检查,因为每一条都会收窄下一条:
ss -tnp | grep -E ':(443|8443)\b' ip -br link show type tun openssl s_client -connect vpn.example.net:443 -servername vpn.example.net -brief tcpdump -ni tun0 -c 5 -X
先看外层连接。到 TLS 端口的一条长寿命 TCP 会话,正是 WebSocket 载体的样子;而到无关主机的第二条会话,在你继续深挖之前值得解释。接着在客户端上找到隧道接口,因为出现 tun 或 tap 设备就说明客户端在处理 IP 包,而不是在代理某一条应用流。
隧道接口上的抓包通常能一锤定音。那里头部可读,说明内层数据包从未单独加密;载荷熵很高,说明单独加密过。再到外层接口重复抓包并比较两者:如果两边都像随机数据,内层就在做外层无法撤销的工作。
复核你实际授予的信任
已安装的企业根证书是关于客户端信任库的事实,而不是关于任何代理记录或检查了什么的陈述。要确定实际部署的策略和真实连接路径,而不是从某个 CA 是否存在去推断行为。
在受管设备上,问管理员三件事:哪个组件终止 TLS,客户端校验的是固定证书还是公共证书,载荷格式是什么。这三个答案会给出一个日后站得住脚的结论。
把结果写成边界,而不是判决。"TLS 在检查代理处终止,内层载荷用只有端点持有的密钥单独加密"是可核查的陈述,换了供应商也依然成立。"WebSockets 是安全的"就不成立。