如何通过独立渠道核对 VPN 证书指纹
只有在预期值来自攻击者无法控制的渠道时,指纹才能证明身份。请按以下顺序执行检查。
指纹的可信度,取决于告诉你预期值的那个渠道的可信度。如果你用来比对的数值与正在测试的连接来自同一渠道,那么这次比对除了说明控制该连接的一方前后一致之外,什么也证明不了。检查必须按固定顺序执行,某一步失败就应该让你停下,而不是推着你继续下一步。下面是 VPN 客户端报告证书发生变化时我使用的顺序。
先说明对象,再说明数值
先确定预期值属于服务器的叶证书、签发它的 CA,还是叶证书里的公钥。这是三种不同的检查,失败方式也各不相同。匹配的 CA 指纹并不能固定服务器,而 公钥哈希会被有意保留到沿用同一密钥的重新签发之后。
算法也要一并记下。SHA-256 是现在的常规预期,但旧工具在你没有特别要求时会打印 SHA-1,而 40 个字符的十六进制值意味着你即将比对两件不同的东西。
通过第二条渠道取得预期值
第二条渠道必须是你要防范的一方无法写入的渠道。你已经通过身份验证的配置下发系统、保险柜里的纸质记录、配置数据库,或由另一个流程送达的签名文档,都算合格。同事发来的聊天消息、通过搜索结果打开的帮助页面,以及粘贴到你正在调试的终端里的任何内容,都不算合格。
要索取完整数值和算法,绝不要只给前缀。冒号和大小写都不重要,但截断会把一次 256 位的核对变成猜谜。
如果没有人能从独立来源给出预期值,诚实的结论就是:今天无法验证这张证书。把这句话说出来,胜过把客户端刚刚显示的值直接固定下来,因为后者会把警告变成习惯。
在自己这一侧提取数值
对于证书文件,命令是 openssl x509 -in server.crt -noout -fingerprint -sha256。显式指定摘要很重要:不指定的话,输出会取决于你的 OpenSSL 版本。结果是用冒号分隔的十六进制字符串,所以在比对之前,去掉冒号并把两边都转为小写。
对于直接使用 TLS 的端点,可以连接并把证书管道输出:openssl s_client -connect vpn.example.com:443 -servername vpn.example.com < /dev/null | openssl x509 -noout -fingerprint -sha256。这适用于 Web 管理面板或 TLS 监听端口,不适用于控制通道位于隧道内部的 OpenVPN。对 OpenVPN,请从磁盘上的配置文件读取证书,而不是尝试从套接字里抓取。
比对应当是机械化的。用 tr -d ':' 和大小写统一把两边的值规范化,然后比对整个字符串。靠肉眼比对,正是匹配的前缀掩盖最后一个字节差异的方式。如果两个字符串不同,在寻找解释之前,先把差异当作真实的。
停止条件
对象不同就停止,因为 CA 指纹不能为叶证书开脱,叶证书也不能为 CA 开脱。算法不同就停止,因为用 SHA-1 值去比 SHA-256 值什么也说明不了。指纹匹配但证书已过期、已被吊销,或签发给另一个名称时就停止;匹配的哈希不是有效性检查,也修不好断掉的链条。如果序列号与参照记录中的序列号不一致,同样要停止,因为这是发现证书被调包最省事的办法。
如果新数值是从你正在测试的那条渠道来的,就停止。轮换可以合法地改变指纹,但替代值仍然必须来自可信流程,而不是来自引出这个问题的警告对话框。
如果建议的修法是关闭主机名验证或链条验证,就停止。指纹是给常规检查再加一道约束,而不是取代它们,除非部署方有意选择了只做固定的模型,而那个选择要承担自己的运维成本。
比对之后
记录你比对了什么、预期值来自哪里,以及时间。如果证书被重新签发,就通过同一条可信渠道更新保存的预期值,并记下新的序列号,因为正是序列号让下一次轮换可以被审计。
然后要把边界讲清楚。指纹确认的是:你手上的证书,就是有人让你预期的那一张。它并不说明主机没有被入侵,也不说明服务是否记录或限制这张证书能到达的范围。