QORMQORM v0.8.4 docs 开始使用 EN

QORM Bundle 签名

QORM 应用以单个 JSON Bundle 的形式分发:内容寻址、可选 ed25519 签名。 签名是空中下载(OTA)UI 交付背后的信任原语 —— Runtime 在运行 Bundle 之前 先验证它,而不是信任投递它的服务器。实现位于 internal/bundle(格式与验证)、 internal/keys(密钥生成与存储)和 internal/ota(拉取与验证)。

本页描述的是代码中实际实现的格式与信任模型。旧文档中出现过的、代码里并不存在 的字段(bundleVersionminRuntimeVersion、顶层 hash),以下文的代码行为 为准。

Bundle 格式

一个真实的 Bundle,由 qorm build examples/counter --key qorm_key --version 1.0.0 生成(content 部分有省略):

{
  "format": "qorm-bundle/1",
  "content": {
    "app": {
      "id": "qorm_counter",
      "type": "app",
      "name": "QORM Premium Counter",
      "version": "1.0.0",
      "entry": "main"
    },
    "scenes": {
      "main": { "id": "main", "type": "scene", "root": { "...": "..." } }
    },
    "actions": {
      "increment": { "id": "increment", "type": "action", "...": "..." }
    }
  },
  "contentHash": "sha256:FoElQYNuc1V8SWyz8F3g53INEnriY9wR7w1PM1jfzck=",
  "signature": {
    "algorithm": "ed25519",
    "keyId": "fod5SMmyqtJJ",
    "value": "ziVsnGtOX6oiB/sNBq2D18mRJKYgoGx2y6y6+YGlGpn8rao1DPbk7NsL/4CLZiYfe1IRjonB0lZ7v0IckMEADA=="
  }
}

顶层字段:

应用 version 就位于这里,处于被签名内容之内。

运行所需能力;平台缺少其中任何一项时,Runtime 拒绝激活该 Bundle。用 qorm build --require-capability 盖入。

不存在多文件形态:构建 Bundle 时会先把所有源文档(qorm.jsonscenes/*.jsonactions/*.json)收集进 content,因此单个 hash 即覆盖 清单、全部场景、全部动作与语言目录。"只对根文件签名"的问题在格式层面就不存在 —— 没有根文件,只有 content

规范化与内容哈希

哈希是对 content 的 Go encoding/json 序列化结果计算的,该序列化是确定性的:

同一个 content 值重新序列化总是得到相同的字节,因此哈希跨机器稳定。 signaturecontentHash 本身位于被哈希区域之外;content 内的一切 —— 包括盖入的 versionrequiredCapabilities —— 都被哈希以及后续的任何签名 覆盖。

签名

签名是对 contentHash 字符串(即 "sha256:FoEl..." 的 ASCII 字节,而非原始 摘要)的ed25519 分离签名:

hash:      对规范化 content JSON 做 SHA-256,base64 编码,加 "sha256:" 前缀
signature: 对 contentHash 字符串做 ed25519 签名,base64 编码存入 signature.value

signature.algorithm"ed25519";其他任何值都会验证失败。 signature.keyId 用于标识签名密钥,供展示与诊断使用。key id 由公钥派生 —— 取原始公钥字节 base64 编码的前 12 个字符 —— qorm build --keyqorm sign 会自动填写。由于签名只覆盖 contentHash,keyId 本身并未被 签名;它只能当作提示,绝不能作为信任决策的输入(见下文"吊销")。

验证流程

bundle.VerifyWithRevocation 按以下顺序执行:

1. 解码 JSON;format != "qorm-bundle/1" 则拒绝
2. 重新计算内容哈希;不匹配则拒绝(内容被篡改)
3. 若未提供可信公钥:到此为止 —— 完整性已验证,真实性未验证
4. 要求 signature 存在
5. 要求 signature.algorithm == "ed25519"
6. base64 解码 signature.value
7. 用可信公钥对 contentHash 做 ed25519 验证
8. 若提供了吊销列表:验证密钥已被吊销则拒绝

每一步失败都是硬错误 —— 在 Verify 内部,验证绝不会降级为"带警告继续运行"。 (qorm run 在你不带 --trust 加载 Bundle 时会自己打印一条警告,因为该运行 模式只验证完整性;传入 --trust 才会强制要求签名。)

完整性与真实性

计算之后未被损坏,但对制作者一无所知。qorm verify 报告 OK ... (integrity)

OK ... (integrity + signature (key fod5SMmyqtJJ))

威胁模型

Bundle 签名防御的是:

检查;由可信密钥之外的人重新签名的 Bundle 过不了签名检查。服务器只是传输 通道,不是信任根。

会被拒绝。

它不能防御未被吊销的私钥泄漏,也不能替代平台权限:已签名的 Bundle 仍然只能获得 平台与策略授予的能力(见 permission-model.md)。

密钥

qorm keygen [--out-dir .] 生成 ed25519 密钥对并写入两个文件(权限 0600):

qorm_key       QORM-ED25519-PRIVATE-KEY\n<base64 私钥>
qorm_key.pub   QORM-ED25519-PUBLIC-KEY\n<base64 公钥>

密钥管理建议:

令牌,或至少是受访问控制的文件)。qorm signqorm build --key 只在 签名时刻需要私钥文件。

或通过 qorm package --update-url ... --trust qorm_key.pub 内嵌进包)。

Bundle;过渡期间客户端更新后即信任新密钥,切换完成后可把旧密钥加入吊销 列表。

吊销

吊销列表是一个本地 JSON 文件 —— 裸数组或对象均可:

["fod5SMmyqtJJ"]
{ "revoked": ["fod5SMmyqtJJ"] }

吊销检查刻意针对实际验证密钥派生出的 id,而不是 Bundle 自我声明的 signature.keyId:签名只覆盖 contentHash,否则持有被吊销密钥的人可以把 keyId 改成任意未被吊销的字符串,在签名仍然有效的情况下绕过吊销。

当前实现没有远程吊销刷新;吊销列表就是你通过 --revoked 传入(或在打包时 内嵌)的本地文件。更新后的列表与公钥走同一个分发渠道。

OTA 更新

internal/ota 是传输的一半:先拉取字节,先验证、后激活 —— 顺序从不颠倒。

产出 Bundle,调用方只需继续运行当前 Bundle。这就是回滚策略:不作为式 回滚(rollback by inaction) —— 失败的更新从不触碰正在运行的应用,因此 最近一个已知良好的 Bundle 就是当前正在运行的那个。

在 Bundle 上执行 qorm run

以 Bundle 文件(而非源码目录)运行时,服务器具备 OTA 能力,多两个端点(均对 跨域调用者屏蔽):

验证、按本平台检查 requiredCapabilities,然后激活。服务器必须以 --trust 启动,否则该端点拒绝服务(403),因为真实性无法验证。任何失败都返回 409, 正在运行的应用继续保持之前的 Bundle。 source 的拉取带有 SSRF 防护:私网(RFC 1918 / RFC 4193)、链路本地 (覆盖 169.254.169.254 / fe80:: 云元数据地址)、组播及未指定地址一律 拒绝。检查在 DNS 解析之后、建立连接时执行,因此重定向跳转或 DNS 重绑定 的域名都绕不过去;重定向最多跟随 5 跳,且只允许 http(s)。回环地址与本地 文件路径仍然放行 —— 本机上的 Bundle 服务器是正常的开发工作流,而回环 并不会给本地调用方增加它本来没有的可达性。

成功 /update 所替换掉的那个)。

打包应用

qorm package --update-url <url> --trust <key.pub> 把 OTA 能力烘焙进打包的 Web/移动应用;这两个参数强制成对出现。信任的划分是刻意的:

TLS 源站 —— 与投递 Runtime 本身的是同一渠道,因此启动时不再重复验证;

存储中恢复的)在激活前都要用内嵌的信任公钥做 ed25519 验证。验证失败的 Bundle 被丢弃,应用回退一层(当前更新 → 上一次更新 → 随包载荷)。

CLI 参考

qorm keygen [--out-dir .]                                     生成 ed25519 签名密钥对
qorm build <app-dir> [-o out] [--key priv] [--version v] [--require-capability a,b]
                                                              编译 Bundle;给出 --key 时签名
qorm sign <bundle> --key priv [-o out]                        为已有(如 agent 导出的)Bundle 签名
qorm verify <bundle> [--trust pub] [--revoked list.json]      验证完整性(+ 签名,+ 吊销)
qorm run <bundle> [--trust pub] [--revoked list.json]         运行 Bundle;运行前先验证,启用 /update 与 /rollback

qorm sign 在签名前会重新计算内容哈希,因此给被篡改的 Bundle 重新签名并不能 洗白它 —— 此时签名覆盖的是被篡改后的哈希,只有当篡改者持有私钥时验证方才 会接受。保证链始终是:可信公钥决定谁的内容可以运行。

本格式不做的事

明确列出,以免被误当作保证:

接受任何带该格式标记的 Bundle;版本信息(content.app.version)仅供展示 (出现在更新/回滚状态行中),不是激活条件。

有效。

文件。