这里造成最大伤害的是两个信念:自签名 VPN 证书意味着隧道没有加密,以及安装 VPN 客户端要求的任何东西只影响 VPN。两者都经不起真实部署的检验。自签名证书 描述的是签名关系,而不是安全结论,而你所添加的信任范围取决于它落在哪个存储里。我见过一个服务台把提供商的根证书导入一台 Windows 机器来消除一条警告,然后用一下午解释浏览器为什么不再对另一个无关门户抱怨。这个问题的两半属于同一件事,所以请把它们当作一件来处理。

证书不是信任存储

服务器出示的文件带有公钥、主体名称、颁发者名称、有效期和扩展。自签名意味着主体和颁发者是同一个实体,签名用证书自己的密钥验证。这个定义里没有任何东西说连接很弱,也没有任何东西说客户端会接受它。

接受来自别处:你指向客户端的 CA 文件、你手动比对的指纹,或已经存在于客户端所读存储中的根证书。一个部署可能完全健康,但它的证书会通不过任何公开测试。对系统包使用 openssl verify 会报告未知颁发者,而这个报告是正确的,不是故障。

OpenVPN 的 ca 选项是应用级信任最清楚的例子。该文件指定客户端将接受的锚点,它可能包含一个私有 CA,或在小规模部署中包含服务器自己的自签名证书。2.6 版还接受 --peer-fingerprint,用于固定 SHA-256 指纹,而不必分发 CA 文件。WireGuard 走得更远:它根本没有证书,只有每个对等端一个静态公钥,所以自签名的问题不适用于它。

你把信任装在哪里,决定了谁会继承它

把同一张证书放进三个地方,你会得到三种不同的影响半径。只有 VPN 客户端读取的包影响那个客户端。操作系统存储里的根证书影响所有查询该存储的程序。带有自己信任数据库的浏览器可能两者都忽略,这就是为什么在看似成功的导入之后,一条警告仍可能留下。

在 Debian 或 Ubuntu 上,把文件放进 /usr/local/share/ca-certificates/ 并运行 update-ca-certificates 会改变全系统的包。在 Windows 上,certutil -addstore -f Root 写入机器存储,需要提升的命令提示符,而添加 -user 则针对当前用户的存储;两者不能互换。Firefox 保留自己的 NSS 数据库,所以操作系统层面的导入可能永远不会出现在那里。

范围很重要,因为一个权威能做什么。颁发 CA 可以为与它毫无关系的名字签名,所以任何信任该存储的东西都会接受这些签名。单个固定叶证书做不到这一点;它恰好认证一个对等端身份。如果目标只是让一个 VPN 连上,应用级 CA 或固定叶证书就能达到,而全机器范围的根证书是比症状所需大得多的让步。

没有公共 CA,你仍然在加密

TLS 握手会派生会话密钥,无论对等端证书是否链到你已经信任的东西。自签名不意味着明文,也不意味着流量在传输中可读。它意味着客户端没有先验理由相信密钥属于它所请求的服务器。

真正的攻击就活在这个空隙里。配置成接受任何证书的客户端,无论是通过关闭校验设置,还是通过一次点击后就被遗忘的“仍然信任”按钮,都会乐于向任何应答者加密。拦截发生在认证步骤,在应用数据移动之前。没有身份校验的加密只能把被动观察者挡在外面,别的几乎做不了。

信任颁发者并不能跳过名称检查

链校验、名称匹配和证书用途是彼此独立的决定。链可以有效而名称是错的,名称可以匹配而证书是为另一个角色签发的。RFC 5280 定义了承载这些约束的字段,而客户端执行得有多严格是每个应用自己的事。

OpenVPN 让这种分离变得可见。verify-x509-name 通过 name、name-prefix 或 subject 匹配来固定预期主体。remote-cert-tls server 要求对等端证书能用作服务器证书,这会拒绝仅客户端证书,即使颁发者是受信任的。这两项设置都不能靠发送 CA 文件来替代。

在做任何决定之前先检查证书:

  • openssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName 显示谁签发、为哪些名称、到何时。
  • openssl verify -CAfile ca.crt server.crt 检查你打算交付的链是否真的验证了叶证书。
  • openssl s_client -connect vpn.example.net:443 -servername vpn.example.net -CAfile ca.crt -verify_return_error 适用于网页门户这类 TLS 端点;1194 上的 OpenVPN 协议不是普通 TLS 流,所以该测试在那里不适用。

日期已过期、没有用于你所连接名称的 subjectAltName,或缺少 serverAuth 用途的证书,无论锚点装得多仔细都会失败。

接受一张证书前的五项检查

在不是你正试图修复的那条连接的通道上比对指纹:控制台、你已有的安装程序、已签名的配置文件。服务器上的 openssl x509 -in server.crt -noout -fingerprint -sha256 和客户端日志里的相同值,足以让你知道自己看的是同一个对象。

接下来,决定你要装的是什么:一张叶证书,还是一个能签发更多证书的权威。然后决定它去哪里,并点名读取该存储的应用。趁还记得时写下移除路径,因为“信任完就继续”正是临时导入变成永久导入的方式。

最后,规划变更。固定证书或指纹必须在证书轮换时通过同一条受信任通道更新,而私有 CA 有自己的签发与吊销生命周期。如果私钥泄露,重新信任同一张证书的副本什么也修不好;身份必须替换。

如果一条警告活过了全部五项检查,有用的问题不是哪个按钮让它闭嘴,而是客户端实际读取哪个存储,以及那个根证书从哪里来。回答这一点,你即将做的导入就会成为决定,而不是条件反射。

相关文章