DEV Community

Katz Sakai
Katz Sakai

Posted on

Building a Cost-Effective Windows Code Signing Pipeline: Sectigo + Google Cloud KMS on GitHub Actions

Overview

By combining a Sectigo code signing certificate with a Google Cloud KMS HSM, we built an environment that automatically signs Windows application binaries on GitHub Actions.
By performing the signing operations on our own Google Cloud KMS, we avoid the per-signature usage-based billing burden and achieve a more flexible workflow.
This article covers the background, architecture, and step-by-step setup instructions.

System Architecture of the Code Signing Environment on GitHub Actions

The signing environment built on the GitHub Actions Windows Runner is structured as shown below.
The signing process with SignTool.exe is performed using the key stored on the HSM via the KMS CNG (Cryptography Next Generation) Provider supplied by Google. This allows the signing process to be executed securely without ever holding the private key on the GitHub Actions runner.

System Architecture

Why We Chose Sectigo's Code Signing Certificate

The reason is that it allows us to build a code signing environment without incurring unnecessary costs.

For example, the code signing certificates offered by competitor SSL.com are subject to usage-based billing per number of signatures. This usage-based billing can become surprisingly burdensome.
The screenshot below shows SSL.com's code signing pricing. At first glance, 20 signatures per month for 20 USD/month might seem sufficient. However, in practice, production and staging environments are often separate, and if you sign staging binaries each time as well, 20 signatures are quickly exhausted. As a result, you end up needing a higher-tier plan with 100 or 300 signatures, paying a substantial monthly fee.

SSL.com codesign pricing

While we used SSL.com as an example, most vendors that provide their own signing systems employ a similar usage-based billing model.

In contrast, Sectigo code signing allows you to perform signing operations on your own Google Cloud KMS HSM. With this approach, key storage costs just 2.50 USD/month, and signing operations cost 0.15 USD per 10,000 signatures—an extremely cost-effective option.
(All prices as of July 2025.)

Because signing costs are essentially negligible, you can freely sign binaries for both production and staging environments. This guarantees the origin and integrity of binaries in both environments, enabling a more secure distribution pipeline. Furthermore, since signing costs are not a concern, you can flexibly handle re-signing and verification during incident response.

Setup Instructions

Sectigo publishes the procedure as a PDF, and we followed it closely.

https://certificategeneration.com/en/content/pdf/google-kms-code-signing.pdf

Generate a Signing Private Key on Google Cloud KMS HSM

The private key used for code signing is generated on the Google Cloud KMS HSM (Hardware Security Module).
This key does not need to be regenerated as long as its security is maintained, and the same key can be continued to be used when the certificate is renewed (you can skip this step at the next certificate renewal).

(1) Create a Key Ring

Create a key ring to group your keys.
We selected the multi-region asia1 for the region.

(2) Create a Signing Key

Next, create the key used for signing.
On the screen where you specify the key name and protection level, you must select "HSM" as the protection level.
This is because the code signing certificate issuance requirements mandate that "the private key must be generated within an HSM and must not be exportable from the HSM"1.

For the purpose and algorithm, select "Asymmetric sign" and "4096 bit RSA - PKCS#1 v1.5 padding - SHA256 digest" respectively.
We chose this key type because this combination is most commonly used in code signing for Windows binaries in practice (see the notes at the bottom of the page for details).

Once the settings are configured, execute the key generation.

(3) Obtain the Attestation for the Generated Key

An attestation is data that certifies that "a key was securely generated and is securely stored on the HSM." Certificate vendors such as Sectigo need to verify the key's integrity using the attestation when issuing a code signing certificate, so you must download the attestation from Google Cloud KMS.

To download the attestation, first select the generated key, then click "Verify attestation" from the three-dot menu.

Next, in the dialog that appears, click "Download attestation bundle" to download a zip file. Save this file and submit it when purchasing the certificate.

Create a CSR on Your Local Machine

To issue a certificate, you need to create a CSR (Certificate Signing Request) corresponding to the signing key.
This procedure is easiest to perform in a Linux or WSL environment.

The overall architecture for the CSR creation environment is as shown below.

System setup for CSR

A PKCS#11 plugin is integrated into OpenSSL's Engine API (plugin mechanism)2, and the PKCS#11 plugin accesses the Google Cloud HSM through Google's PKCS#11 Library to generate the CSR using the private key.

Install the Required Tools

First, install the tools needed to access Google Cloud KMS from OpenSSL via the PKCS#11 interface.

sudo apt-get install libengine-pkcs11-openssl opensc
Enter fullscreen mode Exit fullscreen mode

Next, download Google's PKCS#11 library from the GitHub releases page and extract it to a directory of your choice.
pkcs#11 · Releases · GoogleCloudPlatform/kms-integrations

Set the path to libkmsp11.so in the extracted directory as the environment variable PKCS11_MODULE_PATH.

PKCS11_MODULE_PATH="/path/to/libkmsp11.so"
Enter fullscreen mode Exit fullscreen mode

Next, create a YAML file at any location that specifies the path to the key ring containing the key used for CSR creation.
The YAML content should look like this.
Replace YOUR_GCP_PROJECT_ID and KEY_RING_NAME with your own Google Cloud project ID and key ring name.

---
tokens:
  - key_ring: "projects/YOUR_GCP_PROJECT_ID/locations/asia1/keyRings/KEY_RING_NAME"
Enter fullscreen mode Exit fullscreen mode

Set the path of the created YAML file as the environment variable KMS_PKCS11_CONFIG.

KMS_PKCS11_CONFIG="/path/to/google_hsm_config.yaml"
Enter fullscreen mode Exit fullscreen mode

Next, obtain the Application Default Credentials (ADC) using the Google Cloud CLI. The account used for login must have the necessary permissions to access the created key.

gcloud auth application-default login
Enter fullscreen mode Exit fullscreen mode

If the ADC has been obtained and the plugin and YAML configuration described above are correctly set up, you should be able to retrieve the key information with the following command.

pkcs11-tool --module /path/to/libkmsp11.so --list-objects
Enter fullscreen mode Exit fullscreen mode

Finally, generate the CSR using the openssl command. Below is an example command for CSR creation.
Replace "EXAMPLE, Inc." with your company's name, and replace test-authenticode-key with the name of the key you created.

openssl req -new -subj '/CN=EXAMPLE, Inc./' -sha256 -engine pkcs11 -keyform engine -key pkcs11:object=test-authenticode-key
Enter fullscreen mode Exit fullscreen mode

Purchase the Code Signing Certificate

Once the attestation and CSR are prepared, proceed to purchase the code signing certificate.
When purchasing from Sectigo Code Signing Certificates, select "Install on Existing HSM" as the Certificate Delivery Method.
There is little reason to choose Extended Validation for the Validation Option (see the notes at the bottom of the page), so Standard Validation should suffice.

You will be prompted to submit the attestation and CSR during the purchase process.

After the purchase, there is a review process for issuing the certificate.
When issuing a certificate under a corporate entity, the following steps are required:

  • Applicant identity verification
    • Submit a photo of the passport itself
    • Submit a selfie holding the passport next to your face
  • Company existence verification
    • Provide your corporate number, and Sectigo will verify it by querying the government's corporate registry
  • Company phone number verification
    • An automated voice call is placed to the company phone number set during purchase, providing a numeric code that you enter into a web form to complete the verification

Once this review process is complete, the certificate is issued.

Integrate into GitHub Actions

Once the certificate has been issued, integrate the code signing process into the Windows Runner on GitHub Actions.
The signing process uses signtool.exe on the Windows Runner. The signing key stored in Google Cloud KMS is accessed through the KMS CNG (Cryptography Next Generation) Provider supplied by Google. Additionally, a timestamp can be applied to the signature, allowing the signature to remain valid even after the certificate expires3.

Below is an example GitHub Actions configuration for performing the signing process.

    - name: Install Google Cloud KMS CNG provider for code signing with Sectigo BYO HSM, and add SignTool.exe to PATH
      run: |
        curl -L https://github.com/GoogleCloudPlatform/kms-integrations/releases/download/cng-v1.2/kmscng-1.2-windows-amd64.zip -o kmscng.zip
        unzip kmscng.zip
        cd kmscng-1.2-windows-amd64
        msiexec /i kmscng.msi /quiet /qn /norestart

        # Create a working directory
        New-Item -Path "C:\Windows\KMSCNG" -ItemType Directory -Force

        # Add SignTool.exe to PATH
        echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $env:GITHUB_PATH
      shell: pwsh

    - name: Decode the BASE64-encoded Sectigo code signing certificate and save it to a file
      run: |
        [IO.File]::WriteAllBytes(
          'C:\Windows\KMSCNG\cert.p12',
          [Convert]::FromBase64String("${{ vars.sectigo_code_signing_cert_base64 }}")
        )
      shell: pwsh

    # The account used here must be granted the roles/cloudkms.signerVerifier role
    - name: Authenticate to Google Cloud for access to the Sectigo code signing private key on Google Cloud KMS
      uses: google-github-actions/auth@v2
      with:
        project_id: YOUR_PROJECT_ID
        create_credentials_file: true
        workload_identity_provider: SAMPLE_PROVIDER
        service_account: YOUR_SERVICE_ACCOUNT@PROJECT.iam.gserviceaccount.com
        export_environment_variables: true
        access_token_lifetime: 600

    - name: Sign the code
      run: |
        signtool.exe sign /v /debug /as /fd sha256 /tr http://timestamp.sectigo.com /td SHA384 /f C:\Windows\KMSCNG\cert.p12 /csp "Google Cloud KMS Provider" /kc projects/YOUR_GCP_PROJECT_ID/locations/asia1/keyRings/KEY_RING_NAME/cryptoKeys/KEY_NAME/cryptoKeyVersions/1 C:\Temp\binary-to-be-signed.exe
      shell: cmd
Enter fullscreen mode Exit fullscreen mode

Notes

Key Types and Bit Lengths Commonly Used in Code Signing

We surveyed the key types and bit lengths used for code signing in real-world Windows applications. The results from examining 30 Windows application installers on hand as of May 2025 are as follows.

Key Type / Bit Length Count
RSA 4096-bit 17
RSA 3072-bit 9
ECC 384-bit 1
ECC 256-bit 1

These results show that choosing RSA 4096-bit is the safe bet.

Instant SmartScreen Bypass via EV Certificate Is No Longer Possible

Previously, performing code signing with an EV (Extended Validation) code signing certificate could bypass the Microsoft SmartScreen warning screen.

However, due to a change in Windows behavior in March 2024, even EV certificates can no longer instantly bypass SmartScreen. As of now, SmartScreen warnings may still appear until sufficient reputation has been built up for the certificate itself or the signed binary.

This is also noted on Sectigo's EV code signing certificate purchase page:

Note: In March 2024, Microsoft changed the way MS SmartScreen interacts with EV Code Signing certificates. EV Code Signing certificates remain the highest trust certificates available, but they no longer instantly remove SmartScreen warnings.

Therefore, there is no longer a reason to choose an EV certificate for the purpose of SmartScreen mitigation.


  1. This became an industry requirement because there were numerous incidents where private keys were leaked and code signing certificates were abused, as exemplified by NVIDIA's code signing private key leak

  2. For more on the OpenSSL Engine API, see the article What is the OpenSSL Engine API that enables integration with cloud HSMs and YubiKeys

  3. When a trusted third-party timestamp is applied, the date and time of signing become provable, allowing a third party to verify that the certificate was indeed valid at the time of signing. Without a timestamp, the signing date and time are self-reported by the signer, making it impossible for a third party to verify whether the certificate was valid at the time of signing (since it would be possible to set the machine's clock to a past date and sign). Therefore, once the certificate expires, the code signature is also considered invalid. 

Top comments (0)