Skip to content

ADR-004 规范化序列化格式

  • 状态:提议,待总控决策
  • 日期:2026-09-23
  • 决策范围:协议签名 transcript、AEAD AAD、实例配置、身份/预密钥包和测试向量的确定性字节表示
  • 关联:ADR-001、ADR-003、OpenAPI 契约

背景

v1.1 要求所有协议对象具有 canonical encoding 和跨语言测试向量,并指出二进制对象可使用 Base64URL 或 CBOR,生产签名不能依赖不确定的 JSON 浮点或 map 顺序。

确定性不等于安全规范化。编码选择还必须定义:字段集合、类型、顺序、整数宽度、重复键、未知字段、Unicode、浮点、标签、非最短编码、嵌套和资源上限。

已核对事实

  • Protocol Buffers 官方文档明确说明 protobuf serialization is not canonical;deterministic serialization 不能作为跨语言、跨版本长期签名字节的通用保证。
  • RFC 8949 第 4.2 节定义 CBOR deterministic encoding considerations,可作为严格 Profile 的基础。
  • RFC 8785 定义 JSON Canonicalization Scheme,但依赖 JSON/I-JSON、UTF-16 属性排序和 ECMAScript 数字序列化规则,跨 Swift/Go/Rust 实现需额外验证。

约束

  • 同一逻辑安全对象在所有支持实现中必须产生完全相同字节。
  • 解码后重新编码必须得到同一 canonical 字节;非规范但可解析的安全对象默认拒绝。
  • 签名/transcript 不直接覆盖 URI 文本、JSON 展示、protobuf 未知字段集合或数据库行。
  • 编码版本与协议版本明确绑定;未知编码版本失败关闭。
  • 浮点数不进入安全关键对象;时间和大小使用有界整数与明确单位。

选项

A Deterministic CBOR 严格 Profile

优点:二进制紧凑;RFC 8949 有确定性编码规则;适合 byte string、整数和有界 map/array。

风险:库可能默认接受重复键、非最短整数、任意 tag、浮点或非规范顺序;“支持 CBOR”不等于支持同一严格 Profile。

B Protobuf deterministic 模式

优点:Swift/Go/Rust 类型生成成熟,schema 演进工具丰富。

风险:官方不保证通用 canonical;map、未知字段、跨语言/构建稳定性不能满足长期签名对象要求。

可作为 API/内部类型候选,但不建议直接定义签名字节。

C RFC 8785 JCS

优点:人类可查看、调试方便、有公开规范。

风险:字符串/数字约束复杂、体积大、二进制需 Base64URL;不同语言实现与 Unicode/数值边界需要大量差分测试。

建议

为安全关键协议对象选择 A:RFC 8949 基础上的 VMax Deterministic CBOR Profile v1。OpenAPI JSON 和生成类型可保留为传输/应用层表示,但进入签名、KDF transcript 或 AEAD AAD 前必须构造独立的规范对象并编码为 CBOR。

此建议待总控决策;在跨 Swift/Go/Rust 试验和安全审查通过前不标记 Accepted。

VMax Deterministic CBOR Profile v1 候选

  1. 仅允许 unsigned/negative integer、byte string、UTF-8 text、array、map、true/false/null 中 schema 明确需要的类型。
  2. 禁止浮点、indefinite-length、未声明 tag、simple value 和共享引用。
  3. 整数使用最短编码;每字段有数值范围,时间统一为无符号毫秒或秒并在 schema 中固定。
  4. map key 仅使用小的无符号整数;键按 RFC 8949 确定性规则排序;禁止重复键。
  5. 文本只用于展示或明确需要的规范字段;禁止依赖隐式 Unicode normalization。需要规范化的字段必须在 schema 中指定 NFC 并用向量覆盖。
  6. byte string 有固定或最大长度;公钥、签名、nonce、ID 不使用可变文本表示进入签名对象。
  7. 未知字段:握手/身份/签名对象默认拒绝;允许扩展的对象必须通过显式 extensions 容器和版本规则。
  8. 顶层对象包含 object_typeencoding_versionprotocol_version,且 schema 定义其整数键和值。
  9. 解码器限制总字节、嵌套深度、map/array 项数和单字段长度。
  10. 签名输入使用域分离前缀和 canonical bytes,例如 len(label) || label || cbor;精确格式由协议规范冻结。

Schema 管理

  • 每个安全对象有表格化 schema:整数键、名称、类型、长度/范围、必需性、未知策略和安全用途。
  • 已发布键号不复用;字段语义变化需要新字段或新对象版本。
  • 可选字段的“缺失”与 null 不得混同,除非 schema 明确等价。
  • 服务端 JSON 包装中的 Base64URL 必须无填充/字母表规则一致,但签名覆盖解码后的 canonical 对象,不覆盖外层文本。
  • OpenAPI 生成模型不能自动成为协议模型;二者通过显式转换和验证器隔离。

测试向量

每个对象至少包含:

  • 正常最小/最大值、空与可选字段、所有已知版本;
  • 精确十六进制 canonical bytes、签名/transcript/AAD 结果;
  • map 顺序变化、重复键、非最短整数、indefinite-length、未知 tag、错误类型、超长/深嵌套;
  • Unicode 组合/分解、无效 UTF-8、Base64URL 变体(仅外层);
  • Swift、Go、Rust 三方 encode/decode/re-encode 字节一致。

向量更新必须经过审查。实现不能为了让新代码通过而静默重写既有黄金向量。

迁移

  • encoding_version 不支持时拒绝,不尝试“宽松解码再按新格式解释”。
  • 协议套件固定编码版本;升级建立新会话/新对象,不在已签名对象中混用。
  • 数据库存储可保留原始 canonical bytes 以审计/重放验证,但日志不得输出完整密文或敏感对象。

待总控决策

  • 接受 CBOR Profile、选择 JCS,或要求其他候选。
  • 选定 Swift/Go/Rust CBOR 库的精确版本、许可证和严格模式能力。
  • map 使用整数键还是固定 array;推荐整数键 map 便于可选字段,但必须严格拒绝重复/未知。
  • Unicode 是否完全排除在安全关键身份字段之外;推荐 ID/密钥使用 bytes,展示名不参与身份签名语义。

参考

系统初步设计 · 待决事项不代表批准 · 安全方案尚未完成独立审计