1use std::collections::{BTreeMap, BTreeSet};
4
5use indexmap::IndexMap;
6use serde_json::Value;
7
8use crate::did::Uri;
9use crate::jose::{CompactJws, DefaultVerifier, JwsHeader, JwsVerifier};
10#[cfg(feature = "sd-jwt")]
11use crate::sd_jwt_vc::{SdJwtCredentialV2, SdJwtCredentialValidator, SdJwtValidationOptions};
12use crate::vc::{
13 CredentialError, CredentialV2, EnvelopedVc, Jwt, JwtClaims, Party, Presentation,
14 PresentationV2, Subject,
15};
16
17#[cfg(feature = "sd-jwt")]
18use super::CredentialStatus;
19use super::credential::{NO_TYP, check_controller, check_typ, decode, decode_v2, plain_claims};
20use super::{
21 DecodedCredential, DecodedCredentialV2, FailFast, JwtCredentialValidator, KeyResolver, KeyRole,
22 PresentationValidationError, PresentationValidationOptions, StatusCheck,
23 SubjectHolderRelationship, ValidationError,
24};
25
26#[derive(Debug, Clone)]
29#[non_exhaustive]
30pub struct DecodedPresentation {
31 pub header: JwsHeader,
33 pub claims: JwtClaims,
35 pub presentation: Presentation,
37 pub credentials: Vec<DecodedCredential>,
39}
40
41impl DecodedPresentation {
42 pub fn holder(&self) -> Option<&Uri> {
44 self.presentation.holder.as_ref().map(Party::id)
45 }
46}
47
48#[derive(Debug, Clone)]
51#[non_exhaustive]
52pub struct DecodedPresentationV2 {
53 pub header: JwsHeader,
55 pub claims: JwtClaims,
57 pub presentation: PresentationV2,
60 pub credentials: Vec<PresentedCredentialV2>,
63}
64
65impl DecodedPresentationV2 {
66 pub fn holder(&self) -> Option<&Uri> {
68 self.presentation.holder.as_ref().map(Party::id)
69 }
70}
71
72#[derive(Debug, Clone)]
76#[non_exhaustive]
77pub enum PresentedCredentialV2 {
78 Jwt(Box<DecodedCredentialV2>),
80 #[cfg(feature = "sd-jwt")]
82 #[cfg_attr(docsrs, doc(cfg(feature = "sd-jwt")))]
83 SdJwt(Box<SdJwtCredentialV2>),
84}
85
86impl PresentedCredentialV2 {
87 pub fn credential(&self) -> &CredentialV2 {
89 match self {
90 Self::Jwt(decoded) => &decoded.credential,
91 #[cfg(feature = "sd-jwt")]
92 Self::SdJwt(credential) => credential.credential(),
93 }
94 }
95
96 pub fn header(&self) -> &JwsHeader {
98 match self {
99 Self::Jwt(decoded) => &decoded.header,
100 #[cfg(feature = "sd-jwt")]
101 Self::SdJwt(credential) => credential.header(),
102 }
103 }
104
105 pub fn as_jwt(&self) -> Option<&DecodedCredentialV2> {
107 match self {
108 Self::Jwt(decoded) => Some(decoded),
109 #[cfg(feature = "sd-jwt")]
110 Self::SdJwt(_) => None,
111 }
112 }
113
114 #[cfg(feature = "sd-jwt")]
116 #[cfg_attr(docsrs, doc(cfg(feature = "sd-jwt")))]
117 pub fn as_sd_jwt(&self) -> Option<&SdJwtCredentialV2> {
118 match self {
119 Self::SdJwt(credential) => Some(credential),
120 Self::Jwt(_) => None,
121 }
122 }
123}
124
125pub struct JwtPresentationValidator<R, V = DefaultVerifier> {
178 credentials: JwtCredentialValidator<R, V>,
179}
180
181impl<R: KeyResolver, V: JwsVerifier> JwtPresentationValidator<R, V> {
182 pub fn new(resolver: R, verifier: V) -> Self {
185 Self {
186 credentials: JwtCredentialValidator::new(resolver, verifier),
187 }
188 }
189
190 pub fn from_credential_validator(credentials: JwtCredentialValidator<R, V>) -> Self {
192 Self { credentials }
193 }
194
195 #[must_use]
197 pub fn with_status_check(mut self, check: impl StatusCheck + 'static) -> Self {
198 self.credentials = self.credentials.with_status_check(check);
199 self
200 }
201
202 pub fn credential_validator(&self) -> &JwtCredentialValidator<R, V> {
204 &self.credentials
205 }
206
207 pub async fn validate(
215 &self,
216 jwt: &Jwt,
217 options: &PresentationValidationOptions,
218 ) -> Result<DecodedPresentation, PresentationValidationError> {
219 let mut decoded = self
220 .validate_presentation(jwt, options)
221 .await
222 .map_err(PresentationValidationError::presentation)?;
223 let holder = decoded.holder().cloned();
224 let mut errors = BTreeMap::new();
225 for (index, credential) in decoded
226 .presentation
227 .verifiable_credential
228 .iter()
229 .enumerate()
230 {
231 let result = self
232 .credentials
233 .validate(credential, &options.credentials)
234 .await
235 .and_then(|validated| {
236 let credential = &validated.credential;
237 check_subject_holder(
238 &credential.properties,
239 credential.credential_subject.iter(),
240 holder.as_ref(),
241 options.subject_holder,
242 )?;
243 Ok(validated)
244 });
245 match result {
246 Ok(validated) => decoded.credentials.push(validated),
247 Err(error) => {
248 errors.insert(index, error);
249 if options.fail_fast == FailFast::FirstError {
250 break;
251 }
252 }
253 }
254 }
255 if errors.is_empty() {
256 Ok(decoded)
257 } else {
258 Err(PresentationValidationError::credentials(errors))
259 }
260 }
261
262 pub async fn validate_v2(
283 &self,
284 jwt: &Jwt,
285 options: &PresentationValidationOptions,
286 ) -> Result<DecodedPresentationV2, PresentationValidationError> {
287 let mut decoded = self
288 .validate_presentation_v2(jwt, options)
289 .await
290 .map_err(PresentationValidationError::presentation)?;
291 let holder = decoded.holder().cloned();
292 let mut errors = BTreeMap::new();
293 for (index, enveloped) in decoded
294 .presentation
295 .verifiable_credential
296 .iter()
297 .enumerate()
298 {
299 match self
300 .validate_enveloped(enveloped, holder.as_ref(), options)
301 .await
302 {
303 Ok(validated) => decoded.credentials.push(validated),
304 Err(error) => {
305 errors.insert(index, error);
306 if options.fail_fast == FailFast::FirstError {
307 break;
308 }
309 }
310 }
311 }
312 if errors.is_empty() {
313 Ok(decoded)
314 } else {
315 Err(PresentationValidationError::credentials(errors))
316 }
317 }
318
319 async fn validate_enveloped(
322 &self,
323 enveloped: &EnvelopedVc,
324 holder: Option<&Uri>,
325 options: &PresentationValidationOptions,
326 ) -> Result<PresentedCredentialV2, ValidationError> {
327 #[cfg(feature = "sd-jwt")]
328 if enveloped.is_sd_jwt() {
329 let sd_jwt = enveloped.to_sd_jwt()?;
330 let credential =
331 SdJwtCredentialV2::from_sd_jwt_with(sd_jwt, &options.credentials.additional_typ)
332 .map_err(crate::sd_jwt_vc::SdJwtValidationError::from)?;
333 let sd_jwt_options = SdJwtValidationOptions {
334 time: options.credentials.time,
335 key_binding: options.key_binding.clone(),
336 status: options.credentials.status,
337 ..SdJwtValidationOptions::default()
338 };
339 let validated = SdJwtCredentialValidator::new(
340 self.credentials.resolver(),
341 self.credentials.verifier(),
342 )
343 .validate_credential_v2_unchecked_status(credential, &sd_jwt_options)
344 .await?;
345 let credential = validated.credential;
346 self.credentials
347 .check_status(
348 credential
349 .credential()
350 .statuses()
351 .map(CredentialStatus::Vcdm)
352 .chain(credential.status().map(CredentialStatus::SdJwt)),
353 options.credentials.status,
354 options.credentials.time.now(),
355 )
356 .await?;
357 check_subject_holder(
358 &credential.credential().properties,
359 credential.credential().credential_subject.iter(),
360 holder,
361 options.subject_holder,
362 )?;
363 return Ok(PresentedCredentialV2::SdJwt(Box::new(credential)));
364 }
365 let jwt = enveloped.to_jwt()?;
366 let validated = self
367 .credentials
368 .validate_v2(&jwt, &options.credentials)
369 .await?;
370 check_subject_holder(
371 &validated.credential.properties,
372 validated.credential.credential_subject.iter(),
373 holder,
374 options.subject_holder,
375 )?;
376 Ok(PresentedCredentialV2::Jwt(Box::new(validated)))
377 }
378
379 async fn validate_presentation(
381 &self,
382 jwt: &Jwt,
383 options: &PresentationValidationOptions,
384 ) -> Result<DecodedPresentation, ValidationError> {
385 let jws = jwt.decode()?;
386 check_typ(jws.header(), "JWT", &options.additional_typ)?;
387 let claims = plain_claims(&jws)?;
388 let presentation = Presentation::from_jwt_claims(&claims)?;
389 self.check_holder_and_claims(&jws, presentation.holder.as_ref(), "iss", &claims, options)
390 .await?;
391 Ok(DecodedPresentation {
392 header: jws.header().clone(),
393 claims,
394 presentation,
395 credentials: Vec::new(),
396 })
397 }
398
399 async fn validate_presentation_v2(
401 &self,
402 jwt: &Jwt,
403 options: &PresentationValidationOptions,
404 ) -> Result<DecodedPresentationV2, ValidationError> {
405 let jws = jwt.decode()?;
406 let (claims, presentation) = decode_payload_v2(&jws, &options.additional_typ)?;
407 self.check_holder_and_claims(
408 &jws,
409 presentation.holder.as_ref(),
410 "holder",
411 &claims,
412 options,
413 )
414 .await?;
415 Ok(DecodedPresentationV2 {
416 header: jws.header().clone(),
417 claims,
418 presentation,
419 credentials: Vec::new(),
420 })
421 }
422
423 async fn check_holder_and_claims(
427 &self,
428 jws: &CompactJws,
429 holder: Option<&Party>,
430 missing: &'static str,
431 claims: &JwtClaims,
432 options: &PresentationValidationOptions,
433 ) -> Result<(), ValidationError> {
434 let (reference, key) = self
435 .credentials
436 .verify(
437 jws,
438 KeyRole::Subject,
439 holder.map(|holder| holder.id().as_str()),
440 )
441 .await?;
442 let holder = holder.ok_or(CredentialError::MissingClaim(missing))?;
443 check_controller(holder, &reference, &key, "holder")?;
444 options.time.check_claims(claims)?;
445 if let Some(expected) = &options.audience
446 && !claims.audiences().any(|aud| aud == expected)
447 {
448 return Err(ValidationError::AudienceMismatch {
449 expected: expected.clone(),
450 found: claims.aud.clone(),
451 });
452 }
453 if let Some(expected) = &options.nonce {
454 let found = claims.additional.get("nonce").and_then(Value::as_str);
455 if found != Some(expected.as_str()) {
456 return Err(ValidationError::NonceMismatch {
457 expected: expected.clone(),
458 found: found.map(str::to_owned),
459 });
460 }
461 }
462 Ok(())
463 }
464}
465
466impl<R: core::fmt::Debug, V: core::fmt::Debug> core::fmt::Debug for JwtPresentationValidator<R, V> {
467 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
468 f.debug_struct("JwtPresentationValidator")
469 .field("credentials", &self.credentials)
470 .finish()
471 }
472}
473
474pub fn decode_presentation(jwt: &Jwt) -> Result<DecodedPresentation, ValidationError> {
484 decode_presentation_with(jwt, &NO_TYP, &NO_TYP)
485}
486
487pub(crate) fn decode_presentation_with(
490 jwt: &Jwt,
491 presentation_typ: &BTreeSet<String>,
492 credential_typ: &BTreeSet<String>,
493) -> Result<DecodedPresentation, ValidationError> {
494 let jws = jwt.decode()?;
495 check_typ(jws.header(), "JWT", presentation_typ)?;
496 let claims = plain_claims(&jws)?;
497 let presentation = Presentation::from_jwt_claims(&claims)?;
498 let credentials = presentation
499 .verifiable_credential
500 .iter()
501 .map(|credential| decode(&credential.decode()?, credential_typ))
502 .collect::<Result<_, _>>()?;
503 Ok(DecodedPresentation {
504 header: jws.header().clone(),
505 claims,
506 presentation,
507 credentials,
508 })
509}
510
511pub fn decode_presentation_v2(jwt: &Jwt) -> Result<DecodedPresentationV2, ValidationError> {
524 let jws = jwt.decode()?;
525 let (claims, presentation) = decode_payload_v2(&jws, &NO_TYP)?;
526 let credentials = presentation
527 .verifiable_credential
528 .iter()
529 .map(decode_enveloped)
530 .collect::<Result<_, _>>()?;
531 Ok(DecodedPresentationV2 {
532 header: jws.header().clone(),
533 claims,
534 presentation,
535 credentials,
536 })
537}
538
539fn decode_enveloped(enveloped: &EnvelopedVc) -> Result<PresentedCredentialV2, ValidationError> {
541 #[cfg(feature = "sd-jwt")]
542 if enveloped.is_sd_jwt() {
543 let credential = SdJwtCredentialV2::from_sd_jwt(enveloped.to_sd_jwt()?)
544 .map_err(crate::sd_jwt_vc::SdJwtValidationError::from)?;
545 return Ok(PresentedCredentialV2::SdJwt(Box::new(credential)));
546 }
547 Ok(PresentedCredentialV2::Jwt(Box::new(decode_v2(
548 &enveloped.to_jwt()?.decode()?,
549 &NO_TYP,
550 )?)))
551}
552
553fn decode_payload_v2(
556 jws: &CompactJws,
557 additional_typ: &BTreeSet<String>,
558) -> Result<(JwtClaims, PresentationV2), ValidationError> {
559 check_typ(jws.header(), PresentationV2::JWT_TYP, additional_typ)?;
560 let claims = plain_claims(jws)?;
561 let presentation = PresentationV2::from_jwt_claims(&claims)?;
562 Ok((claims, presentation))
563}
564
565fn check_subject_holder<'a>(
567 properties: &IndexMap<String, Value>,
568 subjects: impl Iterator<Item = &'a Subject>,
569 holder: Option<&Uri>,
570 relationship: SubjectHolderRelationship,
571) -> Result<(), ValidationError> {
572 let required = match relationship {
573 SubjectHolderRelationship::AlwaysSubject => true,
574 SubjectHolderRelationship::SubjectOnNonTransferable => properties
575 .get("nonTransferable")
576 .is_some_and(|value| value == &Value::Bool(true)),
577 SubjectHolderRelationship::Any => false,
578 };
579 if !required {
580 return Ok(());
581 }
582 let holder = holder.expect("a validated presentation has a holder");
584 for subject in subjects {
585 if subject.id.as_ref() != Some(holder) {
586 return Err(ValidationError::SubjectHolderMismatch {
587 subject: subject.id.clone(),
588 holder: holder.clone(),
589 });
590 }
591 }
592 Ok(())
593}