摘要
- 客户端发给代理的请求,与代理补入证书后发给后端的请求,并不具有同样的字段大小。前一段能接收,不能证明后一段还留有余量。
- 证书的 DER 二进制大小、HTTP 解压后的文本字段大小、线上压缩流量及压缩表容量,分别约束不同对象,不能互相代替。
- 代理需要为新增信息预留空间,但不能靠悄悄丢弃既定认证输入来制造“请求已经装下”的假象。证书链和会话恢复下的字段存在性也必须保留明确语义。
请求已经发出,体积却还会增长
设想一个场景:客户端与前置代理完成了双向认证的 TLS 连接,然后发送一份代理愿意接收的 HTTP 请求。代理在转发前,按照与应用约定的方式补入客户端证书。后端此时面对的,已经不是客户端原样发送的那组字段,而是一份增加了证书信息的请求。
如果这份请求超过后端愿意处理的大小,外层 TLS 握手并不需要失败。问题发生在信息换了一种表示、又进入另一套容量约束的时候。这个场景只是解释机制的假设,没有实际执行测试,也不是从某个厂商的事故中抽取的案例。它的作用是指出增量产生的位置,而不是宣称某种部署必然出错。
RFC9440 于 2023 年 7 月发布,名称是 Client-Cert HTTP Header Field,属于 IETF 的 Informational RFC,并非 Internet Standards Track 规范。它试图把已有的证书转发实践规定得更可预测,减少独立组件之间的互操作摩擦。可预测的字段表示,不是对任何真实系统容量、默认设置或性能收益的认证。
规范讨论的是终止 TLS 的反向代理。代理把原始客户端在双向认证连接中提供的证书信息送给源站应用。中间即使还有其他连接和代理,这两个字段描述的仍然是最初客户端的证书,不会自动变成最后一跳使用的证书。应用可以据此处理自身逻辑,但客户端的原始连接并没有因此物理延伸到应用。
可信路径仍然是必要条件。然而,这篇文章关心的不是再次证明代理边界为何重要,而是一个可信路径同样会遇到的容量问题:谁为代理追加的字节留了位置?按哪种表示计量?当证书链、路由或字段存在策略变化时,谁知道原来的估计已经不够?这些问题不能靠一句“TLS 已成功”回答。
同样叫“解码”,得到的东西并不相同
RFC9440 不是把证书的二进制对象直接塞入 HTTP。它把 DER 编码的 X.509 证书表示为 Structured Fields 的 Byte Sequence:使用 base64 文本,两侧以冒号分隔,里面不夹带换行、空格或 base64 字母表之外的字符。因此,证书对象占多少二进制字节,与它在字段值里占多少文本字节,不是同一个数字。
编码转换会增加文本长度。字段名、分隔符、列表语法以及请求中的其他字段,也需要各自的位置。这种关系可以依据序列化方式计算,但计算出的表示长度,不是所有接收端都必须接受的统一上限。数学上的长度关系与某个应用的容量决策,分属不同层次。
接下来还有一处容易混淆的转换。HTTP 的字段压缩解码器从压缩块恢复字段名和字段值。恢复出的证书字段值,在这个阶段仍然是 base64 文本。随后把 base64 解码成 DER,才得到证书的二进制对象。不能因为 HTTP 解压完成,就把字段大小按更小的二进制证书重新计量。
这也解释了为什么线上流量减少,并不必然缓解字段大小限制。压缩表中的引用可能只需要少量传输字节,但接收端恢复出的字段值并没有缩短。反过来,代理可以把字段作为字面值发送而不留在动态表里,字段仍然属于后端需要处理的请求。网络、状态和消息容量都有成本,但没有一种成本能够自动替另一种清账。
实际协作中,证书团队可能说的是 DER 大小,代理团队说的是编码后的头部,传输团队看的是压缩流量,应用团队限制的是整个字段段。四个数字都可能算对,却没有一个共同的计量对象。容量讨论首先需要消除这种错位,而不是急于选择一个看起来足够大的上限。
解析器能处理,不等于服务器必须收下
Structured Fields 里有一条容易被拿错位置的要求。RFC8941 及其后继 RFC9651 都要求 Byte Sequence 解析器支持二进制解码后至少 16,384 个八位字节的数据。这里规定的是数据类型的解析能力,不是要求每台 HTTP 服务器都接纳包含这样一个值的完整请求。
完整请求不仅有这个对象。base64 的文本表示还没有消失,字段名、证书链、原有的请求字段和列表分隔也会占用空间。把解析器支持的二进制长度直接抄成 HTTP 字段上限,既混淆了单位,也把单个数据类型的能力误认为整个消息的容量承诺。
因此,一个实现可以具备规范要求的解析能力,同时对某条请求采用更窄的运行容量。这两件事并不矛盾。反过来,允许很大的字段段,也不会证明其中的对象是合法证书,更不会证明应用应该授权某个操作。容量与对象验证、认证和授权都需要各自的依据。
RFC9110 并没有给所有 HTTP 字段行、字段值或整个字段段规定一个通用最大值。接收端会根据自身情况选择限制;收到超过其愿意处理大小的请求字段时,服务器须给出适当的 4xx 响应。规范特别警告,直接忽略这些字段会增加请求走私攻击的风险。这不是让实现把不合适的认证输入默默删掉,再把缩小后的请求当成等价成功。
两个协议的参数名称不能互换
HTTP/2 在 RFC9113 中使用 SETTINGS_MAX_HEADER_LIST_SIZE。这个参数具有建议性质,以字段名和字段值的未压缩八位字节长度计量,并为每条字段行计入 32 个八位字节的开销。接收端可以针对某一请求执行比公布值更低的限制。初始设置没有上限,不代表所有应用、代理和具体路由都拥有无限处理能力。
HTTP/3 的名称则是 SETTINGS_MAX_FIELD_SECTION_SIZE。RFC9114 同样根据未压缩的字段名和值计量,每个字段另加 32 字节。收到参数的对端应避免发送超过该值的字段段。但规范也指出,每个处理消息的实现都会独立适用自己的限制。低于一路上某个接收端的公告值,不能保证其他实现也接受。
这两个名字不能被简化成同一个可随意替换的标签。它们都不是把压缩后的帧长度拿来作为字段段大小。传输中一个很小的压缩块,可能还原出接收端不愿处理的文本字段;符合一个传输设置的请求,也可能碰到应用层更低的限制。
判断容量时,真正有用的问题是:哪个接收端公布了这个数?数的是哪种表示?它适用于哪份请求?公告并非给所有未来路由作出的不可撤回承诺。把“看到一个上限”直接理解为“整条路径已经协调好”,会跳过 RFC 明确保留的本地决策。
余量必须留在追加之前
代理的特殊之处,是它既接收请求,又改变请求。如果它把后端公布的最大容量原样转告客户端,客户端可能在证书追加之前就把位置占完。代理此时收到了一份可接收的请求,却准备送出另一份不可接收的请求。问题不在于客户一定做错了什么,而在于协议转换的增量没有被算进边界之间的约定。
RFC9440 建议,能公布最大可接受头部大小的中间方,应把自己的新增字段计算在内,向上游公布一个足够小、能为追加数据留下空间的值。这个建议没有指定所有地方都要减去的固定常数,也不是新发明的一条普遍强制设置。要留多少,取决于实际追加的信息和实际接收者。
双边约定至少应说明追加哪些字段、按什么单位计算,以及什么变化会使原来的估计失效。验证链不同、请求中其他字段增加、序列化方式变化,或者下一跳变成另一个应用,都可能改变预算关系。这些都是未执行的设计问题,不是本文观察到的某次迁移。
证书信息也不是落入一个空容器。客户端的原有字段与代理其他新增字段共享同一个接收容量。单个证书能解析、一次示范请求能装下,分别只能说明它们所覆盖的情况,不能替所有路径、所有未来链和所有字段组合提供保证。运行约定需要明确复核触发条件,而不是在对象已经变化后继续保留同一个数字。
“可选证书链”不等于“可以悄悄丢掉”
Client-Cert 只用于请求,承载终端实体证书,不能包含列表,也不能在请求中出现多次。Client-Cert-Chain 是可选的 Byte Sequence 列表,遵循 TLS 的证书顺序,不重复前一个字段已经承载的终端实体证书;没有 Client-Cert 时,它也不能独立出现。
可选性让参与方选择运行安排,并不意味着一个已经依赖证书链的应用,在证书链消失后仍然收到了同样的输入。减少字节和保留应用语义,是两项必须分别说明的结果。不能因为请求终于低于限制,就自动判定新的运行方式与旧方式等价。
规范允许省略根证书,但条件是已知目标源站拥有被省略的信任锚。需要省空间并不能满足这个条件,也不能据此随意移除应用原本需要的中间证书。条件指向具体接收者,而不是一个脱离部署的“链总能缩短”规则。
证书链列表可以分成多个字段实例,RFC9440 指出这种形式可能有利于压缩。然而,字段段的计量也会为新增字段行计算名字和每行开销。某种表示有利于重用,不一定有利于总字段大小。选择怎样排列字段之前,必须知道最终由哪一种预算决定接收。
压缩改善的是哪一笔成本
RFC9440 的压缩讨论带有明确场景条件:代理与源站之间使用字段压缩,一条连接又复用多个客户端的请求。较大、经常变化的证书字段值,可能显著降低压缩效率。这是规范提出的运行考虑,不是本文对某个系统测得的结果,也不能扩大成所有证书转发都会出现同样问题。
规范列出两种可能的取舍。源站可以考虑扩大动态表;代理如果判断源站的表不够大,可能发现一直把证书字段作为字面值发送、而不把它加入表中,更适合其情况。前者增加可保留状态的空间,后者可能避免保留不容易再次利用的值。规范没有宣布其中一种是对所有工作负载都最好的设置。
HPACK 的 RFC7541 另有表项计量方式:以 Huffman 编码之前的字段名和字段值长度,加上 32 个八位字节计算表项大小。所用协议限定表的最大可用容量,编码端也可以选择使用更小容量。扩大这个容量,改变的是压缩状态的预算,不会自动提高应用愿意接收的字段段大小。
QPACK 在 RFC9204 中通过解码端的 SETTINGS_QPACK_MAX_TABLE_CAPACITY 约束动态表的最大容量。最大容量为零时,不能插入表项。这个参数与 HTTP/3 的字段段大小上限并不相同;禁止动态表插入也不是一条“所有字段都无法压缩”的结论。本文不假定 QPACK 一定阻塞证书请求,也不规定各部署统一采用某个值。
运行选择取决于信息的重复方式。稳定重用的证书值和不断出现的不同证书值,不提供相同的重用机会。传输负责人可以比较保留状态与重新传输的成本,但这种比较不能替接收端决定其处理容量。即使这些组件都由一个团队运营,也需要保留不同预算的含义。
请求缩小了,认证输入可能也变了
RFC6585 定义的 431,表示服务器不愿处理过大的请求字段集合或某个单独过大的字段。规范允许缩小字段后重新发送;如果由单一字段造成,响应表示应指出是哪一个。但使用 431 并非强制,规范也允许受攻击的服务器选择丢弃连接或采取其他措施。因此,不能把证书转发中的所有容量问题都预言为一个必然可见的 431 响应。
如果追加字段发生在代理内部,客户端未必能够直接删减造成超限的数据。重复同样的请求不会创造余量。为了让请求装下而删掉证书,则可能改变应用的认证输入。它可以得到更短的消息,却不一定得到同样含义的消息,更不一定得到本应相同的访问决策。
字段缺失本身可以是正当安排。例如,应用可能使用另一种认证方式。这里不是要求所有没有证书字段的请求都被拒绝,而是要求参与方明确选择安排。容量优化不应替他们悄悄决定“以后不用这些信息”。对缺失输入怎样处理,属于应用已经说明的策略,不能交给压缩编码器或超限补救自行发明。
会话恢复还带来另一种存在性变化。有些 TLS 实现恢复会话时不能保留客户端证书信息。RFC9440 建议,不能提供一致字段值的实现,要么对证书连接禁用恢复,要么从一开始就不发送以后可能拿不到的字段。这是有条件的设计取舍,不代表所有恢复都会丢失证书,也不是本文要求修改任何现有服务。
缓存同样不能混为一谈。当 Client-Cert 决定响应内容时,RFC9440 要求响应不可缓存,或者通过 Vary: Client-Cert 把重用限定在相同字段值的后续请求。终止代理遇到 Vary 包含证书字段时,应把其值变为 *,避免用户代理缓存。431 响应则不得缓存。这些是响应重用的规则,不是 HPACK 或 QPACK 动态表的设置问题。
可信路径、字段存在性、消息容量和响应选择仍然各有义务。代理必须清理不可信输入中的这些字段,后端只能从规定的可信路径接受它们,内部通信也需要防止不应参与的观察或修改。装得下不能证明这些条件成立。容量约定是对部署安排的补充,不是它的安全证明。
来源与结论边界
本文依据公开规范和卢恒的协调原则,不依据生产遥测。所有路由、证书链变化、恢复和重试场景均是假设,未执行测试。本文不宣称任何厂商的默认限制、安全通用大小、事故、量化收益或已通过的负向测试;编辑建议与规范要求分别说明。
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
