338 lines
14 KiB
C
338 lines
14 KiB
C
/* 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;
|
||
}
|