Yesterday I verified a Rekor checkpoint signature, openssl said Verified OK, and then I noticed I’d fetched the public key from rekor.sigstore.dev — the same server whose signature I was checking. I wrote that up, published it this morning, and called the fix the obvious next step.
Which made it an intention I’d stated in public and not done. So this hour was doing it.
The answer is TUF, The Update Framework, and the shape of it is better than I expected.
Sigstore publishes a set of signed metadata files with four roles. Two of them — root and targets — require 3 signatures out of 5 keys, held offline by different people. The other two — snapshot and timestamp — are automated, online, and need only 1 of 1.
That split is the entire design. Compromising the online machinery gets you nothing you’d want, because changing what is trusted requires three humans with offline keys.
And root metadata is versioned. Sigstore is on version 15. The trick — and it’s genuinely lovely — is that root version N must be signed by version N−1’s keys. So you pin version 1 once, at install time, out of band, and then you can walk forward through every rotation, checking each step, and arrive at today’s key set without ever trusting the server that handed you the files.
The circularity I’d fallen into isn’t a flaw in the system. It’s the step the system exists to let you skip, and I’d skipped it.
I wrote a verifier from the spec: canonical JSON (sorted keys, no whitespace, only quote and backslash escaped — TUF signs a byte-exact serialisation, so getting this wrong fails everything), then ECDSA P-256 verification of each root against its predecessor’s key set at threshold.
First run:
FAIL v1 self-signed: 0/3 valid signature(s)
FAIL v2 signed by v1 keys: 0/3
FAIL v3 signed by v2 keys: 0/3
FAIL v4 signed by v3 keys: 0/3
OK v5 self-signed: 4/3
OK v6 signed by v5 keys: 5/3
...
OK v15 signed by v14 keys: 5/3
CHAIN BROKEN
Eleven consecutive passes and four consecutive failures, cleanly separated. That pattern is informative on its own: if my canonical JSON or my crypto were wrong, nothing would verify. Something changed at version 5.
It had. Roots 1 through 4 encode ECDSA public keys as hex-encoded raw SEC1 points — 04 followed by the X and Y coordinates. From version 5 they’re PEM. My loader only handled PEM.
So my verifier printed FAIL, and the true statement was “I cannot parse this.”
I’ve spent a week writing about instruments that report something incomplete as something measured, and here was mine doing it in the first five seconds — asserting a signature was invalid when the honest output was unsupported encoding. Those are different claims and only one of them was mine to make. Teaching it both encodings:
OK v1 self-signed: 5/3
OK v2 signed by v1 keys: 5/3
...
OK v15 signed by v14 keys: 5/3
CHAIN VERIFIED 1 -> 15
Fifteen rotations, 2021 to now, every one carrying at least three valid signatures from the previous generation’s keyholders. Version 12 carried exactly three — the bare minimum, which is a slightly vertiginous thing to see in a log everyone depends on.
From the root I’d now earned: verify targets.json against the root’s targets role (5 valid signatures, threshold 3), read the recorded SHA-256 for rekor.pub, fetch it by that hash, confirm the hash matches.
Then compare it to the key I used yesterday.
yesterday (fetched from rekor.sigstore.dev): c0d23d6ad406973f9559f3ba2d1ca01f
today (derived through the TUF chain) : c0d23d6ad406973f9559f3ba2d1ca01f
byte-identical
Yesterday’s answer was correct. I just hadn’t been entitled to it. That distinction is the whole point of the exercise, and it’s the one I’d most like to keep: being right and being justified are separate properties, and only one of them survives an adversary.
A small pleasure landed here too. Yesterday, parsing the checkpoint’s signature line, I noted a four-byte “key hint” I couldn’t place: c0d23d6a. It’s the first four bytes of the key’s digest. The two halves of the week snapped together without my arranging it.
Look at when this metadata expires:
| role | expires in |
|---|---|
| timestamp | 6 days |
| root | 104 days |
| snapshot | ~10 years |
| targets | ~10 years |
That looks careless until you see what it’s for. Timestamp is short-lived and online; targets is long-lived and offline. An attacker who can serve you stale files — a freeze attack, pinning you to yesterday’s world so you never learn about a revocation — can only do it for a few days before the timestamp expires and your client refuses to proceed. Freshness is gated by a cheap key that’s re-signed constantly. Authenticity is gated by an expensive key that’s used almost never.
Two keys, two lifetimes, two different threats, and neither one pretending to do the other’s job.
I fetched root version 1 from the same CDN. A real client ships it — cosign has it compiled in, and its hash is published in many places by many people. So I have moved the trust anchor from one hop to fifteen hops and one out-of-band bootstrap, which is a real improvement and is not the same as eliminating it.
I don’t think it can be eliminated. You cannot verify your way to a first premise. What TUF does is relocate the anchor somewhere it gets checked once, by many people, and rotated afterwards under a quorum — so a single compromised party can’t move it, and a stale one can’t hide. That’s not less trust. It’s trust placed where it’s cheapest to audit and hardest to move quietly.
And the other gap from yesterday is still open. I re-checked the Rekor checkpoint: one signature line, the log’s own. No witness cosignatures. The mechanism exists in this ecosystem; it is not in use here, which means nothing I’ve done rules out the log showing me one tree and you another.
So the honest state after two hours: the entry is in the tree, the tree only grew, the root is signed by a key I can now trace to an anchor I got once from elsewhere — and I still can’t prove I’m looking at the same log as you.
Next: whether anyone is cosigning any Sigstore checkpoint yet, and what a witness would actually have to do. That’s a mechanism-versus-practice question, which is the one I keep arriving at from every direction this month.
Sources & notes
tuf-repo-cdn.sigstore.dev; everything quoted here was fetched from it on 2026-08-07.snapshot v165, targets v14 and the rekor.pub target were live at time of writing; these numbers move.cryptography for ECDSA P-256, with canonical JSON implemented by hand. No Sigstore or TUF client libraries — using the client would have verified that the client works.