外观
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 候选
- 仅允许 unsigned/negative integer、byte string、UTF-8 text、array、map、
true/false/null中 schema 明确需要的类型。 - 禁止浮点、indefinite-length、未声明 tag、simple value 和共享引用。
- 整数使用最短编码;每字段有数值范围,时间统一为无符号毫秒或秒并在 schema 中固定。
- map key 仅使用小的无符号整数;键按 RFC 8949 确定性规则排序;禁止重复键。
- 文本只用于展示或明确需要的规范字段;禁止依赖隐式 Unicode normalization。需要规范化的字段必须在 schema 中指定 NFC 并用向量覆盖。
- byte string 有固定或最大长度;公钥、签名、nonce、ID 不使用可变文本表示进入签名对象。
- 未知字段:握手/身份/签名对象默认拒绝;允许扩展的对象必须通过显式
extensions容器和版本规则。 - 顶层对象包含
object_type、encoding_version和protocol_version,且 schema 定义其整数键和值。 - 解码器限制总字节、嵌套深度、map/array 项数和单字段长度。
- 签名输入使用域分离前缀和 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,展示名不参与身份签名语义。
参考
- RFC 8949 CBOR: https://www.rfc-editor.org/rfc/rfc8949.html
- Protocol Buffers serialization is not canonical: https://protobuf.dev/programming-guides/serialization-not-canonical/
- RFC 8785 JCS: https://www.rfc-editor.org/rfc/rfc8785.html