From f8cf3a0cf2f8682f15dd997807c463b26a48b188 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 29 Jul 2026 10:11:34 +0200 Subject: [PATCH] fix(packaging/windows): the decoded signing key must not outlive the build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both driver builds base64-decode DRIVER_CERT_PFX_B64 to `driver-signing.pfx` on disk and never delete it. The existing cleanup line only removes the EPHEMERAL cert from the store — it is guarded on $cleanupCert, which is null on exactly the path that writes this file. That was harmless while the cert was a per-build throwaway. It is not harmless now: the key is stable and trusted as a machine root on every box that installs punktfunk, so a .pfx sitting in a build directory is a standing credential on the machine that runs build jobs. Every windows-host build would have left one. Adds Remove-SigningPfx, called after the last signing step and from a script-scope trap so a mid-build failure doesn't strand the key either. Verified on the runner: both scripts parse, the key is gone on the success path, gone on the failure path, and a second call is a no-op. The `break` in that trap is explicitness, not correctness. I first wrote it claiming a bare trap would resume past the error and let a build finish with unsigned drivers; measuring on the runner disproved that — bare and break behave identically (exit 1, no resumption) for `throw` at script scope, `throw` inside a function, and a cmdlet error under EAP=Stop. Kept, with the comment saying what was actually measured rather than what I assumed. Co-Authored-By: Claude Opus 5 (1M context) --- packaging/windows/build-gamepad-drivers.ps1 | 19 +++++++++++++++++++ packaging/windows/build-pf-vdisplay.ps1 | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/packaging/windows/build-gamepad-drivers.ps1 b/packaging/windows/build-gamepad-drivers.ps1 index f9bf7660..09024fd5 100644 --- a/packaging/windows/build-gamepad-drivers.ps1 +++ b/packaging/windows/build-gamepad-drivers.ps1 @@ -35,6 +35,23 @@ $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $PSNativeCommandUseErrorActionPreference = $false +# The decoded signing key must not outlive this script. It is a STABLE key now — trusted as a +# machine root on every box that installs punktfunk — so a .pfx left behind in a build directory is +# a standing credential on a machine that runs build jobs, not the throwaway it used to be. A +# script-scope trap covers the failure paths; Remove-SigningPfx is also called on the way out. +$script:ShredPfx = $null +function Remove-SigningPfx { + if ($script:ShredPfx -and (Test-Path $script:ShredPfx)) { + Remove-Item $script:ShredPfx -Force -ErrorAction SilentlyContinue + $script:ShredPfx = $null + } +} +# `break` is for explicitness, not correctness: measured on the runner, a bare trap and +# trap+break behave identically here (exit 1, no resumption) for `throw` at script scope, +# `throw` inside a function, and a cmdlet error under EAP=Stop. Kept because it states the +# intent — shred, then re-throw — instead of relying on a default that is easy to misread. +trap { Remove-SigningPfx; break } + $DriversDir = (Resolve-Path $DriversDir).Path $clear = Join-Path $PSScriptRoot 'clear-force-integrity.ps1' @@ -99,6 +116,7 @@ $cleanupCert = $null if ($CertPfxB64) { Write-Host '==> signing with supplied driver cert (DRIVER_CERT_PFX_B64)' $pfx = Join-Path (Split-Path -Parent $Out) 'driver-signing.pfx' + $script:ShredPfx = $pfx [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($CertPfxB64)) $sec = if ($CertPassword) { ConvertTo-SecureString $CertPassword -AsPlainText -Force } else { $null } $signArgs = @('/f', $pfx); if ($CertPassword) { $signArgs += @('/p', $CertPassword) } @@ -145,6 +163,7 @@ foreach ($d in $drivers) { # --- 6. one shared public .cer ---------------------------------------------------------------- Export-Certificate -Cert $pubForCer -FilePath (Join-Path $Out 'punktfunk-driver.cer') | Out-Null if ($cleanupCert) { Remove-Item "Cert:\CurrentUser\My\$($cleanupCert.Thumbprint)" -Force -ErrorAction SilentlyContinue } +Remove-SigningPfx Write-Host "==> built + signed gamepad drivers DriverVer=$DriverVer -> $Out" Get-ChildItem $Out -File | ForEach-Object { " $($_.Name) ($($_.Length) bytes)" } diff --git a/packaging/windows/build-pf-vdisplay.ps1 b/packaging/windows/build-pf-vdisplay.ps1 index 47433990..83fa39b7 100644 --- a/packaging/windows/build-pf-vdisplay.ps1 +++ b/packaging/windows/build-pf-vdisplay.ps1 @@ -37,6 +37,23 @@ $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $PSNativeCommandUseErrorActionPreference = $false +# The decoded signing key must not outlive this script. It is a STABLE key now — trusted as a +# machine root on every box that installs punktfunk — so a .pfx left behind in a build directory is +# a standing credential on a machine that runs build jobs, not the throwaway it used to be. A +# script-scope trap covers the failure paths; Remove-SigningPfx is also called on the way out. +$script:ShredPfx = $null +function Remove-SigningPfx { + if ($script:ShredPfx -and (Test-Path $script:ShredPfx)) { + Remove-Item $script:ShredPfx -Force -ErrorAction SilentlyContinue + $script:ShredPfx = $null + } +} +# `break` is for explicitness, not correctness: measured on the runner, a bare trap and +# trap+break behave identically here (exit 1, no resumption) for `throw` at script scope, +# `throw` inside a function, and a cmdlet error under EAP=Stop. Kept because it states the +# intent — shred, then re-throw — instead of relying on a default that is easy to misread. +trap { Remove-SigningPfx; break } + $DriversDir = (Resolve-Path $DriversDir).Path $inx = Join-Path $DriversDir 'pf-vdisplay\pf_vdisplay.inx' $clear = Join-Path $PSScriptRoot 'clear-force-integrity.ps1' @@ -96,6 +113,7 @@ $cleanupCert = $null if ($CertPfxB64) { Write-Host '==> signing with supplied driver cert (DRIVER_CERT_PFX_B64)' $pfx = Join-Path $Out '..\driver-signing.pfx' + $script:ShredPfx = $pfx [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($CertPfxB64)) $sec = if ($CertPassword) { ConvertTo-SecureString $CertPassword -AsPlainText -Force } else { $null } $signArgs = @('/f', $pfx); if ($CertPassword) { $signArgs += @('/p', $CertPassword) } @@ -138,6 +156,7 @@ if (-not (Test-Path $sCat)) { throw "Inf2Cat did not produce $sCat" } if ($LASTEXITCODE -ne 0) { throw "signtool sign (cat) failed ($LASTEXITCODE)" } Export-Certificate -Cert $pubForCer -FilePath $sCer | Out-Null if ($cleanupCert) { Remove-Item "Cert:\CurrentUser\My\$($cleanupCert.Thumbprint)" -Force -ErrorAction SilentlyContinue } +Remove-SigningPfx # --- 5. guard: assert the freshly-built catalog covers the inf + dll --------------------------- # Built-from-source can't drift, but this catches a botched stampinf/Inf2Cat ordering. Test-FileCatalog