/* meow_verify.c — MeowOS .mpk 签名校验器(固件 /usr/bin/meow_verify,静态 mipsel)。 * * 功能(MPK_SIGNATURE_DESIGN §5.1 + 2B §7): * - Ed25519 验签(TweetNaCl crypto_sign_open,公钥编译期固化进 meow_signing_pub.h,**多钥数组**); * - payload_sha256 = SHA-256(.mpk 去掉尾部 footer 的其余字节,即 tar.gz 前缀,分块读、无需解包); * - 包名绑定①:footer.package == 期望包名(--package 或文件名茎); * - 被签消息:v1 msg = "MEOWSIG1"||alg||pkg_len||package||payload_sha256;v2 msg = v1||ts(4B,LE)。 * 2B 加固:多公钥(任一通过即放行,为轮换铺路);v2 块含**签名时间戳 ts**,超未来宽限即拒 * (MEOW_SIG_TS_GRACE=24h;只做「未来异常」拒,防重放/过期明确不做)。 * 绑定②(app.json.package)与③(os.meow.* 前缀)由调用方(nyai / launcher)在安装流里执行, * 本器只保证「有签名就必须验签通过」。见 MPK_SIGNATURE_DESIGN §3.3(分工诚实标注)。 * 无签名第三方包安装放行由调用方决定,本器对无 footer 返回 2(调用方据分级处理)。 * * 用法: * meow_verify # 验签(默认系统模式:固件公钥 + 绑定①) * meow_verify --system # 同上(显式) * meow_verify --package # 显式给期望包名(否则从文件名茎推导) * meow_verify --has-sig # 只探测是否存在 MEOWSIG1 footer:输出 yes/no * meow_verify --strip # 输出去 footer 的干净 tar.gz 到 stdout(无 footer 则原样复制) * * 退出码:0=通过;1=有签但不通过(篡改/包名不符/公钥不符/格式错/ts异常);2=无签名 footer;3=用法/IO 错误。 */ #include #include #include #include #include #include #include #include "tweetnacl.h" #include "sha256.h" #include "meow_sig.h" /* 固件公钥数组(编译期固化,多钥 Ed25519)。由 meow_sign keygen 生成后写死于此头; * 轮换/增补公钥=改数组+MEOW_PUB_KEY_COUNT+重编 meow_verify(见 meow_signing_pub.h)。 */ #include "meow_signing_pub.h" /* 验签侧绝不调用 keypair,但 tweetnacl.c 的 crypto_sign_keypair 引用了 randombytes; * 提供 stub 以满足链接(静态链接整对象,函数未调用)。 */ void randombytes(unsigned char *x, unsigned long long xlen) { (void)x; (void)xlen; } static int file_size(const char *path, long long *sz) { struct stat st; if (stat(path, &st) != 0) return -1; *sz = (long long)st.st_size; return 0; } /* 读文件尾部签名块:先读最后 4 字节 block_len 判版本(v1=180 / v2=184),再整块读入 buf。 * 返回实际块字节数(>0);-2 太短/块长非法(无签名空间);-1 IO 错。magic 校验留给调用方。 */ static int read_block_bytes(const char *path, long long fsize, uint8_t *buf, int bufn) { int fd = open(path, O_RDONLY); if (fd < 0) return -1; uint8_t bl4[4]; if (lseek(fd, fsize - 4, SEEK_SET) < 0) { close(fd); return -1; } if (read(fd, bl4, 4) != 4) { close(fd); return -1; } uint32_t blen = meow_le32(bl4); if (blen != MEOW_SIG_BLOCK_LEN_V1 && blen != MEOW_SIG_BLOCK_LEN_V2) { close(fd); return -2; } if (fsize < (long long)blen || (int)blen > bufn) { close(fd); return -2; } if (lseek(fd, fsize - (long long)blen, SEEK_SET) < 0) { close(fd); return -1; } if (read(fd, buf, blen) != (ssize_t)blen) { close(fd); return -1; } close(fd); return (int)blen; } /* 分块 SHA-256 计算文件前 plen 字节。返回 0 成功,-1 IO/读错。 */ static int sha256_prefix(const char *path, long long plen, uint8_t out[MEOW_SIG_SHA256_LEN]) { int fd = open(path, O_RDONLY); if (fd < 0) return -1; meow_sha256_ctx c; meow_sha256_init(&c); uint8_t buf[8192]; long long remaining = plen; while (remaining > 0) { size_t chunk = remaining > (long long)sizeof buf ? sizeof buf : (size_t)remaining; ssize_t r = read(fd, buf, chunk); if (r <= 0) { close(fd); return -1; } meow_sha256_update(&c, buf, (size_t)r); remaining -= (long long)r; } close(fd); meow_sha256_final(&c, out); return 0; } /* 从 .mpk 路径取名:去掉目录 + 去 .mpk 后缀。成功 0。 */ static int derive_pkg(const char *mpk, char *out, int n) { const char *bn = strrchr(mpk, '/'); bn = bn ? bn + 1 : mpk; const char *dot = strrchr(bn, '.'); if (!dot || strcmp(dot, ".mpk") != 0) return -1; int len = (int)(dot - bn); if (len <= 0 || len >= n) return -1; memcpy(out, bn, (size_t)len); out[len] = 0; return 0; } /* 核心验签。pkg 为期望包名(nil 则从文件名推导)。返回 0/1/2/3。 */ static int verify_mpk(const char *mpk, const char *pkg) { long long fsize; if (file_size(mpk, &fsize) != 0) return 3; if (fsize < (long long)MEOW_SIG_BLOCK_LEN_V1) return 2; /* 无签名空间 */ uint8_t buf[MEOW_SIG_BLOCK_MAX]; int blen = read_block_bytes(mpk, fsize, buf, sizeof buf); if (blen < 0) return blen == -2 ? 2 : 3; if (memcmp(buf, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) != 0) return 2; /* 无 MEOWSIG1 footer */ if (buf[8] != MEOW_SIG_ALG_ED25519) return 1; /* 算法不符 */ int v2 = (blen == (int)MEOW_SIG_BLOCK_LEN_V2); if (!v2 && blen != (int)MEOW_SIG_BLOCK_LEN_V1) return 1; /* 块长未知:格式错 */ uint8_t want_flags = v2 ? MEOW_SIG_FLAG_TS : 0; if ((buf[9] & MEOW_SIG_FLAG_TS) != want_flags) return 1; /* flags 与版本不符 */ char pkgsig[MEOW_SIG_PKG_MAX + 1]; memcpy(pkgsig, buf + MEOW_SIG_OFF_PKG, MEOW_SIG_PKG_MAX); pkgsig[MEOW_SIG_PKG_MAX] = 0; /* 保证 NUL 终止 */ char pkgbuf[MEOW_SIG_PKG_MAX + 1]; if (pkg && *pkg) { snprintf(pkgbuf, sizeof pkgbuf, "%s", pkg); } else { if (derive_pkg(mpk, pkgbuf, sizeof pkgbuf) != 0) return 3; } /* 绑定①:期望包名 == footer.package */ if (strcmp(pkgbuf, pkgsig) != 0) { fprintf(stderr, "meow_verify: package mismatch (want %s, sig %s)\n", pkgbuf, pkgsig); return 1; } /* payload 哈希 */ long long plen = fsize - (long long)blen; uint8_t sha[MEOW_SIG_SHA256_LEN]; if (sha256_prefix(mpk, plen, sha) != 0) return 3; if (memcmp(buf + MEOW_SIG_OFF_SHA, sha, MEOW_SIG_SHA256_LEN) != 0) { fprintf(stderr, "meow_verify: payload sha256 mismatch (content tampered)\n"); return 1; } /* 重组被签消息(v2 含 ts)+ 验签(多公钥,任一通过即放行) */ uint32_t ts = v2 ? meow_le32(buf + MEOW_SIG_OFF_TS) : 0; char msg[MEOW_SIG_MSG_MAX]; unsigned mlen; if (v2) mlen = meow_sig_msg_build_v2(msg, pkgsig, sha, ts); else mlen = meow_sig_msg_build(msg, pkgsig, sha); uint32_t blk_msglen = meow_le32(buf + (v2 ? MEOW_SIG_OFF_MSGLEN_V2 : MEOW_SIG_OFF_MSGLEN_V1)); if (blk_msglen != mlen) { fprintf(stderr, "meow_verify: msg_len field mismatch\n"); return 1; } uint8_t sm[MEOW_SIG_SIG_LEN + MEOW_SIG_MSG_MAX]; memcpy(sm, buf + MEOW_SIG_OFF_SIG, MEOW_SIG_SIG_LEN); memcpy(sm + MEOW_SIG_SIG_LEN, msg, mlen); uint8_t out[MEOW_SIG_SIG_LEN + MEOW_SIG_MSG_MAX]; int ok = 0; for (int i = 0; i < MEOW_PUB_KEY_COUNT; i++) { unsigned long long outlen = 0; if (crypto_sign_open(out, &outlen, sm, (unsigned long long)(MEOW_SIG_SIG_LEN + mlen), g_meow_pub[i]) == 0) { ok = 1; break; } } if (!ok) { fprintf(stderr, "meow_verify: ed25519 signature invalid\n"); return 1; } /* 2B:v2 块签名时间戳未来宽限校验(仅防「未来异常」,防重放/过期不做) */ if (v2) { if (ts == 0) { fprintf(stderr, "meow_verify: ts field is zero (malformed)\n"); return 1; } time_t now = time(NULL); if ((time_t)ts > now + (time_t)MEOW_SIG_TS_GRACE) { fprintf(stderr, "meow_verify: signature ts in future beyond grace (ts=%u now=%ld)\n", ts, (long)now); return 1; } } printf("%s\n", pkgsig); return 0; } /* --strip:输出干净 tar.gz(去 footer;无 footer 原样复制到 stdout)。返回 0=有签,2=无签,3=IO 错。 */ static int strip_mpk(const char *mpk) { long long fsize; if (file_size(mpk, &fsize) != 0) return 3; long long payload = fsize; int has_sig = 0; if (fsize >= (long long)MEOW_SIG_BLOCK_LEN_V1) { uint8_t buf[MEOW_SIG_BLOCK_MAX]; int blen = read_block_bytes(mpk, fsize, buf, sizeof buf); if (blen >= 0 && memcmp(buf, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) == 0) { payload = fsize - (long long)blen; has_sig = 1; } } int fd = open(mpk, O_RDONLY); if (fd < 0) return 3; uint8_t buf[16384]; long long remaining = payload; while (remaining > 0) { size_t chunk = remaining > (long long)sizeof buf ? sizeof buf : (size_t)remaining; ssize_t r = read(fd, buf, chunk); if (r <= 0) { close(fd); return 3; } if (fwrite(buf, 1, (size_t)r, stdout) != (size_t)r) { close(fd); return 3; } remaining -= (long long)r; } close(fd); return has_sig ? 0 : 2; } static int has_sig(const char *mpk) { long long fsize; if (file_size(mpk, &fsize) != 0) return 3; if (fsize < (long long)MEOW_SIG_BLOCK_LEN_V1) { printf("no\n"); return 2; } uint8_t buf[MEOW_SIG_BLOCK_MAX]; int blen = read_block_bytes(mpk, fsize, buf, sizeof buf); if (blen < 0) { printf("no\n"); return 2; } int h = (memcmp(buf, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) == 0); printf(h ? "yes\n" : "no\n"); return h ? 0 : 2; } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s [--system|--has-sig|--strip] [--package ] \n", argv[0]); return 3; } int i = 1; int mode = 0; /* 0=verify, 1=has-sig, 2=strip */ const char *pkg = NULL; const char *mpk = NULL; for (; i < argc; i++) { if (!strcmp(argv[i], "--system")) { mode = 0; } else if (!strcmp(argv[i], "--has-sig")) { mode = 1; } else if (!strcmp(argv[i], "--strip")) { mode = 2; } else if (!strcmp(argv[i], "--package")) { if (i + 1 >= argc) { fprintf(stderr, "--package needs arg\n"); return 3; } pkg = argv[++i]; } else { mpk = argv[i]; } } if (!mpk) { fprintf(stderr, "missing \n"); return 3; } if (mode == 1) return has_sig(mpk); if (mode == 2) return strip_mpk(mpk); return verify_mpk(mpk, pkg); }