摘要

  • 传输连接关闭只能说明字节流停了,不能证明对端想发送的 TLS 记录已经全部到达。close_notify 在受保护序列里加入了有方向的终止声明。
  • TLS 1.3 不再强迫双向立即一起关闭。警报只结束发送者的写方向;Content-Length、分块终止符与 END_STREAM 仍负责证明应用消息完整。

真实的前缀,不等于完整的答案

一段响应突然停止,socket 返回 EOF。此前每条 TLS 记录都通过认证。接收方因此知道已有内容没有被篡改,也知道传输层不会再交出字节,却无法据此确认对端原本就只打算发送这么多。

应用协议有时能提供第二份证据。Content-Length 写着 10,000,却只收到 9,996,缺口很清楚。分块编码没有最后的零长度 chunk,同样没有结束。若响应只能靠连接关闭分界,正常完成与丢失尾部在外观上完全相同。

TLS 1.0 把这种风险称为截断攻击。客户端与服务器必须共同知道连接正在结束。close_notify 因而成为 TLS 内部的声明:发送者不会在该连接上再发消息;收到警报后的数据必须忽略。

这项声明没有把 TLS 变成文档格式。它只回答本层能够回答的问题——这个发送方向的受保护记录序列结束了。响应、文件或交易是否完整,仍需上层语法判断。

TLS 1.0 曾把关闭失败带到下一次连接

TLS 1.0 规定,连接若没有正确交换关闭警报,会话就不能恢复。未被证明的结束会让下一次连接失去复用既有密码状态的捷径。

运行实践没有守住这项惩罚。TLS 1.2 记载,TLS 1.1 为顺应普遍实现,已经取消“关闭不完整便不得恢复”的自动后果。正常关闭写侧之前发送 close_notify 的义务仍然存在。

这次改动把语义与政策拆开。缺少警报仍表示终止证据不足,却不必单独决定整个会话能否在未来恢复。

旧式对称关闭会切掉反向数据

TLS 1.3 之前,一方收到 close_notify 后必须立刻回送自己的警报、关闭连接并丢弃待发送内容;发起者也不必等回信就关闭读侧。

但应用并不总是双向同时完成。客户端可能已经发完请求,却还在等待服务器最后的响应。若服务器收到警报便清空写队列,防截断机制反而截断了返回方向。

TLS 1.3 将 close_notify 改写为一个方向的有序关闭。发出警报只关闭自己的写侧,不影响读侧;对端不必丢弃反向待写数据,也不必立即回应。两个方向各自在真正完成时结束。

规范也精确保留了未知范围:若先收到传输层关闭而没有 close_notify,接收方无法知道对端发送的数据是否全到。已认证记录不会因此倒退成不可信;不确定的是最后一条之后是否还存在缺失记录。

HTTP 提供 TLS 看不见的边界

HTTP over TLS 很早就区分了“已收到数据的安全性”与“后续数据是否被截断”。TLS 不理解 HTTP 请求和响应边界,只能把判断交给 HTTP framing。

今天的 HTTP/1.1 仍这样处理。Content-Length 给出可核对的字节数;chunked 编码以零长度 chunk 收尾。少了数字要求的内容或终止 chunk,消息就是不完整。若两者都没有,响应由关闭分界;在 TLS 上只有收到有效关闭警报,才能把它认作完整。

因此,不完整关闭没有统一答案。若一条消息已收满声明长度,可以承认该消息完成,却不能把结论扩张到之后可能存在的消息。若关闭本身是唯一边界,接受前缀会把未知伪装成结果。

HTTP/2 用 END_STREAM 标记某一条流的终点,其他流仍可共享同一连接继续工作。它是应用流边界,不是 TLS 连接关闭;一条流可以先证明自己结束,随后共享通道再独立关闭。

有限声明才可靠

close_notify 不认证人,不授权交易,不证明应用已读取内容,也不保证每个上层消息完整。它只声明:发送者在这个方向不会再发送 TLS 消息。

EOF 是传输事件,关闭警报是受保护的意图,长度、终止 chunk 与 END_STREAM 是应用结构。把三者都叫“关闭”,会抹掉各自真正拥有的证据。

来源与边界

本文依据 RFC 2246、RFC 2818、RFC 5246、RFC 8446、RFC 9112 与 RFC 9113。它们规定机制与历史变化,不测量当前部署、库默认值,也不能从一次缺失警报推断原因。