The runbook was a dozen copy-pasted lines with three separate ways to get it
subtly wrong, so it is a script now. It prints the thumbprint, writes the two
secret values to files (not to the terminal, so they stay out of scrollback), and
never puts the private key in a certificate store — the only thing to clean up is
the output folder. `-TestOnly` does a full dry run and keeps nothing.
Three things the script exists to get right, all of which bit during testing:
Generation uses the .NET CertificateRequest API rather than
New-SelfSignedCertificate, so no key container is involved and it works over SSH.
New-SelfSignedCertificate fails there with NTE_PERM 0x80090010 — a network logon
has no key container. CONSUMING a .pfx still needs one, so the signtool self-test
reports SKIPPED over SSH instead of failing; distinguishing "cannot test here"
from "test failed" matters, because the first is a property of the session and the
second is a broken key. Any other signtool error is still fatal.
The extension set is explicit and matches what the drivers have actually been
signed with, read off the certs sitting on the runner: KeyUsage=DigitalSignature
(critical), EKU=codeSigning (non-critical), SubjectKeyIdentifier, and NO
basicConstraints. Improvising here would surface as a failed driver install on a
user's machine rather than as a build error, so it copies the known-good shape.
.NET's PKCS#12 writer, not OpenSSL — OpenSSL 3's default AES-256/PBKDF2 produces a
.pfx that Windows CryptoAPI frequently cannot read. And RandomNumberGenerator for
the passphrase, not Get-Random, which is System.Random.
Dry-run verified on the windows-amd64 runner: 3072-bit, 10-year, the three
expected extensions, self-test correctly SKIPPED with the NTE_PERM reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>