强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 证书透明度用于监控和审计,防范错误签发

📚 扩展阅读


上一章第 1 章:CA 证书概述 下一章第 3 章:证书类型 — 了解 DV、OV、EV 等各类证书的区别与适用场景。