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

63 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-502
title: Ed25519 离线许可证验证
phase: 5
deps: [T-501]
status: TODO
created: 2026-07-20
issue: null
context_ref: null
claim_branch: null
work_branch: null
write_paths:
- docs/tasks/T-502.md
- core/internal/canonicaljson/
- core/catalog/canonical.go
- core/catalog/canonical_vectors_test.go
- core/licensing/
- schemas/license.schema.json
- testdata/license/
- docs/00-ai-start-here.md
- docs/04-architecture.md
- docs/06-tasks.md
- docs/api.md
- docs/current-state.md
---
## 问题 / 背景
T-501 已能从 Windows 平台得到不含原始硬件标识的 `machine_hash`,但客户端尚不能解析、验签或比较许可证。因此复制许可证到另一台机器没有可执行的拒绝逻辑,`application/launch.AuthorizationChecker` 也还没有可由后续 composition 使用的可信授权事实。
现有许可证 JSON 只有字段草案,未指定签名载荷、重复字段、未知字段、签名编码、时间格式或机器摘要格式的 fail-closed 规则。Catalog 已有经过静态 corpus 覆盖的受限 canonical JSON 和 Ed25519 模式;另写一套近似实现会让同一服务端签发的协议在两个安全模块中漂移。
## 方案
1. 把 Catalog 当前的受限 JSON 解析与 canonical 输出下沉到仅 `core/` 可见的 `core/internal/canonicaljson`,保持 Catalog 导出的错误身份和静态 corpus 行为不变。该实现只接受有效 UTF-8、无未配对 surrogate、无重复 object key、整数(拒绝 `-0`、小数和指数),按递归字典序 key、无多余空白的 JSON 生成 signing bytes;Catalog 保留薄 wrapper,避免改变既有公开行为。
2. 在纯 Go 1.20 的 `core/licensing` 增加 `License`、`Verifier` 和只读产品授权查询。`NewVerifier(publicKey)` 复制并严格要求 32 字节 Ed25519 公钥;`Verify(document, expectedMachineHash)` 先以共享 canonical JSON 移除顶层 `signature` 得到 signing bytes,要求 64 字节标准 padded Base64 签名,再验签、严格 decode 和验证字段,最后精确比较调用方提供的 64 位小写 `machine_hash`。任何失败返回稳定、无许可证内容的 sentinel;成功结果深拷贝 products,不保存 document、签名或原始机器来源。
3. 冻结 License v1:只允许 `schema_version`、`license_id`、`machine_hash`、`products`、`issued_at`、`perpetual`、`update_policy`、`rebind_policy`、`signature` 九个顶层字段,全部必需且不得重复/未知。`license_id`、product 与 policy 分别匹配 `^lic-[a-z0-9][a-z0-9-]{0,59}$`、`^[a-z0-9-]+$`、`^[a-z0-9][a-z0-9._:-]{0,127}$`;products 非空且不重复,`issued_at` 是精确 RFC3339 UTC 秒级文本,`machine_hash` 必须为 T-501 的 64 字符小写 hex。canonical signing bytes 是删除顶层 `signature` 后的共享受限 canonical JSON;`schema_version` 只能为 1。`perpetual`、update/rebind policy 仅作为已签名元数据,本任务不实施 expiry、trial、revocation 或 rebind 决策。
4. 新增 `schemas/license.schema.json` 和只含虚构产品、测试公钥、已签名 document/签名载荷的静态 license corpus。绝不提交生产公钥、任何生产或测试私钥、真实许可证 ID、机器摘要或注册码。测试证明有效语料、对象键/空白等价 canonicalization、篡改、错误 key、重复/未知字段、非 canonical Base64、非法 JSON/数字、错误机器摘要、产品查询以及输入/输出深拷贝均 fail closed;现有 Catalog corpus 继续覆盖下沉后的兼容性。
5. 更新 API/架构/路线图和项目快照:当前 core verifier 只接收调用方注入的 public key 和 T-501 hash,尚无生产信任根、许可证文件读写、UI、试用、撤销名单、update/rebind 策略或启动用例 composition。T-503 才从受控 `licenses/` 文件导入、展示并把经验证的 product 授权接到盒子与子软件流程。
## 验收要点
- `core/licensing` 不 import Gio、Windows、文件系统或网络,在 Go 1.20 下能离线验证 v1 许可证;public key、document、expected machine hash、signature、字段形状或产品授权任一不可信时 fail closed。复制同一已签名 document 到不同 expected hash 必须返回 `ErrMachineMismatch`,而不是“未授权”或成功。
- License v1 的签名覆盖所有除顶层 `signature` 外字段,签名文本仅接受 canonical padded standard Base64 的 64 字节 Ed25519 值;重复/未知字段、额外 JSON、未配对 surrogate、`-0`、小数/指数、非 UTC 秒级时间、大小写错误 hash、空/重复 products 和无效 policy/ID 都被拒绝,错误和 API 返回不包含许可证 JSON、signature、ID 或机器标识。
- Catalog 的现有 canonical corpus 继续原样通过;许可证静态 corpus 不含私钥或真实值,却能固定跨实现 signing bytes 和验签结论。返回 `License` 的 products 及输入 public key 均无法在调用后改变 verifier 内部授权结论。
- `schemas/license.schema.json`、`docs/api.md`、架构和任务验收严格一致;文档明确 T-502 不提供生产公钥、文件导入、持久化、trial/revocation/rebind 或 UI,不能用 allow-all checker 宣称启动授权闭环已完成。
- `go -C core vet ./...`、`go -C core test -count=1 ./...`、两个 app 的 `go test -count=1 ./...`、两个 Windows amd64 主程序构建、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py` 与 `python scripts/validate_harness_governance.py` 全部通过。
## 边界(不改什么)
- 不提交生产公钥、任何私钥、真实许可证/机器摘要/注册码;不发明环境变量、网络取 key、allow-all verifier 或未签名许可证回退。
- 不读写 `licenses/`、不实现文件导入、原子存储、撤销名单、宽限期、试用/过期、换绑、SDK 分发、Gio 授权页或 `application/launch` composition;这些由 T-503 和发布配置处理。
- 不修改 T-501 的机器来源/摘要算法、Catalog 信任语义、下载、安装、更新、自更新、Windows API 或 Gio Layout;Catalog canonical 重构仅限保持现有行为的内部复用,不改变 manifest 协议。
## 协作约束
- 当前项目为单 Agent 串行模式;当前 Agent 独占全部 `write_paths`,自行完成规格、实现、测试、审查、状态更新和提交,不启动子 Agent。
- 本规格提交后才领取任务:将本文件改为 `DOING`,填写当时 HEAD 至 `context_ref` 与 `work_branch: agent/codex/T-502`,重跑基线后再实现。完成时只更新本任务为 `DONE`,不提前落成或实现 T-503。
## 执行记录
- 2026-07-20:正式落成。冻结 License v1 的九字段、canonical signing bytes、严格 Ed25519/机器摘要验证与不含私钥的静态语料;决定提取并复用既有 Catalog canonical JSON,而不是复制安全解析器。明确生产 trust root、文件导入、trial/revocation/rebind 与 UI 均留给后续任务。