2. TLS证书体系:X.509证书结构、证书链验证、自签名证书与CA证书、证书吊销机制(CRL/OCSP)

聊TLS,绕不开证书。说白了,证书就是互联网世界的「身份证」。你访问一个网站,浏览器怎么知道对方是真的银行,而不是钓鱼网站?靠的就是这套X.509证书体系。我做了这么多年安全审计,见过太多因为证书配置不当导致的漏洞。这一章,咱们就把证书的里里外外扒清楚。

2.1 X.509证书结构:一张身份证的解剖

X.509是国际电信联盟(ITU-T)定义的标准。目前主流的是v3版本。一个证书文件,打开看(比如用openssl x509 -text),里面其实是一堆字段。我习惯把它分成三块:

  • 基本信息:序列号、签名算法、颁发者、有效期、主体(Subject)
  • 公钥信息:算法(RSA/ECDSA)、公钥值
  • 扩展信息(v3特有):使用者可选名称(SAN)、密钥用法、基本约束、CRL分发点等

核心字段速查表

字段含义常见坑点
Subject证书持有者身份CN与SAN不一致导致浏览器报错
Issuer颁发者身份自签名证书Issuer=Subject
Validity有效期过期证书是审计高频问题
Subject Public Key Info公钥密钥长度不足(如RSA 1024位已不安全)
X509v3 Subject Alternative Name域名列表缺少SAN会导致现代浏览器拒绝连接
X509v3 Basic Constraints是否为CA误将终端证书标记为CA会带来风险

我记得有一次排查一个内部系统,浏览器一直报「证书名称不匹配」。打开一看,Subject里写的CN是服务器IP,但SAN字段是空的。嗯,从Chrome 58开始,SAN是必须的,只看CN的时代过去了。

2.2 证书链验证:信任是怎么传递的

你访问一个HTTPS网站,浏览器不会直接信任服务器发来的证书。它会问:「你是谁担保的?」这就是证书链的概念。

典型的证书链长这样:

  1. 根证书(Root CA):自签名,预埋在操作系统或浏览器中。
  2. 中间证书(Intermediate CA):由根证书签发,用于分担风险。
  3. 终端证书(Leaf/Server Cert):由中间证书签发,直接用于服务器。

验证过程是自底向上的:

  • 用中间证书的公钥验证终端证书的签名
  • 用根证书的公钥验证中间证书的签名
  • 检查根证书是否在受信任列表中

避坑指南:我曾经遇到一个客户,部署HTTPS时只发了终端证书,没发中间证书。结果Android手机全报错,因为Android没预装那个中间CA。解决方案是在服务器配置里把中间证书和终端证书拼在一起发送。Nginx配置里ssl_certificate指向一个包含完整链的PEM文件。

为什么会这样?因为浏览器只预装根证书,中间证书需要服务器自己带上来。你想想看,如果每次都要浏览器自己去下载中间证书,那体验得多差。

2.3 自签名证书与CA证书:自己签还是找机构?

这是个经典选择题。我直接说结论:

类型适用场景风险
自签名证书开发测试、内网工具、个人实验浏览器不信任,需要手动导入;容易被中间人攻击
公共CA证书生产环境、面向公网的服务需要付费(Let's Encrypt免费),但信任度高
私有CA证书企业内部系统、物联网设备需要自己维护CA基础设施,但可控性强

自签名证书说白了就是「我自己证明我自己」。这在技术上没问题,但浏览器不认。我见过不少开发人员图省事,在生产环境用自签名证书,结果被安全扫描工具报高危漏洞。嗯,这不是证书本身不安全,而是信任模型被破坏了。

警告:永远不要在面向公网的生产环境使用自签名证书。如果你需要免费方案,用Let's Encrypt。如果你需要内部CA,用OpenSSL或cfssl搭建私有CA,然后把根证书推送到所有客户端。

生成自签名证书很简单,一行命令:

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes

但注意,这个证书没有SAN。现代浏览器会报错。正确的做法是加一个openssl.cnf配置文件,把SAN写进去。我习惯用这个模板:

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
CN = example.com

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
IP.1 = 192.168.1.100

2.4 证书吊销机制:证书被黑了怎么办?

证书不是永久的。私钥泄露、域名变更、员工离职……这些情况都需要吊销证书。但问题是:浏览器怎么知道一个证书被吊销了?

目前主流两种机制:

  • CRL(证书吊销列表):CA定期发布一个黑名单文件,列出所有被吊销的证书序列号。
  • OCSP(在线证书状态协议):实时查询,客户端向CA的OCSP服务器询问某个证书是否有效。

CRL vs OCSP 对比

维度CRLOCSP
实时性低(依赖更新周期)高(实时查询)
网络开销客户端下载整个列表每次查询一个证书
隐私好(不暴露访问记录)差(CA知道你在访问哪个网站)
性能列表可能很大(几MB)轻量,但OCSP服务器可能成为瓶颈
可靠性离线可用依赖网络,OCSP服务器宕机可能导致连接失败

我个人更倾向于OCSP Stapling。这个技术让服务器自己定期去CA获取OCSP响应,然后和证书一起发给客户端。好处是客户端不用自己去连OCSP服务器,性能更好,隐私也保护了。Nginx配置里加一行:

ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 valid=300s;

我曾经遇到一个案例:某公司的一个内部CA证书泄露了,但他们没有启用CRL或OCSP。结果攻击者用泄露的CA签发了大量假证书,在内网横行。等发现的时候,已经过了三个月。嗯,从那以后,我经手的每个CA系统,CRL和OCSP都是必配项。

小技巧:用OpenSSL查看证书的CRL分发点和OCSP地址:

openssl x509 -in cert.pem -noout -text | grep -A 4 "CRL Distribution Points"
openssl x509 -in cert.pem -noout -text | grep -A 2 "Authority Information Access"

2.5 知识体系总览

下面这张图,把证书体系的几个核心模块串起来了。你对照着看,思路会更清晰。

TLS证书体系核心结构 X.509证书体系 证书结构 证书链验证 自签名 vs CA 吊销机制 基本信息 公钥信息 扩展信息(SAN) 签名算法 根证书 中间证书 终端证书 CRL OCSP OCSP Stapling 自签名 公共CA 私有CA 核心要点 证书是信任的载体,链式验证保证信任传递,吊销机制处理信任失效。

这张图把证书结构、证书链、自签名与CA、吊销机制四个模块的关系画清楚了。你仔细看,每个模块下面还有子节点。做安全审计的时候,这四个方面都要检查到,缺一个都可能出问题。

实战提醒:我审计过的项目中,证书链不完整和吊销机制缺失是最常见的两个问题。前者导致部分客户端无法访问,后者让泄露的证书依然有效。建议你把证书链检查和OCSP Stapling配置纳入日常巡检脚本。