Implement offline license verification (T-502)

This commit is contained in:
ila
2026-07-20 01:29:40 +08:00
parent c51ff30c54
commit cd76f7f900
12 changed files with 887 additions and 239 deletions
+6 -4
View File
@@ -3,12 +3,12 @@ id: T-502
title: Ed25519 离线许可证验证
phase: 5
deps: [T-501]
status: TODO
status: DONE
created: 2026-07-20
issue: null
context_ref: null
context_ref: c51ff30c54ad612b5f91649b35f9e940a28be573
claim_branch: null
work_branch: null
work_branch: agent/codex/T-502
write_paths:
- docs/tasks/T-502.md
- core/internal/canonicaljson/
@@ -33,7 +33,7 @@ T-501 已能从 Windows 平台得到不含原始硬件标识的 `machine_hash`
## 方案
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、签名或原始机器来源。
2. 在纯 Go 1.20 的 `core/licensing` 增加 `License`、`Verifier` 和只读 `License.AuthorizesProduct(productID) bool` 查询。`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 授权接到盒子与子软件流程。
@@ -60,3 +60,5 @@ T-501 已能从 Windows 平台得到不含原始硬件标识的 `machine_hash`
## 执行记录
- 2026-07-20:正式落成。冻结 License v1 的九字段、canonical signing bytes、严格 Ed25519/机器摘要验证与不含私钥的静态语料;决定提取并复用既有 Catalog canonical JSON,而不是复制安全解析器。明确生产 trust root、文件导入、trial/revocation/rebind 与 UI 均留给后续任务。
- 2026-07-20:领取任务,基于 `c51ff30c54ad612b5f91649b35f9e940a28be573` 在 `agent/codex/T-502` 执行;先重跑基线,再实现共享 canonical JSON、纯 core verifier 与静态测试语料。
- 2026-07-20:完成。将 Catalog 的受限 JSON 解析/canonical 输出下沉为 `core/internal/canonicaljson`,Catalog wrapper 与既有静态 corpus 保持通过;新增纯 Go 1.20 `licensing.Verifier`、严格 License v1 九字段/Ed25519/Base64/expected machine_hash 验证和 `License.AuthorizesProduct`。`schemas/license.schema.json` 与 `testdata/license` 只含虚构值、测试公钥和预签静态 documents,没有任何私钥。完整 core/两端 app 测试、双端 Windows amd64 构建、`./scripts/verify_phase0.ps1`、上下文与治理校验均通过。