dpp_vc/credential/
verify.rs1use chrono::{DateTime, Utc};
2
3use super::revocation::{RevocationOutcome, check_revocation};
4use super::trust::TrustedIssuerRegistry;
5use super::types::{Audience, CredentialRole, DppAccessCredential};
6use crate::status_list::StatusList;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum VerificationResult {
13 Valid {
15 audience: Audience,
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_product_group: 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(product_group) = required_product_group {
74 let subjects_product_groups = &credential.credential_subject.product_groups;
75 if !subjects_product_groups.is_empty()
76 && !subjects_product_groups.iter().any(|s| s == product_group)
77 {
78 return VerificationResult::OutOfScope {
79 reason: format!(
80 "Credential covers product_groups {:?}, but '{}' was requested",
81 subjects_product_groups, product_group
82 ),
83 };
84 }
85 }
86
87 let role = credential.credential_subject.role.clone();
88 let audience = role.audience();
89 VerificationResult::Valid {
90 audience,
91 role,
92 holder_did: credential.credential_subject.id.clone(),
93 }
94}
95
96pub fn verify_credential_with_revocation(
106 credential: &DppAccessCredential,
107 required_product_group: Option<&str>,
108 now: DateTime<Utc>,
109 status_list: Option<&StatusList>,
110) -> VerificationResult {
111 let base = verify_credential_claims(credential, required_product_group, now);
112 if !base.is_valid() {
113 return base;
114 }
115 apply_revocation_check(base, credential, status_list)
116}
117
118fn apply_revocation_check(
125 base: VerificationResult,
126 credential: &DppAccessCredential,
127 status_list: Option<&StatusList>,
128) -> VerificationResult {
129 if credential.credential_status.is_none() {
130 return base;
131 }
132 match status_list {
133 None => VerificationResult::Revoked,
134 Some(list) => match check_revocation(credential, list) {
135 RevocationOutcome::NotRevoked => base,
136 RevocationOutcome::Revoked | RevocationOutcome::Indeterminate => {
137 VerificationResult::Revoked
138 }
139 },
140 }
141}
142
143pub fn verify_credential_claims_with_trust(
146 credential: &DppAccessCredential,
147 required_product_group: Option<&str>,
148 required_product_category: Option<&str>,
149 now: DateTime<Utc>,
150 trusted_issuers: &dyn TrustedIssuerRegistry,
151) -> VerificationResult {
152 let base = verify_credential_claims(credential, required_product_group, now);
153 if !base.is_valid() {
154 return base;
155 }
156
157 if let Some(required_cat) = required_product_category {
158 let cats = &credential.credential_subject.product_categories;
159 if !cats.is_empty() && !cats.iter().any(|c| c == required_cat) {
160 return VerificationResult::OutOfScope {
161 reason: format!(
162 "Credential covers product categories {:?}, but '{}' was requested",
163 cats, required_cat
164 ),
165 };
166 }
167 }
168
169 let required_audience = credential.credential_subject.role.audience();
170 if !trusted_issuers.is_trusted_for_audience(&credential.issuer, required_audience) {
171 return VerificationResult::UntrustedIssuer {
172 issuer_did: credential.issuer.clone(),
173 };
174 }
175
176 base
177}
178
179pub fn verify_credential_with_revocation_and_trust(
182 credential: &DppAccessCredential,
183 required_product_group: Option<&str>,
184 required_product_category: Option<&str>,
185 now: DateTime<Utc>,
186 status_list: Option<&StatusList>,
187 trusted_issuers: &dyn TrustedIssuerRegistry,
188) -> VerificationResult {
189 let base = verify_credential_claims_with_trust(
190 credential,
191 required_product_group,
192 required_product_category,
193 now,
194 trusted_issuers,
195 );
196 if !base.is_valid() {
197 return base;
198 }
199 apply_revocation_check(base, credential, status_list)
200}