QCecuring - Enterprise Security Solutions

Code Signing and Software Supply Chain Security: A Complete Guide

Code signing 20 Mar, 2026 · 05 Mins read

Code signing proves software authenticity and integrity. Here's how to implement it across CI/CD pipelines, protect signing keys, and defend against supply chain attacks like SolarWinds and xz-utils.


SolarWinds (2020): attackers compromised the build system and injected malware into a signed software update. 18,000 organizations installed it — because it was signed by SolarWinds’ legitimate code signing certificate.

xz-utils (2024): a social engineering campaign over 2 years inserted a backdoor into a critical Linux compression library. It passed code review, was merged, and would have been distributed to virtually every Linux system.

Codecov (2021): attackers modified a bash script in Codecov’s CI pipeline. Every company using Codecov’s CI integration leaked their environment variables (including secrets) for 2 months.

The common thread: supply chain attacks bypass all endpoint security because the malicious code arrives through trusted channels — signed, reviewed, and distributed by legitimate infrastructure.

Code signing is the first line of defense. But signing alone isn’t enough. You need signing + provenance + verification + monitoring across the entire supply chain.


What Code Signing Actually Protects Against

Code signing provides two guarantees:

1. Authenticity: This software was published by the claimed entity (verified by the CA that issued the signing certificate).

2. Integrity: This software hasn’t been modified since it was signed (any change invalidates the signature).

What code signing does NOT protect against:

  • Malicious code signed by a legitimate key (SolarWinds scenario — the build was compromised before signing)
  • Compromised signing keys (attacker signs their own malware)
  • Dependencies with vulnerabilities (signing doesn’t audit code quality)

This is why code signing is necessary but not sufficient. You also need: build provenance, dependency verification, and runtime verification.


The Modern Code Signing Stack

Layer 1: Sign Everything

Every artifact that leaves your build pipeline should be signed:

Artifact TypeSigning MethodTool
Windows executables (.exe, .dll)Authenticodesigntool, AzureSignTool
macOS applications (.app)Apple codesigncodesign + notarytool
Java archives (.jar, .war)JAR signingjarsigner
Linux packages (.rpm, .deb)GPG signingrpm —addsign, dpkg-sig
Container imagesOCI signaturescosign (Sigstore)
Helm chartsProvenancehelm package —sign
npm packagesPackage signaturesnpm publish (with provenance)
Python packagesPGP/Sigstoretwine upload (with attestation)
Go binariesSigstorego build + cosign
FirmwareSecure Boot signingsbsign, UEFI signing tools

Layer 2: Protect the Signing Key

The signing key is the most valuable asset in your supply chain. If compromised, an attacker can sign anything as you.

Key storage hierarchy:

Best:  HSM (FIPS 140-2 Level 3) — key never extractable
Good:  Cloud KMS (AWS KMS, Azure Key Vault, GCP KMS) — key never exposed via API
OK:    Secrets manager (Vault) — key delivered to signing process at runtime
Bad:   CI/CD secret (environment variable) — extractable by any pipeline job
Worst: Developer's laptop — one theft away from compromise

Since June 2023, the CA/Browser Forum requires all code signing private keys to be stored in hardware (HSM, hardware token, or cloud HSM). Software-stored code signing keys are no longer permitted for publicly-trusted certificates.

Layer 3: Build Provenance (SLSA)

Signing proves WHO signed. Provenance proves HOW it was built:

{
  "subject": {
    "name": "myapp-v2.1.0.tar.gz",
    "digest": {"sha256": "abc123..."}
  },
  "predicate": {
    "buildType": "https://github.com/slsa-framework/slsa-github-generator",
    "builder": {"id": "https://github.com/actions/runner"},
    "invocation": {
      "configSource": {
        "uri": "git+https://github.com/myorg/myapp@refs/tags/v2.1.0",
        "digest": {"sha1": "def456..."}
      }
    }
  }
}

This provenance attestation (SLSA Level 3) proves:

  • The artifact was built from this specific Git commit
  • On this specific CI/CD platform
  • Using this specific workflow file
  • No human could have modified the build process

Layer 4: Verify Before Deploy

Signing is useless if nobody checks the signature. Verification must be enforced:

Kubernetes (Kyverno policy):

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce
  rules:
  - name: verify-cosign-signature
    match:
      resources:
        kinds: ["Pod"]
    verifyImages:
    - imageReferences: ["ghcr.io/myorg/*"]
      attestors:
      - entries:
        - keyless:
            issuer: "https://token.actions.githubusercontent.com"
            subject: "https://github.com/myorg/*"

Windows (AppLocker/WDAC):

# Only allow executables signed by specific publishers
New-CIPolicy -Level Publisher -FilePath policy.xml
# Blocks unsigned or unknown-signed executables from running

macOS (Gatekeeper):

# Verify signature before running
codesign --verify --deep --strict MyApp.app
spctl --assess --type execute MyApp.app

CI/CD Pipeline Integration

GitHub Actions (Keyless Signing with Sigstore)

name: Build and Sign
on:
  push:
    tags: ['v*']

jobs:
  build-sign:
    runs-on: ubuntu-latest
    permissions:
      id-token: write      # For OIDC token (keyless signing)
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Build container image
        run: docker build -t ghcr.io/myorg/myapp:${{ github.ref_name }} .

      - name: Push to registry
        run: docker push ghcr.io/myorg/myapp:${{ github.ref_name }}

      - name: Sign with cosign (keyless)
        uses: sigstore/cosign-installer@v3
      - run: |
          cosign sign ghcr.io/myorg/myapp@$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/myorg/myapp:${{ github.ref_name }} | cut -d@ -f2)

      - name: Generate SLSA provenance
        uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0

Azure DevOps (HSM-backed Signing)

steps:
  - task: DotNetCoreCLI@2
    inputs:
      command: 'publish'
      publishWebProjects: true

  - script: |
      AzureSignTool sign \
        -kvu "$(SigningVaultURL)" \
        -kvi "$(SigningClientId)" \
        -kvs "$(SigningClientSecret)" \
        -kvc "$(CertificateName)" \
        -tr http://timestamp.digicert.com \
        -td sha256 \
        "$(Build.ArtifactStagingDirectory)/**/*.exe"
    displayName: 'Sign executables with Azure Key Vault'

GitLab CI (Vault-backed Signing)

sign:
  stage: sign
  script:
    - export VAULT_TOKEN=$(vault write -field=token auth/jwt/login role=signer jwt=$CI_JOB_JWT)
    - vault write -field=signature transit/sign/code-signing-key \
        input=$(base64 < build/myapp) > signature.b64
    - cosign attach signature --signature $(cat signature.b64) \
        registry.example.com/myapp:$CI_COMMIT_TAG

Defending Against Supply Chain Attacks

Attack: Compromised Build System (SolarWinds Pattern)

How it works: Attacker gains access to CI/CD, modifies the build to inject malware, legitimate signing key signs the compromised build.

Defenses:

  • SLSA Level 3+ provenance: Proves the build came from a specific source commit on a hardened builder. If the build was tampered with, provenance won’t match.
  • Reproducible builds: Anyone can rebuild from source and verify the output matches the signed artifact. Injected code would produce a different hash.
  • Separated signing: Build system produces unsigned artifacts. A separate, hardened signing system signs them after verification. Compromising the build system doesn’t grant signing access.

Attack: Dependency Confusion

How it works: Attacker publishes a malicious package to a public registry with the same name as your internal package. Build system fetches the public (malicious) version.

Defenses:

  • Lock files with hashes: package-lock.json, go.sum, Pipfile.lock pin exact versions with integrity hashes. Any change is detected.
  • Private registry priority: Configure package managers to check your private registry first.
  • Scoped packages: Use @myorg/package-name (npm) or organization-scoped names that can’t be squatted on public registries.

Attack: Compromised Signing Key

How it works: Attacker steals the code signing private key and signs malware.

Defenses:

  • HSM storage: Key is non-extractable. Attacker must compromise the signing infrastructure, not just steal a file.
  • Signing approval gates: Require human approval (or automated policy check) before each signing operation.
  • Signing volume monitoring: Alert on unusual signing patterns (signing at 3 AM, signing 100 artifacts when normal is 5/day).
  • Certificate Transparency for code signing: Sigstore’s Rekor log records all signing events publicly. Unauthorized signatures are detectable.

Timestamp: The Often-Forgotten Requirement

Code signing without a timestamp has a critical flaw: when the signing certificate expires, the signature becomes unverifiable. Software signed 2 years ago with a now-expired certificate shows “signature expired” warnings.

Timestamping proves WHEN the signature was created. If the certificate was valid at signing time (proven by the timestamp), the signature remains valid forever — even after the certificate expires.

# Always include a timestamp
signtool sign /fd SHA256 \
  /tr http://timestamp.digicert.com \  # RFC 3161 timestamp
  /td SHA256 \
  MyApp.exe

# Without /tr: signature expires with certificate (1-3 years)
# With /tr: signature valid indefinitely

Never skip timestamping. There’s no reason to — timestamp services are free and add milliseconds to the signing process.


FAQ

Q: Do I need code signing if I only deploy to my own infrastructure? A: Yes — code signing proves that what’s running in production is what your CI/CD built. Without it, a compromised deployment pipeline or container registry can inject modified artifacts that look legitimate.

Q: Sigstore (keyless) vs traditional code signing — which should I use? A: Sigstore for container images and internal artifacts (simpler, no key management). Traditional code signing (with HSM-stored key) for software distributed to customers (Windows executables, macOS apps, Java JARs) — because OS verification requires traditional certificates.

Q: How do I handle signing for open-source projects? A: Use Sigstore (free, keyless, identity-based). Sign with your GitHub identity. Consumers verify the signature came from the expected GitHub Actions workflow. No signing keys to manage or protect.

Q: What happens if my signing key is compromised? A: Revoke the certificate immediately (contact your CA). All software signed after the compromise date is suspect. Software signed before (with valid timestamps) remains trustworthy. Notify customers. Generate a new key. Re-sign current releases with the new key.

Stay Ahead on Crypto & PKI

Monthly insights on certificate management, post-quantum readiness, and enterprise security.

Subscribe Free

Related Insights

SSL/TLS

Apache SSL/TLS Configuration Guide: Complete Setup & Hardening

Configure Apache HTTPD with SSL/TLS from scratch — mod_ssl setup, VirtualHost HTTPS, cipher hardening, HSTS, OCSP stapling, Let's Encrypt with Certbot, SNI multi-site hosting, and mTLS client authentication. Working configs for Ubuntu/Debian and RHEL/CentOS.

By Sneha gupta

15 May, 2026 · 06 Mins read

SSL/TLSPractical GuidesDevOps

DevOps

Certificate Expiry Monitoring with Prometheus & Grafana: Complete Setup

Set up certificate expiry monitoring using Prometheus exporters (x509-certificate-exporter, Blackbox exporter, cert-manager metrics), PromQL alerting rules, Grafana dashboards, and AlertManager notifications for Slack and PagerDuty.

By Sneha gupta

15 May, 2026 · 05 Mins read

DevOpsPractical GuidesSSL/TLS

SSL/TLS

Fix 'Keystore Was Tampered With, or Password Was Incorrect' in Java

Fix the Java keystore error caused by wrong password, JKS/PKCS12 type mismatch, or corrupted keystore file. Includes recovery steps and keytool commands.

By Shivam sharma

15 May, 2026 · 03 Mins read

SSL/TLSTroubleshootingDevOps

Ready to Secure Your Enterprise?

Experience how our cryptographic solutions simplify, centralize, and automate identity management for your entire organization.

Stay ahead on cryptography & PKI

Get monthly insights on certificate management, post-quantum readiness, and enterprise security. No spam.

We respect your privacy. Unsubscribe anytime.