Files
soft_quay/docs/tasks/T-614.md

75 lines
8.1 KiB
Markdown

---
id: T-614
title: 冻结 Catalog 规范化与签名跨实现测试向量
phase: 1
deps: [T-613]
status: DONE
created: 2026-07-18
issue: null
context_ref: 9e5f3f4840b03dc90342a9bde923c39d6178b029
claim_branch: null
work_branch: agent/codex/T-614
write_paths:
- docs/tasks/T-614.md
- core/catalog/
- testdata/catalog/
- testdata/README.md
- docs/api.md
- docs/04-architecture.md
- docs/05-coding-rules.md
- docs/review/phase1-security-review.md
- docs/00-ai-start-here.md
- docs/06-tasks.md
- docs/current-state.md
---
## 问题 / 背景
T-101 的 Catalog verifier 已能拒绝重复字段、尾随 JSON 和非整数数字,但它的合法签名测试由同一个客户端 `parseRestrictedJSON` / `canonicalJSON` 在测试期生成。这只能证明客户端实现自洽,不能发现发布端 `softbox-catalog` 与客户端对 Unicode、数字或 Base64 文本规则理解不同的问题。Phase 1 审核定稿的下一项要求在协议冻结前建立独立、固定的 canonicalization/签名测试向量。
现有实现也存在两处未冻结的歧义:Go JSON decoder 会把孤立 `\uD800` / `\uDC00` 替换为 U+FFFD,而 `base64.StdEncoding.Strict()` 仍接受 CR/LF。若不在解析和签名域边界显式拒绝,不同发布实现可能对相同文本产生不同签名字节或接受非唯一签名表示。`-0` 也必须明确处理,不能作为与 `0` 语义相同而字节不同的受限整数形式留在协议中。
本仓库不包含 `softbox-catalog` 发布端或生产私钥。因此本任务交付可由独立发布端消费的版本化 testdata corpus、公开测试公钥、固定 canonical bytes 与固定 Ed25519 签名;不把客户端运行时 canonicalizer 当成向量的生成器,也不宣称已经替外部发布端执行了集成测试。
## 方案
1. 在 `docs/api.md`、架构和编码规则冻结 Catalog 签名域的精确边界:
- JSON 字符串必须形成合法 Unicode scalar sequence;`\u` 高代理项必须紧跟低代理项,孤立低代理项或不完整/不匹配 pair 一律拒绝,不得替换为 U+FFFD。
- 数字只接受 `0`、正整数或负的非零整数;拒绝 `-0`、小数、指数、前导零和超出 JSON token 的其他表示。大整数按原始十进制 token 写入 canonical bytes,不转浮点。
- 顶层及 package 签名只接受标准 padded Base64 的唯一文本形式:解码后重新编码必须与输入逐字节相等,故 CR/LF、其他空白、缺/多 padding 一律拒绝。
2. 在 `core/catalog` 以共享的受限 JSON/签名辅助路径落实上述规则,不引入第三方 canonicalization、JSON 或 crypto 依赖。保留“只删除顶层 `signature`,嵌套 package `signature` 仍属于被签名 payload”的已有语义。
3. 在 `testdata/catalog/` 新建版本化静态 vector corpus 和说明,每个 vector 固定保存原始 document、预期 canonical signing bytes、测试公钥和 Ed25519 signature 或确定的拒绝分类。有效向量至少覆盖 Unicode 键排序、JSON 转义、`<>&`、U+2028/U+2029、合法 surrogate pair、`-0` 拒绝、大整数、嵌套 signature、标准 padding 和“同一语义的不同 JSON 表示得到相同 signing bytes”;拒绝向量至少覆盖孤立 high/low surrogate、非成对 surrogate、Base64 CR/LF/其他空白和 padding 异常。
4. 测试只读取 corpus 的静态 `signed_payload` / `signature` 作为预期值:先断言客户端输出的 canonical bytes 完全相等,再用 corpus 公钥验签。不得调用 `signCatalogPayload`、`canonicalJSON` 或测试期私钥去生成合法向量的期望值。现有 unit tests 可继续用于局部行为,但 corpus 必须是跨实现契约回归门。
5. 同步审核记录、路线图和当前状态。明确 T-614 关闭的是客户端协议契约与可交付的独立输入/输出向量;外部 `softbox-catalog` 必须在其仓库消费同一 corpus 并由发布流程证明通过,该外部动作不在本任务内。
## 验收要点
- `docs/api.md` 对 Unicode surrogate、整数 `-0`、大整数写法、Base64 padding/空白和仅顶层 signature 删除行为没有歧义;架构、编码规则与 corpus README 一致。
- `testdata/catalog/` 存在可机器读取、版本化的固定 corpus,只含虚构公开测试公钥与固定数据,不含生产私钥、真实 URL 或真实签名。有效 vector 的 expected signing bytes 和 Ed25519 signature 均为静态值;不同语义相同的 document 映射到同一静态 bytes/signature。
- corpus 覆盖 Unicode 键排序、字符串转义、`<>&`、U+2028/U+2029、合法 surrogate pair、非法 surrogate、`-0`、大整数、嵌套 signature、Base64 padding、CR/LF/其他空白及同语义不同 JSON 表示。
- Verifier 与 Parser 对孤立/不匹配 surrogate、`-0`、非唯一或非法 signature Base64 fail closed;嵌套 package signature 未被误删;合法向量在不重新签名的情况下通过。
- corpus 测试的 expected canonical bytes/signature 不由当前 canonicalizer 或运行时私钥生成;测试通过静态 public key 与固定向量验证,从而能发现客户端与发布端的字节级漂移。
- `GOWORK=off go -C core vet ./catalog`、`GOWORK=off go -C core test -count=10 ./catalog`、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py`、`python scripts/validate_harness_governance.py` 与提交前差异检查通过。
## 边界(不改什么)
- 不实现或伪造外部 `softbox-catalog` 发布器、生产密钥管理、密钥 ID/轮换、package 独立签名域、HTTPS 请求、Catalog UI 或 T-302 安装整合。
- 不更改 Catalog manifest/app/package Schema、字段、channel/filter 语义、缓存策略、下载队列、ZIP 安装、Gio 或 Go/Gio 工具链。
- 不引入第三方 JSON canonicalization/crypto 库,不为了兼容旧的非规范签名文本而放宽拒绝规则。
- 不把 corpus 的客户端测试表述为外部发布端已消费或全链路发布证明;外部仓库接入和 CI 证据需另行协调。
- 不修改、提交或删除用户的 `soft_quay.code-workspace`,不启动子 Agent,不提前落成或领取 T-302。
## 协作约束
- 按仓库当前规则由单 Agent 串行执行,不启动子 Agent。
- 本任务只允许修改 frontmatter 中的 `write_paths`;若需要外部发布仓库提交、生产密钥、真实发布签名或协议字段/密钥轮换,必须停止并另立任务或请求外部协调。
- T-614 完成、完整验证并提交前,不落成或领取 T-302;T-302/T-601 的 Windows VM/真机断电、文件锁/杀毒干扰验证仍不因本任务被省略。
## 执行记录
- 2026-07-18:根据 `docs/review/phase1-security-review.md` 最终处理顺序第 4 项落成;T-613 已完成,因此 T-614 成为下一项唯一任务。
- 2026-07-18:前置检索确认现有合法签名测试由同一客户端 canonicalizer/测试私钥生成,Go JSON decoder 会将非法 surrogate 替换为 U+FFFD,且标准库 strict Base64 仍容忍 CR/LF。任务据此冻结拒绝策略和静态跨实现 corpus,不把客户端自举测试误作发布端互操作证明。
- 2026-07-18:新增 `testdata/catalog/canonical-vectors.json` v1 和客户端 corpus 测试。有效向量固定断言 canonical bytes、顶层签名和 RFC 8032 公开测试公钥验证结果,不调用 `canonicalJSON` 或测试期私钥生成预期;拒绝向量覆盖孤立/不匹配 surrogate、`-0`、Base64 CR/LF/space/tab 与 padding 缺失/额外。两种 key order/空白不同的 JSON 固定映射到同一 bytes/signature。
- 2026-07-18:在受限 JSON 解析器增加 surrogate 扫描,拒绝 Go decoder 原会替换的非法 pair;整数 token 只接受 `0`、正整数或负非零整数。Verifier 和 Parser 共享 Base64 decode→reencode 相等校验,因此非唯一文本不再被接受;嵌套 `signature` 仍在 signed payload。
- 2026-07-18:验证通过:`GOWORK=off go -C core vet ./catalog`、`GOWORK=off go -C core test -count=10 ./catalog`、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py`、`python scripts/validate_harness_governance.py` 与 `git diff --check`;完整验证同时覆盖 Go 1.20.14 core、现代版与 Win7 版构建/测试。外部 `softbox-catalog` 的 corpus 消费和 CI 证据不在本仓库,已作为跨仓库待协调项如实保留,无当前代码 blocker。