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:
@@ -22,7 +22,9 @@
|
|||||||
#
|
#
|
||||||
# Signing reuses the client's MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD secrets (CN=unom). Without them
|
# Signing reuses the client's MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD secrets (CN=unom). Without them
|
||||||
# an ephemeral self-signed cert is generated and its public .cer published next to the installer
|
# an ephemeral self-signed cert is generated and its public .cer published next to the installer
|
||||||
# (import once to LocalMachine\TrustedPublisher). See packaging/windows/pack-host-installer.ps1.
|
# (import once to LocalMachine\TrustedPublisher). That fallback is for canary/CI ONLY — on a v* tag
|
||||||
|
# the pack script FAILS CLOSED rather than ship a release signed by a per-build throwaway cert.
|
||||||
|
# See packaging/windows/pack-host-installer.ps1.
|
||||||
#
|
#
|
||||||
# GPU backends: the host builds with --features nvenc,amf-qsv,qsv = all three vendors in one installer.
|
# GPU backends: the host builds with --features nvenc,amf-qsv,qsv = all three vendors in one installer.
|
||||||
# - NVENC (NVIDIA, direct SDK): nothing needed at build time — the entry points are resolved at
|
# - NVENC (NVIDIA, direct SDK): nothing needed at build time — the entry points are resolved at
|
||||||
|
|||||||
@@ -21,11 +21,14 @@
|
|||||||
# Published to the generic registry + the `canary/` alias.
|
# Published to the generic registry + the `canary/` alias.
|
||||||
# Both arches share the version; artifacts are arch-suffixed (..._x64.msix / ..._arm64.msix).
|
# Both arches share the version; artifacts are arch-suffixed (..._x64.msix / ..._arm64.msix).
|
||||||
#
|
#
|
||||||
# Signing (packaging/pack-msix.ps1): if the MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD Actions secrets
|
# Signing (clients/windows/packaging/pack-msix.ps1): if the MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD
|
||||||
# are set (a real or shared code-signing .pfx whose subject DN == Publisher), the package is signed
|
# Actions secrets are set (a real or shared code-signing .pfx whose subject DN == Publisher), the
|
||||||
# with them. Otherwise an ephemeral self-signed cert is generated and its public .cer is published
|
# package is signed with them. Otherwise an ephemeral self-signed cert is generated and its public
|
||||||
# next to the .msix (users import it to Trusted People before install). Drop in a real cert later
|
# .cer is published next to the .msix (users import it to Trusted People before install).
|
||||||
# with no workflow change — just add the secrets (+ pass -Publisher if its subject differs).
|
#
|
||||||
|
# That fallback is for canary/CI ONLY. On a v* tag the pack script FAILS CLOSED — a missing secret
|
||||||
|
# aborts the build instead of quietly shipping a release signed by a per-build throwaway cert that
|
||||||
|
# no one can pin. Nothing to opt into here: the script reads GITHUB_REF itself.
|
||||||
name: windows-msix
|
name: windows-msix
|
||||||
# One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels
|
# One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels
|
||||||
# it (a canary only needs the latest commit; each release tag is its own ref so tag runs never
|
# it (a canary only needs the latest commit; each release tag is its own ref so tag runs never
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
in-process. The package installs only where that cert is trusted, so the matching public
|
in-process. The package installs only where that cert is trusted, so the matching public
|
||||||
.cer is exported next to the .msix for the user to import (Trusted People) before install.
|
.cer is exported next to the .msix for the user to import (Trusted People) before install.
|
||||||
Swap in a real cert later with zero manifest changes — just pass -PfxBase64/-Publisher.
|
Swap in a real cert later with zero manifest changes — just pass -PfxBase64/-Publisher.
|
||||||
|
This fallback is for canary/CI/dev ONLY: on a v* tag build a missing cert is a hard failure
|
||||||
|
(-RequireSignedCert), never a silent downgrade to a throwaway cert.
|
||||||
|
|
||||||
Run on the Windows runner (or the dev VM) with the MSVC/Windows SDK present.
|
Run on the Windows runner (or the dev VM) with the MSVC/Windows SDK present.
|
||||||
|
|
||||||
@@ -33,7 +35,9 @@ param(
|
|||||||
[string]$OutDir = (Join-Path $TargetDir 'msix'),
|
[string]$OutDir = (Join-Path $TargetDir 'msix'),
|
||||||
[string]$Publisher = 'CN=unom', # MUST equal the signing cert subject DN
|
[string]$Publisher = 'CN=unom', # MUST equal the signing cert subject DN
|
||||||
[string]$PfxBase64 = $env:MSIX_CERT_PFX_B64, # optional: base64 of a code-signing .pfx
|
[string]$PfxBase64 = $env:MSIX_CERT_PFX_B64, # optional: base64 of a code-signing .pfx
|
||||||
[string]$PfxPassword = $env:MSIX_CERT_PASSWORD
|
[string]$PfxPassword = $env:MSIX_CERT_PASSWORD,
|
||||||
|
# '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'
|
$ErrorActionPreference = 'Stop'
|
||||||
$ProgressPreference = 'SilentlyContinue'
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
@@ -138,11 +142,23 @@ $msix = Join-Path $OutDir "punktfunk-client-windows_${Version}_${Arch}.msix"
|
|||||||
if ($LASTEXITCODE -ne 0) { throw "makeappx pack failed ($LASTEXITCODE)" }
|
if ($LASTEXITCODE -ne 0) { throw "makeappx pack failed ($LASTEXITCODE)" }
|
||||||
|
|
||||||
# --- signing cert (supplied stable pfx OR ephemeral self-signed) ---
|
# --- 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 a package
|
||||||
|
# signed with one is indistinguishable from a package signed by an attacker's. Silently falling back
|
||||||
|
# on a tag build would ship exactly that to users under the release's name — so on refs/tags/v* an
|
||||||
|
# absent MSIX_CERT_PFX_B64 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 for local testing.)
|
||||||
|
$requireCert = if ($RequireSignedCert -eq 'auto') { $env:GITHUB_REF -like 'refs/tags/v*' }
|
||||||
|
else { [Convert]::ToBoolean($RequireSignedCert) }
|
||||||
$pfxPath = Join-Path $OutDir 'signing.pfx'
|
$pfxPath = Join-Path $OutDir 'signing.pfx'
|
||||||
$cerPath = Join-Path $OutDir "punktfunk-client-windows_${Version}_${Arch}.cer"
|
$cerPath = Join-Path $OutDir "punktfunk-client-windows_${Version}_${Arch}.cer"
|
||||||
if ($PfxBase64) {
|
if ($PfxBase64) {
|
||||||
Write-Host "signing with supplied code-signing cert (MSIX_CERT_PFX_B64)"
|
Write-Host "signing with supplied code-signing cert (MSIX_CERT_PFX_B64)"
|
||||||
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($PfxBase64))
|
[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 {
|
} else {
|
||||||
Write-Host "no MSIX_CERT_PFX_B64 -> generating an ephemeral self-signed cert (subject $Publisher)"
|
Write-Host "no MSIX_CERT_PFX_B64 -> generating an ephemeral self-signed cert (subject $Publisher)"
|
||||||
if (-not $PfxPassword) { $PfxPassword = 'punktfunk' }
|
if (-not $PfxPassword) { $PfxPassword = 'punktfunk' }
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
From a release `cargo build -p punktfunk-host --features nvenc` output (the exe), this:
|
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
|
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,
|
2. signs the inner punktfunk-host.exe,
|
||||||
3. stages the pf-vdisplay virtual-display driver bundle (unless -NoDriver),
|
3. stages the pf-vdisplay virtual-display driver bundle (unless -NoDriver),
|
||||||
4. runs ISCC to build punktfunk-host-setup-<ver>.exe,
|
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]$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
|
[string]$VbCableDir = $env:VBCABLE_DIR, # official base VB-CABLE package -> bundle the virtual mic
|
||||||
[switch]$NoDriver, # build without the bundled pf-vdisplay driver
|
[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'
|
$ErrorActionPreference = 'Stop'
|
||||||
$ProgressPreference = 'SilentlyContinue'
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
@@ -71,6 +75,17 @@ $iscc = Find-Iscc
|
|||||||
Write-Host "ISCC: $iscc"
|
Write-Host "ISCC: $iscc"
|
||||||
|
|
||||||
# --- signing cert (supplied stable pfx OR ephemeral self-signed) -----------------------------
|
# --- 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'
|
$pfxPath = Join-Path $OutDir 'signing.pfx'
|
||||||
$cerPath = Join-Path $OutDir "punktfunk-host-windows_${Version}.cer"
|
$cerPath = Join-Path $OutDir "punktfunk-host-windows_${Version}.cer"
|
||||||
$signtool = $null
|
$signtool = $null
|
||||||
@@ -81,6 +96,11 @@ if (-not $NoSign) {
|
|||||||
Write-Host "signing with supplied code-signing cert (MSIX_CERT_PFX_B64)"
|
Write-Host "signing with supplied code-signing cert (MSIX_CERT_PFX_B64)"
|
||||||
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($PfxBase64))
|
[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 {
|
else {
|
||||||
Write-Host "no MSIX_CERT_PFX_B64 -> generating an ephemeral self-signed cert (subject $Publisher)"
|
Write-Host "no MSIX_CERT_PFX_B64 -> generating an ephemeral self-signed cert (subject $Publisher)"
|
||||||
if (-not $PfxPassword) { $PfxPassword = 'punktfunk' }
|
if (-not $PfxPassword) { $PfxPassword = 'punktfunk' }
|
||||||
|
|||||||
Reference in New Issue
Block a user