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:
1 parent
f50abae02a
commit
3f57d7fd4f
22 files changed
+1978
-15
No files matched your search
@@ -41,6 +41,8 @@ __pycache__/
|
|||||||
font/atlas/
|
font/atlas/
|
||||||
# SSH 私钥/认证(绝不入库;.pub 公钥可留但这里一并忽略,按需手动 add)
|
# SSH 私钥/认证(绝不入库;.pub 公钥可留但这里一并忽略,按需手动 add)
|
||||||
ssh_keys/
|
ssh_keys/
|
||||||
|
# .mpk 签名私钥(Ed25519,离线维护者侧;绝不入库)
|
||||||
|
.keys/
|
||||||
# 二进制备份(*.bak 由 build/升级流程生成,勿入库)
|
# 二进制备份(*.bak 由 build/升级流程生成,勿入库)
|
||||||
*.bak
|
*.bak
|
||||||
*.orig
|
*.orig
|
||||||
@@ -65,3 +67,8 @@ ssh_keys/
|
|||||||
devdocs/
|
devdocs/
|
||||||
scripts/build/
|
scripts/build/
|
||||||
fonts_lib/
|
fonts_lib/
|
||||||
|
|
||||||
|
# 字体源文件(体积大,不进库;产物在 TF 卡)
|
||||||
|
font/src/**/*.otf
|
||||||
|
font/src/**/*.ttf
|
||||||
|
font/src/**/*.ttc
|
||||||
@@ -506,6 +506,82 @@ static int meow_app_prepare(const char *exec)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- II 期签名校验 / 导出保留签名(MPK_SIGNATURE_DESIGN §5.1/§5.4,shell-out meow_verify) ---- */
|
||||||
|
|
||||||
|
/* meow_verify --system <mpk> --package <pkg>。返回 0=有效签 /1=有签不通过 /2=无签名 /127=工具不可用。 */
|
||||||
|
static int pkg_verify_sig(const char *mpk, const char *pkg)
|
||||||
|
{
|
||||||
|
const char *vb = getenv("MEOW_VERIFY_BIN");
|
||||||
|
if (!vb || !*vb) vb = "/usr/bin/meow_verify";
|
||||||
|
char cmd[700];
|
||||||
|
snprintf(cmd, sizeof cmd, "'%s' --system '%s' --package '%s' >/dev/null 2>&1", vb, mpk, pkg);
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return 127;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return 127;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 读已解压 app.json 的某字符串字段(用 launcher 的 json_str;返回 0 成功)。 */
|
||||||
|
static int pkg_json_field(const char *appjs, const char *key, char *out, int n)
|
||||||
|
{
|
||||||
|
if (!appjs || !key || n <= 0) return -1;
|
||||||
|
FILE *f = fopen(appjs, "r");
|
||||||
|
if (!f) return -1;
|
||||||
|
static char j[16384];
|
||||||
|
size_t r = fread(j, 1, sizeof j - 1, f);
|
||||||
|
fclose(f);
|
||||||
|
j[r] = 0;
|
||||||
|
if (!r) return -1;
|
||||||
|
if (json_str(j, key, out, n) <= 0) { out[0] = 0; return -1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 读 .mpk 内 app.json 的某字符串字段(gzip -dc | tar -xO app.json;返回 0 成功)。 */
|
||||||
|
static int pkg_json_field_mpk(const char *mpk, const char *key, char *out, int n)
|
||||||
|
{
|
||||||
|
char cmd[700];
|
||||||
|
snprintf(cmd, sizeof cmd, "gzip -dc '%s' 2>/dev/null | tar -xO app.json 2>/dev/null", mpk);
|
||||||
|
FILE *pf = popen(cmd, "r");
|
||||||
|
if (!pf) return -1;
|
||||||
|
static char j[16384];
|
||||||
|
size_t r = fread(j, 1, sizeof j - 1, pf);
|
||||||
|
pclose(pf);
|
||||||
|
if (!r) return -1;
|
||||||
|
j[r] = 0;
|
||||||
|
if (json_str(j, key, out, n) <= 0) { out[0] = 0; return -1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 安装成功后,把原签 .mpk 另存 MeowData/<pkg>/data/<pkg>.mpk.src(导出保留签名)。 */
|
||||||
|
static void pkg_save_src(const char *mpk, const char *pkg)
|
||||||
|
{
|
||||||
|
char dst[320], cmd[720];
|
||||||
|
snprintf(dst, sizeof dst, "/usr/data/mnt/sd_0/MeowData/%s/data/%s.mpk.src", pkg, pkg);
|
||||||
|
snprintf(cmd, sizeof cmd, "mkdir -p '/usr/data/mnt/sd_0/MeowData/%s/data' 2>/dev/null && "
|
||||||
|
"cp '%s' '%s' 2>/dev/null", pkg, mpk, dst);
|
||||||
|
if (system(cmd) != 0) logmsg("pkg_install: save .mpk.src fail for %s", pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导出保留签名:缓存 .mpk.src 存在 + 验签通过 + 其 app.json 版本 == 已装 → 直接复制(不二次签名)。
|
||||||
|
* 成功 0;否则 -1(调用方现场重打包,无签名)。 */
|
||||||
|
static int pkg_export_preserve_sig(const char *pkg, const char *dst)
|
||||||
|
{
|
||||||
|
char srcp[320], instj[280], cmd[760], tmp[340];
|
||||||
|
snprintf(srcp, sizeof srcp, "/usr/data/mnt/sd_0/MeowData/%s/data/%s.mpk.src", pkg, pkg);
|
||||||
|
if (access(srcp, R_OK) != 0) return -1;
|
||||||
|
if (pkg_verify_sig(srcp, pkg) != 0) { logmsg("pkg_export: cached .mpk.src sig fail %s", pkg); return -1; }
|
||||||
|
char cv[64], iv[64];
|
||||||
|
snprintf(instj, sizeof instj, "/usr/data/mnt/sd_0/MeowAPPs/%s/app.json", pkg);
|
||||||
|
if (pkg_json_field(instj, "version", iv, sizeof iv) != 0) return -1;
|
||||||
|
if (pkg_json_field_mpk(srcp, "version", cv, sizeof cv) != 0) return -1;
|
||||||
|
if (strcmp(iv, cv) != 0) { logmsg("pkg_export: cached version %s != installed %s", cv, iv); return -1; }
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.tmp", dst);
|
||||||
|
snprintf(cmd, sizeof cmd, "cp '%s' '%s' 2>/dev/null", srcp, tmp);
|
||||||
|
if (system(cmd) != 0 || access(tmp, R_OK) != 0) return -1;
|
||||||
|
if (rename(tmp, dst) != 0) { unlink(tmp); return -1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* .mpk 安装(本期接口/CLI + 文档;UI 后期):解压 .mpk → MeowAPPs/<pkg>/。
|
/* .mpk 安装(本期接口/CLI + 文档;UI 后期):解压 .mpk → MeowAPPs/<pkg>/。
|
||||||
* 约定 .mpk = tar.gz,顶层即包目录(含 app.json);包名 = 文件名茎 <pkg>.mpk。
|
* 约定 .mpk = tar.gz,顶层即包目录(含 app.json);包名 = 文件名茎 <pkg>.mpk。
|
||||||
* 同名先备份;解压后校验 app.json;随后数据 init(MeowData/<pkg>/{data,cache})。
|
* 同名先备份;解压后校验 app.json;随后数据 init(MeowData/<pkg>/{data,cache})。
|
||||||
@@ -525,9 +601,25 @@ int meow_pkg_install(const char *mpk_path)
|
|||||||
if (len <= 0 || len >= (int)sizeof pkg) { logmsg("pkg_install: bad pkg name"); return -1; }
|
if (len <= 0 || len >= (int)sizeof pkg) { logmsg("pkg_install: bad pkg name"); return -1; }
|
||||||
memcpy(pkg, bn, len); pkg[len] = 0;
|
memcpy(pkg, bn, len); pkg[len] = 0;
|
||||||
/* 目标 MeowAPPs/<pkg>/;同名先备份(.bak_<pid>),再解压,再校验 app.json */
|
/* 目标 MeowAPPs/<pkg>/;同名先备份(.bak_<pid>),再解压,再校验 app.json */
|
||||||
char dst[256], bak[256], cmd[700];
|
char dst[256], bak[256], cmd[760];
|
||||||
snprintf(dst, sizeof dst, "/usr/data/mnt/sd_0/MeowAPPs/%s", pkg);
|
snprintf(dst, sizeof dst, "/usr/data/mnt/sd_0/MeowAPPs/%s", pkg);
|
||||||
snprintf(bak, sizeof bak, "%s.bak_%d", dst, (int)getpid());
|
snprintf(bak, sizeof bak, "%s.bak_%d", dst, (int)getpid());
|
||||||
|
int replacing = (access(dst, F_OK) == 0);
|
||||||
|
|
||||||
|
/* II 期:签名分级先于解包(MPK_SIGNATURE_DESIGN §5.2:先验签再解包,失败不落盘)。
|
||||||
|
* 有签名必须验签(失败拒装);os.meow.* 必须有有效签名;无签名第三方放行(告警)。 */
|
||||||
|
int vr = pkg_verify_sig(mpk_path, pkg);
|
||||||
|
int sysp = (strncmp(pkg, "os.meow.", 8) == 0);
|
||||||
|
if (vr == 1) { logmsg("pkg_install: sig invalid, reject %s", pkg); return -3; }
|
||||||
|
else if (vr == 2) {
|
||||||
|
if (sysp) { logmsg("pkg_install: sys pkg %s must be signed, rejected", pkg); return -3; }
|
||||||
|
logmsg("pkg_install: %s unsigned (3rd party allowed)", pkg);
|
||||||
|
}
|
||||||
|
else if (vr != 0) {
|
||||||
|
if (sysp) { logmsg("pkg_install: meow_verify unavailable rc=%d, sys pkg reject", vr); return -3; }
|
||||||
|
logmsg("pkg_install: meow_verify unavailable rc=%d, %s unverified (3rd party)", vr, pkg);
|
||||||
|
}
|
||||||
|
|
||||||
/* BusyBox tar 无 -z:用 gzip -dc | tar -x 处理 tar.gz。 */
|
/* BusyBox tar 无 -z:用 gzip -dc | tar -x 处理 tar.gz。 */
|
||||||
snprintf(cmd, sizeof cmd,
|
snprintf(cmd, sizeof cmd,
|
||||||
"rm -rf '%s' 2>/dev/null; "
|
"rm -rf '%s' 2>/dev/null; "
|
||||||
@@ -535,10 +627,27 @@ int meow_pkg_install(const char *mpk_path)
|
|||||||
"mkdir -p '%s' && gzip -dc '%s' | tar -x -C '%s' 2>/dev/null && "
|
"mkdir -p '%s' && gzip -dc '%s' | tar -x -C '%s' 2>/dev/null && "
|
||||||
"[ -f '%s/app.json' ]", bak, dst, dst, bak, dst, mpk_path, dst, dst);
|
"[ -f '%s/app.json' ]", bak, dst, dst, bak, dst, mpk_path, dst, dst);
|
||||||
if (system(cmd) != 0) { logmsg("pkg_install: extract/validate fail for %s", pkg); return -1; }
|
if (system(cmd) != 0) { logmsg("pkg_install: extract/validate fail for %s", pkg); return -1; }
|
||||||
|
|
||||||
|
/* II 期:app.json.package 绑定(== 包名,禁改包名/目录改名;防第三方面具系统包)。 */
|
||||||
|
{
|
||||||
|
char appj[280], ph[64];
|
||||||
|
snprintf(appj, sizeof appj, "/usr/data/mnt/sd_0/MeowAPPs/%s/app.json", pkg);
|
||||||
|
if (pkg_json_field(appj, "package", ph, sizeof ph) == 0 && strcmp(ph, pkg) != 0) {
|
||||||
|
logmsg("pkg_install: app.json.package %s != %s, reject", ph, pkg);
|
||||||
|
char rb[760];
|
||||||
|
snprintf(rb, sizeof rb, "rm -rf '%s' 2>/dev/null; [ -d '%s' ] && mv '%s' '%s' 2>/dev/null",
|
||||||
|
dst, bak, bak, dst);
|
||||||
|
system(rb);
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 数据 init:MeowData/<pkg>/{data,cache} */
|
/* 数据 init:MeowData/<pkg>/{data,cache} */
|
||||||
snprintf(cmd, sizeof cmd, "mkdir -p '/usr/data/mnt/sd_0/MeowData/%s/data' "
|
snprintf(cmd, sizeof cmd, "mkdir -p '/usr/data/mnt/sd_0/MeowData/%s/data' "
|
||||||
"'/usr/data/mnt/sd_0/MeowData/%s/cache' 2>/dev/null", pkg, pkg);
|
"'/usr/data/mnt/sd_0/MeowData/%s/cache' 2>/dev/null", pkg, pkg);
|
||||||
system(cmd);
|
system(cmd);
|
||||||
|
/* 有效签 → 原签 .mpk 另存 MeowData/<pkg>/data/<pkg>.mpk.src(导出保留签名) */
|
||||||
|
if (vr == 0) pkg_save_src(mpk_path, pkg);
|
||||||
logmsg("pkg_install: %s installed (old backed up %s)", pkg, bak);
|
logmsg("pkg_install: %s installed (old backed up %s)", pkg, bak);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -556,6 +665,11 @@ int meow_pkg_export(const char *pkg, const char *out_path)
|
|||||||
snprintf(src, sizeof src, "/usr/data/mnt/sd_0/MeowAPPs/%s", pkg);
|
snprintf(src, sizeof src, "/usr/data/mnt/sd_0/MeowAPPs/%s", pkg);
|
||||||
if (access(src, R_OK) != 0) { logmsg("pkg_export: no such pkg"); return -1; }
|
if (access(src, R_OK) != 0) { logmsg("pkg_export: no such pkg"); return -1; }
|
||||||
snprintf(dst, sizeof dst, "%s", out_path);
|
snprintf(dst, sizeof dst, "%s", out_path);
|
||||||
|
/* II 期:导出保留签名 —— 若安装时存了原签 .mpk.src 且其版本==已装 → 直接复制(不二次签名)。 */
|
||||||
|
if (pkg_export_preserve_sig(pkg, dst) == 0) {
|
||||||
|
logmsg("pkg_export: %s -> %s (signed, preserved)", pkg, dst);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
/* BusyBox tar 无 -z:tar -c | gzip -c > out(先写 .tmp 再 mv,防半成品)。 */
|
/* BusyBox tar 无 -z:tar -c | gzip -c > out(先写 .tmp 再 mv,防半成品)。 */
|
||||||
char cmd[512];
|
char cmd[512];
|
||||||
snprintf(cmd, sizeof cmd, "tar -c -C '%s' . | gzip -c > '%s.tmp' 2>/dev/null && "
|
snprintf(cmd, sizeof cmd, "tar -c -C '%s' . | gzip -c > '%s.tmp' 2>/dev/null && "
|
||||||
|
|||||||
Executable
+28
@@ -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
|
||||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -10,8 +10,12 @@
|
|||||||
* 动作逻辑与 launcher `power_menu.c` 的 `sys_do_reboot`/`set_boot_default_hiby`/`bootmode.sh Recovery`
|
* 动作逻辑与 launcher `power_menu.c` 的 `sys_do_reboot`/`set_boot_default_hiby`/`bootmode.sh Recovery`
|
||||||
* **同源 0-diff 搬移**,仅入口(CLI vs IPC)与鉴权层不同。
|
* **同源 0-diff 搬移**,仅入口(CLI vs IPC)与鉴权层不同。
|
||||||
*
|
*
|
||||||
* 鉴权(一期基础校验):IPC 服务端受理 MSG_POWER_ACTION 前以 `SO_PEERCRED` 校验调用方 `uid==0`(root)
|
* 鉴权(II 期,MPK_SIGNATURE_DESIGN §6):IPC 服务端按【连接级身份】分级——`SO_PEERCRED` 取 pid →
|
||||||
* ——非 root 直接 `MEOW_ERR_PERM` 拒绝;完整 `os.meow.*` 包名鉴权(§5)留二期。CLI=root 免鉴权。
|
* `readlink /proc/pid/exe` → 命中 `MeowAPPs/<pkg>/` 判定包名,或命中系统二进制 allowlist
|
||||||
|
* (/usr/bin/meow_*、/usr/data/meow/meow*、MeowPanel 后端)判定为系统身份;其余为 OTHER。
|
||||||
|
* 动作级策略:系统动作(shutdown/reboot/upgrade/back_hiby/set-default-*)仅放行 `os.meow.*` 包或系统身份;
|
||||||
|
* 读/状态类(status/battery/reboot_reason/upgrade_status/screen/dac)放行所有 root 调用方;非 root 一律拒。
|
||||||
|
* `MSG_HELLO` 自报 app_id 不在此作权威(仅交叉校验,见 §6.1)。CLI=root 免鉴权。
|
||||||
*
|
*
|
||||||
* 通知转储归属(§3.2/§3.3 已定案):通知存储为 launcher 进程内存(meow_notif.c g_slots[]),服务跨进程
|
* 通知转储归属(§3.2/§3.3 已定案):通知存储为 launcher 进程内存(meow_notif.c g_slots[]),服务跨进程
|
||||||
* 不可达——**转储由调用方 launcher 先做**,服务对关机/重启/升级一律 `sync()` 落盘,不重实现转储。
|
* 不可达——**转储由调用方 launcher 先做**,服务对关机/重启/升级一律 `sync()` 落盘,不重实现转储。
|
||||||
@@ -593,15 +597,99 @@ static int send_power_status(int fd, uint32_t req_id, const m_power_status_t *st
|
|||||||
return send_frame(fd, MSG_POWER_STATUS, req_id, &s, sizeof s);
|
return send_frame(fd, MSG_POWER_STATUS, req_id, &s, sizeof s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------- II 期:调用方身份解析(连接级,MPK_SIGNATURE_DESIGN §6.2) ---------------- */
|
||||||
|
typedef enum { CALL_OTHER, CALL_PKG, CALL_SYS } caller_kind_t;
|
||||||
|
typedef struct {
|
||||||
|
caller_kind_t kind;
|
||||||
|
char pkg[MEOW_APP_ID_MAX]; /* CALL_PKG=MeowAPPs/<pkg>/ 取包名;CALL_SYS=系统身份别名 */
|
||||||
|
uid_t uid;
|
||||||
|
} caller_t;
|
||||||
|
|
||||||
|
/* 系统动作(SYSTEM_FRAMEWORK_SERVICES §2.4 + 设计 §6.3 表):须 os.meow.* 包或系统身份。 */
|
||||||
|
static int is_system_action(uint32_t cmd)
|
||||||
|
{
|
||||||
|
switch (cmd) {
|
||||||
|
case POWER_SHUTDOWN: case POWER_REBOOT: case POWER_UPGRADE:
|
||||||
|
case POWER_BACK_HIBY: case POWER_SET_DEFAULT_MEOW: case POWER_SET_DEFAULT_HIBY:
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 是否可信系统身份:CALL_SYS(受信系统二进制)或 CALL_PKG 且包前缀 os.meow.*。 */
|
||||||
|
static int is_sys_identity(const caller_t *c)
|
||||||
|
{
|
||||||
|
if (c->kind == CALL_SYS) return 1;
|
||||||
|
if (c->kind == CALL_PKG && !strncmp(c->pkg, "os.meow.", 8)) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 由 exe 路径判身份(纯函数,便于宿主自测 + 生产共用)。前提:out 已 memset(kind 缺省 CALL_OTHER)。
|
||||||
|
* - exe 前缀 /usr/data/mnt/sd_0/MeowAPPs/ → MeowAPP 包(kind=CALL_PKG,pkg=目录名)
|
||||||
|
* - 否则命中系统二进制 allowlist → CALL_SYS
|
||||||
|
* - 其余保持 CALL_OTHER
|
||||||
|
* msg 自报 app_id 仅作交叉校验(不在此处做权威判定,见设计 §6.1/§6.5)。 */
|
||||||
|
static void classify_exe(const char *exe, caller_t *out)
|
||||||
|
{
|
||||||
|
/* MeowAPPs/<pkg>/(与 app_launcher.c meow_pkg_name 同口径:去前缀取第一段目录名) */
|
||||||
|
static const char *mp = "/usr/data/mnt/sd_0/MeowAPPs/";
|
||||||
|
if (strncmp(exe, mp, strlen(mp)) == 0) {
|
||||||
|
const char *p = exe + strlen(mp);
|
||||||
|
if (*p == '/') p++;
|
||||||
|
const char *end = strchr(p, '/');
|
||||||
|
int plen = end ? (int)(end - p) : (int)strlen(p);
|
||||||
|
if (plen > 0 && plen < MEOW_APP_ID_MAX) {
|
||||||
|
memcpy(out->pkg, p, (size_t)plen); out->pkg[plen] = 0;
|
||||||
|
out->kind = CALL_PKG;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 系统二进制 allowlist(/usr/bin/meow_*、/usr/data/meow_*、MeowPanel 后端) */
|
||||||
|
char tmp[MEOW_APP_ID_MAX];
|
||||||
|
if (strcmp(exe, "/usr/bin/meow_launcherd") == 0) {
|
||||||
|
snprintf(tmp, sizeof tmp, "%s", "os.meow.launcher");
|
||||||
|
} else if (strstr(exe, "/MeowPanel/") || strstr(exe, "meowpanel")) {
|
||||||
|
snprintf(tmp, sizeof tmp, "%s", "os.meow.panel");
|
||||||
|
} else if (strncmp(exe, "/usr/bin/meow_", 14) == 0) {
|
||||||
|
/* 截取 "meow_xyz"(去 "/usr/bin/" 前缀)作近似别名 */
|
||||||
|
size_t l = strlen(exe + 11);
|
||||||
|
if (l >= sizeof tmp) l = sizeof tmp - 1;
|
||||||
|
memcpy(tmp, exe + 11, l); tmp[l] = 0;
|
||||||
|
} else if (strncmp(exe, "/usr/data/meow/", 15) == 0) {
|
||||||
|
snprintf(tmp, sizeof tmp, "%s", "os.meow.core");
|
||||||
|
} else {
|
||||||
|
return; /* CALL_OTHER */
|
||||||
|
}
|
||||||
|
snprintf(out->pkg, sizeof out->pkg, "%s", tmp);
|
||||||
|
out->kind = CALL_SYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 由连接 SO_PEERCRED 取 pid → readlink /proc/<pid>/exe → classify_exe(连接创建时缓存,循环内复用)。 */
|
||||||
|
static void classify_caller(int fd, caller_t *out)
|
||||||
|
{
|
||||||
|
memset(out, 0, sizeof *out);
|
||||||
|
out->uid = (uid_t)-1;
|
||||||
|
struct ucred cred;
|
||||||
|
socklen_t cl = sizeof cred;
|
||||||
|
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cl) != 0) return;
|
||||||
|
out->uid = cred.uid;
|
||||||
|
|
||||||
|
char procpath[64], exe[256];
|
||||||
|
snprintf(procpath, sizeof procpath, "/proc/%d/exe", (int)cred.pid);
|
||||||
|
ssize_t n = readlink(procpath, exe, sizeof exe - 1);
|
||||||
|
if (n <= 0) return;
|
||||||
|
exe[n] = 0;
|
||||||
|
classify_exe(exe, out);
|
||||||
|
}
|
||||||
|
|
||||||
/* 处理单条连接(连接内循环读帧:客户端先 MSG_HELLO 握手,再 MSG_POWER_ACTION)。 */
|
/* 处理单条连接(连接内循环读帧:客户端先 MSG_HELLO 握手,再 MSG_POWER_ACTION)。 */
|
||||||
static void handle_conn(int c)
|
static void handle_conn(int c)
|
||||||
{
|
{
|
||||||
/* 基础鉴权:调用方 uid==0(root)才受理动作。 */
|
/* II 期鉴权升级(MPK_SIGNATURE_DESIGN §6.2):uid==0 判定升级为连接级身份解析(pid→exe→包名/系统身份)。 */
|
||||||
struct ucred cred;
|
caller_t caller;
|
||||||
socklen_t cl = sizeof cred;
|
classify_caller(c, &caller);
|
||||||
int uid0 = 0;
|
|
||||||
if (getsockopt(c, SOL_SOCKET, SO_PEERCRED, &cred, &cl) == 0 && cred.uid == 0)
|
|
||||||
uid0 = 1;
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||||||
@@ -629,12 +717,20 @@ static void handle_conn(int c)
|
|||||||
memset(&a, 0, sizeof a);
|
memset(&a, 0, sizeof a);
|
||||||
if (rlen > sizeof a) rlen = sizeof a;
|
if (rlen > sizeof a) rlen = sizeof a;
|
||||||
memcpy(&a, payload, rlen);
|
memcpy(&a, payload, rlen);
|
||||||
if (!uid0) {
|
/* 动作级策略(设计 §6.3):非 root 一律拒;系统动作须 os.meow.* 包或受信系统身份;
|
||||||
|
* 读/状态类(STATUS/BATTERY/REBOOT_REASON/UPGRADE_STATUS/SCREEN/DAC)放行所有 root 调用方。 */
|
||||||
|
int allow = 0;
|
||||||
|
if (caller.uid == 0) {
|
||||||
|
if (is_system_action(a.cmd)) allow = is_sys_identity(&caller);
|
||||||
|
else allow = 1;
|
||||||
|
}
|
||||||
|
if (!allow) {
|
||||||
m_power_status_t st;
|
m_power_status_t st;
|
||||||
memset(&st, 0, sizeof st);
|
memset(&st, 0, sizeof st);
|
||||||
st.code = MEOW_ERR_PERM;
|
st.code = MEOW_ERR_PERM;
|
||||||
st.cmd = a.cmd;
|
st.cmd = a.cmd;
|
||||||
snprintf(st.msg, sizeof st.msg, "permission denied (uid!=0)");
|
snprintf(st.msg, sizeof st.msg, "permission denied (caller kind=%d uid=%d)",
|
||||||
|
(int)caller.kind, (int)caller.uid);
|
||||||
send_power_status(c, req_id, &st);
|
send_power_status(c, req_id, &st);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,93 @@
|
|||||||
|
/* svc_power_authtest.c — meow_svc_power II 期 IPC 鉴权逻辑 宿主自测(本程序非生产代码)。
|
||||||
|
*
|
||||||
|
* 通过 #include 生产源文件(meow_svc_power.c,用 #define main 改名避免入口冲突),在同一 TU 内直接
|
||||||
|
* 访问其 FILE-STATIC 函数 classify_exe / is_system_action / is_sys_identity,验证鉴别/分级策略的决策表。
|
||||||
|
* 覆盖「真机需特权路径 /proc/pid/exe 判定」之外的所有纯逻辑分支;真实 pid→exe 解析留真机验证。
|
||||||
|
*
|
||||||
|
* 用途:dev docs 复现级宿主自测;不进入固件/不部署。用法:
|
||||||
|
* cc -Wall -Wextra -I../meow_launcherd svc_power_authtest.c -o svc_power_authtest && ./svc_power_authtest
|
||||||
|
*/
|
||||||
|
#define main meow_svc_power_prod_main_unused
|
||||||
|
#include "meow_svc_power.c"
|
||||||
|
#undef main
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static int g_fail = 0;
|
||||||
|
#define CHECK(cond) do { if (!(cond)) { printf(" FAIL: %s (line %d)\n", #cond, __LINE__); g_fail++; } \
|
||||||
|
else { printf(" ok : %s\n", #cond); } } while (0)
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
caller_t c;
|
||||||
|
|
||||||
|
printf("== classify_exe ==\n");
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/data/mnt/sd_0/MeowAPPs/os.meow.tst/meow", &c);
|
||||||
|
CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "os.meow.tst") == 0);
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/data/mnt/sd_0/MeowAPPs/com.example.tool/meow", &c);
|
||||||
|
CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "com.example.tool") == 0);
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/data/mnt/sd_0/MeowAPPs/os.meow.tst/sub/meow", &c);
|
||||||
|
CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "os.meow.tst") == 0); /* 取首段目录 */
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/bin/meow_launcherd", &c);
|
||||||
|
CHECK(c.kind == CALL_SYS && strcmp(c.pkg, "os.meow.launcher") == 0);
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/bin/meow_svc_power", &c);
|
||||||
|
CHECK(c.kind == CALL_SYS); /* /usr/bin/meow_* 前缀批准 */
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/backend/meowpanel", &c);
|
||||||
|
CHECK(c.kind == CALL_SYS && strcmp(c.pkg, "os.meow.panel") == 0);
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/data/meow/settings_tool", &c);
|
||||||
|
CHECK(c.kind == CALL_SYS); /* /usr/data/meow/ 批准 */
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/bin/bash", &c);
|
||||||
|
CHECK(c.kind == CALL_OTHER);
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/bin/sh", &c);
|
||||||
|
CHECK(c.kind == CALL_OTHER);
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
classify_exe("/usr/data/mnt/sd_0/MeowAPPs/justadirty/k", &c);
|
||||||
|
CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "justadirty") == 0); /* 第三方包(非 os.meow.*) */
|
||||||
|
CHECK(is_sys_identity(&c) == 0); /* 非系统身份 → 系统动作被拒 */
|
||||||
|
|
||||||
|
printf("== is_sys_identity ==\n");
|
||||||
|
c.kind = CALL_PKG; snprintf(c.pkg, sizeof c.pkg, "%s", "os.meow.tst");
|
||||||
|
CHECK(is_sys_identity(&c) == 1);
|
||||||
|
c.kind = CALL_PKG; snprintf(c.pkg, sizeof c.pkg, "%s", "com.example.tool");
|
||||||
|
CHECK(is_sys_identity(&c) == 0);
|
||||||
|
c.kind = CALL_SYS; snprintf(c.pkg, sizeof c.pkg, "%s", "os.meow.launcher");
|
||||||
|
CHECK(is_sys_identity(&c) == 1);
|
||||||
|
c.kind = CALL_OTHER; c.pkg[0] = 0;
|
||||||
|
CHECK(is_sys_identity(&c) == 0);
|
||||||
|
|
||||||
|
printf("== is_system_action ==\n");
|
||||||
|
CHECK(is_system_action(POWER_SHUTDOWN) == 1);
|
||||||
|
CHECK(is_system_action(POWER_REBOOT) == 1);
|
||||||
|
CHECK(is_system_action(POWER_UPGRADE) == 1);
|
||||||
|
CHECK(is_system_action(POWER_BACK_HIBY) == 1);
|
||||||
|
CHECK(is_system_action(POWER_SET_DEFAULT_MEOW) == 1);
|
||||||
|
CHECK(is_system_action(POWER_SET_DEFAULT_HIBY) == 1);
|
||||||
|
CHECK(is_system_action(POWER_STATUS) == 0);
|
||||||
|
CHECK(is_system_action(POWER_BATTERY) == 0);
|
||||||
|
CHECK(is_system_action(POWER_REBOOT_REASON) == 0);
|
||||||
|
CHECK(is_system_action(POWER_UPGRADE_STATUS) == 0);
|
||||||
|
CHECK(is_system_action(POWER_SCREEN) == 0);
|
||||||
|
CHECK(is_system_action(POWER_DAC) == 0);
|
||||||
|
|
||||||
|
if (g_fail) { printf("\nAUTH TEST: %d FAIL\n", g_fail); return 1; }
|
||||||
|
printf("\nAUTH TEST: ALL PASS\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Executable
+43
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# build_verify.sh — meow_verify 交叉编译(mipsel 静态,固件 /usr/bin/meow_verify)+ 宿主编译可选。
|
||||||
|
#
|
||||||
|
# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。纯 POSIX+Linux(无外部依赖,除 vendor
|
||||||
|
# tweetnacl.c/sha256.c 全内编)。公钥编译期固化:meow_signing_pub.h(由 meow_sign keygen 生成)。
|
||||||
|
#
|
||||||
|
# 警告策略(诚实标注):本项目自有代码为 0 warning;
|
||||||
|
# - meow_verify.c / sha256.c / meow_sig.h → -Wall -Wextra(0 warning)
|
||||||
|
# - tweetnacl.c(第三方 public-domain vendor 单文件)→ 单独对象 + -Wno-sign-compare
|
||||||
|
# -Wno-unterminated-string-initialization -Wno-unused-function(作者原写法:u64/u32 与 int 混比、
|
||||||
|
# "expand 32-byte k" 16B char 数组塞 17 字节。非本项目代码,局部放宽)。
|
||||||
|
# - 不启用 gc-sections:meow_verify.c 的 randombytes() stub 满足 crypto_sign_keypair 的引用,整链静态。
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# ./build_verify.sh # 交叉编译 → patch_v2/meow_verify/meow_verify(mipsel 静态)
|
||||||
|
# ./build_verify.sh host # 宿主编译 → patch_v2/meow_verify/meow_verify_host
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
ROOT="$HOME/Project/R3ProII"
|
||||||
|
CC="$ROOT/.tools/zig-mipsel-cc"
|
||||||
|
TNFLAGS="-Os -Wno-sign-compare -Wno-unterminated-string-initialization -Wno-unused-function -static"
|
||||||
|
|
||||||
|
if [ "$1" = "host" ]; then
|
||||||
|
HOST_CC="${CC_HOST:-cc}"
|
||||||
|
"$HOST_CC" -Os -Wno-sign-compare -Wno-unterminated-string-initialization -Wno-unused-function -c tweetnacl.c -o tweetnacl_v.o
|
||||||
|
"$HOST_CC" -Os -Wall -Wextra -c sha256.c -o sha256_v.o
|
||||||
|
"$HOST_CC" -Os -Wall -Wextra -c meow_verify.c -o meow_verify_v.o
|
||||||
|
"$HOST_CC" meow_verify_v.o sha256_v.o tweetnacl_v.o -o meow_verify_host
|
||||||
|
rm -f tweetnacl_v.o sha256_v.o meow_verify_v.o
|
||||||
|
echo "== meow_verify (host) =="
|
||||||
|
ls -la meow_verify_host; md5sum meow_verify_host; file meow_verify_host
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$CC" $TNFLAGS -c tweetnacl.c -o tweetnacl_v.o
|
||||||
|
"$CC" -Os -Wall -Wextra -static -c sha256.c -o sha256_v.o
|
||||||
|
"$CC" -Os -Wall -Wextra -static -c meow_verify.c -o meow_verify_v.o
|
||||||
|
"$CC" -Os -static meow_verify_v.o sha256_v.o tweetnacl_v.o -o meow_verify
|
||||||
|
rm -f tweetnacl_v.o sha256_v.o meow_verify_v.o
|
||||||
|
echo "== meow_verify (mipsel static) =="
|
||||||
|
ls -la meow_verify
|
||||||
|
md5sum meow_verify
|
||||||
|
file meow_verify
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/* meow_sig.h — .mpk 签名块格式(MPK_SIGNATURE_DESIGN §3.1)+ 被签消息构造(§3.1)。
|
||||||
|
*
|
||||||
|
* .mpk = [ tar.gz bytes ......................... ] [ meow_sig_block(定长 LE)]
|
||||||
|
*
|
||||||
|
* meow_sig_block(packed,总长 MEOW_SIG_BLOCK_LEN):
|
||||||
|
* char magic[8]; "MEOWSIG1"
|
||||||
|
* uint8_t alg; 1 = ed25519
|
||||||
|
* uint8_t flags; 0
|
||||||
|
* uint16_t pad; (0)
|
||||||
|
* char package[64]; 权威包名(== app.json.package == 文件名茎)
|
||||||
|
* uint8_t payload_sha256[32];sha256(整个 .mpk 去掉尾部 footer 的其余字节)
|
||||||
|
* uint8_t sig[64]; Ed25519(pk, msg)
|
||||||
|
* uint32_t msg_len; 实际签名的 msg 字节数(LE)
|
||||||
|
* uint32_t block_len; 本块总长(LE;= MEOW_SIG_BLOCK_LEN)
|
||||||
|
*
|
||||||
|
* 被签消息 msg = "MEOWSIG1" || alg(1B) || package_len(4B,LE) || package || payload_sha256(32B)。
|
||||||
|
* 本文件由 meow_verify 与 meow_sign 共用(meow_sign 用相对路径 include)。 */
|
||||||
|
#ifndef MEOW_SIG_H
|
||||||
|
#define MEOW_SIG_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MEOW_SIG_MAGIC "MEOWSIG1"
|
||||||
|
#define MEOW_SIG_MAGIC_LEN 8
|
||||||
|
#define MEOW_SIG_ALG_ED25519 1
|
||||||
|
#define MEOW_SIG_PKG_MAX 64
|
||||||
|
#define MEOW_SIG_SHA256_LEN 32
|
||||||
|
#define MEOW_SIG_SIG_LEN 64
|
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
uint8_t magic[MEOW_SIG_MAGIC_LEN];
|
||||||
|
uint8_t alg;
|
||||||
|
uint8_t flags;
|
||||||
|
uint16_t pad;
|
||||||
|
char package[MEOW_SIG_PKG_MAX];
|
||||||
|
uint8_t payload_sha256[MEOW_SIG_SHA256_LEN];
|
||||||
|
uint8_t sig[MEOW_SIG_SIG_LEN];
|
||||||
|
uint32_t msg_len;
|
||||||
|
uint32_t block_len;
|
||||||
|
} meow_sig_block_t;
|
||||||
|
|
||||||
|
#define MEOW_SIG_BLOCK_LEN (sizeof(meow_sig_block_t)) /* 定长 180 */
|
||||||
|
|
||||||
|
/* 按字节读取 LE 的 uint32(避免 packed 解引用对齐告警) */
|
||||||
|
static inline uint32_t meow_le32(const uint8_t *p)
|
||||||
|
{
|
||||||
|
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||||
|
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||||
|
}
|
||||||
|
static inline void meow_le32_put(uint8_t *p, uint32_t v)
|
||||||
|
{
|
||||||
|
p[0] = (uint8_t)(v & 0xff);
|
||||||
|
p[1] = (uint8_t)((v >> 8) & 0xff);
|
||||||
|
p[2] = (uint8_t)((v >> 16) & 0xff);
|
||||||
|
p[3] = (uint8_t)((v >> 24) & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MEOW_SIG_MSG_MAX (MEOW_SIG_MAGIC_LEN + 1 + 4 + MEOW_SIG_PKG_MAX + MEOW_SIG_SHA256_LEN)
|
||||||
|
|
||||||
|
/* 按设计构造被签消息 msg = "MEOWSIG1" || alg(1B) || package_len(4B,LE) || package || payload_sha256(32B)。
|
||||||
|
* 返回 msg 字节数。签名端与验签端必须用同一构造(meow_sign / meow_verify 共用)。 */
|
||||||
|
static inline unsigned meow_sig_msg_build(char *out, const char *package,
|
||||||
|
const uint8_t payload_sha256[MEOW_SIG_SHA256_LEN])
|
||||||
|
{
|
||||||
|
unsigned pkg_len = (unsigned)strlen(package);
|
||||||
|
if (pkg_len > MEOW_SIG_PKG_MAX) pkg_len = MEOW_SIG_PKG_MAX;
|
||||||
|
unsigned o = 0;
|
||||||
|
memcpy(out + o, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN); o += MEOW_SIG_MAGIC_LEN;
|
||||||
|
out[o++] = MEOW_SIG_ALG_ED25519;
|
||||||
|
meow_le32_put((uint8_t *)out + o, (uint32_t)pkg_len); o += 4;
|
||||||
|
memcpy(out + o, package, pkg_len); o += pkg_len;
|
||||||
|
memcpy(out + o, payload_sha256, MEOW_SIG_SHA256_LEN); o += MEOW_SIG_SHA256_LEN;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MEOW_SIG_H */
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* meow_signing_pub.h — 固件公钥(Ed25519 32B,编译期固化进 meow_verify)。
|
||||||
|
* 由 `meow_sign keygen` 生成(.keys/signing.key.pub),本头只含【公钥】,可入库;
|
||||||
|
* 私钥仅在 .keys/(已加入 .gitignore,绝不入库/报告)。轮换公钥=重生成本头+重编 meow_verify。 */
|
||||||
|
#ifndef MEOW_SIGNING_PUB_H
|
||||||
|
#define MEOW_SIGNING_PUB_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
static const uint8_t g_meow_pub[32] =
|
||||||
|
{
|
||||||
|
0x22, 0xa7, 0x83, 0xac, 0x28, 0xd3, 0x25, 0x6d,
|
||||||
|
0x3f, 0x84, 0x72, 0x72, 0x50, 0x5b, 0x2c, 0x61,
|
||||||
|
0x60, 0x97, 0xff, 0xc3, 0x98, 0x78, 0x14, 0x9d,
|
||||||
|
0xc7, 0x37, 0x8c, 0x58, 0x72, 0x26, 0x37, 0xf2
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* MEOW_SIGNING_PUB_H */
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,215 @@
|
|||||||
|
/* 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);
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,97 @@
|
|||||||
|
/* sha256.c — 紧凑 SHA-256(FIPS 180-4),公开领域参考写法,-Wall -Wextra 0 warning。
|
||||||
|
* 流式 context;meow_verify / meow_sign 共用。 */
|
||||||
|
#include "sha256.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const uint32_t K[64] = {
|
||||||
|
0x428a2f98u,0x71374491u,0xb5c0fbcfu,0xe9b5dba5u,0x3956c25bu,0x59f111f1u,0x923f82a4u,0xab1c5ed5u,
|
||||||
|
0xd807aa98u,0x12835b01u,0x243185beu,0x550c7dc3u,0x72be5d74u,0x80deb1feu,0x9bdc06a7u,0xc19bf174u,
|
||||||
|
0xe49b69c1u,0xefbe4786u,0x0fc19dc6u,0x240ca1ccu,0x2de92c6fu,0x4a7484aau,0x5cb0a9dcu,0x76f988dau,
|
||||||
|
0x983e5152u,0xa831c66du,0xb00327c8u,0xbf597fc7u,0xc6e00bf3u,0xd5a79147u,0x06ca6351u,0x14292967u,
|
||||||
|
0x27b70a85u,0x2e1b2138u,0x4d2c6dfcu,0x53380d13u,0x650a7354u,0x766a0abbu,0x81c2c92eu,0x92722c85u,
|
||||||
|
0xa2bfe8a1u,0xa81a664bu,0xc24b8b70u,0xc76c51a3u,0xd192e819u,0xd6990624u,0xf40e3585u,0x106aa070u,
|
||||||
|
0x19a4c116u,0x1e376c08u,0x2748774cu,0x34b0bcb5u,0x391c0cb3u,0x4ed8aa4au,0x5b9cca4fu,0x682e6ff3u,
|
||||||
|
0x748f82eeu,0x78a5636fu,0x84c87814u,0x8cc70208u,0x90befffau,0xa4506cebu,0xbef9a3f7u,0xc67178f2u
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ROR(x,n) (((x) >> (n)) | ((x) << (32 - (n))))
|
||||||
|
#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
|
||||||
|
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
|
||||||
|
#define BSIG0(x) (ROR(x,2) ^ ROR(x,13) ^ ROR(x,22))
|
||||||
|
#define BSIG1(x) (ROR(x,6) ^ ROR(x,11) ^ ROR(x,25))
|
||||||
|
#define SSIG0(x) (ROR(x,7) ^ ROR(x,18) ^ ((x) >> 3))
|
||||||
|
#define SSIG1(x) (ROR(x,17) ^ ROR(x,19) ^ ((x) >> 10))
|
||||||
|
|
||||||
|
void meow_sha256_init(meow_sha256_ctx *c)
|
||||||
|
{
|
||||||
|
c->state[0] = 0x6a09e667u; c->state[1] = 0xbb67ae85u;
|
||||||
|
c->state[2] = 0x3c6ef372u; c->state[3] = 0xa54ff53au;
|
||||||
|
c->state[4] = 0x510e527fu; c->state[5] = 0x9b05688cu;
|
||||||
|
c->state[6] = 0x1f83d9abu; c->state[7] = 0x5be0cd19u;
|
||||||
|
c->count = 0; c->buflen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sha256_block(meow_sha256_ctx *c, const uint8_t *p)
|
||||||
|
{
|
||||||
|
uint32_t w[64];
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
w[i] = ((uint32_t)p[i*4] << 24) | ((uint32_t)p[i*4+1] << 16) |
|
||||||
|
((uint32_t)p[i*4+2] << 8) | (uint32_t)p[i*4+3];
|
||||||
|
}
|
||||||
|
for (i = 16; i < 64; i++)
|
||||||
|
w[i] = SSIG1(w[i-2]) + w[i-7] + SSIG0(w[i-15]) + w[i-16];
|
||||||
|
|
||||||
|
uint32_t a = c->state[0], b = c->state[1], cc = c->state[2], d = c->state[3];
|
||||||
|
uint32_t e = c->state[4], f = c->state[5], g = c->state[6], h = c->state[7];
|
||||||
|
|
||||||
|
for (i = 0; i < 64; i++) {
|
||||||
|
uint32_t t1 = h + BSIG1(e) + CH(e,f,g) + K[i] + w[i];
|
||||||
|
uint32_t t2 = BSIG0(a) + MAJ(a,b,cc);
|
||||||
|
h = g; g = f; f = e; e = d + t1;
|
||||||
|
d = cc; cc = b; b = a; a = t1 + t2;
|
||||||
|
}
|
||||||
|
c->state[0] += a; c->state[1] += b; c->state[2] += cc; c->state[3] += d;
|
||||||
|
c->state[4] += e; c->state[5] += f; c->state[6] += g; c->state[7] += h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void meow_sha256_update(meow_sha256_ctx *c, const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
c->count += (uint64_t)len;
|
||||||
|
/* 先补满残余块 */
|
||||||
|
if (c->buflen) {
|
||||||
|
size_t need = 64 - c->buflen;
|
||||||
|
size_t take = len < need ? len : need;
|
||||||
|
memcpy(c->buf + c->buflen, data, take);
|
||||||
|
c->buflen += take; data += take; len -= take;
|
||||||
|
if (c->buflen == 64) { sha256_block(c, c->buf); c->buflen = 0; }
|
||||||
|
}
|
||||||
|
while (len >= 64) { sha256_block(c, data); data += 64; len -= 64; }
|
||||||
|
if (len) { memcpy(c->buf, data, len); c->buflen = len; }
|
||||||
|
}
|
||||||
|
|
||||||
|
void meow_sha256_final(meow_sha256_ctx *c, uint8_t out[32])
|
||||||
|
{
|
||||||
|
uint64_t bitlen = c->count * 8;
|
||||||
|
uint8_t *b = c->buf;
|
||||||
|
size_t bl = c->buflen;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
b[bl++] = 0x80; /* 填充:1 位 1 */
|
||||||
|
if (bl > 56) { /* 一块放不下长度 → 补齐到 64 后压一块 */
|
||||||
|
while (bl < 64) b[bl++] = 0;
|
||||||
|
sha256_block(c, b);
|
||||||
|
bl = 0;
|
||||||
|
}
|
||||||
|
while (bl < 56) b[bl++] = 0; /* 补到 56 */
|
||||||
|
for (i = 0; i < 8; i++) /* 大端 64 位 bit 长度 */
|
||||||
|
b[bl++] = (uint8_t)(bitlen >> (56 - i * 8));
|
||||||
|
sha256_block(c, b);
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
out[i*4] = (uint8_t)(c->state[i] >> 24);
|
||||||
|
out[i*4+1] = (uint8_t)(c->state[i] >> 16);
|
||||||
|
out[i*4+2] = (uint8_t)(c->state[i] >> 8);
|
||||||
|
out[i*4+3] = (uint8_t)(c->state[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/* sha256.h — minimal public-domain SHA-256 context/API for MeowOS .mpk payload hashing.
|
||||||
|
* Used by meow_verify (device) and meow_sign (host) to compute .mpk 的 payload_sha256
|
||||||
|
* (tar.gz 前缀哈希)。流式 API,避免整包载入内存。 */
|
||||||
|
#ifndef MEOW_SHA256_H
|
||||||
|
#define MEOW_SHA256_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t state[8];
|
||||||
|
uint64_t count; /* 已输入总字节数 */
|
||||||
|
uint8_t buf[64]; /* 未满一块的缓冲 */
|
||||||
|
size_t buflen;
|
||||||
|
} meow_sha256_ctx;
|
||||||
|
|
||||||
|
void meow_sha256_init(meow_sha256_ctx *c);
|
||||||
|
void meow_sha256_update(meow_sha256_ctx *c, const uint8_t *data, size_t len);
|
||||||
|
void meow_sha256_final(meow_sha256_ctx *c, uint8_t out[32]);
|
||||||
|
|
||||||
|
#endif /* MEOW_SHA256_H */
|
||||||
@@ -0,0 +1,816 @@
|
|||||||
|
/*
|
||||||
|
* TweetNaCl — 公有领域(public domain)加密库,作者 Bernard van Gastel 等
|
||||||
|
* 原始来源:https://tweetnacl.cr.yp.to/20140427/tweetnacl.c (20140427)
|
||||||
|
* 本文件为原样 vendor(仅去掉尾部空行),供 MeowOS meow_verify/meow_sign 使用
|
||||||
|
* Ed25519(crypto_sign_*)与 SHA-512(crypto_hash)子集。
|
||||||
|
* 编译注意:与本项目其余代码不同,本文件用 `-Wno-sign-compare` 等放宽(vendor 代码),
|
||||||
|
* 见 build_verify.sh / build_sign.sh。 */
|
||||||
|
#include "tweetnacl.h"
|
||||||
|
#define FOR(i,n) for (i = 0;i < n;++i)
|
||||||
|
#define sv static void
|
||||||
|
|
||||||
|
typedef unsigned char u8;
|
||||||
|
typedef unsigned long u32;
|
||||||
|
typedef unsigned long long u64;
|
||||||
|
typedef long long i64;
|
||||||
|
typedef i64 gf[16];
|
||||||
|
extern void randombytes(u8 *,u64);
|
||||||
|
|
||||||
|
static const u8
|
||||||
|
_0[16],
|
||||||
|
_9[32] = {9};
|
||||||
|
static const gf
|
||||||
|
gf0,
|
||||||
|
gf1 = {1},
|
||||||
|
_121665 = {0xDB41,1},
|
||||||
|
D = {0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203},
|
||||||
|
D2 = {0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406},
|
||||||
|
X = {0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169},
|
||||||
|
Y = {0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666},
|
||||||
|
I = {0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83};
|
||||||
|
|
||||||
|
static u32 L32(u32 x,int c) { return (x << c) | ((x&0xffffffff) >> (32 - c)); }
|
||||||
|
|
||||||
|
static u32 ld32(const u8 *x)
|
||||||
|
{
|
||||||
|
u32 u = x[3];
|
||||||
|
u = (u<<8)|x[2];
|
||||||
|
u = (u<<8)|x[1];
|
||||||
|
return (u<<8)|x[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 dl64(const u8 *x)
|
||||||
|
{
|
||||||
|
u64 i,u=0;
|
||||||
|
FOR(i,8) u=(u<<8)|x[i];
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
sv st32(u8 *x,u32 u)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
FOR(i,4) { x[i] = u; u >>= 8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
sv ts64(u8 *x,u64 u)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 7;i >= 0;--i) { x[i] = u; u >>= 8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vn(const u8 *x,const u8 *y,int n)
|
||||||
|
{
|
||||||
|
u32 i,d = 0;
|
||||||
|
FOR(i,n) d |= x[i]^y[i];
|
||||||
|
return (1 & ((d - 1) >> 8)) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_verify_16(const u8 *x,const u8 *y)
|
||||||
|
{
|
||||||
|
return vn(x,y,16);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_verify_32(const u8 *x,const u8 *y)
|
||||||
|
{
|
||||||
|
return vn(x,y,32);
|
||||||
|
}
|
||||||
|
|
||||||
|
sv core(u8 *out,const u8 *in,const u8 *k,const u8 *c,int h)
|
||||||
|
{
|
||||||
|
u32 w[16],x[16],y[16],t[4];
|
||||||
|
int i,j,m;
|
||||||
|
|
||||||
|
FOR(i,4) {
|
||||||
|
x[5*i] = ld32(c+4*i);
|
||||||
|
x[1+i] = ld32(k+4*i);
|
||||||
|
x[6+i] = ld32(in+4*i);
|
||||||
|
x[11+i] = ld32(k+16+4*i);
|
||||||
|
}
|
||||||
|
|
||||||
|
FOR(i,16) y[i] = x[i];
|
||||||
|
|
||||||
|
FOR(i,20) {
|
||||||
|
FOR(j,4) {
|
||||||
|
FOR(m,4) t[m] = x[(5*j+4*m)%16];
|
||||||
|
t[1] ^= L32(t[0]+t[3], 7);
|
||||||
|
t[2] ^= L32(t[1]+t[0], 9);
|
||||||
|
t[3] ^= L32(t[2]+t[1],13);
|
||||||
|
t[0] ^= L32(t[3]+t[2],18);
|
||||||
|
FOR(m,4) w[4*j+(j+m)%4] = t[m];
|
||||||
|
}
|
||||||
|
FOR(m,16) x[m] = w[m];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (h) {
|
||||||
|
FOR(i,16) x[i] += y[i];
|
||||||
|
FOR(i,4) {
|
||||||
|
x[5*i] -= ld32(c+4*i);
|
||||||
|
x[6+i] -= ld32(in+4*i);
|
||||||
|
}
|
||||||
|
FOR(i,4) {
|
||||||
|
st32(out+4*i,x[5*i]);
|
||||||
|
st32(out+16+4*i,x[6+i]);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
FOR(i,16) st32(out + 4 * i,x[i] + y[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_core_salsa20(u8 *out,const u8 *in,const u8 *k,const u8 *c)
|
||||||
|
{
|
||||||
|
core(out,in,k,c,0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_core_hsalsa20(u8 *out,const u8 *in,const u8 *k,const u8 *c)
|
||||||
|
{
|
||||||
|
core(out,in,k,c,1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const u8 sigma[16] = "expand 32-byte k";
|
||||||
|
|
||||||
|
int crypto_stream_salsa20_xor(u8 *c,const u8 *m,u64 b,const u8 *n,const u8 *k)
|
||||||
|
{
|
||||||
|
u8 z[16],x[64];
|
||||||
|
u32 u,i;
|
||||||
|
if (!b) return 0;
|
||||||
|
FOR(i,16) z[i] = 0;
|
||||||
|
FOR(i,8) z[i] = n[i];
|
||||||
|
while (b >= 64) {
|
||||||
|
crypto_core_salsa20(x,z,k,sigma);
|
||||||
|
FOR(i,64) c[i] = (m?m[i]:0) ^ x[i];
|
||||||
|
u = 1;
|
||||||
|
for (i = 8;i < 16;++i) {
|
||||||
|
u += (u32) z[i];
|
||||||
|
z[i] = u;
|
||||||
|
u >>= 8;
|
||||||
|
}
|
||||||
|
b -= 64;
|
||||||
|
c += 64;
|
||||||
|
if (m) m += 64;
|
||||||
|
}
|
||||||
|
if (b) {
|
||||||
|
crypto_core_salsa20(x,z,k,sigma);
|
||||||
|
FOR(i,b) c[i] = (m?m[i]:0) ^ x[i];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_stream_salsa20(u8 *c,u64 d,const u8 *n,const u8 *k)
|
||||||
|
{
|
||||||
|
return crypto_stream_salsa20_xor(c,0,d,n,k);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_stream(u8 *c,u64 d,const u8 *n,const u8 *k)
|
||||||
|
{
|
||||||
|
u8 s[32];
|
||||||
|
crypto_core_hsalsa20(s,n,k,sigma);
|
||||||
|
return crypto_stream_salsa20(c,d,n+16,s);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_stream_xor(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k)
|
||||||
|
{
|
||||||
|
u8 s[32];
|
||||||
|
crypto_core_hsalsa20(s,n,k,sigma);
|
||||||
|
return crypto_stream_salsa20_xor(c,m,d,n+16,s);
|
||||||
|
}
|
||||||
|
|
||||||
|
sv add1305(u32 *h,const u32 *c)
|
||||||
|
{
|
||||||
|
u32 j,u = 0;
|
||||||
|
FOR(j,17) {
|
||||||
|
u += h[j] + c[j];
|
||||||
|
h[j] = u & 255;
|
||||||
|
u >>= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const u32 minusp[17] = {
|
||||||
|
5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252
|
||||||
|
} ;
|
||||||
|
|
||||||
|
int crypto_onetimeauth(u8 *out,const u8 *m,u64 n,const u8 *k)
|
||||||
|
{
|
||||||
|
u32 s,i,j,u,x[17],r[17],h[17],c[17],g[17];
|
||||||
|
|
||||||
|
FOR(j,17) r[j]=h[j]=0;
|
||||||
|
FOR(j,16) r[j]=k[j];
|
||||||
|
r[3]&=15;
|
||||||
|
r[4]&=252;
|
||||||
|
r[7]&=15;
|
||||||
|
r[8]&=252;
|
||||||
|
r[11]&=15;
|
||||||
|
r[12]&=252;
|
||||||
|
r[15]&=15;
|
||||||
|
|
||||||
|
while (n > 0) {
|
||||||
|
FOR(j,17) c[j] = 0;
|
||||||
|
for (j = 0;(j < 16) && (j < n);++j) c[j] = m[j];
|
||||||
|
c[j] = 1;
|
||||||
|
m += j; n -= j;
|
||||||
|
add1305(h,c);
|
||||||
|
FOR(i,17) {
|
||||||
|
x[i] = 0;
|
||||||
|
FOR(j,17) x[i] += h[j] * ((j <= i) ? r[i - j] : 320 * r[i + 17 - j]);
|
||||||
|
}
|
||||||
|
FOR(i,17) h[i] = x[i];
|
||||||
|
u = 0;
|
||||||
|
FOR(j,16) {
|
||||||
|
u += h[j];
|
||||||
|
h[j] = u & 255;
|
||||||
|
u >>= 8;
|
||||||
|
}
|
||||||
|
u += h[16]; h[16] = u & 3;
|
||||||
|
u = 5 * (u >> 2);
|
||||||
|
FOR(j,16) {
|
||||||
|
u += h[j];
|
||||||
|
h[j] = u & 255;
|
||||||
|
u >>= 8;
|
||||||
|
}
|
||||||
|
u += h[16]; h[16] = u;
|
||||||
|
}
|
||||||
|
|
||||||
|
FOR(j,17) g[j] = h[j];
|
||||||
|
add1305(h,minusp);
|
||||||
|
s = -(h[16] >> 7);
|
||||||
|
FOR(j,17) h[j] ^= s & (g[j] ^ h[j]);
|
||||||
|
|
||||||
|
FOR(j,16) c[j] = k[j + 16];
|
||||||
|
c[16] = 0;
|
||||||
|
add1305(h,c);
|
||||||
|
FOR(j,16) out[j] = h[j];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_onetimeauth_verify(const u8 *h,const u8 *m,u64 n,const u8 *k)
|
||||||
|
{
|
||||||
|
u8 x[16];
|
||||||
|
crypto_onetimeauth(x,m,n,k);
|
||||||
|
return crypto_verify_16(h,x);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_secretbox(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (d < 32) return -1;
|
||||||
|
crypto_stream_xor(c,m,d,n,k);
|
||||||
|
crypto_onetimeauth(c + 16,c + 32,d - 32,c);
|
||||||
|
FOR(i,16) c[i] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_secretbox_open(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *k)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u8 x[32];
|
||||||
|
if (d < 32) return -1;
|
||||||
|
crypto_stream(x,32,n,k);
|
||||||
|
if (crypto_onetimeauth_verify(c + 16,c + 32,d - 32,x) != 0) return -1;
|
||||||
|
crypto_stream_xor(m,c,d,n,k);
|
||||||
|
FOR(i,32) m[i] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sv set25519(gf r, const gf a)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
FOR(i,16) r[i]=a[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
sv car25519(gf o)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
i64 c;
|
||||||
|
FOR(i,16) {
|
||||||
|
o[i]+=(1LL<<16);
|
||||||
|
c=o[i]>>16;
|
||||||
|
o[(i+1)*(i<15)]+=c-1+37*(c-1)*(i==15);
|
||||||
|
o[i]-=c<<16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sv sel25519(gf p,gf q,int b)
|
||||||
|
{
|
||||||
|
i64 t,i,c=~(b-1);
|
||||||
|
FOR(i,16) {
|
||||||
|
t= c&(p[i]^q[i]);
|
||||||
|
p[i]^=t;
|
||||||
|
q[i]^=t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sv pack25519(u8 *o,const gf n)
|
||||||
|
{
|
||||||
|
int i,j,b;
|
||||||
|
gf m,t;
|
||||||
|
FOR(i,16) t[i]=n[i];
|
||||||
|
car25519(t);
|
||||||
|
car25519(t);
|
||||||
|
car25519(t);
|
||||||
|
FOR(j,2) {
|
||||||
|
m[0]=t[0]-0xffed;
|
||||||
|
for(i=1;i<15;i++) {
|
||||||
|
m[i]=t[i]-0xffff-((m[i-1]>>16)&1);
|
||||||
|
m[i-1]&=0xffff;
|
||||||
|
}
|
||||||
|
m[15]=t[15]-0x7fff-((m[14]>>16)&1);
|
||||||
|
b=(m[15]>>16)&1;
|
||||||
|
m[14]&=0xffff;
|
||||||
|
sel25519(t,m,1-b);
|
||||||
|
}
|
||||||
|
FOR(i,16) {
|
||||||
|
o[2*i]=t[i]&0xff;
|
||||||
|
o[2*i+1]=t[i]>>8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int neq25519(const gf a, const gf b)
|
||||||
|
{
|
||||||
|
u8 c[32],d[32];
|
||||||
|
pack25519(c,a);
|
||||||
|
pack25519(d,b);
|
||||||
|
return crypto_verify_32(c,d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 par25519(const gf a)
|
||||||
|
{
|
||||||
|
u8 d[32];
|
||||||
|
pack25519(d,a);
|
||||||
|
return d[0]&1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sv unpack25519(gf o, const u8 *n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
FOR(i,16) o[i]=n[2*i]+((i64)n[2*i+1]<<8);
|
||||||
|
o[15]&=0x7fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
sv A(gf o,const gf a,const gf b)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
FOR(i,16) o[i]=a[i]+b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
sv Z(gf o,const gf a,const gf b)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
FOR(i,16) o[i]=a[i]-b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
sv M(gf o,const gf a,const gf b)
|
||||||
|
{
|
||||||
|
i64 i,j,t[31];
|
||||||
|
FOR(i,31) t[i]=0;
|
||||||
|
FOR(i,16) FOR(j,16) t[i+j]+=a[i]*b[j];
|
||||||
|
FOR(i,15) t[i]+=38*t[i+16];
|
||||||
|
FOR(i,16) o[i]=t[i];
|
||||||
|
car25519(o);
|
||||||
|
car25519(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
sv S(gf o,const gf a)
|
||||||
|
{
|
||||||
|
M(o,a,a);
|
||||||
|
}
|
||||||
|
|
||||||
|
sv inv25519(gf o,const gf i)
|
||||||
|
{
|
||||||
|
gf c;
|
||||||
|
int a;
|
||||||
|
FOR(a,16) c[a]=i[a];
|
||||||
|
for(a=253;a>=0;a--) {
|
||||||
|
S(c,c);
|
||||||
|
if(a!=2&&a!=4) M(c,c,i);
|
||||||
|
}
|
||||||
|
FOR(a,16) o[a]=c[a];
|
||||||
|
}
|
||||||
|
|
||||||
|
sv pow2523(gf o,const gf i)
|
||||||
|
{
|
||||||
|
gf c;
|
||||||
|
int a;
|
||||||
|
FOR(a,16) c[a]=i[a];
|
||||||
|
for(a=250;a>=0;a--) {
|
||||||
|
S(c,c);
|
||||||
|
if(a!=1) M(c,c,i);
|
||||||
|
}
|
||||||
|
FOR(a,16) o[a]=c[a];
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_scalarmult(u8 *q,const u8 *n,const u8 *p)
|
||||||
|
{
|
||||||
|
u8 z[32];
|
||||||
|
i64 x[80],r,i;
|
||||||
|
gf a,b,c,d,e,f;
|
||||||
|
FOR(i,31) z[i]=n[i];
|
||||||
|
z[31]=(n[31]&127)|64;
|
||||||
|
z[0]&=248;
|
||||||
|
unpack25519(x,p);
|
||||||
|
FOR(i,16) {
|
||||||
|
b[i]=x[i];
|
||||||
|
d[i]=a[i]=c[i]=0;
|
||||||
|
}
|
||||||
|
a[0]=d[0]=1;
|
||||||
|
for(i=254;i>=0;--i) {
|
||||||
|
r=(z[i>>3]>>(i&7))&1;
|
||||||
|
sel25519(a,b,r);
|
||||||
|
sel25519(c,d,r);
|
||||||
|
A(e,a,c);
|
||||||
|
Z(a,a,c);
|
||||||
|
A(c,b,d);
|
||||||
|
Z(b,b,d);
|
||||||
|
S(d,e);
|
||||||
|
S(f,a);
|
||||||
|
M(a,c,a);
|
||||||
|
M(c,b,e);
|
||||||
|
A(e,a,c);
|
||||||
|
Z(a,a,c);
|
||||||
|
S(b,a);
|
||||||
|
Z(c,d,f);
|
||||||
|
M(a,c,_121665);
|
||||||
|
A(a,a,d);
|
||||||
|
M(c,c,a);
|
||||||
|
M(a,d,f);
|
||||||
|
M(d,b,x);
|
||||||
|
S(b,e);
|
||||||
|
sel25519(a,b,r);
|
||||||
|
sel25519(c,d,r);
|
||||||
|
}
|
||||||
|
FOR(i,16) {
|
||||||
|
x[i+16]=a[i];
|
||||||
|
x[i+32]=c[i];
|
||||||
|
x[i+48]=b[i];
|
||||||
|
x[i+64]=d[i];
|
||||||
|
}
|
||||||
|
inv25519(x+32,x+32);
|
||||||
|
M(x+16,x+16,x+32);
|
||||||
|
pack25519(q,x+16);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_scalarmult_base(u8 *q,const u8 *n)
|
||||||
|
{
|
||||||
|
return crypto_scalarmult(q,n,_9);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_box_keypair(u8 *y,u8 *x)
|
||||||
|
{
|
||||||
|
randombytes(x,32);
|
||||||
|
return crypto_scalarmult_base(y,x);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_box_beforenm(u8 *k,const u8 *y,const u8 *x)
|
||||||
|
{
|
||||||
|
u8 s[32];
|
||||||
|
crypto_scalarmult(s,x,y);
|
||||||
|
return crypto_core_hsalsa20(k,_0,s,sigma);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_box_afternm(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k)
|
||||||
|
{
|
||||||
|
return crypto_secretbox(c,m,d,n,k);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_box_open_afternm(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *k)
|
||||||
|
{
|
||||||
|
return crypto_secretbox_open(m,c,d,n,k);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_box(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *y,const u8 *x)
|
||||||
|
{
|
||||||
|
u8 k[32];
|
||||||
|
crypto_box_beforenm(k,y,x);
|
||||||
|
return crypto_box_afternm(c,m,d,n,k);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_box_open(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *y,const u8 *x)
|
||||||
|
{
|
||||||
|
u8 k[32];
|
||||||
|
crypto_box_beforenm(k,y,x);
|
||||||
|
return crypto_box_open_afternm(m,c,d,n,k);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 R(u64 x,int c) { return (x >> c) | (x << (64 - c)); }
|
||||||
|
static u64 Ch(u64 x,u64 y,u64 z) { return (x & y) ^ (~x & z); }
|
||||||
|
static u64 Maj(u64 x,u64 y,u64 z) { return (x & y) ^ (x & z) ^ (y & z); }
|
||||||
|
static u64 Sigma0(u64 x) { return R(x,28) ^ R(x,34) ^ R(x,39); }
|
||||||
|
static u64 Sigma1(u64 x) { return R(x,14) ^ R(x,18) ^ R(x,41); }
|
||||||
|
static u64 sigma0(u64 x) { return R(x, 1) ^ R(x, 8) ^ (x >> 7); }
|
||||||
|
static u64 sigma1(u64 x) { return R(x,19) ^ R(x,61) ^ (x >> 6); }
|
||||||
|
|
||||||
|
static const u64 K[80] =
|
||||||
|
{
|
||||||
|
0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
|
||||||
|
0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
|
||||||
|
0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
|
||||||
|
0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
|
||||||
|
0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
|
||||||
|
0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
|
||||||
|
0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
|
||||||
|
0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
|
||||||
|
0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
|
||||||
|
0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
|
||||||
|
0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
|
||||||
|
0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
|
||||||
|
0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
|
||||||
|
0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
|
||||||
|
0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
|
||||||
|
0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
|
||||||
|
0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
|
||||||
|
0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
|
||||||
|
0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
|
||||||
|
0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
|
||||||
|
};
|
||||||
|
|
||||||
|
int crypto_hashblocks(u8 *x,const u8 *m,u64 n)
|
||||||
|
{
|
||||||
|
u64 z[8],b[8],a[8],w[16],t;
|
||||||
|
int i,j;
|
||||||
|
|
||||||
|
FOR(i,8) z[i] = a[i] = dl64(x + 8 * i);
|
||||||
|
|
||||||
|
while (n >= 128) {
|
||||||
|
FOR(i,16) w[i] = dl64(m + 8 * i);
|
||||||
|
|
||||||
|
FOR(i,80) {
|
||||||
|
FOR(j,8) b[j] = a[j];
|
||||||
|
t = a[7] + Sigma1(a[4]) + Ch(a[4],a[5],a[6]) + K[i] + w[i%16];
|
||||||
|
b[7] = t + Sigma0(a[0]) + Maj(a[0],a[1],a[2]);
|
||||||
|
b[3] += t;
|
||||||
|
FOR(j,8) a[(j+1)%8] = b[j];
|
||||||
|
if (i%16 == 15)
|
||||||
|
FOR(j,16)
|
||||||
|
w[j] += w[(j+9)%16] + sigma0(w[(j+1)%16]) + sigma1(w[(j+14)%16]);
|
||||||
|
}
|
||||||
|
|
||||||
|
FOR(i,8) { a[i] += z[i]; z[i] = a[i]; }
|
||||||
|
|
||||||
|
m += 128;
|
||||||
|
n -= 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
FOR(i,8) ts64(x+8*i,z[i]);
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const u8 iv[64] = {
|
||||||
|
0x6a,0x09,0xe6,0x67,0xf3,0xbc,0xc9,0x08,
|
||||||
|
0xbb,0x67,0xae,0x85,0x84,0xca,0xa7,0x3b,
|
||||||
|
0x3c,0x6e,0xf3,0x72,0xfe,0x94,0xf8,0x2b,
|
||||||
|
0xa5,0x4f,0xf5,0x3a,0x5f,0x1d,0x36,0xf1,
|
||||||
|
0x51,0x0e,0x52,0x7f,0xad,0xe6,0x82,0xd1,
|
||||||
|
0x9b,0x05,0x68,0x8c,0x2b,0x3e,0x6c,0x1f,
|
||||||
|
0x1f,0x83,0xd9,0xab,0xfb,0x41,0xbd,0x6b,
|
||||||
|
0x5b,0xe0,0xcd,0x19,0x13,0x7e,0x21,0x79
|
||||||
|
} ;
|
||||||
|
|
||||||
|
int crypto_hash(u8 *out,const u8 *m,u64 n)
|
||||||
|
{
|
||||||
|
u8 h[64],x[256];
|
||||||
|
u64 i,b = n;
|
||||||
|
|
||||||
|
FOR(i,64) h[i] = iv[i];
|
||||||
|
|
||||||
|
crypto_hashblocks(h,m,n);
|
||||||
|
m += n;
|
||||||
|
n &= 127;
|
||||||
|
m -= n;
|
||||||
|
|
||||||
|
FOR(i,256) x[i] = 0;
|
||||||
|
FOR(i,n) x[i] = m[i];
|
||||||
|
x[n] = 128;
|
||||||
|
|
||||||
|
n = 256-128*(n<112);
|
||||||
|
x[n-9] = b >> 61;
|
||||||
|
ts64(x+n-8,b<<3);
|
||||||
|
crypto_hashblocks(h,x,n);
|
||||||
|
|
||||||
|
FOR(i,64) out[i] = h[i];
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sv add(gf p[4],gf q[4])
|
||||||
|
{
|
||||||
|
gf a,b,c,d,t,e,f,g,h;
|
||||||
|
|
||||||
|
Z(a, p[1], p[0]);
|
||||||
|
Z(t, q[1], q[0]);
|
||||||
|
M(a, a, t);
|
||||||
|
A(b, p[0], p[1]);
|
||||||
|
A(t, q[0], q[1]);
|
||||||
|
M(b, b, t);
|
||||||
|
M(c, p[3], q[3]);
|
||||||
|
M(c, c, D2);
|
||||||
|
M(d, p[2], q[2]);
|
||||||
|
A(d, d, d);
|
||||||
|
Z(e, b, a);
|
||||||
|
Z(f, d, c);
|
||||||
|
A(g, d, c);
|
||||||
|
A(h, b, a);
|
||||||
|
|
||||||
|
M(p[0], e, f);
|
||||||
|
M(p[1], h, g);
|
||||||
|
M(p[2], g, f);
|
||||||
|
M(p[3], e, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
sv cswap(gf p[4],gf q[4],u8 b)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
FOR(i,4)
|
||||||
|
sel25519(p[i],q[i],b);
|
||||||
|
}
|
||||||
|
|
||||||
|
sv pack(u8 *r,gf p[4])
|
||||||
|
{
|
||||||
|
gf tx, ty, zi;
|
||||||
|
inv25519(zi, p[2]);
|
||||||
|
M(tx, p[0], zi);
|
||||||
|
M(ty, p[1], zi);
|
||||||
|
pack25519(r, ty);
|
||||||
|
r[31] ^= par25519(tx) << 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
sv scalarmult(gf p[4],gf q[4],const u8 *s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
set25519(p[0],gf0);
|
||||||
|
set25519(p[1],gf1);
|
||||||
|
set25519(p[2],gf1);
|
||||||
|
set25519(p[3],gf0);
|
||||||
|
for (i = 255;i >= 0;--i) {
|
||||||
|
u8 b = (s[i/8]>>(i&7))&1;
|
||||||
|
cswap(p,q,b);
|
||||||
|
add(q,p);
|
||||||
|
add(p,p);
|
||||||
|
cswap(p,q,b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sv scalarbase(gf p[4],const u8 *s)
|
||||||
|
{
|
||||||
|
gf q[4];
|
||||||
|
set25519(q[0],X);
|
||||||
|
set25519(q[1],Y);
|
||||||
|
set25519(q[2],gf1);
|
||||||
|
M(q[3],X,Y);
|
||||||
|
scalarmult(p,q,s);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_sign_keypair(u8 *pk, u8 *sk)
|
||||||
|
{
|
||||||
|
u8 d[64];
|
||||||
|
gf p[4];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
randombytes(sk, 32);
|
||||||
|
crypto_hash(d, sk, 32);
|
||||||
|
d[0] &= 248;
|
||||||
|
d[31] &= 127;
|
||||||
|
d[31] |= 64;
|
||||||
|
|
||||||
|
scalarbase(p,d);
|
||||||
|
pack(pk,p);
|
||||||
|
|
||||||
|
FOR(i,32) sk[32 + i] = pk[i];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const u64 L[32] = {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10};
|
||||||
|
|
||||||
|
sv modL(u8 *r,i64 x[64])
|
||||||
|
{
|
||||||
|
i64 carry,i,j;
|
||||||
|
for (i = 63;i >= 32;--i) {
|
||||||
|
carry = 0;
|
||||||
|
for (j = i - 32;j < i - 12;++j) {
|
||||||
|
x[j] += carry - 16 * x[i] * L[j - (i - 32)];
|
||||||
|
carry = (x[j] + 128) >> 8;
|
||||||
|
x[j] -= carry << 8;
|
||||||
|
}
|
||||||
|
x[j] += carry;
|
||||||
|
x[i] = 0;
|
||||||
|
}
|
||||||
|
carry = 0;
|
||||||
|
FOR(j,32) {
|
||||||
|
x[j] += carry - (x[31] >> 4) * L[j];
|
||||||
|
carry = x[j] >> 8;
|
||||||
|
x[j] &= 255;
|
||||||
|
}
|
||||||
|
FOR(j,32) x[j] -= carry * L[j];
|
||||||
|
FOR(i,32) {
|
||||||
|
x[i+1] += x[i] >> 8;
|
||||||
|
r[i] = x[i] & 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sv reduce(u8 *r)
|
||||||
|
{
|
||||||
|
i64 x[64],i;
|
||||||
|
FOR(i,64) x[i] = (u64) r[i];
|
||||||
|
FOR(i,64) r[i] = 0;
|
||||||
|
modL(r,x);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_sign(u8 *sm,u64 *smlen,const u8 *m,u64 n,const u8 *sk)
|
||||||
|
{
|
||||||
|
u8 d[64],h[64],r[64];
|
||||||
|
i64 i,j,x[64];
|
||||||
|
gf p[4];
|
||||||
|
|
||||||
|
crypto_hash(d, sk, 32);
|
||||||
|
d[0] &= 248;
|
||||||
|
d[31] &= 127;
|
||||||
|
d[31] |= 64;
|
||||||
|
|
||||||
|
*smlen = n+64;
|
||||||
|
FOR(i,n) sm[64 + i] = m[i];
|
||||||
|
FOR(i,32) sm[32 + i] = d[32 + i];
|
||||||
|
|
||||||
|
crypto_hash(r, sm+32, n+32);
|
||||||
|
reduce(r);
|
||||||
|
scalarbase(p,r);
|
||||||
|
pack(sm,p);
|
||||||
|
|
||||||
|
FOR(i,32) sm[i+32] = sk[i+32];
|
||||||
|
crypto_hash(h,sm,n + 64);
|
||||||
|
reduce(h);
|
||||||
|
|
||||||
|
FOR(i,64) x[i] = 0;
|
||||||
|
FOR(i,32) x[i] = (u64) r[i];
|
||||||
|
FOR(i,32) FOR(j,32) x[i+j] += h[i] * (u64) d[j];
|
||||||
|
modL(sm + 32,x);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unpackneg(gf r[4],const u8 p[32])
|
||||||
|
{
|
||||||
|
gf t, chk, num, den, den2, den4, den6;
|
||||||
|
set25519(r[2],gf1);
|
||||||
|
unpack25519(r[1],p);
|
||||||
|
S(num,r[1]);
|
||||||
|
M(den,num,D);
|
||||||
|
Z(num,num,r[2]);
|
||||||
|
A(den,r[2],den);
|
||||||
|
|
||||||
|
S(den2,den);
|
||||||
|
S(den4,den2);
|
||||||
|
M(den6,den4,den2);
|
||||||
|
M(t,den6,num);
|
||||||
|
M(t,t,den);
|
||||||
|
|
||||||
|
pow2523(t,t);
|
||||||
|
M(t,t,num);
|
||||||
|
M(t,t,den);
|
||||||
|
M(t,t,den);
|
||||||
|
M(r[0],t,den);
|
||||||
|
|
||||||
|
S(chk,r[0]);
|
||||||
|
M(chk,chk,den);
|
||||||
|
if (neq25519(chk, num)) M(r[0],r[0],I);
|
||||||
|
|
||||||
|
S(chk,r[0]);
|
||||||
|
M(chk,chk,den);
|
||||||
|
if (neq25519(chk, num)) return -1;
|
||||||
|
|
||||||
|
if (par25519(r[0]) == (p[31]>>7)) Z(r[0],gf0,r[0]);
|
||||||
|
|
||||||
|
M(r[3],r[0],r[1]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_sign_open(u8 *m,u64 *mlen,const u8 *sm,u64 n,const u8 *pk)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u8 t[32],h[64];
|
||||||
|
gf p[4],q[4];
|
||||||
|
|
||||||
|
*mlen = -1;
|
||||||
|
if (n < 64) return -1;
|
||||||
|
|
||||||
|
if (unpackneg(q,pk)) return -1;
|
||||||
|
|
||||||
|
FOR(i,n) m[i] = sm[i];
|
||||||
|
FOR(i,32) m[i+32] = pk[i];
|
||||||
|
crypto_hash(h,m,n);
|
||||||
|
reduce(h);
|
||||||
|
scalarmult(p,q,h);
|
||||||
|
|
||||||
|
scalarbase(q,sm + 32);
|
||||||
|
add(p,q);
|
||||||
|
pack(t,p);
|
||||||
|
|
||||||
|
n -= 64;
|
||||||
|
if (crypto_verify_32(sm, t)) {
|
||||||
|
FOR(i,n) m[i] = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FOR(i,n) m[i] = sm[i + 64];
|
||||||
|
*mlen = n;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/* tweetnacl.h — TweetNaCl 用于 MeowOS .mpk 签名/验签的最小声明头(Ed25519 + SHA-512 子集)。
|
||||||
|
* 实现:https://tweetnacl.cr.yp.to/20140427/tweetnacl.c (公有领域)。
|
||||||
|
* 仅暴露 meow_verify / meow_sign 需要的一小片 API;其余(salsa20/box/secretbox…)
|
||||||
|
* 在 tweetnacl.c 内保留但未声明,未引用即不进入最终链接。 */
|
||||||
|
#ifndef TWEETNACL_H
|
||||||
|
#define TWEETNACL_H
|
||||||
|
|
||||||
|
#define crypto_sign_BYTES 64
|
||||||
|
#define crypto_sign_PUBLICKEYBYTES 32
|
||||||
|
#define crypto_sign_SECRETKEYBYTES 64
|
||||||
|
#define crypto_hash_BYTES 64
|
||||||
|
|
||||||
|
int crypto_verify_16(const unsigned char *x, const unsigned char *y);
|
||||||
|
int crypto_verify_32(const unsigned char *x, const unsigned char *y);
|
||||||
|
int crypto_hash(unsigned char *out, const unsigned char *m, unsigned long long n);
|
||||||
|
|
||||||
|
int crypto_sign_keypair(unsigned char *public_key, unsigned char *secret_key);
|
||||||
|
int crypto_sign(unsigned char *sm, unsigned long long *smlen,
|
||||||
|
const unsigned char *m, unsigned long long n, const unsigned char *sk);
|
||||||
|
int crypto_sign_open(unsigned char *m, unsigned long long *mlen,
|
||||||
|
const unsigned char *sm, unsigned long long n, const unsigned char *pk);
|
||||||
|
|
||||||
|
#endif /* TWEETNACL_H */
|
||||||
Binary file not shown.
+136
-4
@@ -23,6 +23,7 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#define NYAI_VERSION "0.2.0"
|
#define NYAI_VERSION "0.2.0"
|
||||||
#define BUF 1024
|
#define BUF 1024
|
||||||
@@ -282,6 +283,7 @@ typedef struct {
|
|||||||
char name[256];
|
char name[256];
|
||||||
char version[64];
|
char version[64];
|
||||||
char exec[512];
|
char exec[512];
|
||||||
|
char package[64]; /* app.json "package"(权威包名,== 文件名茎;II 期绑定校验用) */
|
||||||
char author[128];
|
char author[128];
|
||||||
char routes[512];
|
char routes[512];
|
||||||
char services[512];
|
char services[512];
|
||||||
@@ -305,6 +307,7 @@ static int load_app_meta(const char *path, appmeta *m)
|
|||||||
if (bal != 0) return -1;
|
if (bal != 0) return -1;
|
||||||
if (json_str(j,"name",m->name,sizeof m->name)!=0 || !m->name[0]) return -1;
|
if (json_str(j,"name",m->name,sizeof m->name)!=0 || !m->name[0]) return -1;
|
||||||
if (json_str(j,"exec",m->exec,sizeof m->exec)!=0 || !m->exec[0]) return -1;
|
if (json_str(j,"exec",m->exec,sizeof m->exec)!=0 || !m->exec[0]) return -1;
|
||||||
|
if (json_str(j,"package",m->package,sizeof m->package)!=0) m->package[0]=0;
|
||||||
if (json_str(j,"version",m->version,sizeof m->version)!=0) m->version[0]=0;
|
if (json_str(j,"version",m->version,sizeof m->version)!=0) m->version[0]=0;
|
||||||
if (json_str(j,"author",m->author,sizeof m->author)!=0) m->author[0]=0;
|
if (json_str(j,"author",m->author,sizeof m->author)!=0) m->author[0]=0;
|
||||||
if (json_str(j,"depends",m->depends,sizeof m->depends)!=0) m->depends[0]=0;
|
if (json_str(j,"depends",m->depends,sizeof m->depends)!=0) m->depends[0]=0;
|
||||||
@@ -648,7 +651,10 @@ static int cmd_update(void)
|
|||||||
return ok ? 0 : 1;
|
return ok ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- index <dir> ---------------- */
|
/* ---------------- index <dir> ----------------
|
||||||
|
* 注意(MPK_SIGNATURE_DESIGN §5.5):索引记录的 SHA256 = 整包哈希(含尾部签名 footer 的 .mpk)。
|
||||||
|
* 必须先 `meow_sign sign <pkg>.mpk` 完成签名、再 `nyai index <dir>`,否则索引与装后的整包不一致,
|
||||||
|
* install 的 sha256_file 校验会失败。本命令不改,仅标注此顺序约束(签名分级见 install_mpk_file)。 */
|
||||||
static int cmd_index(const char *dir)
|
static int cmd_index(const char *dir)
|
||||||
{
|
{
|
||||||
if (!dir || !*dir) { fprintf(stderr,"nyai: index: 缺目录\n"); return 2; }
|
if (!dir || !*dir) { fprintf(stderr,"nyai: index: 缺目录\n"); return 2; }
|
||||||
@@ -746,6 +752,89 @@ static int cmd_list_upgradable(pkgrec *recs, int nrec)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------- II 期签名校验(MPK_SIGNATURE_DESIGN §5.1,shell-out meow_verify) ---------------- */
|
||||||
|
/* 系统包前缀:os.meow.*(防伪造系统包名;独立于 valid_pkg,不改变现有合法包名序列)。 */
|
||||||
|
static int is_system_pkg(const char *pkg)
|
||||||
|
{
|
||||||
|
return pkg && !strncmp(pkg, "os.meow.", 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* meow_verify 二进制路径:环境变量 MEOW_VERIFY_BIN 覆盖(宿主测试用),缺省 /usr/bin/meow_verify。 */
|
||||||
|
static const char *verify_bin(void)
|
||||||
|
{
|
||||||
|
const char *v = getenv("MEOW_VERIFY_BIN");
|
||||||
|
return (v && *v) ? v : "/usr/bin/meow_verify";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 调用 meow_verify --system <mpk> --package <pkg> 验签。
|
||||||
|
* out_pkg 回传校验器输出的 footer.package(成功时)。返回校验器退出码:
|
||||||
|
* 0=有效签(三重绑定①通过) / 1=有签但不通过(改包名/负载/公钥) / 2=无签名 / 127=工具不可用。 */
|
||||||
|
static int verify_mpk_signature(const char *mpk, const char *pkg, char *out_pkg, int n)
|
||||||
|
{
|
||||||
|
if (!mpk || !pkg) return 127;
|
||||||
|
if (unsafe_path(mpk)) return 127;
|
||||||
|
char cmd[1200];
|
||||||
|
snprintf(cmd, sizeof cmd, "'%s' --system '%s' --package '%s' 2>/dev/null",
|
||||||
|
verify_bin(), mpk, pkg);
|
||||||
|
if (out_pkg && n > 0) out_pkg[0] = 0;
|
||||||
|
FILE *pf = popen(cmd, "r");
|
||||||
|
if (!pf) return 127;
|
||||||
|
char line[128];
|
||||||
|
if (fgets(line, sizeof line, pf) != NULL) {
|
||||||
|
char *e = line + strlen(line);
|
||||||
|
while (e > line && (e[-1]=='\n'||e[-1]=='\r'||e[-1]==' '||e[-1]=='\t')) *--e = 0;
|
||||||
|
if (out_pkg && n > 0) {
|
||||||
|
size_t cl = strlen(line);
|
||||||
|
if (cl >= (size_t)n) cl = (size_t)n - 1;
|
||||||
|
memcpy(out_pkg, line, cl); out_pkg[cl] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int rc = pclose(pf);
|
||||||
|
if (rc == -1) return 127;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return 127;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 安装期保存「原签 .mpk」到 MeowData/<pkg>/data/<pkg>.mpk.src(导出保留签名用,§5.4)。
|
||||||
|
* 仅当本包带有效签名时调用。失败只告警(导不出签名但安装成功)。 */
|
||||||
|
static void save_mpk_source(const char *mpk, const char *pkg)
|
||||||
|
{
|
||||||
|
char sp[BUF], d[BUF], cmd[1100];
|
||||||
|
path_data(sp, sizeof sp, pkg, "data");
|
||||||
|
mkdir_p(sp);
|
||||||
|
snprintf(d, sizeof d, "%s/%s.mpk.src", sp, pkg);
|
||||||
|
snprintf(cmd, sizeof cmd, "cp '%s' '%s' 2>/dev/null", mpk, d);
|
||||||
|
if (system(cmd) != 0)
|
||||||
|
fprintf(stderr, "nyai: 警告: 保存原签包 %s.mpk.src 失败(导出将无签名)\n", pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导出保留签名:MeowData/<pkg>/data/<pkg>.mpk.src 存在 + 验签通过 + 其 app.json 版本 == 已装版本
|
||||||
|
* → 直接复制它作为导出结果(不二次签名);否则返回 -1(调用方现场重打包,无签名)。 */
|
||||||
|
static int export_preserve_sig(const char *pkg, const char *dst)
|
||||||
|
{
|
||||||
|
char sp[BUF], srcp[BUF], instj[BUF], cmd[1100];
|
||||||
|
path_data(sp, sizeof sp, pkg, "data");
|
||||||
|
snprintf(srcp, sizeof srcp, "%s/%s.mpk.src", sp, pkg);
|
||||||
|
if (access(srcp, R_OK) != 0) return -1;
|
||||||
|
char op[64];
|
||||||
|
int rc = verify_mpk_signature(srcp, pkg, op, sizeof op);
|
||||||
|
if (rc != 0) { fprintf(stderr, "nyai: export: 缓存原签包验签失败(rc=%d),回退重打包\n", rc); return -1; }
|
||||||
|
appmeta cached, inst;
|
||||||
|
if (mpk_appinfo(srcp, &cached) != 0) return -1;
|
||||||
|
snprintf(instj, sizeof instj, "%s/MeowAPPs/%s/app.json", meow_root(), pkg);
|
||||||
|
if (load_app_meta(instj, &inst) != 0) return -1;
|
||||||
|
if (strcmp(cached.version, inst.version) != 0) {
|
||||||
|
fprintf(stderr, "nyai: export: 缓存原签包版本(%s) != 已装(%s),回退重打包\n",
|
||||||
|
cached.version[0]?cached.version:"(?)", inst.version[0]?inst.version:"(?)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
char tmp[520]; snprintf(tmp, sizeof tmp, "%s.tmp", dst);
|
||||||
|
snprintf(cmd, sizeof cmd, "cp '%s' '%s' 2>/dev/null", srcp, tmp);
|
||||||
|
if (system(cmd) != 0 || access(tmp, F_OK) != 0) return -1;
|
||||||
|
if (rename(tmp, dst) != 0) { rm_rf(tmp); return -1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------- install(文件或名称) ---------------- */
|
/* ---------------- install(文件或名称) ---------------- */
|
||||||
static int install_mpk_file(const char *mpk_path)
|
static int install_mpk_file(const char *mpk_path)
|
||||||
{
|
{
|
||||||
@@ -769,8 +858,8 @@ static int install_mpk_file(const char *mpk_path)
|
|||||||
snprintf(stg,sizeof stg,"%s/MeowAPPs/.stg_%d_%s", meow_root(), (int)getpid(), pkg);
|
snprintf(stg,sizeof stg,"%s/MeowAPPs/.stg_%d_%s", meow_root(), (int)getpid(), pkg);
|
||||||
int replacing = (access(dst,F_OK)==0);
|
int replacing = (access(dst,F_OK)==0);
|
||||||
|
|
||||||
/* 签名分级预留(未实现验签):os.meow.* 系统包二期须有有效签名,无签名/验签失败一律拒装 */
|
/* 签名分级(II 期,MPK_SIGNATURE_DESIGN §5.2):有签名必须验签;os.meow.* 必须有有效签名;
|
||||||
if (!strncmp(pkg,"os.meow.", 8)) printf("nyai: 系统包 %s 需签名(二期)\n", pkg);
|
* 无签名第三方放行(告警)。验签在 ① 解到 stg 后、② 防降级前执行(stg=临时目录,失败即 rm 不落盘)。 */
|
||||||
|
|
||||||
/* ① 先解到临时目录读元数据,再决定替换(防降级 + 校验 app.json) */
|
/* ① 先解到临时目录读元数据,再决定替换(防降级 + 校验 app.json) */
|
||||||
rm_rf(stg);
|
rm_rf(stg);
|
||||||
@@ -788,7 +877,34 @@ static int install_mpk_file(const char *mpk_path)
|
|||||||
fprintf(stderr,"nyai: install: app.json 不合法(缺 name 或 exec)\n");
|
fprintf(stderr,"nyai: install: app.json 不合法(缺 name 或 exec)\n");
|
||||||
rm_rf(stg); return 1;
|
rm_rf(stg); return 1;
|
||||||
}
|
}
|
||||||
/* TODO(2A): meow_verify 验签挂点,见 MPK_SIGNATURE_DESIGN.md */
|
/* ①.5 签名分级 + 三重绑定(MPK_SIGNATURE_DESIGN §3.3/§5.2 失败行为表) */
|
||||||
|
int vr = verify_mpk_signature(mpk_path, pkg, NULL, 0);
|
||||||
|
int system_pkg = is_system_pkg(pkg);
|
||||||
|
int keep_src = 0; /* 有效签 → 安装后另存原签 .mpk.src */
|
||||||
|
if (vr == 0) {
|
||||||
|
keep_src = 1;
|
||||||
|
printf("nyai: 签名校验通过 (package=%s)\n", pkg);
|
||||||
|
} else if (vr == 1) {
|
||||||
|
fprintf(stderr,"nyai: install: 签名验证失败(内容被篡改/包名不符/公钥不符),拒绝安装\n");
|
||||||
|
rm_rf(stg); return 1;
|
||||||
|
} else if (vr == 2) {
|
||||||
|
if (system_pkg) {
|
||||||
|
fprintf(stderr,"nyai: install: 系统包 %s 必须签名(无签名),拒绝安装\n", pkg);
|
||||||
|
rm_rf(stg); return 1;
|
||||||
|
}
|
||||||
|
printf("nyai: 提示: %s 未签名(第三方包放行安装)\n", pkg);
|
||||||
|
} else {
|
||||||
|
if (system_pkg) {
|
||||||
|
fprintf(stderr,"nyai: install: meow_verify 不可用(rc=%d),os.meow.* 拒装(宁缺勿装)\n", vr);
|
||||||
|
rm_rf(stg); return 1;
|
||||||
|
}
|
||||||
|
printf("nyai: 警告: meow_verify 不可用(rc=%d),%s 未验签放行\n", vr, pkg);
|
||||||
|
}
|
||||||
|
/* 绑定(一般/②):解包 app.json.package == 文件名茎(防目录改名/改 app.json;meow_verify 已保证① footer.package==pkg) */
|
||||||
|
if (m.package[0] && strcmp(m.package, pkg) != 0) {
|
||||||
|
fprintf(stderr,"nyai: install: app.json.package(%s) != 包名(%s),拒绝\n", m.package, pkg);
|
||||||
|
rm_rf(stg); return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ② 同包名替换前的防降级门禁:新版必须 >= 已装(相等=重装,允许;<即降级拒装) */
|
/* ② 同包名替换前的防降级门禁:新版必须 >= 已装(相等=重装,允许;<即降级拒装) */
|
||||||
if (replacing) {
|
if (replacing) {
|
||||||
@@ -823,6 +939,9 @@ static int install_mpk_file(const char *mpk_path)
|
|||||||
mkdir_p(d1); mkdir_p(d2);
|
mkdir_p(d1); mkdir_p(d2);
|
||||||
printf("Setting up %s (%s) ...\n", pkg, m.version[0]?m.version:"?");
|
printf("Setting up %s (%s) ...\n", pkg, m.version[0]?m.version:"?");
|
||||||
|
|
||||||
|
/* ④.5 II 期:原签 .mpk 另存 MeowData/<pkg>/data/<pkg>.mpk.src(导出保留签名) */
|
||||||
|
if (keep_src) save_mpk_source(mpk_path, pkg);
|
||||||
|
|
||||||
/* ⑤ 安装成功后清理本次创建的 .bak_<pid>(失败回滚路径不动) */
|
/* ⑤ 安装成功后清理本次创建的 .bak_<pid>(失败回滚路径不动) */
|
||||||
if (replacing && bak[0]) { rm_rf(bak); printf("Removed backup %s\n", bak); }
|
if (replacing && bak[0]) { rm_rf(bak); printf("Removed backup %s\n", bak); }
|
||||||
printf("OK\n");
|
printf("OK\n");
|
||||||
@@ -1064,6 +1183,11 @@ static int cmd_export(const char *pkg, const char *out)
|
|||||||
snprintf(outbuf,sizeof outbuf,"%s", out && *out ? out : pkg);
|
snprintf(outbuf,sizeof outbuf,"%s", out && *out ? out : pkg);
|
||||||
if (!strstr(outbuf,".mpk")) strncat(outbuf,".mpk", sizeof outbuf-strlen(outbuf)-1);
|
if (!strstr(outbuf,".mpk")) strncat(outbuf,".mpk", sizeof outbuf-strlen(outbuf)-1);
|
||||||
snprintf(dst,sizeof dst,"%s", outbuf);
|
snprintf(dst,sizeof dst,"%s", outbuf);
|
||||||
|
/* II 期:导出保留签名 —— 若安装时存了原签 .mpk.src 且其版本==已装 → 直接复制(不二次签名)。 */
|
||||||
|
if (export_preserve_sig(pkg, dst) == 0) {
|
||||||
|
printf("OK (signed, preserved from .mpk.src)\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
printf("Exporting %s -> %s ...\n", pkg, dst);
|
printf("Exporting %s -> %s ...\n", pkg, dst);
|
||||||
char tmpp[BUF]; snprintf(tmpp,sizeof tmpp,"%s.tmp", dst);
|
char tmpp[BUF]; snprintf(tmpp,sizeof tmpp,"%s.tmp", dst);
|
||||||
int rc;
|
int rc;
|
||||||
@@ -1115,6 +1239,14 @@ static int cmd_upgrade(const char *pkg, const char *file)
|
|||||||
if (access(mpk,R_OK)!=0) { fprintf(stderr,"nyai: upgrade: 找不到包文件 %s\n", mpk); return 1; }
|
if (access(mpk,R_OK)!=0) { fprintf(stderr,"nyai: upgrade: 找不到包文件 %s\n", mpk); return 1; }
|
||||||
char dst[BUF]; snprintf(dst,sizeof dst,"%s/MeowAPPs/%s", meow_root(), pkg);
|
char dst[BUF]; snprintf(dst,sizeof dst,"%s/MeowAPPs/%s", meow_root(), pkg);
|
||||||
if (access(dst,F_OK)!=0) { fprintf(stderr,"nyai: upgrade: %s 未安装(如需全新安装请用 install)\n", pkg); return 1; }
|
if (access(dst,F_OK)!=0) { fprintf(stderr,"nyai: upgrade: %s 未安装(如需全新安装请用 install)\n", pkg); return 1; }
|
||||||
|
/* 签名分级(与 install 同规范):os.meow.* 必须有有效签名;有签必须验签;无签第三方放行。 */
|
||||||
|
{
|
||||||
|
int vr = verify_mpk_signature(mpk, pkg, NULL, 0);
|
||||||
|
int sysp = is_system_pkg(pkg);
|
||||||
|
if (vr == 1) { fprintf(stderr,"nyai: upgrade: 签名验证失败,拒绝升级\n"); return 1; }
|
||||||
|
else if (vr == 2) { if (sysp) { fprintf(stderr,"nyai: upgrade: 系统包 %s 必须签名,拒绝升级\n", pkg); return 1; } printf("nyai: 提示: %s 未签名(第三方放行)\n", pkg); }
|
||||||
|
else if (vr != 0) { if (sysp) { fprintf(stderr,"nyai: upgrade: meow_verify 不可用(rc=%d),os.meow.* 拒升\n", vr); return 1; } printf("nyai: 警告: meow_verify 不可用(rc=%d),%s 未验签放行\n", vr, pkg); }
|
||||||
|
}
|
||||||
/* 防降级门禁(与 install 同规范):新版必须 >= 已装(相等=重装允许) */
|
/* 防降级门禁(与 install 同规范):新版必须 >= 已装(相等=重装允许) */
|
||||||
{
|
{
|
||||||
char aj[BUF]; snprintf(aj,sizeof aj,"%s/app.json", dst);
|
char aj[BUF]; snprintf(aj,sizeof aj,"%s/app.json", dst);
|
||||||
|
|||||||
Binary file not shown.
Reference in new issue
Block a user