首页 / 博客 / 本文

TLS 1.3 握手全流程与证书链排障:从 ClientHello 到会话复用

🔐 网络安全📅 2026-09-14⏱ 16 分钟

前面几篇我们把 DNS 解析、DHCP 拿地址、IP 选路都讲了一遍——这些都发生在「找到对方」的阶段。但从客户端点下回车到收到第一个字节,中间还夹着一段几乎没人细看的过程:TLS 握手。它决定了这次连接能不能建立、要花几个往返、以及你是不是「看起来安全但实际可被中间人替换」。

先立一个观念。TLS 解决三个问题,且只有三个:机密性(内容看不见)、完整性(内容改不了)、身份认证(对端是不是它声称的那个)。它不负责「服务器本身是否可信」——证书只证明「你连的确实是 example.com」,不证明「example.com 不干坏事」。把这个边界想清楚,很多安全判断就不会跑偏。

一、TLS 1.3 一次握手:一个往返就够

TLS 1.2 完整握手需要 两个往返(2-RTT),1.3 把它压到了 1-RTT,会话复用场景甚至能做到 0-RTT。压缩的关键在于:1.3 让客户端在第一条消息里就把「密钥交换的素材」一起送来,服务器不必再单独回一次。逐帧看是这样:

#方向消息关键内容
1Client → ServerClientHello支持的版本、密码套件列表、key_share(密钥交换公钥)、SNI、ALPN 列表
2Server → ClientServerHello选定版本与套件、服务器的 key_share
3Server → Client{EncryptedExtensions}此后全部加密:选定的 ALPN 等扩展
4Server → Client{Certificate}服务器证书链(含中间 CA)
5Server → Client{CertificateVerify}用证书私钥对握手内容签名,证明持有私钥
6Server → Client{Finished}握手完整性校验值
7Client → Server{Finished}客户端确认,握手完成,开始传应用数据

两点值得注意:

1.2 与 1.3 的差异对照

TLS 1.2TLS 1.3
完整握手往返2-RTT1-RTT
会话复用往返1-RTT(Session Ticket)0-RTT(可带早期数据)
证书是否加密明文加密
密钥交换RSA 或 (EC)DHE,均有历史遗留仅 (EC)DHE,前向保密强制
对称加密含 CBC / RC4 等弱模式仅 AEAD(AES-GCM / ChaCha20-Poly1305)
重协商支持(曾多次被利用)移除
三个「为什么」一次说清。①为什么 1.3 要砍掉 RSA 密钥交换?因为它不提供前向保密——私钥一旦泄露,攻击者可以解密此前录下的全部流量。②为什么砍掉 CBC?因为 MAC-then-Encrypt 的结构催生了 BEAST/Lucky13 等一系列填充预言攻击。③为什么要移除重协商?因为它给了攻击者「在一次连接里不断改变安全参数」的机会。1.3 的设计哲学是:把历史上出过事的选项直接删掉,而不是留着让你配错。

二、证书链验证:四个检查点,缺一不可

浏览器判断「这张证书可信」并不是看它本身,而是沿着一条信任链往上走,最终落到操作系统/浏览器内置的一个根证书上。这条链上的每一环都要通过四个检查:

检查验证什么失败典型报错
① 签名链子证书的签名能否用父证书公钥验证,一路到受信任根unable to verify the first certificate
② 有效期链上每一张证书都在有效期内certificate has expired
③ 吊销状态是否被 CA 吊销(CRL / OCSP)certificate revoked
④ 主机名匹配证书的 CN 或 SAN 是否覆盖你访问的域名Hostname mismatch / NET::ERR_CERT_COMMON_NAME_INVALID

实践中最常出问题的不是「证书错了」,而是链不完整:服务器只发了自己的证书,没发中间 CA 证书,于是浏览器无法把链接到根上。

# 只看服务器发了什么链(-showcerts 打印全部)
openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | grep -E "^(subject|issuer|Verify)"

# 证书链是否完整(返回 0 表示链完整可信)
openssl s_client -connect example.com:443 -servername example.com -verify_return_error </dev/null 2>&1 | grep -E "Verify return code"
为什么「我的浏览器能打开,别人的打不开」?因为浏览器有中间证书缓存和补全机制(AIA Fetching):它发现链断了会自己去把中间证书下载回来补上,并且缓存住。所以站长自己测永远正常,而首次访问的新用户(尤其是移动端 App、Java 客户端、curl 这类不做 AIA 补全的客户端)就会直接握手失败。把「站长的浏览器正常」当成验收集是最大的坑——一定要用 openssl s_client 或目标客户端实测。

证书类型速查

类型覆盖域名数典型用途
DV 单域名1个人站、API 网关
DV 通配符主域 + 全部一级子域(*.a.com)多子域业务;注意不覆盖 a.com 本身与多级子域
多域名 SAN多个不相关域名一次部署覆盖多个站点
OV / EV单/多域名 + 组织实名对信任等级有要求的场景

三、SNI 与 ALPN:握手阶段就决定了「给哪个网站、说哪种话」

这两个扩展都不涉及加密,却是虚拟主机与 HTTP/2 能否工作的开关。

# 看 ALPN 是否协商到 h2
openssl s_client -connect example.com:443 -servername example.com -alpn h2,http/1.1 2>/dev/null | grep -i "ALPN"

# 指定 TLS 版本与套件排查(模拟老客户端)
openssl s_client -connect example.com:443 -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256
一个隐蔽的坑:-servername 不能省。用 IP 直连做测试时如果不带 -servername,curl / openssl 的请求里就没有 SNI,服务器会回落到默认站点、发错证书,于是你得到一堆「域名不匹配」的假故障。测试脚本里务必显式指定 SNI,这也解释了为什么「用 IP 访问 HTTPS 站点」经常直接失败。

四、OCSP Stapling:让证书吊销检查不再拖慢握手

验证证书是否被吊销,最原始的做法是客户端自己去问 CA 的 OCSP 服务器。问题有两个:多一次网络往返(握手变慢),而且等于把你的访问目标暴露给 CA(隐私问题)。

OCSP Stapling 的思路很聪明:让服务器自己去问 CA 拿一份带时间戳、由 CA 签名的吊销状态声明,然后在握手时一并捎给客户端。客户端不必外访,延迟和隐私问题同时解决。

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;   # 通常是「根 + 中间」证书
resolver 223.5.5.5 119.29.29.29 valid=300s;         # 必须配 resolver,否则无法取 OCSP
resolver_timeout 5s;

排障时经常遇到「配了 stapling 但没生效」,最可能的原因就是漏配 resolver——Nginx 需要自己解析 OCSP 服务器域名。用 openssl s_client ... -status 可以直接看到服务器有没有捎带 OCSP 响应。

五、会话复用:为什么第二次握手快得多

完整握手有非对称运算(签名验证、密钥交换),成本远高于对称加密。用户连续访问同一站点时,每次都做完整握手是巨大浪费。两种复用机制:

机制原理特点
Session ID服务器保存会话状态,用 ID 索引有状态,多机部署需共享存储/粘性
Session Ticket把会话状态加密后交给客户端保存,下次带回无状态,天然适合负载均衡集群,1.3 的标准做法
ssl_session_cache shared:SSL:10m;   # 10m 大约可存 4 万个会话
ssl_session_timeout 1d;
ssl_session_tickets on;

TLS 1.3 里去掉了 Session ID,统一用 PSK(预共享密钥) 模式:客户端在 ClientHello 里带上 pre_shared_key 扩展,服务器若能接受,直接在同一往返里完成握手,这就是 0-RTT。

0-RTT 的代价:可重放。0-RTT 的早期数据没有前向保密,且不具备抗重放能力——攻击者录下这段数据可以原样重发。因此规范要求:只有幂等的读请求才允许放在 0-RTT 里,任何有副作用的操作(下单、转账、状态变更)必须等握手完全结束。在 Nginx 里可以通过 ssl_early_data on 开启,但应用侧必须配合做重放防护。

六、排障手册:从报错反推到配置

1. 一套通用诊断命令

# ① 握手是否成功、用的哪个版本和套件
openssl s_client -connect example.com:443 -servername example.com </dev/null | head -20

# ② 只看证书链与有效期
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

# ③ 逐级看链上每张证书(谁签的谁)
openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null \
  | grep -E "^ *[0-9]+ s:|^ *i:"

# ④ 模拟不支持 SNI 的老客户端,看默认站点返回什么证书
openssl s_client -connect example.com:443 </dev/null 2>&1 | grep "subject="

# ⑤ 在本机校验证书链是否完整
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt chain.pem

2. 常见报错与根因对照

报错根因处理
unable to verify the first certificate服务器没发中间证书把 fullchain.pem(域名证书 + 中间 CA)配进 ssl_certificate
certificate has expired链上某张证书过期(常是中间证书)更新证书后 reload,并检查自动化续期是否真的生效
Hostname mismatch证书 SAN 不含该域名换证书,或为该域名单独配置 server 块
no peer certificate available客户端发的 SNI 命中不到任何 server 块,回落到默认站加 server_name 或补默认站点证书
握手成功但页面卡住ALPN 协商到 h2,但中间设备不支持 HTTP/2临时关闭 http2 或排查中间设备
iOS / 微信内打不开,浏览器正常证书链不完整,客户端不做 AIA 补全同上,务必补齐中间证书

3. 一份可直接用的现代 TLS 配置

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;            # TLS1.3 由客户端选更合适
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305;

ssl_certificate     /etc/nginx/ssl/fullchain.pem;   # 必须含中间证书
ssl_certificate_key /etc/nginx/ssl/privkey.pem;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;                  # 多机部署时需共享 ticket key,否则建议关

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
resolver 223.5.5.5 119.29.29.29 valid=300s;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
关于 HSTS 的提醒。max-age=31536000 意味着浏览器在整整一年内强制用 HTTPS 访问你的域名,并且会忽略用户点击「继续访问」的选项。如果站点里还有没配好证书的子域,加上 includeSubDomains 会把这些子域直接打死。建议上线顺序:先长期观察证书续期确实稳定 → 再用小 max-age(如 300 秒)验证 → 最后才提到一年。
总结。把 TLS 当成一条流水线来看:ClientHello 里带够信息(版本、套件、key_share、SNI、ALPN)→ 服务器选一套并证明持有私钥 → 双方用证书链验证身份 → 会话密钥建立 → 后续走对称加密或会话复用。排障时记住一句话:「浏览器能开」不等于「证书没问题」,永远用 openssl s_client 和真实客户端去验。掌握了这一层,从 DNS 到 TLS 的整条访问链路就打通了。
#TLS#HTTPS#证书#网络安全#排障
广告