在大多数 OpenVPN 的 TLS 讨论中,三件不同的事被压缩成一句话:你的客户端构建能做什么、配置允许什么,以及会话实际协商了什么。一份简短的 TLS 1.3 套件列表只回答了中间那个问题的一半,对协议版本下限只字未提。请按顺序完成下面的检查,并在第一个不成立的检查处停下,因为后面的每一项检查都假定前面的已经解决。

读任何列表之前先检查构建

配置指令无法给缺少协议支持的二进制文件增加支持。先看版本横幅,它会指出编译时使用的加密库:

openvpn --version

链接 OpenSSL 的 2.6 构建同时接受 tls-cipher 和 tls-ciphersuites。使用 mbed TLS 的构建根本没有 TLS 1.3 套件选择,2.6 手册把 tls-ciphersuites 标为 OpenSSL 选项。如果服务器换用另一个后端重新构建,同一份配置可能在完全不改一行的情况下改变行为。

既然来了,就运行 openvpn --show-tls 并把输出留存。那就是你之后要对照的清单,在任何人开始改配置之前抓下来很便宜。

把横幅和列表写进变更工单,而不是聊天窗口。当两个人争论六周后客户端还能不能提供某个套件时,留存的输出一行就能定论。

把版本边界与密码列表分开

在2.6 手册里,这是三个控制项,而不是一个:

  • tls-cipher 为 TLS 1.2 及更早版本设定密码策略,并传给 OpenSSL 的密码列表;
  • tls-ciphersuites 选择 TLS 1.3 套件;
  • tls-version-min 和 tls-version-max 限定连接可使用的协议版本。

所以,一份精心裁剪的 TLS 1.3 套件列表并不能证明旧版本仍被允许。如果要求是「仅 TLS 1.3」,承担它的是版本下限,而套件列表只是在这个下限之内收窄选择:

tls-version-min 1.3
tls-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

这一对配置做两件事。删掉其中一行,另一行所强制的就比你想象的少,而这正是整套检查要抓的故障。

数据通道是第四个控制项。data-ciphers 协商隧道内的对称密码,它的结果与控制通道的 TLS 版本无关。在会话摘要里看到 AES-256-GCM,任何方向都不能证明 TLS 1.3。

把 show-tls 当作本地清单来读

--show-tls 按优先级列出该二进制文件所链接的加密库支持的套件。它不是网关给出的报告。手册明确指出列表来自本地库,这意味着装有 OpenSSL 3.x 的笔记本和较旧构建的路由器在与同一台服务器通信时会打印出不同的列表。

EvidenceWhat it actually describes
--show-tls outputCapabilities of this client build and its library
Profile directivesWhat this attempt is allowed to offer
Session log lineWhat this one negotiation selected

假设客户端支持套件 A、B、C,某次会话选择了 B。这既不能说明服务器是否允许 A 或 C,也不能说明 B 是否是它唯一允许的选择。把三行分开看,大多数令人困惑的 TLS 讨论就会自行化解。

这份清单仍然值得保留。当会话失败时,把库列表与配置列表对比,可以看出客户端是否有机会提供服务器想要的东西。它还能看出后端变更是否悄悄把 TLS_CHACHA20_POLY1305_SHA256 从旧客户端依赖的池子里移除了——这类回归表现为少数用户连不上,而其他人毫无察觉。

向网关提出它能回答的问题

如果你管理这项服务,请检查运行实例的实际配置,而不是磁盘上的文件。OpenVPN 可能用另一个 --config 路径启动,可能引入包含文件,也可能自修改后从未重启。客户端遇到的是运行中守护进程的策略。

如果你不管理它,就向运营方索取受支持的客户端要求,或一份范围明确的验收测试。被拒绝的尝试并不自动等于被拒绝的套件:版本下限、remote-cert-tls 这类证书身份检查,或 tls-auth 与 tls-crypt 之间的不匹配,都可能在套件选择起作用之前就终止交换。改客户端列表之前先读服务器错误,因为「handshake failed」涵盖四种不同原因,其中只有一种是密码不匹配。

把 openssl s_client 指向 VPN 端口同样复现不了那个问题。控制通道运行在 OpenVPN 自己的帧结构里,所以裸 TLS 探测是在一个期待别的东西的端口上测试另一种协议。由此得出的任何结论都只关于探测,而不关于隧道。

记录两个结果,并把它们分开保存

一次新的被允许的会话会产生一行控制通道日志,写明协议版本和所选密码;还会产生单独一行数据通道日志,写明隧道密码。把两者连同客户端版本和配置名一起保存,并记下是哪台服务器实例应答的:

openvpn --config office.ovpn --verb 4 2>&1 | tee session.txt

然后从那个文件里读这两行,而不是凭记忆。这一对才是证据;只凭套件名只是偏好,版本边界则是关于服务器拒绝什么的独立陈述。

按以下顺序执行检查。用 --version 确认构建和后端。在做任何改动之前先抓取 --show-tls。把要求的版本下限与套件列表分开陈述。与所有者确认运行实例的实际策略。观察一次被允许的会话,若计划允许,再观察一次旧版本被拒绝的尝试。把控制通道和数据通道的结果作为两条独立发现记录下来。

如果某个必需的客户端无法达到新的最低要求,就明确做出迁移或例外决定,而不是悄悄降低一条无关的验收规则,让推广看起来已经完成。

相关文章