第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 SignatureCA 对整个证书的签名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:CATRUETRUEFALSE
是否用于签发是是否
是否在 TLS 握手时发送否(客户端自己存)是(需发送)是

你看,叶子证书的 Basic Constraints 必须设成 CA:FALSE,不然服务端/客户端会认为它还能再签下级证书,这就危险了。我在一个项目中就见过有人把服务器证书的 CA 位设成了 TRUE,虽然功能上没毛病,但安全审计直接亮红灯。

3.4 一张图看懂证书链

下面这张 SVG 把三者的关系和验证路径画清楚了。你盯着它看一分钟,比读十页文字管用。

X.509 证书链结构示意图 证书链结构(根 → 中间 → 叶子) 根证书 自签发 | CA:TRUE 私钥离线存储 中间证书 由根签发 | CA:TRUE 私钥可在线 叶子证书 由中间签发 | CA:FALSE 服务器/客户端用 验证方向 ↑ 自底向上 ↑ 依次验签 TLS 发送 ↓ 叶 + 中间 不含根

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 避坑总结:证书链最常见的三个问题

  1. 链不完整:只发了叶子证书,没发中间证书。客户端无法构建信任路径,报错 certificate chain incomplete。
  2. 中间证书顺序反了:把根证书放在中间证书前面,或叶子证书放在中间后面。标准顺序是叶子证书在前,中间证书在后,根证书不发送。
  3. 根证书过期或不受信任:客户端的信任存储里没有对应的根证书,验证直接失败。尤其是自建私有 CA 的场景,你得确保所有客户端都安装了你的根证书。

嗯,证书链这东西,理解起来就三句话:根信任锚、中间做隔离、叶子干实事。但配起来细节贼多。我个人建议你学完这章后,马上用 OpenSSL 自己搭一套根 + 中间 + 叶子的实验室环境,亲手签几张证书,走一遍验证流程,比看十遍文档都管用。