dpp_vc/credential/trust.rs
1use std::collections::HashSet;
2
3use dpp_domain::Audience;
4
5/// Registry of issuer DIDs authorised to issue credentials for each audience.
6///
7/// For MVP single-tenant, this is a static allow-list loaded from operator
8/// configuration (see [`StaticTrustedIssuers`]). Use [`AllowAllIssuers`] only
9/// in tests or pre-configuration bootstrapping — never in production.
10pub trait TrustedIssuerRegistry: Send + Sync {
11 fn is_trusted_for_audience(&self, issuer_did: &str, audience: Audience) -> bool;
12}
13
14/// Configuration-driven allow-list implementation of [`TrustedIssuerRegistry`].
15///
16/// **Issuer trust is hierarchical; data visibility is not.** An issuer trusted
17/// to attest an authority is also trusted to attest a legitimate interest — the
18/// harder attestation implies the easier one. That says nothing about what the
19/// resulting credential can *see*: [`Audience::may_see`] withholds Annex XIII
20/// point 4 from authorities regardless of who vouched for them. Do not use this
21/// hierarchy as evidence that audiences are ordered.
22pub struct StaticTrustedIssuers {
23 legitimate_interest_dids: HashSet<String>,
24 authority_dids: HashSet<String>,
25}
26
27impl StaticTrustedIssuers {
28 pub fn new(
29 legitimate_interest_dids: impl IntoIterator<Item = impl Into<String>>,
30 authority_dids: impl IntoIterator<Item = impl Into<String>>,
31 ) -> Self {
32 Self {
33 legitimate_interest_dids: legitimate_interest_dids
34 .into_iter()
35 .map(Into::into)
36 .collect(),
37 authority_dids: authority_dids.into_iter().map(Into::into).collect(),
38 }
39 }
40
41 /// Single DID trusted for every audience (e.g. the operator's own issuer DID).
42 pub fn single(trusted_did: impl Into<String>) -> Self {
43 let did = trusted_did.into();
44 Self {
45 legitimate_interest_dids: HashSet::from([did.clone()]),
46 authority_dids: HashSet::from([did]),
47 }
48 }
49}
50
51impl TrustedIssuerRegistry for StaticTrustedIssuers {
52 fn is_trusted_for_audience(&self, issuer_did: &str, audience: Audience) -> bool {
53 match audience {
54 Audience::Public => true,
55 Audience::LegitimateInterest => {
56 self.legitimate_interest_dids.contains(issuer_did)
57 || self.authority_dids.contains(issuer_did)
58 }
59 Audience::Authority => self.authority_dids.contains(issuer_did),
60 // Fail-closed: an unmodelled future audience trusts no issuer until
61 // it is explicitly handled.
62 _ => false,
63 }
64 }
65}
66
67/// Trust registry that accepts any issuer DID — use in tests or single-operator
68/// bootstrap only. In production, supply a [`StaticTrustedIssuers`] loaded from
69/// operator configuration.
70pub struct AllowAllIssuers;
71
72impl TrustedIssuerRegistry for AllowAllIssuers {
73 fn is_trusted_for_audience(&self, _issuer_did: &str, _audience: Audience) -> bool {
74 true
75 }
76}