Files
DevFlow/docs/01-技术文档/签名与发布SOP-2026-10-10.md
T
lxy 78dd079344 文档: 签名发布SOP与两份走查报告落档
- 签名与发布 SOP: 签名→打包→发布清单→自动升级全链可重复执行
- 上下文管理五维走查全文(瓶颈清单已登记 939)
- 版本升级链走查报告
2026-10-11 07:38:02 +08:00

10 KiB

DevFlow 签名与发布 SOP

2026-10-10 制定。适用对象:上游发布者(你)。目标:让「签名 → 打包 → 发布清单 → 客户端自动升级」这条链可重复执行、可验证、密钥可控。 相关文档:《版本更新与自动升级-设计-2026-10-09.md》(链路设计)、《团队级基建-部署与运维-2026-10-05.md》(分发设施)。


一、密钥体系一句话

DevFlow 用 minisign / ed25519 非对称签名:

角色 文件 位置 作用
私钥(绝密) devflow.key %USERPROFILE%\.tauri\ 签名安装包,泄露=可伪造升级
公钥(公开) devflow.key.pub 同上 验签,同时硬编码进客户端
公钥副本 pubkey 字段 src-tauri/tauri.conf.json plugins.updater.pubkey 编译进 exe,客户端用它验签

核心约束:客户端 exe 里的公钥是编译期固化的。换密钥 = 换公钥 = 已分发的老客户端永远无法自动升级到新密钥签的包(只能手动重装)。所以密钥轮换是断链级操作,非必要不做。


二、密钥文件格式(排障必知)

Tauri 私钥文件是 base64 文本包 base64 的两层结构:

外层 base64 → 解码得文本,两行:
  L1  untrusted comment: ...
  L2  <内层 base64>  → 解码得 payload 字节

payload 布局:

长度 类型 含义
106 B 明文(未加密) sig_alg(2)+keynum(8)+sk(64)+ck(32)
158 B 加密(Scrypt KDF 段) sig_alg(2)+kdf_alg(2)+ck_alg(2)+salt(32)+opslimit(8)+memlimit(8)+keynum(8)+sk(64)+ck(32)

⚠️ cargo tauri signer generate 产出的都是 158 B(带 KDF 段)——即使你「没设密码」,结构上仍是加密布局,只是口令为空。所以不能用文件大小判断是否设了密码,必须用「空密码能否签名」实测(见 §六 自检)。

公钥 payload 固定 42 B:sig_alg(2)+keynum(8)+key(32)。keynum = payload[2:10],是密钥指纹,用于快速比对「配置公钥 ↔ 私钥 ↔ 签名」三者是否同一把钥匙。


三、密钥生命周期操作

3.1 生成(首次或轮换)

# 无密码(不推荐,当前生产就是这个状态)
cargo tauri signer generate -w "$env:USERPROFILE\.tauri\devflow.key"

# 带密码(推荐)
cargo tauri signer generate -p "<强口令>" -w "$env:USERPROFILE\.tauri\devflow.key"

生成后把 .pub 的内容填进 src-tauri/tauri.conf.json 的 plugins.updater.pubkey,然后重新编译客户端才生效。

3.2 备份(强制)

私钥丢失 = 无法再签发升级包 = 只能原地冻版。生成后立刻:

1. 复制 devflow.key / devflow.key.pub 到离线介质(U 盘 / 加密卷)
2. 记录密钥指纹(keynum),见 §六
3. 关键:口令单独记录(不要和私钥同处存储)

3.3 轮换(断链级,慎用)

代价:已分发的所有客户端自动升级链断裂 → 必须手动重装
步骤:
1. 生成新密钥对 → 旧密钥改名归档(不要删,历史包验签要用)
2. 更新 tauri.conf.json pubkey → 重新编译 → 发布「桥接版」
   (桥接版仍用旧私钥签,老客户端能升上来;桥接版内含新公钥,之后再用新私钥签)
3. 桥接版铺开 ≥1 个版本周期后,才切新私钥签发

单管理员自用场景可简化:接受「一次手动重装」,直接换。对外分发则必须走桥接版。


四、发布流程(一键脚本)

cd E:\wk-lab\devflow
.\scripts\publish-release.ps1                     # 完整发布(含 tauri build)
.\scripts\publish-release.ps1 -Notes "修复xx"      # 手动指定更新说明
.\scripts\publish-release.ps1 -SkipBuild          # 复用已有产物,只生成清单+上传

脚本六步(每步失败即 exit 1,不静默):

步 动作 失败含义
1 版本三源校验(Cargo.toml / tauri.conf.json / package.json) 三处版本号不一致,先对齐(见 §七 坑 2)
2 签名私钥就位检查(env 优先,回退 ~/.tauri/devflow.key) 私钥缺失
3 cargo tauri build(产出 devflow_<ver>_x64-setup.exe + .exe.sig) 编译/打包失败
4 生成 latest.json(UTF-8 无 BOM) —
5 上传 ssh-proxy → suke-dev:/var/www/app-site/download/devflow/ SSH/上传失败
6 远端 ls + cat latest.json 回显验证 上传未落盘

发布清单契约(latest.json):

{
  "version": "0.1.2",
  "notes": "...",
  "pub_date": "2026-10-10T14:00:00Z",
  "platforms": {
    "windows-x86_64": {
      "signature": "<.exe.sig 全文>",
      "url": "https://app.1216.top/download/devflow/devflow_0.1.2_x64-setup.exe"
    }
  }
}

app.1216.top 已配 no-cache,改完即时生效,无需刷 CDN。


五、手工签名(脚本之外,补签/演练用)

# 无密码密钥
cargo tauri signer sign -f "$env:USERPROFILE\.tauri\devflow.key" -p "" --app-version 0.1.2 <file>

# 带密码密钥(口令走参数或环境变量,勿写进脚本)
cargo tauri signer sign -f "$env:USERPROFILE\.tauri\devflow.key" -p "<口令>" --app-version 0.1.2 <file>

# 环境变量方式(CI 友好)
$env:TAURI_SIGNING_PRIVATE_KEY = "$env:USERPROFILE\.tauri\devflow.key"
$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "<口令>"

务必带 --app-version:它把版本号写进签名的 trusted comment 并被签名覆盖。若客户端/服务端开启 requireSignedVersion,清单里改版本号就会被拒——这是防「篡改清单把新版号配旧包」的关键绑定。cargo tauri build 会自动带上,手工签要自己带。


六、验证(发布后必做)

6.1 一键验签(推荐)

node temp/verify_sig.mjs

输出:配置公钥 keynum / 每个 exe 的 keynum 匹配性 / ed25519 实际验签结果 / latest.json 与 .sig 内容一致性。 验签数学:payload 首 2 字节 ED 表示 blake2b512 预哈希模式,即 verify(blake2b512(file), sig);Ed 表示直接对原文。

6.2 密钥指纹自检

python temp/sign_state_check.py     # 三源 keynum 对照
python temp/key_layout_final.py     # 私钥格式(106/158B)判定

判据:配置公钥 keynum == 预期私钥对应 keynum。不等 = 拿错钥匙,签出来的包客户端一律拒。

6.3 「私钥是否设了密码」实测(唯一可信方法)

$probe = "$env:TEMP\pw_probe.txt"; "x" | Set-Content $probe -NoNewline
cargo tauri signer sign -f "$env:USERPROFILE\.tauri\devflow.key" -p "" "$probe" ; $LASTEXITCODE
# exit=0 → 无密码保护;exit≠0 且报 incorrect password → 有密码

七、踩坑记录(实证)

坑 1 — latest.json 的 UTF-8 BOM Windows PowerShell 5.1 的 Out-File -Encoding utf8 / Set-Content -Encoding utf8 会写 BOM(EF BB BF)。JSON 解析器遇到 BOM 报 expected value at line 1 column 1,表现为「签名没错、包没错,updater 就是拉不到清单」。 ✅ 修正:[System.IO.File]::WriteAllText($path, $json, (New-Object System.Text.UTF8Encoding($false)))。已固化进 publish-release.ps1。

坑 2 — 版本号三源分叉 版本号存在于三处:src-tauri/Cargo.toml(Rust crate / env!("CARGO_PKG_VERSION"))、src-tauri/tauri.conf.json(打包器实际使用)、package.json(前端)。 任一处漏改 → 设置页显示与实际安装版本不符(灵异现象)。 ✅ 修正:publish-release.ps1 第 1 步硬校验三源一致,不一致直接拒发。2026-10-10 已修 package.json 0.1.1 → 0.1.2。

坑 3 — 发布脚本硬编码版本号 swap-install.ps1 曾硬编码 devflow_0.1.1_x64-setup.exe。升版忘改 → 若旧包残留同目录,静默装回旧版(伪成功,最危险)。 ✅ 修正:改 glob 探测 + Sort-Object LastWriteTime -Descending | Select-Object -First 1。

坑 4 — 用文件大小判密码 cargo tauri signer generate 产出的私钥一律 158 B(带 Scrypt KDF 段),无密码时只是口令为空。按大小判断会误判。 ✅ 修正:用 §6.3 空密码实签判定。

坑 5 — 密钥文件被误覆盖 2026-10-10 曾发生野密钥覆盖生产密钥,靠备份 *.bak-20261010-213046 恢复。 ✅ 教训:动密钥前先归档(restore-key.ps1 的归档-恢复-校验三步式可复用);密钥目录加 ACL 防误改(见 §八)。


八、安全加固(已实施 2026-10-10)

威胁模型:任何能读到 devflow.key 的本地进程,都能签发被客户端信任的更新包 → 推恶意升级。

已实施的缓解(见 temp/harden_key_acl.ps1):

%USERPROFILE%\.tauri\                     阻断继承,仅 (当前用户 / SYSTEM)
%USERPROFILE%\.tauri\devflow.key*         阻断继承,仅 (当前用户 读写删 / SYSTEM 全控)

仍未消除的风险(重要):私钥当前无口令保护,文件本身即等价于签名能力;ACL 只挡了「非授权本地用户」,挡不住「以当前用户身份运行的程序」。

根治方案(需拍板,有代价):

方案 强度 代价
A. 私钥加口令 中 必须重新生成密钥对(signer 无 reencrypt 子命令)→ 公钥变 → 换公钥 → 见 §3.3 断链(老客户端需手动重装)
B. 私钥迁离线介质 高 每次发布需插盘;CI 不可用
C. A+B 组合 最高 同上,推荐对外分发场景
D. 仅 ACL(现状) 低 零代价,只防误改/误读

现状为 D。若 DevFlow 仅自用,D 可接受;一旦对外分发,建议升到 C。


九、应急:私钥泄露怎么办

1. 立即停发:删除远端 latest.json(客户端拉不到清单即停止升级链)
2. 评估:泄露时间窗内是否有未授权包被签(检查远端目录文件时间戳)
3. 轮换:按 §3.3 生成新密钥对 → 发布桥接版(旧私钥签)→ 铺开后切新私钥
4. 通告:对外分发场景需告知用户手动重装

注意:删除 latest.json 是唯一的「远端急停」手段——客户端只在 check 时拉清单,清单不在 = 无法升级。


十、日常检查清单

发布前:

  • 版本三源一致(脚本第 1 步自动校验)
  • 私钥就位且指纹与配置公钥一致(node temp/verify_sig.mjs 的 keynum 对照)
  • 密钥已备份(首次/轮换后)

发布后:

  • node temp/verify_sig.mjs 全绿(验签 + latest.json 对账)
  • 远端 curl https://app.1216.top/download/devflow/latest.json 可取且 version 正确
  • 老客户端实测一次「检查更新 → 下载 → 安装 → 重启后版本号变新」(B3 端到端)