Files
MeowOS 5fac5e380d fix(nyai): 导出 exec 绝对路径相对化 + gzip stderr 重定向 (agent-签名产签链路)
- cmd_export 原先对 app.json 的 exec(绝对路径,如 /usr/data/mnt/sd_0/MeowAPPs/<pkg>/<bin>)
  误用 sanitize_member 剥前导 '/' 拼到包目录下,导致 access(<src>/usr/data/...) 必然失败,
  所有真实 app 导出被拒(exec 文件不存在或不可执行),卡死「导出→产签」链路。
  新增 exec_rel_to_pkg(): 按 ①src/ 前缀 ②/MeowAPPs/<pkg>/ 段(根路径不一致的沙箱/CI)
  ③相对路径 ④绝对但定位不到包目录→拒, 正确相对化 exec。
- install/upgrade 解包命令把 2>/dev/null 只绑到 tar,gzip stderr(footer trailing garbage /
  损坏 crc error)会漏到用户输出;改 gzip 也重定向。
- 同链验证: 宿主 meow_sign/meow_verify_host/qemu-mipsel 验签矩阵通过; 真机(10.78.193.50)
  无签 os.meow.*拒装/已签安装OK+.mpk.src/篡改拒装/降级拒装 全过; 装后已卸并恢复原状。
  (devdocs 本地不入库; 产签实走记录+README 见 patch_v2/meow_sign/README.md §8)
2026-08-31 19:30:32 +08:00

146 lines
8.4 KiB
Markdown
Raw Permalink 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.
# 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_path> # 生成密钥对:priv(0600) + <priv>.pub(32B raw) + stdout 打印 pub_hex
meow_sign sign <pkg>.mpk <priv> [--v1] # 给纯 tar.gz 追加签名 footer(默认 v2 含时间戳;--v1=2A 旧版无 ts)
meow_sign verify <mpk> <pub|hex> # 用公钥(.pub 文件或 64 位 hex)验签指定 .mpk(产签后自测)
```
- `sign` 的 **`<pkg>.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 <build>/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=<unix> 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 根(**无** `<pkg>/` 顶层包裹目录)。
打包用 `tar -C <pkg> .`(导出生成本就是 `tar -C src .`),否则设备安装报 `解压/校验 app.json 失败`。
2. **包名 = 文件名茎**:`meow_sign sign` 从 `<pkg>.mpk` 文件名茎推导包名并写入 footer;`<pkg>.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 文件 … 不存在或不可执行」。