TT Lab
Get started
Learn Learning paths Courses

The Build Was Green — So Who Put That Library In?

The signature is fine — but we revoked that key

Continue in TT Lab

In one line

All a signature guarantees is that "these bytes were signed with that key", and the other half — do we still trust that key — has to be managed separately, outside the signature algorithm.

Why this matters

The sentence "we signed the artifact" says less than it seems to. The guarantee comes not from where you attach the signature but from where verification fails. So the first thing to do when adopting a signing scheme is to write down every case that should fail and then make each one actually fail.

When you write the list, it is usually four lines: the artifact was changed, the signature is not from our key, it is our key but it has been revoked or has expired, and it is a key we do not know at all. The signature algorithm catches the first two by itself. It does not catch the last two. A raw key pair has no expiry, and the key itself does not contain the data needed to answer the question "is this key ours?"

How it works

A signature is applied not to the whole file but to the digest. You compute the hash first and then sign that value with the private key, so if the artifact differs by even one byte, the hash changes entirely and the same signature can no longer match. So tamper detection comes virtually for free.

The problem is the key. If we hold the key, who put it where, how we notice if it leaks, and what has to be re-signed when we rotate it all become our homework. This is why sigstore solves this homework in a different way. In the default flow, identity-based signing, an ephemeral key pair is created in memory, and a certificate authority verifies an OpenID Connect identity token and then issues a short-lived certificate that binds that identity to the public key. Once signing is done, the private key is destroyed right away and the certificate expires. The verifier makes its judgment not from the destroyed key but from a timestamped entry left in the transparency log (sigstore signing overview).

If you want to manage the keys yourself, cosign lets you do so through a KMS. You point to a provider such as AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault, or Kubernetes Secrets with a URI such as awskms://, gcpkms://, or k8s://, and extract the public key with cosign public-key --key <URI> to use for verification (cosign key management). On the verification side, the documentation says cosign verify checks not only the signature but also whether the signing certificate came from a trusted certificate authority (cosign verification).

Whichever path you take, the remaining principle is the same. "Is this signature valid?" and "Do we trust this key right now?" are different questions, and you must ask both. If you manage keys yourself, you need a keyring that records which key is trusted from when to when, and if you use sigstore, the certificate's lifetime and the transparency log take that place.

What it looks like in the field

The most expensive failure is verifying but swallowing the failure. This takes the form of a pipeline that does not check the exit code of the verification command, or one that only writes the error to the log and moves on to the next step. This state is worse than having no signature — because everyone believes they are protected.

The second shows up at key rotation. If you move to a new key without removing the old key from the keyring, artifacts signed with the revoked key keep passing. Conversely, if you simply delete the old key, every earlier release that has not yet been deployed gets blocked. That is why recording a validity period instead of deleting is the approach that survives in operations.

The third is an unclear signing subject. If what was signed is not contained in the signature file, the verifier has to guess from the file name that "this signature belongs to that file". In a directory with several releases, that guess soon turns out wrong. So in practice, rather than signing the artifact directly, teams sign a document that records the subject and its digest and distribute that document alongside it. The provenance in the next module is exactly that document.

The fourth is leaving the private key readable during the build step. If the build script can reach the key, any code running in that build can reach the key, and the moment one dependency is compromised the signature itself becomes meaningless. Designs that put the key behind a KMS or use ephemeral keys target this problem head-on.

The fifth is never verifying after creating a signature. A signing command usually succeeds silently, so even if you gave different hash algorithms or signed with the wrong key, nothing happens on the spot. The problem surfaces weeks later in the deployment pipeline, and on an urgent day at that. One line that runs verification right after signing, in the same place, eliminates this accident entirely.

What you will do in the next lab

You create a key pair, sign an artifact, and build the four cases where verification must fail: a tampered copy, a perfectly valid signature made with a revoked old key, a key not in the keyring, and the normal artifact. At the end you build a verifier that reads the keyring and checks the validity period as well, telling the four cases apart in one run.