diff --git a/.gitignore b/.gitignore index 1230bf2..6bee47a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ __pycache__/ font/atlas/ # SSH 私钥/认证(绝不入库;.pub 公钥可留但这里一并忽略,按需手动 add) ssh_keys/ +# .mpk 签名私钥(Ed25519,离线维护者侧;绝不入库) +.keys/ # 二进制备份(*.bak 由 build/升级流程生成,勿入库) *.bak *.orig @@ -65,3 +67,8 @@ ssh_keys/ devdocs/ scripts/build/ fonts_lib/ + +# 字体源文件(体积大,不进库;产物在 TF 卡) +font/src/**/*.otf +font/src/**/*.ttf +font/src/**/*.ttc diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 719f25e..0d0ae05 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -506,6 +506,82 @@ static int meow_app_prepare(const char *exec) } +/* ---- II 期签名校验 / 导出保留签名(MPK_SIGNATURE_DESIGN §5.1/§5.4,shell-out meow_verify) ---- */ + +/* meow_verify --system --package 。返回 0=有效签 /1=有签不通过 /2=无签名 /127=工具不可用。 */ +static int pkg_verify_sig(const char *mpk, const char *pkg) +{ + const char *vb = getenv("MEOW_VERIFY_BIN"); + if (!vb || !*vb) vb = "/usr/bin/meow_verify"; + char cmd[700]; + snprintf(cmd, sizeof cmd, "'%s' --system '%s' --package '%s' >/dev/null 2>&1", vb, mpk, pkg); + int rc = system(cmd); + if (rc == -1) return 127; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return 127; +} + +/* 读已解压 app.json 的某字符串字段(用 launcher 的 json_str;返回 0 成功)。 */ +static int pkg_json_field(const char *appjs, const char *key, char *out, int n) +{ + if (!appjs || !key || n <= 0) return -1; + FILE *f = fopen(appjs, "r"); + if (!f) return -1; + static char j[16384]; + size_t r = fread(j, 1, sizeof j - 1, f); + fclose(f); + j[r] = 0; + if (!r) return -1; + if (json_str(j, key, out, n) <= 0) { out[0] = 0; return -1; } + return 0; +} + +/* 读 .mpk 内 app.json 的某字符串字段(gzip -dc | tar -xO app.json;返回 0 成功)。 */ +static int pkg_json_field_mpk(const char *mpk, const char *key, char *out, int n) +{ + char cmd[700]; + snprintf(cmd, sizeof cmd, "gzip -dc '%s' 2>/dev/null | tar -xO app.json 2>/dev/null", mpk); + FILE *pf = popen(cmd, "r"); + if (!pf) return -1; + static char j[16384]; + size_t r = fread(j, 1, sizeof j - 1, pf); + pclose(pf); + if (!r) return -1; + j[r] = 0; + if (json_str(j, key, out, n) <= 0) { out[0] = 0; return -1; } + return 0; +} + +/* 安装成功后,把原签 .mpk 另存 MeowData//data/.mpk.src(导出保留签名)。 */ +static void pkg_save_src(const char *mpk, const char *pkg) +{ + char dst[320], cmd[720]; + snprintf(dst, sizeof dst, "/usr/data/mnt/sd_0/MeowData/%s/data/%s.mpk.src", pkg, pkg); + snprintf(cmd, sizeof cmd, "mkdir -p '/usr/data/mnt/sd_0/MeowData/%s/data' 2>/dev/null && " + "cp '%s' '%s' 2>/dev/null", pkg, mpk, dst); + if (system(cmd) != 0) logmsg("pkg_install: save .mpk.src fail for %s", pkg); +} + +/* 导出保留签名:缓存 .mpk.src 存在 + 验签通过 + 其 app.json 版本 == 已装 → 直接复制(不二次签名)。 + * 成功 0;否则 -1(调用方现场重打包,无签名)。 */ +static int pkg_export_preserve_sig(const char *pkg, const char *dst) +{ + char srcp[320], instj[280], cmd[760], tmp[340]; + snprintf(srcp, sizeof srcp, "/usr/data/mnt/sd_0/MeowData/%s/data/%s.mpk.src", pkg, pkg); + if (access(srcp, R_OK) != 0) return -1; + if (pkg_verify_sig(srcp, pkg) != 0) { logmsg("pkg_export: cached .mpk.src sig fail %s", pkg); return -1; } + char cv[64], iv[64]; + snprintf(instj, sizeof instj, "/usr/data/mnt/sd_0/MeowAPPs/%s/app.json", pkg); + if (pkg_json_field(instj, "version", iv, sizeof iv) != 0) return -1; + if (pkg_json_field_mpk(srcp, "version", cv, sizeof cv) != 0) return -1; + if (strcmp(iv, cv) != 0) { logmsg("pkg_export: cached version %s != installed %s", cv, iv); return -1; } + snprintf(tmp, sizeof tmp, "%s.tmp", dst); + snprintf(cmd, sizeof cmd, "cp '%s' '%s' 2>/dev/null", srcp, tmp); + if (system(cmd) != 0 || access(tmp, R_OK) != 0) return -1; + if (rename(tmp, dst) != 0) { unlink(tmp); return -1; } + return 0; +} + /* .mpk 安装(本期接口/CLI + 文档;UI 后期):解压 .mpk → MeowAPPs//。 * 约定 .mpk = tar.gz,顶层即包目录(含 app.json);包名 = 文件名茎 .mpk。 * 同名先备份;解压后校验 app.json;随后数据 init(MeowData//{data,cache})。 @@ -525,9 +601,25 @@ int meow_pkg_install(const char *mpk_path) if (len <= 0 || len >= (int)sizeof pkg) { logmsg("pkg_install: bad pkg name"); return -1; } memcpy(pkg, bn, len); pkg[len] = 0; /* 目标 MeowAPPs//;同名先备份(.bak_),再解压,再校验 app.json */ - char dst[256], bak[256], cmd[700]; + char dst[256], bak[256], cmd[760]; snprintf(dst, sizeof dst, "/usr/data/mnt/sd_0/MeowAPPs/%s", pkg); snprintf(bak, sizeof bak, "%s.bak_%d", dst, (int)getpid()); + int replacing = (access(dst, F_OK) == 0); + + /* II 期:签名分级先于解包(MPK_SIGNATURE_DESIGN §5.2:先验签再解包,失败不落盘)。 + * 有签名必须验签(失败拒装);os.meow.* 必须有有效签名;无签名第三方放行(告警)。 */ + int vr = pkg_verify_sig(mpk_path, pkg); + int sysp = (strncmp(pkg, "os.meow.", 8) == 0); + if (vr == 1) { logmsg("pkg_install: sig invalid, reject %s", pkg); return -3; } + else if (vr == 2) { + if (sysp) { logmsg("pkg_install: sys pkg %s must be signed, rejected", pkg); return -3; } + logmsg("pkg_install: %s unsigned (3rd party allowed)", pkg); + } + else if (vr != 0) { + if (sysp) { logmsg("pkg_install: meow_verify unavailable rc=%d, sys pkg reject", vr); return -3; } + logmsg("pkg_install: meow_verify unavailable rc=%d, %s unverified (3rd party)", vr, pkg); + } + /* BusyBox tar 无 -z:用 gzip -dc | tar -x 处理 tar.gz。 */ snprintf(cmd, sizeof cmd, "rm -rf '%s' 2>/dev/null; " @@ -535,10 +627,27 @@ int meow_pkg_install(const char *mpk_path) "mkdir -p '%s' && gzip -dc '%s' | tar -x -C '%s' 2>/dev/null && " "[ -f '%s/app.json' ]", bak, dst, dst, bak, dst, mpk_path, dst, dst); if (system(cmd) != 0) { logmsg("pkg_install: extract/validate fail for %s", pkg); return -1; } + + /* II 期:app.json.package 绑定(== 包名,禁改包名/目录改名;防第三方面具系统包)。 */ + { + char appj[280], ph[64]; + snprintf(appj, sizeof appj, "/usr/data/mnt/sd_0/MeowAPPs/%s/app.json", pkg); + if (pkg_json_field(appj, "package", ph, sizeof ph) == 0 && strcmp(ph, pkg) != 0) { + logmsg("pkg_install: app.json.package %s != %s, reject", ph, pkg); + char rb[760]; + snprintf(rb, sizeof rb, "rm -rf '%s' 2>/dev/null; [ -d '%s' ] && mv '%s' '%s' 2>/dev/null", + dst, bak, bak, dst); + system(rb); + return -3; + } + } + /* 数据 init:MeowData//{data,cache} */ snprintf(cmd, sizeof cmd, "mkdir -p '/usr/data/mnt/sd_0/MeowData/%s/data' " "'/usr/data/mnt/sd_0/MeowData/%s/cache' 2>/dev/null", pkg, pkg); system(cmd); + /* 有效签 → 原签 .mpk 另存 MeowData//data/.mpk.src(导出保留签名) */ + if (vr == 0) pkg_save_src(mpk_path, pkg); logmsg("pkg_install: %s installed (old backed up %s)", pkg, bak); return 0; } @@ -556,6 +665,11 @@ int meow_pkg_export(const char *pkg, const char *out_path) snprintf(src, sizeof src, "/usr/data/mnt/sd_0/MeowAPPs/%s", pkg); if (access(src, R_OK) != 0) { logmsg("pkg_export: no such pkg"); return -1; } snprintf(dst, sizeof dst, "%s", out_path); + /* II 期:导出保留签名 —— 若安装时存了原签 .mpk.src 且其版本==已装 → 直接复制(不二次签名)。 */ + if (pkg_export_preserve_sig(pkg, dst) == 0) { + logmsg("pkg_export: %s -> %s (signed, preserved)", pkg, dst); + return 0; + } /* BusyBox tar 无 -z:tar -c | gzip -c > out(先写 .tmp 再 mv,防半成品)。 */ char cmd[512]; snprintf(cmd, sizeof cmd, "tar -c -C '%s' . | gzip -c > '%s.tmp' 2>/dev/null && " diff --git a/patch_v2/meow_sign/build_sign.sh b/patch_v2/meow_sign/build_sign.sh new file mode 100755 index 0000000..4be6f78 --- /dev/null +++ b/patch_v2/meow_sign/build_sign.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# build_sign.sh — meow_sign 宿主编译(产签工具;非目标机二进制,无需 mipsel)。 +# +# 用法: +# ./build_sign.sh # 宿主编译 → patch_v2/meow_sign/meow_sign +# ./build_sign.sh static # 静态宿主编译(可选,便于分发/一致验证) +set -e +cd "$(dirname "$0")" +ROOT="$HOME/Project/R3ProII" +HOST_CC="${CC_HOST:-cc}" +V="$ROOT/patch_v2/meow_verify" + +# tweetnacl.c 为 vendor 代码:放宽 -Wno-sign-compare / -Wno-unused-function(其 u64/u32 与 int 混比)。 +CFLAGS="-Os -Wall -Wextra" +if [ "$1" = "static" ]; then + "$HOST_CC" $CFLAGS -static meow_sign.c "$V/tweetnacl.c" "$V/sha256.c" -o meow_sign_static + echo "== meow_sign (static) ==" + ls -la meow_sign_static; md5sum meow_sign_static; file meow_sign_static + exit 0 +fi + +# tweetnacl.c 单独编译并放宽警告;meow_sign.c 全警告编译 +"$HOST_CC" -Os -Wno-sign-compare -Wno-unused-function -c "$V/tweetnacl.c" -o tweetnacl_meow.o +"$HOST_CC" -Os -Wall -Wextra -c "$V/sha256.c" -o sha256_meow.o +"$HOST_CC" $CFLAGS meow_sign.c tweetnacl_meow.o sha256_meow.o -o meow_sign +rm -f tweetnacl_meow.o sha256_meow.o +echo "== meow_sign (host) ==" +ls -la meow_sign; md5sum meow_sign; file meow_sign diff --git a/patch_v2/meow_sign/meow_sign b/patch_v2/meow_sign/meow_sign new file mode 100755 index 0000000..ced32c4 Binary files /dev/null and b/patch_v2/meow_sign/meow_sign differ diff --git a/patch_v2/meow_sign/meow_sign.c b/patch_v2/meow_sign/meow_sign.c new file mode 100644 index 0000000..0603539 --- /dev/null +++ b/patch_v2/meow_sign/meow_sign.c @@ -0,0 +1,184 @@ +/* meow_sign.c — MeowOS .mpk 产签工具(宿主端,非设备;离线维护者侧使用)。 + * + * 功能(MPK_SIGNATURE_DESIGN §4.2): + * - keygen :生成 Ed25519 密钥对。私钥(64B seed||pub,0600)写 ; + * 公钥(32B hex + raw)写 .pub,并在 stdout 打印 hex(便于固化进 meow_verify)。 + * - sign .mpk :对 .mpk(当前无 footer = 纯 tar.gz)计算 payload_sha256、 + * 组被签消息、Ed25519 签名,并**尾部追加** 180B 定长 footer(.mpk = tar.gz || meow_sig_block)。 + * + * 私钥绝不落设备、绝不入库(仓库 .gitignore 已忽略 .keys/)。 + * 复用 ../meow_verify 的 tweetnacl.c(Ed25519)+ sha256.c。 */ +#include +#include +#include +#include +#include +#include +#include + +#include "../meow_verify/tweetnacl.h" +#include "../meow_verify/sha256.h" +#include "../meow_verify/meow_sig.h" + +/* keygen 需真随机(从 /dev/urandom 读);sign 不调用 keypair。 */ +void randombytes(unsigned char *x, unsigned long long xlen) +{ + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { fprintf(stderr, "meow_sign: /dev/urandom open fail: %s\n", strerror(errno)); exit(1); } + size_t off = 0; + while (off < (size_t)xlen) { + ssize_t r = read(fd, x + off, (size_t)xlen - off); + if (r <= 0) { close(fd); fprintf(stderr, "meow_sign: urandom read fail\n"); exit(1); } + off += (size_t)r; + } + close(fd); +} + +static int write_file(const char *path, const void *buf, size_t n, mode_t mode) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); + if (fd < 0) return -1; + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, (const char *)buf + off, n - off); + if (w <= 0) { close(fd); return -1; } + off += (size_t)w; + } + close(fd); + return 0; +} + +static void hex_print(const char *label, const uint8_t *b, int n) +{ + printf("%s=", label); + for (int i = 0; i < n; i++) printf("%02x", b[i]); + printf("\n"); +} + +static int keygen(const char *priv_path) +{ + uint8_t pk[32], sk[64]; + crypto_sign_keypair(pk, sk); + if (write_file(priv_path, sk, sizeof sk, 0600) != 0) { + fprintf(stderr, "meow_sign: write %s fail: %s\n", priv_path, strerror(errno)); + return 1; + } + char pub_path[512]; + snprintf(pub_path, sizeof pub_path, "%s.pub", priv_path); + if (write_file(pub_path, pk, sizeof pk, 0644) != 0) { + fprintf(stderr, "meow_sign: write %s fail: %s\n", pub_path, strerror(errno)); + return 1; + } + printf("keygen ok:\n priv -> %s (0640? no: 0600)\n pub -> %s\n", priv_path, pub_path); + hex_print("pub_hex", pk, 32); + return 0; +} + +/* 分块 SHA-256 计算整文件(其内容作为 payload;此时文件尚无 footer)。 */ +static int file_sha256(const char *path, 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]; + for (;;) { + ssize_t r = read(fd, buf, sizeof buf); + if (r <= 0) break; + meow_sha256_update(&c, buf, (size_t)r); + } + close(fd); + meow_sha256_final(&c, out); + return 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; +} + +static int sign(const char *mpk, const char *priv_path) +{ + char pkg[MEOW_SIG_PKG_MAX]; + if (derive_pkg(mpk, pkg, sizeof pkg) != 0) { + fprintf(stderr, "meow_sign: 需要 .mpk(包名=文件名茎)\n"); + return 1; + } + /* 读私钥(64B seed||pub) */ + int fd = open(priv_path, O_RDONLY); + if (fd < 0) { fprintf(stderr, "meow_sign: open %s fail: %s\n", priv_path, strerror(errno)); return 1; } + uint8_t sk[64]; + size_t off = 0; + while (off < sizeof sk) { + ssize_t r = read(fd, sk + off, sizeof sk - off); + if (r <= 0) { close(fd); fprintf(stderr, "meow_sign: priv key too short\n"); return 1; } + off += (size_t)r; + } + close(fd); + + /* 计算 payload sha256(当前文件无 footer) */ + uint8_t sha[MEOW_SIG_SHA256_LEN]; + if (file_sha256(mpk, sha) != 0) { fprintf(stderr, "meow_sign: read %s fail\n", mpk); return 1; } + + /* 组被签消息 + 签出 64B 分离签名 */ + char msg[MEOW_SIG_MSG_MAX]; + unsigned mlen = meow_sig_msg_build(msg, pkg, sha); + uint8_t sm[MEOW_SIG_SIG_LEN + MEOW_SIG_MSG_MAX]; + unsigned long long smlen = 0; + crypto_sign(sm, &smlen, (const unsigned char *)msg, mlen, sk); + if (smlen != mlen + MEOW_SIG_SIG_LEN) { fprintf(stderr, "meow_sign: sign len mismatch\n"); return 1; } + + /* 构造 footer 并尾部追加 */ + meow_sig_block_t blk; + memset(&blk, 0, sizeof blk); + memcpy(blk.magic, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN); + blk.alg = MEOW_SIG_ALG_ED25519; + blk.flags = 0; + blk.pad = 0; + snprintf(blk.package, sizeof blk.package, "%s", pkg); + memcpy(blk.payload_sha256, sha, MEOW_SIG_SHA256_LEN); + memcpy(blk.sig, sm, MEOW_SIG_SIG_LEN); + blk.msg_len = mlen; + blk.block_len = MEOW_SIG_BLOCK_LEN; + + int afd = open(mpk, O_WRONLY | O_APPEND); + if (afd < 0) { fprintf(stderr, "meow_sign: append %s fail: %s\n", mpk, strerror(errno)); return 1; } + ssize_t w = write(afd, &blk, MEOW_SIG_BLOCK_LEN); + close(afd); + if (w != MEOW_SIG_BLOCK_LEN) { fprintf(stderr, "meow_sign: footer write fail\n"); return 1; } + + printf("sign ok: %s package=%s msg_len=%u block_len=%u payload_sha256=", + mpk, pkg, mlen, (unsigned)blk.block_len); + hex_print("", sha, 32); + return 0; +} + +static void usage(const char *a0) +{ + fprintf(stderr, + "usage:\n" + " %s keygen 生成密钥对(priv 0600 + .pub,明文、不破坏现有)\n" + " %s sign .mpk 对 .mpk 尾部追加 Ed25519 签名 footer\n", a0, a0); +} + +int main(int argc, char **argv) +{ + if (argc < 2) { usage(argv[0]); return 2; } + if (!strcmp(argv[1], "keygen")) { + if (argc < 3) { usage(argv[0]); return 2; } + return keygen(argv[2]); + } + if (!strcmp(argv[1], "sign")) { + if (argc < 4) { usage(argv[0]); return 2; } + return sign(argv[2], argv[3]); + } + usage(argv[0]); + return 2; +} diff --git a/patch_v2/meow_svc_power/meow_svc_power b/patch_v2/meow_svc_power/meow_svc_power index bc576ab..27457ca 100755 Binary files a/patch_v2/meow_svc_power/meow_svc_power and b/patch_v2/meow_svc_power/meow_svc_power differ diff --git a/patch_v2/meow_svc_power/meow_svc_power.c b/patch_v2/meow_svc_power/meow_svc_power.c index b4f20d8..0a4a586 100644 --- a/patch_v2/meow_svc_power/meow_svc_power.c +++ b/patch_v2/meow_svc_power/meow_svc_power.c @@ -10,8 +10,12 @@ * 动作逻辑与 launcher `power_menu.c` 的 `sys_do_reboot`/`set_boot_default_hiby`/`bootmode.sh Recovery` * **同源 0-diff 搬移**,仅入口(CLI vs IPC)与鉴权层不同。 * - * 鉴权(一期基础校验):IPC 服务端受理 MSG_POWER_ACTION 前以 `SO_PEERCRED` 校验调用方 `uid==0`(root) - * ——非 root 直接 `MEOW_ERR_PERM` 拒绝;完整 `os.meow.*` 包名鉴权(§5)留二期。CLI=root 免鉴权。 + * 鉴权(II 期,MPK_SIGNATURE_DESIGN §6):IPC 服务端按【连接级身份】分级——`SO_PEERCRED` 取 pid → + * `readlink /proc/pid/exe` → 命中 `MeowAPPs//` 判定包名,或命中系统二进制 allowlist + * (/usr/bin/meow_*、/usr/data/meow/meow*、MeowPanel 后端)判定为系统身份;其余为 OTHER。 + * 动作级策略:系统动作(shutdown/reboot/upgrade/back_hiby/set-default-*)仅放行 `os.meow.*` 包或系统身份; + * 读/状态类(status/battery/reboot_reason/upgrade_status/screen/dac)放行所有 root 调用方;非 root 一律拒。 + * `MSG_HELLO` 自报 app_id 不在此作权威(仅交叉校验,见 §6.1)。CLI=root 免鉴权。 * * 通知转储归属(§3.2/§3.3 已定案):通知存储为 launcher 进程内存(meow_notif.c g_slots[]),服务跨进程 * 不可达——**转储由调用方 launcher 先做**,服务对关机/重启/升级一律 `sync()` 落盘,不重实现转储。 @@ -593,15 +597,99 @@ static int send_power_status(int fd, uint32_t req_id, const m_power_status_t *st return send_frame(fd, MSG_POWER_STATUS, req_id, &s, sizeof s); } +/* ---------------- II 期:调用方身份解析(连接级,MPK_SIGNATURE_DESIGN §6.2) ---------------- */ +typedef enum { CALL_OTHER, CALL_PKG, CALL_SYS } caller_kind_t; +typedef struct { + caller_kind_t kind; + char pkg[MEOW_APP_ID_MAX]; /* CALL_PKG=MeowAPPs// 取包名;CALL_SYS=系统身份别名 */ + uid_t uid; +} caller_t; + +/* 系统动作(SYSTEM_FRAMEWORK_SERVICES §2.4 + 设计 §6.3 表):须 os.meow.* 包或系统身份。 */ +static int is_system_action(uint32_t cmd) +{ + switch (cmd) { + case POWER_SHUTDOWN: case POWER_REBOOT: case POWER_UPGRADE: + case POWER_BACK_HIBY: case POWER_SET_DEFAULT_MEOW: case POWER_SET_DEFAULT_HIBY: + return 1; + default: + return 0; + } +} + +/* 是否可信系统身份:CALL_SYS(受信系统二进制)或 CALL_PKG 且包前缀 os.meow.*。 */ +static int is_sys_identity(const caller_t *c) +{ + if (c->kind == CALL_SYS) return 1; + if (c->kind == CALL_PKG && !strncmp(c->pkg, "os.meow.", 8)) return 1; + return 0; +} + +/* 由 exe 路径判身份(纯函数,便于宿主自测 + 生产共用)。前提:out 已 memset(kind 缺省 CALL_OTHER)。 + * - exe 前缀 /usr/data/mnt/sd_0/MeowAPPs/ → MeowAPP 包(kind=CALL_PKG,pkg=目录名) + * - 否则命中系统二进制 allowlist → CALL_SYS + * - 其余保持 CALL_OTHER + * msg 自报 app_id 仅作交叉校验(不在此处做权威判定,见设计 §6.1/§6.5)。 */ +static void classify_exe(const char *exe, caller_t *out) +{ + /* MeowAPPs//(与 app_launcher.c meow_pkg_name 同口径:去前缀取第一段目录名) */ + static const char *mp = "/usr/data/mnt/sd_0/MeowAPPs/"; + if (strncmp(exe, mp, strlen(mp)) == 0) { + const char *p = exe + strlen(mp); + if (*p == '/') p++; + const char *end = strchr(p, '/'); + int plen = end ? (int)(end - p) : (int)strlen(p); + if (plen > 0 && plen < MEOW_APP_ID_MAX) { + memcpy(out->pkg, p, (size_t)plen); out->pkg[plen] = 0; + out->kind = CALL_PKG; + return; + } + } + + /* 系统二进制 allowlist(/usr/bin/meow_*、/usr/data/meow_*、MeowPanel 后端) */ + char tmp[MEOW_APP_ID_MAX]; + if (strcmp(exe, "/usr/bin/meow_launcherd") == 0) { + snprintf(tmp, sizeof tmp, "%s", "os.meow.launcher"); + } else if (strstr(exe, "/MeowPanel/") || strstr(exe, "meowpanel")) { + snprintf(tmp, sizeof tmp, "%s", "os.meow.panel"); + } else if (strncmp(exe, "/usr/bin/meow_", 14) == 0) { + /* 截取 "meow_xyz"(去 "/usr/bin/" 前缀)作近似别名 */ + size_t l = strlen(exe + 11); + if (l >= sizeof tmp) l = sizeof tmp - 1; + memcpy(tmp, exe + 11, l); tmp[l] = 0; + } else if (strncmp(exe, "/usr/data/meow/", 15) == 0) { + snprintf(tmp, sizeof tmp, "%s", "os.meow.core"); + } else { + return; /* CALL_OTHER */ + } + snprintf(out->pkg, sizeof out->pkg, "%s", tmp); + out->kind = CALL_SYS; +} + +/* 由连接 SO_PEERCRED 取 pid → readlink /proc//exe → classify_exe(连接创建时缓存,循环内复用)。 */ +static void classify_caller(int fd, caller_t *out) +{ + memset(out, 0, sizeof *out); + out->uid = (uid_t)-1; + struct ucred cred; + socklen_t cl = sizeof cred; + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cl) != 0) return; + out->uid = cred.uid; + + char procpath[64], exe[256]; + snprintf(procpath, sizeof procpath, "/proc/%d/exe", (int)cred.pid); + ssize_t n = readlink(procpath, exe, sizeof exe - 1); + if (n <= 0) return; + exe[n] = 0; + classify_exe(exe, out); +} + /* 处理单条连接(连接内循环读帧:客户端先 MSG_HELLO 握手,再 MSG_POWER_ACTION)。 */ static void handle_conn(int c) { - /* 基础鉴权:调用方 uid==0(root)才受理动作。 */ - struct ucred cred; - socklen_t cl = sizeof cred; - int uid0 = 0; - if (getsockopt(c, SOL_SOCKET, SO_PEERCRED, &cred, &cl) == 0 && cred.uid == 0) - uid0 = 1; + /* II 期鉴权升级(MPK_SIGNATURE_DESIGN §6.2):uid==0 判定升级为连接级身份解析(pid→exe→包名/系统身份)。 */ + caller_t caller; + classify_caller(c, &caller); for (;;) { uint8_t hdr[MEOW_IPC_HDR_LEN]; @@ -629,12 +717,20 @@ static void handle_conn(int c) memset(&a, 0, sizeof a); if (rlen > sizeof a) rlen = sizeof a; memcpy(&a, payload, rlen); - if (!uid0) { + /* 动作级策略(设计 §6.3):非 root 一律拒;系统动作须 os.meow.* 包或受信系统身份; + * 读/状态类(STATUS/BATTERY/REBOOT_REASON/UPGRADE_STATUS/SCREEN/DAC)放行所有 root 调用方。 */ + int allow = 0; + if (caller.uid == 0) { + if (is_system_action(a.cmd)) allow = is_sys_identity(&caller); + else allow = 1; + } + if (!allow) { m_power_status_t st; memset(&st, 0, sizeof st); st.code = MEOW_ERR_PERM; st.cmd = a.cmd; - snprintf(st.msg, sizeof st.msg, "permission denied (uid!=0)"); + snprintf(st.msg, sizeof st.msg, "permission denied (caller kind=%d uid=%d)", + (int)caller.kind, (int)caller.uid); send_power_status(c, req_id, &st); continue; } diff --git a/patch_v2/meow_svc_power/meow_svc_power_host b/patch_v2/meow_svc_power/meow_svc_power_host index 9148c36..e47498f 100755 Binary files a/patch_v2/meow_svc_power/meow_svc_power_host and b/patch_v2/meow_svc_power/meow_svc_power_host differ diff --git a/patch_v2/meow_svc_power/svc_power_authtest.c b/patch_v2/meow_svc_power/svc_power_authtest.c new file mode 100644 index 0000000..ed4b96d --- /dev/null +++ b/patch_v2/meow_svc_power/svc_power_authtest.c @@ -0,0 +1,93 @@ +/* svc_power_authtest.c — meow_svc_power II 期 IPC 鉴权逻辑 宿主自测(本程序非生产代码)。 + * + * 通过 #include 生产源文件(meow_svc_power.c,用 #define main 改名避免入口冲突),在同一 TU 内直接 + * 访问其 FILE-STATIC 函数 classify_exe / is_system_action / is_sys_identity,验证鉴别/分级策略的决策表。 + * 覆盖「真机需特权路径 /proc/pid/exe 判定」之外的所有纯逻辑分支;真实 pid→exe 解析留真机验证。 + * + * 用途:dev docs 复现级宿主自测;不进入固件/不部署。用法: + * cc -Wall -Wextra -I../meow_launcherd svc_power_authtest.c -o svc_power_authtest && ./svc_power_authtest + */ +#define main meow_svc_power_prod_main_unused +#include "meow_svc_power.c" +#undef main + +#include +#include +#include + +static int g_fail = 0; +#define CHECK(cond) do { if (!(cond)) { printf(" FAIL: %s (line %d)\n", #cond, __LINE__); g_fail++; } \ + else { printf(" ok : %s\n", #cond); } } while (0) + +int main(void) +{ + caller_t c; + + printf("== classify_exe ==\n"); + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/os.meow.tst/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "os.meow.tst") == 0); + + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/com.example.tool/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "com.example.tool") == 0); + + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/os.meow.tst/sub/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "os.meow.tst") == 0); /* 取首段目录 */ + + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/meow_launcherd", &c); + CHECK(c.kind == CALL_SYS && strcmp(c.pkg, "os.meow.launcher") == 0); + + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/meow_svc_power", &c); + CHECK(c.kind == CALL_SYS); /* /usr/bin/meow_* 前缀批准 */ + + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/backend/meowpanel", &c); + CHECK(c.kind == CALL_SYS && strcmp(c.pkg, "os.meow.panel") == 0); + + memset(&c, 0, sizeof c); + classify_exe("/usr/data/meow/settings_tool", &c); + CHECK(c.kind == CALL_SYS); /* /usr/data/meow/ 批准 */ + + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/bash", &c); + CHECK(c.kind == CALL_OTHER); + memset(&c, 0, sizeof c); + classify_exe("/bin/sh", &c); + CHECK(c.kind == CALL_OTHER); + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/justadirty/k", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "justadirty") == 0); /* 第三方包(非 os.meow.*) */ + CHECK(is_sys_identity(&c) == 0); /* 非系统身份 → 系统动作被拒 */ + + printf("== is_sys_identity ==\n"); + c.kind = CALL_PKG; snprintf(c.pkg, sizeof c.pkg, "%s", "os.meow.tst"); + CHECK(is_sys_identity(&c) == 1); + c.kind = CALL_PKG; snprintf(c.pkg, sizeof c.pkg, "%s", "com.example.tool"); + CHECK(is_sys_identity(&c) == 0); + c.kind = CALL_SYS; snprintf(c.pkg, sizeof c.pkg, "%s", "os.meow.launcher"); + CHECK(is_sys_identity(&c) == 1); + c.kind = CALL_OTHER; c.pkg[0] = 0; + CHECK(is_sys_identity(&c) == 0); + + printf("== is_system_action ==\n"); + CHECK(is_system_action(POWER_SHUTDOWN) == 1); + CHECK(is_system_action(POWER_REBOOT) == 1); + CHECK(is_system_action(POWER_UPGRADE) == 1); + CHECK(is_system_action(POWER_BACK_HIBY) == 1); + CHECK(is_system_action(POWER_SET_DEFAULT_MEOW) == 1); + CHECK(is_system_action(POWER_SET_DEFAULT_HIBY) == 1); + CHECK(is_system_action(POWER_STATUS) == 0); + CHECK(is_system_action(POWER_BATTERY) == 0); + CHECK(is_system_action(POWER_REBOOT_REASON) == 0); + CHECK(is_system_action(POWER_UPGRADE_STATUS) == 0); + CHECK(is_system_action(POWER_SCREEN) == 0); + CHECK(is_system_action(POWER_DAC) == 0); + + if (g_fail) { printf("\nAUTH TEST: %d FAIL\n", g_fail); return 1; } + printf("\nAUTH TEST: ALL PASS\n"); + return 0; +} diff --git a/patch_v2/meow_verify/build_verify.sh b/patch_v2/meow_verify/build_verify.sh new file mode 100755 index 0000000..9b154dd --- /dev/null +++ b/patch_v2/meow_verify/build_verify.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# build_verify.sh — meow_verify 交叉编译(mipsel 静态,固件 /usr/bin/meow_verify)+ 宿主编译可选。 +# +# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。纯 POSIX+Linux(无外部依赖,除 vendor +# tweetnacl.c/sha256.c 全内编)。公钥编译期固化:meow_signing_pub.h(由 meow_sign keygen 生成)。 +# +# 警告策略(诚实标注):本项目自有代码为 0 warning; +# - meow_verify.c / sha256.c / meow_sig.h → -Wall -Wextra(0 warning) +# - tweetnacl.c(第三方 public-domain vendor 单文件)→ 单独对象 + -Wno-sign-compare +# -Wno-unterminated-string-initialization -Wno-unused-function(作者原写法:u64/u32 与 int 混比、 +# "expand 32-byte k" 16B char 数组塞 17 字节。非本项目代码,局部放宽)。 +# - 不启用 gc-sections:meow_verify.c 的 randombytes() stub 满足 crypto_sign_keypair 的引用,整链静态。 +# +# 用法: +# ./build_verify.sh # 交叉编译 → patch_v2/meow_verify/meow_verify(mipsel 静态) +# ./build_verify.sh host # 宿主编译 → patch_v2/meow_verify/meow_verify_host +set -e +cd "$(dirname "$0")" +ROOT="$HOME/Project/R3ProII" +CC="$ROOT/.tools/zig-mipsel-cc" +TNFLAGS="-Os -Wno-sign-compare -Wno-unterminated-string-initialization -Wno-unused-function -static" + +if [ "$1" = "host" ]; then + HOST_CC="${CC_HOST:-cc}" + "$HOST_CC" -Os -Wno-sign-compare -Wno-unterminated-string-initialization -Wno-unused-function -c tweetnacl.c -o tweetnacl_v.o + "$HOST_CC" -Os -Wall -Wextra -c sha256.c -o sha256_v.o + "$HOST_CC" -Os -Wall -Wextra -c meow_verify.c -o meow_verify_v.o + "$HOST_CC" meow_verify_v.o sha256_v.o tweetnacl_v.o -o meow_verify_host + rm -f tweetnacl_v.o sha256_v.o meow_verify_v.o + echo "== meow_verify (host) ==" + ls -la meow_verify_host; md5sum meow_verify_host; file meow_verify_host + exit 0 +fi + +"$CC" $TNFLAGS -c tweetnacl.c -o tweetnacl_v.o +"$CC" -Os -Wall -Wextra -static -c sha256.c -o sha256_v.o +"$CC" -Os -Wall -Wextra -static -c meow_verify.c -o meow_verify_v.o +"$CC" -Os -static meow_verify_v.o sha256_v.o tweetnacl_v.o -o meow_verify +rm -f tweetnacl_v.o sha256_v.o meow_verify_v.o +echo "== meow_verify (mipsel static) ==" +ls -la meow_verify +md5sum meow_verify +file meow_verify diff --git a/patch_v2/meow_verify/meow_sig.h b/patch_v2/meow_verify/meow_sig.h new file mode 100644 index 0000000..e094de3 --- /dev/null +++ b/patch_v2/meow_verify/meow_sig.h @@ -0,0 +1,77 @@ +/* meow_sig.h — .mpk 签名块格式(MPK_SIGNATURE_DESIGN §3.1)+ 被签消息构造(§3.1)。 + * + * .mpk = [ tar.gz bytes ......................... ] [ meow_sig_block(定长 LE)] + * + * meow_sig_block(packed,总长 MEOW_SIG_BLOCK_LEN): + * char magic[8]; "MEOWSIG1" + * uint8_t alg; 1 = ed25519 + * uint8_t flags; 0 + * uint16_t pad; (0) + * char package[64]; 权威包名(== app.json.package == 文件名茎) + * uint8_t payload_sha256[32];sha256(整个 .mpk 去掉尾部 footer 的其余字节) + * uint8_t sig[64]; Ed25519(pk, msg) + * uint32_t msg_len; 实际签名的 msg 字节数(LE) + * uint32_t block_len; 本块总长(LE;= MEOW_SIG_BLOCK_LEN) + * + * 被签消息 msg = "MEOWSIG1" || alg(1B) || package_len(4B,LE) || package || payload_sha256(32B)。 + * 本文件由 meow_verify 与 meow_sign 共用(meow_sign 用相对路径 include)。 */ +#ifndef MEOW_SIG_H +#define MEOW_SIG_H + +#include +#include + +#define MEOW_SIG_MAGIC "MEOWSIG1" +#define MEOW_SIG_MAGIC_LEN 8 +#define MEOW_SIG_ALG_ED25519 1 +#define MEOW_SIG_PKG_MAX 64 +#define MEOW_SIG_SHA256_LEN 32 +#define MEOW_SIG_SIG_LEN 64 + +typedef struct __attribute__((packed)) { + uint8_t magic[MEOW_SIG_MAGIC_LEN]; + uint8_t alg; + uint8_t flags; + uint16_t pad; + char package[MEOW_SIG_PKG_MAX]; + uint8_t payload_sha256[MEOW_SIG_SHA256_LEN]; + uint8_t sig[MEOW_SIG_SIG_LEN]; + uint32_t msg_len; + uint32_t block_len; +} meow_sig_block_t; + +#define MEOW_SIG_BLOCK_LEN (sizeof(meow_sig_block_t)) /* 定长 180 */ + +/* 按字节读取 LE 的 uint32(避免 packed 解引用对齐告警) */ +static inline uint32_t meow_le32(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} +static inline void meow_le32_put(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v & 0xff); + p[1] = (uint8_t)((v >> 8) & 0xff); + p[2] = (uint8_t)((v >> 16) & 0xff); + p[3] = (uint8_t)((v >> 24) & 0xff); +} + +#define MEOW_SIG_MSG_MAX (MEOW_SIG_MAGIC_LEN + 1 + 4 + MEOW_SIG_PKG_MAX + MEOW_SIG_SHA256_LEN) + +/* 按设计构造被签消息 msg = "MEOWSIG1" || alg(1B) || package_len(4B,LE) || package || payload_sha256(32B)。 + * 返回 msg 字节数。签名端与验签端必须用同一构造(meow_sign / meow_verify 共用)。 */ +static inline unsigned meow_sig_msg_build(char *out, const char *package, + const uint8_t payload_sha256[MEOW_SIG_SHA256_LEN]) +{ + unsigned pkg_len = (unsigned)strlen(package); + if (pkg_len > MEOW_SIG_PKG_MAX) pkg_len = MEOW_SIG_PKG_MAX; + unsigned o = 0; + memcpy(out + o, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN); o += MEOW_SIG_MAGIC_LEN; + out[o++] = MEOW_SIG_ALG_ED25519; + meow_le32_put((uint8_t *)out + o, (uint32_t)pkg_len); o += 4; + memcpy(out + o, package, pkg_len); o += pkg_len; + memcpy(out + o, payload_sha256, MEOW_SIG_SHA256_LEN); o += MEOW_SIG_SHA256_LEN; + return o; +} + +#endif /* MEOW_SIG_H */ diff --git a/patch_v2/meow_verify/meow_signing_pub.h b/patch_v2/meow_verify/meow_signing_pub.h new file mode 100644 index 0000000..3ce23ec --- /dev/null +++ b/patch_v2/meow_verify/meow_signing_pub.h @@ -0,0 +1,17 @@ +/* meow_signing_pub.h — 固件公钥(Ed25519 32B,编译期固化进 meow_verify)。 + * 由 `meow_sign keygen` 生成(.keys/signing.key.pub),本头只含【公钥】,可入库; + * 私钥仅在 .keys/(已加入 .gitignore,绝不入库/报告)。轮换公钥=重生成本头+重编 meow_verify。 */ +#ifndef MEOW_SIGNING_PUB_H +#define MEOW_SIGNING_PUB_H + +#include + +static const uint8_t g_meow_pub[32] = +{ + 0x22, 0xa7, 0x83, 0xac, 0x28, 0xd3, 0x25, 0x6d, + 0x3f, 0x84, 0x72, 0x72, 0x50, 0x5b, 0x2c, 0x61, + 0x60, 0x97, 0xff, 0xc3, 0x98, 0x78, 0x14, 0x9d, + 0xc7, 0x37, 0x8c, 0x58, 0x72, 0x26, 0x37, 0xf2 +}; + +#endif /* MEOW_SIGNING_PUB_H */ diff --git a/patch_v2/meow_verify/meow_verify b/patch_v2/meow_verify/meow_verify new file mode 100755 index 0000000..2dcc470 Binary files /dev/null and b/patch_v2/meow_verify/meow_verify differ diff --git a/patch_v2/meow_verify/meow_verify.c b/patch_v2/meow_verify/meow_verify.c new file mode 100644 index 0000000..f5dd32a --- /dev/null +++ b/patch_v2/meow_verify/meow_verify.c @@ -0,0 +1,215 @@ +/* meow_verify.c — MeowOS .mpk 签名校验器(固件 /usr/bin/meow_verify,静态 mipsel)。 + * + * 功能(MPK_SIGNATURE_DESIGN §5.1): + * - Ed25519 验签(TweetNaCl crypto_sign_open,公钥编译期固化进 meow_signing_pub.h); + * - payload_sha256 = SHA-256(.mpk 去掉尾部 footer 的其余字节,即 tar.gz 前缀,分块读、无需解包); + * - 包名绑定①:footer.package == 期望包名(--package 或文件名茎); + * - 被签消息 msg = "MEOWSIG1" || alg(1B) || package_len(4B,LE) || package || payload_sha256(32B)。 + * 绑定②(app.json.package)与③(os.meow.* 前缀)由调用方(nyai / launcher)在安装流里执行, + * 本器只保证「有签名就必须验签通过」。见 MPK_SIGNATURE_DESIGN §3.3(分工诚实标注)。 + * + * 用法: + * 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=有签但不通过(篡改/包名不符/公钥不符/格式错);2=无签名 footer;3=用法/IO 错误。 + */ +#include +#include +#include +#include +#include +#include + +#include "tweetnacl.h" +#include "sha256.h" +#include "meow_sig.h" + +/* 固件公钥(编译期固化,32B Ed25519)。由 meow_sign keygen 生成后写死于此头。 */ +#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; +} + +/* 读文件尾部 MEOW_SIG_BLOCK_LEN 字节作为 footer。返回 0 成功,-2 文件太短(无签名空间),-1 IO 错。 */ +static int read_footer(const char *path, long long fsize, meow_sig_block_t *blk) +{ + if (fsize < (long long)MEOW_SIG_BLOCK_LEN) return -2; + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + if (lseek(fd, fsize - (long long)MEOW_SIG_BLOCK_LEN, SEEK_SET) < 0) { close(fd); return -1; } + ssize_t n = read(fd, blk, MEOW_SIG_BLOCK_LEN); + close(fd); + if (n != (ssize_t)MEOW_SIG_BLOCK_LEN) return -1; + return 0; +} + +/* 分块 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) return 2; /* 无签名空间 */ + + meow_sig_block_t blk; + if (read_footer(mpk, fsize, &blk) != 0) return 3; + if (memcmp(blk.magic, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) != 0) return 2; /* 无 MEOWSIG1 footer */ + if (blk.alg != MEOW_SIG_ALG_ED25519 || blk.block_len != MEOW_SIG_BLOCK_LEN) return 1; /* 格式错 */ + + char pkgbuf[MEOW_SIG_PKG_MAX]; + if (pkg && *pkg) { + snprintf(pkgbuf, sizeof pkgbuf, "%s", pkg); + } else { + if (derive_pkg(mpk, pkgbuf, sizeof pkgbuf) != 0) return 3; + } + blk.package[MEOW_SIG_PKG_MAX - 1] = 0; /* 保证 NUL 终止 */ + /* 绑定①:期望包名 == footer.package */ + if (strcmp(pkgbuf, blk.package) != 0) { + fprintf(stderr, "meow_verify: package mismatch (want %s, sig %s)\n", pkgbuf, blk.package); + return 1; + } + + /* payload 哈希 */ + long long plen = fsize - (long long)MEOW_SIG_BLOCK_LEN; + uint8_t sha[MEOW_SIG_SHA256_LEN]; + if (sha256_prefix(mpk, plen, sha) != 0) return 3; + if (memcmp(sha, blk.payload_sha256, MEOW_SIG_SHA256_LEN) != 0) { + fprintf(stderr, "meow_verify: payload sha256 mismatch (content tampered)\n"); + return 1; + } + + /* 重组被签消息 + 验签 */ + char msg[MEOW_SIG_MSG_MAX]; + unsigned mlen = meow_sig_msg_build(msg, blk.package, blk.payload_sha256); + if (blk.msg_len != 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, blk.sig, MEOW_SIG_SIG_LEN); + memcpy(sm + MEOW_SIG_SIG_LEN, msg, mlen); + uint8_t out[MEOW_SIG_SIG_LEN + MEOW_SIG_MSG_MAX]; + unsigned long long outlen = 0; + if (crypto_sign_open(out, &outlen, sm, (unsigned long long)(MEOW_SIG_SIG_LEN + mlen), g_meow_pub) != 0) { + fprintf(stderr, "meow_verify: ed25519 signature invalid\n"); + return 1; + } + + printf("%s\n", blk.package); + 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) { + meow_sig_block_t blk; + if (read_footer(mpk, fsize, &blk) == 0 && + memcmp(blk.magic, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) == 0) { + payload = fsize - (long long)MEOW_SIG_BLOCK_LEN; + 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) { printf("no\n"); return 2; } + meow_sig_block_t blk; + if (read_footer(mpk, fsize, &blk) != 0) { printf("no\n"); return 2; } + int h = (memcmp(blk.magic, 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); +} diff --git a/patch_v2/meow_verify/meow_verify_host b/patch_v2/meow_verify/meow_verify_host new file mode 100755 index 0000000..b6e23e4 Binary files /dev/null and b/patch_v2/meow_verify/meow_verify_host differ diff --git a/patch_v2/meow_verify/sha256.c b/patch_v2/meow_verify/sha256.c new file mode 100644 index 0000000..b6d9e9c --- /dev/null +++ b/patch_v2/meow_verify/sha256.c @@ -0,0 +1,97 @@ +/* sha256.c — 紧凑 SHA-256(FIPS 180-4),公开领域参考写法,-Wall -Wextra 0 warning。 + * 流式 context;meow_verify / meow_sign 共用。 */ +#include "sha256.h" +#include + +static const uint32_t K[64] = { + 0x428a2f98u,0x71374491u,0xb5c0fbcfu,0xe9b5dba5u,0x3956c25bu,0x59f111f1u,0x923f82a4u,0xab1c5ed5u, + 0xd807aa98u,0x12835b01u,0x243185beu,0x550c7dc3u,0x72be5d74u,0x80deb1feu,0x9bdc06a7u,0xc19bf174u, + 0xe49b69c1u,0xefbe4786u,0x0fc19dc6u,0x240ca1ccu,0x2de92c6fu,0x4a7484aau,0x5cb0a9dcu,0x76f988dau, + 0x983e5152u,0xa831c66du,0xb00327c8u,0xbf597fc7u,0xc6e00bf3u,0xd5a79147u,0x06ca6351u,0x14292967u, + 0x27b70a85u,0x2e1b2138u,0x4d2c6dfcu,0x53380d13u,0x650a7354u,0x766a0abbu,0x81c2c92eu,0x92722c85u, + 0xa2bfe8a1u,0xa81a664bu,0xc24b8b70u,0xc76c51a3u,0xd192e819u,0xd6990624u,0xf40e3585u,0x106aa070u, + 0x19a4c116u,0x1e376c08u,0x2748774cu,0x34b0bcb5u,0x391c0cb3u,0x4ed8aa4au,0x5b9cca4fu,0x682e6ff3u, + 0x748f82eeu,0x78a5636fu,0x84c87814u,0x8cc70208u,0x90befffau,0xa4506cebu,0xbef9a3f7u,0xc67178f2u +}; + +#define ROR(x,n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define BSIG0(x) (ROR(x,2) ^ ROR(x,13) ^ ROR(x,22)) +#define BSIG1(x) (ROR(x,6) ^ ROR(x,11) ^ ROR(x,25)) +#define SSIG0(x) (ROR(x,7) ^ ROR(x,18) ^ ((x) >> 3)) +#define SSIG1(x) (ROR(x,17) ^ ROR(x,19) ^ ((x) >> 10)) + +void meow_sha256_init(meow_sha256_ctx *c) +{ + c->state[0] = 0x6a09e667u; c->state[1] = 0xbb67ae85u; + c->state[2] = 0x3c6ef372u; c->state[3] = 0xa54ff53au; + c->state[4] = 0x510e527fu; c->state[5] = 0x9b05688cu; + c->state[6] = 0x1f83d9abu; c->state[7] = 0x5be0cd19u; + c->count = 0; c->buflen = 0; +} + +static void sha256_block(meow_sha256_ctx *c, const uint8_t *p) +{ + uint32_t w[64]; + int i; + for (i = 0; i < 16; i++) { + w[i] = ((uint32_t)p[i*4] << 24) | ((uint32_t)p[i*4+1] << 16) | + ((uint32_t)p[i*4+2] << 8) | (uint32_t)p[i*4+3]; + } + for (i = 16; i < 64; i++) + w[i] = SSIG1(w[i-2]) + w[i-7] + SSIG0(w[i-15]) + w[i-16]; + + uint32_t a = c->state[0], b = c->state[1], cc = c->state[2], d = c->state[3]; + uint32_t e = c->state[4], f = c->state[5], g = c->state[6], h = c->state[7]; + + for (i = 0; i < 64; i++) { + uint32_t t1 = h + BSIG1(e) + CH(e,f,g) + K[i] + w[i]; + uint32_t t2 = BSIG0(a) + MAJ(a,b,cc); + h = g; g = f; f = e; e = d + t1; + d = cc; cc = b; b = a; a = t1 + t2; + } + c->state[0] += a; c->state[1] += b; c->state[2] += cc; c->state[3] += d; + c->state[4] += e; c->state[5] += f; c->state[6] += g; c->state[7] += h; +} + +void meow_sha256_update(meow_sha256_ctx *c, const uint8_t *data, size_t len) +{ + c->count += (uint64_t)len; + /* 先补满残余块 */ + if (c->buflen) { + size_t need = 64 - c->buflen; + size_t take = len < need ? len : need; + memcpy(c->buf + c->buflen, data, take); + c->buflen += take; data += take; len -= take; + if (c->buflen == 64) { sha256_block(c, c->buf); c->buflen = 0; } + } + while (len >= 64) { sha256_block(c, data); data += 64; len -= 64; } + if (len) { memcpy(c->buf, data, len); c->buflen = len; } +} + +void meow_sha256_final(meow_sha256_ctx *c, uint8_t out[32]) +{ + uint64_t bitlen = c->count * 8; + uint8_t *b = c->buf; + size_t bl = c->buflen; + int i; + + b[bl++] = 0x80; /* 填充:1 位 1 */ + if (bl > 56) { /* 一块放不下长度 → 补齐到 64 后压一块 */ + while (bl < 64) b[bl++] = 0; + sha256_block(c, b); + bl = 0; + } + while (bl < 56) b[bl++] = 0; /* 补到 56 */ + for (i = 0; i < 8; i++) /* 大端 64 位 bit 长度 */ + b[bl++] = (uint8_t)(bitlen >> (56 - i * 8)); + sha256_block(c, b); + + for (i = 0; i < 8; i++) { + out[i*4] = (uint8_t)(c->state[i] >> 24); + out[i*4+1] = (uint8_t)(c->state[i] >> 16); + out[i*4+2] = (uint8_t)(c->state[i] >> 8); + out[i*4+3] = (uint8_t)(c->state[i]); + } +} diff --git a/patch_v2/meow_verify/sha256.h b/patch_v2/meow_verify/sha256.h new file mode 100644 index 0000000..2ba51bd --- /dev/null +++ b/patch_v2/meow_verify/sha256.h @@ -0,0 +1,21 @@ +/* sha256.h — minimal public-domain SHA-256 context/API for MeowOS .mpk payload hashing. + * Used by meow_verify (device) and meow_sign (host) to compute .mpk 的 payload_sha256 + * (tar.gz 前缀哈希)。流式 API,避免整包载入内存。 */ +#ifndef MEOW_SHA256_H +#define MEOW_SHA256_H + +#include +#include + +typedef struct { + uint32_t state[8]; + uint64_t count; /* 已输入总字节数 */ + uint8_t buf[64]; /* 未满一块的缓冲 */ + size_t buflen; +} meow_sha256_ctx; + +void meow_sha256_init(meow_sha256_ctx *c); +void meow_sha256_update(meow_sha256_ctx *c, const uint8_t *data, size_t len); +void meow_sha256_final(meow_sha256_ctx *c, uint8_t out[32]); + +#endif /* MEOW_SHA256_H */ diff --git a/patch_v2/meow_verify/tweetnacl.c b/patch_v2/meow_verify/tweetnacl.c new file mode 100644 index 0000000..1a6099f --- /dev/null +++ b/patch_v2/meow_verify/tweetnacl.c @@ -0,0 +1,816 @@ +/* + * TweetNaCl — 公有领域(public domain)加密库,作者 Bernard van Gastel 等 + * 原始来源:https://tweetnacl.cr.yp.to/20140427/tweetnacl.c (20140427) + * 本文件为原样 vendor(仅去掉尾部空行),供 MeowOS meow_verify/meow_sign 使用 + * Ed25519(crypto_sign_*)与 SHA-512(crypto_hash)子集。 + * 编译注意:与本项目其余代码不同,本文件用 `-Wno-sign-compare` 等放宽(vendor 代码), + * 见 build_verify.sh / build_sign.sh。 */ +#include "tweetnacl.h" +#define FOR(i,n) for (i = 0;i < n;++i) +#define sv static void + +typedef unsigned char u8; +typedef unsigned long u32; +typedef unsigned long long u64; +typedef long long i64; +typedef i64 gf[16]; +extern void randombytes(u8 *,u64); + +static const u8 + _0[16], + _9[32] = {9}; +static const gf + gf0, + gf1 = {1}, + _121665 = {0xDB41,1}, + D = {0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203}, + D2 = {0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406}, + X = {0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169}, + Y = {0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666}, + I = {0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83}; + +static u32 L32(u32 x,int c) { return (x << c) | ((x&0xffffffff) >> (32 - c)); } + +static u32 ld32(const u8 *x) +{ + u32 u = x[3]; + u = (u<<8)|x[2]; + u = (u<<8)|x[1]; + return (u<<8)|x[0]; +} + +static u64 dl64(const u8 *x) +{ + u64 i,u=0; + FOR(i,8) u=(u<<8)|x[i]; + return u; +} + +sv st32(u8 *x,u32 u) +{ + int i; + FOR(i,4) { x[i] = u; u >>= 8; } +} + +sv ts64(u8 *x,u64 u) +{ + int i; + for (i = 7;i >= 0;--i) { x[i] = u; u >>= 8; } +} + +static int vn(const u8 *x,const u8 *y,int n) +{ + u32 i,d = 0; + FOR(i,n) d |= x[i]^y[i]; + return (1 & ((d - 1) >> 8)) - 1; +} + +int crypto_verify_16(const u8 *x,const u8 *y) +{ + return vn(x,y,16); +} + +int crypto_verify_32(const u8 *x,const u8 *y) +{ + return vn(x,y,32); +} + +sv core(u8 *out,const u8 *in,const u8 *k,const u8 *c,int h) +{ + u32 w[16],x[16],y[16],t[4]; + int i,j,m; + + FOR(i,4) { + x[5*i] = ld32(c+4*i); + x[1+i] = ld32(k+4*i); + x[6+i] = ld32(in+4*i); + x[11+i] = ld32(k+16+4*i); + } + + FOR(i,16) y[i] = x[i]; + + FOR(i,20) { + FOR(j,4) { + FOR(m,4) t[m] = x[(5*j+4*m)%16]; + t[1] ^= L32(t[0]+t[3], 7); + t[2] ^= L32(t[1]+t[0], 9); + t[3] ^= L32(t[2]+t[1],13); + t[0] ^= L32(t[3]+t[2],18); + FOR(m,4) w[4*j+(j+m)%4] = t[m]; + } + FOR(m,16) x[m] = w[m]; + } + + if (h) { + FOR(i,16) x[i] += y[i]; + FOR(i,4) { + x[5*i] -= ld32(c+4*i); + x[6+i] -= ld32(in+4*i); + } + FOR(i,4) { + st32(out+4*i,x[5*i]); + st32(out+16+4*i,x[6+i]); + } + } else + FOR(i,16) st32(out + 4 * i,x[i] + y[i]); +} + +int crypto_core_salsa20(u8 *out,const u8 *in,const u8 *k,const u8 *c) +{ + core(out,in,k,c,0); + return 0; +} + +int crypto_core_hsalsa20(u8 *out,const u8 *in,const u8 *k,const u8 *c) +{ + core(out,in,k,c,1); + return 0; +} + +static const u8 sigma[16] = "expand 32-byte k"; + +int crypto_stream_salsa20_xor(u8 *c,const u8 *m,u64 b,const u8 *n,const u8 *k) +{ + u8 z[16],x[64]; + u32 u,i; + if (!b) return 0; + FOR(i,16) z[i] = 0; + FOR(i,8) z[i] = n[i]; + while (b >= 64) { + crypto_core_salsa20(x,z,k,sigma); + FOR(i,64) c[i] = (m?m[i]:0) ^ x[i]; + u = 1; + for (i = 8;i < 16;++i) { + u += (u32) z[i]; + z[i] = u; + u >>= 8; + } + b -= 64; + c += 64; + if (m) m += 64; + } + if (b) { + crypto_core_salsa20(x,z,k,sigma); + FOR(i,b) c[i] = (m?m[i]:0) ^ x[i]; + } + return 0; +} + +int crypto_stream_salsa20(u8 *c,u64 d,const u8 *n,const u8 *k) +{ + return crypto_stream_salsa20_xor(c,0,d,n,k); +} + +int crypto_stream(u8 *c,u64 d,const u8 *n,const u8 *k) +{ + u8 s[32]; + crypto_core_hsalsa20(s,n,k,sigma); + return crypto_stream_salsa20(c,d,n+16,s); +} + +int crypto_stream_xor(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k) +{ + u8 s[32]; + crypto_core_hsalsa20(s,n,k,sigma); + return crypto_stream_salsa20_xor(c,m,d,n+16,s); +} + +sv add1305(u32 *h,const u32 *c) +{ + u32 j,u = 0; + FOR(j,17) { + u += h[j] + c[j]; + h[j] = u & 255; + u >>= 8; + } +} + +static const u32 minusp[17] = { + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252 +} ; + +int crypto_onetimeauth(u8 *out,const u8 *m,u64 n,const u8 *k) +{ + u32 s,i,j,u,x[17],r[17],h[17],c[17],g[17]; + + FOR(j,17) r[j]=h[j]=0; + FOR(j,16) r[j]=k[j]; + r[3]&=15; + r[4]&=252; + r[7]&=15; + r[8]&=252; + r[11]&=15; + r[12]&=252; + r[15]&=15; + + while (n > 0) { + FOR(j,17) c[j] = 0; + for (j = 0;(j < 16) && (j < n);++j) c[j] = m[j]; + c[j] = 1; + m += j; n -= j; + add1305(h,c); + FOR(i,17) { + x[i] = 0; + FOR(j,17) x[i] += h[j] * ((j <= i) ? r[i - j] : 320 * r[i + 17 - j]); + } + FOR(i,17) h[i] = x[i]; + u = 0; + FOR(j,16) { + u += h[j]; + h[j] = u & 255; + u >>= 8; + } + u += h[16]; h[16] = u & 3; + u = 5 * (u >> 2); + FOR(j,16) { + u += h[j]; + h[j] = u & 255; + u >>= 8; + } + u += h[16]; h[16] = u; + } + + FOR(j,17) g[j] = h[j]; + add1305(h,minusp); + s = -(h[16] >> 7); + FOR(j,17) h[j] ^= s & (g[j] ^ h[j]); + + FOR(j,16) c[j] = k[j + 16]; + c[16] = 0; + add1305(h,c); + FOR(j,16) out[j] = h[j]; + return 0; +} + +int crypto_onetimeauth_verify(const u8 *h,const u8 *m,u64 n,const u8 *k) +{ + u8 x[16]; + crypto_onetimeauth(x,m,n,k); + return crypto_verify_16(h,x); +} + +int crypto_secretbox(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k) +{ + int i; + if (d < 32) return -1; + crypto_stream_xor(c,m,d,n,k); + crypto_onetimeauth(c + 16,c + 32,d - 32,c); + FOR(i,16) c[i] = 0; + return 0; +} + +int crypto_secretbox_open(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *k) +{ + int i; + u8 x[32]; + if (d < 32) return -1; + crypto_stream(x,32,n,k); + if (crypto_onetimeauth_verify(c + 16,c + 32,d - 32,x) != 0) return -1; + crypto_stream_xor(m,c,d,n,k); + FOR(i,32) m[i] = 0; + return 0; +} + +sv set25519(gf r, const gf a) +{ + int i; + FOR(i,16) r[i]=a[i]; +} + +sv car25519(gf o) +{ + int i; + i64 c; + FOR(i,16) { + o[i]+=(1LL<<16); + c=o[i]>>16; + o[(i+1)*(i<15)]+=c-1+37*(c-1)*(i==15); + o[i]-=c<<16; + } +} + +sv sel25519(gf p,gf q,int b) +{ + i64 t,i,c=~(b-1); + FOR(i,16) { + t= c&(p[i]^q[i]); + p[i]^=t; + q[i]^=t; + } +} + +sv pack25519(u8 *o,const gf n) +{ + int i,j,b; + gf m,t; + FOR(i,16) t[i]=n[i]; + car25519(t); + car25519(t); + car25519(t); + FOR(j,2) { + m[0]=t[0]-0xffed; + for(i=1;i<15;i++) { + m[i]=t[i]-0xffff-((m[i-1]>>16)&1); + m[i-1]&=0xffff; + } + m[15]=t[15]-0x7fff-((m[14]>>16)&1); + b=(m[15]>>16)&1; + m[14]&=0xffff; + sel25519(t,m,1-b); + } + FOR(i,16) { + o[2*i]=t[i]&0xff; + o[2*i+1]=t[i]>>8; + } +} + +static int neq25519(const gf a, const gf b) +{ + u8 c[32],d[32]; + pack25519(c,a); + pack25519(d,b); + return crypto_verify_32(c,d); +} + +static u8 par25519(const gf a) +{ + u8 d[32]; + pack25519(d,a); + return d[0]&1; +} + +sv unpack25519(gf o, const u8 *n) +{ + int i; + FOR(i,16) o[i]=n[2*i]+((i64)n[2*i+1]<<8); + o[15]&=0x7fff; +} + +sv A(gf o,const gf a,const gf b) +{ + int i; + FOR(i,16) o[i]=a[i]+b[i]; +} + +sv Z(gf o,const gf a,const gf b) +{ + int i; + FOR(i,16) o[i]=a[i]-b[i]; +} + +sv M(gf o,const gf a,const gf b) +{ + i64 i,j,t[31]; + FOR(i,31) t[i]=0; + FOR(i,16) FOR(j,16) t[i+j]+=a[i]*b[j]; + FOR(i,15) t[i]+=38*t[i+16]; + FOR(i,16) o[i]=t[i]; + car25519(o); + car25519(o); +} + +sv S(gf o,const gf a) +{ + M(o,a,a); +} + +sv inv25519(gf o,const gf i) +{ + gf c; + int a; + FOR(a,16) c[a]=i[a]; + for(a=253;a>=0;a--) { + S(c,c); + if(a!=2&&a!=4) M(c,c,i); + } + FOR(a,16) o[a]=c[a]; +} + +sv pow2523(gf o,const gf i) +{ + gf c; + int a; + FOR(a,16) c[a]=i[a]; + for(a=250;a>=0;a--) { + S(c,c); + if(a!=1) M(c,c,i); + } + FOR(a,16) o[a]=c[a]; +} + +int crypto_scalarmult(u8 *q,const u8 *n,const u8 *p) +{ + u8 z[32]; + i64 x[80],r,i; + gf a,b,c,d,e,f; + FOR(i,31) z[i]=n[i]; + z[31]=(n[31]&127)|64; + z[0]&=248; + unpack25519(x,p); + FOR(i,16) { + b[i]=x[i]; + d[i]=a[i]=c[i]=0; + } + a[0]=d[0]=1; + for(i=254;i>=0;--i) { + r=(z[i>>3]>>(i&7))&1; + sel25519(a,b,r); + sel25519(c,d,r); + A(e,a,c); + Z(a,a,c); + A(c,b,d); + Z(b,b,d); + S(d,e); + S(f,a); + M(a,c,a); + M(c,b,e); + A(e,a,c); + Z(a,a,c); + S(b,a); + Z(c,d,f); + M(a,c,_121665); + A(a,a,d); + M(c,c,a); + M(a,d,f); + M(d,b,x); + S(b,e); + sel25519(a,b,r); + sel25519(c,d,r); + } + FOR(i,16) { + x[i+16]=a[i]; + x[i+32]=c[i]; + x[i+48]=b[i]; + x[i+64]=d[i]; + } + inv25519(x+32,x+32); + M(x+16,x+16,x+32); + pack25519(q,x+16); + return 0; +} + +int crypto_scalarmult_base(u8 *q,const u8 *n) +{ + return crypto_scalarmult(q,n,_9); +} + +int crypto_box_keypair(u8 *y,u8 *x) +{ + randombytes(x,32); + return crypto_scalarmult_base(y,x); +} + +int crypto_box_beforenm(u8 *k,const u8 *y,const u8 *x) +{ + u8 s[32]; + crypto_scalarmult(s,x,y); + return crypto_core_hsalsa20(k,_0,s,sigma); +} + +int crypto_box_afternm(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k) +{ + return crypto_secretbox(c,m,d,n,k); +} + +int crypto_box_open_afternm(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *k) +{ + return crypto_secretbox_open(m,c,d,n,k); +} + +int crypto_box(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *y,const u8 *x) +{ + u8 k[32]; + crypto_box_beforenm(k,y,x); + return crypto_box_afternm(c,m,d,n,k); +} + +int crypto_box_open(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *y,const u8 *x) +{ + u8 k[32]; + crypto_box_beforenm(k,y,x); + return crypto_box_open_afternm(m,c,d,n,k); +} + +static u64 R(u64 x,int c) { return (x >> c) | (x << (64 - c)); } +static u64 Ch(u64 x,u64 y,u64 z) { return (x & y) ^ (~x & z); } +static u64 Maj(u64 x,u64 y,u64 z) { return (x & y) ^ (x & z) ^ (y & z); } +static u64 Sigma0(u64 x) { return R(x,28) ^ R(x,34) ^ R(x,39); } +static u64 Sigma1(u64 x) { return R(x,14) ^ R(x,18) ^ R(x,41); } +static u64 sigma0(u64 x) { return R(x, 1) ^ R(x, 8) ^ (x >> 7); } +static u64 sigma1(u64 x) { return R(x,19) ^ R(x,61) ^ (x >> 6); } + +static const u64 K[80] = +{ + 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL +}; + +int crypto_hashblocks(u8 *x,const u8 *m,u64 n) +{ + u64 z[8],b[8],a[8],w[16],t; + int i,j; + + FOR(i,8) z[i] = a[i] = dl64(x + 8 * i); + + while (n >= 128) { + FOR(i,16) w[i] = dl64(m + 8 * i); + + FOR(i,80) { + FOR(j,8) b[j] = a[j]; + t = a[7] + Sigma1(a[4]) + Ch(a[4],a[5],a[6]) + K[i] + w[i%16]; + b[7] = t + Sigma0(a[0]) + Maj(a[0],a[1],a[2]); + b[3] += t; + FOR(j,8) a[(j+1)%8] = b[j]; + if (i%16 == 15) + FOR(j,16) + w[j] += w[(j+9)%16] + sigma0(w[(j+1)%16]) + sigma1(w[(j+14)%16]); + } + + FOR(i,8) { a[i] += z[i]; z[i] = a[i]; } + + m += 128; + n -= 128; + } + + FOR(i,8) ts64(x+8*i,z[i]); + + return n; +} + +static const u8 iv[64] = { + 0x6a,0x09,0xe6,0x67,0xf3,0xbc,0xc9,0x08, + 0xbb,0x67,0xae,0x85,0x84,0xca,0xa7,0x3b, + 0x3c,0x6e,0xf3,0x72,0xfe,0x94,0xf8,0x2b, + 0xa5,0x4f,0xf5,0x3a,0x5f,0x1d,0x36,0xf1, + 0x51,0x0e,0x52,0x7f,0xad,0xe6,0x82,0xd1, + 0x9b,0x05,0x68,0x8c,0x2b,0x3e,0x6c,0x1f, + 0x1f,0x83,0xd9,0xab,0xfb,0x41,0xbd,0x6b, + 0x5b,0xe0,0xcd,0x19,0x13,0x7e,0x21,0x79 +} ; + +int crypto_hash(u8 *out,const u8 *m,u64 n) +{ + u8 h[64],x[256]; + u64 i,b = n; + + FOR(i,64) h[i] = iv[i]; + + crypto_hashblocks(h,m,n); + m += n; + n &= 127; + m -= n; + + FOR(i,256) x[i] = 0; + FOR(i,n) x[i] = m[i]; + x[n] = 128; + + n = 256-128*(n<112); + x[n-9] = b >> 61; + ts64(x+n-8,b<<3); + crypto_hashblocks(h,x,n); + + FOR(i,64) out[i] = h[i]; + + return 0; +} + +sv add(gf p[4],gf q[4]) +{ + gf a,b,c,d,t,e,f,g,h; + + Z(a, p[1], p[0]); + Z(t, q[1], q[0]); + M(a, a, t); + A(b, p[0], p[1]); + A(t, q[0], q[1]); + M(b, b, t); + M(c, p[3], q[3]); + M(c, c, D2); + M(d, p[2], q[2]); + A(d, d, d); + Z(e, b, a); + Z(f, d, c); + A(g, d, c); + A(h, b, a); + + M(p[0], e, f); + M(p[1], h, g); + M(p[2], g, f); + M(p[3], e, h); +} + +sv cswap(gf p[4],gf q[4],u8 b) +{ + int i; + FOR(i,4) + sel25519(p[i],q[i],b); +} + +sv pack(u8 *r,gf p[4]) +{ + gf tx, ty, zi; + inv25519(zi, p[2]); + M(tx, p[0], zi); + M(ty, p[1], zi); + pack25519(r, ty); + r[31] ^= par25519(tx) << 7; +} + +sv scalarmult(gf p[4],gf q[4],const u8 *s) +{ + int i; + set25519(p[0],gf0); + set25519(p[1],gf1); + set25519(p[2],gf1); + set25519(p[3],gf0); + for (i = 255;i >= 0;--i) { + u8 b = (s[i/8]>>(i&7))&1; + cswap(p,q,b); + add(q,p); + add(p,p); + cswap(p,q,b); + } +} + +sv scalarbase(gf p[4],const u8 *s) +{ + gf q[4]; + set25519(q[0],X); + set25519(q[1],Y); + set25519(q[2],gf1); + M(q[3],X,Y); + scalarmult(p,q,s); +} + +int crypto_sign_keypair(u8 *pk, u8 *sk) +{ + u8 d[64]; + gf p[4]; + int i; + + randombytes(sk, 32); + crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + scalarbase(p,d); + pack(pk,p); + + FOR(i,32) sk[32 + i] = pk[i]; + return 0; +} + +static const u64 L[32] = {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10}; + +sv modL(u8 *r,i64 x[64]) +{ + i64 carry,i,j; + for (i = 63;i >= 32;--i) { + carry = 0; + for (j = i - 32;j < i - 12;++j) { + x[j] += carry - 16 * x[i] * L[j - (i - 32)]; + carry = (x[j] + 128) >> 8; + x[j] -= carry << 8; + } + x[j] += carry; + x[i] = 0; + } + carry = 0; + FOR(j,32) { + x[j] += carry - (x[31] >> 4) * L[j]; + carry = x[j] >> 8; + x[j] &= 255; + } + FOR(j,32) x[j] -= carry * L[j]; + FOR(i,32) { + x[i+1] += x[i] >> 8; + r[i] = x[i] & 255; + } +} + +sv reduce(u8 *r) +{ + i64 x[64],i; + FOR(i,64) x[i] = (u64) r[i]; + FOR(i,64) r[i] = 0; + modL(r,x); +} + +int crypto_sign(u8 *sm,u64 *smlen,const u8 *m,u64 n,const u8 *sk) +{ + u8 d[64],h[64],r[64]; + i64 i,j,x[64]; + gf p[4]; + + crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + *smlen = n+64; + FOR(i,n) sm[64 + i] = m[i]; + FOR(i,32) sm[32 + i] = d[32 + i]; + + crypto_hash(r, sm+32, n+32); + reduce(r); + scalarbase(p,r); + pack(sm,p); + + FOR(i,32) sm[i+32] = sk[i+32]; + crypto_hash(h,sm,n + 64); + reduce(h); + + FOR(i,64) x[i] = 0; + FOR(i,32) x[i] = (u64) r[i]; + FOR(i,32) FOR(j,32) x[i+j] += h[i] * (u64) d[j]; + modL(sm + 32,x); + + return 0; +} + +static int unpackneg(gf r[4],const u8 p[32]) +{ + gf t, chk, num, den, den2, den4, den6; + set25519(r[2],gf1); + unpack25519(r[1],p); + S(num,r[1]); + M(den,num,D); + Z(num,num,r[2]); + A(den,r[2],den); + + S(den2,den); + S(den4,den2); + M(den6,den4,den2); + M(t,den6,num); + M(t,t,den); + + pow2523(t,t); + M(t,t,num); + M(t,t,den); + M(t,t,den); + M(r[0],t,den); + + S(chk,r[0]); + M(chk,chk,den); + if (neq25519(chk, num)) M(r[0],r[0],I); + + S(chk,r[0]); + M(chk,chk,den); + if (neq25519(chk, num)) return -1; + + if (par25519(r[0]) == (p[31]>>7)) Z(r[0],gf0,r[0]); + + M(r[3],r[0],r[1]); + return 0; +} + +int crypto_sign_open(u8 *m,u64 *mlen,const u8 *sm,u64 n,const u8 *pk) +{ + int i; + u8 t[32],h[64]; + gf p[4],q[4]; + + *mlen = -1; + if (n < 64) return -1; + + if (unpackneg(q,pk)) return -1; + + FOR(i,n) m[i] = sm[i]; + FOR(i,32) m[i+32] = pk[i]; + crypto_hash(h,m,n); + reduce(h); + scalarmult(p,q,h); + + scalarbase(q,sm + 32); + add(p,q); + pack(t,p); + + n -= 64; + if (crypto_verify_32(sm, t)) { + FOR(i,n) m[i] = 0; + return -1; + } + + FOR(i,n) m[i] = sm[i + 64]; + *mlen = n; + return 0; +} diff --git a/patch_v2/meow_verify/tweetnacl.h b/patch_v2/meow_verify/tweetnacl.h new file mode 100644 index 0000000..b9f85f6 --- /dev/null +++ b/patch_v2/meow_verify/tweetnacl.h @@ -0,0 +1,23 @@ +/* tweetnacl.h — TweetNaCl 用于 MeowOS .mpk 签名/验签的最小声明头(Ed25519 + SHA-512 子集)。 + * 实现:https://tweetnacl.cr.yp.to/20140427/tweetnacl.c (公有领域)。 + * 仅暴露 meow_verify / meow_sign 需要的一小片 API;其余(salsa20/box/secretbox…) + * 在 tweetnacl.c 内保留但未声明,未引用即不进入最终链接。 */ +#ifndef TWEETNACL_H +#define TWEETNACL_H + +#define crypto_sign_BYTES 64 +#define crypto_sign_PUBLICKEYBYTES 32 +#define crypto_sign_SECRETKEYBYTES 64 +#define crypto_hash_BYTES 64 + +int crypto_verify_16(const unsigned char *x, const unsigned char *y); +int crypto_verify_32(const unsigned char *x, const unsigned char *y); +int crypto_hash(unsigned char *out, const unsigned char *m, unsigned long long n); + +int crypto_sign_keypair(unsigned char *public_key, unsigned char *secret_key); +int crypto_sign(unsigned char *sm, unsigned long long *smlen, + const unsigned char *m, unsigned long long n, const unsigned char *sk); +int crypto_sign_open(unsigned char *m, unsigned long long *mlen, + const unsigned char *sm, unsigned long long n, const unsigned char *pk); + +#endif /* TWEETNACL_H */ diff --git a/patch_v2/meowpkg/nyai b/patch_v2/meowpkg/nyai index f234b6c..e9869ca 100755 Binary files a/patch_v2/meowpkg/nyai and b/patch_v2/meowpkg/nyai differ diff --git a/patch_v2/meowpkg/nyai.c b/patch_v2/meowpkg/nyai.c index 277dfaf..a35e348 100644 --- a/patch_v2/meowpkg/nyai.c +++ b/patch_v2/meowpkg/nyai.c @@ -23,6 +23,7 @@ #include #include #include +#include #define NYAI_VERSION "0.2.0" #define BUF 1024 @@ -282,6 +283,7 @@ typedef struct { char name[256]; char version[64]; char exec[512]; + char package[64]; /* app.json "package"(权威包名,== 文件名茎;II 期绑定校验用) */ char author[128]; char routes[512]; char services[512]; @@ -305,6 +307,7 @@ static int load_app_meta(const char *path, appmeta *m) if (bal != 0) return -1; if (json_str(j,"name",m->name,sizeof m->name)!=0 || !m->name[0]) return -1; if (json_str(j,"exec",m->exec,sizeof m->exec)!=0 || !m->exec[0]) return -1; + if (json_str(j,"package",m->package,sizeof m->package)!=0) m->package[0]=0; if (json_str(j,"version",m->version,sizeof m->version)!=0) m->version[0]=0; if (json_str(j,"author",m->author,sizeof m->author)!=0) m->author[0]=0; if (json_str(j,"depends",m->depends,sizeof m->depends)!=0) m->depends[0]=0; @@ -648,7 +651,10 @@ static int cmd_update(void) return ok ? 0 : 1; } -/* ---------------- index ---------------- */ +/* ---------------- index ---------------- + * 注意(MPK_SIGNATURE_DESIGN §5.5):索引记录的 SHA256 = 整包哈希(含尾部签名 footer 的 .mpk)。 + * 必须先 `meow_sign sign .mpk` 完成签名、再 `nyai index `,否则索引与装后的整包不一致, + * install 的 sha256_file 校验会失败。本命令不改,仅标注此顺序约束(签名分级见 install_mpk_file)。 */ static int cmd_index(const char *dir) { if (!dir || !*dir) { fprintf(stderr,"nyai: index: 缺目录\n"); return 2; } @@ -746,6 +752,89 @@ static int cmd_list_upgradable(pkgrec *recs, int nrec) return 0; } +/* ---------------- II 期签名校验(MPK_SIGNATURE_DESIGN §5.1,shell-out meow_verify) ---------------- */ +/* 系统包前缀:os.meow.*(防伪造系统包名;独立于 valid_pkg,不改变现有合法包名序列)。 */ +static int is_system_pkg(const char *pkg) +{ + return pkg && !strncmp(pkg, "os.meow.", 8); +} + +/* meow_verify 二进制路径:环境变量 MEOW_VERIFY_BIN 覆盖(宿主测试用),缺省 /usr/bin/meow_verify。 */ +static const char *verify_bin(void) +{ + const char *v = getenv("MEOW_VERIFY_BIN"); + return (v && *v) ? v : "/usr/bin/meow_verify"; +} + +/* 调用 meow_verify --system --package 验签。 + * out_pkg 回传校验器输出的 footer.package(成功时)。返回校验器退出码: + * 0=有效签(三重绑定①通过) / 1=有签但不通过(改包名/负载/公钥) / 2=无签名 / 127=工具不可用。 */ +static int verify_mpk_signature(const char *mpk, const char *pkg, char *out_pkg, int n) +{ + if (!mpk || !pkg) return 127; + if (unsafe_path(mpk)) return 127; + char cmd[1200]; + snprintf(cmd, sizeof cmd, "'%s' --system '%s' --package '%s' 2>/dev/null", + verify_bin(), mpk, pkg); + if (out_pkg && n > 0) out_pkg[0] = 0; + FILE *pf = popen(cmd, "r"); + if (!pf) return 127; + char line[128]; + if (fgets(line, sizeof line, pf) != NULL) { + char *e = line + strlen(line); + while (e > line && (e[-1]=='\n'||e[-1]=='\r'||e[-1]==' '||e[-1]=='\t')) *--e = 0; + if (out_pkg && n > 0) { + size_t cl = strlen(line); + if (cl >= (size_t)n) cl = (size_t)n - 1; + memcpy(out_pkg, line, cl); out_pkg[cl] = 0; + } + } + int rc = pclose(pf); + if (rc == -1) return 127; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return 127; +} + +/* 安装期保存「原签 .mpk」到 MeowData//data/.mpk.src(导出保留签名用,§5.4)。 + * 仅当本包带有效签名时调用。失败只告警(导不出签名但安装成功)。 */ +static void save_mpk_source(const char *mpk, const char *pkg) +{ + char sp[BUF], d[BUF], cmd[1100]; + path_data(sp, sizeof sp, pkg, "data"); + mkdir_p(sp); + snprintf(d, sizeof d, "%s/%s.mpk.src", sp, pkg); + snprintf(cmd, sizeof cmd, "cp '%s' '%s' 2>/dev/null", mpk, d); + if (system(cmd) != 0) + fprintf(stderr, "nyai: 警告: 保存原签包 %s.mpk.src 失败(导出将无签名)\n", pkg); +} + +/* 导出保留签名:MeowData//data/.mpk.src 存在 + 验签通过 + 其 app.json 版本 == 已装版本 + * → 直接复制它作为导出结果(不二次签名);否则返回 -1(调用方现场重打包,无签名)。 */ +static int export_preserve_sig(const char *pkg, const char *dst) +{ + char sp[BUF], srcp[BUF], instj[BUF], cmd[1100]; + path_data(sp, sizeof sp, pkg, "data"); + snprintf(srcp, sizeof srcp, "%s/%s.mpk.src", sp, pkg); + if (access(srcp, R_OK) != 0) return -1; + char op[64]; + int rc = verify_mpk_signature(srcp, pkg, op, sizeof op); + if (rc != 0) { fprintf(stderr, "nyai: export: 缓存原签包验签失败(rc=%d),回退重打包\n", rc); return -1; } + appmeta cached, inst; + if (mpk_appinfo(srcp, &cached) != 0) return -1; + snprintf(instj, sizeof instj, "%s/MeowAPPs/%s/app.json", meow_root(), pkg); + if (load_app_meta(instj, &inst) != 0) return -1; + if (strcmp(cached.version, inst.version) != 0) { + fprintf(stderr, "nyai: export: 缓存原签包版本(%s) != 已装(%s),回退重打包\n", + cached.version[0]?cached.version:"(?)", inst.version[0]?inst.version:"(?)"); + return -1; + } + char tmp[520]; snprintf(tmp, sizeof tmp, "%s.tmp", dst); + snprintf(cmd, sizeof cmd, "cp '%s' '%s' 2>/dev/null", srcp, tmp); + if (system(cmd) != 0 || access(tmp, F_OK) != 0) return -1; + if (rename(tmp, dst) != 0) { rm_rf(tmp); return -1; } + return 0; +} + /* ---------------- install(文件或名称) ---------------- */ static int install_mpk_file(const char *mpk_path) { @@ -769,8 +858,8 @@ static int install_mpk_file(const char *mpk_path) snprintf(stg,sizeof stg,"%s/MeowAPPs/.stg_%d_%s", meow_root(), (int)getpid(), pkg); int replacing = (access(dst,F_OK)==0); - /* 签名分级预留(未实现验签):os.meow.* 系统包二期须有有效签名,无签名/验签失败一律拒装 */ - if (!strncmp(pkg,"os.meow.", 8)) printf("nyai: 系统包 %s 需签名(二期)\n", pkg); + /* 签名分级(II 期,MPK_SIGNATURE_DESIGN §5.2):有签名必须验签;os.meow.* 必须有有效签名; + * 无签名第三方放行(告警)。验签在 ① 解到 stg 后、② 防降级前执行(stg=临时目录,失败即 rm 不落盘)。 */ /* ① 先解到临时目录读元数据,再决定替换(防降级 + 校验 app.json) */ rm_rf(stg); @@ -788,7 +877,34 @@ static int install_mpk_file(const char *mpk_path) fprintf(stderr,"nyai: install: app.json 不合法(缺 name 或 exec)\n"); rm_rf(stg); return 1; } - /* TODO(2A): meow_verify 验签挂点,见 MPK_SIGNATURE_DESIGN.md */ + /* ①.5 签名分级 + 三重绑定(MPK_SIGNATURE_DESIGN §3.3/§5.2 失败行为表) */ + int vr = verify_mpk_signature(mpk_path, pkg, NULL, 0); + int system_pkg = is_system_pkg(pkg); + int keep_src = 0; /* 有效签 → 安装后另存原签 .mpk.src */ + if (vr == 0) { + keep_src = 1; + printf("nyai: 签名校验通过 (package=%s)\n", pkg); + } else if (vr == 1) { + fprintf(stderr,"nyai: install: 签名验证失败(内容被篡改/包名不符/公钥不符),拒绝安装\n"); + rm_rf(stg); return 1; + } else if (vr == 2) { + if (system_pkg) { + fprintf(stderr,"nyai: install: 系统包 %s 必须签名(无签名),拒绝安装\n", pkg); + rm_rf(stg); return 1; + } + printf("nyai: 提示: %s 未签名(第三方包放行安装)\n", pkg); + } else { + if (system_pkg) { + fprintf(stderr,"nyai: install: meow_verify 不可用(rc=%d),os.meow.* 拒装(宁缺勿装)\n", vr); + rm_rf(stg); return 1; + } + printf("nyai: 警告: meow_verify 不可用(rc=%d),%s 未验签放行\n", vr, pkg); + } + /* 绑定(一般/②):解包 app.json.package == 文件名茎(防目录改名/改 app.json;meow_verify 已保证① footer.package==pkg) */ + if (m.package[0] && strcmp(m.package, pkg) != 0) { + fprintf(stderr,"nyai: install: app.json.package(%s) != 包名(%s),拒绝\n", m.package, pkg); + rm_rf(stg); return 1; + } /* ② 同包名替换前的防降级门禁:新版必须 >= 已装(相等=重装,允许;<即降级拒装) */ if (replacing) { @@ -823,6 +939,9 @@ static int install_mpk_file(const char *mpk_path) mkdir_p(d1); mkdir_p(d2); printf("Setting up %s (%s) ...\n", pkg, m.version[0]?m.version:"?"); + /* ④.5 II 期:原签 .mpk 另存 MeowData//data/.mpk.src(导出保留签名) */ + if (keep_src) save_mpk_source(mpk_path, pkg); + /* ⑤ 安装成功后清理本次创建的 .bak_(失败回滚路径不动) */ if (replacing && bak[0]) { rm_rf(bak); printf("Removed backup %s\n", bak); } printf("OK\n"); @@ -1064,6 +1183,11 @@ static int cmd_export(const char *pkg, const char *out) snprintf(outbuf,sizeof outbuf,"%s", out && *out ? out : pkg); if (!strstr(outbuf,".mpk")) strncat(outbuf,".mpk", sizeof outbuf-strlen(outbuf)-1); snprintf(dst,sizeof dst,"%s", outbuf); + /* II 期:导出保留签名 —— 若安装时存了原签 .mpk.src 且其版本==已装 → 直接复制(不二次签名)。 */ + if (export_preserve_sig(pkg, dst) == 0) { + printf("OK (signed, preserved from .mpk.src)\n"); + return 0; + } printf("Exporting %s -> %s ...\n", pkg, dst); char tmpp[BUF]; snprintf(tmpp,sizeof tmpp,"%s.tmp", dst); int rc; @@ -1115,6 +1239,14 @@ static int cmd_upgrade(const char *pkg, const char *file) if (access(mpk,R_OK)!=0) { fprintf(stderr,"nyai: upgrade: 找不到包文件 %s\n", mpk); return 1; } char dst[BUF]; snprintf(dst,sizeof dst,"%s/MeowAPPs/%s", meow_root(), pkg); if (access(dst,F_OK)!=0) { fprintf(stderr,"nyai: upgrade: %s 未安装(如需全新安装请用 install)\n", pkg); return 1; } + /* 签名分级(与 install 同规范):os.meow.* 必须有有效签名;有签必须验签;无签第三方放行。 */ + { + int vr = verify_mpk_signature(mpk, pkg, NULL, 0); + int sysp = is_system_pkg(pkg); + if (vr == 1) { fprintf(stderr,"nyai: upgrade: 签名验证失败,拒绝升级\n"); return 1; } + else if (vr == 2) { if (sysp) { fprintf(stderr,"nyai: upgrade: 系统包 %s 必须签名,拒绝升级\n", pkg); return 1; } printf("nyai: 提示: %s 未签名(第三方放行)\n", pkg); } + else if (vr != 0) { if (sysp) { fprintf(stderr,"nyai: upgrade: meow_verify 不可用(rc=%d),os.meow.* 拒升\n", vr); return 1; } printf("nyai: 警告: meow_verify 不可用(rc=%d),%s 未验签放行\n", vr, pkg); } + } /* 防降级门禁(与 install 同规范):新版必须 >= 已装(相等=重装允许) */ { char aj[BUF]; snprintf(aj,sizeof aj,"%s/app.json", dst); diff --git a/patch_v2/meowpkg/nyai_host b/patch_v2/meowpkg/nyai_host index e657539..67ba967 100755 Binary files a/patch_v2/meowpkg/nyai_host and b/patch_v2/meowpkg/nyai_host differ