← All use cases
Software & Firmware Integrity·19 min

Code signing: proving who shipped a binary, and what it can't prove

Every operating system update, driver, mobile app, and container image you install is checked against a digital signature first. Here's what that signature actually binds, why timestamps and revocation dates matter more than the algorithm, and why signing keys now have to live in hardware.

View as slides: 8 visual slides, ready to present
Developer / EngineerSecurity ArchitectIT Ops / DevOpsGRC / Risk & ComplianceExecutive / Business LeaderResearcher / Academic
A signature provesNot one bit has changedWho held the signing keyThat they approved these bytesIt doesn't proveThe code is safeThere is no backdoorThe build wasn't tampered withvs
Proving who shipped a binary, and what that proof can't tell youOpen the slides →

What a code signature binds, and what it doesn't

A code signature answers two questions about a file: has a single bit changed since it was signed, and who signed it? The first comes from the hash; the second from the certificate chain that ties the signing public key to a named publisher. That is the whole promise. A signature says nothing about whether the code is safe, well written, or free of a backdoor: it only says that whoever held the publisher's private key approved these exact bytes.

That distinction is the lesson of most real code-signing incidents. Stuxnet (2010) carried drivers signed with private keys stolen from two Taiwanese hardware vendors, so Windows loaded them as legitimate. In the ASUS 'ShadowHammer' attack (disclosed 2019), attackers inside the vendor's update infrastructure pushed a trojanized updater signed with ASUS's own certificate. In SolarWinds (2020) the malicious code was injected during the build, so the normal pipeline signed it exactly as it signed every other release. In each case the cryptography worked perfectly; what failed was control over the key or over what reached it.

The mechanics: hash, sign, wrap, ship

Signing a binary is the hash-then-sign pattern from the signatures module, applied to a file format. The signing tool hashes the file (skipping the bytes where the signature itself will go, so adding it doesn't change the hash), signs that digest with the publisher's private key, and embeds the result together with the certificate chain. Windows Authenticode wraps it all in a CMS SignedData structure (RFC 5652); Apple's codesign, Android's APK Signature Scheme, and Java JAR signing each use their own container, but the cryptographic core is the same.

The algorithms are the familiar ones: SHA-256 or SHA-384 for the digest, and RSA, ECDSA, or (in newer ecosystems such as package registries and Sigstore-style tooling) Ed25519 for the signature. Much of the deployed Windows ecosystem still uses RSA with PKCS#1 v1.5 padding for signatures, which remains acceptable for signing even though v1.5 encryption padding is broken; RSA-PSS is the more modern choice where verifiers support it. Signature size depends only on the key, not the file: a 2 GB installer and a 2 KB script signed with the same RSA-3072 key carry identical-size signatures.

Signing and verifying a release

  1. 1

    Build

    The pipeline produces the final artifact. Anything injected before this point gets signed along with everything else.

  2. 2

    Hash

    The signing tool computes SHA-256 over the artifact, excluding the signature slot.

  3. 3

    Sign inside the HSM

    Only the digest goes to the HSM or signing service; the private key never leaves it.

  4. 4

    Timestamp

    The signature's hash goes to a Time-Stamping Authority, which returns a signed token binding it to the current time (RFC 3161).

  5. 5

    Verify on install

    The OS rehashes the file, checks the signature, builds the chain to a trusted root, and checks the certificate was valid and unrevoked at the timestamped time.

Practice

A publisher signs a 2 GB installer with an RSA-3072 key. How many bytes is the RSA signature value itself?

Timestamps: why a signature can outlive its certificate

Code-signing certificates expire, and since 1 March 2026 the CA/Browser Forum caps publicly trusted ones at 460 days (Ballot CSC-31, down from 39 months). Software, though, stays installed for a decade. Without help, every signature would stop verifying the day its certificate expired.

The fix is a trusted timestamp. Right after signing, the tool sends a hash of the signature to a Time-Stamping Authority, which returns its own signed token saying 'this signature existed at this time' (RFC 3161). A verifier then asks a different question: not 'is the certificate valid now?' but 'was it valid, and not revoked, at the timestamped moment?' A timestamped signature from 2019 still verifies in 2026; an untimestamped one generally does not once the certificate has expired.

Revoking a signing key without breaking every past release

When a signing key leaks, revoking its certificate the ordinary way would be a disaster: every legitimate binary ever signed with it would fail verification along with the attacker's. Code signing handles this with the timestamp. The revocation is recorded as taking effect from the estimated time of compromise, so signatures timestamped before that point keep verifying and anything timestamped after it is rejected.

That only works if the publisher can say when the compromise happened and the timestamps are trustworthy, which is one more reason the timestamp comes from an independent authority rather than the signer's own clock. It also explains why investigators care so much about establishing an incident timeline: the revocation date decides which shipped software customers keep trusting.

Practice

A vendor's signing key was compromised on day 150. Its certificate is revoked effective day 150. Four releases carry valid timestamps from days 10, 100, 200, and 300. How many still verify?

Why signing keys now have to live in hardware

Because a stolen signing key turns malware into trusted software, the CA/Browser Forum's Code Signing Baseline Requirements have required since 1 June 2023 that the private key for any publicly trusted code-signing certificate, OV as well as EV, be generated and kept in a hardware module validated to at least FIPS 140-2 Level 2 or Common Criteria EAL 4+, or an equivalent. In practice that means a USB token, an on-premises HSM, or a cloud HSM-backed signing service; a .pfx file on a build server no longer qualifies.

Hardware stops the key from being copied, but not from being used. An attacker inside the build system can still submit malicious code to a perfectly protected HSM. So mature setups add controls around the key's use: signing as a separate service the build calls with a digest, per-release approvals or M-of-N authorization for production keys, separate keys for test and release, and logs of every signing operation that someone actually reviews. The key itself is usually created in a recorded key ceremony with dual control, the same discipline used for CA root keys.

Go deeperKeyless signing and transparency logs⌄

Sigstore, now common for open-source packages and container images, removes the long-lived key altogether. The signer authenticates with an OpenID Connect identity (a developer's account, or a CI workflow's identity), generates an ephemeral key pair, and receives a certificate from the Fulcio CA that binds the public key to that identity for about ten minutes. The signature, certificate, and artifact hash are recorded in the Rekor transparency log, and the ephemeral private key is thrown away.

Verification then checks the identity in the certificate ('built by this repository's release workflow') and the log inclusion proof, which also stands in for the timestamp. There is no key to steal later, and every signature is publicly auditable, the same idea Certificate Transparency applied to TLS certificates. The trade-off is that trust moves to the identity provider, the CA, and the log, and that verifiers must be configured with which identities to expect.

Knowledge check

Test what you just learned →

8 quick questions, with an explanation for every answer.

Up next

Secure boot: a chain of signatures from the first instruction to the OS →

Malware that runs before the operating system can hide from everything that runs after it. Secure boot makes each boot stage verify the next, starting from a key the attacker can't change. Here's how UEFI Secure Boot, measured boot, and anti-rollback fit together, and where they have failed.

Questions & feedback

Discuss this page

Ask a question or comment with your GitHub account. New questions get a first answer from Claude, an AI model, clearly marked as AI; people can correct or add to it. How this works

Showing the discussion loads a widget from giscus.app and GitHub, which then see your IP address and browser details.