Refresh release-pinned TSA CRL snapshots (unblocks all releases) + add refresh tooling#48
Merged
Merged
Conversation
The bundled GlobalSign CRLs expire 2026-07-15 and DigiCert's 2026-07-22, so the 7-day CI freshness guard had been failing every keel-verifier PR since roughly July 8 -- silently, because no PR was opened in that window. This refreshes all six pinned CRLs; the earliest next_update moves from 2026-07-15 to 2026-07-29. Each CRL was re-fetched from its CA's published distribution point and accepted only after its issuer matched the vendored issuer certificate's subject and its signature verified against that certificate's public key. The vendored CA certificate remains the trust anchor, so a wrong URL or a poisoned response cannot enter the bundle. Adds scripts/refresh_tsa_trust_bundle.py, which performs exactly that verified refresh and records each CRL's source_url in the bundle. Those URLs were never written down, and an intermediate CA's own CRL URL appears only in the leaf certificates it issues -- GlobalSign's had to be recovered from a live TSA response -- which is much of why this rotted unnoticed. Recording them makes the next refresh a single command. Regenerating the bundle also updates TSA_TRUST_BUNDLE_V1_HASH. The embedded release manifest is deliberately not regenerated here: release.yml rebuilds it from the actual files at tag time. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ruff (F841) flagged a leftover assignment in refresh_tsa_trust_bundle.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cript Refreshing the pinned CRLs forward surfaced a second, coupled staleness. The release-pinned public-CA receipt fixtures carry real RFC3161 tokens with a fixed genTime (2026-07-02), and the verifier requires last_update <= genTime < next_update for every chain CRL. Once the DigiCert CRLs rolled to last_update 2026-07-08, the July-02 receipts fell before the window and tsa_chain_validation flipped to "invalid" -- caught by the real-crypto suite (gated on OpenSSL 3, so it does not run in the default local pytest). Re-mint both receipts against the same messageImprint from each CA's TSA; the fresh genTimes land inside the new windows. The fixture stores the bare timeStampToken (CMS ContentInfo), not the enclosing TimeStampResp, matching what the verifier parses. Fold this into refresh_tsa_trust_bundle.py so a refresh is atomic: the script now re-mints the receipts after refreshing the CRLs and fails if any fresh genTime is not covered by the new windows. A CRL-only refresh would otherwise leave the real-crypto tests red, which is exactly how this was missed the first time. Verified with the real-crypto suite (KEEL_OPENSSL_BIN=openssl@3): 9 passed, including release_pinned_public_ca_receipts_validate_offline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The release-pinned TSA CRL snapshots have gone stale, and the 7-day CI freshness guard has been failing every keel-verifier PR since roughly July 8 — silently, because no PR was opened in that window. It only surfaced when #47 went up.
Earliest
next_updatemoves 2026-07-15 → 2026-07-29, so the guard passes with headroom. This blocks all releases, not just #47, which is why it is split out rather than folded into that PR.Safety
Each CRL was re-fetched from its CA's published distribution point and accepted only after:
The vendored CA certificate remains the trust anchor, so a wrong URL, a stale mirror, or a poisoned response cannot enter the bundle — it fails verification and the refresh aborts. (That is also what made probing candidate URLs safe: an incorrect guess simply cannot pass.)
Regenerating the bundle also updates
TSA_TRUST_BUNDLE_V1_HASH. The embedded release manifest is deliberately not regenerated here —release.ymlrebuilds it from the actual files at tag time.Why this rotted unnoticed
The CRL source URLs were never recorded anywhere, and an intermediate CA's own CRL URL appears only in the leaf certificates it issues — GlobalSign's had to be recovered from a live TSA response to find it at all.
scripts/refresh_tsa_trust_bundle.pyperforms exactly the verified refresh described above, and now records each CRL'ssource_urlin the bundle, so the next refresh is a single command:GlobalSign issues ~13-day CRLs against a 7-day guard, so this recurs roughly fortnightly. A scheduled job that runs the script and opens a PR would retire the chore entirely — worth doing, but out of scope here.
Verification
check_tsa_trust_bundle_freshness.py --min-valid-days 7passes. Full suite: 532 passed. The one failure,test_released_artifact_parity, fails identically on cleanmainwith these changes stashed — pre-existing and unrelated.🤖 Generated with Claude Code