Skip to main content

RevocationDiscovery

Struct RevocationDiscovery 

Source
#[non_exhaustive]
pub struct RevocationDiscovery { pub include_registry_attested: bool, pub on_failure: DiscoveryFailurePolicy, pub total_timeout: Duration, }
Expand description

RFC-ACDP-0014 §8 revocation auto-discovery configuration.

When set on RevocationPolicy::discover, this instructs verification to look up revocations itself — via find_revocations and, when Self::include_registry_attested is true, additionally find_registry_attested_revocations — instead of relying solely on RevocationPolicy::known.

§Cost

Discovery is expensive, and every request it issues is serial. MAX_SEARCH_PAGES = 10 (crate::revocation) bounds search round-trips per (type_form, status) pair, and there are 6 such pairs (2 type forms × 3 statuses) — so up to 60 search requests, each of which can name up to 100 per-candidate context retrieves (GET /contexts/{id}, capped at 1 MB apiece), for up to 6,000 + 60 + 100 = 6,160 requests / ~6.1 GB in the worst case for one of the two discovery functions. The retrieve fan-out is not bounded by MAX_LINEAGE_WALKS = 100 — that cap is only checked after the retrieves have already gone out. With Self::include_registry_attested set, both functions run: ≈12,321 requests / ~12.2 GB worst case for the pair (the extra 1 is the unconditional client.capabilities() fetch find_registry_attested_revocations makes).

Self::total_timeout is an availability bound, not a bytes or memory bound: it stops verification from hanging forever against a slow or hostile registry, but a hostile registry on a fast link can still serve gigabytes of legitimate-looking traffic inside the window — the 1 MB cap applies per request, not in aggregate, and verified revocations accumulate in a Vec for the call’s duration. There is no request-count or byte budget in this version, and no cache: every call re-discovers from scratch. A caller verifying many contexts against the same producer should discover once itself and pass the results via RevocationPolicy::known instead of setting discover on every call — the same hoisting guidance crate::revocation’s find_registry_attested_revocations doc already gives callers of that function directly (see its “Cost note for callers verifying many contexts”).

§Reentrancy

Discovery calls back into verification, and that reentrancy has two consequences worth stating explicitly rather than leaving implicit:

  1. Each candidate body find_revocations turns up is verified via Verifier::new(resolver).verify_body — not verify_retrieved — so discovered revocation bodies are themselves checked without revocation checking of their own. That is defensible under RFC-ACDP-0014 §5 step 1’s “currently authorized key,” but it is an assumption this type is making on the caller’s behalf, not an accident.
  2. Verifying a key-revocation context now also triggers discovery against the same producer, so the revocation-fetch path itself becomes fragile under DiscoveryFailurePolicy::FailClosed: a producer whose revocation search is briefly unreachable can no longer be verified as revoked, either.

§No Default

This type deliberately has no Default impl — construct it via Self::producer_signed_only or Self::all_trust_classes. RFC-ACDP-0014 §6’s “lost-everything” fallback means a producer that has lost every key it could sign a revocation with can only be revoked registry-attested — so the catastrophic case is exactly the one a silently-defaulted-off trust class would skip. A quiet Default::default() that leaves include_registry_attested: false would make that skip invisible at every call site; forcing a named constructor puts the choice at the type level instead, where a reviewer (and git grep) can see it. Callers protecting against key loss, or otherwise unwilling to assume a producer always retains signing capacity, MUST use Self::all_trust_classes.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§include_registry_attested: bool

Whether to also run find_registry_attested_revocations (the §6 registry-attested trust class), in addition to the producer-signed search every discovery configuration runs. Requires the registry to serve /.well-known/acdp.json; under DiscoveryFailurePolicy::FailClosed a registry that serves no capabilities document fails verification when this is true.

§on_failure: DiscoveryFailurePolicy

What to do when discovery itself fails (a transient transport error from either search, or the search-safety-cap error AcdpError::SearchTruncated). Default DiscoveryFailurePolicy::FailClosed.

SearchTruncated and a transport error (e.g. a 503) are NOT equivalent, even though both take this same on_failure path. SearchTruncated means “this producer has more revocations than we will page through” (MAX_SEARCH_PAGES) — an attacker-inducible security downgrade, since a hostile producer or registry can pad the result set specifically to exhaust the page cap and hide a real revocation from discovery. A 503 is an ordinary availability blip. Under DiscoveryFailurePolicy::ProceedWithKnown both are treated the same way (proceed on RevocationPolicy::known alone, record the failure) — choose ProceedWithKnown knowing it also waives truncation, not just transient unavailability.

§total_timeout: Duration

Wall-clock budget for the whole discovery step (both searches, if Self::include_registry_attested is set). An availability bound only — see the type-level cost section above. Matches this crate’s existing ResolverOptions::total_timeout precedent (crate::cross_registry), defaulting to the same 30 s rather than exceeding it on the core verify path.

Enforced via tokio::time::timeout, which requires the executing Tokio runtime to have its time driver enabled (#[tokio::main] and #[tokio::test] enable it by default; a hand-built Builder::new_current_thread() runtime does not unless .enable_time() or .enable_all() is called). Calling verify_retrieved with discover: Some(..) from a runtime without the time driver panics — it does not return Err — the same requirement ResolverOptions::total_timeout (crate::cross_registry) already carries on its opt-in walk, but here it sits on the core verify path whenever discovery is configured, not just on an explicit cross-registry walk.

Implementations§

Source§

impl RevocationDiscovery

Source

pub fn producer_signed_only() -> Self

Discover producer-signed revocations only (include_registry_attested: false). Cheapest of the two constructors, and the default choice for callers that are not specifically defending against a producer that has lost every signing key — see the “No Default” section above for who must NOT stop here.

Source

pub fn all_trust_classes() -> Self

Discover both trust classes: producer-signed AND registry-attested (include_registry_attested: true). Required to catch RFC-ACDP-0014 §6’s “lost-everything” fallback, where a producer with no signing key left can only be revoked registry-attested. Requires the registry to serve a capabilities document.

Trait Implementations§

Source§

impl Clone for RevocationDiscovery

Source§

fn clone(&self) -> RevocationDiscovery

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for RevocationDiscovery

Source§

impl Debug for RevocationDiscovery

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for RevocationDiscovery

Source§

impl PartialEq for RevocationDiscovery

Source§

fn eq(&self, other: &RevocationDiscovery) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for RevocationDiscovery

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more