摘要
- TLS 1.3 的
HelloRetryRequest不是重新开局。第二个 ClientHello 除了协议明示许可的密钥份额、early data 删除、cookie 回送、binder 重算等变化,必须保持第一次提议不变。 - 协议把
ClientHello1压缩为携带其摘要的合成message_hash消息,再与重试和后续握手共同认证。无状态重试压缩了历史,并未删除历史。 - 运营证据必须同时保留两次 ClientHello、重试原因、允许的差异、cookie 策略、最终群组、告警和额外时延。握手成功本身不能证明重试合法,也不能证明最终群组原本就是客户端首选。
从中途开始的抓包制造了错误归因
现场看上去没有异常。第二个 ClientHello 携带一个群组的密钥份额,ServerHello 选择同一群组,随后证书、CertificateVerify 与 Finished 全部通过。监控系统于是把这个群组登记为“客户端提议并被服务端接受”。
缺失的第一段却记录着另一件事。客户端在 supported_groups 中声明多个可用群组,只为其中一个预先计算了 key_share。服务端不愿采用该预测,于是发出 HelloRetryRequest,要求客户端为一个已经声明支持、但尚未发送份额的群组重新提供材料。第二个 ClientHello 的单一份额是服务端请求的结果,不是客户端最初意愿的证据。
这四个状态不能混为一谈:配置允许什么、首次报文声明支持什么、首次报文实际预测了什么、最后协商选择了什么。丢失第一次问候的系统,往往会把服务端的选择倒写成客户端偏好,把一次额外往返误算成网络抖动,并在算法迁移事故中指错责任主体。
重试权由允许清单限定
当前 TLS 1.3 规范要求第二个 ClientHello 基本复现第一个。若重试含有 key_share,客户端只能换成被点名群组的一份新密钥份额;若第一次尝试了 0-RTT,必须移除 early_data;若服务端给出 cookie,必须原样带回;若使用预共享密钥,则按包含重试历史的规则重算 binder。未来扩展只有在重试中出现并明确规定变化时,才能增加例外。
这不是“尽量相似”,而是一张确定的允许清单。被保护的不只是最终算法,还有哪些字段有权在两次问候之间改变。
群组请求又受两层限制。它必须出现在第一次 supported_groups 中,服务端不能凭空给客户端增加能力;它不能已经在第一次 key_share 中出现,否则重试没有有效变化。客户端应拒绝无变化的重试、同一连接中的第二次重试、未曾提议的密码套件以及与重试群组不一致的 ServerHello。
由此,retry 这个日常词汇被收窄成一个可本地验证的状态转换:服务端可以请求一次许可范围内的纠偏,却没有权力重新打开所有字段,更不能反复施压直至客户端接受另一套策略。
message_hash 让无状态不等于无历史
TLS 的多个密码学计算依赖按顺序构造的握手记录哈希。若服务端希望发送 cookie 后释放每个客户端的临时状态,完整保存第一次 ClientHello 或某个实现专用的中间哈希状态都会增加成本。
协议因此使用一个合成握手消息。它的类型是值为 254 的 message_hash,正文是 Hash(ClientHello1)。后续记录再接上 HelloRetryRequest、ClientHello2、ServerHello 以及认证消息。CertificateVerify、Finished 和重试后的 PSK binder 都不能假装第一次问候从未发生。
这个设计保存的是密码学承诺,而非可逆副本。端点可以发现后续记录是否违背原始历史,但仅有摘要的运营团队无法还原第一次报文里每个扩展、群组和份额。因此,协议记录与可观测记录承担不同职责:前者保证连续性,后者解释究竟改变了什么。
“支持”“预测”和“选择”是三份不同证据
TLS 1.3 客户端为了省掉一次往返,会在第一次消息里预测一个或多个密钥份额。给所有支持群组都生成份额会增加计算量和 ClientHello 大小;只发一个可以节省字节,却可能在服务端偏好另一群组时触发重试。
混合后量子群组让这项取舍更明显。OpenSSL 允许运营者定义群组层级和预发份额。其文档同时指出,大型首报文可能越过 TCP 分段边界并遭遇错误防火墙,而不预发大型份额则把额外往返留给确实要求该群组的服务端。GnuTLS 也把群组策略与“只发一个 TLS 1.3 密钥份额”暴露为独立控制项。
所以资产清单里的一行“支持 X”远远不够。实现可能认识 X,配置可能启用 X,第一次报文可能列出 X,第一次报文却未携带 X 的份额;服务端随后可能请求 X,连接最后也可能选择 X。每一步都需要自己的字段与时间戳。
重试路径不保留 early data
第一次 ClientHello 若带有 early_data,第二次必须删除。HelloRetryRequest 因而是这次握手没有接受 0-RTT 的明确证据。
客户端可能在收到重试前已经发出早期应用数据。接下来是否重放操作、如何判定幂等、是否已经看到响应,仍由应用负责。TLS 在 1-RTT 路径最终成功,并不会把一个有副作用的请求自动变安全。
监控应把“尝试 early data”“被重试拒绝”“应用决定重发”“最终业务结果”分开。只显示“恢复会话成功”会抹掉对业务最有影响的状态转变。
cookie 只能证明设计赋予它的事实
服务端可以在 HelloRetryRequest 中放入不透明 cookie,并要求客户端在第二次问候中原样返回。cookie 可封装首次问候哈希、已选参数、时间或路由上下文;完整性保护可以证明它来自持有密钥的服务端且未被篡改,但结论受密钥保管、有效期和验证逻辑约束。
DTLS 1.3 给出另一种用途:把 cookie 与客户端表面地址绑定,在发出更大响应前先验证回程可达性。规范讨论密钥轮换、重叠接受窗口和时间戳,说明无状态令牌同样有生命周期。
地址可达性不是身份。返回 cookie 最多表明有效期内有人能接收该地址的流量,不能证明具体个人、设备角色或应用权限。有效 cookie 也不能免除对两次 ClientHello 差异的检查。令牌可以携带状态,却不能扩大这次状态转换的授权范围。
新扩展也必须加入同一段历史
Encrypted Client Hello 提供了一个边界清楚的组合案例。HelloRetryRequest 的 random 使用固定值,ECH 不能像普通 ServerHello 那样把接受信号写在那里,于是另行定义 encrypted_client_hello 扩展,让确认值绑定内部 ClientHello 与修改后的重试记录。
这里的结论不是把本文改写成 ECH 指南,而是说明新功能没有私有的协商宇宙。扩展必须说明哪些字段可变、信号放在哪里、如何加入已有认证历史。基础记录仍然约束重试权。
能经受事故追问的证据
可靠记录要从多数仪表盘所称“连接”之前开始。保存 ClientHello1 的哈希及合规允许的解析字段、完整 HelloRetryRequest、cookie 摘要、ClientHello2 解析字段和最终 ServerHello,同时记录生成决策的实现版本与配置版本。
群组证据至少包括启用列表、声明顺序、首次预发份额、服务端偏好模式、请求群组、回送群组和最终选择。重试证据包括触发原因、差异是否落在允许清单、拒绝时的告警以及是否出现第二次重试。效果证据包括新增往返、两次 ClientHello 大小、分段现象、early-data 结果、完成率和客户端群体。
还要主动保存失败证据:客户端应拒绝未声明群组、已经预发的群组、无实质变化的请求、密码套件漂移、第二次重试及与请求不一致的 ServerHello。BoringSSL 的测试运行器覆盖多种畸形变体,正因为拒绝路径本身就是互操作能力。
成功握手只是故事的终点,不是此前每个决定都正确的证明。真正的证明,是一串受限且可复核的变化。
来源
- https://www.rfc-editor.org/rfc/rfc9846.html
- https://www.rfc-editor.org/rfc/rfc9147.html
- https://www.rfc-editor.org/rfc/rfc9849.html
- https://www.rfc-editor.org/rfc/rfc7919.html
- https://www.rfc-editor.org/rfc/rfc8422.html
- https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
- https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
- https://docs.openssl.org/4.0/man3/SSL_CTX_set1_curves/
- https://docs.openssl.org/4.0/man1/openssl-s_client/
- https://gnutls.org/manual/html_node/Priority-Strings.html
- https://www.gnutls.org/manual/gnutls.html
- https://boringssl.googlesource.com/boringssl/+/refs/heads/main/ssl/test/runner/handshake_server.go
- https://blog.cloudflare.com/rfc-8446-aka-tls-1-3/
- https://heng.lu/running-code-primary-the-patch-needed-to-preserve-the-internet-original-design/
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
