Rekor transparency log

Durable, tamper-evident proof of signing.

Expert30 min · lesson 9 of 15

At 03:12 UTC someone briefly hijacks the identity your release workflow signs with, signs a backdoored image, and pushes it. That identity comes from OIDC, short for OpenID Connect: the login standard your build system uses to prove who it is without holding a password. The certificate behind it comes from Fulcio, Sigstore's certificate authority. Ten minutes later that certificate has expired and its private key is gone. By the ordinary rules of PKI (public key infrastructure, the machinery of certificates and keys that vouches for who signed what) there is nothing left to point at. The signature survives anyway. It sits in a public ledger nobody can edit or reorder, the way a clerk stamps a filing into a bound book. Your on-call can look it up, prove it happened, and read off the exact minute it was made. That same ledger answers a much duller question you will hit far more often: how do you verify a six-month-old release when the certificate that signed it lived for ten minutes, half a year ago? The ledger is Rekor, Sigstore's transparency log, and it is what makes keyless signing checkable years later and auditable by anyone who cares to look.

Proving a dead certificate was once alive

Here is the problem stated plainly. Fulcio hands out certificates that are valid for roughly ten minutes. That short life is the entire point of keyless signing: there is no durable private key sitting anywhere to be stolen, backed up, or rotated. It also breaks the obvious way of checking a signature. Six months on, the signing certificate is expired, and an expired certificate normally means reject. A transparency log settles this by witnessing the signature at the moment it is made. A transparency log is an append-only record: entries can be added, never changed or removed, and every addition is provable with hashes. It behaves like a postmark. The stamp on an envelope does not care whether the post office is open today, it records that the letter went through on that date. When Rekor accepts an entry it returns a Signed Entry Timestamp (SET), a countersignature made with Rekor's own key over the entry's contents and the time it was folded into the log. That SET is your trusted timestamp. Verification stops asking 'is this certificate valid right now?' and starts asking 'was it valid at the instant Rekor witnessed the signature?' The second question still has an answer in ten years.

find every signature made under an identity
$ rekor-cli search --email [email protected]
Found matching entries (listed by UUID):
24296fb24b8ad77a3f6c1b0e9d2a5c8f4e7b1a06d3c9f52e8b7a41d0c6e35f9a2b8d4c1e70f63a95b
24296fb24b8ad77a9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1
# rekor-cli talks to the public-good log at rekor.sigstore.dev by default.
# Each UUID = <16-hex tree/shard id><64-hex entry hash>, one line per signature.

Notice what you searched by. Not the artifact, the identity that signed it. Searching by email address (or by --sha for a digest) hands back every entry ever recorded under that identity. That list is the raw material for two different jobs. Audit: show me everything my release identity has ever signed. Detection: is there anything on this list I cannot account for? Each UUID (universally unique identifier, the address of one entry) that comes back glues a shard and tree id onto the front of the entry's own hash. That combination is how you point at a single leaf of the log in the next step.

Reading an entry: digest, signature, and the Fulcio cert

inspect one recorded entry
$ rekor-cli get --uuid 24296fb24b8ad77a3f6c1b0e9d2a5c8f4e7b1a06d3c9f52e8b7a41d0c6e35f9a2b8d4c1e70f63a95b
LogID: c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d
Index: 148216387
IntegratedTime: 2026-05-14T09:41:22Z
UUID: 24296fb24b8ad77a3f6c1b0e9d2a5c8f...
Body: {
"HashedRekordObj": {
"data": {
"hash": { "algorithm": "sha256", "value": "9f2c8b0e...c1a4" } # the image digest
},
"signature": {
"content": "MEUCIQDb3f...gk=", # the raw signature
"publicKey": {
"content": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t..." # base64 Fulcio cert
}
}
}
}

A cosign image signature is stored as a hashedrekord, and three pieces of it matter. The artifact's SHA-256 digest (Secure Hash Algorithm, 256-bit: a fingerprint of the exact bytes) sits under data.hash. The raw signature sits under signature.content. The signer's Fulcio certificate, base64-encoded, sits under signature.publicKey.content. That certificate is the thread leading back to an identity. Decode the base64 and you get a PEM (Privacy-Enhanced Mail, the BEGIN CERTIFICATE text format certificates travel in) whose Subject Alternative Name field carries the workflow identity, usually a URI (uniform resource identifier, a web-style address) pointing at the release workflow's ref. Sigstore's OIDC-issuer extension, object identifier 1.3.6.1.4.1.57264.1.8, records which login provider authenticated it. One entry is therefore self-contained evidence: this digest, signed by this identity, witnessed at this time. None of that depends on the certificate still being valid today.

Signatures are not the only thing Rekor keeps. When cosign attaches an in-toto attestation, meaning a signed statement about an artifact such as build provenance, a test result, or the SBOM (software bill of materials) you will generate with Syft in the next lesson, it is recorded under the dsse entry type instead. DSSE stands for Dead Simple Signing Envelope, the wrapper Sigstore uses to carry signed statements. The envelope's payload hash and the signer's certificate land in the log the same way a signature does. Your evidence gets the same permanence and the same public audit trail your signatures get.

How you check an inclusion proof without trusting Rekor

The append-only promise is not something you take on faith. It is arithmetic you can redo yourself. Rekor is a Merkle tree, named after Ralph Merkle: every entry is a leaf, and pairs of hashes get hashed together over and over until one value at the top stands for the whole log. The leaf hash is SHA-256(0x00 ‖ entry_bytes). Every internal node is SHA-256(0x01 ‖ left ‖ right). Those leading bytes come from RFC 6962, the Certificate Transparency standard Rekor borrows its hashing rules from, and they keep a leaf hash from ever being mistaken for a node hash. Fold the whole thing up and you get a single root hash that commits to every entry in the log. Change one byte anywhere and the root changes. An inclusion proof hands you the audit path: the short list of sibling hashes you need to rebuild that root starting from your own leaf, and nothing more. You hash your entry into a leaf, fold in each sibling from the proof on the correct side (left or right is fixed by your leaf's index and the current tree size, so there is no guessing), and climb until you are holding a candidate root.

the audit path and signed tree head
$ curl -s https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77a3f6c1b0e9d2a5c8f4e7b1a06d3c9f52e8b7a41d0c6e35f9a2b8d4c1e70f63a95b \
| jq '.[].verification.inclusionProof' # the entries endpoint returns a { uuid: entry } map, so jq '.[]' picks the one entry
{
"checkpoint": "rekor.sigstore.dev - 1193050959916656506\n148902551\nb0Xq9m4...root...=\n\n— rekor.sigstore.dev wNI9ajBEAiAe...sig...==\n",
"hashes": [
"5e6f8a1c...", # sibling at level 0 ┐
"7a8b0c2d...", # sibling at level 1 ├─ audit path
"9c0d2e4f...", # sibling at level 2 │
"1f3a5b7d..." # sibling at level 3 ┘
],
"logIndex": 148216387,
"rootHash": "a1b2c3d4...e5f6",
"treeSize": 148902551
}
# rekor-cli's own --format json prints a getCmdOutput without the proof;
# use the API above (or `rekor-cli get --format tle`) to get inclusionProof + SET.

Now hold that candidate root up against the checkpoint. A checkpoint is Rekor's Signed Tree Head: a short signed note saying that at this moment the tree held this many entries and its root hash was this, signed with Rekor's log key. Two things have to be true at once. Your recomputed root has to equal the checkpoint's rootHash, and the checkpoint's signature has to verify against Rekor's public key. That public key does not come from a live call to Rekor's API (application programming interface, the web endpoints the log answers on). cosign takes it from the Sigstore TUF root (The Update Framework, a signed bundle of trust anchors that cosign ships with and keeps refreshed), so trust is anchored in TUF rather than in whatever a host happens to reply with. Recomputing the root yourself is the whole exercise. You never accept Rekor's 'yes, it is in there,' you check it against a key you already hold. Consistency proofs are a separate trick with a separate job: they show that a tree of N entries is a straight append-only extension of an earlier tree of M entries, which is how a monitor confirms the log never rewrote or forked its own history.

Verifying a Rekor inclusion proof yourself
1leaf hash
SHA-256(0x00 ‖ entry_bytes)
2fold in audit path
combine with each proof sibling
3climb to candidate root
SHA-256(0x01 ‖ left ‖ right)
4match checkpoint root
recomputed == signed rootHash
5verify checkpoint sig
Rekor key from TUF, not the API
You rebuild the root from the leaf and trust only Rekor's public key plus the hashing, never the server's claim that the entry is there.

Rekor as a tripwire, if somebody is watching it

Because every keyless signature is public and bound to an identity, the log doubles as a place to catch an attacker. An entry signed by your release identity that your pipeline never produced is a loud alarm. The likeliest explanation is that your OIDC identity or your build got abused, which is the 03:12 scenario from the top of this lesson. Rekor detects. It does not prevent. And nobody audits a log by staring at it, so rekor-monitor does the watching for you, with two jobs. Consistency monitoring keeps verifying consistency proofs on a schedule, so a log that quietly forked or rewrote itself gets caught. Identity monitoring raises an alert whenever a new entry matches certificate identities you named up front. In production, keep the verification itself offline and cheap: cosign 2.x bundles the SET and the inclusion proof alongside the artifact, so you verify from the bundle instead of hammering the rate-limited public log on every deploy. Then run a monitor, so the tripwire is actually armed.

identity monitoring with rekor-monitor
# config.yaml — alert on any log entry matching our signing identities.
# certSubject and issuers are regular expressions.
monitoredValues:
certIdentities:
- certSubject: [email protected]
issuers:
- https://token.actions.githubusercontent.com
- certSubject: https://github.com/acme/api/.github/workflows/release.yml@refs/heads/main
issuers:
- https://token.actions.githubusercontent.com
$ rekor_monitor --config-file config.yaml --once
level=info msg="verified consistency proof: 148890003 -> 148902551 (append-only OK)"
level=warn msg="identity match" [email protected] \
uuid=24296fb24b8ad77a9f... index=148902488 integratedTime=2026-05-14T03:12:07Z
# 03:12 UTC entry your pipeline has no record of producing — investigate now.
Switching off tlog verification silently throws the guarantee away
cosign checks Rekor inclusion by default, and the first time CI (continuous integration) cannot reach the log, somebody 'fixes' it with --insecure-ignore-tlog=true. Tlog is short for transparency log, and that flag is not skipping a formality. It discards the trusted timestamp and the proof of durability, so a signature made with a certificate that expired long ago, backed by no log entry at all, now sails straight through. If you have to verify offline, verify from the cosign bundle, which carries the SET and the inclusion proof with it. Never by turning the check off. And hold on to the other limit: Rekor detects abuse, it never blocks it. An unmonitored log is a camera nobody looks at.
Quick check
01An inclusion proof exists so you can confirm an entry really is in the log without taking Rekor's word for it. What does a correct verifier actually do?
Correct — You rebuild the root yourself and trust only the log's public key and the hashing, never what the server asserts.
Incorrect — That is taking Rekor's word for it, which is the exact thing the proof was invented to avoid.
Incorrect — Freshness has nothing to do with it. Entries are meant to be checked long after they were made, which is the point of the log.
Incorrect — Fulcio certificates die in minutes. Verification deliberately does not depend on current validity, and that is precisely why the log exists.
02You run rekor-cli get on a cosign image-signature entry and it comes back as a hashedrekord. Which three things does that one entry actually hold?
Correct — The embedded Fulcio certificate is what ties the entry back to the workflow identity, which lets the entry stand on its own as evidence.
Incorrect — No. Rekor never stores artifact bytes, the private key is destroyed seconds after signing, and the OIDC token is not logged.
Incorrect — No. The inclusion proof comes from the REST API rather than the hashedrekord body, and Rekor's private key never appears in an entry.
Incorrect — No. An SBOM is recorded as a dsse attestation entry, not inside a hashedrekord signature entry.
03rekor-monitor --once prints 'verified consistency proof: 148890003 -> 148902551 (append-only OK)' and then warns 'identity match [email protected] ... integratedTime=2026-05-14T03:12:07Z' for an entry your pipeline has no record of making. What does that tell you, and what do you do about it?
Incorrect — No. The monitor said the consistency proof verified (append-only OK), so the entry is genuinely in the log.
Correct — This is the tripwire doing its job: a public entry bound to your identity that you cannot account for.
Incorrect — No. Rekor and its monitor only detect. They never block, remove, or prevent entries.
Incorrect — No. Expiry is beside the point. The entry is permanently timestamped, which is exactly what makes it alarming.

Try this

Work through “Rekor as a tripwire, if somebody is watching it” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: switching off tlog verification silently throws the guarantee away. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related