摘要

  • 2026 年 9 月 5 日发布的 draft-templeman-scitt-framing-space-00 是一份个人 Informational Internet-Draft。它报告实验结果,不是 RFC、IETF 共识或规范变更。
  • 实验只改变六项二元 CBOR framing 选择,就得到 64 组不同字节与 64 个不同 data-hash;全部样本的 Sig_structure 只有一种,因此同一签名在 64 种编码上都有效。
  • 64 种输入没有一项被解码器拒绝,其中 31 种在“读取后重新编码”时悄悄变成同一规范形式。若系统没有先保留原始字节,它可能再也无法重现自己已经发出的标识符。

设想一条看似正常的入站链。网关收到一份已签名 CBOR,先算哈希,把结果作为登记编号返回;随后解码内容,再把对象交给消息队列与数据库。数据库只保存库函数重新编码后的字节。几小时后,客户端拿编号回来查询,系统从保存对象重算哈希,却得到另一个值。

每个局部步骤都可能显示成功。字节有效,解码成功,签名有效,存储成功。真正丢失的是最早那份“究竟收到了哪些字节”的证据。

Nicholas Templeman 的新草案把这件事做成了可复查测量。I-D 公告明确记录文件于 9 月 5 日可用。草案也主动限定身份:个人提交、Informational、没有规范要求、不提出文字修改。读者应当把它当作一份机制证据,而不是机构裁决。

被签名的是结构,不是所有外框

RFC 9052规定 COSE 的签名与验证以 Sig_structure 为输入。COSE_Sign1 的签名结构包括上下文、受保护属性、外部认证数据与 payload。包住这些内容的全部 CBOR 表示选择并不都进入签名输入。

RFC 8949允许一个数据模型值拥有多种序列化。数组与 map 可以采用定长或不定长;byte string 可以整体输出或分块;在相应 profile 接受时,tag 可以有或没有。这些差异会改变在线路上出现的 octet,却不一定改变解码后的数据项。

实验从一份 165-octet 的 COSE_Sign1 对象 A 出发,只切换六个二元自由度:tag 18 是否存在、外层数组是否定长、未保护 header map 是否定长,以及受保护 header、payload、signature 三段 byte string 是整体还是分块。

六个开关组合成 64 个样本,长度从 164 到 170 octet。所有样本都被 cbor2 6.1.3 接受,也都重建出同一份 109-octet Sig_structure。报告给出的这份签名结构 SHA-256 是 60b4c76b84c456ed0604305075f51f6d050476177ef17be70a13a7b9abfb370f。

与此同时,64 组线路字节产生 64 个 data-hash,没有碰撞。这里没有密码学矛盾:哈希输入不同,输出本来就应该不同;签名输入相同,签名本来就应该继续有效。

64 说明组合问题,不说明问题只到 64

草案没有测试整数宽度或 map key 排序,因此 64 不是全集。它揭示的是治理方式:如果运营规则只逐条禁止已知异常形式,每发现一个自由度,都可能把剩余空间再次成倍扩大。

例如,只禁用无 tag 输入,并不能解决定长与不定长、整体与分块之间的组合。一个“我们已经列出坏格式”的检查表无法证明列表完整。真正能封闭这类问题的是明确字节边界:标识符究竟对哪一段、哪一时刻、哪一份不可变 byte sequence 求值。

草案发布的framing-space 实验记录含有输入、变化、结果与生成脚本;data-hash vector提供起始字节。报告还注明运行环境,并记录另一位参与者用不同平台、不同 reader 且不使用 COSE library 复算了其中一项 framing 轴。

这使机制可以重复验证,却不能推出部署普及率,更不能指称某个现网产品存在漏洞。现实报道的纪律,就是不把一份可复现实验扩大成未经证明的事故统计。

“静默修复”会修掉取证能力

最值得运营者警惕的数据是 31。64 种输入中,有 31 种经过 decode-then-re-encode 后变成对象 A 的规范字节,没有告警,也没有拒绝。

这种行为对普通数据处理可能很方便。对于以“收到时的字节”为标识对象的系统,它却改变了证据。系统如果先解析、后持久化,就可能只留下“本库会怎样输出”,而不是“对方实际发来了什么”。一旦原始请求体被丢弃,公开 data-hash 与留存对象之间的断裂可能不可逆。

危险并不限于查询失败。data-hash 可能是去重键、索引键、receipt 坐标,或者下一份 attestation 的 subject。framing 被改变但签名不失效时,同一签名内容可能被登记到意外位置、绕过一次去重,或在确实持有它的服务中显得不存在。

正确顺序应在 parser 之前建立保全点。保存完整原始 octet;对这份不可变对象计算 wire hash;记录接收时间、来源、字节长度与所用命名字节边界;解析结果另存;任何规范化版本都不得覆盖证据版本。

另一条路是协议选择并强制唯一确定性表示。RFC 8949 的确定性编码章节提供基础,但它不会让所有被通用 CBOR decoder 接受的输入自动变成确定性输入。要求必须写进具体协议,并在入口执行。

现行as-transmitted 绑定草案选择了精确字节边界:不做 canonicalization,并要求引用格式中已经命名的 byte production。该草案早于这次测量,也同样是个人工作。新实验说明这种精度为何重要,不替它制造标准地位。

一次绿色校验不能替四个层次发言

RFC 9943 的 SCITT 架构把 signed statement、transparency service、receipt 与后续 appraisal 分开。当前 SCITT Reference APIs 和 CCF receipt profile工作让 data-hash 成为登记与检索中的现实坐标,因此这里的字节差异会产生运营后果。

但每张回执仍有边界。保存的 raw bytes 说明一个组件收到了什么;有效签名说明指定 signing input 在某个 key 与算法下通过;应用仍须判断 key 与身份是否匹配、身份是否有权行动。登记 receipt 说明服务按其规则接纳了一项记录;它不证明陈述真实、新鲜、适合某个决定。

Heng Lu 所说的现实优先在这里不是立场,而是排错方法。decoder、encoder 与签名库都可以各自正确;结构性错误发生在有人让它们互相代替证据。

运行代码优先则把问题落到真实路径:谁最先拿到请求体?哈希发生在解码之前还是之后?消息队列传 byte string 还是对象?数据库能否按标识符返还原始字节?服务于哪个边界做了签名验证?

技术能力与权威的区分要求再问一步:库函数能接受并重排 CBOR,并不意味着它有权重写客户收到的身份。真正的控制者,是能冻结入口、保存原始证据、指定哈希边界,并让索引、receipt 与后续证明都引用同一份字节的人。

这不是反对 canonicalization,也不是说 wire hash 永远优于语义标识。结论更窄:系统必须先决定自己要标识什么,并保留足够证据重现这个决定。若声明哈希代表“传输时的 statement”,那么第一次解析已经不该拥有改写过去的权力。

Sources