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网站,浏览器不会直接信任服务器发来的证书。它会问:「你是谁担保的?」这就是证书链的概念。
典型的证书链长这样:
- 根证书(Root CA):自签名,预埋在操作系统或浏览器中。
- 中间证书(Intermediate CA):由根证书签发,用于分担风险。
- 终端证书(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 对比
| 维度 | CRL | OCSP |
|---|---|---|
| 实时性 | 低(依赖更新周期) | 高(实时查询) |
| 网络开销 | 客户端下载整个列表 | 每次查询一个证书 |
| 隐私 | 好(不暴露访问记录) | 差(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 知识体系总览
下面这张图,把证书体系的几个核心模块串起来了。你对照着看,思路会更清晰。
这张图把证书结构、证书链、自签名与CA、吊销机制四个模块的关系画清楚了。你仔细看,每个模块下面还有子节点。做安全审计的时候,这四个方面都要检查到,缺一个都可能出问题。
实战提醒:我审计过的项目中,证书链不完整和吊销机制缺失是最常见的两个问题。前者导致部分客户端无法访问,后者让泄露的证书依然有效。建议你把证书链检查和OCSP Stapling配置纳入日常巡检脚本。