4. 证书链与根证书:根证书、中间证书、服务器证书的关系
说实话,证书链这个概念,我见过太多人栽跟头了。
很多人觉得双向认证嘛,不就是客户端和服务端各拿一张证书握个手?结果一上线就报错,查半天发现是证书链没配全。嗯,今天咱们就把这个链条彻底捋清楚。
4.1 为什么需要证书链?
你想想看,如果全世界所有设备的证书都直接由根证书签发,那根证书就得24小时在线。一旦被攻破,整个信任体系就崩了。
所以实际做法是:根证书只用来签发中间证书,中间证书再去签发服务器证书或客户端证书。这样根证书可以离线保存,安全性高得多。
核心原则:根证书是信任锚点,中间证书是代理签发者,终端证书是实际使用者。三者缺一不可。
4.2 三者的角色与关系
我习惯用一个比喻来解释:
- 根证书(Root CA) —— 相当于国家外交部。它给自己签个名,证明“我是我”。一般不直接干活。
- 中间证书(Intermediate CA) —— 相当于各省外事办。由外交部授权,负责具体签发工作。
- 服务器/客户端证书(End Entity) —— 相当于你的护照。由省外事办签发,上面有你的身份信息。
验证的时候,验证方会从终端证书开始,逐级往上找,直到找到自己信任的根证书。这个过程叫证书链验证。
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 客户端连接服务端时:
- 服务端发送自己的证书链(server.crt + intermediate.crt)。
- 客户端用预置的根证书(ca.crt)验证 intermediate.crt 的签名。
- 验证通过后,再用 intermediate.crt 验证 server.crt 的签名。
- 全部通过,握手成功。否则断开连接。
反过来,服务端验证客户端证书时,流程完全一样。这就是双向认证的对称性。
嗯,证书链这块其实不难,但细节特别多。配置的时候多检查一遍,能省下后面排查问题的大把时间。