摘要

  • TLS 1.3 允许持有恢复票据的客户端把应用数据和 ClientHello 一起发出,省去一到两个网络往返。但 0-RTT 数据不具备跨连接不可重放的保证。
  • RFC 8470 没让 TLS 猜测业务含义,而是让中间节点以 Early-Data: 1 保存来路,让源站按资源决定是否承担重复执行风险,并以 425 拒绝某一个请求。
  • 客户端收到 425 后可以自动重试,但重试不得再次使用早期数据。方法、目标与正文都可以不变;改变的是请求取得行动资格的握手时刻。HTTP/3 在 QUIC 上继续采用这套边界。

速度来自先于服务器发言

普通 TLS 请求要等。客户端和服务器交换握手消息,生成包含双方新鲜输入的密钥,随后才发送 HTTP 数据。跨洲链路上的一两个往返很贵,于是 TLS 1.3 给老客户提供了捷径。

服务器在一次会话结束前可以交给客户端恢复材料。下次连接时,客户端用预共享密钥证明自己掌握那段历史,并把应用数据直接放在 ClientHello 旁边。HTTP 请求不必等待新的 ServerHello,这就是 0-RTT。

捷径没有取消握手,只是把有用工作移到了新证据之前。RFC 8446 明确说明其代价:早期数据虽被加密,却没有前向保密,也不保证在不同连接之间不可重放。它发出时还没有新的服务器随机数,因此无法像普通 1-RTT 数据那样绑定到这一条新连接。

边界必须说准。攻击者不能任意改写受认证的字节,也不能把早期数据变成普通 1-RTT 数据,更不能在同一连接里让相同记录凭空出现两次。风险是把整份早期飞行复制到仍接受同一恢复状态的另一条连接。

重放与重试是两条路径

重试是客户端知道的恢复动作:响应丢了、连接断了,或者用户明确再试。重放则可能在客户端毫不知情时发生。把两者混为一谈,会看不见分布式部署最危险的组合。

假设两个区域没有完全共享重放记录。A 接受复制来的早期请求并执行;B 拒绝早期数据,却完成正常握手。攻击者若遮住 A 的响应,客户端会在 B 上合理地重发。一次效果来自攻击者重放,另一次来自客户端恢复,同一个业务动作最终发生两次。

TLS 1.3 提供单次票据、ClientHello 记录、有效时间窗与票据权威区域等办法。它们能缩小直接重放的空间,却依赖机器、地区和重启之间的可靠一致性。更根本的问题是,TLS 只看得到记录,不知道一次请求是在取静态样式表,还是兑换只能使用一次的权利。

方法名不是资源的全部事实

HTTP 已有“安全方法”和“幂等方法”的语义。客户端没有更好信息时,可以据此限制哪些请求进入 0-RTT。但 RFC 8470 没把 GET 当成普遍安全证书。

一个名义安全的读取可能消耗签名 URL、触发按次计费、启动昂贵计算或留下不可忽略的审计记录。相反,一个带有明确事务键的写操作可能知道如何识别重复。是否容忍重放,是资源的属性,不是方法字符串独自拥有的真理。

客户端知道自己想提前什么;TLS 知道字节处在哪个阶段;网关知道往哪里转;只有源站通常知道执行两次会损失什么。TLS 因此要求应用协议先定义 0-RTT 配置,也不允许 TLS 库自行开启或自动重发被拒绝的早期数据。

三种拒绝保留三种粒度

服务器可以在 TLS 层拒绝整批早期数据。做法简单,但 TLS 无法在同一早期飞行中按 HTTP 请求挑选,一批请求都会失去时延优势。

服务器也可以接收并延后处理。它读取足以路由的信息,却不产生外部效果,等握手完成后再放行。在多路复用连接上,不同请求可以拥有不同的等待策略。

第三种办法是对单个请求返回 425 Too Early。源站不愿在可重放状态下处理它,客户端负责把它移到握手之后。这使 0-RTT 不必因为少数敏感资源而全站关闭,也不必为了速度把所有资源都推上同一风险曲线。

一位风险必须穿过每个代理

懂业务的源站常常不终止浏览器的 TLS。CDN、反向代理和网关会让第一跳的早期状态在抵达应用前消失。若没有另一个载体,源站看到的只会是一条普通已建立连接。

Early-Data 请求字段只允许值 1。中间节点在面向客户端的握手完成前转发请求,必须在缺失时加上它;已经收到它,就不得删除。如果请求可能已经被自身或其他实例转发,也要保留标记。

这个字段不是“请加速”,而是来路证据:上游某处可能在可重放窗口内接收或转发了这份请求。下游等待自己的握手,不能洗掉上游已经产生的不确定性。带标记的请求若不能安全执行,源站仍应返回 425。

多个或不合规的字段实例都按同一风险位处理,避免语法歧义变成清除历史的办法。它不进入响应、不放在 trailer,也不能被 Connection 声明为逐跳字段。

425 改变的是请求的时间

用户代理以早期数据发送请求并收到 425 后,应当自动重试。关键条件是:这次重试不得仍是早期数据。客户端等到握手完成,再用普通应用数据发送。

URI、方法、正文和业务意图都可以原封不动。425 不是重定向,也不是说证书无效或源站不健康。它只拒绝“这份请求与这段可重放历史”的组合。

它不同于 421:421 怀疑连接对目标源站的权威,425 可以来自正确连接上的正确源站。它不同于 429 的配额,也不同于 503 的不可用,更不同于 100 Continue 对是否发送正文的协调。425 是针对行动时机的最终响应,默认不可缓存。

没有早期数据或 Early-Data: 1 证据时,服务器不应随意发送 425,因为它不能假定普通客户端理解必须退出早期数据的特殊重试规则。IANA 状态码登记表 也把 425 的狭义名称固定为 Too Early。

中间节点只能修正自己刚制造的早期

任何中间节点都可以转发 425。若收到的请求本来就带标记,它必须转发,因为不确定性发生在更上游,本节点换一条下游连接也不能清除。

只有一种局部不对称:当前中间节点就是最先以早期数据收到请求的一方,且上游没有既有标记。它可以等面向客户端的握手完成,再替客户端向源站重试。此时它确实知道风险从哪里开始,也确实把同一请求移出了那个时刻。

网关若不知道源站能否理解字段并正确生成 425,就不得把早期请求直接交给源站。它应当等待或自己拒绝。边缘设备不能替应用承担应用无法看见的重复后果。

行动开始点比最后一个包更重要

请求可能跨越握手:头部在早期数据里,正文后来才到。QUIC 的调度甚至可能在握手完成后才把一个早期 stream 交给应用。到达时间并不能改变它的来源。

真正的边界是实例何时开始产生效果。解析头部、选择后端可能无害;写数据库、调用支付、消耗令牌、修改缓存或向外部队列投递则不同。所有可能接收同一重放的实例必须同意“不得过早行动”,即使一个拒绝 TLS、一个等待、另一个返回 425。

刚重启的实例缺少启动前累积的重放历史。在记录窗口重叠期间,它应拒绝 0-RTT。局部失忆不能成为扩大请求权力的理由。

HTTP/3 又增加了一种需要证明的记忆

RFC 9114 将 RFC 8470 的重放缓解措施继续用于 QUIC 0-RTT。客户端提前发送时还看不到新连接的 SETTINGS,只能依赖上一会话记住的值。

服务器若无法确认旧设置与当前设置兼容,就必须拒绝 0-RTT。一旦接受,也不能随后宣布更低限制,使已经发出的数据突然违规。

SETTINGS 兼容与资源重放容忍不是一个检查。前者保护 HTTP/3 协议状态,后者保护业务效果。0-RTT 让两类原本由等待隐含提供的证据都必须显式出现。

来源与证据边界

封闭来源集为 TLS 1.3:RFC 8446、HTTP 早期数据:RFC 8470、HTTP/3:RFC 9114 与当前 IANA HTTP 状态码登记表。它们证明协议设计、重放边界和规定行为,不证明当前部署比例、所有客户端都支持、某起具体攻击、某家服务的量化节省或业务只执行一次。