- 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)
146 lines
8.4 KiB
Markdown
146 lines
8.4 KiB
Markdown
# 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 文件 … 不存在或不可执行」。
|