2. TLS/SSL 基础回顾:公钥基础设施 (PKI) 原理、X.509 证书结构、证书链验证机制

聊证书轮换,绕不开 PKI。说白了,PKI 就是一套让互联网上互不相识的双方能安全通信的信任体系。你想想看,浏览器怎么知道它连的真的是你的服务器,而不是中间人?靠的就是这套体系。

我刚开始接触证书时,觉得这东西很玄乎。后来踩了几个坑,才明白核心就三件事:谁签发、谁持有、怎么验证。咱们一个一个说。

2.1 公钥基础设施 (PKI) 原理

PKI 不是某个具体技术,而是一套完整的框架。它包括硬件、软件、人员、策略和流程。核心目标是:绑定公钥与实体身份。

一个典型的 PKI 系统包含以下角色:

  • 证书颁发机构 (CA):信任的起点。CA 负责签发和吊销证书。根 CA 的证书是自签名的,浏览器和操作系统会内置一批受信任的根证书。
  • 注册机构 (RA):负责验证申请者的身份。RA 确认“你就是你”之后,才通知 CA 签发证书。很多企业把 RA 和 CA 合在一起部署。
  • 证书持有者 (Subscriber):持有公私钥对和证书的实体,比如你的 Web 服务器。
  • 依赖方 (Relying Party):需要验证证书有效性的客户端,比如浏览器或你的内部监控系统。
  • 证书吊销列表 (CRL) / 在线证书状态协议 (OCSP):用于查询证书是否已被吊销。嗯,这里要注意,OCSP 响应器如果挂了,客户端可能会回退到 CRL,甚至直接允许连接——这其实是个安全风险。

核心原则:信任不是凭空产生的,而是传递的。根 CA 信任中间 CA,中间 CA 信任最终实体证书。这就是信任链。

我在项目中遇到过一件事:某次内部系统升级,我们换了新的中间 CA,但忘了把旧中间 CA 的证书从信任库移除。结果新旧证书同时有效,运维同事分不清该用哪个,导致轮换时配错了证书链。从那以后,我养成了一个习惯——每次轮换前,先清理信任库里的过期或废弃 CA。

2.2 X.509 证书结构

X.509 是证书的标准格式。你打开一个 PEM 文件,看到 -----BEGIN CERTIFICATE----- 开头,那就是 X.509 证书。它的内部结构其实很清晰,我拆开给你看。

一个标准的 X.509 v3 证书包含以下字段:

字段 说明 轮换时关注点
版本号 (Version) 目前主流是 v3 (值为 2) 基本不用管
序列号 (Serial Number) CA 分配的唯一编号 每次签发都不同,用于吊销查询
签名算法 (Signature Algorithm) 如 SHA256-RSA、ECDSA-SHA256 轮换时建议升级到更安全的算法
颁发者 (Issuer) 签发该证书的 CA 名称 (DN) 必须与中间 CA 的 Subject 匹配
有效期 (Validity) notBefore 和 notAfter 轮换的核心依据
主体 (Subject) 证书持有者的名称 (DN) 通常包含 CN (通用名称)
公钥信息 (Subject Public Key Info) 算法 + 公钥值 轮换时会生成新的公私钥对
扩展 (Extensions) v3 引入,包含 SAN、密钥用法等 SAN 字段是轮换时最容易出错的地方

我的经验:轮换证书时,主体备用名称 (SAN) 是最容易遗漏的。有一次我轮换了一个内部服务的证书,只更新了 CN,忘了加 SAN,结果所有客户端都报主机名不匹配。从那以后,我每次签发新证书都会用 openssl x509 -in cert.pem -text -noout | grep -A 1 "Subject Alternative Name" 确认一遍。

你可以用 OpenSSL 查看证书的详细信息:

openssl x509 -in server.crt -text -noout

输出会包含上面表格里的所有字段。我个人习惯重点关注 Issuer、Validity 和 X509v3 Subject Alternative Name 这三块。

2.3 证书链验证机制

客户端拿到你的服务器证书后,不会直接信任它。它会做一件事:构建并验证证书链。

流程大致是这样的:

  1. 构建链:从服务器证书开始,根据 Issuer 字段找到签发它的 CA 证书,再根据那个 CA 证书的 Issuer 继续往上找,直到找到一个自签名的根证书。
  2. 验证签名:用上一级证书的公钥,验证下一级证书的签名。一级一级往上,直到根证书。
  3. 检查有效期:链上每个证书的当前时间必须在 notBefore 和 notAfter 之间。
  4. 检查吊销状态:通过 CRL 或 OCSP 确认证书未被吊销。
  5. 检查扩展约束:比如 Basic Constraints 里的 CA 标志、Key Usage、Extended Key Usage 等。

注意:很多运维同学在配置证书时,只上传了服务器证书,忘了附带中间 CA 证书。结果客户端无法构建完整的证书链,导致验证失败。我曾经排查过一个线上故障,折腾了两小时,最后发现就是少传了一个中间证书。

下面这张图展示了完整的证书链验证流程:

证书链验证流程 根 CA (自签名) 中间 CA 服务器证书 验证步骤: 1. 用根 CA 公钥验证中间 CA 签名 2. 用中间 CA 公钥验证服务器证书签名 3. 检查链上所有证书的有效期、吊销状态、扩展约束

你可能会问:为什么客户端不直接信任服务器证书?原因很简单——防伪造。如果每个服务器都自己签证书,那中间人攻击就太容易了。通过 CA 这个可信第三方,我们才能确保你连接的确实是目标服务器。

避坑指南:我曾经遇到过一个问题:轮换证书后,客户端仍然报错。排查发现,旧证书虽然过期了,但客户端缓存了 OCSP 响应,导致新证书的验证被跳过。解决方案是:轮换后主动刷新 OCSP 缓存,或者缩短 OCSP 响应的有效期。

2.4 证书链的常见问题

在实际运维中,证书链相关的坑不少。我列几个常见的:

  • 链不完整:服务器只发了自己的证书,没发中间 CA。客户端如果没缓存中间 CA,就无法构建完整链。
  • 链顺序错误:有些 Web 服务器要求按顺序发送证书链(服务器证书 -> 中间 CA -> 根 CA)。顺序错了,客户端可能拒绝连接。
  • 中间 CA 过期:服务器证书还在有效期内,但签发它的中间 CA 过期了。这种情况在轮换时很容易被忽略。
  • 交叉认证:不同 CA 之间互相签发证书,导致链变长。我见过一个客户,证书链长达 5 级,每次轮换都要检查所有中间证书。

嗯,这里要特别强调一下:轮换证书时,一定要同时检查整个链的有效性。不要只看服务器证书的过期时间。

你可以用 OpenSSL 验证证书链:

openssl verify -CAfile ca-chain.crt server.crt

如果输出 server.crt: OK,说明链没问题。如果报错,它会告诉你具体是哪一级证书出了问题。

我个人习惯在轮换脚本里加上这步验证。自动化轮换时,如果验证失败,直接告警并回滚,避免把坏证书推上线。