第2章 证书体系:X.509 证书结构、CA 信任链、自签名证书与 CA 签名证书

2.1 X.509 证书到底长什么样?

很多人把证书当成一个“加密文件”,其实它更像一张数字身份证。我刚开始接触 TLS 时,总以为证书里就只有公钥,后来被生产环境的一个证书链问题折腾了半夜——

记忆点:X.509 v3 证书 = 公钥 + 身份信息 + 签名 + 一堆扩展。

打开一个 PEM 格式的证书,用 openssl x509 -text -noout 看,你会看到这些字段(按 ASN.1 顺序):

  • 版本号:通常是 v3(值 2)。别问我 v1/v2 哪去了,你基本遇不到。
  • 序列号:CA 给的唯一编号。企业内我见过有人用随机 128 位,也有懒人直接 timestamp。
  • 签名算法:比如 sha256WithRSAEncryption。我个人习惯看一眼这个字段,因为有些老旧系统还用 sha1,该换了。
  • 颁发者(Issuer):签这个证书的实体 DN。你想想看,这其实就是“谁给你签的名”。
  • 有效期:notBefore 和 notAfter。我遇到过证书过期但没告警导致中断的事——openssl x509 -checkend 86400 定期跑一下很重要。
  • 主体(Subject):证书持有者的 DN。对于服务器证书,CN 字段过去用域名,现在主流用 SAN 扩展。
  • 公钥信息:算法 + 公钥值。RSA 2048 位以上或 ECDSA P-256 都常见。
  • 扩展(Extensions):v3 的精华。Basic Constraints(标记 CA 能力)、Key Usage、Extended Key Usage、Subject Alternative Name(SAN,重点中的重点)。
  • 签名值(Signature):CA 用私钥对整个证书做签名。嗯,这里要注意:签名覆盖的是 tbsCertificate 部分,不包括签名本身。
实际排坑:很多开发者生成自签名证书时不加 SAN,结果浏览器报“不安全”。我经常提醒团队——SAN 才是现代浏览器认可的身份字段,CN 已经退居二线了。

2.2 CA 信任链——为什么浏览器会“认识”你的证书?

说白了,信任链就是个“签名接力”。你拿到一张服务器证书,浏览器不会直接信它,而是会去看是谁签了它,然后再看是谁签了那个签发者……一直追到浏览器内置的信任锚(根证书)。

我参与过某大厂的内部 PKI 改造,当时有三十多台中间 CA,链验证的代码写错顺序直接导致全站 TLS 握手失败。所以理解这个结构非常关键:

  • 根 CA(Root CA):自己签自己,也叫自签名根证书。所有信任的起点。通常离线保存,极少签发实体证书。
  • 中间 CA(Intermediate CA):由根 CA 签发,Basic Constraints 中 CA:TRUE。它负责下签服务器或客户端证书。好处是根可以锁起来,中间泄露也能吊销而不影响根。
  • 叶证书(Leaf Certificate):即你的服务器证书,CA: FALSE。它不能被用来签发其他证书。

验证路径时,接收方必须收到完整的证书链(根除外,因为客户端已有信任锚)。我曾经遇到客户只发了叶证书,结果安卓设备全跪——链不完整。

避坑指南:部署时务必把中间证书和服务器证书拼成一个文件(服务器在前,中间在后)。否则 Apache/Nginx 会返回链不完整。我曾经因为这个排查到凌晨三点。

2.3 自签名证书 vs CA 签名证书

自签名证书就是自己当自己的 CA——证书里的 Issuer 和 Subject 相同,签名用的也是自己的私钥。CA 签名证书则是由一个独立的 CA 实体签发,Issuer 和 Subject 不同。

你可能会问:为什么要有自签名?其实很多内部环境、开发测试甚至一些 IoT 设备都用它。但生产环境对外服务,千万别偷懒——用户浏览器会给你一个大红叉。

对比项 自签名证书 CA 签名证书
信任来源 需要手动导入信任锚 浏览器/OS 内置信任根
浏览器信任 不信任(除非本地安装根) 默认信任(公共 CA)
部署成本 零成本,一条 openssl 命令搞定 需购买或搭建内部 CA,流程较长
适用场景 开发/测试、内网安全、物联网 生产环境、对外服务、公众访问
维护复杂度 低,到期自己续就行 需要管理 CRL/OCSP,吊销策略
我的习惯:小型团队内部可以用自签名 + 导入根证书,但别忘记加 SAN。大型企业则建议搭建私有 CA(比如用 cfssl 或 easy-rsa),统一管理。

2.4 核心逻辑一览(SVG 结构图)

下面这张图把本章三个知识点串在了一起——你可以看到证书链的层级关系,以及自签名证书在这个体系中的不同位置。

证书体系核心逻辑:结构 · 信任链 · 自签名 根 CA(信任锚) 中间 CA(由根签发) 服务器证书(由中间签发) 自签名证书 验证路径 CA 签名:逐级向上至信任锚 自签名:直接信任自身公钥 Issuer = Subject

2.5 实操:用 OpenSSL 生成证书看看

光说不练假把式。下面几条命令你可以跟练一遍,马上就能感受到自签名和 CA 签名的区别:

生成自签名证书(带 SAN):
openssl req -x509 -newkey rsa:2048 -keyout self.key -out self.crt \
  -days 365 -nodes \
  -subj "/CN=myserver.local" \
  -addext "subjectAltName = DNS:myserver.local,DNS:localhost,IP:127.0.0.1"
注意! -addext 需要 OpenSSL 1.1.1+,旧版本要用配置文件。我曾在 CentOS 7 上踩过这个坑。

查看证书细节:

openssl x509 -in self.crt -text -noout | head -30

你会看到 Issuer 和 Subject 完全相同,Basic Constraints: CA:FALSE(除非你加 -extensions v3_ca)。

要搭建一个迷你 CA 链,需要先自签根:

openssl req -x509 -newkey rsa:2048 -keyout ca.key -out ca.crt \
  -days 3650 -nodes -subj "/CN=My Test Root CA" -extensions v3_ca

然后用根签中间:

# 生成中间 CSR
openssl req -newkey rsa:2048 -keyout inter.key -out inter.csr \
  -nodes -subj "/CN=My Test Intermediate CA"
# 用根签发中间(加 CA 扩展)
openssl x509 -req -in inter.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out inter.crt -days 1825 \
  -extfile <(echo "basicConstraints=critical,CA:TRUE")

最后用中间签服务器:

openssl x509 -req -in server.csr -CA inter.crt -CAkey inter.key \
  -CAcreateserial -out server.crt -days 365 \
  -extfile <(echo "subjectAltName=DNS:example.com")
验证链是否完整:
openssl verify -CAfile ca.crt -untrusted inter.crt server.crt
如果输出 server.crt: OK,说明信任链没问题。

你可能会发现,自签名证书直接 openssl verify self.crt 会报错——因为没指定信任锚。加 -CAfile self.crt 就好了,这恰好证明了自签名的信任要靠显式导入。

好了,到此你已经掌握了 X.509 证书的核心字段、CA 信任链的层级结构,以及自签名与 CA 签名证书的根本区别。这些知识是后续排查 TLS 握手失败、证书链不完整、浏览器不信任等问题的基础。记住:证书不是加密的,它只负责绑定公钥和身份,真正的安全要靠 TLS 握手协议来落实。