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
+136
-4
@@ -23,6 +23,7 @@
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#define NYAI_VERSION "0.2.0"
|
||||
#define BUF 1024
|
||||
@@ -282,6 +283,7 @@ typedef struct {
|
||||
char name[256];
|
||||
char version[64];
|
||||
char exec[512];
|
||||
char package[64]; /* app.json "package"(权威包名,== 文件名茎;II 期绑定校验用) */
|
||||
char author[128];
|
||||
char routes[512];
|
||||
char services[512];
|
||||
@@ -305,6 +307,7 @@ static int load_app_meta(const char *path, appmeta *m)
|
||||
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,"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,"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;
|
||||
@@ -648,7 +651,10 @@ static int cmd_update(void)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* ---------------- 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(文件或名称) ---------------- */
|
||||
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);
|
||||
int replacing = (access(dst,F_OK)==0);
|
||||
|
||||
/* 签名分级预留(未实现验签):os.meow.* 系统包二期须有有效签名,无签名/验签失败一律拒装 */
|
||||
if (!strncmp(pkg,"os.meow.", 8)) printf("nyai: 系统包 %s 需签名(二期)\n", pkg);
|
||||
/* 签名分级(II 期,MPK_SIGNATURE_DESIGN §5.2):有签名必须验签;os.meow.* 必须有有效签名;
|
||||
* 无签名第三方放行(告警)。验签在 ① 解到 stg 后、② 防降级前执行(stg=临时目录,失败即 rm 不落盘)。 */
|
||||
|
||||
/* ① 先解到临时目录读元数据,再决定替换(防降级 + 校验 app.json) */
|
||||
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");
|
||||
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) {
|
||||
@@ -823,6 +939,9 @@ static int install_mpk_file(const char *mpk_path)
|
||||
mkdir_p(d1); mkdir_p(d2);
|
||||
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>(失败回滚路径不动) */
|
||||
if (replacing && bak[0]) { rm_rf(bak); printf("Removed backup %s\n", bak); }
|
||||
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);
|
||||
if (!strstr(outbuf,".mpk")) strncat(outbuf,".mpk", sizeof outbuf-strlen(outbuf)-1);
|
||||
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);
|
||||
char tmpp[BUF]; snprintf(tmpp,sizeof tmpp,"%s.tmp", dst);
|
||||
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; }
|
||||
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; }
|
||||
/* 签名分级(与 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 同规范):新版必须 >= 已装(相等=重装允许) */
|
||||
{
|
||||
char aj[BUF]; snprintf(aj,sizeof aj,"%s/app.json", dst);
|
||||
|
||||
Reference in new issue
Block a user