feat(windows): stable code-signing cert for the MSIX (one-time per-machine trust)
apple / swift (push) Successful in 54s
windows-msix / package (push) Successful in 1m0s
windows / build (push) Successful in 55s
android / android (push) Failing after 56s
ci / web (push) Successful in 32s
ci / docs-site (push) Successful in 39s
ci / rust (push) Failing after 3m21s
deb / build-publish (push) Successful in 2m5s
decky / build-publish (push) Successful in 14s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
ci / bench (push) Successful in 4m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 5m27s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 5m28s
docker / deploy-docs (push) Successful in 10s
apple / swift (push) Successful in 54s
windows-msix / package (push) Successful in 1m0s
windows / build (push) Successful in 55s
android / android (push) Failing after 56s
ci / web (push) Successful in 32s
ci / docs-site (push) Successful in 39s
ci / rust (push) Failing after 3m21s
deb / build-publish (push) Successful in 2m5s
decky / build-publish (push) Successful in 14s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
ci / bench (push) Successful in 4m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 5m27s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 5m28s
docker / deploy-docs (push) Successful in 10s
Sign every MSIX build with one STABLE self-signed cert instead of a fresh per-build cert, so the Trusted People import is a one-time, per-machine step that survives upgrades (a fresh cert each build forced a re-import every time). The cert (CN=unom, SHA-1 CD1EFDEE…E941, valid to 2036) lives in the MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD Actions secrets; its public half is checked in as packaging/punktfunk-codesign.cer and published next to each .msix. pack-msix.ps1 now always exports the signing cert's public .cer (extracted from a supplied pfx too, not just the ephemeral-generated path) and warns if the cert subject != manifest Publisher (the mismatch Add-AppxPackage would otherwise reject). Documents the path to a publicly-trusted (no-import) cert: swap the two secrets + pass a matching -Publisher. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -37,25 +37,34 @@ MSIX requires a strictly 4-part numeric version. The workflow computes:
|
||||
|
||||
## Signing & install
|
||||
|
||||
Signing precedence in `pack-msix.ps1`:
|
||||
1. **`MSIX_CERT_PFX_B64` / `MSIX_CERT_PASSWORD`** Actions secrets — a real (or shared) code-signing
|
||||
`.pfx` whose **subject DN must equal `-Publisher`** (default `CN=unom`). Drop these in later to
|
||||
move off self-signed with **no manifest change**; if the cert's subject differs, pass a matching
|
||||
`-Publisher` (it's stamped into the manifest `Identity`).
|
||||
2. otherwise an **ephemeral self-signed** cert (subject = `-Publisher`) is generated and its public
|
||||
`.cer` is published next to the `.msix`.
|
||||
|
||||
To install a self-signed build, trust the cert once, then add the package:
|
||||
CI signs every build with a **stable self-signed code-signing cert** (`CN=unom`, SHA-1
|
||||
`CD1EFDEEEC9743AFC38F56C5AF30C5A3009BE941`, valid to 2036). Its public half is checked in as
|
||||
[`punktfunk-codesign.cer`](punktfunk-codesign.cer); the private `.pfx` + password live in the
|
||||
`MSIX_CERT_PFX_B64` / `MSIX_CERT_PASSWORD` Actions secrets. Because it's the *same* cert every build,
|
||||
trusting it is **one-time, per machine** — once imported, every future build and in-place upgrade is
|
||||
trusted with no further prompt:
|
||||
|
||||
```powershell
|
||||
Import-Certificate -FilePath .\punktfunk-client-windows_<ver>_x64.cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople
|
||||
# once per machine (elevated): trust the publisher
|
||||
Import-Certificate -FilePath .\punktfunk-codesign.cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople
|
||||
# then install (and re-run for each upgrade — no re-trust needed)
|
||||
Add-AppxPackage -Path .\punktfunk-client-windows_<ver>_x64.msix
|
||||
```
|
||||
|
||||
The matching `.cer` is also published next to each `.msix` in the registry, so it's always at hand.
|
||||
|
||||
The MSIX declares a dependency on the Windows App SDK 2.x runtime; install
|
||||
[the App SDK runtime](https://aka.ms/windowsappsdk) if `Add-AppxPackage` reports a missing
|
||||
`Microsoft.WindowsAppRuntime.2` framework.
|
||||
|
||||
`pack-msix.ps1` signing precedence: it uses the **`MSIX_CERT_PFX_B64` / `MSIX_CERT_PASSWORD`** secrets
|
||||
when present (the stable cert above), else generates an *ephemeral* self-signed cert (forks / local
|
||||
builds without the secrets). Either way it exports the signing cert's public `.cer` for the import.
|
||||
**To move to a publicly-trusted (no-import) cert** — Azure Artifact Signing or a public OV cert —
|
||||
replace the two secrets with the new `.pfx`; the cert's subject DN must equal the manifest
|
||||
`Publisher`, so pass a matching `-Publisher` (it's stamped into the package `Identity`, and changing
|
||||
it changes the package identity → a one-time reinstall).
|
||||
|
||||
## Building locally
|
||||
|
||||
On the Windows runner / dev VM (MSVC + Windows SDK present), after a release build:
|
||||
|
||||
@@ -92,28 +92,37 @@ $msix = Join-Path $OutDir "punktfunk-client-windows_${Version}_x64.msix"
|
||||
& $makeappx pack /o /d $layout /p $msix
|
||||
if ($LASTEXITCODE -ne 0) { throw "makeappx pack failed ($LASTEXITCODE)" }
|
||||
|
||||
# --- signing cert ---
|
||||
# --- signing cert (supplied stable pfx OR ephemeral self-signed) ---
|
||||
$pfxPath = Join-Path $OutDir 'signing.pfx'
|
||||
$cerPath = Join-Path $OutDir "punktfunk-client-windows_${Version}_x64.cer"
|
||||
$selfSigned = $false
|
||||
if ($PfxBase64) {
|
||||
Write-Host "signing with supplied code-signing cert (MSIX_CERT_PFX_B64)"
|
||||
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($PfxBase64))
|
||||
} else {
|
||||
Write-Host "no MSIX_CERT_PFX_B64 -> generating an ephemeral self-signed cert (subject $Publisher)"
|
||||
$selfSigned = $true
|
||||
if (-not $PfxPassword) { $PfxPassword = 'punktfunk' }
|
||||
$cert = New-SelfSignedCertificate -Type Custom -Subject $Publisher `
|
||||
$tmp = New-SelfSignedCertificate -Type Custom -Subject $Publisher `
|
||||
-KeyUsage DigitalSignature -FriendlyName 'punktfunk MSIX (self-signed)' `
|
||||
-CertStoreLocation 'Cert:\CurrentUser\My' `
|
||||
-TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3', '2.5.29.19={text}')
|
||||
$sec = ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText
|
||||
Export-PfxCertificate -Cert "Cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath $pfxPath -Password $sec | Out-Null
|
||||
Export-Certificate -Cert "Cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath $cerPath | Out-Null
|
||||
Remove-Item "Cert:\CurrentUser\My\$($cert.Thumbprint)" -Force
|
||||
Export-PfxCertificate -Cert "Cert:\CurrentUser\My\$($tmp.Thumbprint)" -FilePath $pfxPath -Password $sec | Out-Null
|
||||
Remove-Item "Cert:\CurrentUser\My\$($tmp.Thumbprint)" -Force
|
||||
}
|
||||
|
||||
# --- sign (timestamp best-effort; a self-signed cert needs no TSA) ---
|
||||
# Always export the public .cer from the pfx. For a self-signed / private-trust cert it's the file
|
||||
# users import once (Trusted People) — a STABLE cert (same pfx every build via the secret) means that
|
||||
# import is a one-time, per-machine step that keeps working across upgrades. For a public-CA cert
|
||||
# it's just an unused extra (harmless). The manifest Publisher must equal the cert's subject DN.
|
||||
$pwsec = if ($PfxPassword) { ConvertTo-SecureString -String $PfxPassword -Force -AsPlainText } else { $null }
|
||||
$pubCert = if ($pwsec) { Get-PfxCertificate -FilePath $pfxPath -Password $pwsec } else { Get-PfxCertificate -FilePath $pfxPath }
|
||||
Export-Certificate -Cert $pubCert -FilePath $cerPath | Out-Null
|
||||
Write-Host "signing cert subject=$($pubCert.Subject) thumbprint=$($pubCert.Thumbprint)"
|
||||
if ($pubCert.Subject -ne $Publisher) {
|
||||
Write-Warning "cert subject '$($pubCert.Subject)' != manifest Publisher '$Publisher' — Add-AppxPackage will reject the mismatch. Pass -Publisher '$($pubCert.Subject)'."
|
||||
}
|
||||
|
||||
# --- sign (timestamp best-effort) ---
|
||||
$signArgs = @('sign', '/fd', 'SHA256', '/f', $pfxPath)
|
||||
if ($PfxPassword) { $signArgs += @('/p', $PfxPassword) }
|
||||
& $signtool ($signArgs + @('/tr', 'http://timestamp.digicert.com', '/td', 'SHA256', $msix))
|
||||
@@ -124,16 +133,12 @@ if ($LASTEXITCODE -ne 0) {
|
||||
}
|
||||
Remove-Item $pfxPath -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# if self-signed, the public .cer must travel with the package; otherwise drop it (chain is public)
|
||||
if (-not $selfSigned) { Remove-Item $cerPath -Force -ErrorAction SilentlyContinue }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "==> MSIX: $msix"
|
||||
if ($selfSigned) {
|
||||
Write-Host "==> self-signed; trust before install: Import-Certificate -FilePath '$cerPath' -CertStoreLocation Cert:\LocalMachine\TrustedPeople"
|
||||
}
|
||||
Write-Host "==> trust the cert once per machine (then it stays trusted across all future builds):"
|
||||
Write-Host " Import-Certificate -FilePath '$cerPath' -CertStoreLocation Cert:\LocalMachine\TrustedPeople"
|
||||
# emit paths for the workflow to publish (only under CI, where GITHUB_ENV is set)
|
||||
if ($env:GITHUB_ENV) {
|
||||
"MSIX_PATH=$msix" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
if ($selfSigned) { "MSIX_CER_PATH=$cerPath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 }
|
||||
"MSIX_CER_PATH=$cerPath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user