Sign Windows releases with Azure Artifact Signing — a 3-day leaf makes timestamping mandatory #228
Merged
enricobuehler
merged 2 commits from 2026-08-14 17:20:12 +00:00
worktree-azure-trusted-signing into main
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2d15548e38 |
ci(windows): provision the signing toolchain — no .NET runtime meant signtool exited 3 in silence
apple / swift (pull_request) Successful in 2m7s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
android / android (pull_request) Failing after 2m16s
ci / web (pull_request) Successful in 2m29s
ci / rust-arm64 (pull_request) Successful in 2m35s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 4m24s
ci / bun-nix (pull_request) Successful in 5m25s
ci / rust (pull_request) Successful in 7m16s
ci / docs-site (pull_request) Successful in 7m48s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 7m59s
Verified the whole Azure signing path on the runner (.133) today and it failed twice, for two reasons that neither error message named. Both are now provisioned here so a rebuild from the unom/infra Packer template cannot silently un-fix them. Azure.CodeSigning.Dlib.dll is a mixed-mode C++/CLI assembly: it ships Ijwhost.dll and a runtimeconfig.json pinning Microsoft.NETCore.App 8.0.0. The runner had NO .NET runtime at all — pwsh 7 is a self-contained install and brings no shared runtime — so signtool exited 3 having printed absolutely nothing. Installing the .NET 8 runtime turned that into a clean sign. The client itself installs machine-wide under C:\trusted-signing rather than a user's .nuget, because act_runner runs as SYSTEM, whose USERPROFILE is C:\Windows\System32\config\systemprofile. A per-user install under Administrator is invisible to every job that actually builds. Confirmed by resolving Find-AzureDlib from a SYSTEM scheduled task, which is also how the earlier SSH-only attempts misled: over a network logon New-SelfSignedCertificate hits NTE_PERM, so a control test that "fails" there proves nothing about how CI will behave. Both downloads are SHA-256 pinned against version-immutable URLs (nuget.org flat-container and the dotnet builds CDN), so they fail closed on tampering rather than on every Microsoft patch release — unlike the BtbN `latest` pin above, which re-rolls. The .NET install uses Start-Process -Wait because the bundle is a GUI PE that returns instantly under `&`, leaving $LASTEXITCODE unset and racing the completion check (cost one false failure here). End-to-end result on .133, as SYSTEM: sign rc=0, verify rc=0, chain Microsoft Identity Verification Root CA 2020 -> ID Verified CS EOC CA 04 -> "unom - Enrico Buhler", leaf thumbprint DD6A610F242CB5B2078C2A5D628699B6AB0CAC07 (matches the profile Azure reports), timestamped, leaf expires in 3 days as expected. Signing an unsigned binary and reading the subject back reproduces pack-msix.ps1's Publisher assertion exactly (match=True) — checked against a NON-catalog-signed binary on purpose, because Get-AuthenticodeSignature on a catalog-signed system exe returns the catalog signer and would have read as a false mismatch. |
||
|
|
b79ff45bd1 |
feat(windows): sign via Azure Artifact Signing — a 3-day leaf makes timestamping mandatory
Releases move from the self-signed CN=unom cert to Azure Artifact Signing (formerly Trusted Signing): account `unomsigning`, profile `unom-io`, signed by the `punktfunk-ci-signing` service principal, which holds only the Artifact Signing Certificate Profile Signer role scoped to that one profile. Both pack scripts gain the backend ahead of the existing .pfx and ephemeral fallbacks, so canary and fork builds are unaffected. Three things that are easy to get wrong, and are handled here rather than discovered in the field: Azure mints a leaf certificate per signing request that expires in about three days. Both scripts previously retried WITHOUT a timestamp when a timestamped sign failed — under Azure that ships an artifact which verifies on the runner and goes untrusted days later, on every user's machine at once. The retry is now gated on the mode: still lenient for a .pfx whose cert outlives the release, a hard failure for Azure. The MSIX manifest Publisher must equal the signer subject byte-for-byte, because package identity is Name + Publisher. The default is now the profile's verified subject, written with `[char]0xFC` escapes rather than literal umlauts so this UTF-8-without-BOM file cannot silently mojibake the DN into one that no longer matches. pack-msix.ps1 now also reads the signature back off the packed .msix and fails on drift — asymmetric on purpose: a subject that disagrees is fatal, a subject that cannot be read is only a warning, since Get-AuthenticodeSignature's .msix support varies by Windows version and signtool has already reported success by then. NOTE this changes package identity, so existing installs need an uninstall, not an upgrade. The updater's leaf-pinning note was wrong and is corrected: update/windows.rs claimed the AUTHENTICODE_SHA256 field made Trusted Signing "a manifest edit", but a per-request leaf is exactly what a leaf pin cannot track — a pin would go stale within days and reject every release after it. Drivers are deliberately untouched: their catalogs keep the DRIVER_CERT_* cert and the installer still plants it as a machine root. The two signatures were always independent (SmartScreen/UAC vs PnP), which is why the installer could move without them. Whether a publicly-trusted catalog would let us drop that root plant is recorded as an unverified follow-up, not assumed. Verified: both scripts parse under the PowerShell 7 AST parser, both workflows are valid YAML, the evaluated Publisher default matches the subject Azure reports for the profile (86 chars, ordinal), rustfmt clean. NOT verified on Windows — the sign path itself needs an on-glass run on .133. |