两张 CA 证书可以持有相同的公钥和相同的 subject,却仍然不能互换。一张是自签名,另一张由旧根交叉签名,它们的颁发者名称、序列号和签名都不同。叶证书可以通过自己的 Authority Key Identifier 只与其中一张绑定。这种绑定一直不可见,直到客户端选错链、握手在没有明显原因的情况下失败。

按顺序做完这五项检查:每一项排除一种不同的故障。

检查 1:先画出两条链,再动模板

把路径写下来。旧用户群信任旧根 A,A 签发了 CA B,B 签发了服务器叶证书。新用户群直接信任自签名的 CA B。两张 B 证书持有相同的密钥和 subject,正因为如此,读 subject 行的人看到一个 CA,而客户端看到两个。

交叉签名的副本也必须是真正的 CA 证书,具备允许签名的 basic constraints 和 key usage。

检查 2:比较 AKI 字段,而不只是密钥

叶证书的颁发者名称必须与 B 的 subject 匹配,这部分通常成立。两个副本分道扬镳的地方是 Authority Key Identifier,因为它可以携带密钥标识符、颁发者证书和序列号。密钥标识符与两个副本都匹配,因为密钥完全相同。颁发者和序列号字段只与其中一个精确匹配,所以针对自签名副本构建的链,可能在交叉签名副本上失败。

去检查叶证书,而不是猜测:openssl x509 -in server.pem -noout -ext authorityKeyIdentifier。

然后比较两个候选:openssl x509 -in b-cross.pem -noout -text | grep -A2 "Authority Key Identifier",并把同一条命令用在自签名副本上。

如果叶证书携带颁发者和序列号,就只有这些值所描述的那张证书能补全链,无论客户端加载了哪个副本。

检查 3:改签发策略,绝不改已签名的字节

扩展位于签名内部。在现有叶证书上增加、删除或修改扩展不是修复,而是另一张证书,需要重新签发。改动属于签发策略:authorityKeyIdentifier = keyid,issuer:always 让新叶证书携带证书专属的绑定,而只有 keyid 时保留更宽松的形式,适合有意为之的交叉签名过渡。在下一批签发之前,先决定你的 PKI 要哪一种。

修改共享模板也会改变未来每一张证书,而不只是你眼前的服务器。

检查 4:分别验证每个信任群体

把旧路径和新路径当作两次独立的运行来测试。openssl verify -purpose sslserver -verify_hostname vpn.example -CAfile old-root.pem -untrusted b-cross-signed.pem server.pem 走的是交叉签名路径,而使用新根、不带 untrusted 文件的同一条命令走的是自签名路径。把两个根混在一个 bundle 里会掩盖到底选了哪条链。之后再用真实客户端重复一遍,因为 VPN 后端可能以 openssl 不会采用的方式偏好某条链或钉住某张证书。

通过客户端真正读取的服务配置下发中间证书,而不是通过维基页面。

检查 5:给旧路径设一个退役时钟

交叉签名按设计让旧的信任路径继续存活,这既是目的也是风险。它不能补救已被攻破的旧根,也不能把那个根从任何客户端移除。写下旧路径何时不再授予信任,并确认那一天真的会到来。

如果所有客户端都能在叶证书迁移之前接受新根,那么普通的双重信任铺开比交叉签名更简单,也少了一件要退役的事。

相关文章