From 7b42d9b057bdcf38ffda2ab8b3d57d5c3c13d7ba Mon Sep 17 00:00:00 2001 From: tokisaki galaxy Date: Tue, 20 Oct 2026 22:59:50 +0800 Subject: [PATCH] fix(installer): widen the dev certificate to 90 days and surface its expiry --- installer/README.md | 3 ++- installer/import-dev-trust.ps1 | 19 +++++++++++++++---- installer/sign-dev-build.ps1 | 8 +++++--- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/installer/README.md b/installer/README.md index 979a93ae..db7be9b5 100644 --- a/installer/README.md +++ b/installer/README.md @@ -16,7 +16,8 @@ `dev-build.yml` workflow 给没有本地工具链的贡献者出可安装的包。触发见 [CONTRIBUTING.md](../CONTRIBUTING.md#没有本地工具链时dev-build-workflow)。签名证书由 `sign-dev-build.ps1` 在 runner 上现场生成,subject 固定为 `CN=Metasequoia IME Dev Build Code Signing`,私钥只在 runner 内存里存在一次构建;公钥 `.cer` 随 artifact 分发,用户用 `import-dev-trust.ps1` 导入本机受信任存储(按 subject 清旧导新,重复构建不会膨胀存储)。 -- `sign-dev-build.ps1` 复用同一次运行早先创建的证书,所以包内二进制和安装包总在同一签名下;`DEV_CERT_PATH` 指出 `.cer` 的导出位置 +- `sign-dev-build.ps1` 复用同一次运行早先创建的证书,所以包内二进制和安装包总在同一签名下;复用的依据是本次运行写在 `RUNNER_TEMP` 里的指纹标记,不按 subject 猜,机器上残留的旧证书不会被误用。`DEV_CERT_PATH` 指出 `.cer` 的导出位置 +- 证书有效期 90 天:artifact 只留 7 天,但用户可能下载到本地后放很久才装,而安装器校验的是签名时刻证书是否有效;`import-dev-trust.ps1` 会打印这张 `.cer` 自带的到期时间,发现已过期就直接退出,不往受信任存储里写 - `import-dev-trust.ps1` 也随 artifact 分发,用户解压后在管理员 PowerShell 里直接跑 - 版本号带 `-dev.<日期>.`,由 `scripts/dev-build-version.py` 从 `version.txt` 派生;设置页的更新检查会剥掉 `-dev` 后缀做数字比较,dev 包不会提示升级 - 不要把这两个脚本接进 release 路径:正式发布的签名在 `scripts/ci/sign-binaries.ps1` 或 SignPath,两套互不相干 diff --git a/installer/import-dev-trust.ps1 b/installer/import-dev-trust.ps1 index 0aae8658..5bd70144 100644 --- a/installer/import-dev-trust.ps1 +++ b/installer/import-dev-trust.ps1 @@ -28,10 +28,6 @@ function Test-Administrator { return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } -if (-not (Test-Administrator)) { - throw '需要管理员 PowerShell:写入 LocalMachine 受信任存储必须提升权限。' -} - if (-not $CertificatePath) { $candidates = @( Get-ChildItem -LiteralPath $PSScriptRoot -Filter '*.cer' -File -ErrorAction SilentlyContinue @@ -53,6 +49,20 @@ if ($certificate.Subject -ne $Subject) { throw "这不是 dev-build 签名证书(subject:$($certificate.Subject))。预期:$Subject" } +# 到期时间读的是这张 .cer 自带的字段,不靠「构建过了几天」去推。签名在证书有效期内完成才能 +# 通过校验,所以证书过期后这份包就装不上了,导入得再干净也没用,必须换新构建。 +# 这一步必须排在写受信任存储之前:过期证书导入进去只会让本机多一条无用的信任记录。 +if ($certificate.NotAfter -le (Get-Date)) { + Write-Warning "这张证书已经过期($($certificate.NotAfter.ToString('yyyy-MM-dd HH:mm'))),包内签名无法通过校验。" + Write-Warning '请重新下载最新的 dev build,用它的证书和安装包;不要继续安装当前这份。' + exit 1 +} + +# 权限检查放在体检之后:先让没提升权限的用户知道包本身已经过期,再告诉他去开管理员。 +if (-not (Test-Administrator)) { + throw '需要管理员 PowerShell:写入 LocalMachine 受信任存储必须提升权限。' +} + # 按固定 subject 清理所有旧证书,再导入新的一张。跳过与本次导入指纹相同的那份。 if (-not $Keep) { foreach ($store in $TrustStores) { @@ -80,4 +90,5 @@ finally { Write-Host '' Write-Host "Dev 证书已就绪:$($certificate.Subject)" Write-Host "指纹:$($certificate.Thumbprint)(本次构建专有,换新 build 需重跑本脚本)" +Write-Host "有效期至:$($certificate.NotAfter.ToString('yyyy-MM-dd HH:mm'))(本机时间)" Write-Host '现在可以安装同目录下的 dev 安装包了。' diff --git a/installer/sign-dev-build.ps1 b/installer/sign-dev-build.ps1 index 82a7367b..ef84507c 100644 --- a/installer/sign-dev-build.ps1 +++ b/installer/sign-dev-build.ps1 @@ -66,8 +66,10 @@ function Get-RunCertificate { } function New-RunCertificate { - # -Type CodeSigningCert 负责代码签名 EKU。有效期对齐 artifact 的 7 天保留期再放宽, - # 避免 runner 时钟略慢导致证书尚未生效。 + # -Type CodeSigningCert 负责代码签名 EKU。有效期权衡的是两件事:artifact 只留 7 天, + # 但用户可能下载到本地后放很久才装,而安装器检查的是签名时刻证书是否有效;拉得太长又会 + # 让一张随构建轮换的证书变成长期可信身份。90 天覆盖到拖延的安装,又不至于成为长期凭据。 + # import-dev-trust.ps1 会打印实际的到期时间,安装器发现包内证书过期时也会明确报出来。 $certificate = New-SelfSignedCertificate ` -Type CodeSigningCert ` -Subject $CertificateSubject ` @@ -78,7 +80,7 @@ function New-RunCertificate { -KeyUsage DigitalSignature ` -CertStoreLocation 'Cert:\CurrentUser\My' ` -NotBefore (Get-Date).AddHours(-1) ` - -NotAfter (Get-Date).AddDays(30) + -NotAfter (Get-Date).AddDays(90) return $certificate }