CA 证书详解:从原理到实践的完整教程 / 第 2 章:工作原理
第 2 章:工作原理
本章深入剖析 TLS 握手过程中证书的验证机制,包括证书链构建、信任锚点匹配、OCSP 在线验证等核心流程。
2.1 TLS 握手概览
TLS(Transport Layer Security)握手是客户端与服务器建立加密连接的过程。证书在其中扮演"身份证明"的角色。
TLS 1.2 握手流程
Client Server
│ │
│──── ClientHello ──────────────────▶│ ① 客户端发送支持的 TLS 版本、
│ │ 密码套件列表、随机数
│◀─── ServerHello ──────────────────│ ② 服务器选定密码套件
│◀─── Certificate ─────────────────│ ③ 服务器发送证书链
│◀─── ServerHelloDone ─────────────│
│ │
│──── ClientKeyExchange ────────────▶│ ④ 客户端生成预主密钥
│──── ChangeCipherSpec ─────────────▶│ ⑤ 双方切换到加密通信
│──── Finished ─────────────────────▶│
│ │
│◀─── ChangeCipherSpec ────────────│
│◀─── Finished ────────────────────│
│ │
│◀═══ Application Data (加密) ═════▶│ ⑥ 开始加密通信
TLS 1.3 握手改进
TLS 1.3 将握手简化为 1-RTT(Round-Trip Time),甚至支持 0-RTT 恢复:
Client Server
│ │
│──── ClientHello + KeyShare ───────▶│ ① 密钥交换提前
│◀─── ServerHello + EncryptedExt ──│ ② 证书被加密传输
│◀─── Certificate ─────────────────│ ③
│◀─── CertificateVerify ───────────│ ④
│◀─── Finished ────────────────────│
│ │
│──── Finished ─────────────────────▶│
│ │
│◀═══ Application Data ════════════▶│
| 特性 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手往返次数 | 2-RTT | 1-RTT |
| 0-RTT 恢复 | 不支持 | 支持(有重放风险) |
| 密码套件数量 | 较多(含不安全算法) | 精简(仅 AEAD) |
| 证书传输 | 明文 | 加密 |
| 前向保密 | 可选 | 强制((EC)DHE) |
# 检查服务器支持的 TLS 版本
openssl s_client -connect www.baidu.com:443 -tls1_2 </dev/null 2>/dev/null | grep "Protocol"
openssl s_client -connect www.baidu.com:443 -tls1_3 </dev/null 2>/dev/null | grep "Protocol"
2.2 证书验证过程
当客户端收到服务器的证书时,需要执行一系列验证步骤。
验证清单
收到证书后,客户端依次检查:
┌─────────────────────────────────────┐
│ 1. 证书格式是否合法(ASN.1 解码) │
├─────────────────────────────────────┤
│ 2. 证书是否在有效期内 │
├─────────────────────────────────────┤
│ 3. 证书签名是否有效 │
├─────────────────────────────────────┤
│ 4. 证书链是否能追溯到信任锚点 │
├─────────────────────────────────────┤
│ 5. 域名是否匹配 │
├─────────────────────────────────────┤
│ 6. 证书是否被吊销(CRL / OCSP) │
├─────────────────────────────────────┤
│ 7. 证书扩展用途(EKU)是否匹配 │
└─────────────────────────────────────┘
用 OpenSSL 模拟完整验证
# 完整验证过程
echo | openssl s_client -connect github.com:443 -servername github.com \
-verify 5 -verify_return_error -status \
-CApath /etc/ssl/certs 2>&1 | grep -E "Verify|verify|depth|OCSP"
# 输出示例:
# depth=2 C=US, O=DigiCert Inc, CN=DigiCert Global Root G3
# depth=1 C=US, CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1
# depth=0 CN=github.com
# Verify return code: 0 (ok)
# OCSP Response Status: successful (0)
逐项验证详解
2.2.1 有效期验证
# 查看证书有效期
echo | openssl s_client -connect expired.badssl.com:443 2>/dev/null \
| openssl x509 -noout -dates
# 输出:
# notBefore=Apr 9 00:00:00 2015 GMT
# notAfter=Apr 12 23:59:59 2015 GMT
# ↑ 已过期
# 验证过期证书会报错
openssl s_client -connect expired.badssl.com:443 </dev/null 2>&1 | grep "Verify"
# Verify return code: 10 (certificate has expired)
2.2.2 签名验证
签名验证的数学原理:
签名过程(CA 签发时):
证书数据 → Hash(证书数据) → CA私钥加密 → Signature
验证过程(客户端验证时):
Signature → CA公钥解密 → Hash'
证书数据 → Hash(证书数据) → Hash''
比较 Hash' == Hash'' → 签名有效
# 手动验证签名
# 1. 获取证书
echo | openssl s_client -connect www.baidu.com:443 2>/dev/null \
| openssl x509 > leaf.pem
# 2. 获取签发者证书(中间证书)
echo | openssl s_client -connect www.baidu.com:443 -showcerts 2>/dev/null \
| awk '/BEGIN/{n++} n==2' > intermediate.pem
# 3. 验证签名
openssl verify -CAfile intermediate.pem leaf.pem
2.3 证书链构建
AIA(Authority Information Access)
如果服务器没有发送完整的证书链,客户端可以通过证书中的 AIA 扩展信息自动下载缺失的中间证书。
# 查看证书的 AIA 扩展
echo | openssl s_client -connect www.baidu.com:443 2>/dev/null \
| openssl x509 -noout -text | grep -A2 "Authority Information Access"
# 输出示例:
# Authority Information Access:
# OCSP - URI:http://ocsp.digicert.com
# CA Issuers - URI:http://cacerts.digicert.com/DigiCertSecureSiteCNCAG3.crt
⚠️ 注意:依赖 AIA 会导致额外的 HTTP 请求,增加 TLS 握手延迟(约 50-200ms)。最佳实践是在服务端配置完整的证书链。
Path Building 算法
客户端构建证书路径的策略:
策略 1:深度优先(Depth-First)
从终端证书开始,沿 issuer 字段逐级向上查找
策略 2:广度优先(Breadth-First)
同时搜索所有可能的中间证书路径
策略 3:信任锚点匹配(Trust Anchor Matching)
从本地信任存储中的根证书出发,向下匹配
Linux 系统中的路径构建
# 使用 openssl 验证证书链
# -CAfile: 指定信任的根证书
# -untrusted: 指定中间证书
openssl verify \
-CAfile /etc/ssl/certs/ca-certificates.crt \
-untrusted chain.pem \
leaf.pem
# 如果缺少中间证书,可能报错:
# error 2 at 1 depth lookup: unable to get issuer certificate
2.4 信任锚点(Trust Anchor)
信任锚点是整个验证过程的"终点"——一个被客户端预先信任的根证书。
信任锚点的管理
# 查看系统信任锚点数量
ls /etc/ssl/certs/*.pem 2>/dev/null | wc -l
# 输出: 130+ (Ubuntu)
# 列出信任锚点摘要
for cert in /etc/ssl/certs/*.pem; do
subject=$(openssl x509 -in "$cert" -noout -subject 2>/dev/null)
if echo "$subject" | grep -q "Root"; then
echo "$subject"
fi
done | head -20
自定义信任锚点
# 添加自定义 CA 为信任锚点(Debian/Ubuntu)
sudo cp my-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# 验证
openssl verify /path/to/cert-signed-by-my-ca.pem
2.5 OCSP(Online Certificate Status Protocol)
OCSP 用于在线查询证书的吊销状态,是 CRL(Certificate Revocation List)的替代方案。
OCSP vs CRL
| 特性 | CRL | OCSP |
|---|---|---|
| 查询方式 | 下载完整吊销列表 | 实时查询单张证书状态 |
| 数据量 | 大(所有吊销证书) | 小(仅查询的证书) |
| 实时性 | 低(定期更新) | 高(实时查询) |
| 隐私 | 高(不暴露访问哪个网站) | 低(CA 知道用户访问了哪个网站) |
| 协议 | LDAP / HTTP | HTTP |
OCSP 查询示例
# 获取 OCSP 响应者 URL
echo | openssl s_client -connect www.baidu.com:443 2>/dev/null \
| openssl x509 -noout -ocsp_uri
# 输出: http://ocsp.digicert.com
# 发送 OCSP 查询
# 1. 提取证书和签发者证书
echo | openssl s_client -connect www.baidu.com:443 -showcerts 2>/dev/null > chain.pem
# 2. 构建 OCSP 请求
openssl ocsp \
-issuer intermediate.pem \
-cert leaf.pem \
-url http://ocsp.digicert.com \
-resp_text -no_nonce
# 输出示例:
# Response Status: successful (0)
# Cert Status: good ← 证书有效
# This Update: May 8 00:00:00 2025 GMT
# Next Update: May 15 00:00:00 2025 GMT
OCSP Stapling
OCSP Stapling(OCSP 装订)解决了 OCSP 的隐私和性能问题:由服务器主动获取 OCSP 响应,在 TLS 握手时"钉"在证书旁边发送给客户端。
传统 OCSP:
客户端 ──▶ OCSP Responder ──▶ "证书有效"
(每次连接都要查询,CA 知道用户访问了哪些网站)
OCSP Stapling:
服务器 ──定期──▶ OCSP Responder(获取响应并缓存)
客户端 ◀─── 证书 + OCSP 响应 ──── 服务器
(客户端无需额外请求,保护隐私)
Nginx 配置 OCSP Stapling:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# 启用 OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# 指定信任的 CA 证书链(用于验证 OCSP 响应)
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
# DNS 解析器
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
}
# 验证 OCSP Stapling 是否生效
echo | openssl s_client -connect example.com:443 -status 2>/dev/null \
| grep -A2 "OCSP Response"
# 输出:
# OCSP Response Status: successful (0)
# Cert Status: good
2.6 证书吊销机制
CRL(Certificate Revocation List)
# 获取 CRL 分发点
echo | openssl s_client -connect www.baidu.com:443 2>/dev/null \
| openssl x509 -noout -text | grep -A2 "CRL Distribution Points"
# 下载并查看 CRL
curl -s "http://crl3.digicert.com/DigiCertGlobalRootG3.crl" > ca.crl
openssl crl -in ca.crl -noout -text | head -20
吊销检查的优先级
客户端收到证书后:
1. 先检查 OCSP Stapling 响应(最快)
2. 如果没有,查询 OCSP 响应者
3. 如果 OCSP 不可用,下载 CRL
4. 如果都不可用(soft-fail),继续连接或拒绝
| 浏览器 | 默认策略 |
|---|---|
| Chrome | 使用 CRLSets(本地吊销列表,定期更新),不实时查询 |
| Firefox | 实时 OCSP 查询,支持 OCSP Stapling |
| Safari | OCSP 查询 + CRL |
🔒 安全:Chrome 的 CRLSets 策略意味着它不实时检查证书吊销状态,而是依赖 Google 推送的增量吊销列表。这在安全和性能之间做了一个权衡。
2.7 证书透明度(Certificate Transparency, CT)
CT 是一个开放的框架,用于监控和审计所有公开签发的证书。
CT 的工作原理
CA 签发证书
│
▼
提交到 CT Log(多个独立的日志服务器)
│
▼
获得 SCT(Signed Certificate Timestamp)
│
├── 嵌入到证书中(precertificate SCT)
├── 通过 TLS 扩展发送(TLS extension SCT)
└── 通过 OCSP 响应发送(OCSP SCT)
│
▼
浏览器验证 SCT 是否存在
# 检查证书中的 SCT
echo | openssl s_client -connect www.google.com:443 -ct 2>/dev/null \
| grep -A5 "SCT"
# 使用 crt.sh 查询域名的 CT 日志
curl -s "https://crt.sh/?q=baidu.com&output=json" | jq '.[0:3] | .[] | {issuer_name, not_before, not_after}'
📋 业务场景:如果有人冒用你的域名向 CA 申请了证书,你可以在 CT 日志中发现这张"幽灵证书"。推荐使用 crt.sh 或 Facebook 的 Certificate Transparency Monitoring 来监控你的域名。
2.8 TLS 握手性能优化
会话复用
| 机制 | 说明 | 优势 |
|---|---|---|
| Session ID | 服务器缓存会话参数 | 客户端可恢复之前的会话 |
| Session Ticket | 服务器将会话参数加密发给客户端 | 服务器无需维护状态 |
| TLS 1.3 PSK | Pre-Shared Key 恢复 | 0-RTT 或 1-RTT 恢复 |
Nginx 配置会话复用
# SSL 会话缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Session Ticket
ssl_session_tickets on;
# 建议定期轮换 ticket key
ssl_session_ticket_key /etc/nginx/ssl/ticket.key;
Certificate Compression
TLS 1.3 支持证书压缩,减少握手数据量:
# Nginx(需要 OpenSSL 3.0+)
ssl_certificate_compression zlib;
2.9 完整验证示例脚本
#!/usr/bin/env bash
# ssl-verify.sh - 全面验证目标站点的证书状态
# 用法: ./ssl-verify.sh <hostname> [port]
set -euo pipefail
HOST="${1:?用法: $0 <hostname> [port]}"
PORT="${2:-443}"
echo "========================================="
echo " SSL/TLS 证书验证报告: ${HOST}:${PORT}"
echo "========================================="
echo ""
# 1. 获取证书信息
CERT_INFO=$(echo | openssl s_client -connect "${HOST}:${PORT}" \
-servername "${HOST}" -status 2>/dev/null)
echo "【1. 基本信息】"
echo "$CERT_INFO" | openssl x509 -noout \
-subject -issuer -dates -serial -fingerprint 2>/dev/null
echo ""
# 2. 验证证书链
echo "【2. 证书链验证】"
VERIFY_RESULT=$(echo | openssl s_client -connect "${HOST}:${PORT}" \
-servername "${HOST}" -verify 5 -CApath /etc/ssl/certs 2>&1)
echo "$VERIFY_RESULT" | grep -E "Verify|depth"
echo ""
# 3. 证书链详情
echo "【3. 证书链详情】"
echo "$CERT_INFO" | grep -E "^ [0-9] s:|^ i:" 2>/dev/null
echo ""
# 4. OCSP 状态
echo "【4. OCSP 状态】"
if echo "$CERT_INFO" | grep -q "OCSP Response Status: successful"; then
echo "OCSP Stapling: ✅ 已启用"
echo "$CERT_INFO" | grep "Cert Status"
else
echo "OCSP Stapling: ❌ 未启用"
fi
echo ""
# 5. TLS 版本
echo "【5. TLS 版本】"
for ver in tls1_2 tls1_3; do
if echo | openssl s_client -connect "${HOST}:${PORT}" \
-servername "${HOST}" -"${ver}" </dev/null 2>/dev/null | grep -q "Protocol.*TLSv"; then
PROTO=$(echo | openssl s_client -connect "${HOST}:${PORT}" \
-servername "${HOST}" -"${ver}" </dev/null 2>/dev/null | grep "Protocol" | awk '{print $NF}')
echo " ${ver}: ✅ 支持 (${PROTO})"
else
echo " ${ver}: ❌ 不支持"
fi
done
echo ""
# 6. 密码套件
echo "【6. 协商的密码套件】"
echo "$CERT_INFO" | grep "Cipher is"
echo ""
# 7. 天数检查
echo "【7. 有效期检查】"
NOT_AFTER=$(echo | openssl s_client -connect "${HOST}:${PORT}" \
-servername "${HOST}" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null \
| cut -d= -f2)
EXPIRE_EPOCH=$(date -d "$NOT_AFTER" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$NOT_AFTER" +%s 2>/dev/null)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRE_EPOCH - NOW_EPOCH) / 86400 ))
if [ "$DAYS_LEFT" -gt 30 ]; then
echo " 证书剩余天数: ${DAYS_LEFT} 天 ✅"
elif [ "$DAYS_LEFT" -gt 0 ]; then
echo " 证书剩余天数: ${DAYS_LEFT} 天 ⚠️ 即将过期!"
else
echo " 证书已过期 ${DAYS_LEFT#-} 天 ❌"
fi
echo ""
echo "========================================="
echo " 验证完成"
echo "========================================="
# 运行示例
chmod +x ssl-verify.sh
./ssl-verify.sh github.com
./ssl-verify.sh www.baidu.com
2.10 本章小结
| 主题 | 关键要点 |
|---|---|
| TLS 握手 | TLS 1.3 简化为 1-RTT,证书在 TLS 1.3 中被加密传输 |
| 证书验证 | 包括有效期、签名、链追溯、域名匹配、吊销状态等 |
| 证书链构建 | 优先使用服务器提供的链,AIA 作为后备 |
| OCSP Stapling | 解决 OCSP 隐私和性能问题,应积极启用 |
| CT | 证书透明度用于监控和审计,防范错误签发 |
📚 扩展阅读
- RFC 8446 - TLS 1.3
- RFC 6960 - OCSP
- RFC 6962 - Certificate Transparency
- High Performance Browser Networking - TLS
- Let’s Encrypt - Chain of Trust
上一章:第 1 章:CA 证书概述 下一章:第 3 章:证书类型 — 了解 DV、OV、EV 等各类证书的区别与适用场景。