pub struct TrustStore { /* private fields */ }Expand description
The keys a host trusts, per provider — the local answer to “who may sign evidence I will treat as attested?” (ADR 0016).
Serde-able and persistable, mirroring
ConsentStore, because it is the same kind
of object: a record of a decision one person made about one provider on one
machine. Nothing populates it implicitly — there is no discovery, no
fetching, and no trust-on-first-use. An empty store is a host that verifies
nothing and loses nothing, which is the default posture.
Implementations§
Source§impl TrustStore
impl TrustStore
Sourcepub fn new() -> Self
pub fn new() -> Self
An empty store: no provider has a trusted key, so every attestation
resolves to AttestationState::NoTrustedKey and every frame is still
served (F9).
Sourcepub fn trust(&mut self, provider_id: impl Into<String>, key: TrustedKey)
pub fn trust(&mut self, provider_id: impl Into<String>, key: TrustedKey)
Trust key for provider_id, replacing any key already held under the
same key_id.
Sourcepub fn revoke(&mut self, provider_id: &str, key_id: &str) -> bool
pub fn revoke(&mut self, provider_id: &str, key_id: &str) -> bool
Stop trusting one key. Returns whether a key was actually removed.
This is the whole of revocation, and it is local: nothing here learns that a key was compromised, so a host that is told so out of band calls this, and a host that is never told keeps trusting it (ADR 0016).
Sourcepub fn key(&self, provider_id: &str, key_id: &str) -> Option<&TrustedKey>
pub fn key(&self, provider_id: &str, key_id: &str) -> Option<&TrustedKey>
The key held for (provider_id, key_id), if any.
Sourcepub fn keys_for(&self, provider_id: &str) -> impl Iterator<Item = &TrustedKey>
pub fn keys_for(&self, provider_id: &str) -> impl Iterator<Item = &TrustedKey>
Every key trusted for one provider, in key_id order.
Sourcepub fn check(
&self,
provider_id: &str,
frame: &ContextFrame,
attestation: &ProvenanceAttestation,
) -> AttestationState
pub fn check( &self, provider_id: &str, frame: &ContextFrame, attestation: &ProvenanceAttestation, ) -> AttestationState
Check one attestation against this store and report what was found
(SPEC.md §6.5.4).
Total: every input produces a state, and none of them is an error a caller could mistake for a reason to drop the frame (F9). The cheap structural checks run first so a hostile attestation cannot buy more than a constant amount of work before it is dismissed.
Sourcepub fn check_signed_as(
&self,
local_id: &str,
signing_id: &str,
frame: &ContextFrame,
attestation: &ProvenanceAttestation,
) -> AttestationState
pub fn check_signed_as( &self, local_id: &str, signing_id: &str, frame: &ContextFrame, attestation: &ProvenanceAttestation, ) -> AttestationState
check with the trust-lookup id and the signing id told
apart. See check_result_signed_as for
why a host needs both: local_id decides whose key may sign this, and
signing_id — the handshake-declared provider.name — decides what
bytes were signed (SPEC.md §6.5.2).
Sourcepub fn check_result(
&self,
provider_id: &str,
result: &ContextQueryResult,
) -> Vec<FrameAttestationOutcome>
pub fn check_result( &self, provider_id: &str, result: &ContextQueryResult, ) -> Vec<FrameAttestationOutcome>
Check the evidence a provider attached to one query result, and return
one outcome per frame in it — including the frames no entry covered,
which are AttestationState::Unattested.
The evidence is read off result itself
(frame_attestations and
result_attestation), not
passed alongside it. That is deliberate and it is the whole of #161: an
attestation has exactly one home (SPEC.md §6.5.5, ADR 0014), so a
caller cannot hand this method a set of signatures that disagrees with
the answer they cover, and no tie-breaking rule is needed because there
is never a tie.
The result is a total account of the frames: a caller can read a state for every frame it is about to compose, and never has to guess whether an absent entry means unsigned or unchecked.
At most one entry is checked per frame, and at most
result.frames.len() entries are examined at all. A conforming provider
sends no more than one entry per frame, so the cap binds only a provider
that already over-sent — and the consequence falls on that provider
alone: its own later entries read as absent, and its frames are still
served.
Sourcepub fn check_result_signed_as(
&self,
local_id: &str,
signing_id: &str,
result: &ContextQueryResult,
) -> Vec<FrameAttestationOutcome>
pub fn check_result_signed_as( &self, local_id: &str, signing_id: &str, result: &ContextQueryResult, ) -> Vec<FrameAttestationOutcome>
check_result with the two provider identities
told apart: local_id is the host’s own key for this provider, and
signing_id is the name the provider signs under.
§Why there are two
SPEC.md §6.5.2 puts the provider id inside the signed preimage, and is
explicit about which id: the handshake-declared provider.name, because
a host’s local id “is not a string the provider ever sees — so it is not
one a provider could sign against”. A host that recomputes the commitment
with its own local id gets a different preimage and therefore a different
digest, and reports AttestationVerdict::CommitmentMismatch — the
verdict §6.5.4 reserves for a frame that changed after signing. An
operator who merely named the provider something else in their config
would be handed a tampering incident over honest evidence.
The two ids cannot be collapsed in the other direction either. Trust is
keyed on local_id because that is the id the operator chose, in the
same act as the consent grant; keying it on the declared name would let a
provider claim another’s trusted key by declaring its name, which is the
substitution the identity binding exists to prevent. So the local id
answers “whose key may sign this?” and the declared name answers “what
bytes were signed?” — different questions with different right answers.
§The same split decides matching, not only verifying
A provider has no notion of this host’s local routing id, so every
FrameAttestation::frame it puts on the wire is built from the same
name it signs under (the reference provider builds both from its one
attestation_provider_id()). Matching offered against
frame.identity(local_id) would therefore miss every entry whenever an
operator’s config id differs from the provider’s declared name — the
same false negative this split exists to close, one layer above the
signature check itself. The lookup key is frame.identity(signing_id)
for that reason.
The returned FrameIds are keyed by local_id, not
signing_id: FrameId::provider_id is documented as “the same
routing/consent key the host registered it under”, and it is the id
the rest of the composition path (compose.rs, usage_report) already
builds its own FrameIds from. Echoing signing_id here instead would
desynchronize this ledger from every other identity the host emits for
the same frame.
Trait Implementations§
Source§impl Clone for TrustStore
impl Clone for TrustStore
Source§fn clone(&self) -> TrustStore
fn clone(&self) -> TrustStore
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more