From b4453130ded0efbbe0470a91ac762253d95a8c3e Mon Sep 17 00:00:00 2001 From: ila Date: Mon, 20 Jul 2026 08:43:24 +0800 Subject: [PATCH] Define authorization integration task (T-503) --- docs/tasks/T-503.md | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/tasks/T-503.md diff --git a/docs/tasks/T-503.md b/docs/tasks/T-503.md new file mode 100644 index 0000000..cf748c4 --- /dev/null +++ b/docs/tasks/T-503.md @@ -0,0 +1,72 @@ +--- +id: T-503 +title: 授权界面、离线导入与撤销状态 +phase: 5 +deps: [T-502, T-204] +status: TODO +created: 2026-07-20 +issue: null +context_ref: null +claim_branch: null +work_branch: null +write_paths: + - docs/tasks/T-503.md + - core/licensing/ + - core/application/ + - core/application/launch/ + - core/application/install/ + - core/installer/ + - core/storage/ + - app-modern/cmd/softbox/ + - app-modern/platform/windows/ + - app-modern/ui/gio/ + - app-win7/cmd/softbox/ + - app-win7/platform/windows/ + - app-win7/ui/gio/ + - schemas/ + - testdata/license/ + - docs/04-architecture.md + - docs/06-tasks.md + - docs/api.md + - docs/current-state.md + - docs/routes.md +--- + +## 问题 / 背景 + +T-501/T-502 已经能在调用方给出机器摘要和公钥时,严格离线验证一份 License v1;它仍是纯验证器。仓库没有可信的许可证文件生命周期、撤销缓存、授权状态快照或 Gio 页面,也没有把已经验签的 `product_id` 接到启动边界。直接把导入的 JSON、测试 key 或“无文件即允许”的判断接到 UI/启动器,会破坏机器绑定和 fail-closed 边界。 + +现有 `app.json` 的 `product_id` 与 `supports_trial` 已经在受签名 ZIP 内校验,但安装记录尚未保留它们。若启动检查继续按 app ID 判断授权,客户端无法把许可证产品和已安装软件安全关联。License v1 也没有到期字段,不能把 `perpetual:false` 擅自解释成仍有效的本地试用。 + +## 方案 + +1. 在 `core/licensing` 冻结并实现 Revocation List v1。它使用调用方显式注入的授权 Ed25519 公钥(与 License v1 可以是同一受控配置,但绝不回退到测试/环境 key),只允许 `schema_version`、`generated_at`、`expires_at`、`revoked_license_ids`、`signature` 五个必需顶层字段;采用与 License 相同的受限 canonical JSON、严格 padded Base64 Ed25519 签名、UTC 秒级时间、唯一且合法的 `lic-...` ID。`expires_at` 必须晚于 `generated_at` 且间隔不超过 31 天。已验证撤销立即拒绝;未撤销且缓存未过期为 `current`;过期后最多 7 天为 `grace`;无缓存、无效缓存或超过宽限期为 `unavailable`,均不可授权。调用方注入时钟;所有错误/快照/UI 都不包含许可证 ID、签名、原始文档或机器来源。 +2. 在 `core/storage` 新增受限 `licenses/` 存储。仅在 `licensing.Verifier` 成功匹配当前 machine hash 后,才把原始许可证 document 原子写入 `licenses/v1/.license`(0600、临时文件 sync/close 后 rename);读取时只接受真实目录和该文件名模式、1 MiB 以下普通文件,并逐份重新验签。未知/链接/损坏缓存让授权状态 fail closed,不信任残留内容;相同 document 的导入幂等。撤销列表仅由已经验签的 document 原子替换 `licenses/revocations-v1.json`,与许可证分开保存。更新/安装/自更新都不得删除或覆盖此根。 +3. 增加纯 core 的授权用例与受净化事件快照。它以显式的 verifier、machine hash、store、clock 和 `LicenseImportSource`/撤销来源组合,后台完成启动加载、导入与撤销更新,并只向 UI 发布 `LicenseChanged` 的固定状态码、machine hash、产品 ID、正式/非永久展示状态、撤销状态和换绑策略;不发送 license ID、签名、JSON、文件路径或原始 Windows 标识。默认 `UnconfiguredAuthorizationService` 明确发布 `authorization_unconfigured`,不创建 `licenses/`、不允许导入、不授权。UI 点击只入队请求;文件选择、文件读取、验签和写盘均在后台进行。 +4. 将已通过签名 ZIP 的 `product_id` 与 `supports_trial` 从 manifest 经 `VerifiedPackage` 保存到 `installed-app.json`,并把启动的 `AuthorizationChecker` 入参改为 `product_id`。历史安装记录缺少这些字段视为授权元数据不可用而拒绝启动;`application/launch` 只接受已验证且撤销状态可用/宽限中的产品授权。`supports_trial:true` 仅表示软件支持试用,License v1 的 `perpetual:false` 仅显示为“非永久许可证”:本任务不实现本地试用计时、到期、绕过授权或自助换绑。 +5. 在两套 Gio shell 增加同语义的 `LicensePanel`:左侧可进入“授权”,显示 machine hash、授权产品、正式/非永久状态、撤销当前/宽限/不可用状态,以及不修改本机绑定的换绑说明。无配置、无授权、撤销、宽限和导入失败均有明确可访问文案;只有配置就绪时显示可操作的导入按钮。按钮、键盘焦点和语义标签满足现有最小 44dp 可点击目标;Layout 只 drain 内存事件/请求,不做 I/O。现代和 Win7 command 都仅装配安全默认值;测试可注入假 source/受控测试 key 验证完整后台→relay→UI 链路,发布配置/真实信任根留给 T-603。 +6. 更新 API、架构、路由、路线图和快照,使 License v1、撤销/7 天宽限、存储布局、状态码、产品映射和“不提供本地试用/换绑服务”的边界一致;新增不含私钥或生产值的静态/动态测试向量。 + +## 验收要点 + +- 许可证在写入前、每次启动加载和每次授权判断时都重新验证签名、machine hash 与 product;外来 JSON、错误 key/hash、超限/链接/未知/损坏缓存、撤销、缺失或超过宽限的撤销缓存一律不能授权,也不泄露敏感原文。 +- Revocation List v1 覆盖 canonical 签名、重复/未知字段、非法 ID/时间/Base64、future/过期边界、撤销优先和恰好 7 天宽限边界;不存在本地 allow-all 或使用测试 key 的 production composition。 +- 导入写入限制在受控 `licenses/v1/`,幂等、原子且 0600;列表返回深拷贝的非敏感授权事实。导入/加载/撤销失败均映射稳定状态码,不把路径、license ID、签名、JSON、GUID 或卷序列号放入 event、日志或界面。 +- 已安装记录保留受验证 manifest 的 product/trial 元数据;启动以 product ID 检查且旧记录缺字段 fail closed。试用和换绑页面只展示已签名事实/下一步说明,不创建未签名试用授权、不改变 machine hash、不替换许可证。 +- 两端 Gio 页面能从 `LicenseChanged` 事件刷新,未配置、无授权、撤销、宽限、导入失败和已授权状态均可见;导入点击不在 Layout 做磁盘/网络/哈希,后台结果通过现有有界 relay 唤醒 UI。 +- `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` 全部通过。 + +## 边界(不改什么) + +- 不提交生产公钥、任何私钥、真实 license/revocation、机器摘要、注册码或正式下载 URL;不从环境变量、网络或 UI 文本取得信任根。 +- 不实现在线账号、支付、自助换绑、人工申诉接口、许可证签发、撤销列表下载协议、试用计时/过期/本地绕过,或子软件 SDK 分发;这些需要发布端/产品协议另行确定。 +- 不改 T-501 的指纹算法、Catalog 协议、下载/安装/更新/自更新事务或 Gio 之外的架构。安装记录只做携带已验证 manifest 授权元数据的最小兼容升级。 + +## 协作约束 + +- 当前项目为单 Agent 串行模式;当前 Agent 独占全部 `write_paths`,自行完成规格、实现、测试、审查、状态更新和提交,不启动子 Agent。 +- 本规格提交后才领取任务:将本文件改为 `DOING`,填写当时 HEAD 至 `context_ref` 与 `work_branch: agent/codex/T-503`,重跑基线后再实现。完成时只更新本任务为 `DONE`,不提前落成后续任务。 + +## 执行记录 + +- 2026-07-20:正式落成。冻结 Revocation List v1、7 天离线宽限和受限 `licenses/` 缓存;明确产品授权必须使用受验证 manifest 的 product ID,且 License v1 不足以实现本地 trial/rebind。默认发布构建仍以未配置状态 fail closed,真实 trust root/发布配置交由 T-603。