X.509 是一种证书格式标准,HTTPS、mTLS、代码签名、S/MIME、电子邮件加密全在用。但它的字段密密麻麻、术语晦涩——SAN、CN、SKI、AKI、KU、EKU、CRL、OCSP……不弄清楚每个字段干什么,配错证书就会”看起来没问题但客户端就是不认”。这篇把 X.509 的关键字段从语义到坑一次讲透。
一张证书包含什么
一张 X.509 证书本质上是一份结构化数据,CA 用自己的私钥对其签名。核心分三块:
| 块 | 内容 | 作用 |
|---|---|---|
| TBS Certificate(待签名部分) | 序列号、签发者、主体、公钥、扩展字段 | 实际信息载体 |
| Signature Algorithm | sha256WithRSAEncryption 等 | 标识签名算法 |
| Signature Value | 签发者私钥对 TBS 部分的签名 | 防篡改 |
客户端验证时用签发者的公钥对 Signature Value 解签,再算一遍 TBS 部分的哈希对比——一致就说明未被篡改、确实由该 CA 签发。
主体身份字段:CN 和 SAN
历史上证书绑定域名靠 CN(Common Name)——CN=www.example.com。但 CN 设计上是 Distinguished Name 的一个组成部分(DN 还包含 O 组织、OU 部门、C 国家、L 城市等),语义模糊:同一个 CN 可以是”个人姓名”、“机器名”、“域名”。
RFC 6125(2011 年)正式推荐 SAN(Subject Alternative Name) 作为主体身份的权威来源。Chrome 58(2017 年)、Firefox 48(2016 年)起完全忽略 CN 里的域名信息——只看 SAN。
SAN 支持多种类型:
SAN: DNS:example.com,
DNS:www.example.com,
DNS:*.api.example.com,
IP:192.168.1.1,
email:admin@example.com,
URI:https://example.com/path
实务规则:
- 申请证书时 SAN 里必须列出所有要保护的域名,包括和 CN 同名的
- 通配符证书
*.example.com只匹配一级子域名,不匹配a.b.example.com - 多域名证书(SAN 证书)一张能装几十上百个域名
- 自签证书测试时同样要带 SAN,否则 curl/Go/Node 客户端报
x509: certificate is not valid for any names
签发者:颁发者 DN 和密钥标识符
签发者信息有两种:
- Issuer DN:
CN=Let's Encrypt R3, O=Let's Encrypt, C=US——人类可读 - Authority Key Identifier(AKI):通常是签发者公钥的 SHA-1 摘要——机器精确匹配
仅靠 Issuer DN 找不到唯一签发者——同名 CA 可能签发多套证书。AKI 才是构建证书链时的精确指针:本证书的 AKI 应该等于上级证书的 Subject Key Identifier(SKI)。
链验证逻辑(简化):
- 取叶子证书的 AKI
- 在受信库 + bundle 里找 SKI 等于该 AKI 的证书
- 找到的就是上级,递归到根
链断了就是 SKI/AKI 对不上——这是 Chain issues: incomplete 的真实原因。
证书链:根、中间、叶子的分工
实际部署是三层结构:
| 层 | 作用 | 寿命 |
|---|---|---|
| 根证书(Root CA) | 自签名,浏览器/系统内置 | 20-30 年 |
| 中间证书(Intermediate CA) | 根签发的”二级 CA”,实际签用户证书 | 5-10 年 |
| 叶子证书(Leaf) | 服务器/用户实际使用 | 1 年(DV/OV)/ 90 天(Let’s Encrypt) |
为什么要中间层:根证书私钥离线保管(HSM、断网机房),几十年才取出用一次签发新中间 CA;中间 CA 在线签具体用户证书。万一中间 CA 私钥泄露,吊销中间 CA 即可,根不动;而根 CA 一旦泄露就是行业级灾难。
HTTPS 服务端必须发完整链:叶子在前、中间紧跟。根可省——浏览器自带。Nginx 配置:
ssl_certificate /etc/ssl/fullchain.pem; # 叶子 + 中间,按顺序拼
ssl_certificate_key /etc/ssl/privkey.pem;
只配叶子证书的话,桌面 Chrome 可能因为缓存过中间证书而能访问,但手机端、新设备首次访问就会失败——非常隐蔽的坑。
有效期与时钟陷阱
Not Before 生效时间、Not After 过期时间,都是 UTC。客户端用本地系统时钟比对:
- 时钟回拨到 1970 → 所有证书都”未生效”,HTTPS 全挂
- 时钟超前 → 当前证书”已过期”
- 树莓派、IoT 设备无 RTC → NTP 同步前一切证书都不可用
续期节奏:
- 老式 DV/OV 证书:1-2 年
- Let’s Encrypt:90 天,建议过期前 30 天自动续
- CA/Browser Forum 趋势:2027 年统一缩到 47 天,2029 年前到 6 天
短期证书是大势所趋——自动化续期比”挑长期证书”更值得投入。
Key Usage 与 Extended Key Usage
证书签了不等于什么都能干。两组扩展字段限制合法用途:
Key Usage(KU) 基础权限位:
digitalSignature:用于签名keyEncipherment:用于密钥交换(RSA TLS)keyCertSign:用于签发下级证书(CA 才有)cRLSign:用于签发吊销列表
Extended Key Usage(EKU) 细分用途:
serverAuth(1.3.6.1.5.5.7.3.1):HTTPS 服务端clientAuth(1.3.6.1.5.5.7.3.2):mTLS 客户端codeSigning(1.3.6.1.5.5.7.3.3):代码签名emailProtection(1.3.6.1.5.5.7.3.4):S/MIMEtimeStamping(1.3.6.1.5.5.7.3.8):时间戳服务
实务:
- 浏览器看到 EKU 不含
serverAuth→ 直接拒绝,不管其他字段对不对 - 代码签名证书不能拿去做 HTTPS,CA 也不会签发跨用途证书
- mTLS 客户端证书必须有
clientAuth
吊销机制:CRL 与 OCSP
证书过期前如果发现私钥泄露、错签、主体作恶等情况,CA 需要提前作废。两种机制:
- CRL(Certificate Revocation List):CA 定期发布”已吊销证书的序列号清单”,客户端下载比对。问题:CRL 文件越来越大,更新延迟可达数天
- OCSP(Online Certificate Status Protocol):客户端每次握手时实时查询 CA”这个证书还有效吗”。问题:每次访问都要联网查 CA,慢且暴露用户访问轨迹
- OCSP Stapling:服务端定期向 CA 取 OCSP 应答(带签名+时间戳),握手时附带发给客户端,避免客户端直连 CA。Nginx 一行
ssl_stapling on;就开
Chrome 的现状:Chrome 已基本放弃 OCSP,改用 CRLite——把 CRL 压缩成 Bloom filter 推给客户端,不再回 CA。
PEM 与 DER:同一份证书的两种皮
- DER:纯二进制,文件最小但不能 cat 看
- PEM:DER 做 base64 +
-----BEGIN CERTIFICATE-----包头,纯文本
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUBYPn7xK0g8...
(base64 内容)
-----END CERTIFICATE-----
扩展名约定(不是强制):
| 扩展名 | 通常是 |
|---|---|
.pem .crt | PEM |
.der | DER |
.cer | 两边都用——必须用 file 命令或看头部判断 |
.p7b .p7c | PKCS#7 容器(含证书链,无私钥) |
.pfx .p12 | PKCS#12 容器(含证书链 + 私钥,加密) |
互转:
openssl x509 -inform DER -outform PEM -in cert.der -out cert.pem
openssl x509 -inform PEM -outform DER -in cert.pem -out cert.der
混用证书时统一转成 PEM 再操作,省得格式错误抓不到原因。
自签证书的常见坑
测试环境常用自签证书,但配错的概率非常高:
- CN 写了域名却没加 SAN → 现代客户端全拒
- basicConstraints 缺失 → 客户端不知道这是 CA 还是叶子
- 链没补全 → 中间 CA 自签后没把 CA 证书塞进 trust store
- EKU 缺 serverAuth → 浏览器静默拒绝
最小可用的自签命令(带 SAN):
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=test.local" \
-addext "subjectAltName=DNS:test.local,DNS:*.test.local"
测试完别忘了从 trust store 删除——长期挂在那里是个隐患。
一份证书诊断清单
拿到一张证书后,按顺序确认:
- 主体:SAN 里有没有要保护的域名?通配符是不是覆盖到位?
- 签发链:解析 AKI,看上级 SKI 能不能匹配?bundle 里中间证书全不全?
- 有效期:Not Before / Not After,注意时区换算
- 算法:签名算法是不是 SHA-256+ RSA-2048+ 或 ECDSA-P256+?仍在用 SHA-1 / RSA-1024 立刻替换
- EKU:要做 HTTPS 必须有
serverAuth - 吊销:CRL/OCSP URL 写没写?有没有 OCSP Stapling?
把这六条逐项过一遍,配错证书的概率会降到极低。