feat(m190): 产物署名(PE VERSIONINFO)+ 清单 Ed25519 多签名与更新前验签

两件事:让「这是谁发的」在产物与清单上都留下可验证的痕迹。

① PE 版本资源(两个 bin):`packaging/pe_version.rs` 是唯一口径(CompanyName
   「Unlucky Launcher Project」/ ProductName、FileDescription「Unlucky Launcher」/
   LegalCopyright「© 2026 … · Apache-2.0」/ 四段式版本号与 Cargo.toml 同源),被
   `crates/{cli,app-iced}/build.rs` 各 include 一次;非 windows 目标整段不执行。
   判据是读产物字节的 `packaging/check_pe_version.py`(CI 的 Windows 槽位)。

② 清单签名:`manifest.json` 旁边多一份 `manifest.json.sig`(分离签名,签的是清单**原始
   字节**),信封是 `{format, signatures:[{alg,key_id,sig,manifest_sha256}]}` —— 多签名数组,
   任意一条命中钉住的钥即放行(换钥窗口)。信任锚是 `crates/core/src/update/trust.rs`
   里的编译期公钥表(正式表为空 = fail-closed,测试钥只在 cfg(test)/update-test-source 里)。
   管线顺序改成「先验签、再解析」:`UpdateSource` 只交原文 + `.sig`,`verified_manifest`
   是唯一收口点,`Job::apply` 因此在取包**之前**就拒。新增五类错误分类
   (signature_missing / malformed / unknown_key / keyring_empty / invalid ),全部不可重试,
   CLI 的 hint 单独说人话。

③ Ed25519 验签自带实现(`update/ed25519/*`,零新依赖):scope 不含 core 的 Cargo.toml,
   用 RFC 8032 §7.1 向量 + 可锻/小阶/非规范编码的拒绝用例钉住;签名侧是
   `packaging/sign_manifest.py`(纯 Python 参考实现,零 pip 依赖,`--self-test` 同向量)。

④ 守门:`release-publish.yml` 加签名步(`set +x`,只回显公钥;没 Secret / 钥没钉 / 签不上
   一律红 ⇒ 不出 Release);`release-build.yml` 加 PE 资源读回;`release-guard.yml` 跑两个
   脚本的 `--self-test`。密钥纪律与轮换(旧钥签新钥)写在 `packaging/docs/signing.md`。

Cargo.lock:新增 winresource(build-dependency)与 toml 系;toml_datetime 1.1.2→1.1.3 是
toml 1.2.1 的硬要求(`cargo update --precise 1.1.2` 已实测被拒)。
This commit is contained in:
AskaEth committed 2026-11-09 01:59:05 +08:00
1 parent 44a0c87a11
commit b7d9b99e12
34 files changed
+2885 -104

No files matched your search

+19 -1
View File
@@ -30,6 +30,8 @@
//! manifest 清单模型 + JSON 校验 target 目标平台与包形态
//! source UpdateSource trait + 本地目录源 github GitHub Releases 源(D50)
//! verify 暂存 + 长度/摘要校验 checker 选择哪一份发布、急不急
//! signature 清单签名信封 + 验签 trust 钉进二进制的公钥表(keyring)
//! ed25519 Ed25519 严格验签(自写,无新依赖)
//! install 原子替换 + 回滚 replace_io 改名/复制/重试与跨卷判据(注入点)
//! error 分类与文案 job 检查/应用的一条龙(GUI 与 CLI 共用同一入口)
//! ```
@@ -49,17 +51,32 @@
//! `--apply-to`),属启动器外壳的编排;本模块只保证搬运本身原子且可回滚。
//! * **不装安装器形态**(msi/dmg/deb/rpm):交给系统包管理器。
//! * **不做差分/增量**:清单里没有 patch 字段,宁可每次全量,也别先上一套没验过的差分协议。
//!
//! # 信任根(M190)
//!
//! ```text
//! 源只取字节 ──> signature::verify_manifest(清单**原始字节** + `manifest.json.sig`)
//! │ 判据:钉死的公钥(trust::keyring,编译期常量)验 Ed25519
//! ├─ 过了 ──> manifest::from_json ──> checker/apply
//! └─ 不过 ──> 五类签名错误(不可重试),**什么都不会被安装**
//! ```
//!
//! 一句话:**先验签、再解析**。清单的 sha256 只保证「包体和清单对上」,清单本身可不可信由签名回答——
//! 两条判据互不替代(`tests/signature_cases.rs` 分别证明)。
pub mod channel;
pub mod checker;
mod ed25519;
pub mod error;
pub mod github;
pub mod install;
pub mod job;
pub mod manifest;
mod replace_io;
mod signature;
pub mod source;
pub mod target;
mod trust;
pub mod verify;
pub mod version;
@@ -77,7 +94,8 @@ pub use self::job::{
default_source, process_dest, staging_dir, Applied, Job, UPDATE_DIR_ENV, UPDATE_TEST_SOURCE_ENV,
};
pub use self::manifest::{Artifact, Release, ReleaseManifest};
pub use self::source::{DirSource, UpdateSource};
pub use self::signature::VerifiedSignature;
pub use self::source::{verified_manifest, DirSource, ManifestDocuments, UpdateSource};
pub use self::target::{Arch, PackageFormat, Target};
pub use self::verify::{stage, verify_file, StagedPackage};
pub use self::version::{Stage, Version, VersionError};