identity_kit/vc/validation/
presentation.rs1use std::collections::BTreeMap;
4
5use serde_json::Value;
6
7use crate::did::{Uri, VerificationRelationship};
8use crate::jose::{DefaultVerifier, JwsHeader, JwsVerifier};
9use crate::vc::{Jwt, JwtClaims, Party, Presentation};
10
11use super::credential::{check_controller, check_typ, decode};
12use super::{
13 DecodedCredential, FailFast, JwtCredentialValidator, KeyResolver, PresentationValidationError,
14 PresentationValidationOptions, StatusCheck, SubjectHolderRelationship, ValidationError,
15};
16
17#[derive(Debug, Clone)]
20#[non_exhaustive]
21pub struct DecodedPresentation {
22 pub header: JwsHeader,
24 pub claims: JwtClaims,
26 pub presentation: Presentation,
28 pub credentials: Vec<DecodedCredential>,
30}
31
32impl DecodedPresentation {
33 pub fn holder(&self) -> Option<&Uri> {
35 self.presentation.holder.as_ref().map(Party::id)
36 }
37}
38
39pub struct JwtPresentationValidator<R, V = DefaultVerifier> {
45 credentials: JwtCredentialValidator<R, V>,
46}
47
48impl<R: KeyResolver, V: JwsVerifier> JwtPresentationValidator<R, V> {
49 pub fn new(resolver: R, verifier: V) -> Self {
52 Self {
53 credentials: JwtCredentialValidator::new(resolver, verifier),
54 }
55 }
56
57 pub fn from_credential_validator(credentials: JwtCredentialValidator<R, V>) -> Self {
59 Self { credentials }
60 }
61
62 #[must_use]
64 pub fn with_status_check(mut self, check: impl StatusCheck + 'static) -> Self {
65 self.credentials = self.credentials.with_status_check(check);
66 self
67 }
68
69 pub fn credential_validator(&self) -> &JwtCredentialValidator<R, V> {
71 &self.credentials
72 }
73
74 pub async fn validate(
82 &self,
83 jwt: &Jwt,
84 options: &PresentationValidationOptions,
85 ) -> Result<DecodedPresentation, PresentationValidationError> {
86 let mut decoded = self
87 .validate_presentation(jwt, options)
88 .await
89 .map_err(PresentationValidationError::presentation)?;
90 let holder = decoded.holder().cloned();
91 let mut errors = BTreeMap::new();
92 for (index, credential) in decoded
93 .presentation
94 .verifiable_credential
95 .iter()
96 .enumerate()
97 {
98 let result = self
99 .credentials
100 .validate(credential, &options.credentials)
101 .await
102 .and_then(|validated| {
103 check_subject_holder(&validated, holder.as_ref(), options.subject_holder)?;
104 Ok(validated)
105 });
106 match result {
107 Ok(validated) => decoded.credentials.push(validated),
108 Err(error) => {
109 errors.insert(index, error);
110 if options.fail_fast == FailFast::FirstError {
111 break;
112 }
113 }
114 }
115 }
116 if errors.is_empty() {
117 Ok(decoded)
118 } else {
119 Err(PresentationValidationError::credentials(errors))
120 }
121 }
122
123 async fn validate_presentation(
125 &self,
126 jwt: &Jwt,
127 options: &PresentationValidationOptions,
128 ) -> Result<DecodedPresentation, ValidationError> {
129 let jws = jwt.decode()?;
130 check_typ(jws.header(), "JWT")?;
131 let claims: JwtClaims = serde_json::from_slice(jws.payload())
132 .map_err(|e| ValidationError::Payload(e.to_string()))?;
133 let presentation = Presentation::from_jwt_claims(&claims)?;
134 let kid = self
135 .credentials
136 .verify(&jws, VerificationRelationship::Authentication)
137 .await?;
138 let holder = presentation
139 .holder
140 .as_ref()
141 .ok_or(crate::vc::CredentialError::MissingClaim("iss"))?;
142 check_controller(holder, &kid, "holder")?;
143 options.time.check_claims(&claims)?;
144 if let Some(expected) = &options.audience
145 && !claims.audiences().any(|aud| aud == expected)
146 {
147 return Err(ValidationError::AudienceMismatch {
148 expected: expected.clone(),
149 found: claims.aud.clone(),
150 });
151 }
152 if let Some(expected) = &options.nonce {
153 let found = claims.additional.get("nonce").and_then(Value::as_str);
154 if found != Some(expected.as_str()) {
155 return Err(ValidationError::NonceMismatch {
156 expected: expected.clone(),
157 found: found.map(str::to_owned),
158 });
159 }
160 }
161 Ok(DecodedPresentation {
162 header: jws.header().clone(),
163 claims,
164 presentation,
165 credentials: Vec::new(),
166 })
167 }
168}
169
170impl<R: core::fmt::Debug, V: core::fmt::Debug> core::fmt::Debug for JwtPresentationValidator<R, V> {
171 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
172 f.debug_struct("JwtPresentationValidator")
173 .field("credentials", &self.credentials)
174 .finish()
175 }
176}
177
178pub fn decode_presentation(jwt: &Jwt) -> Result<DecodedPresentation, ValidationError> {
186 let jws = jwt.decode()?;
187 check_typ(jws.header(), "JWT")?;
188 let claims: JwtClaims = serde_json::from_slice(jws.payload())
189 .map_err(|e| ValidationError::Payload(e.to_string()))?;
190 let presentation = Presentation::from_jwt_claims(&claims)?;
191 let credentials = presentation
192 .verifiable_credential
193 .iter()
194 .map(|credential| decode(&credential.decode()?))
195 .collect::<Result<_, _>>()?;
196 Ok(DecodedPresentation {
197 header: jws.header().clone(),
198 claims,
199 presentation,
200 credentials,
201 })
202}
203
204fn check_subject_holder(
206 credential: &DecodedCredential,
207 holder: Option<&Uri>,
208 relationship: SubjectHolderRelationship,
209) -> Result<(), ValidationError> {
210 let required = match relationship {
211 SubjectHolderRelationship::AlwaysSubject => true,
212 SubjectHolderRelationship::SubjectOnNonTransferable => credential
213 .credential
214 .properties
215 .get("nonTransferable")
216 .is_some_and(|value| value == &Value::Bool(true)),
217 SubjectHolderRelationship::Any => false,
218 };
219 if !required {
220 return Ok(());
221 }
222 let holder = holder.expect("a validated presentation has a holder");
224 for subject in credential.credential.credential_subject.iter() {
225 if subject.id.as_ref() != Some(holder) {
226 return Err(ValidationError::SubjectHolderMismatch {
227 subject: subject.id.clone(),
228 holder: holder.clone(),
229 });
230 }
231 }
232 Ok(())
233}