Skip to content

verification: add CRL revocation checker - #15012

Draft
tnytown wants to merge 2 commits into
pyca:mainfrom
trail-of-forks:tnytown/crl-impl
Draft

verification: add CRL revocation checker#15012
tnytown wants to merge 2 commits into
pyca:mainfrom
trail-of-forks:tnytown/crl-impl

Conversation

@tnytown

@tnytown tnytown commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Depends on C2SP/x509-limbo#577 and friends.

@tnytown
tnytown force-pushed the tnytown/crl-impl branch 10 times, most recently from be91500 to e44edab Compare June 18, 2026 15:38
@tnytown
tnytown force-pushed the tnytown/crl-impl branch from e44edab to 3536232 Compare June 18, 2026 15:40
@mangrisano

Copy link
Copy Markdown

Thanks for working on this — CRL support has been a long-standing gap and the implementation reads cleanly. I work on TLS revocation tooling, so the temporal/staleness behavior caught my eye, and since #10393 called out fail-open vs fail-closed as an open question I wanted to think it through here.

Staleness is currently strict fail-closed. In Policy::permits_crl, a CRL whose nextUpdate is in the past (expired) — or whose thisUpdate is in the future — produces a hard Err("CRL is not in effect at validation time") that propagates through is_revoked(...)? and fails the whole validation. Is that the intended long-term semantics, or a placeholder pending the #10393 discussion? It'd be worth stating explicitly in the docstring either way.

Worth separating two models, because they pull in different directions:

  • In the online revocation model (the one browsers implement), stale or unreachable revocation data is typically soft-failed — treated as "unknown" so a CA's lagging endpoint doesn't turn into a mass outage.
  • Here the CRLs are caller-supplied and offline, so fail-closed is far more defensible: an expired CRL is a property of the input the caller chose to pass, not a transient network condition.

So I'm not arguing the default is wrong — just that the distinction is real and the choice deserves to be documented.

One structural point for if/when a soft-fail mode is considered. In is_revoked, permits_crl runs before serials.contains(serial). A soft-fail that simply tolerates a stale CRL would, as currently ordered, also tolerate a certificate that CRL explicitly lists as revoked — staleness would end up masking a positive revocation. On pure security grounds a "revoked" determination should never be weakened by the freshness of the source: only the "looks unrevoked, but the CRL is stale" case is genuinely unknown. That suggests membership should be evaluated before (or independently of) the staleness gate, so a soft-fail can only ever downgrade the unknown case.

If useful, I'd be glad to add the matching x509-limbo temporal vectors — the key one is expired-CRL + valid-cert, whose expected result is exactly what the fail-open/closed choice determines (succeeds under fail-open, fails under fail-closed), plus expired-CRL + revoked-cert for documentation. Those would pin whichever semantics you land on.

Minor:

  • permits_crl returns the same "not in effect" text for both the thisUpdate-in-future and expired cases; distinct messages would aid debugging.
  • The nextUpdate MUST is RFC 5280 §5.1.2.5 specifically (comment says "5280 5").

@tnytown

tnytown commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@mangrisano Please don't communicate with me this way; I don't find it useful. I'm happy to take feedback related to your concrete use case, if any.

@mangrisano

mangrisano commented Aug 13, 2026

Copy link
Copy Markdown

Fair enough — I don't have a concrete blocker with your current design, just wanted to flag the fail-open perspective. That's it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants