4. 证书链与根证书:根证书、中间证书、服务器证书的关系

说实话,证书链这个概念,我见过太多人栽跟头了。

很多人觉得双向认证嘛,不就是客户端和服务端各拿一张证书握个手?结果一上线就报错,查半天发现是证书链没配全。嗯,今天咱们就把这个链条彻底捋清楚。

4.1 为什么需要证书链?

你想想看,如果全世界所有设备的证书都直接由根证书签发,那根证书就得24小时在线。一旦被攻破,整个信任体系就崩了。

所以实际做法是:根证书只用来签发中间证书,中间证书再去签发服务器证书或客户端证书。这样根证书可以离线保存,安全性高得多。

核心原则:根证书是信任锚点,中间证书是代理签发者,终端证书是实际使用者。三者缺一不可。

4.2 三者的角色与关系

我习惯用一个比喻来解释:

  • 根证书(Root CA) —— 相当于国家外交部。它给自己签个名,证明“我是我”。一般不直接干活。
  • 中间证书(Intermediate CA) —— 相当于各省外事办。由外交部授权,负责具体签发工作。
  • 服务器/客户端证书(End Entity) —— 相当于你的护照。由省外事办签发,上面有你的身份信息。

验证的时候,验证方会从终端证书开始,逐级往上找,直到找到自己信任的根证书。这个过程叫证书链验证。

证书链结构示意图 根证书 (Root CA) 自签名 · 信任锚点 中间证书 (Intermediate) 由根证书签发 服务器证书 由中间证书签发 客户端证书 由中间证书签发 签发 签发 签发 验证方向: 终端 → 中间 → 根

4.3 常见的证书链配置错误

我在项目中遇到过最典型的问题,就是只发了终端证书,没发中间证书。

举个例子:服务器配置了 server.crt,但没把中间证书拼进去。客户端拿到 server.crt 后,发现签发它的中间证书不在本地信任列表里,又找不到上级证书,直接报错。

避坑指南:我曾经帮一个客户排查问题,他们折腾了两天,最后发现是 Nginx 配置里只写了 ssl_certificate 指向 server.crt,没把中间证书和根证书拼成一个 chain 文件。加上之后,问题秒解。

4.4 如何正确配置证书链

不同的 MQTT Broker 配置方式略有不同,但原理一样。我以 Mosquitto 和 Nginx 为例:

4.4.1 Mosquitto 配置

# 正确做法:将服务器证书与中间证书合并为一个文件
cat server.crt intermediate.crt > server-chain.crt

# mosquitto.conf 配置
cafile /etc/mosquitto/certs/ca.crt          # 根证书
certfile /etc/mosquitto/certs/server-chain.crt  # 合并后的证书链
keyfile /etc/mosquitto/certs/server.key     # 私钥

4.4.2 Nginx 配置(用于 MQTT over WebSocket)

server {
    listen 443 ssl;
    ssl_certificate     /etc/nginx/certs/server-chain.crt;  # 证书链
    ssl_certificate_key /etc/nginx/certs/server.key;        # 私钥
    ssl_trusted_certificate /etc/nginx/certs/ca.crt;        # 根证书
}

小技巧:合并证书链时,顺序很重要。终端证书必须放在最前面,然后是中间证书,最后是根证书。我曾经见过有人把顺序搞反,结果验证死活过不去。

4.5 验证证书链是否完整

配置完之后,别急着上线。我习惯用 OpenSSL 先验证一遍:

# 查看证书链中的证书数量
openssl crl2pkcs7 -nocrl -certfile server-chain.crt | openssl pkcs7 -print_certs -text | grep "Subject:"

# 验证证书链是否完整
openssl verify -CAfile ca.crt -untrusted intermediate.crt server.crt

如果输出显示 server.crt: OK,说明证书链没问题。如果报错,八成是中间证书缺失或顺序不对。

4.6 根证书的存储与更新

根证书是信任的基石。我建议:

  • 离线存储:根证书的私钥不要放在生产服务器上,最好用硬件安全模块(HSM)保存。
  • 定期轮换:根证书有效期一般很长(10-20年),但中间证书建议2-5年轮换一次。
  • 客户端预置:MQTT 客户端需要预置根证书,用于验证服务端证书链。如果根证书过期,所有客户端都得更新。

经验之谈:我见过一个项目,根证书还有3年才过期,但中间证书已经过期了。结果所有新连接都失败,老连接还能用。排查起来特别坑,因为很多人只检查终端证书的有效期,忽略了中间证书。

4.7 证书链的验证流程

最后,咱们把整个验证流程串起来。当 MQTT 客户端连接服务端时:

  1. 服务端发送自己的证书链(server.crt + intermediate.crt)。
  2. 客户端用预置的根证书(ca.crt)验证 intermediate.crt 的签名。
  3. 验证通过后,再用 intermediate.crt 验证 server.crt 的签名。
  4. 全部通过,握手成功。否则断开连接。

反过来,服务端验证客户端证书时,流程完全一样。这就是双向认证的对称性。

嗯,证书链这块其实不难,但细节特别多。配置的时候多检查一遍,能省下后面排查问题的大把时间。