Most code signing tutorials published before 2023 follow the same pattern: export a PFX file, base64-encode it, store it as a CI secret, and decode it at signing time. That pattern is obsolete. Since June 2023, the CA/B Forum’s hardware storage mandate means commercially issued OV and EV code signing private keys must live in FIPS 140-2 Level 2+ hardware, not in files. A private key that can be exported to a PFX cannot be issued by a public CA.
This guide covers how code signing actually integrates with CI/CD pipelines in 2026: the three current approaches, platform-specific configurations for GitHub Actions and Azure DevOps, secret management practices, and the verification gate that makes signing a quality check rather than a checkbox.
Any tutorial that instructs you to download your code signing certificate as a PFX file and store it in a CI secret is describing a workflow that is no longer possible for commercially issued OV and EV certificates. If you are following such a tutorial, you need this guide instead.
The Three Current Integration Patterns
With hardware-resident private keys, pipeline signing requires the pipeline to reach the hardware where the key lives. There are three practical ways to arrange this:
| Pattern | How it works | Best for | Limitations |
| Cloud HSM signing service | The CA or provider manages HSMs in the cloud. The pipeline authenticates with API credentials and submits signing requests. The key never leaves the provider’s hardware. | Most teams. No hardware to manage. Works from any runner including GitHub-hosted. | API credentials must be protected; additional cost over token delivery; depends on service availability. |
| Self-hosted runner with USB token | A dedicated machine runs the CI runner software and has a USB token permanently connected. The pipeline executes on that runner; signing uses the local token. | Organizations that need on-premises key control or already have hardware token infrastructure. | Single point of failure; token lockout risk from repeated PIN failures; key is exposed to anything running on that machine. |
| Enterprise HSM via API | An on-premises network HSM (Thales, Entrust, Utimaco) exposes a signing API. The pipeline authenticates and requests signatures via that API. | Large enterprises with existing HSM infrastructure and dedicated security teams managing key material. | Requires HSM infrastructure investment; complex initial setup; availability depends on internal HSM uptime. |
For most teams that don’t have enterprise HSM infrastructure, the choice is between the cloud HSM signing services. Microsoft Trusted Signing (~$10/month, US/Canada orgs with 3+ year history) and SSL.com eSigner (subscription model, 30-day free trial, international availability) are the two that have the best GitHub Actions and Azure DevOps integration as of 2026. DigiCert KeyLocker is the enterprise-grade option. All three eliminate the USB token problem entirely.
GitHub Actions: Practical Configuration
Pattern A: Microsoft Trusted Signing
Microsoft Trusted Signing has a first-party GitHub Actions integration through the Azure/trusted-signing-action. This is the simplest setup for teams already on Azure and eligible for Trusted Signing (US/Canada, organizations with 3+ years of verifiable operating history).
| # .github/workflows/release.yml
name: Release Build and Sign
on: push: tags: [‘v*’]
jobs: build-sign: runs-on: windows-latest steps: – uses: actions/checkout@v4
– name: Build run: dotnet publish -c Release -o ./publish
# Sign using Microsoft Trusted Signing – name: Sign with Trusted Signing uses: azure/trusted-signing-action@v0 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: ${{ secrets.SIGNING_ACCOUNT }} certificate-profile-name: ${{ secrets.CERT_PROFILE }} files-folder: ./publish files-folder-filter: exe,dll file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com timestamp-digest: SHA256
# Verify signature after signing (required gate) – name: Verify signatures run: | Get-ChildItem ./publish -Include *.exe,*.dll -Recurse | ForEach-Object { $result = signtool verify /pa /v $_.FullName if ($LASTEXITCODE -ne 0) { Write-Error “Signature verification failed: $($_.Name)” exit 1 } } shell: pwsh |
Pattern B: SSL.com eSigner via CodeSignTool
SSL.com eSigner uses CodeSignTool, a Java-based CLI that handles authentication with the eSigner API and performs the signing operation. The tool is cross-platform and works on Windows, Linux, and macOS runners.
| # Signing step using SSL.com eSigner CodeSignTool
– name: Sign with SSL.com eSigner env: ESIGNER_USERNAME: ${{ secrets.ESIGNER_USERNAME }} ESIGNER_PASSWORD: ${{ secrets.ESIGNER_PASSWORD }} ESIGNER_CREDENTIAL_ID: ${{ secrets.ESIGNER_CREDENTIAL_ID }} ESIGNER_TOTP_SECRET: ${{ secrets.ESIGNER_TOTP_SECRET }} run: | java -jar CodeSignTool.jar sign \ -username=$ESIGNER_USERNAME \ -password=$ESIGNER_PASSWORD \ -credential_id=$ESIGNER_CREDENTIAL_ID \ -totp_secret=$ESIGNER_TOTP_SECRET \ -input_file_path=./publish/YourApp.exe \ -output_dir_path=./signed/ shell: bash |
Pattern C: DigiCert KeyLocker via smctl
| # DigiCert KeyLocker signing using smctl
– name: Install smctl run: | Invoke-WebRequest -Uri ‘https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download’ -OutFile smtools.msi Start-Process msiexec.exe -ArgumentList ‘/i smtools.msi /quiet’ -Wait shell: pwsh
– name: Sync certificate to Windows store run: smctl windows certsync env: SM_API_KEY: ${{ secrets.SM_API_KEY }} SM_CLIENT_CERT_FILE: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }} SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
– name: Sign with signtool via smctl KSP run: | signtool sign /sha1 $env:SM_CODE_SIGNING_CERT_SHA1_HASH \ /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 \ /v ./publish/YourApp.exe env: SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CERT_THUMBPRINT }} |
Azure DevOps: Pipeline Configuration
Azure DevOps has native integration with Azure Key Vault for secret management and with Microsoft Trusted Signing through the Trusted Signing extension (available in the Azure DevOps Marketplace).
Using the Trusted Signing extension
| # azure-pipelines.yml
trigger: tags: include: [‘v*’]
pool: vmImage: ‘windows-latest’
steps: – task: DotNetCoreCLI@2 displayName: ‘Build’ inputs: command: ‘publish’ publishWebProjects: false arguments: ‘-c Release -o $(Build.ArtifactStagingDirectory)’
– task: TrustedSigning@0 displayName: ‘Sign with Trusted Signing’ inputs: Endpoint: ‘https://eus.codesigning.azure.net/’ TrustedSigningAccountName: ‘$(SIGNING_ACCOUNT)’ CertificateProfileName: ‘$(CERT_PROFILE)’ FilesFolder: ‘$(Build.ArtifactStagingDirectory)’ FilesFolderFilter: ‘exe,dll,msi’ FileDigest: ‘SHA256’ TimestampRfc3161: ‘http://timestamp.acs.microsoft.com’ TimestampDigest: ‘SHA256’
– task: PowerShell@2 displayName: ‘Verify all signatures’ inputs: targetType: ‘inline’ script: | $files = Get-ChildItem ‘$(Build.ArtifactStagingDirectory)’ -Include *.exe,*.dll -Recurse foreach ($file in $files) { signtool verify /pa $file.FullName if ($LASTEXITCODE -ne 0) { throw “Verification failed: $file” } } |
Secret Management for Signing Credentials
Signing credentials in pipelines are high-value targets. The credential that can call the signing API can sign anything as your organization. Managing these credentials deserves the same rigor as managing the private key itself.
What credentials are involved
- Microsoft Trusted Signing: Azure service principal (tenant ID, client ID, client secret) or Workload Identity Federation. Workload Identity Federation is preferred: no secret stored in GitHub, authentication uses OIDC tokens.
- com eSigner: username, password, credential ID, and a TOTP secret. The TOTP secret is the most sensitive element: anyone with it can generate valid OTP codes. Store as an encrypted secret, never in plaintext.
- DigiCert KeyLocker: API key and client certificate. The client certificate provides mutual TLS authentication; both must be stored securely.
Least-privilege credential design
Create signing credentials that have the minimum permissions needed for signing, nothing more. For Microsoft Trusted Signing, this means a service principal with the Trusted Signing Certificate Profile Signer role only : not Contributor or Owner on the resource group. For eSigner and KeyLocker, use credentials scoped to the specific certificate profile used for signing, not to the entire account.
Rotate signing credentials independently of signing certificates. A compromised API token should be revokable without requiring a new certificate. Set expiry on API tokens and rotate them on a schedule (quarterly is reasonable for most threat models) rather than letting them persist indefinitely.
Restricting which pipelines can sign
Not every pipeline should have access to signing credentials. A PR build or feature branch build that produces unsigned artifacts is appropriate; signing credentials should be limited to release pipelines triggered by tags or merges to main.
In GitHub Actions, use environment protection rules to gate access to signing secrets: create an environment named ‘release’, store signing secrets in that environment, and require a approval or restrict which branches can deploy to it. In Azure DevOps, use variable groups with environment restrictions.
The Sign-Last Principle in Pipeline Context
Code signing produces a cryptographic hash of the file at signing time. Any modification to the file after signing breaks the signature. In a pipeline, this means signing must be the last operation performed on any artifact that will be distributed.
A correctly ordered pipeline for a Windows application:
- Compile and link
- Run unit and integration tests
- Package (create MSI, MSIX, or ZIP)
- Sign all packaged executables and DLLs
- Sign the installer package itself as a separate step
- Verify all signatures pass signtool verify /pa
- Upload to distribution (store, website, update server)
Nothing after signing should touch the signed files. Version stampers, telemetry injectors, post-processing scripts, and patchers must all run before the signing step. If a tool claims to add value by modifying binaries after signing, either the tool must run before signing or the binary must be re-signed after the tool runs.
A common anti-pattern: signing during the test phase to verify the signing process works, then running the release build again which produces unsigned artifacts. The signed artifact from testing is discarded; the released artifact is unsigned. Sign once, on the final release artifact, as the last step before upload.
Signature Verification as a Required Pipeline Gate
Signing without verification is a step, not a control. A pipeline that signs files but doesn’t verify the signatures produces no guarantee that signing succeeded. Verification should be a required step that fails the build if any signed file doesn’t pass.
What verification catches that signing success alone doesn’t:
- A signing step that exited without error but didn’t actually sign the file
- A post-sign step that modified the signed file before verification ran
- A timestamp server failure that left the file signed without a timestamp
- The wrong certificate being selected (if using /a auto-select)
The verification step should run signtool verify /pa on every file that the signing step touched. The /pa flag enforces the full Authenticode verification policy, matching what Windows actually checks at execution time. A file that passes /pa is correctly signed with a trusted chain. A file that fails should cause the pipeline to fail and the artifact to be discarded.
Multi-Platform Signing in One Pipeline
A cross-platform application distributed on Windows, macOS, and Android requires platform-specific signing for each target. This is typically handled with separate jobs in the pipeline, each running on the appropriate platform runner:
- Windows signing job: runs on windows-latest runner; uses signtool with a cloud HSM service; signs all PE binaries and the installer.
- macOS signing job: runs on macos-latest runner; uses codesign with a Developer ID Application certificate stored in the runner’s Keychain (imported from secrets); submits to xcrun notarytool; staples the ticket.
- Android signing job: runs on ubuntu-latest or windows-latest; uses Gradle signingConfig with keystore credentials stored as secrets; produces a signed AAB for Play Store upload.
The critical constraint: macOS signing and notarization must run on a macOS runner. Apple’s codesign and notarytool tools don’t run on Linux or Windows. GitHub provides hosted macOS runners (macos-latest, macos-14); Azure DevOps provides macOS agents in their hosted pools.
The Audit Trail: What CI/CD Signing Gives You Beyond a Signature
Signing in a CI/CD pipeline produces more than a signed binary. It produces a traceable audit record connecting every distributed artifact to a specific build.
- Build-to-artifact traceability: the pipeline run that produced and signed a specific file is recorded in the CI system. If a signed binary is later found to be malicious, the pipeline run that produced it can be identified, and all other artifacts from that run can be assessed.
- Signing operation logs: cloud HSM services log every signing operation with timestamp, certificate fingerprint, file hash, and the authenticated identity that requested the signing. This log is independently auditable.
- Attestation: some platforms (GitHub Actions with artifact attestation, SLSA provenance) can generate signed attestations linking a build artifact to the specific workflow and commit that produced it. This extends the trust chain beyond Authenticode to the build environment itself.
- Access control evidence: the pipeline’s secret management records show which credentials were used for which signing operations. Combined with the signing service’s own logs, this provides evidence that signing credentials were only used for authorized builds.
Frequently Asked Questions
Can I still use a PFX file in my pipeline for testing with a self-signed certificate?
Yes, for development and testing with a self-signed certificate, the PFX approach still works. Self-signed certificates are not commercially issued and not subject to the CA/B Forum hardware storage mandate. Using a self-signed certificate in a PFX file in your pipeline is fine for testing the signing process itself. The output won’t be trusted by end users’ Windows installations, but it lets you validate your pipeline’s signing logic before connecting it to a production signing service.
What happens if the signing service is down during a release build?
Build in retry logic with exponential backoff for signing operations. A transient signing service outage shouldn’t fail a release immediately. Most signing service SDKs support retry configuration. For critical releases where a service outage would be unacceptable, consider configuring a secondary signing path (a different cloud signing service with the same certificate) as a fallback, or maintaining a self-hosted runner with a USB token as an emergency backup option.
How should signing credentials be rotated without breaking running pipelines?
The rotation procedure: create a new credential in the signing service portal, update the pipeline secret in your CI system to the new credential value, verify a test build succeeds with the new credential, then revoke the old credential in the signing service portal. The overlap period between old and new credentials should be short. In GitHub Actions, secret updates take effect on the next pipeline run; no restart is needed. In Azure DevOps, variable group updates apply to the next run.

Gloria Bradford is a renowned expert in the field of encryption, widely recognized for her pioneering work in safeguarding digital information and communication. With a career spanning over two decades, she has played a pivotal role in shaping the landscape of cybersecurity and data protection.
Throughout her illustrious career, Gloria has occupied key roles in both private industry and government agencies. Her expertise has been instrumental in developing state-of-the-art encryption and code signing technologies that have fortified digital fortresses against the relentless tide of cyber threats.