你检查服务器证书,看到时钟上还有几个月,可客户端仍然以过期消息拒绝连接。你读到的日期属于叶子证书,而抱怨可能指向路径中更深处的某个证书。验证会走一条证书链,这条链选中的任何证书都可能在各自的有限期内失败。在续期任何证书之前,先读清楚是哪一个失败了。

深度指出失败的是哪一张证书

在 OpenSSL 的诊断里,深度 0 是目标证书,深度 1 是签发它的签发者,更大的数字继续通向信任锚。深度 1 报告的过期,并不被叶子上当前的 notAfter 所否定,所以再盯一眼看得见的证书,永远解释不了这个错误。

记录失败证书的指纹及其 subject,因为不同的证书可能带着同一个 CA 名称。交叉签名恰好制造这种情况:同一个机构出现不止一次,且有效期不同,而验证器选中的正是你没想到的那一张。熟悉的标签不是标识,指纹才能区分两个同名对象。

还要留意拥有多个证书角色的产品。在 OpenVPN Access Server 上,保护 Web 界面的证书与用于 VPN 连接的证书是分开的,所以能打开管理页面的浏览器,并不能证明隧道路径没有问题。

信任输入不等于链路候选

把两个输入分开、明确地交给验证器。信任文件存放你接受的锚点;不受信任的包存放用于构建路径的中间证书。把它们混在一起,正是人们不小心把中间证书提升为根证书、然后又纳闷同样错误为何在机器之间跟着自己的原因。

$ openssl verify -verbose -show_chain -purpose sslserver \
    -CAfile trusted-roots.pem -no-CApath -no-CAstore \
    -untrusted intermediates.pem server.pem

用 -no-CApath -no-CAstore 同时关闭默认路径和默认存储,能让测试保持诚实:结果取决于你指定的文件,而不是工作站碰巧信任的东西。OpenSSL 验证参考记录了这些选项以及它们产生的诊断。

-show_chain 在验证成功时打印构建出的证书链,这有助于确认路径正是你想要的。验证失败时,保留报告的深度和 subject;这两个字段会告诉下一位读者该检查哪张证书。

它做不到的,是复现每个客户端的策略。固定、用途限制或客户端特有的身份检查,生活在客户端里,而不是这条命令里。当服务器上验证通过、连接却仍然失败时,需要检查的正是这些设置。把两种诊断分开,否则你会一边追着固定的错误,一边续期一张本来健康的证书。

这项检查证明了什么,又漏掉了什么

这是针对你提供的信任文件所做的路径与用途检查。做 HTTPS 名称检查时,用合适的选项补上预期的主机名;做 VPN 连接时,保留客户端已配置的身份检查,并使用该客户端真正读取的信任材料。

服务器发送的是一组候选,而不是你的客户端最终构建出的路径。本地信任和库的构建规则共同塑造结果,这就是同一个响应可能在一台机器上通过验证、在另一台上失败的原因。在假定评估完全一致之前,先把失败客户端的受管信任存储和 TLS 实现与正常客户端比较一下。那里的差异能解释许多「只有这台笔记本失败」的报告。

要一次看齐每张证书的有效期,而不是逐张看,可以按集合打印:

$ openssl x509 -in server.pem -noout -subject -dates
$ openssl crl2pkcs7 -nocrl -certfile intermediates.pem \
    | openssl pkcs7 -print_certs -noout

第二条命令列出 subject 和签发者,不必手动解码每个文件,这样就能发现日期已过、叶子却看似正常的中间证书。

在不削弱验证的前提下修复路径

当提供的中间证书已过期,或交叉签名证书已过时时,取得签发者当前支持的证书链,并通过服务方有文档的证书流程部署。当客户端缺少预期的受管锚点时,更新其受管信任配置或客户端软件。不要为了压掉消息,就走把包内所有证书都当作根证书的捷径。

根证书的处理值得记一笔,因为概述常常夸大它。RFC 5280 把锚点视为位于预期路径之外的输入,而 OpenSSL 记录了包含其根 CA 证书的有效性检查。两种说法对各自的体系都成立;把某个库的根过期行为推广到所有客户端,只会得到自信的错误答案。

改动之后,在受影响的客户端上强制重新认证,并确认那条具体的过期消息已经消失。只有叶子本身不合适时,才续期叶子。关闭时间检查或恢复历史交叉证书,并不会生成一条当前有效的路径;它只是把下一次失败往后推。

相关文章