4、证书验证与使用:证书路径验证、CRL/OCSP在线状态检查、TLS握手中的证书协商
证书拿到手,不是终点,而是起点。我见过太多团队把证书部署上去就以为万事大吉,结果线上出了大问题才回头排查——嗯,证书验证这个环节,其实才是真正考验功底的地方。
这一章我们聚焦三个核心问题:怎么证明证书链是可信的?怎么知道证书还没被吊销?TLS握手时证书是怎么协商出来的?说白了,就是回答「这张证书到底能不能用」。
4.1 证书路径验证:信任是怎么传递的?
你想想看,浏览器里预装了上百个根证书,但你的服务器证书又不是根CA直接签的。中间可能隔了两三层。浏览器凭什么信任你?
靠的就是证书路径构建与验证(Path Validation)。
我习惯把这个过程拆成两步:
- 路径构建:从终端实体证书往上找,直到找到一个信任锚(根证书)。
- 路径验证:沿着这条链,逐级检查签名、有效期、密钥用法、名称约束等。
核心原则:信任是单向传递的。根CA信任中间CA,中间CA信任终端实体。但反过来不行——终端实体不能代表中间CA做任何事。
具体验证时,RFC 5280 规定了至少要做这几项检查:
| 检查项 | 说明 | 我踩过的坑 |
|---|---|---|
| 签名验证 | 用父证书的公钥验子证书的签名 | 有一次中间CA证书的签名算法是SHA1,但终端证书是SHA256,链直接断了 |
| 有效期检查 | 每个证书的当前时间必须在notBefore和notAfter之间 | 跨时区服务器时间不同步,导致证书「提前过期」 |
| 密钥用法检查 | CA证书必须有keyCertSign,终端证书不能有 | 有人把终端证书配了keyCertSign,直接被OpenSSL拒绝 |
| 名称约束检查 | 中间CA签发的证书域名必须在允许范围内 | 某次私有CA没配nameConstraints,子CA签了不该签的域名 |
| 基本约束检查 | CA证书的pathLenConstraint限制了链深度 | 超过深度限制的链,验证直接失败 |
一个小技巧:我写验证代码时,习惯先构建完整的证书链,再逐级验证。不要边构建边验证——路径可能有多条,先找到最优路径再验证更可靠。
4.2 CRL与OCSP:证书的「死亡证明」
证书还没过期,但私钥泄露了怎么办?或者员工离职了,证书得立刻作废。这时候就需要吊销检查。
业界有两种主流方案:CRL 和 OCSP。我分别说说。
4.2.1 CRL(证书吊销列表)
CRL 说白了就是一张黑名单。CA 定期发布一个列表,里面是所有被吊销证书的序列号。验证方下载这个列表,查一下目标证书在不在里面。
优点:简单,离线也能查。
缺点:列表会越来越大,实时性差。
我曾经在一个大客户现场遇到过一个问题:他们的 CRL 文件已经超过 50MB,每次下载都要好几秒,TLS 握手直接超时。后来我们切到了 OCSP 才解决。
注意:CRL 有有效期(nextUpdate)。如果验证方拿到的 CRL 已经过期,必须拒绝信任。我见过有人缓存了过期的 CRL 没刷新,结果吊销的证书还在用。
4.2.2 OCSP(在线证书状态协议)
OCSP 是实时查询。验证方向 OCSP 响应器发一个请求,问「这张证书现在什么状态?」响应器返回 good、revoked 或 unknown。
优点:实时、轻量。
缺点:OCSP 响应器可能挂掉,或者被中间人攻击。
为了解决 OCSP 响应器单点故障的问题,业界搞了个OCSP Stapling。什么意思呢?服务器自己定期去 OCSP 响应器拿一个带时间戳的响应,然后在 TLS 握手时「钉」给客户端。客户端不用再主动查 OCSP,省了一次网络开销。
我建议:生产环境优先用 OCSP Stapling。既保证了实时性,又避免了客户端直连 OCSP 响应器的性能瓶颈和隐私泄露问题。
下面这张图展示了 CRL 和 OCSP 的核心流程对比:
4.3 TLS握手中的证书协商
终于到了最实际的部分。TLS 握手时,证书是怎么被选出来、发出去、被验证的?
我以 TLS 1.3 为例,把流程简化成几个关键步骤:
- ClientHello:客户端告诉服务器自己支持的密码套件、TLS 版本,以及可选的 SNI(服务器名称指示)。
- ServerHello + 证书:服务器根据 SNI 选出一张合适的证书,连同完整的证书链(不含根)一起发给客户端。
- 证书验证:客户端收到证书链后,执行我们前面说的路径验证 + 吊销检查。
- 密钥交换:验证通过后,继续完成握手,生成会话密钥。
一个常见的坑:服务器配置了多张证书,但 SNI 匹配逻辑写错了。我曾经排查过一个案例,客户端发来 SNI 是 www.example.com,服务器却返回了 *.example.com 的通配符证书。虽然也能用,但浏览器弹了安全警告——因为通配符证书不匹配具体子域名。
关于证书协商,有几个关键点我特别想强调:
- 证书链必须完整:服务器发证书链时,一定要包含中间证书,不能只发终端证书。否则客户端没有中间证书,构建不了路径。我见过太多运维只配了服务器证书,忘了配中间证书链。
- OCSP Stapling 的协商:服务器在 Certificate 消息之后,可以带上一个 CertificateStatus 消息,里面就是 OCSP Stapling 响应。客户端如果支持,就直接用这个响应,不再主动查 OCSP。
- 证书类型匹配:ECC 证书和 RSA 证书的密码套件不同。服务器要根据客户端支持的密码套件,选择合适的证书类型。如果客户端只支持 ECDHE 套件,服务器却配了 RSA 证书,握手会失败。
我个人的习惯:在 Nginx 或 Apache 里配置证书时,我会用 OpenSSL 命令行先验证一遍证书链:openssl verify -CAfile root.pem -untrusted intermediate.pem server.pem。这条命令能提前发现链不完整、顺序不对、签名算法不兼容等问题。
最后说一个真实案例。有一次我帮一个客户排查 TLS 握手失败的问题。客户端报错「certificate unknown」,但证书链看起来没问题。折腾了半天,发现是 OCSP 响应器的 URL 配错了——证书里的 Authority Information Access 扩展指向了一个内网地址,客户端访问不到。嗯,这种问题,光看证书本身是看不出来的,必须抓包看扩展字段。
证书验证这件事,细节决定成败。路径验证、吊销检查、握手协商,环环相扣。任何一个环节出问题,用户看到的都是「连接不安全」或者「无法建立安全连接」。所以,我建议你在上线前,把这三个环节都走一遍端到端测试,别等到线上出问题再回头查。