We hit this wall while taking our Windows kernel driver through HLK certification: an EV cert in an HSM-backed Key Vault, and no Microsoft tool that could sign the submission package with it. This is the tool we wished existed.
Built by Tracenyx — kernel-native network security for Kubernetes.
A small .NET 8 console tool that signs Microsoft HLK submission packages
(.hlkx) using an EV code-signing certificate stored in Azure Key Vault
(Premium SKU, RSA-HSM, non-exportable).
.hlkx files are Open Packaging Convention (OPC) packages — the same family
as .vsix, .nupkg, .docx. They are not Authenticode-signed, so
signtool / AzureSignTool reject them ("not a recognized file type").
Microsoft signs them via XML Digital Signatures embedded in the package
itself — see
https://learn.microsoft.com/en-us/windows-hardware/test/hlk/user/hlk-signing-with-an-hsm.
This tool builds the OPC XML-DSIG signature directly (manipulating the
package as a ZipArchive) and calls RSA.SignHash() on an
RSAKeyVaultProvider instance that proxies the operation to Key Vault. The
private key never leaves the HSM — only the precomputed hash of the
canonicalized <SignedInfo> is sent over the wire, and the signed bytes
come back.
We do not use System.IO.Packaging.PackageDigitalSignatureManager,
because it reads cert.PrivateKey internally, which on .NET 8 Windows
forces an RSA.ExportParameters(true) call. Non-exportable HSM-backed keys
fail that call with "Private keys cannot be exported by this provider".
The manual XML-DSIG approach (same as
OpenOpcSignTool) avoids this
entirely.
Requires the .NET 8 SDK on Windows.
dotnet build -c ReleaseThe built executable is at:
bin\Release\net8.0\HLKSigner.exe
For a self-contained, single-file build:
dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=trueThe tool authenticates with DefaultAzureCredential. The simplest path on a
developer machine:
az login
az account set --subscription "<your subscription>"The signed-in identity (or whatever credential DefaultAzureCredential
resolves — managed identity, service principal env vars, etc.) needs two
Key Vault role assignments on the vault holding the cert:
| Role | Why |
|---|---|
| Key Vault Crypto User | Sign + Get on the underlying RSA-HSM key |
| Key Vault Reader | Get on the certificate (to fetch the public part) |
Using legacy access policies instead of RBAC, you need:
- Key permissions:
Sign,Get - Certificate permissions:
Get
HLKSigner.exe <package.hlkx> <kv_url> <cert_name> [timestamp_url]timestamp_url is optional and defaults to none — no RFC 3161 timestamp is
applied, matching what HLK Studio itself produces. Pass a TSA URL (e.g.
http://timestamp.digicert.com) to add a timestamp Object to the
signature.
The package is modified in place — back it up first if you need the unsigned original.
Example:
HLKSigner.exe `
C:\submissions\MyDriver_2025_05_24.hlkx `
https://contoso-signing.vault.azure.net `
contoso-ev-codesignExit codes: 0 success, 1 usage, 2 bad input/package, 3 auth failure,
4 Key Vault error, 5 signing failure.
A successfully signed .hlkx contains a digital-signature origin and one or
more XML signature parts.
.hlkx is a zip. Rename a copy to .zip and open it (or use any zip tool).
Confirm these paths exist:
package/services/digital-signature/origin.psdsorpackage/services/digital-signature/xml-signature/*.psdsxspackage/services/digital-signature/certificate/*.cer
PowerShell one-liner:
Add-Type -AssemblyName System.IO.Compression.FileSystem
[IO.Compression.ZipFile]::OpenRead("C:\submissions\MyDriver.hlkx").Entries |
Where-Object { $_.FullName -like "package/services/digital-signature/*" } |
Select-Object FullNamePackageDigitalSignatureManager can verify packages our tool signs, but
note it requires the cert chain to validate against the local trust store
(an EV cert from DigiCert should chain cleanly on any up-to-date Windows
box). PowerShell snippet:
Add-Type -AssemblyName WindowsBase
$pkg = [System.IO.Packaging.Package]::Open(
"C:\submissions\MyDriver.hlkx",
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read)
$mgr = New-Object System.IO.Packaging.PackageDigitalSignatureManager $pkg
"IsSigned : $($mgr.IsSigned)"
"Verify result : $($mgr.VerifySignatures($true))"
foreach ($sig in $mgr.Signatures) {
" Signer : $($sig.Signer.Subject)"
" Signed parts : $($sig.SignedParts.Count)"
}
$pkg.Close()- The cert public part is loaded from Key Vault via
CertificateClient.GetCertificate. - An
RSAinstance is created withRSAKeyVaultProvider.RSAFactory.Createbound to the certificate'sKeyId. The only operation we invoke on it isSignHash(byte[], HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1), which proxies to Key Vault'scryptographyClient.Sign(RS256, hash). - The package is opened as a
ZipArchiveinUpdatemode. We:- Ensure
[Content_Types].xmlhasDefaultentries for.psdsor/.psdsxsand anOverridefor the origin part. - Create an empty origin part at
package/services/digital-signature/origin.psdsor. - Add a
Relationshipfrom the package root to the origin (type.../digital-signature/origin). - Write an origin-rels file
package/services/digital-signature/_rels/origin.psdsor.relspointing to the signature part (type.../digital-signature/signature). - Compute SHA-256 of every other part in the zip and build the XML signature manifest from those digests.
- Canonicalize the manifest
<Object>and<SignedInfo>with C14N, compute SHA-256 of<SignedInfo>, and callkvRsa.SignHash(...)to produce the<SignatureValue>. - Embed the cert in
<KeyInfo><X509Data><X509Certificate>and write the full XML topackage/services/digital-signature/xml-signature/{thumbprint}.psdsxs.
- Ensure
- Digest algorithm: SHA-256 throughout
(
http://www.w3.org/2001/04/xmlenc#sha256for digests,http://www.w3.org/2001/04/xmldsig-more#rsa-sha256for the signature). - RFC 3161 timestamping is optional and off by default (default
none, matching HLK Studio). Passing a TSA URL adds a<TimeStamp>element built viaRfc3161TimestampRequestover the<SignatureValue>.
- No batch mode. One package per invocation.
- No global tool packaging. Plain
.exeonly.
| Symptom | Likely cause |
|---|---|
CredentialUnavailableException |
Run az login; verify az account show returns the right tenant. |
403 Forbidden on the certificate fetch |
Missing Key Vault Reader (or Certificate Get permission). |
403 Forbidden on signing |
Missing Key Vault Crypto User (or Key Sign permission). |
FileFormatException opening the package |
Not a valid OPC/zip package, or file is open in another process. |
CryptographicException: Invalid algorithm specified |
Vault key is not RSA, or vault is Standard SKU and EV cert needs Premium HSM. |
- Microsoft: HLK signing with an HSM
- OpenOpcSignTool — reference OPC signing implementation
- dotnet/sign — Microsoft's multi-format signer
- RSAKeyVaultProvider — RSA-over-Key-Vault shim