Choosing between keyless and key-based code signing is really a choice about which secret you are willing to defend: a long-lived private key that signs everything, or the identity system and transparency log that hand out a fresh certificate for every signature. Keyless signing binds a throwaway key pair to an OpenID Connect (OIDC) identity through a short-lived certificate and records the event in a public or private log. Key-based signing keeps one private key, ideally in a hardware module or cloud KMS, and trusts whatever it signs for as long as the key is trusted. This guide compares the two models on threat model, architecture, verification policy, offline and air-gapped constraints, failure modes and migration, for release engineers, platform teams and supply chain security owners. It extends the Sigstore Guide and the Code Signing Security Guide; tools named are examples, not recommendations.
Key takeaways
- Keyless moves the crown jewel, it does not remove it. There is no signing key to steal, but anyone who can obtain an OIDC token for a trusted identity can sign, so the identity provider and CI account become the root of your software trust.
- Verification policy is the real control in both models. A keyless check that accepts any valid certificate proves only that somebody signed; a key-based check that trusts a key shared across ten products proves only that one of ten pipelines signed. Pin identity and issuer, or a key per product.
- Key theft is silent; identity abuse is logged. A stolen private key can sign in private for years, while every keyless signature lands in a transparency log that you can monitor for your own identities, if someone actually watches.
- Air gaps favour keys, but keyless can work offline. Verification can run fully offline with bundles and a distributed trust root; signing offline needs a private certificate authority, log and identity provider, which is a real platform to run.
- Most organisations end up hybrid. Keyless for CI-built artefacts with strong workload identities, key-based (HSM-held, timestamped) for regulated or platform-mandated formats such as Authenticode, firmware and repository metadata.
The short answer
Choose keyless signing for artefacts built by a hosted CI system that issues a workload identity naming the repository, workflow and ref, where your consumers can verify online or accept a periodically refreshed trust root. Choose key-based signing where the platform demands a specific certificate type (Windows Authenticode, kernel drivers, firmware, Apple notarisation), where signers or verifiers sit in disconnected networks, or where signatures must remain verifiable for decades without depending on a third-party log. In both cases spend more effort on the verifier than on the signer: an unpinned policy defeats either model.
Key-based signing asks "can we keep one secret safe for years?"; keyless asks "can we keep every signing identity safe and notice misuse?". The Short-Lived vs Long-Lived Certificates comparison covers the same trade-off for TLS and workload certificates.
Side by side
| Dimension | Keyless (OIDC-bound ephemeral certificate plus log) | Key-based (long-lived private key) |
|---|---|---|
| What signs | A key pair generated in memory for one signing event, then discarded | One private key reused for every release, held in an HSM, cloud KMS, token or (wrongly) a file |
| What binds key to signer | A certificate from a signing CA (Sigstore Fulcio issues ten-minute certificates) carrying the OIDC identity and issuer | Out-of-band distribution of the public key, or a CA-issued code signing certificate valid for months |
| Root of trust | The OIDC issuer, the signing CA and the log keys, distributed through a trust root (for example TUF) | The key holder's custody controls and the channel that publishes the public key |
| Main threat | Compromise of a trusted identity, CI workflow or OIDC issuer | Theft or misuse of the key, or of whatever can call it |
| Detection of misuse | Signatures for your identity appear in the log; monitor it | Usually only from HSM or KMS audit logs, which see calls, not intent |
| Revocation | Rarely needed per signature (certificate already expired); fix the identity and trust policy | Revoke the key or certificate and re-sign or re-timestamp affected releases |
| Offline use | Verification offline with bundles; signing needs a private stack | Native for both signing and verification |
| Operational burden | Low for public-good services; high for a private deployment | Key ceremonies, HSM operations, rotation planning, consumer key distribution |
Keyless signing in depth
Architecture in words
The signer (a CI job or a person) obtains an OIDC ID token, generates an ephemeral key pair and sends the public key with the token to a signing CA. The CA validates the token with the issuer and returns a short-lived X.509 certificate whose subject is the identity in the token; Sigstore's Fulcio documents a ten-minute lifetime and logs every certificate to a certificate transparency log. The signer signs the artefact digest, records the signature and certificate in a signature transparency log (Rekor in Sigstore) and keeps a bundle containing the signature, certificate and inclusion evidence. The private key is then thrown away. A verifier later checks the signature, the certificate chain to a trusted root, that the identity and issuer match its policy, and that the log proves the signature was made while the certificate was valid.
GitHub artifact attestations use the Sigstore public-good instance for public repositories and GitHub's own Sigstore instance for private ones, and need the id-token: write and attestations: write workflow permissions. PyPI attestations implement PEP 740, tie each release file's digest to a Trusted Publisher identity (GitHub Actions, GitLab CI/CD or Google Cloud) and support SLSA Provenance and PyPI Publish predicates. Key distribution failed in practice: when PyPI removed PGP support in 2023, its audit found only 36 percent of the 1,069 unique signing keys seen over three years could be meaningfully verified.
Threat model
| Threat | What the attacker gets | Primary mitigation |
|---|---|---|
| Trusted CI workflow hijacked (script injection, poisoned dependency, malicious pull request reaching a privileged trigger) | Valid signatures under your real workflow identity | Harden the workflow; pin identity to a release workflow on protected tag refs only; require approvals on release environments |
| Developer or maintainer OIDC account compromised | Signatures as that person for any artefact the policy lets them sign | Prefer workload identities over personal ones; phishing-resistant MFA on any human signer |
| OIDC issuer compromised | Signatures as any identity the issuer covers | Trust only hardened issuers; pin the issuer as well as the identity; monitor the log |
| Signing CA compromised | Certificates for any identity, which look valid to verifiers | Trust root rotation (TUF), CT log monitoring |
| Log compromised or unavailable | Hidden or backdated entries; or verifiers unable to confirm inclusion | Witnesses and consistency monitoring; offline bundles with signed entry timestamps |
| Permissive verifier | Attacker signs with their own free account and passes | Mandatory identity and issuer pinning in every verification path |
Sigstore's threat model is explicit that "OIDC account compromise is not handled by Sigstore" and that only properly hardened OIDC issuers should be used. It also states that verification proves only that a signature came from someone who authenticated as an identity at a given time: whether that identity should have signed, and whether the artefact is safe, are policy questions. The second row matters most in practice. In the Nx s1ngularity attack an unsanitised pull_request_target workflow gave the attacker code execution inside the project's pipeline; the attacker then stole a stored publishing token, but the same foothold inside a workflow that held signing permissions would have produced signatures carrying the project's legitimate identity. Keyless signing does not stop that class of attack; workflow hardening does.
Failure modes specific to keyless
- Identity too broad: pinning to a repository with any workflow and any branch, so a feature branch or a test workflow can mint release signatures.
- Personal identities in release paths: a maintainer's email address as the trusted signer, protected only by that account's MFA.
- Repository renames and reuse: GitHub's offline verification documentation warns that name checks compare only names recorded at signing time and cannot detect a rename or a different repository now using the old name; it recommends comparing the immutable repository ID recorded in the certificate.
- Unwatched log: transparency only becomes detection when someone monitors entries for your identities; Sigstore describes identity monitoring in rekor-monitor as work in progress for a limited set of identities.
- Hidden dependency on public services: the public Rekor instance publishes a 99.5 percent availability objective, so a release or admission path that must verify online inherits that dependency.
Key-based signing in depth
Architecture in words
A key pair is generated once, ideally inside a hardware security module or cloud KMS so the private key is never exportable. Either the public key is distributed directly (OpenPGP keys for repository metadata, a cosign public key, a vendor-published key file), or a CA issues a code signing certificate that chains to a root the platform already trusts. Signing requests reach the key through a signing service or a pipeline step; a timestamp authority countersigns so the signature outlives the certificate. Notary Project's Notation, for example, adds an RFC 3161 timestamp countersignature with --timestamp-url and --timestamp-root-cert, and without a configured tsa trust store, verification fails once the signing certificate expires. Verifiers install the public key or trust store and check signatures against it.
Threat model
| Threat | What the attacker gets | Primary mitigation |
|---|---|---|
| Key exfiltrated from a file, CI variable or laptop | Unlimited, undetectable signing until revocation | Non-exportable keys in HSM or KMS; no key material in pipelines |
| Caller of the key compromised (pipeline, service account, KMS IAM role) | Signing on demand, without holding the key | Signing service with authenticated, logged, approved requests; narrow IAM on the sign operation |
| Key shared across products or teams | One compromise signs everything the key covers | One key per product or trust domain |
| Weak key distribution | Consumers trust an attacker's key, or cannot verify at all | Distribute keys through a signed update channel (for example TUF) or the platform's own trust store |
| Rotation never rehearsed | A compromise becomes a long outage for consumers | Documented rotation, overlap periods and consumer update path |
The HashiCorp GPG key exposure shows the classic failure: the key that signed SHA256SUMS files sat in a CI environment variable, the tampered Codecov uploader sent pipeline environment variables to an attacker, and recovery meant revoking the key, re-signing releases and shipping Terraform patch releases so clients would trust the new key. The Signing keys and certificates in breaches page collects more cases.
Failure modes specific to key-based
- Key in reach of build steps: SLSA Build L3 requires that the signing secret is inaccessible to user-defined build steps; a key in a CI secret fails that test however strong the key is.
- No timestamp: releases stop verifying when the certificate expires, which pushes teams to extend certificate lifetimes or disable checks.
- Silent misuse: HSM and KMS logs show that a sign call happened, not what was signed, unless the signing service records the digest and requester.
Verification policy: where both models succeed or fail
Signing is worthless without a verifier that enforces a precise policy. Write it per artefact and make it answer the questions Sigstore's threat model lists: how artefacts are identified, which identities may sign each one, which identity providers are acceptable, which instances and keys to trust, and how revocation is handled.
| Policy element | Keyless setting | Key-based setting |
|---|---|---|
| Who may sign | Exact identity, for example the release workflow path at refs/tags/v* (cosign --certificate-identity) | A specific public key or certificate subject per product |
| Who vouches for the signer | Exact issuer (cosign --certificate-oidc-issuer) | The CA or key distribution channel |
| Build context | Certificate extensions such as source repository, ref and build trigger; immutable repository ID | Signed provenance attestation verified against the same key |
| Signing time | Log inclusion or signed timestamp within certificate validity | RFC 3161 timestamp from a trusted TSA |
| Trust material | Trust root distributed and refreshed through TUF or a managed trusted-root file | Trust store or keyring, locked down and change-alerted |
| Failure behaviour | Fail closed in production admission; alert on any verification failure | Same |
Identity pinning should be as narrow as the release process allows. "Any workflow in this organisation" is close to "anyone". The How to Scope CI OIDC Trust to Branch and Environment page covers the same discipline for cloud federation, and the claims you pin there are the claims you should pin in signing policy.
Offline and air-gapped environments
Here the models differ most, and the split is between verifying and signing.
- Offline verification, keyless: GitHub documents a three-step process: download the attestation bundle and the trusted root while online (
gh attestation trusted-root), copy them across, then verify with--bundleand--custom-trusted-root. The trusted root has no built-in expiry, the instances rotate keys a few times a year, and offline you cannot tell whether a key has been revoked since you generated the file, so refresh the root with every import of new signed material. - Offline verification, key-based: install the public key or trust store once; revocation still needs a channel (CRL import or a new keyring), which in practice is the same import process.
- Offline signing, keyless: requires a private stack: an internal OIDC issuer, a private signing CA and log, and your own trust root. Sigstore documents a preference order of the scaffolding project, your own TUF repository, a trusted-root file created with
cosign trusted-root create, and environment variables only as a last resort. That is a platform with its own availability, key ceremonies and monitoring. - Offline signing, key-based: native, which is why firmware, OT and classified environments usually stay key-based with an HSM and a ceremony process.
For trust root distribution in either model, The Update Framework (TUF) Guide explains threshold signing, expiry and rotation of root metadata.
Decision criteria
| If this is true | Lean towards | Why |
|---|---|---|
| Artefacts are built on a hosted CI platform with fine-grained OIDC claims | Keyless | The workflow identity is more specific than any key, and there is nothing to exfiltrate |
| The platform mandates a certificate type (Authenticode, drivers, firmware, Apple) | Key-based in an HSM or managed signing service | The verifier only trusts its own roots |
| Signers or verifiers have no internet access | Key-based, or a private keyless stack | Public logs and issuers are unreachable |
| Signatures must verify for 10 years or more | Key-based with timestamping, or keyless with archived bundles and trust roots | Long-term verification must not depend on a service still existing |
| You publish open source to an ecosystem that supports attestations | Keyless via the ecosystem's trusted publishing | Consumers can verify without fetching your key |
| You cannot yet harden CI workflows (untrusted triggers, broad tokens) | Fix CI first; either model inherits the weakness | A hijacked pipeline signs in both models |
Small organisations: public-good keyless signing removes key custody entirely and is usually the safer default for CI-built artefacts. Large organisations: expect a hybrid with a central signing service for key-based formats, a private or vendor-run Sigstore instance for internal artefacts, and one verification policy engine for both.
Worked scenario
A software company ships a CLI for Linux, macOS and Windows, a container image, and a firmware update for an appliance installed in customer data centres, some of them air-gapped. Today one OpenPGP key in a CI secret signs everything.
- Split by verifier. Windows binaries keep Authenticode with a key in a cloud HSM behind a signing service; the firmware gets its own HSM-held key and ceremony; the container image and Linux tarballs move to keyless signing from the release workflow.
- Narrow the identity. Only the release workflow on protected
v*tags, in an environment with required reviewers, holdsid-token: write. Build and test workflows cannot sign. - Publish the policy. Release notes give the identity, issuer and repository ID to pin.
- Serve air-gapped customers. Each release ships its bundle plus a current trusted-root file; the appliance firmware remains verifiable against the firmware key alone.
- Retire the old key. Dual-sign for two releases, announce the end date, then revoke the OpenPGP key and remove it from the CI secret store.
- Watch. A log monitor alerts on any entry for the release identity outside a release window; the signing service alerts on any request without a matching change ticket.
Migrating from keys to keyless
- Inventory: list every signing key, where it lives, what it signs and which consumers pin it. Treat each as a privileged machine identity.
- Harden CI first: read-only default tokens, no secrets on untrusted triggers, pinned actions, protected release environments. See the CI/CD Pipeline Identity Security Guide.
- Dual-sign: add keyless signatures alongside existing ones; verifiers run the new policy in audit mode and log results.
- Enforce: switch admission and install checks to fail closed on the keyless policy for one product, then expand.
- Retire keys: announce, revoke, delete the secret, and keep the old public key available for verifying historic releases.
- Rollback: keep the key-based signing path documented and the HSM key available (not deleted) until two release cycles pass without verification failures.
Detection and measurement
- Log entries for your identities: every keyless signing event for a release identity, matched to a release record; anything unmatched is an incident.
- Signing service and KMS logs: requester, digest, approval reference and time for every key-based signature.
- Verifier results and trust material: verification failures, "skip verification" exceptions, and edits to trust roots, keyrings and trust stores.
| Metric | Target |
|---|---|
| Production artefacts verified with a pinned identity or per-product key at deploy | 100 percent |
| Signing keys held outside HSM or KMS | Zero |
| Log entries for release identities without a matching release | Zero, each investigated |
| Age of trusted root in offline environments | Refreshed at every import |
| Workflows able to obtain a signing identity | Release workflows only |
| Time to rotate a signing key in a rehearsal | Measured and falling |
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev 5 | SI-7(15) Code Authentication; CM-14 Signed Components; SR-4 Provenance; SR-11 Component Authenticity; SA-10 Developer Configuration Management | Requiring and verifying signatures and provenance before installation |
| NIST CSF 2.0 | ID.RA-09; GV.SC-07 | Assessing software authenticity and integrity before use; supplier risk |
| SLSA v1.1 | Build L2 signed provenance; Build L3 signing secret inaccessible to user-defined steps | Where signing keys or identities may live in the build |
| MITRE ATT&CK v20 | T1553.002 Code Signing; T1588.003 Code Signing Certificates; T1195.002 Compromise Software Supply Chain; mitigation M1045 Code Signing | The attacker behaviours each model must resist |
See the SLSA and Build Provenance Guide for the provenance side.
Common failure patterns
- Verifying "is it signed?" instead of "is it signed by this identity?" Both models collapse to nothing when the verifier accepts any valid signature.
- Leaving the stored key in place after moving to keyless. Attackers use the weaker path; retire the key once keyless is enforced.
- Air-gapped sites with stale trust material. Trust roots imported once and never refreshed cannot reflect revocations or rotations.
- No rotation plan for consumers. Teams avoid rotating a compromised key because they cannot reach the systems that pin it.
Practitioner checklist
- Inventory every signing key and every identity allowed to sign, per artefact.
- Pin exact identity and issuer (keyless) or a per-product key (key-based) in every verifier.
- Restrict signing identities to release workflows on protected refs with required reviewers.
- Keep all private keys non-exportable in an HSM or KMS behind a logged signing service.
- Timestamp every key-based signature with a trusted TSA.
- Monitor the transparency log for your identities and reconcile against releases.
- Ship bundles and current trust roots to offline consumers; refresh at each import.
- Dual-sign during migration, then revoke and remove the old key.
- Rehearse key rotation and trust root rotation once a year.
Standards and references
- Sigstore: Threat Model
- Sigstore: Verifying Signatures
- Sigstore: Configuring Cosign with Custom Components
- SLSA: Security Levels v1.1
- GitHub Docs: Verifying attestations offline
- PyPI Docs: Attestations
- Notary Project: Timestamping
- PyPI Blog: Removing PGP from PyPI
Related NHI Mgmt Group resources: Sigstore Guide, Code Signing Security Guide, Compromised Code Signing Certificate Playbook, Model Signing vs Model Scanning, Software Supply Chain Security Guide.