Expand description
Issue #257: a cache for RFC-ACDP-0014 §7/§8 revocation discovery.
This is deliberately two objects sharing one lock, not one:
-
Facts — verified
KeyRevocations. RFC-ACDP-0014 §7:114 licenses caching these indefinitely (“the statement is permanent”). They are always unioned into classification bycrate::verified::VerifiedContext’s internalverify_retrieved— never used to replace or subset a discovery result. A revocation is monotone (more revocations ⇒ an earlier effective boundary ⇒ strictly more fail-closed verdicts), so seeding from the fact store can only tighten a verdict, never loosen one. That makes the fact store an anti-rollback security control: without it, a registry that serves a revocation on one call and hides it on the next causes the client to forget it ever saw it. SeeRevocationCache::facts_for(read) andRevocationCache::record_success(write) — both crate-private.Every stored fact carries the vantage that minted it (its
origin, set fromrecord_success’sauthorityparameter — seeStoredFact). RFC-ACDP-0014 §6 draws a real distinction here: a producer-signed fact is self-contained and “verifies identically wherever it came from” (§8), so it is never filtered by origin — this is what makes it correct for it to cross vantages (seecrate::verified’s anti-rollback tests). A registry-attested fact is one specific registry’s claim, and §6 licenses applying it only “for contexts served by or receipted by that same registry” — so a read filters registry-attested facts toorigin == current vantage. Storing onlytrust_class(as an earlier revision of this cache did) answers a different question than “was this claim made by the registry now serving this context” — collapsing the two let a fact minted at a hostile or deceived registry A fail-close a producer’s contexts at every OTHER authority, for the cache’s lifetime: a targeted cross-registry DoS exactly bounded by §6’s scoping default. -
Freshness markers — “vantage V was asked about producer/controller P for trust class C, and a full, untruncated discovery completed at time T.” This is a cached absence, which §7:114 does not license and which §8 explicitly warns about: “a malicious registry can hide a revocation … absence of search results is not evidence of absence.” So a marker is TTL-bounded (
crate::verified::RevocationDiscovery::freshness, defaultstd::time::Duration::ZERO— i.e. off), per-vantage, and read viaRevocationCache::marker_fresh(crate-private) — the only method in this module that can cause a lookup to be skipped rather than merely supplemented.
Both objects live in the same per-producer CacheEntry (crate-private)
so eviction is whole-entry: facts and markers for one producer always leave together,
which makes “a fresh marker over an evicted (or fact-capped) set” —
stale-absence-over-nothing — structurally unrepresentable rather than
merely guarded against.
Never call anything here holding the lock across an .await — every
method in this module is synchronous and returns before its caller does
any network I/O.
Structs§
- Revocation
Cache - A cache of RFC-ACDP-0014 revocation facts and discovery-freshness
markers, injectable into a
crate::RegistryClientviacrate::RegistryClient::with_revocation_cache.