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

338 lines
14 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_sign.c — MeowOS .mpk 产签/验签工具(宿主端,非设备;离线维护者侧使用)。
*
* 功能(MPK_SIGNATURE_DESIGN §4.2 + 2B §7):
* - 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> [--v1]:对 .mpk(当前无 footer = 纯 tar.gz)计算 payload_sha256、
* 组被签消息、Ed25519 签名,并**尾部追加**签名 footer。默认产出 **v2**(含签名时间戳 ts,
* 纳入被签消息,验签端做未来宽限校验,见 meow_verify);`--v1` 产 2A 旧版(无 ts,跨旧固件)。
* - verify <mpk> <pubkey>:宿主侧用任意**公钥**(hex 或 .pub 文件路径)验签指定 .mpk,输出通过/原因。
* 用于产签后、分发前自测(对应用户定案「先验签再装」的宿主侧镜像);固件侧统一走 meow_verify。
*
* 私钥绝不落设备、绝不入库(仓库 .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 <time.h>
#include "../meow_verify/tweetnacl.h"
#include "../meow_verify/sha256.h"
#include "../meow_verify/meow_sig.h"
/* keygen 需真随机(从 /dev/urandom 读);sign/verify 不调用 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");
}
/* 十六进制串 → 字节。返回字节数(-1 非法)。 */
static int hex_decode(const char *s, uint8_t *out, int maxn)
{
int n = (int)strlen(s);
if ((n & 1) || n / 2 > maxn) return -1;
for (int i = 0; i < n; i += 2) {
int hi = s[i], lo = s[i + 1], v = 0;
if (hi >= '0' && hi <= '9') v = hi - '0';
else if (hi >= 'a' && hi <= 'f') v = hi - 'a' + 10;
else if (hi >= 'A' && hi <= 'F') v = hi - 'A' + 10;
else return -1;
v <<= 4;
if (lo >= '0' && lo <= '9') v += lo - '0';
else if (lo >= 'a' && lo <= 'f') v += lo - 'a' + 10;
else if (lo >= 'A' && lo <= 'F') v += lo - 'A' + 10;
else return -1;
out[i / 2] = (uint8_t)v;
}
return n / 2;
}
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 (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;
}
/* 分块 SHA-256 计算文件前 plen 字节(verify 用:payload = .mpk 去 footer 前缀)。 */
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;
}
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;
}
/* sign:默认 v2(含 ts);legacy_v1 非 0 则产 2A 旧版 footer(无 ts)。 */
static int sign(const char *mpk, const char *priv_path, int legacy_v1)
{
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; }
/* 组被签消息(v2 含 ts) + 签出 64B 分离签名 */
uint32_t ts = legacy_v1 ? 0u : (uint32_t)time(NULL);
char msg[MEOW_SIG_MSG_MAX];
unsigned mlen = legacy_v1 ? meow_sig_msg_build(msg, pkg, sha)
: meow_sig_msg_build_v2(msg, pkg, sha, ts);
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; }
if (legacy_v1) {
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; /* 无 ts */
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_V1;
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_V1);
close(afd);
if (w != (ssize_t)MEOW_SIG_BLOCK_LEN_V1) { fprintf(stderr, "meow_sign: footer write fail\n"); return 1; }
printf("sign ok (v1): %s package=%s msg_len=%u block_len=%u payload_sha256=",
mpk, pkg, mlen, (unsigned)MEOW_SIG_BLOCK_LEN_V1);
hex_print("", sha, 32);
return 0;
}
meow_sig_block_v2_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 = MEOW_SIG_FLAG_TS; /* 含 ts (v2) */
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.ts = ts;
blk.msg_len = mlen;
blk.block_len = MEOW_SIG_BLOCK_LEN_V2;
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_V2);
close(afd);
if (w != (ssize_t)MEOW_SIG_BLOCK_LEN_V2) { fprintf(stderr, "meow_sign: footer write fail\n"); return 1; }
printf("sign ok (v2): %s package=%s ts=%u msg_len=%u block_len=%u payload_sha256=",
mpk, pkg, ts, mlen, (unsigned)MEOW_SIG_BLOCK_LEN_V2);
hex_print("", sha, 32);
return 0;
}
/* 读文件尾部签名块(与 meow_verify 同逻辑)。返回块字节数;-2 太短/块长非法;-1 IO 错。 */
static int read_tail_block(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;
}
/* verify:宿主侧用任意公钥验签。pubkey 可为 32B 原始公钥文件路径、或 64 位 hex。 */
static int verify(const char *mpk, const char *pubkey)
{
struct stat st;
uint8_t pk[32];
if (stat(pubkey, &st) == 0) {
int fd = open(pubkey, O_RDONLY);
if (fd < 0) { fprintf(stderr, "meow_sign: open pub %s fail: %s\n", pubkey, strerror(errno)); return 1; }
if (read(fd, pk, sizeof pk) != (ssize_t)sizeof pk) { close(fd); fprintf(stderr, "meow_sign: pub file not 32B\n"); return 1; }
close(fd);
} else if (hex_decode(pubkey, pk, sizeof pk) != 32) {
fprintf(stderr, "meow_sign: pubkey 非法(需 32B .pub 文件路径,或 64 位 hex)\n");
return 1;
}
if (stat(mpk, &st) != 0) { fprintf(stderr, "meow_sign: stat %s fail\n", mpk); return 1; }
long long fsize = (long long)st.st_size;
if (fsize < (long long)MEOW_SIG_BLOCK_LEN_V1) { fprintf(stderr, "meow_sign: no signature footer\n"); return 2; }
uint8_t buf[MEOW_SIG_BLOCK_MAX];
int blen = read_tail_block(mpk, fsize, buf, sizeof buf);
if (blen < 0) { fprintf(stderr, "meow_sign: no signature footer (or truncated)\n"); return 2; }
if (memcmp(buf, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) != 0) { fprintf(stderr, "meow_sign: no MEOWSIG1 footer\n"); return 2; }
if (buf[8] != MEOW_SIG_ALG_ED25519) { fprintf(stderr, "meow_sign: unknown alg\n"); return 1; }
int v2 = (blen == (int)MEOW_SIG_BLOCK_LEN_V2);
if (!v2 && blen != (int)MEOW_SIG_BLOCK_LEN_V1) { fprintf(stderr, "meow_sign: bad block_len\n"); return 1; }
uint8_t want_flags = v2 ? MEOW_SIG_FLAG_TS : 0;
if ((buf[9] & MEOW_SIG_FLAG_TS) != want_flags) { fprintf(stderr, "meow_sign: flags/version mismatch\n"); return 1; }
char pkgsig[MEOW_SIG_PKG_MAX + 1];
memcpy(pkgsig, buf + MEOW_SIG_OFF_PKG, MEOW_SIG_PKG_MAX);
pkgsig[MEOW_SIG_PKG_MAX] = 0;
long long plen = fsize - (long long)blen;
uint8_t sha[MEOW_SIG_SHA256_LEN];
if (sha256_prefix(mpk, plen, sha) != 0) { fprintf(stderr, "meow_sign: read payload fail\n"); return 3; }
if (memcmp(buf + MEOW_SIG_OFF_SHA, sha, MEOW_SIG_SHA256_LEN) != 0) {
fprintf(stderr, "meow_sign: payload sha256 mismatch\n"); return 1;
}
uint32_t ts = v2 ? meow_le32(buf + MEOW_SIG_OFF_TS) : 0;
char msg[MEOW_SIG_MSG_MAX];
unsigned mlen = v2 ? meow_sig_msg_build_v2(msg, pkgsig, sha, ts)
: 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_sign: msg_len 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];
unsigned long long outlen = 0;
if (crypto_sign_open(out, &outlen, sm, (unsigned long long)(MEOW_SIG_SIG_LEN + mlen), pk) != 0) {
fprintf(stderr, "meow_sign: ed25519 signature invalid\n");
return 1;
}
printf("verify ok: %s package=%s version=%s ts=%u\n", mpk, pkgsig, v2 ? "v2" : "v1", ts);
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> [--v1] 对 .mpk 尾部追加 Ed25519 签名 footer(默认 v2 含时间戳;--v1=2A 旧版无 ts)\n"
" %s verify <mpk> <pub|hex> 用公钥(.pub 文件或 64 位 hex)验签指定 .mpk\n", a0, 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; }
int legacy_v1 = 0;
for (int i = 4; i < argc; i++) if (!strcmp(argv[i], "--v1")) legacy_v1 = 1;
return sign(argv[2], argv[3], legacy_v1);
}
if (!strcmp(argv[1], "verify")) {
if (argc < 4) { usage(argv[0]); return 2; }
return verify(argv[2], argv[3]);
}
usage(argv[0]);
return 2;
}