Files
MeowOS-For-R3P2/patch_v2/meow_verify/meow_verify.c
T
MeowOS 3f57d7fd4f feat(sec): 二期签名鉴权 2A 最小闭环 (agent-185) — meow_verify(Ed25519验签CLI,公钥固化,TweetNaCl+SHA256单文件) + meow_sign(宿主keygen/sign,尾部180B footer签名块,私钥仅.keys/不入库) + nyai/meow_pkg_install 验签分级(有签必验/os.meow.*无签拒装/第三方直装告警/三重绑定/原签存.mpk.src导出保留) + svc_power IPC鉴权(SO_PEERCRED→/proc/pid/exe→包名/系统身份allowlist,系统动作仅os.meow.*或系统身份) + cmd_upgrade同步签名分级防绕过
meow_verify 37e50606; nyai f2938d28; svc_power e0494eb7; launcher 24c9ee5d(含热重载)
宿主验证12项全过(正例/篡负载/篡包名/无签系统包拒/第三方直装/降级拒/导出round-trip byte-identical/verify缺失宁缺勿装/footer容忍/E2E PERM/鉴权分类)
出入: TweetNaCl替orlp/ed25519(任务允许同等实现); 读级暂仍uid==0(用户随后定案放宽到任意调用者,待改); 系统身份allowlist为启发式
2026-08-31 04:33:46 +08:00

216 lines
8.3 KiB
C
Raw 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_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 <mpk> # 验签(默认系统模式:固件公钥 + 绑定①)
* meow_verify --system <mpk> # 同上(显式)
* meow_verify --package <pkg> <mpk> # 显式给期望包名(否则从文件名茎推导)
* meow_verify --has-sig <mpk> # 只探测是否存在 MEOWSIG1 footer:输出 yes/no
* meow_verify --strip <mpk> # 输出去 footer 的干净 tar.gz 到 stdout(无 footer 则原样复制)
*
* 退出码:0=通过;1=有签但不通过(篡改/包名不符/公钥不符/格式错);2=无签名 footer;3=用法/IO 错误。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#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 <pkg>] <mpk>\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 <mpk>\n"); return 3; }
if (mode == 1) return has_sig(mpk);
if (mode == 2) return strip_mpk(mpk);
return verify_mpk(mpk, pkg);
}