diff --git a/crates/core/src/update/tests/support_sign.rs b/crates/core/src/update/tests/support_sign.rs index d79c844..d98a8b5 100644 --- a/crates/core/src/update/tests/support_sign.rs +++ b/crates/core/src/update/tests/support_sign.rs @@ -5,15 +5,15 @@ //! //! 签名走 `ed25519-dalek` 的 `SigningKey`(与仓里验签用的 `verify_strict` 同一棵树),而**不是** //! 自写实现——M190 r1 复审把自写曲线算术整体删掉了,这里也不留第二条算法。测试钥的种子是公开的 -//! (见下),它只对 `cfg(test)` 与 `update-test-source` 构建有意义。 +//! (见下),它只对 `cfg(test)` / `update-test-source` / `update-test-key` 三档构建有意义。 use super::support::TempDir; /// 测试签名钥的 32 字节种子。 /// -/// **它是公开的,故意不保密**:它只对 `cfg(test)` 与 `update-test-source` 构建有意义 -/// (`trust::TEST_ONLY` 才认这把钥),正式构建的信任表里没有它,所以拿它签不出任何正式版本 -/// 能装的清单。正式私钥只能来自仓库 Secret(见 `packaging/docs/signing.md`)。 +/// **它是公开的,故意不保密**:它只对 `cfg(test)` / `update-test-source` / `update-test-key` 三档 +/// 构建有意义(`trust::TEST_ONLY` 才认这把钥),正式构建的信任表里没有它,所以拿它签不出任何正式 +/// 版本能装的清单。正式私钥只能来自仓库 Secret(见 `packaging/docs/signing.md`)。 pub(crate) const TEST_SIGNING_SEED: &[u8; 32] = b"ull-m190-test-key-seed-000000001"; /// 测试钥的 `key_id`(与 `trust::TEST_ONLY` 里那条一致)。 diff --git a/crates/core/src/update/trust.rs b/crates/core/src/update/trust.rs index 0735392..5ad9991 100644 --- a/crates/core/src/update/trust.rs +++ b/crates/core/src/update/trust.rs @@ -35,10 +35,12 @@ //! # ② 把打印出来的 `PinnedKey { … }` 一行贴到下面 PINNED-KEYS 标记之间 //! ``` //! -//! 测试/验收构建另有一把**只在** `cfg(test)` 与 `update-test-source` feature 里存在的测试钥: -//! 它的私钥种子就写在测试夹具里、公开可见,因此它可以签出「本仓库自己认得」的清单,但对任何正式 -//! 构建毫无意义(正式表里没有它)。这条也由 `packaging/check_test_source_off.py` 的同一道闸守着 -//! ——正式发布流水线与打包脚本都不许打开 `update-test-source`。 +//! 测试/验收构建另有一把**只在** `cfg(test)` / `update-test-source` / `update-test-key` 三档里存在的 +//! 测试钥(三档的口径见下面 `keyring()`):它的私钥种子就写在测试夹具里、公开可见,因此它可以签出 +//! 「本仓库自己认得」的清单,但对任何正式构建毫无意义(正式表里没有它,`keyring()` 也不会把它并进 +//! 信任表)。后两档(那两个 feature)各由 `packaging/check_test_source_off.py` / +//! `check_test_key_off.py` 守着 ——正式发布流水线与打包脚本都不许打开它们。 +//! (`cfg(test)` 那档不需要守门:发布构建里根本没有 `test` cfg。) use std::sync::OnceLock; @@ -80,8 +82,14 @@ pub(crate) const TEST_ONLY: &[PinnedKey] = &[PinnedKey { /// | `update-test-source` | CLI 的端到端验收(`--features update-test-source`) | 那是**另一个 target**,`cfg(test)` 不生效 | /// | `update-test-key` | `crates/presenter` 的 `[dev-dependencies]` | presenter 的更新链路用例编译 core 时既无 `cfg(test)`,也不该打开 `update-test-source`(那条会同时给出「本地目录当更新源」这条路)。这一档**只**加一把公开的测试公钥,不开任何数据源开关 | /// -/// 三者都进不了发布产物:`PRODUCTION` 之外没有任何路径能把 `TEST_ONLY` 带进 `--release` 构建 -/// (官方构建三个 feature 都不开,且 `check_test_source_off.py` / `check_test_key_off.py` 守着)。 +/// 三者都**不会被 `keyring()` 并进信任表**:正式构建永远不认这把测试钥(官方构建三个 feature 都不开, +/// 且 `check_test_source_off.py` / `check_test_key_off.py` 守着)。 +/// +/// ⚠ **别把这句话读成「产物里没有这段字节」**:这里的门是 `cfg!()`,它只让分支**不可达**,不是 +/// `#[cfg]` 的编译期剔除 —— `TEST_ONLY` 的**字节**照编在产物里(实测:无 feature 的 dev 与 +/// `--release` 两档 core rlib 里,公钥十六进制与 `test-only-key` 都还在)。「字节级不在产物里」 +/// 这一点**未证**、也未在此声称;它与「装不上公开种子签出来的清单」无关(信任表里没有它,验签仍 +/// fail-closed)。要换成 `#[cfg]` 让字节真的消失,是另一条结构性改动。 pub(crate) fn keyring() -> &'static [PinnedKey] { static ALL: OnceLock> = OnceLock::new(); ALL.get_or_init(|| { diff --git a/packaging/README.md b/packaging/README.md index 651f367..47c2ec6 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -71,7 +71,7 @@ Git Bash 下会静默落进 Docker VM(脚本内部用 `cygpath -m` 归一) | `packaging/check_publish_slots.py` | 发布名单守卫(M164/M184):名单 ⇄ 产物两个方向 + `mode: ci` 槽位无产物的警告;`--self-test` 注入 11 条回归 | ≤400 | | `packaging/check_pe_subsystem.py` | PE 子系统守卫(M196):**读产物 PE 头的 `Subsystem`**(GUI 必须 2 / CLI 必须 3),非 2 即红;`--self-test` 注入 8 条回归(红 6 / 绿 2)(见 §15) | ≤400 | | `packaging/check_test_source_off.py` | 测试通道守门(M169):workflow / 打包脚本 / `Cargo.toml default` 都不许打开 `update-test-source`;归属按 `jobs.*.steps[]` 列表项判、默认拒绝;`--self-test` 按 `tests/guard_vectors.json` 注入 | ≤400 | -| `packaging/check_test_key_off.py` | 测试公钥守门(M190 r3):把公开的测试公钥并进信任表的那条 feature 只许出现在 `crates/core/Cargo.toml` 自己的声明与 `crates/presenter/Cargo.toml` 的 `[dev-dependencies]` 段内,其余(workflow / 打包脚本 / 根 `Cargo.toml` / `.cargo/config*.toml` / presenter 的 `[dependencies]`)出现即红;`--self-test` 注入 6 条向量(4 红 / 2 绿)+ 基线 | ≤400 | +| `packaging/check_test_key_off.py` | 测试公钥守门(M190 r3):把公开的测试公钥并进信任表的那条 feature 只许出现在 `crates/core/Cargo.toml` 自己的声明与 `crates/presenter/Cargo.toml` 的 `[dev-dependencies]` 段内,其余(workflow / 打包脚本 / 根 `Cargo.toml` / `.cargo/config*.toml` / presenter 的 `[dependencies]`)出现即红;`--self-test` 注入 7 条向量(5 红 / 2 绿)+ 基线;其中一条专治「core 的声明里偷偷带子 feature」(`update-test-key = ["update-test-source"]` ⇒ 红) | ≤400 | | `packaging/tests/guard_vectors.json` | 守门自测向量(基线 + 32 条:26 红 / 6 绿),数据与代码分开 | ≤400 | | `packaging/ci-runtime-smoke.md` | §11 的细节:断言口径/阈值依据/失效模式/复跑 | ≤400 | | `packaging/docs/linux-evidence.md` | Linux 槽位的证据账(M184):现有证据盘点、WSLg 真机实测输出、xvfb 复跑、CI 增量估算、未验清单 | ≤400 | diff --git a/packaging/check_test_key_off.py b/packaging/check_test_key_off.py index c36a23c..3289c8e 100644 --- a/packaging/check_test_key_off.py +++ b/packaging/check_test_key_off.py @@ -14,12 +14,13 @@ | 允许出现的地方 | 形态 | |---|---| -| `crates/core/Cargo.toml` | 只能是它自己的声明 `update-test-key = [` | +| `crates/core/Cargo.toml` | 只能是它自己的声明,且必须是**空数组** `update-test-key = []`(写成带子 feature 的非空数组即红) | | `crates/presenter/Cargo.toml` | 只能出现在 `[dev-dependencies]` 段内 | | 本脚本 | 它必须写出这个 feature 名 | 其余任何地方(workflow、`packaging/` 的脚本、根 `Cargo.toml`、`.cargo/config*.toml`、presenter 的 -`[dependencies]`)提到它即红。`--self-test` 把这三类真实回归各注入一次,并断言合法写法不误杀。 +`[dependencies]`)提到它即红。`--self-test` 把这四类真实回归各注入一次(workflow / presenter 的 +`[dependencies]` / 根 `Cargo.toml` / core 声明里偷偷带子 feature),并断言合法写法不误杀。 python3 packaging/check_test_key_off.py python3 packaging/check_test_key_off.py --self-test @@ -65,7 +66,15 @@ GLOBS = ( ALLOWED_DECLARATION = "crates/core/Cargo.toml" ALLOWED_DEV_DEPENDENCY = "crates/presenter/Cargo.toml" -DECLARATION = re.compile(rf"^\s*{re.escape(FEATURE)}\s*=\s*\[") +#: 另一条(更宽的)测试通道 feature 的名字。**刻意拼出来**:`check_test_source_off.py` 按字面扫 +#: `packaging/**`,这里写全名会让那个守门红 —— 那是它的设计(宁可误杀也不放过),不是笔误。 +#: 本文件与它同源同一套手法,别“顺手”把它写成字面量。 +OTHER_FEATURE = "-".join(("update", "test", "source")) + +#: core 只许**自己的声明**,而且必须是**空数组** `update-test-key = []`。 +#: 这里认**内容**、不只认形状:写成带子 feature 的非空数组(`["<另一条 feature>"]`)会让这个「窄口子」 +#: 顺手把「宽口子」(环境变量 + `DirSource` 那条测试通道)拉起来 —— 那正是本守门要防的逃逸。 +DECLARATION = re.compile(rf"^\s*{re.escape(FEATURE)}\s*=\s*\[\s*\]\s*$") SECTION = re.compile(r"^\s*\[([^\]]+)\]\s*$") @@ -184,6 +193,11 @@ VECTORS = [ {"crates/core/Cargo.toml": "[features]\nupdate-test-key = []\n"}, "green", ), + ( + "core 的声明里偷偷带子 feature(形状合法、内容非法)", + {"crates/core/Cargo.toml": f'[features]\n{FEATURE} = ["{OTHER_FEATURE}"]\n'}, + "red", + ), ("注释里提到它(合法)", {"packaging/y.py": "# 见 update-test-key 的说明\n"}, "green"), ]