dpp_crypto/access/credential/
verify.rs1use chrono::{DateTime, Utc};
2
3use super::revocation::{RevocationOutcome, check_revocation};
4use super::trust::TrustedIssuerRegistry;
5use super::types::{AccessTier, CredentialRole, DppAccessCredential};
6use crate::access::status_list::StatusList;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum VerificationResult {
13 Valid {
15 access_tier: AccessTier,
16 role: CredentialRole,
17 holder_did: String,
18 },
19 Expired { expired_at: DateTime<Utc> },
21 InvalidSignature(String),
23 Revoked,
25 MalformedCredential(String),
27 OutOfScope { reason: String },
29 UntrustedIssuer { issuer_did: String },
32}
33
34impl VerificationResult {
35 pub fn is_valid(&self) -> bool {
36 matches!(self, Self::Valid { .. })
37 }
38}
39
40pub fn verify_credential_claims(
48 credential: &DppAccessCredential,
49 required_sector: Option<&str>,
50 now: DateTime<Utc>,
51) -> VerificationResult {
52 if !credential
53 .credential_type
54 .contains(&"VerifiableCredential".to_owned())
55 {
56 return VerificationResult::MalformedCredential(
57 "Missing 'VerifiableCredential' type".into(),
58 );
59 }
60
61 if now > credential.valid_until {
62 return VerificationResult::Expired {
63 expired_at: credential.valid_until,
64 };
65 }
66
67 if now < credential.valid_from {
68 return VerificationResult::MalformedCredential(
69 "Credential issuance date is in the future".into(),
70 );
71 }
72
73 if let Some(sector) = required_sector {
74 let subjects_sectors = &credential.credential_subject.sectors;
75 if !subjects_sectors.is_empty() && !subjects_sectors.iter().any(|s| s == sector) {
76 return VerificationResult::OutOfScope {
77 reason: format!(
78 "Credential covers sectors {:?}, but '{}' was requested",
79 subjects_sectors, sector
80 ),
81 };
82 }
83 }
84
85 let role = credential.credential_subject.role.clone();
86 let access_tier = role.access_tier();
87 VerificationResult::Valid {
88 access_tier,
89 role,
90 holder_did: credential.credential_subject.id.clone(),
91 }
92}
93
94pub fn verify_credential_with_revocation(
104 credential: &DppAccessCredential,
105 required_sector: Option<&str>,
106 now: DateTime<Utc>,
107 status_list: Option<&StatusList>,
108) -> VerificationResult {
109 let base = verify_credential_claims(credential, required_sector, now);
110 if !base.is_valid() {
111 return base;
112 }
113 if credential.credential_status.is_none() {
114 return base;
115 }
116 match status_list {
117 None => VerificationResult::Revoked,
118 Some(list) => match check_revocation(credential, list) {
119 RevocationOutcome::NotRevoked => base,
120 RevocationOutcome::Revoked | RevocationOutcome::Indeterminate => {
121 VerificationResult::Revoked
122 }
123 },
124 }
125}
126
127pub fn verify_credential_claims_with_trust(
130 credential: &DppAccessCredential,
131 required_sector: Option<&str>,
132 required_product_category: Option<&str>,
133 now: DateTime<Utc>,
134 trusted_issuers: &dyn TrustedIssuerRegistry,
135) -> VerificationResult {
136 let base = verify_credential_claims(credential, required_sector, now);
137 if !base.is_valid() {
138 return base;
139 }
140
141 if let Some(required_cat) = required_product_category {
142 let cats = &credential.credential_subject.product_categories;
143 if !cats.is_empty() && !cats.iter().any(|c| c == required_cat) {
144 return VerificationResult::OutOfScope {
145 reason: format!(
146 "Credential covers product categories {:?}, but '{}' was requested",
147 cats, required_cat
148 ),
149 };
150 }
151 }
152
153 let required_tier = credential.credential_subject.role.access_tier();
154 if !trusted_issuers.is_trusted_for_tier(&credential.issuer, required_tier) {
155 return VerificationResult::UntrustedIssuer {
156 issuer_did: credential.issuer.clone(),
157 };
158 }
159
160 base
161}
162
163pub fn verify_credential_with_revocation_and_trust(
166 credential: &DppAccessCredential,
167 required_sector: Option<&str>,
168 required_product_category: Option<&str>,
169 now: DateTime<Utc>,
170 status_list: Option<&StatusList>,
171 trusted_issuers: &dyn TrustedIssuerRegistry,
172) -> VerificationResult {
173 let base = verify_credential_claims_with_trust(
174 credential,
175 required_sector,
176 required_product_category,
177 now,
178 trusted_issuers,
179 );
180 if !base.is_valid() {
181 return base;
182 }
183 if credential.credential_status.is_none() {
184 return base;
185 }
186 match status_list {
187 None => VerificationResult::Revoked,
188 Some(list) => match check_revocation(credential, list) {
189 RevocationOutcome::NotRevoked => base,
190 RevocationOutcome::Revoked | RevocationOutcome::Indeterminate => {
191 VerificationResult::Revoked
192 }
193 },
194 }
195}