# meow_sign — MeowOS `.mpk` 产签/验签工具(宿主端) > 定位:**离线维护者侧工具**,给 `.mpk` 包打 Ed25519 签名,供设备端 `meow_verify`(固件 `/usr/bin/meow_verify`) > 验签。本工具**不落设备、不入库私钥**。设计依据:`devdocs/plan/MPK_SIGNATURE_DESIGN.md`(§4.2 产签流程 / §3.1 块格式 / §5.1 挂点)。 --- ## 0. 安全模型(先读,诚实标注) | 项 | 说明 | |---|---| | 算法 | Ed25519(非对称)。**私钥仅在离线维护机**,设备只持**公钥**(固化进 `meow_sign` 的 `meow_signing_pub.h`)。 | | 私钥 | `.keys/signing.key`(0600)——**绝不入库**(`.gitignore` 已忽略 `.keys/`)、**绝不落设备**;全网唯一,**务必单独备份**。 | | 设备验证 | `meow_verify`(固件)只读内嵌公钥数组验签;`os.meow.*` 包必须有有效签名,否则 nyai/launcher **拒装**(防伪造系统包名)。 | | 签名覆盖 | `payload_sha256`(整个 tar.gz 前缀,含二进制/资源/app.json)+ 显式 `package`(包名)。改内容/改包名 → 验签失败。 | | 诚实天花板 | 签名 + IPC 鉴权是**防误装/防伪造包名/防沿途篡改**,**不是真安全边界**:有 shell 的 root 用户可直接建目录/换 `meow_verify`。它挡住第三方 App 的「无意识越权」,挡不住 root 有意攻击(见 `MPK_SIGNATURE_DESIGN §8`)。 | --- ## 1. 用法 ```sh meow_sign keygen # 生成密钥对:priv(0600) + .pub(32B raw) + stdout 打印 pub_hex meow_sign sign .mpk [--v1] # 给纯 tar.gz 追加签名 footer(默认 v2 含时间戳;--v1=2A 旧版无 ts) meow_sign verify # 用公钥(.pub 文件或 64 位 hex)验签指定 .mpk(产签后自测) ``` - `sign` 的 **`.mpk` 文件名茎必须等于包名**(`app.json.package`),这是三重绑定①(`meow_verify` 会二次校验)。 - 默认产出 **v2** 块(184B):含**被签时间戳 `ts`**,验签端做「未来宽限」校验(超出 `now+86400` 拒)。 如需兼容旧固件(2A 的 `meow_verify` 只认 180B 的 v1)可加 `--v1` 产旧版。**新固件 `meow_verify` v1/v2 都读**。 - `verify` 是**宿主侧镜像**(产签后、分发前确认签名有效);**设备侧真正把关的是 `meow_verify`**。 --- ## 2. 产签流程(给新系统 App 签名) 以新增 `os.meow.xxx` 为例。 ### 2.1 首次:生成/备份密钥(仅一次) ```sh ./patch_v2/meow_sign/meow_sign keygen .keys/signing.key # 私钥 .keys/signing.key(0600) 请离线备份;公钥 .keys/signing.key.pub 可入库/固化进固件。 ``` > 若已存在 `.keys/signing.key`(本仓库 2A 已生成),**直接复用,勿覆盖**(覆盖会令旧包全部失效)。 ### 2.2 打包(不含签名;.mpk = 纯 tar.gz) ```sh mkdir -p /os.meow.xxx # 放入 app.json + meow 二进制 + 资源;app.json 的 package 必须 == 目录名 == 文件名茎;version 单调递增(禁降级) tar czf os.meow.xxx.mpk os.meow.xxx ``` > `app.json` 要点:`package`(= `os.meow.xxx`)、`exec`(指向 `MeowAPPs/os.meow.xxx/` 内二进制)、`version`(≥ 已装,禁止降级)。 ### 2.3 签名(默认 v2) ```sh ./patch_v2/meow_sign/meow_sign sign os.meow.xxx.mpk .keys/signing.key # => sign ok (v2): ... package=os.meow.xxx ts= block_len=184 ... ``` ### 2.4 产签后自测(可选但推荐) ```sh ./patch_v2/meow_sign/meow_sign verify os.meow.xxx.mpk .keys/signing.key.pub # 用自己公钥确认 ./patch_v2/meow_verify/meow_verify_host os.meow.xxx.mpk # 用固件内嵌公钥确认(与设备同口径) ``` ### 2.5 装到设备 / 分发 - 把 `os.meow.xxx.mpk` 放到卡上(如 `MeowOS/opt/nyai/` 或目标目录),用 `nyai install` 安装。 - 安装期由 nyai/launcher 调 `meow_verify`:验签通过 + 三重绑定(footer.package==文件名茎==app.json.package + `os.meow.*` 前缀)才落盘;否则拒装并回滚。 - **`os.meow.*` 无签名/验签失败 → 一律拒装**(防伪造系统包名);第三方包无签可直接装(但建议也签名防篡改)。 --- ## 3. 三重绑定(三方各司其职) | 绑定 | 校验方 | 校验点 | |---|---|---| | ① footer.package == 文件名茎 | `meow_verify`(`--package`/推导) | 防「把 `os.meow.xxx.mpk` 改名为 `evil.mpk`」 | | ② footer.package == 解包 `app.json.package` | nyai / launcher 安装流 | 防「改 app.json 的 package」 | | ③ `os.meow.*` 前缀 + 必须验签过 | nyai / launcher 安装流 | 防「无签名冒充系统包」 | --- ## 4. 多公钥 / 密钥轮换(2B) 固件 `meow_verify` 内嵌**公钥数组**(`meow_signing_pub.h`),验签时**任一公钥通过即放行**。轮换 = 加钥/去钥 + 重编 `meow_verify`(随固件更新): ```c #define MEOW_PUB_KEY_COUNT 2 static const uint8_t g_meow_pub[MEOW_PUB_KEY_COUNT][32] = { { /* key #0:旧主钥(保留做跨代兼容) */ ... }, { /* key #1:新钥 */ ... }, }; ``` - **加钥**:新钥 `meow_sign keygen` 后追加到数组尾部、`MEOW_PUB_KEY_COUNT` +1、重编 `meow_verify` → 旧钥签的包(新固件仍内嵌旧钥)与新旧包都放行(平滑过渡)。 - **去钥**:从数组移除 + 重编 → 原钥签的包在新固件**拒装**。**去钥必须与包重签协同**(先用新钥重签所有在维护的系统包)。 - 私钥更新(新钥签新包)时,务必保证新私钥离线备份,且**旧私钥在轮换期内保留**(否则旧钥签的包失效)。 --- ## 5. v1 / v2 块与兼容 | | v1(2A 旧版) | v2(2B 加固) | |---|---|---| | 总长 | 180B | 184B | | 时间戳 | 无 | 有(`ts`,unix 秒,**被签名**) | | 被签消息 | `"MEOWSIG1"‖alg‖pkg_len‖package‖sha256` | v1 msg ‖ `ts(4B,LE)` | | `meow_verify` | 读取(不检 ts) | 读取(校验 ts 未来宽限,超 `now+86400` 拒;`ts==0` 视为畸形拒) | - 新 `meow_verify`(2B 固件)**v1/v2 都读** → 旧 2A 签的包仍可装/可导出复验。 - **`--v1` 产出的包在旧 2A 固件可装;v2 包需 2B+ 固件**(旧固件 `meow_verify` 只认 180B,会把 184B 块误判为无签 → `os.meow.*` 拒装)。固件与工具链同版本发布即可避免此坑。 --- ## 6. 明确不做(诚实) - **防重放 / 密钥过期 / CRL / 在线验证**:需网络+后端,超出「单机 + 卡分发」模型(`MPK_SIGNATURE_DESIGN §8`)。 - **时间戳只做「未来异常」宽限拒绝**(防错误时钟/手工伪造未来 ts),不做「旧包过期拒装」。 - 签名是**防误装/防伪造包名/防篡改**,不是安全边界。 --- ## 7. 相关文件 | 文件 | 作用 | |---|---| | `meow_sign.c` | 产签/验签工具源码(复用 `../meow_verify` 的 `tweetnacl.c` + `sha256.c` + `meow_sig.h`) | | `build_sign.sh` | 宿主编译(`./build_sign.sh` / `./build_sign.sh static`) | | `../meow_verify/meow_verify.c` | 设备侧校验器(固件 `/usr/bin/meow_verify`) | | `../meow_verify/meow_signing_pub.h` | **固件内嵌公钥数组**(仅公钥,可入库) | | `../meow_verify/meow_sig.h` | 签名块格式 + 被签消息构造(verifier/signer 共用) | | `.keys/` | 私钥目录(**已 gitignore,绝不入库**) | ## 8. 端到端实走(真机已验证,2026-08-31) > 完整「产签→验签→真机安装→导出保留签名」的可复现步骤、逐命令预期输出、6 个失败矩阵项、常见错误速查与本次修复,见 > `devdocs/plan/MPK_SIGNATURE_DESIGN.md` 的「产签实走记录 / 操作步骤(端到端)」节。此处只记三个**最容易踩的坑**: 1. **`.mpk` 文件内文件须在 tar 根**:`app.json`/`meow`/资源须并列于 tar 根(**无** `/` 顶层包裹目录)。 打包用 `tar -C .`(导出生成本就是 `tar -C src .`),否则设备安装报 `解压/校验 app.json 失败`。 2. **包名 = 文件名茎**:`meow_sign sign` 从 `.mpk` 文件名茎推导包名并写入 footer;`.mpk` 必须 == `app.json.package`。 改文件名/包名 → 验签 `package mismatch`。 3. **`exec` 可为绝对路径,导出须相对化**:真实 app 的 `app.json.exec` 是绝对路径(`launcher` 直接 `execve`)。 `nyai export` 须把 `exec` 相对化到包目录(已修 `nyai.c` 的 `exec_rel_to_pkg`)再存在性检查/白名单打包; 否则所有真实 app 导出报「exec 文件 … 不存在或不可执行」。