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为启发式
216 lines
8.3 KiB
C
216 lines
8.3 KiB
C
/* 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);
|
||
}
|