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 {
335 time: options.credentials.time,
336 key_binding: options.key_binding.clone(),
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::StatusClaim))
353 .collect(),
354 options.credentials.status,
355 options.credentials.time.now(),
356 )
357 .await?;
358 check_subject_holder(
359 &credential.credential().properties,
360 credential.credential().credential_subject.iter(),
361 holder,
362 options.subject_holder,
363 )?;
364 return Ok(PresentedCredentialV2::SdJwt(Box::new(credential)));
365 }
366 let jwt = enveloped.to_jwt()?;
367 let validated = self
368 .credentials
369 .validate_v2(&jwt, &options.credentials)
370 .await?;
371 check_subject_holder(
372 &validated.credential.properties,
373 validated.credential.credential_subject.iter(),
374 holder,
375 options.subject_holder,
376 )?;
377 Ok(PresentedCredentialV2::Jwt(Box::new(validated)))
378 }
379
380 async fn validate_presentation(
382 &self,
383 jwt: &Jwt,
384 options: &PresentationValidationOptions,
385 ) -> Result<DecodedPresentation, ValidationError> {
386 let jws = jwt.decode()?;
387 check_typ(jws.header(), "JWT", &options.additional_typ)?;
388 let claims = plain_claims(&jws)?;
389 let presentation = Presentation::from_jwt_claims(&claims)?;
390 self.check_holder_and_claims(&jws, presentation.holder.as_ref(), "iss", &claims, options)
391 .await?;
392 Ok(DecodedPresentation {
393 header: jws.header().clone(),
394 claims,
395 presentation,
396 credentials: Vec::new(),
397 })
398 }
399
400 async fn validate_presentation_v2(
402 &self,
403 jwt: &Jwt,
404 options: &PresentationValidationOptions,
405 ) -> Result<DecodedPresentationV2, ValidationError> {
406 let jws = jwt.decode()?;
407 let (claims, presentation) = decode_payload_v2(&jws, &options.additional_typ)?;
408 self.check_holder_and_claims(
409 &jws,
410 presentation.holder.as_ref(),
411 "holder",
412 &claims,
413 options,
414 )
415 .await?;
416 Ok(DecodedPresentationV2 {
417 header: jws.header().clone(),
418 claims,
419 presentation,
420 credentials: Vec::new(),
421 })
422 }
423
424 async fn check_holder_and_claims(
428 &self,
429 jws: &CompactJws,
430 holder: Option<&Party>,
431 missing: &'static str,
432 claims: &JwtClaims,
433 options: &PresentationValidationOptions,
434 ) -> Result<(), ValidationError> {
435 let (reference, key) = self
436 .credentials
437 .verify(
438 jws,
439 KeyRole::Subject,
440 holder.map(|holder| holder.id().as_str()),
441 )
442 .await?;
443 let holder = holder.ok_or(CredentialError::MissingClaim(missing))?;
444 check_controller(holder, &reference, &key, "holder")?;
445 options.time.check_claims(claims)?;
446 if let Some(expected) = &options.audience
447 && !claims.audiences().any(|aud| aud == expected)
448 {
449 return Err(ValidationError::AudienceMismatch {
450 expected: expected.clone(),
451 found: claims.aud.clone(),
452 });
453 }
454 if let Some(expected) = &options.nonce {
455 let found = claims.additional.get("nonce").and_then(Value::as_str);
456 if found != Some(expected.as_str()) {
457 return Err(ValidationError::NonceMismatch {
458 expected: expected.clone(),
459 found: found.map(str::to_owned),
460 });
461 }
462 }
463 Ok(())
464 }
465}
466
467impl<R: core::fmt::Debug, V: core::fmt::Debug> core::fmt::Debug for JwtPresentationValidator<R, V> {
468 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
469 f.debug_struct("JwtPresentationValidator")
470 .field("credentials", &self.credentials)
471 .finish()
472 }
473}
474
475pub fn decode_presentation(jwt: &Jwt) -> Result<DecodedPresentation, ValidationError> {
485 decode_presentation_with(jwt, &NO_TYP, &NO_TYP)
486}
487
488pub(crate) fn decode_presentation_with(
491 jwt: &Jwt,
492 presentation_typ: &BTreeSet<String>,
493 credential_typ: &BTreeSet<String>,
494) -> Result<DecodedPresentation, ValidationError> {
495 let jws = jwt.decode()?;
496 check_typ(jws.header(), "JWT", presentation_typ)?;
497 let claims = plain_claims(&jws)?;
498 let presentation = Presentation::from_jwt_claims(&claims)?;
499 let credentials = presentation
500 .verifiable_credential
501 .iter()
502 .map(|credential| decode(&credential.decode()?, credential_typ))
503 .collect::<Result<_, _>>()?;
504 Ok(DecodedPresentation {
505 header: jws.header().clone(),
506 claims,
507 presentation,
508 credentials,
509 })
510}
511
512pub fn decode_presentation_v2(jwt: &Jwt) -> Result<DecodedPresentationV2, ValidationError> {
525 let jws = jwt.decode()?;
526 let (claims, presentation) = decode_payload_v2(&jws, &NO_TYP)?;
527 let credentials = presentation
528 .verifiable_credential
529 .iter()
530 .map(decode_enveloped)
531 .collect::<Result<_, _>>()?;
532 Ok(DecodedPresentationV2 {
533 header: jws.header().clone(),
534 claims,
535 presentation,
536 credentials,
537 })
538}
539
540fn decode_enveloped(enveloped: &EnvelopedVc) -> Result<PresentedCredentialV2, ValidationError> {
542 #[cfg(feature = "sd-jwt")]
543 if enveloped.is_sd_jwt() {
544 let credential = SdJwtCredentialV2::from_sd_jwt(enveloped.to_sd_jwt()?)
545 .map_err(crate::sd_jwt_vc::SdJwtValidationError::from)?;
546 return Ok(PresentedCredentialV2::SdJwt(Box::new(credential)));
547 }
548 Ok(PresentedCredentialV2::Jwt(Box::new(decode_v2(
549 &enveloped.to_jwt()?.decode()?,
550 &NO_TYP,
551 )?)))
552}
553
554fn decode_payload_v2(
557 jws: &CompactJws,
558 additional_typ: &BTreeSet<String>,
559) -> Result<(JwtClaims, PresentationV2), ValidationError> {
560 check_typ(jws.header(), PresentationV2::JWT_TYP, additional_typ)?;
561 let claims = plain_claims(jws)?;
562 let presentation = PresentationV2::from_jwt_claims(&claims)?;
563 Ok((claims, presentation))
564}
565
566fn check_subject_holder<'a>(
568 properties: &IndexMap<String, Value>,
569 subjects: impl Iterator<Item = &'a Subject>,
570 holder: Option<&Uri>,
571 relationship: SubjectHolderRelationship,
572) -> Result<(), ValidationError> {
573 let required = match relationship {
574 SubjectHolderRelationship::AlwaysSubject => true,
575 SubjectHolderRelationship::SubjectOnNonTransferable => properties
576 .get("nonTransferable")
577 .is_some_and(|value| value == &Value::Bool(true)),
578 SubjectHolderRelationship::Any => false,
579 };
580 if !required {
581 return Ok(());
582 }
583 let holder = holder.expect("a validated presentation has a holder");
585 for subject in subjects {
586 if subject.id.as_ref() != Some(holder) {
587 return Err(ValidationError::SubjectHolderMismatch {
588 subject: subject.id.clone(),
589 holder: holder.clone(),
590 });
591 }
592 }
593 Ok(())
594}