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为启发式
This commit is contained in:
MeowOS committed 2026-08-31 04:33:46 +08:00
1 parent f50abae02a
commit 3f57d7fd4f
22 files changed
+1978 -15

No files matched your search

+28
View File
@@ -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
BIN
View File
Binary file not shown.
+184
View File
@@ -0,0 +1,184 @@
/* meow_sign.c — MeowOS .mpk 产签工具(宿主端,非设备;离线维护者侧使用)。
*
* 功能(MPK_SIGNATURE_DESIGN §4.2):
* - keygen <priv_path>:生成 Ed25519 密钥对。私钥(64B seed||pub,0600)写 <priv_path>;
* 公钥(32B hex + raw)写 <priv_path>.pub,并在 stdout 打印 hex(便于固化进 meow_verify)。
* - sign <pkg>.mpk <priv_path>:对 .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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#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: 需要 <pkg>.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_path> 生成密钥对(priv 0600 + .pub,明文、不破坏现有)\n"
" %s sign <pkg>.mpk <priv_path> 对 .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;
}