From 05a19a9ea384e825d8a516abcbd632c455c4efa7 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 15:36:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(sec):=20=E7=AD=BE=E5=90=8D=E9=89=B4?= =?UTF-8?q?=E6=9D=83=202B=20(agent-221)=20=E2=80=94=20meow=5Fverify=20?= =?UTF-8?q?=E5=A4=9A=E5=85=AC=E9=92=A5=E6=95=B0=E7=BB=84+v2=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E5=9D=97(=E5=90=ABts,=E6=9C=AA=E6=9D=A5=E5=AE=BD?= =?UTF-8?q?=E9=99=90,=E5=85=BC=E5=AE=B9v1)+meow=5Fsign=20verify=E5=AD=90?= =?UTF-8?q?=E5=91=BD=E4=BB=A4+=E4=BA=A7=E7=AD=BEREADME;=20svc=5Fpower=20?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E8=BA=AB=E4=BB=BD=E7=B2=BE=E7=A1=AE=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E7=BB=91=E5=AE=9A(=E5=88=A0=E5=90=AF=E5=8F=91?= =?UTF-8?q?=E5=BC=8F,3=E5=8F=8D=E4=BE=8B=E9=AA=8C=E8=AF=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meow_verify→6c2e8ed2; svc_power→306df962; qemu+宿主全过 --- patch_v2/meow_sign/README.md | 132 +++++++++++++ patch_v2/meow_sign/meow_sign | Bin 74064 -> 74264 bytes patch_v2/meow_sign/meow_sign.c | 197 ++++++++++++++++--- patch_v2/meow_svc_power/meow_svc_power | Bin 91964 -> 91964 bytes patch_v2/meow_svc_power/meow_svc_power.c | 21 +- patch_v2/meow_svc_power/meow_svc_power_host | Bin 73544 -> 73544 bytes patch_v2/meow_svc_power/svc_power_authtest | Bin 74064 -> 74264 bytes patch_v2/meow_svc_power/svc_power_authtest.c | 15 +- patch_v2/meow_verify/meow_sig.h | 70 ++++++- patch_v2/meow_verify/meow_signing_pub.h | 22 ++- patch_v2/meow_verify/meow_verify | Bin 80416 -> 83072 bytes patch_v2/meow_verify/meow_verify.c | 118 +++++++---- patch_v2/meow_verify/meow_verify_host | Bin 73928 -> 74032 bytes 13 files changed, 497 insertions(+), 78 deletions(-) create mode 100644 patch_v2/meow_sign/README.md diff --git a/patch_v2/meow_sign/README.md b/patch_v2/meow_sign/README.md new file mode 100644 index 0000000..e90aa85 --- /dev/null +++ b/patch_v2/meow_sign/README.md @@ -0,0 +1,132 @@ +# meow_sign — MeowOS `.mpk` 产签/验签工具(宿主端) + +> 定位:**离线维护者侧工具**,给 `.mpk` 包打 Ed25519 签名,供设备端 `meow_verify`(固件 `/usr/bin/meow_verify`) +> 验签。本工具**不落设备、不入库私钥**。设计依据:`devdocs/plan/MPK_SIGNATURE_DESIGN.md`(§4.2 产签流程 / §3.1 块格式 / §5.1 挂点)。 + +--- + +## 0. 安全模型(先读,诚实标注) + +| 项 | 说明 | +|---|---| +| 算法 | Ed25519(非对称)。**私钥仅在离线维护机**,设备只持**公钥**(固化进 `meow_sign` 的 `meow_signing_pub.h`)。 | +| 私钥 | `.keys/signing.key`(0600)——**绝不入库**(`.gitignore` 已忽略 `.keys/`)、**绝不落设备**;全网唯一,**务必单独备份**。 | +| 设备验证 | `meow_verify`(固件)只读内嵌公钥数组验签;`os.meow.*` 包必须有有效签名,否则 nyai/launcher **拒装**(防伪造系统包名)。 | +| 签名覆盖 | `payload_sha256`(整个 tar.gz 前缀,含二进制/资源/app.json)+ 显式 `package`(包名)。改内容/改包名 → 验签失败。 | +| 诚实天花板 | 签名 + IPC 鉴权是**防误装/防伪造包名/防沿途篡改**,**不是真安全边界**:有 shell 的 root 用户可直接建目录/换 `meow_verify`。它挡住第三方 App 的「无意识越权」,挡不住 root 有意攻击(见 `MPK_SIGNATURE_DESIGN §8`)。 | + +--- + +## 1. 用法 + +```sh +meow_sign keygen # 生成密钥对:priv(0600) + .pub(32B raw) + stdout 打印 pub_hex +meow_sign sign .mpk [--v1] # 给纯 tar.gz 追加签名 footer(默认 v2 含时间戳;--v1=2A 旧版无 ts) +meow_sign verify # 用公钥(.pub 文件或 64 位 hex)验签指定 .mpk(产签后自测) +``` + +- `sign` 的 **`.mpk` 文件名茎必须等于包名**(`app.json.package`),这是三重绑定①(`meow_verify` 会二次校验)。 +- 默认产出 **v2** 块(184B):含**被签时间戳 `ts`**,验签端做「未来宽限」校验(超出 `now+86400` 拒)。 + 如需兼容旧固件(2A 的 `meow_verify` 只认 180B 的 v1)可加 `--v1` 产旧版。**新固件 `meow_verify` v1/v2 都读**。 +- `verify` 是**宿主侧镜像**(产签后、分发前确认签名有效);**设备侧真正把关的是 `meow_verify`**。 + +--- + +## 2. 产签流程(给新系统 App 签名) + +以新增 `os.meow.xxx` 为例。 + +### 2.1 首次:生成/备份密钥(仅一次) +```sh +./patch_v2/meow_sign/meow_sign keygen .keys/signing.key +# 私钥 .keys/signing.key(0600) 请离线备份;公钥 .keys/signing.key.pub 可入库/固化进固件。 +``` +> 若已存在 `.keys/signing.key`(本仓库 2A 已生成),**直接复用,勿覆盖**(覆盖会令旧包全部失效)。 + +### 2.2 打包(不含签名;.mpk = 纯 tar.gz) +```sh +mkdir -p /os.meow.xxx +# 放入 app.json + meow 二进制 + 资源;app.json 的 package 必须 == 目录名 == 文件名茎;version 单调递增(禁降级) +tar czf os.meow.xxx.mpk os.meow.xxx +``` +> `app.json` 要点:`package`(= `os.meow.xxx`)、`exec`(指向 `MeowAPPs/os.meow.xxx/` 内二进制)、`version`(≥ 已装,禁止降级)。 + +### 2.3 签名(默认 v2) +```sh +./patch_v2/meow_sign/meow_sign sign os.meow.xxx.mpk .keys/signing.key +# => sign ok (v2): ... package=os.meow.xxx ts= block_len=184 ... +``` + +### 2.4 产签后自测(可选但推荐) +```sh +./patch_v2/meow_sign/meow_sign verify os.meow.xxx.mpk .keys/signing.key.pub # 用自己公钥确认 +./patch_v2/meow_verify/meow_verify_host os.meow.xxx.mpk # 用固件内嵌公钥确认(与设备同口径) +``` + +### 2.5 装到设备 / 分发 +- 把 `os.meow.xxx.mpk` 放到卡上(如 `MeowOS/opt/nyai/` 或目标目录),用 `nyai install` 安装。 +- 安装期由 nyai/launcher 调 `meow_verify`:验签通过 + 三重绑定(footer.package==文件名茎==app.json.package + `os.meow.*` 前缀)才落盘;否则拒装并回滚。 +- **`os.meow.*` 无签名/验签失败 → 一律拒装**(防伪造系统包名);第三方包无签可直接装(但建议也签名防篡改)。 + +--- + +## 3. 三重绑定(三方各司其职) + +| 绑定 | 校验方 | 校验点 | +|---|---|---| +| ① footer.package == 文件名茎 | `meow_verify`(`--package`/推导) | 防「把 `os.meow.xxx.mpk` 改名为 `evil.mpk`」 | +| ② footer.package == 解包 `app.json.package` | nyai / launcher 安装流 | 防「改 app.json 的 package」 | +| ③ `os.meow.*` 前缀 + 必须验签过 | nyai / launcher 安装流 | 防「无签名冒充系统包」 | + +--- + +## 4. 多公钥 / 密钥轮换(2B) + +固件 `meow_verify` 内嵌**公钥数组**(`meow_signing_pub.h`),验签时**任一公钥通过即放行**。轮换 = 加钥/去钥 + 重编 `meow_verify`(随固件更新): + +```c +#define MEOW_PUB_KEY_COUNT 2 +static const uint8_t g_meow_pub[MEOW_PUB_KEY_COUNT][32] = { + { /* key #0:旧主钥(保留做跨代兼容) */ ... }, + { /* key #1:新钥 */ ... }, +}; +``` + +- **加钥**:新钥 `meow_sign keygen` 后追加到数组尾部、`MEOW_PUB_KEY_COUNT` +1、重编 `meow_verify` → 旧钥签的包(新固件仍内嵌旧钥)与新旧包都放行(平滑过渡)。 +- **去钥**:从数组移除 + 重编 → 原钥签的包在新固件**拒装**。**去钥必须与包重签协同**(先用新钥重签所有在维护的系统包)。 +- 私钥更新(新钥签新包)时,务必保证新私钥离线备份,且**旧私钥在轮换期内保留**(否则旧钥签的包失效)。 + +--- + +## 5. v1 / v2 块与兼容 + +| | v1(2A 旧版) | v2(2B 加固) | +|---|---|---| +| 总长 | 180B | 184B | +| 时间戳 | 无 | 有(`ts`,unix 秒,**被签名**) | +| 被签消息 | `"MEOWSIG1"‖alg‖pkg_len‖package‖sha256` | v1 msg ‖ `ts(4B,LE)` | +| `meow_verify` | 读取(不检 ts) | 读取(校验 ts 未来宽限,超 `now+86400` 拒;`ts==0` 视为畸形拒) | + +- 新 `meow_verify`(2B 固件)**v1/v2 都读** → 旧 2A 签的包仍可装/可导出复验。 +- **`--v1` 产出的包在旧 2A 固件可装;v2 包需 2B+ 固件**(旧固件 `meow_verify` 只认 180B,会把 184B 块误判为无签 → `os.meow.*` 拒装)。固件与工具链同版本发布即可避免此坑。 + +--- + +## 6. 明确不做(诚实) + +- **防重放 / 密钥过期 / CRL / 在线验证**:需网络+后端,超出「单机 + 卡分发」模型(`MPK_SIGNATURE_DESIGN §8`)。 +- **时间戳只做「未来异常」宽限拒绝**(防错误时钟/手工伪造未来 ts),不做「旧包过期拒装」。 +- 签名是**防误装/防伪造包名/防篡改**,不是安全边界。 + +--- + +## 7. 相关文件 + +| 文件 | 作用 | +|---|---| +| `meow_sign.c` | 产签/验签工具源码(复用 `../meow_verify` 的 `tweetnacl.c` + `sha256.c` + `meow_sig.h`) | +| `build_sign.sh` | 宿主编译(`./build_sign.sh` / `./build_sign.sh static`) | +| `../meow_verify/meow_verify.c` | 设备侧校验器(固件 `/usr/bin/meow_verify`) | +| `../meow_verify/meow_signing_pub.h` | **固件内嵌公钥数组**(仅公钥,可入库) | +| `../meow_verify/meow_sig.h` | 签名块格式 + 被签消息构造(verifier/signer 共用) | +| `.keys/` | 私钥目录(**已 gitignore,绝不入库**) | diff --git a/patch_v2/meow_sign/meow_sign b/patch_v2/meow_sign/meow_sign index ced32c45f3444e0187b8c87eee94297f787916b7..f8fca6d67be876e47141040a2019d1cec0e54fb6 100755 GIT binary patch delta 12775 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 DQc=ro diff --git a/patch_v2/meow_sign/meow_sign.c b/patch_v2/meow_sign/meow_sign.c index 0603539..49e32d9 100644 --- a/patch_v2/meow_sign/meow_sign.c +++ b/patch_v2/meow_sign/meow_sign.c @@ -1,10 +1,13 @@ -/* meow_sign.c — MeowOS .mpk 产签工具(宿主端,非设备;离线维护者侧使用)。 +/* meow_sign.c — MeowOS .mpk 产签/验签工具(宿主端,非设备;离线维护者侧使用)。 * - * 功能(MPK_SIGNATURE_DESIGN §4.2): + * 功能(MPK_SIGNATURE_DESIGN §4.2 + 2B §7): * - keygen :生成 Ed25519 密钥对。私钥(64B seed||pub,0600)写 ; * 公钥(32B hex + raw)写 .pub,并在 stdout 打印 hex(便于固化进 meow_verify)。 - * - sign .mpk :对 .mpk(当前无 footer = 纯 tar.gz)计算 payload_sha256、 - * 组被签消息、Ed25519 签名,并**尾部追加** 180B 定长 footer(.mpk = tar.gz || meow_sig_block)。 + * - sign .mpk [--v1]:对 .mpk(当前无 footer = 纯 tar.gz)计算 payload_sha256、 + * 组被签消息、Ed25519 签名,并**尾部追加**签名 footer。默认产出 **v2**(含签名时间戳 ts, + * 纳入被签消息,验签端做未来宽限校验,见 meow_verify);`--v1` 产 2A 旧版(无 ts,跨旧固件)。 + * - verify :宿主侧用任意**公钥**(hex 或 .pub 文件路径)验签指定 .mpk,输出通过/原因。 + * 用于产签后、分发前自测(对应用户定案「先验签再装」的宿主侧镜像);固件侧统一走 meow_verify。 * * 私钥绝不落设备、绝不入库(仓库 .gitignore 已忽略 .keys/)。 * 复用 ../meow_verify 的 tweetnacl.c(Ed25519)+ sha256.c。 */ @@ -15,12 +18,13 @@ #include #include #include +#include #include "../meow_verify/tweetnacl.h" #include "../meow_verify/sha256.h" #include "../meow_verify/meow_sig.h" -/* keygen 需真随机(从 /dev/urandom 读);sign 不调用 keypair。 */ +/* keygen 需真随机(从 /dev/urandom 读);sign/verify 不调用 keypair。 */ void randombytes(unsigned char *x, unsigned long long xlen) { int fd = open("/dev/urandom", O_RDONLY); @@ -55,6 +59,27 @@ static void hex_print(const char *label, const uint8_t *b, int n) printf("\n"); } +/* 十六进制串 → 字节。返回字节数(-1 非法)。 */ +static int hex_decode(const char *s, uint8_t *out, int maxn) +{ + int n = (int)strlen(s); + if ((n & 1) || n / 2 > maxn) return -1; + for (int i = 0; i < n; i += 2) { + int hi = s[i], lo = s[i + 1], v = 0; + if (hi >= '0' && hi <= '9') v = hi - '0'; + else if (hi >= 'a' && hi <= 'f') v = hi - 'a' + 10; + else if (hi >= 'A' && hi <= 'F') v = hi - 'A' + 10; + else return -1; + v <<= 4; + if (lo >= '0' && lo <= '9') v += lo - '0'; + else if (lo >= 'a' && lo <= 'f') v += lo - 'a' + 10; + else if (lo >= 'A' && lo <= 'F') v += lo - 'A' + 10; + else return -1; + out[i / 2] = (uint8_t)v; + } + return n / 2; +} + static int keygen(const char *priv_path) { uint8_t pk[32], sk[64]; @@ -69,7 +94,7 @@ static int keygen(const char *priv_path) 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); + printf("keygen ok:\n priv -> %s (0600)\n pub -> %s\n", priv_path, pub_path); hex_print("pub_hex", pk, 32); return 0; } @@ -92,6 +117,27 @@ static int file_sha256(const char *path, uint8_t out[MEOW_SIG_SHA256_LEN]) return 0; } +/* 分块 SHA-256 计算文件前 plen 字节(verify 用:payload = .mpk 去 footer 前缀)。 */ +static int sha256_prefix(const char *path, long long plen, uint8_t out[MEOW_SIG_SHA256_LEN]) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + meow_sha256_ctx c; + meow_sha256_init(&c); + uint8_t buf[8192]; + long long remaining = plen; + while (remaining > 0) { + size_t chunk = remaining > (long long)sizeof buf ? sizeof buf : (size_t)remaining; + ssize_t r = read(fd, buf, chunk); + if (r <= 0) { close(fd); return -1; } + meow_sha256_update(&c, buf, (size_t)r); + remaining -= (long long)r; + } + close(fd); + meow_sha256_final(&c, out); + return 0; +} + static int derive_pkg(const char *mpk, char *out, int n) { const char *bn = strrchr(mpk, '/'); @@ -104,7 +150,8 @@ static int derive_pkg(const char *mpk, char *out, int n) return 0; } -static int sign(const char *mpk, const char *priv_path) +/* sign:默认 v2(含 ts);legacy_v1 非 0 则产 2A 旧版 footer(无 ts)。 */ +static int sign(const char *mpk, const char *priv_path, int legacy_v1) { char pkg[MEOW_SIG_PKG_MAX]; if (derive_pkg(mpk, pkg, sizeof pkg) != 0) { @@ -127,45 +174,145 @@ static int sign(const char *mpk, const char *priv_path) 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 分离签名 */ + /* 组被签消息(v2 含 ts) + 签出 64B 分离签名 */ + uint32_t ts = legacy_v1 ? 0u : (uint32_t)time(NULL); char msg[MEOW_SIG_MSG_MAX]; - unsigned mlen = meow_sig_msg_build(msg, pkg, sha); + unsigned mlen = legacy_v1 ? meow_sig_msg_build(msg, pkg, sha) + : meow_sig_msg_build_v2(msg, pkg, sha, ts); uint8_t sm[MEOW_SIG_SIG_LEN + MEOW_SIG_MSG_MAX]; unsigned long long smlen = 0; crypto_sign(sm, &smlen, (const unsigned char *)msg, mlen, sk); if (smlen != mlen + MEOW_SIG_SIG_LEN) { fprintf(stderr, "meow_sign: sign len mismatch\n"); return 1; } - /* 构造 footer 并尾部追加 */ - meow_sig_block_t blk; + if (legacy_v1) { + meow_sig_block_t blk; + memset(&blk, 0, sizeof blk); + memcpy(blk.magic, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN); + blk.alg = MEOW_SIG_ALG_ED25519; + blk.flags = 0; /* 无 ts */ + blk.pad = 0; + snprintf(blk.package, sizeof blk.package, "%s", pkg); + memcpy(blk.payload_sha256, sha, MEOW_SIG_SHA256_LEN); + memcpy(blk.sig, sm, MEOW_SIG_SIG_LEN); + blk.msg_len = mlen; + blk.block_len = MEOW_SIG_BLOCK_LEN_V1; + int afd = open(mpk, O_WRONLY | O_APPEND); + if (afd < 0) { fprintf(stderr, "meow_sign: append %s fail: %s\n", mpk, strerror(errno)); return 1; } + ssize_t w = write(afd, &blk, MEOW_SIG_BLOCK_LEN_V1); + close(afd); + if (w != (ssize_t)MEOW_SIG_BLOCK_LEN_V1) { fprintf(stderr, "meow_sign: footer write fail\n"); return 1; } + printf("sign ok (v1): %s package=%s msg_len=%u block_len=%u payload_sha256=", + mpk, pkg, mlen, (unsigned)MEOW_SIG_BLOCK_LEN_V1); + hex_print("", sha, 32); + return 0; + } + + meow_sig_block_v2_t blk; memset(&blk, 0, sizeof blk); memcpy(blk.magic, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN); blk.alg = MEOW_SIG_ALG_ED25519; - blk.flags = 0; + blk.flags = MEOW_SIG_FLAG_TS; /* 含 ts (v2) */ blk.pad = 0; snprintf(blk.package, sizeof blk.package, "%s", pkg); memcpy(blk.payload_sha256, sha, MEOW_SIG_SHA256_LEN); memcpy(blk.sig, sm, MEOW_SIG_SIG_LEN); + blk.ts = ts; blk.msg_len = mlen; - blk.block_len = MEOW_SIG_BLOCK_LEN; - + blk.block_len = MEOW_SIG_BLOCK_LEN_V2; int afd = open(mpk, O_WRONLY | O_APPEND); if (afd < 0) { fprintf(stderr, "meow_sign: append %s fail: %s\n", mpk, strerror(errno)); return 1; } - ssize_t w = write(afd, &blk, MEOW_SIG_BLOCK_LEN); + ssize_t w = write(afd, &blk, MEOW_SIG_BLOCK_LEN_V2); 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); + if (w != (ssize_t)MEOW_SIG_BLOCK_LEN_V2) { fprintf(stderr, "meow_sign: footer write fail\n"); return 1; } + printf("sign ok (v2): %s package=%s ts=%u msg_len=%u block_len=%u payload_sha256=", + mpk, pkg, ts, mlen, (unsigned)MEOW_SIG_BLOCK_LEN_V2); hex_print("", sha, 32); return 0; } +/* 读文件尾部签名块(与 meow_verify 同逻辑)。返回块字节数;-2 太短/块长非法;-1 IO 错。 */ +static int read_tail_block(const char *path, long long fsize, uint8_t *buf, int bufn) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + uint8_t bl4[4]; + if (lseek(fd, fsize - 4, SEEK_SET) < 0) { close(fd); return -1; } + if (read(fd, bl4, 4) != 4) { close(fd); return -1; } + uint32_t blen = meow_le32(bl4); + if (blen != MEOW_SIG_BLOCK_LEN_V1 && blen != MEOW_SIG_BLOCK_LEN_V2) { close(fd); return -2; } + if (fsize < (long long)blen || (int)blen > bufn) { close(fd); return -2; } + if (lseek(fd, fsize - (long long)blen, SEEK_SET) < 0) { close(fd); return -1; } + if (read(fd, buf, blen) != (ssize_t)blen) { close(fd); return -1; } + close(fd); + return (int)blen; +} + +/* verify:宿主侧用任意公钥验签。pubkey 可为 32B 原始公钥文件路径、或 64 位 hex。 */ +static int verify(const char *mpk, const char *pubkey) +{ + struct stat st; + uint8_t pk[32]; + if (stat(pubkey, &st) == 0) { + int fd = open(pubkey, O_RDONLY); + if (fd < 0) { fprintf(stderr, "meow_sign: open pub %s fail: %s\n", pubkey, strerror(errno)); return 1; } + if (read(fd, pk, sizeof pk) != (ssize_t)sizeof pk) { close(fd); fprintf(stderr, "meow_sign: pub file not 32B\n"); return 1; } + close(fd); + } else if (hex_decode(pubkey, pk, sizeof pk) != 32) { + fprintf(stderr, "meow_sign: pubkey 非法(需 32B .pub 文件路径,或 64 位 hex)\n"); + return 1; + } + + if (stat(mpk, &st) != 0) { fprintf(stderr, "meow_sign: stat %s fail\n", mpk); return 1; } + long long fsize = (long long)st.st_size; + if (fsize < (long long)MEOW_SIG_BLOCK_LEN_V1) { fprintf(stderr, "meow_sign: no signature footer\n"); return 2; } + uint8_t buf[MEOW_SIG_BLOCK_MAX]; + int blen = read_tail_block(mpk, fsize, buf, sizeof buf); + if (blen < 0) { fprintf(stderr, "meow_sign: no signature footer (or truncated)\n"); return 2; } + if (memcmp(buf, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) != 0) { fprintf(stderr, "meow_sign: no MEOWSIG1 footer\n"); return 2; } + if (buf[8] != MEOW_SIG_ALG_ED25519) { fprintf(stderr, "meow_sign: unknown alg\n"); return 1; } + int v2 = (blen == (int)MEOW_SIG_BLOCK_LEN_V2); + if (!v2 && blen != (int)MEOW_SIG_BLOCK_LEN_V1) { fprintf(stderr, "meow_sign: bad block_len\n"); return 1; } + uint8_t want_flags = v2 ? MEOW_SIG_FLAG_TS : 0; + if ((buf[9] & MEOW_SIG_FLAG_TS) != want_flags) { fprintf(stderr, "meow_sign: flags/version mismatch\n"); return 1; } + + char pkgsig[MEOW_SIG_PKG_MAX + 1]; + memcpy(pkgsig, buf + MEOW_SIG_OFF_PKG, MEOW_SIG_PKG_MAX); + pkgsig[MEOW_SIG_PKG_MAX] = 0; + + long long plen = fsize - (long long)blen; + uint8_t sha[MEOW_SIG_SHA256_LEN]; + if (sha256_prefix(mpk, plen, sha) != 0) { fprintf(stderr, "meow_sign: read payload fail\n"); return 3; } + if (memcmp(buf + MEOW_SIG_OFF_SHA, sha, MEOW_SIG_SHA256_LEN) != 0) { + fprintf(stderr, "meow_sign: payload sha256 mismatch\n"); return 1; + } + + uint32_t ts = v2 ? meow_le32(buf + MEOW_SIG_OFF_TS) : 0; + char msg[MEOW_SIG_MSG_MAX]; + unsigned mlen = v2 ? meow_sig_msg_build_v2(msg, pkgsig, sha, ts) + : meow_sig_msg_build(msg, pkgsig, sha); + uint32_t blk_msglen = meow_le32(buf + (v2 ? MEOW_SIG_OFF_MSGLEN_V2 : MEOW_SIG_OFF_MSGLEN_V1)); + if (blk_msglen != mlen) { fprintf(stderr, "meow_sign: msg_len mismatch\n"); return 1; } + + uint8_t sm[MEOW_SIG_SIG_LEN + MEOW_SIG_MSG_MAX]; + memcpy(sm, buf + MEOW_SIG_OFF_SIG, MEOW_SIG_SIG_LEN); + memcpy(sm + MEOW_SIG_SIG_LEN, msg, mlen); + uint8_t out[MEOW_SIG_SIG_LEN + MEOW_SIG_MSG_MAX]; + unsigned long long outlen = 0; + if (crypto_sign_open(out, &outlen, sm, (unsigned long long)(MEOW_SIG_SIG_LEN + mlen), pk) != 0) { + fprintf(stderr, "meow_sign: ed25519 signature invalid\n"); + return 1; + } + printf("verify ok: %s package=%s version=%s ts=%u\n", mpk, pkgsig, v2 ? "v2" : "v1", ts); + return 0; +} + static void usage(const char *a0) { fprintf(stderr, "usage:\n" - " %s keygen 生成密钥对(priv 0600 + .pub,明文、不破坏现有)\n" - " %s sign .mpk 对 .mpk 尾部追加 Ed25519 签名 footer\n", a0, a0); + " %s keygen 生成密钥对(priv 0600 + .pub,明文、不破坏现有)\n" + " %s sign .mpk [--v1] 对 .mpk 尾部追加 Ed25519 签名 footer(默认 v2 含时间戳;--v1=2A 旧版无 ts)\n" + " %s verify 用公钥(.pub 文件或 64 位 hex)验签指定 .mpk\n", a0, a0, a0); } int main(int argc, char **argv) @@ -177,7 +324,13 @@ int main(int argc, char **argv) } if (!strcmp(argv[1], "sign")) { if (argc < 4) { usage(argv[0]); return 2; } - return sign(argv[2], argv[3]); + int legacy_v1 = 0; + for (int i = 4; i < argc; i++) if (!strcmp(argv[i], "--v1")) legacy_v1 = 1; + return sign(argv[2], argv[3], legacy_v1); + } + if (!strcmp(argv[1], "verify")) { + if (argc < 4) { usage(argv[0]); return 2; } + return verify(argv[2], argv[3]); } usage(argv[0]); return 2; diff --git a/patch_v2/meow_svc_power/meow_svc_power b/patch_v2/meow_svc_power/meow_svc_power index 01f3fbcec48ee20693fb004e350e523bffa53fe7..502f955c09b52819d623f0e9a1030982416d245e 100755 GIT binary patch delta 11575 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 delta 11710 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 delta 3304 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 ye4jul-!fl{o8L10lF=FQ4$n&8LvCIWzBGpG> 24) & 0xff); } -#define MEOW_SIG_MSG_MAX (MEOW_SIG_MAGIC_LEN + 1 + 4 + MEOW_SIG_PKG_MAX + MEOW_SIG_SHA256_LEN) +/* v1 或 v2 的 msg 均不超过此上限(v1 109 / v2 113)。 */ +#define MEOW_SIG_MSG_MAX (MEOW_SIG_MAGIC_LEN + 1 + 4 + MEOW_SIG_PKG_MAX + MEOW_SIG_SHA256_LEN + 4) -/* 按设计构造被签消息 msg = "MEOWSIG1" || alg(1B) || package_len(4B,LE) || package || payload_sha256(32B)。 +/* 按设计构造 v1 被签消息 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]) @@ -74,4 +116,14 @@ static inline unsigned meow_sig_msg_build(char *out, const char *package, return o; } +/* 构造 v2 被签消息 = v1 msg || ts(4B,LE)。返回 msg 字节数(= v1 msg + 4)。 */ +static inline unsigned meow_sig_msg_build_v2(char *out, const char *package, + const uint8_t payload_sha256[MEOW_SIG_SHA256_LEN], + uint32_t ts) +{ + unsigned o = meow_sig_msg_build(out, package, payload_sha256); + meow_le32_put((uint8_t *)out + o, ts); o += 4; + return o; +} + #endif /* MEOW_SIG_H */ diff --git a/patch_v2/meow_verify/meow_signing_pub.h b/patch_v2/meow_verify/meow_signing_pub.h index 3ce23ec..82a1923 100644 --- a/patch_v2/meow_verify/meow_signing_pub.h +++ b/patch_v2/meow_verify/meow_signing_pub.h @@ -1,17 +1,27 @@ /* meow_signing_pub.h — 固件公钥(Ed25519 32B,编译期固化进 meow_verify)。 * 由 `meow_sign keygen` 生成(.keys/signing.key.pub),本头只含【公钥】,可入库; - * 私钥仅在 .keys/(已加入 .gitignore,绝不入库/报告)。轮换公钥=重生成本头+重编 meow_verify。 */ + * 私钥仅在 .keys/(已加入 .gitignore,绝不入库/报告)。 + * + * 2B 多钥兼容(MPK_SIGNATURE_DESIGN §4.3 轮换铺路):固件内嵌**公钥数组**,验签时任一公钥通过即放行。 + * 轮换/增补公钥=重写本头数组(g_meow_pub[MEOW_PUB_KEY_COUNT][])+重编 meow_verify(固件更新)。 + * 保持旧钥在数组前部可作「跨代兼容」(旧钥签的包在新固件仍验过);从尾部追加新钥; + * 若某钥彻底作废,从数组移除即可(注意:会令旧钥签的包在新固件拒装——轮换需与包重签协同)。 + * MEOW_PUB_KEY_COUNT 为当前内嵌公钥数(可增)。 */ #ifndef MEOW_SIGNING_PUB_H #define MEOW_SIGNING_PUB_H #include -static const uint8_t g_meow_pub[32] = +#define MEOW_PUB_KEY_COUNT 1 /* 当前固件内嵌公钥数;增补即在数组追加并 +1、重编 meow_verify */ + +static const uint8_t g_meow_pub[MEOW_PUB_KEY_COUNT][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 + { /* key #0:维护者主钥(.keys/signing.key.pub;与 2A 同钥,跨代兼容) */ + 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 */ diff --git a/patch_v2/meow_verify/meow_verify b/patch_v2/meow_verify/meow_verify index 2dcc4705c37a889f2888f6afb79cd7ee9adffab6..9c8bc1bae544d229d82e12f5fd1dd5b69337155b 100755 GIT binary patch delta 15193 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 delta 12825 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 s9xI}{tG=DaogqZ>JYQwa # 验签(默认系统模式:固件公钥 + 绑定①) @@ -15,7 +18,7 @@ * meow_verify --has-sig # 只探测是否存在 MEOWSIG1 footer:输出 yes/no * meow_verify --strip # 输出去 footer 的干净 tar.gz 到 stdout(无 footer 则原样复制) * - * 退出码:0=通过;1=有签但不通过(篡改/包名不符/公钥不符/格式错);2=无签名 footer;3=用法/IO 错误。 + * 退出码:0=通过;1=有签但不通过(篡改/包名不符/公钥不符/格式错/ts异常);2=无签名 footer;3=用法/IO 错误。 */ #include #include @@ -23,12 +26,14 @@ #include #include #include +#include #include "tweetnacl.h" #include "sha256.h" #include "meow_sig.h" -/* 固件公钥(编译期固化,32B Ed25519)。由 meow_sign keygen 生成后写死于此头。 */ +/* 固件公钥数组(编译期固化,多钥 Ed25519)。由 meow_sign keygen 生成后写死于此头; + * 轮换/增补公钥=改数组+MEOW_PUB_KEY_COUNT+重编 meow_verify(见 meow_signing_pub.h)。 */ #include "meow_signing_pub.h" /* 验签侧绝不调用 keypair,但 tweetnacl.c 的 crypto_sign_keypair 引用了 randombytes; @@ -46,17 +51,22 @@ static int file_size(const char *path, long long *sz) 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) +/* 读文件尾部签名块:先读最后 4 字节 block_len 判版本(v1=180 / v2=184),再整块读入 buf。 + * 返回实际块字节数(>0);-2 太短/块长非法(无签名空间);-1 IO 错。magic 校验留给调用方。 */ +static int read_block_bytes(const char *path, long long fsize, uint8_t *buf, int bufn) { - 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); + uint8_t bl4[4]; + if (lseek(fd, fsize - 4, SEEK_SET) < 0) { close(fd); return -1; } + if (read(fd, bl4, 4) != 4) { close(fd); return -1; } + uint32_t blen = meow_le32(bl4); + if (blen != MEOW_SIG_BLOCK_LEN_V1 && blen != MEOW_SIG_BLOCK_LEN_V2) { close(fd); return -2; } + if (fsize < (long long)blen || (int)blen > bufn) { close(fd); return -2; } + if (lseek(fd, fsize - (long long)blen, SEEK_SET) < 0) { close(fd); return -1; } + if (read(fd, buf, blen) != (ssize_t)blen) { close(fd); return -1; } close(fd); - if (n != (ssize_t)MEOW_SIG_BLOCK_LEN) return -1; - return 0; + return (int)blen; } /* 分块 SHA-256 计算文件前 plen 字节。返回 0 成功,-1 IO/读错。 */ @@ -98,51 +108,80 @@ 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; /* 无签名空间 */ + if (fsize < (long long)MEOW_SIG_BLOCK_LEN_V1) 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; /* 格式错 */ + uint8_t buf[MEOW_SIG_BLOCK_MAX]; + int blen = read_block_bytes(mpk, fsize, buf, sizeof buf); + if (blen < 0) return blen == -2 ? 2 : 3; + if (memcmp(buf, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) != 0) return 2; /* 无 MEOWSIG1 footer */ + if (buf[8] != MEOW_SIG_ALG_ED25519) return 1; /* 算法不符 */ - char pkgbuf[MEOW_SIG_PKG_MAX]; + int v2 = (blen == (int)MEOW_SIG_BLOCK_LEN_V2); + if (!v2 && blen != (int)MEOW_SIG_BLOCK_LEN_V1) return 1; /* 块长未知:格式错 */ + uint8_t want_flags = v2 ? MEOW_SIG_FLAG_TS : 0; + if ((buf[9] & MEOW_SIG_FLAG_TS) != want_flags) return 1; /* flags 与版本不符 */ + + char pkgsig[MEOW_SIG_PKG_MAX + 1]; + memcpy(pkgsig, buf + MEOW_SIG_OFF_PKG, MEOW_SIG_PKG_MAX); + pkgsig[MEOW_SIG_PKG_MAX] = 0; /* 保证 NUL 终止 */ + + char pkgbuf[MEOW_SIG_PKG_MAX + 1]; 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); + if (strcmp(pkgbuf, pkgsig) != 0) { + fprintf(stderr, "meow_verify: package mismatch (want %s, sig %s)\n", pkgbuf, pkgsig); return 1; } /* payload 哈希 */ - long long plen = fsize - (long long)MEOW_SIG_BLOCK_LEN; + long long plen = fsize - (long long)blen; 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) { + if (memcmp(buf + MEOW_SIG_OFF_SHA, sha, MEOW_SIG_SHA256_LEN) != 0) { fprintf(stderr, "meow_verify: payload sha256 mismatch (content tampered)\n"); return 1; } - /* 重组被签消息 + 验签 */ + /* 重组被签消息(v2 含 ts)+ 验签(多公钥,任一通过即放行) */ + uint32_t ts = v2 ? meow_le32(buf + MEOW_SIG_OFF_TS) : 0; 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; } + unsigned mlen; + if (v2) mlen = meow_sig_msg_build_v2(msg, pkgsig, sha, ts); + else mlen = meow_sig_msg_build(msg, pkgsig, sha); + uint32_t blk_msglen = meow_le32(buf + (v2 ? MEOW_SIG_OFF_MSGLEN_V2 : MEOW_SIG_OFF_MSGLEN_V1)); + if (blk_msglen != mlen) { fprintf(stderr, "meow_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, buf + MEOW_SIG_OFF_SIG, MEOW_SIG_SIG_LEN); memcpy(sm + MEOW_SIG_SIG_LEN, msg, mlen); uint8_t out[MEOW_SIG_SIG_LEN + MEOW_SIG_MSG_MAX]; - unsigned long long outlen = 0; - if (crypto_sign_open(out, &outlen, sm, (unsigned long long)(MEOW_SIG_SIG_LEN + mlen), g_meow_pub) != 0) { + int ok = 0; + for (int i = 0; i < MEOW_PUB_KEY_COUNT; i++) { + unsigned long long outlen = 0; + if (crypto_sign_open(out, &outlen, sm, (unsigned long long)(MEOW_SIG_SIG_LEN + mlen), + g_meow_pub[i]) == 0) { ok = 1; break; } + } + if (!ok) { fprintf(stderr, "meow_verify: ed25519 signature invalid\n"); return 1; } - printf("%s\n", blk.package); + /* 2B:v2 块签名时间戳未来宽限校验(仅防「未来异常」,防重放/过期不做) */ + if (v2) { + if (ts == 0) { fprintf(stderr, "meow_verify: ts field is zero (malformed)\n"); return 1; } + time_t now = time(NULL); + if ((time_t)ts > now + (time_t)MEOW_SIG_TS_GRACE) { + fprintf(stderr, "meow_verify: signature ts in future beyond grace (ts=%u now=%ld)\n", + ts, (long)now); + return 1; + } + } + + printf("%s\n", pkgsig); return 0; } @@ -153,11 +192,11 @@ static int strip_mpk(const char *mpk) 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; + if (fsize >= (long long)MEOW_SIG_BLOCK_LEN_V1) { + uint8_t buf[MEOW_SIG_BLOCK_MAX]; + int blen = read_block_bytes(mpk, fsize, buf, sizeof buf); + if (blen >= 0 && memcmp(buf, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) == 0) { + payload = fsize - (long long)blen; has_sig = 1; } } @@ -180,10 +219,11 @@ 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); + if (fsize < (long long)MEOW_SIG_BLOCK_LEN_V1) { printf("no\n"); return 2; } + uint8_t buf[MEOW_SIG_BLOCK_MAX]; + int blen = read_block_bytes(mpk, fsize, buf, sizeof buf); + if (blen < 0) { printf("no\n"); return 2; } + int h = (memcmp(buf, MEOW_SIG_MAGIC, MEOW_SIG_MAGIC_LEN) == 0); printf(h ? "yes\n" : "no\n"); return h ? 0 : 2; } diff --git a/patch_v2/meow_verify/meow_verify_host b/patch_v2/meow_verify/meow_verify_host index b6e23e49bc733c0eb68a4a0e99735126e5cc2845..38b6b5e1ec55e611a9977b0ff06f648cf6fabe7e 100755 GIT binary patch delta 9710 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 delta 8899 zcmc&)dwf(ymae+D(ln_A*eeUgTjn5hKUallqP~tzz(3B(NR0RBZj-*xwkHcxc;$!ZT)_? zzpAfJojP^uRNbnZH2uT2dzY;)+3D@ijN~#=Hg6K0%22*h6sRr{QIokQI@OWfDgI(} z@uecDy7=896Zsut7x-CXQPd!jZOh}2iVE9E9u%waI~>v@`QJ%S%TMPvQK%;I^`w$N zWc*GueqS(tHyght@^@3zwaq7^hH{bG$1O_wxc#4E%nm$mJX-K~aBUQeAWw-im4$#* zPpR3cx3LSz6SvJMtIQ4uQQ2&-TxxI4iE2Y7l20n?kh1}vrjoHZ(cZ_!1EQ$u`fEVpF`FusI!S~us?ux_h9W}c#KQr#l-c? z@4?23Tfoi+>+QkDiN|AHDe*n}F7Hv>U+hGi*U`q;gLR5;!2SztZ4Wk4q{X_#;Mll+ zfgbEYF$3(iVC#CY$>IU9E5SDOU{l3QU|$5=)Po%?j>NjMMsQZ&y<*pvH?q`2E$W;P z$>7L}HyR@V=jy3l4y8n7^c~ndrSA$Zn&J}twgcTO`Zab>QiJDe9F6BxmK%mBJ3Y1Q zR8@TAnVb7JcHQM{ys#vp@q)^?a^~%7pU1j53$*EWwkS_!)rNe>)Gh_>YE*Am+0v_n z405F`P`OXnH6O4Yb(zq4e4e*!2^0fjTbFYbK)!i#*G@h!u4xPp(KrQ9DxT!0N`l3$CBd7Hm!K0CNrV!%v_w4IFRkFu=qzAkh3#tc*04HY z>kBA*jdF@n{u9c~D5o0b=ft6Y8GT3#iykj7^eZs)v&W0k@kRbSA>DFDFKW;7c1_%; zbnat}HirmXKR$RDX~wXi(RFMR3x?Iit!o&oXS!ab@IYG_93nH4nePeS3fWU|t6yP@ zs6Fwl{OVxlt#}-P4Wwr*WT-ye=h;-YJfkY8>*BxT6a4TNdAyAAugT+Hm{0&wau{_< z4kO3)cG_~zl8*IiTY6D99Q||c+A(wF@INz_l;Ktn(Ib}k_c#Zm!!X$#nc4*dwmHPE z{xjpS1Jp4uMhO&y9fNp8cpQuUI~%)I7CcqO8&9js)-#E$>x>aKfy(U{;ZAmb3}beMCBhL7hvaKg`K&7L;f^e+^!~VT|E$P1$Trvm0D$}$-1Tqi~TQ~bi2HSj*L$I#sph3#!W&V$rw3m2=M!+MPM6vl`M4^f1l zxmjt{)wlx`<;VOSt0K^L1`66IvEbcM=2ckHS%lQ*?<<|xqN^&58SV|yiaX{ny==aH ziLAPu3_#mJ>=K$MHK^DEZD~-7SiNL9Ehk3q_7n#{g`z{yfq>Zh0V!pC5j;?t zShp&*p>b_n_@7%(qSIVo?w*rmKzP`fC&L=+a?_Nx`fbJGk8HtH@7RjC zj|+YVgY?#SO3u{SgBM$OmmE`-Tx0HQ`UKy&pS`5F?kTz0`fkY^+4u98{(#PEUggeX z7=1e;Gn^F@#Op4!X3K=2-iLMFEjGGR3q!1FT(CtBG=wHT>O+mAZ7Y}7;hCGuF0IAx z6|Xh+OI|Df>`3!lF?|*@WyJFww-L|nDhoD2PfHXlIz0$$8LRAB#Pc_ZXDk*YY{H1w z_sm(&MW_`SVTw8{OkX5B7%QsBF(XkjYZ7zzRm=OswEAl&%B$xx&6~@7Am9m4SN?qeJr4W6m^Dp1hrh zM0CnU)NWGaaZyr8qrovB%2hu+02gNPvAMp*MDC5`4jTgj! zW#sT>;!wsoGA6 z@v_S{5ibJmb5SrPPl42&A>#<18Zv?4vmsyb6=GLbvvS?{Vp(SQd|Nb6DOJS{WA5OuHSZd8lH2NPHjfwEJy$9t&xm85-zxEE#IoWq`N8H} z#!cin3vayoVddXXi=2|jlu4>+F4@Ju6!(sw$UhXXjISeV>V$jw>tg4GJQSM0o>0ui zFz-GwxAa`|4P~5TgTR6P#!1&k`|192HA?eab^TV{Qii>)>vRHN3v}S+umhM4JPq^! zr@f==K43X;IWPp=Ot@Ru!@wPTbv+4s2EV843WHc z`PtwDx;`I7HgF~IMqnLqDR46|1l$2kYSZ<7z{wv#5pX3iI*PFd;9%gJz&ja&MKt38B0)4=L0oMR`e}RD!v84kp z2krnmVi-FQECjCoCq@EX2do9w0oMStztZ(4;11wnpywO32iAUzcCql#ckn1MyAuY9 zM`mPA$bJ(wu#}bdNvCU89-dlo`NSa+=f$%QTnUg;HXb_dZhKRFH6!PmFsfkVr8Eq= zdORWU0b)zz^EcX`i7FGedbR9HWm@dfBZodX7|%-kG7qX-@MOOwa<3cZr(Wsm-SL*L zcOYagYdR&q$^NABJFdJGpFbHYJ|rC&Bc6PO<$rqH!dRh>kz2fA|5m7fHdqP}MKGeSkxfgNjvAjgxt_Rlu z&Tu};#Jvcv1>9idDG}EKZa28u24ivu!8x#cCQFVcgxuu?N3MVinv~LwwrA8bq11D+ zx4d7OgH{+;8o7%S>COhX035ZaM4SiQ${rkz`C4%6-WRDAIjPH$t)_)a@<%dLh zFK(*Hjd~r~kHsSuSEv4foCgaun(RSx<8i>pd2QloMY_EZ*_CZVsm!(Cifr@;VpL^r zR6Vjg#N5iO>~A9HIVgTxS>}(!k_>%#x#ezfr+aYZu|jakSjvX?O5+_Hm1j6++qjk! zSB?6lpQ)!=Sc-ZF>g!Aow(wF4qcTxdm6`e?>gHoxNP--tY&sGg8Yo5$wQ zvBnMMrnBvQb1 z;Vhu+kX-Tt?K=k{9YDMwP9n+_@{{uWBS+&Ic_A4yTX78!45aF;hy zSQK<;@D5SYJ(4$wRmjg0o4Q9jBNt7LWIiQ_Xz$K&Hoz)M7@U#T3+Kg|+#}{*%y6EC zpDA_e`a(jRsF=LaACmq5KjA3$f9*J;6(~GW_$FuLE#Iks#~Yq(=dKiMbTl$6sqlkG zb8_zD>j?&^F|0u=r80d-9H91PAp_$WsY;xoVj8Rou#^z0ES^PA=*gIQmGx)V=gywB zDs3U!0rZ&SWY%ZW%LNj&kqURIiL8UhqLVX!rZdLlyk7UUM^f|m|M^w$1J7U!^n`Gz4=EZAEF5vpe>8y z2`NuY&RXo2j+eDfQ9Pr-`ao;Ze@BKhOtt)QYw=$oLtXl`&M3@+^}W}s?lhVuu&|bC z$1Du{r8dsabGSpBYv&n$&p=Dg#1#0Ilv(^i>1Qhf_L=^<)F=n_!4ChupHO`iRmtjHT=62|Kv+1ZkxEj3%O{rQrwmpr}5DBUUf9ldxu9nruJOa8PW zjkEDRvLcjbFw7cpt>izJe2ZDl?vwobbdzX6nbHH2_qdHBv(5{vCGX8N`K2bXM~L^s zqHVI`by;x*-UwuA?GTd)Oa2L|s65Lgnk4^w$s6x-aAEmIvEnA=m4XtUfjq?x@fbhO zi1dRLkQL>nW*JgO+GO!%W;tL2`=jJTz4%5;e$wUgFIe(F@<#vcC98o925Y0i_e{f5 zpXArdj{he44#`^v{X_Eer)xt}W}!6L^(D@LFvXHQS5B zuba_EB|Rh6uG0=s^#-Z693Q+Xc?{zidtT#l+!fV=YNv22i9AXx0FlxL!U1)TUg|zE zJN?SE^&)9ie)=M}8A=N10zgCY$`h8kk{KH!`EZUj#soH6!v}HKj7hS3?O2mPD64Of zeECq5koyC>OY+HAntYO!$CqX~C}Ak(FBtHPol?F~ZtFHFzXv>xxwi{Hl=9Ptn}Q3Z ze8kimr#a(ssz@^-Un`~3M6)zgTZB@|0(3!+*d{fUOAQZbn@IRkL%856DU~e8XKkP_ zf_L^f7FqUAGAH3R*(_9GYV}Ix@0%usP3SVf-#5y%#FuPJg{5%1ELlG?-*q&u9NyVN#15Gr@pu}$%^gTDhH;fRn`a0oq-wGZ;0&H3i@MeJ~d$OSCDcl zKV1%IW$W2;uEKIzDYyWT+e*fny=}wrDJ4pNv$WEh%B!?e{1bqGimcuutLx2HtX%Rf za*C~Oah~MkvOxc$C4ch}y