Files
MeowOS-For-R3P2/patch_v2/meow_verify/meow_verify.c
T

256 lines
11 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 + 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 <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=有签但不通过(篡改/包名不符/公钥不符/格式错/ts异常);2=无签名 footer;3=用法/IO 错误。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#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 <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);
}