第3章 证书基础:X.509 证书结构、证书链、根证书、中间证书
说实话,在 TLS 双向认证里,证书是身份的「身份证」。你拿着它去服务端报关,服务端也拿着它的证书让你验。如果连证书长什么样、怎么链起来的都搞不清楚,后面配置 OpenSSL 和 Nginx 肯定得踩坑。这一章我带你把它扒干净。
3.1 X.509 证书到底长啥样?
X.509 是国际电信联盟定的公钥证书格式标准,说白了就是一张结构化的「身份证」。每张证书内部有十几个字段,但常用的也就那么几个。我习惯把它分成三块:身份信息、公钥、签名。
| 字段 | 含义 | 实际长啥样 |
|---|---|---|
| Version | 证书版本 (v1 / v3) | 现在都用 v3,支持扩展 |
| Serial Number | 唯一序列号 | CA 分配,比如 01:AB:3F... |
| Signature Algorithm | 签名算法 | sha256WithRSAEncryption |
| Issuer | 签发者 DN | /C=CN/ST=Beijing/O=Example CA |
| Validity | 有效期 | Not Before – Not After |
| Subject | 持有者 DN | /CN=myserver.example.com |
| Subject Public Key Info | 公钥及算法 | RSA 2048 bit + Exponent |
| Extensions | 扩展(v3 关键) | Basic Constraints, Key Usage, SAN |
| Certificate Signature | CA 对整个证书的签名 | 256 字节二进制 |
注意:Issuer 和 Subject 字段是构建证书链的锚点。Issuer 等于上一级证书的 Subject,才能连起来。我碰到过一个客户,他把 Issuer 写成了自己的 Subject,结果自签证书装上去链一直验证失败……
你可以在终端用一条命令解剖证书:
openssl x509 -in cert.pem -text -noout
输出里你能看到上面那张表所有字段。我个人建议刚上手时多用这个命令多看几张证书,印象会深很多。
3.2 证书链是什么?为啥不能只有一张证书?
你想想看,如果每台服务器都自己签个证书,客户端要怎么相信它?你不能在客户端预置全世界所有服务器的公钥吧。所以就有了证书链——自顶向下,层层担保。
- 根证书 (Root CA):自签的,没有上级。全世界就那么几个根 CA,浏览器和操作系统内置它们的证书。
- 中间证书 (Intermediate CA):由根证书签发,用来分发签发实体证书。目的是保护根——根证书私钥一旦泄露,整个信任体系崩盘。
- 实体证书 (Leaf/End-Entity):最终签给服务器或客户端的证书,就是你配在 Nginx 里那张。
验证过程是自底向上:你拿着叶子证书,找到它的 Issuer(即中间证书),用中间证书的公钥验叶子签名;然后中间证书的 Issuer 是根证书,用根公钥验中间签名;最后根证书是信任锚点,操作系统/浏览器说它可信它就可信。嗯,这里要注意:**根证书不用验自己**,因为它是信任的起点。
曾经有个坑:我在配置双向认证时,只发了叶子证书给客户端,忘了附带中间证书。浏览器提示「证书链不完整」,折腾了半天。后来发现要在服务器配置里把中间证书和叶子证书合并成一个 PEM 文件,按叶子在前、中间在后的顺序拼接。记住:TLS 握手时服务器必须发送完整链(不含根),否则客户端没法验证。
3.3 根证书 vs 中间证书 vs 叶子证书 —— 角色分工
我用一张对比表帮你看清楚他们的区别:
| 特征 | 根证书 | 中间证书 | 叶子证书 |
|---|---|---|---|
| 签发者 | 自己 | 根 CA | 中间 CA |
| 有效期 | 极长(10–30年) | 较长(3–10年) | 短(1–2年) |
| 私钥安全等级 | 最高(离线存储) | 高(可部分在线) | 服务器本地 |
| 是否有 Basic Constraints:CA | TRUE | TRUE | FALSE |
| 是否用于签发 | 是 | 是 | 否 |
| 是否在 TLS 握手时发送 | 否(客户端自己存) | 是(需发送) | 是 |
你看,叶子证书的 Basic Constraints 必须设成 CA:FALSE,不然服务端/客户端会认为它还能再签下级证书,这就危险了。我在一个项目中就见过有人把服务器证书的 CA 位设成了 TRUE,虽然功能上没毛病,但安全审计直接亮红灯。
3.4 一张图看懂证书链
下面这张 SVG 把三者的关系和验证路径画清楚了。你盯着它看一分钟,比读十页文字管用。
3.5 在实践中,证书链怎么配置?
假设你从 Let's Encrypt 或某商业 CA 买了一张证书,通常你会拿到三个文件:
fullchain.pem—— 叶子证书 + 中间证书链(按顺序拼接)privkey.pem—— 私钥chain.pem—— 单独的中间证书链
在 Nginx 里配置双向认证时,我一般这样配:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/fullchain.pem; # 含叶子 + 中间
ssl_certificate_key /etc/ssl/private/privkey.pem;
ssl_client_certificate /etc/ssl/certs/ca-root.crt; # 客户端根证书
ssl_verify_client on;
...
}
这里 ssl_client_certificate 指定的是客户端证书链的**根证书**(或中间证书),用来验证客户端发过来的证书。我的习惯是把整个信任链的根证书单独放一份,因为服务端只需要信任根,中间证书由客户端自己发。
小技巧:我每次配置完都会用 openssl verify -CAfile ca-root.crt -untrusted chain.pem leaf.pem 验证整个链。如果命令输出 leaf.pem: OK,说明链没问题。否则它会提示「unable to get local issuer certificate」,这时候赶紧查中间证书是不是漏了。
3.6 避坑总结:证书链最常见的三个问题
- 链不完整:只发了叶子证书,没发中间证书。客户端无法构建信任路径,报错
certificate chain incomplete。 - 中间证书顺序反了:把根证书放在中间证书前面,或叶子证书放在中间后面。标准顺序是叶子证书在前,中间证书在后,根证书不发送。
- 根证书过期或不受信任:客户端的信任存储里没有对应的根证书,验证直接失败。尤其是自建私有 CA 的场景,你得确保所有客户端都安装了你的根证书。
嗯,证书链这东西,理解起来就三句话:根信任锚、中间做隔离、叶子干实事。但配起来细节贼多。我个人建议你学完这章后,马上用 OpenSSL 自己搭一套根 + 中间 + 叶子的实验室环境,亲手签几张证书,走一遍验证流程,比看十遍文档都管用。