Table of Contents
If your code signing key still lives on a USB token sitting in someone’s desk drawer, you’re already out of step with where this industry is headed. Since June 2023, the CA/Browser Forum has required that private keys for publicly trusted code signing certificates sit on FIPS 140-2 Level 3 hardware. A physical token satisfies that. So does a cloud HSM, and for teams already running on Google Cloud, KMS is the more sensible answer than shipping a token to every build machine.
This guide walks through pairing DigiCert code signing certificates with Google Cloud KMS end to end: generating the key, getting DigiCert to issue against it, installing the CNG provider, and actually signing a binary with SignTool. No filler, no vendor pitch — just the sequence that works and the parts that trip people up.
Why the delivery method isn’t a checkbox anymore
Before the 2023 rule change, most developers stored their code signing private key as a .pfx file on the build server. That was the whole security model. The CA/Browser Forum killed that practice for a reason: a stolen .pfx file is a signed malware campaign waiting to happen, and it had already happened more than once.
Hardware-backed storage means the private key material never exists in exportable form. It’s generated inside the HSM boundary and stays there. When you sign, you’re not pulling the key out — you’re sending a hash to the HSM and getting a signature back. The key itself never leaves.
This is exactly what Google Cloud KMS gives you when you choose an HSM protection level, and it’s why DigiCert lists it as a supported “Install on Existing HSM” delivery method. You’re not bolting on compliance after the fact. You’re building the certificate against hardware DigiCert already trusts.
What Google Cloud KMS is actually doing in this setup
Strip away the marketing language and Cloud KMS is a key vault with a signing API sitting in front of it. You create a key ring (a folder, essentially), generate an asymmetric signing key inside it at the HSM protection level, and Google stores that key in a FIPS 140-2 Level 3 certified module across its infrastructure.
The part that matters for code signing: Cloud KMS never hands you the private key. Not through the console, not through the API, not ever. What you get instead is a resource path — something like projects/your-project/locations/us/keyRings/code-signing-ring/cryptoKeys/signing-key/cryptoKeyVersions/1 — and permission to ask that key to sign things on your behalf.
DigiCert’s role doesn’t change in this arrangement. It still validates your organization, still issues the certificate, still runs the whole chain of trust back to its root. The only thing that moves is where the private key physically sits. Instead of a token plugged into a laptop, it’s a key version inside a Google-managed HSM cluster, reachable from wherever your build pipeline runs.
Before you start
Have these ready before opening a terminal — half the frustration with this setup comes from starting the certificate request before the key exists.
- A Google Cloud project with billing enabled and the Cloud KMS API turned on
- IAM permissions to create key rings and keys — Cloud KMS Admin at minimum, plus Cloud KMS CryptoKey Signer/Verifier for whichever identity will actually sign
- A DigiCert account able to place a Code Signing Certificate order with “Install on Existing HSM” (or equivalent cloud HSM) as the delivery method
- Organization validation already completed, or ready to complete — DigiCert still needs to confirm you’re a legitimate business before issuing anything
- A Windows machine with the Windows SDK installed, for SignTool
- Admin rights on that Windows machine to install the Google Cloud KMS CNG provider
One more thing worth deciding upfront: RSA or EC. SignTool combined with the Google Cloud KMS CNG provider does not support EC keys — you need RSA, and 3072-bit is the safe default if your signing tool doesn’t specify otherwise.
Step 1: Create the key ring and signing key
Everything starts in Cloud KMS. Pick a location — us or global both work, but keep it consistent with wherever your build infrastructure lives, since cross-region calls add latency to every sign operation.
Create the key ring first:
gcloud kms keyrings create code-signing-ring \
--location us
Then create the key itself, set to the HSM protection level and asymmetric signing purpose:
gcloud kms keys create code-signing-key \
--location us \
--keyring code-signing-ring \
--purpose asymmetric-signing \
--default-algorithm rsa-sign-pkcs1-3072-sha256 \
--protection-level hsm
That last flag is the one to double-check. --protection-level hsm is what makes this a FIPS 140-2 Level 3 key rather than a software-backed one. Leave it out and you’ll get a key that works fine for testing but won’t satisfy DigiCert’s hardware requirement for the certificate you’re about to request.
Step 2: Generate the CSR and key attestation
With the key created, you need two things from it: a certificate signing request DigiCert can process, and a key attestation proving the key genuinely lives on HSM-backed hardware. DigiCert won’t take your word for it — the attestation is what closes that gap.
Google Cloud gives you two attestation formats. Download the one packaged as a .zip file, not the PEM version. This trips up more people than anything else in this process — DigiCert’s intake specifically wants the zip, and a PEM upload will bounce back as invalid every time.
For the CSR itself, you have two practical routes: use the PKCS#11 library Google provides (set the KMS_PKCS11_CONFIG environment variable to point at a YAML file describing your key ring), or generate it through a signing tool that already understands Cloud KMS resource paths, like Jsign. Either way, the CSR needs the Code Signing extended key usage set explicitly — most OpenSSL configs default to server auth, and that certificate won’t work for Authenticode signing.
Step 3: Complete DigiCert certificate enrollment
Inside DigiCert’s CertCentral portal, place the code signing certificate order and select the HSM-based delivery method — worded as “Install on Existing HSM” or similar, depending on which product tier you’re on. Upload the CSR and the attestation zip from the previous step when prompted.
From here it’s DigiCert’s standard validation process, and it moves at the speed of your organization’s paperwork, not the technology. Expect a call or email verifying the business, confirming the requester has authority to sign on the organization’s behalf, and possibly a duns number or equivalent business registry check if this is a new account.
Once validation clears, DigiCert issues the certificate against the public key from your CSR. You’ll receive the certificate as a .crt or .cer file — this is the public certificate, not a keystore, since your private key was never exported in the first place. Save it somewhere your signing tool can reach it.
Step 4: Install the Google Cloud KMS CNG provider
On the Windows machine that will actually run SignTool, download and install the Google Cloud KMS CNG provider using the .msi installer from Google’s release page. This registers a Key Storage Provider named “Google Cloud KMS Provider” inside Windows, which is how SignTool learns to route signing requests to Cloud KMS instead of a local certificate store.
After installing, authenticate the machine so it can actually call the KMS API. For a workstation or a one-off signing box, that usually means:
gcloud auth application-default login
For a CI/CD runner, skip the interactive login and attach a service account instead — this is the setup you want for GitHub Actions, Azure DevOps, or any build agent that runs unattended. Grant that service account the Cloud KMS CryptoKey Signer/Verifier role, scoped to the specific key, not the whole project. There’s no reason a build agent needs permission to touch every key in your KMS instance.
Step 5: Sign your executable with SignTool
This is the payoff step, and once it’s configured it looks almost identical to signing with a local certificate. The difference is two extra flags telling SignTool which CNG provider to use and which KMS key resource to sign against.
signtool sign /v /debug ^
/fd sha256 ^
/tr http://timestamp.digicert.com /td sha256 ^
/f "C:\certs\mycodesigningcert.crt" ^
/csp "Google Cloud KMS Provider" ^
/kc "projects/PROJECT_ID/locations/us/keyRings/code-signing-ring/cryptoKeys/code-signing-key/cryptoKeyVersions/1" ^
"C:\build\MyApplication.exe"
A few flags worth explaining rather than just copying: /fd sha256 sets the file digest algorithm — don’t drop below sha256, Windows SmartScreen and most modern OSes penalize weaker hashes. /tr and /td point to a timestamp authority, which matters more than people think: without it, your signature becomes invalid the moment the certificate expires, even on software signed years earlier. /kc is the full resource path to the specific key version — get one character wrong here and SignTool fails with a generic “provider error” that gives you no clue what actually went wrong.
Run it, and if everything’s wired up correctly, SignTool reports success and the binary now carries a valid Authenticode signature backed by a key that has never touched local disk.
Confirming the signature actually holds
Don’t take SignTool’s success message as the final word — verify it separately, especially the first time you run this pipeline end to end.
signtool verify /v /pa "C:\build\MyApplication.exe"
This checks the full chain: your certificate, the intermediate DigiCert Trusted G4 Code Signing CA, and the DigiCert Trusted Root G4 at the top. It also confirms the timestamp is present and valid. If the chain check fails but the signature itself looks fine, it’s usually a missing intermediate certificate in the local store rather than a problem with the KMS side of things.
Worth doing once: right-click the signed file in Windows Explorer, open Properties, and check the Digital Signatures tab. Seeing the publisher name and a valid timestamp there is the sanity check that matters to anyone downstream who isn’t going to run signtool verify themselves.
Where this setup actually breaks
Most problems here aren’t Cloud KMS problems or DigiCert problems — they’re the handoff between the two, or a Windows configuration detail nobody documents clearly.
| Symptom | Usual cause | Fix |
|---|---|---|
| SignTool can’t find the certificate even though it’s in the Windows cert store | SignTool looks for certs paired with the CNG provider, not the general cert store | Reference the .crt file directly with /f, don’t rely on /n or /s store lookups |
| “Provider error” with no detail | Wrong resource path in /kc, or the key version was destroyed/disabled |
Re-check the full path against gcloud kms keys versions list, confirm the version state is ENABLED |
| DigiCert rejects the attestation upload | Uploaded the PEM attestation instead of the zip | Re-download the attestation and confirm it’s the .zip format before resubmitting |
| SignTool fails specifically with EC keys | The CNG provider doesn’t support EC keys for SignTool workflows | Regenerate the key as RSA 3072-bit and re-run certificate enrollment |
| Signature verifies but shows no timestamp | /tr flag omitted or timestamp server unreachable |
Add /tr and /td sha256, confirm outbound HTTPS access from the signing machine |
| Works on one machine, fails on a build agent | Service account missing the CryptoKey Signer/Verifier role on that specific key | Grant the role scoped to the key resource, not just the project |
What it actually costs
Cloud KMS pricing is usage-based and, for most teams, close to a rounding error compared to the certificate itself. An HSM-backed key runs roughly $2.50 per month, plus a small per-operation charge for signing — commonly cited around $0.03 to $0.06 per 10,000 asymmetric signing operations, depending on the algorithm and region.
Unless you’re signing thousands of builds daily, expect the KMS portion of this setup to land in the range of a few dollars a month. The real cost driver remains the DigiCert code signing certificate itself, which is priced the same whether you deliver it to a hardware token or to a cloud HSM. The delivery method changes your operational overhead, not your certificate invoice.
Questions people actually ask before starting this
Can I use Google Cloud KMS with a certificate I already own, or does DigiCert have to reissue it? A certificate already bound to a different key can’t simply be pointed at a new one. You’ll generate a new key in Cloud KMS, submit a fresh CSR, and DigiCert reissues against that key. Your existing certificate’s validity period doesn’t carry over automatically — check with DigiCert support on reissuance terms for your specific order.
Does this work for EV code signing certificates, not just OV? Yes, DigiCert supports both OV and EV code signing certificates delivered to Google Cloud KMS. EV adds stricter identity validation on DigiCert’s side before issuance, but the KMS setup itself doesn’t change.
Do I need a dedicated Cloud HSM cluster, or is a KMS key with HSM protection level enough? A KMS key set to the HSM protection level is sufficient. You don’t need to stand up a separate Cloud HSM cluster — that’s a different, more expensive product meant for other use cases.
Can I sign on macOS or Linux without the CNG provider? The CNG provider is Windows-specific, since it plugs into Microsoft’s Cryptography API. On macOS or Linux, tools like Jsign or the PKCS#11 library talk to Cloud KMS directly without needing a Windows-only component.
What happens if I lose access to the Google Cloud project holding the key? Your signature stays valid on already-signed binaries, since verification only needs the public certificate and the CA chain. But you lose the ability to sign new code until access is restored, so IAM redundancy on who can manage that key ring is worth planning for early.
The takeaway
Hardware-backed key storage stopped being optional in 2023, and it isn’t going back. Pairing DigiCert with Google Cloud KMS gets you compliant without adding a physical token to your supply chain — and once the CNG provider is installed and the IAM roles are set correctly, signing a build looks exactly like it always did, just with a different /csp flag.
The setup takes an afternoon the first time. After that, it’s one command in a build script, same as it’s always been.