fix(packaging/windows): a release must not sign itself with a throwaway cert

Both pack scripts fell back to a fresh `New-SelfSignedCertificate` whenever
MSIX_CERT_PFX_B64 was absent, and said nothing about it beyond one Write-Host.
That fallback is right for canary and dev builds. On a tag it is not: an ephemeral
cert is regenerated per build, so nobody can pin it, and a release signed with one
is indistinguishable from a release signed by whoever else got to the artifact.
A secret that got renamed, rotated away, or simply wasn't inherited by a new
workflow would have downgraded a real release silently and shipped it.

pack-msix.ps1 and pack-host-installer.ps1 now resolve -RequireSignedCert (default
'auto' = true iff GITHUB_REF is refs/tags/v*) and throw instead of falling back;
pack-host-installer.ps1 also refuses -NoSign on the same condition. Reading
GITHUB_REF inside the scripts rather than taking a workflow flag means a future
packaging workflow inherits the guard instead of having to remember it.

No effect on today's releases: MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD are both
set as repo secrets, so the supplied-cert branch is the one tag builds take. This
only closes the trapdoor underneath it.

Verified on the windows-amd64 runner: both files parse, and the guard resolves
true for refs/tags/v0.21.0, false for refs/heads/main and for an unset ref, with
'true'/'false' overriding as intended.

Not touched: the pf-vdisplay / gamepad driver builds use the same pattern with
DRIVER_CERT_PFX_B64, which has no secret behind it — those need attestation
signing (a Partner Center action), and a guard there would just fail every tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 12:12:21 +02:00
co-authored by Claude Opus 5
parent 06ffca985d
commit 5db93c1156
4 changed files with 50 additions and 9 deletions
+22 -2
View File
@@ -5,7 +5,9 @@
.DESCRIPTION
From a release `cargo build -p punktfunk-host --features nvenc` output (the exe), this:
1. resolves a code-signing cert (supplied stable .pfx from CI secrets OR an ephemeral self-signed
CN=unom - same scheme as the client's pack-msix.ps1) and exports the public .cer,
CN=unom - same scheme as the client's pack-msix.ps1) and exports the public .cer. The
ephemeral fallback is for canary/CI/dev ONLY: on a v* tag build a missing cert (or -NoSign)
is a hard failure, never a silent downgrade to a throwaway cert - see -RequireSignedCert,
2. signs the inner punktfunk-host.exe,
3. stages the pf-vdisplay virtual-display driver bundle (unless -NoDriver),
4. runs ISCC to build punktfunk-host-setup-<ver>.exe,
@@ -31,7 +33,9 @@ param(
[string]$BunExe = $env:BUN_EXE, # portable bun.exe runtime for the console + runner
[string]$VbCableDir = $env:VBCABLE_DIR, # official base VB-CABLE package -> bundle the virtual mic
[switch]$NoDriver, # build without the bundled pf-vdisplay driver
[switch]$NoSign # skip signing (local debug)
[switch]$NoSign, # skip signing (local debug)
# 'auto' (default) = required iff this is a v* tag build; 'true'/'false' to force. See below.
[ValidateSet('auto', 'true', 'false')][string]$RequireSignedCert = 'auto'
)
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
@@ -71,6 +75,17 @@ $iscc = Find-Iscc
Write-Host "ISCC: $iscc"
# --- signing cert (supplied stable pfx OR ephemeral self-signed) -----------------------------
# FAIL CLOSED on a real release. The ephemeral fallback below exists so canary/CI/dev builds keep
# working without the secret, but it is a per-build throwaway cert: nobody can pin it, and an
# installer signed with one is indistinguishable from one signed by an attacker. Silently falling
# back on a tag build would ship exactly that to users under the release's name — so on refs/tags/v*
# a missing MSIX_CERT_PFX_B64 (or -NoSign) is a build failure, not a downgrade. ('auto' resolves from
# GITHUB_REF so a workflow can't forget to opt in; -RequireSignedCert true/false overrides.)
$requireCert = if ($RequireSignedCert -eq 'auto') { $env:GITHUB_REF -like 'refs/tags/v*' }
else { [Convert]::ToBoolean($RequireSignedCert) }
if ($NoSign -and $requireCert) {
throw "release build ($env:GITHUB_REF) with -NoSign — refusing to publish an unsigned installer."
}
$pfxPath = Join-Path $OutDir 'signing.pfx'
$cerPath = Join-Path $OutDir "punktfunk-host-windows_${Version}.cer"
$signtool = $null
@@ -81,6 +96,11 @@ if (-not $NoSign) {
Write-Host "signing with supplied code-signing cert (MSIX_CERT_PFX_B64)"
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($PfxBase64))
}
elseif ($requireCert) {
throw ("release build ($env:GITHUB_REF) with no MSIX_CERT_PFX_B64 — refusing to fall back to " +
"an ephemeral self-signed cert. Restore the MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD " +
"repo secrets, or pass -RequireSignedCert false if this really is a test build.")
}
else {
Write-Host "no MSIX_CERT_PFX_B64 -> generating an ephemeral self-signed cert (subject $Publisher)"
if (-not $PfxPassword) { $PfxPassword = 'punktfunk' }