三个不同的问题由 OpenVPN 配置文件的三个不同部分回答,而把它们混为一谈,正是人们关掉一项原本有效的检查的原因。对端证书是由我信任的机构签发的吗?该证书是否声明了 TLS 服务器角色?它是否就是我想要连接的那台服务器?remote-cert-tls server 只回答中间那个问题,而且是通过读取证书内部的扩展来回答的。

三种容易在脑中混为一谈的检查

信任锚位于 ca 指令中。角色检查属于 remote-cert-tls。身份检查属于 verify-x509-name(当配置文件使用它时),或者属于部署所添加的任何名称检查。

它们覆盖不同的故障:

QuestionDirectiveFailure it catches
Who signed this?caCertificate from an unrelated authority
Is this a server?remote-cert-tlsClient certificate presented by a peer
Is this my server?verify-x509-nameAnother valid server from the same CA

一份配置文件可以三者兼备,但如果 CA 被过度广泛地共享,它仍然很弱——这属于密钥管理问题,而不是指令问题。

角色检查读取什么

该指令是一个宏。它会展开为一项密钥用途要求和一项指明 TLS Web Server 角色的扩展密钥用途要求,因此对端证书必须真正声明该用途。为客户端身份验证签发的证书无法满足它,即使两者由同一机构签发。

这一点很重要,因为在小型部署中共享 CA 很常见。一个机构为每个客户端和服务器签名,如果没有角色检查,其中任何一张证书都能完成握手的服务器端。该检查并不能让这种情形变得不可能,但它消除了其最轻易的版本。

该指令在设计上是不对称的:客户端携带 remote-cert-tls server,服务器携带 remote-cert-tls client。把它们对调,每次连接都会因角色错误而失败,看起来就像证书损坏。

较旧的配置文件有时用 ns-cert-type 达到同样的目的。当前的手册页将该选项视为已被取代,因此 仍然依赖它的配置文件值得迁移,而不是去调试。

为什么禁用它感觉像是在修复

当角色检查失败时,连接会因验证错误而停止,而显而易见的做法就是删掉那一行。然后连接就能建立起来,这让人感觉是进展,实际上却正是指令要防止的故障模式。你现在接受了一个其证书从未打算用于该角色的对端。

相反,去读错误信息。它会指出哪项检查失败,而另一侧的证书可以被检查,看它实际携带哪些扩展。读两分钟就能判定故障出在签发还是配置上。靠猜会花掉一个下午,而且通常以一份被削弱的配置文件收场。续期后的服务器证书丢失了扩展密钥用途,是续期中的真实故障,而不是放松客户端的理由。

名称检查是另一条指令

满足角色检查只说明对端是由你的机构签发的某台服务器。它没有说明是哪一台,而当同一个 CA 为多个网关签发证书、其中一个被攻陷或被改作他用时,这一点就很重要。

verify-x509-name 会将证书主题与你配置的值进行比较,并提供精确主题、前缀或单个字段的匹配模式。使用真正能标识该网关的字段,并预期在名称变化时有意更新它。

两条指令都不能取代良好的密钥管理。如果客户端密钥文件随配置文件一起被到处复制,那么对端身份的可信程度就只和分发名单一样。

在编辑配置文件之前先检查证书

先读对端证书,再读客户端日志,顺序就是这样。证书告诉你它声明了哪些角色;日志告诉你哪项检查拒绝了它。

以下命令会打印详细连接中的用途扩展和验证相关行:

openssl x509 -in server.crt -noout -text | grep -A1 -E "Key Usage|Extended Key Usage"
sudo openvpn --config client.ovpn --verb 4 2>&1 | grep -i verify

如果扩展显示服务器角色,而日志仍然报告角色失败,那么配置文件很可能指向了错误的证书文件,而不是证书本身有问题。如果扩展只显示客户端身份验证,就别再盯着客户端,去修签发环节。无论哪种情况,在宣布连接健康之前先把检查恢复,因为移除验证之后握手成功所能证明的,比它取代的那条错误要少。

相关文章