1use std::str::FromStr as _;
5
6use identity_core::convert::FromJson;
7use identity_did::CoreDID;
8use identity_did::DIDUrl;
9use identity_document::document::CoreDocument;
10use identity_document::verifiable::JwsVerificationOptions;
11use identity_verification::jwk::Jwk;
12use identity_verification::jws::DecodedJws;
13use identity_verification::jws::Decoder;
14use identity_verification::jws::JwsValidationItem;
15use identity_verification::jws::JwsVerifier;
16
17use super::CompoundCredentialValidationError;
18use super::DecodedJwtCredential;
19use super::JwtCredentialValidationOptions;
20use super::JwtCredentialValidatorUtils;
21use super::JwtValidationError;
22use super::SignerContext;
23use crate::credential::Credential;
24use crate::credential::CredentialJwtClaims;
25use crate::credential::CredentialT;
26use crate::credential::Jwt;
27use crate::credential::JwtVcV2;
28use crate::validator::DecodedJwtCredentialV2;
29use crate::validator::FailFast;
30
31#[non_exhaustive]
33pub struct JwtCredentialValidator<V: JwsVerifier>(V);
34
35impl<V: JwsVerifier> JwtCredentialValidator<V> {
36 pub fn with_signature_verifier(signature_verifier: V) -> Self {
39 Self(signature_verifier)
40 }
41
42 pub fn validate<DOC, T>(
66 &self,
67 credential_jwt: &Jwt,
68 issuer: &DOC,
69 options: &JwtCredentialValidationOptions,
70 fail_fast: FailFast,
71 ) -> Result<DecodedJwtCredential<T>, CompoundCredentialValidationError>
72 where
73 T: Clone + serde::Serialize + serde::de::DeserializeOwned,
74 DOC: AsRef<CoreDocument>,
75 {
76 let credential_token = self
77 .verify_signature(
78 credential_jwt,
79 std::slice::from_ref(issuer.as_ref()),
80 &options.verification_options,
81 )
82 .map_err(|err| CompoundCredentialValidationError {
83 validation_errors: [err].into(),
84 })?;
85
86 Self::validate_decoded_credential::<CoreDocument, T>(
87 &credential_token.credential,
88 std::slice::from_ref(issuer.as_ref()),
89 options,
90 fail_fast,
91 )?;
92
93 Ok(credential_token)
94 }
95
96 pub fn validate_v2<DOC, T>(
121 &self,
122 credential_jwt: &JwtVcV2,
123 issuer: &DOC,
124 options: &JwtCredentialValidationOptions,
125 fail_fast: FailFast,
126 ) -> Result<DecodedJwtCredentialV2<T>, CompoundCredentialValidationError>
127 where
128 T: Clone + serde::Serialize + serde::de::DeserializeOwned,
129 DOC: AsRef<CoreDocument>,
130 {
131 let credential_token = Self::verify_signature_with_verifier_v2(
132 &self.0,
133 credential_jwt,
134 std::slice::from_ref(issuer.as_ref()),
135 &options.verification_options,
136 )
137 .map_err(|err| CompoundCredentialValidationError {
138 validation_errors: [err].into(),
139 })?;
140
141 Self::validate_decoded_credential(
142 &credential_token.credential,
143 std::slice::from_ref(issuer),
144 options,
145 fail_fast,
146 )?;
147
148 Ok(credential_token)
149 }
150
151 pub fn verify_signature<DOC, T>(
168 &self,
169 credential: &Jwt,
170 trusted_issuers: &[DOC],
171 options: &JwsVerificationOptions,
172 ) -> Result<DecodedJwtCredential<T>, JwtValidationError>
173 where
174 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
175 DOC: AsRef<CoreDocument>,
176 {
177 Self::verify_signature_with_verifier(&self.0, credential, trusted_issuers, options)
178 }
179
180 pub fn verify_signature_v2<DOC, T>(
197 &self,
198 credential: &JwtVcV2,
199 trusted_issuers: &[DOC],
200 options: &JwsVerificationOptions,
201 ) -> Result<DecodedJwtCredentialV2<T>, JwtValidationError>
202 where
203 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
204 DOC: AsRef<CoreDocument>,
205 {
206 Self::verify_signature_with_verifier_v2::<DOC, V, T>(&self.0, credential, trusted_issuers, options)
207 }
208
209 pub(crate) fn validate_decoded_credential<DOC, T>(
213 credential: &dyn CredentialT<Properties = T>,
214 issuers: &[DOC],
215 options: &JwtCredentialValidationOptions,
216 fail_fast: FailFast,
217 ) -> Result<(), CompoundCredentialValidationError>
218 where
219 T: Clone + serde::Serialize + serde::de::DeserializeOwned,
220 DOC: AsRef<CoreDocument>,
221 {
222 let expiry_date_validation = std::iter::once_with(|| {
225 JwtCredentialValidatorUtils::check_expires_on_or_after(
226 credential,
227 options.earliest_expiry_date.unwrap_or_default(),
228 )
229 });
230
231 let issuance_date_validation = std::iter::once_with(|| {
232 JwtCredentialValidatorUtils::check_issued_on_or_before(
233 credential,
234 options.latest_issuance_date.unwrap_or_default(),
235 )
236 });
237
238 let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential));
239
240 let subject_holder_validation = std::iter::once_with(|| {
241 options
242 .subject_holder_relationship
243 .as_ref()
244 .map(|(holder, relationship)| {
245 JwtCredentialValidatorUtils::check_subject_holder_relationship(credential, holder, *relationship)
246 })
247 .unwrap_or(Ok(()))
248 });
249
250 let validation_units_iter = issuance_date_validation
251 .chain(expiry_date_validation)
252 .chain(structure_validation)
253 .chain(subject_holder_validation);
254
255 #[cfg(feature = "revocation-bitmap")]
256 let validation_units_iter = {
257 let revocation_validation =
258 std::iter::once_with(|| JwtCredentialValidatorUtils::check_status(credential, issuers, options.status));
259 validation_units_iter.chain(revocation_validation)
260 };
261
262 let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err());
263 let validation_errors: Vec<JwtValidationError> = match fail_fast {
264 FailFast::FirstError => validation_units_error_iter.take(1).collect(),
265 FailFast::AllErrors => validation_units_error_iter.collect(),
266 };
267
268 if validation_errors.is_empty() {
269 Ok(())
270 } else {
271 Err(CompoundCredentialValidationError { validation_errors })
272 }
273 }
274
275 pub(crate) fn parse_jwk<'a, 'i, DOC>(
276 jws: &JwsValidationItem<'a>,
277 trusted_issuers: &'i [DOC],
278 options: &JwsVerificationOptions,
279 ) -> Result<(&'a Jwk, DIDUrl), JwtValidationError>
280 where
281 DOC: AsRef<CoreDocument>,
282 'i: 'a,
283 {
284 let nonce: Option<&str> = options.nonce.as_deref();
285 if jws.nonce() != nonce {
287 return Err(JwtValidationError::JwsDecodingError(
288 identity_verification::jose::error::Error::InvalidParam("invalid nonce value"),
289 ));
290 }
291
292 let method_id: DIDUrl =
295 match &options.method_id {
296 Some(method_id) => method_id.clone(),
297 None => {
298 let kid: &str = jws.protected_header().and_then(|header| header.kid()).ok_or(
299 JwtValidationError::MethodDataLookupError {
300 source: None,
301 message: "could not extract kid from protected header",
302 signer_ctx: SignerContext::Issuer,
303 },
304 )?;
305
306 DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError {
308 source: Some(err.into()),
309 message: "could not parse kid as a DID Url",
310 signer_ctx: SignerContext::Issuer,
311 })?
312 }
313 };
314
315 let issuer: &CoreDocument = trusted_issuers
317 .iter()
318 .map(AsRef::as_ref)
319 .find(|issuer_doc| <CoreDocument>::id(issuer_doc) == method_id.did())
320 .ok_or(JwtValidationError::DocumentMismatch(SignerContext::Issuer))?;
321
322 issuer
324 .resolve_method(&method_id, options.method_scope)
325 .and_then(|method| method.data().public_key_jwk())
326 .ok_or_else(|| JwtValidationError::MethodDataLookupError {
327 source: None,
328 message: "could not extract JWK from a method identified by kid",
329 signer_ctx: SignerContext::Issuer,
330 })
331 .map(move |jwk| (jwk, method_id))
332 }
333
334 fn verify_signature_with_verifier<DOC, S, T>(
336 signature_verifier: &S,
337 credential: &Jwt,
338 trusted_issuers: &[DOC],
339 options: &JwsVerificationOptions,
340 ) -> Result<DecodedJwtCredential<T>, JwtValidationError>
341 where
342 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
343 DOC: AsRef<CoreDocument>,
344 S: JwsVerifier,
345 {
346 let decoded: JwsValidationItem<'_> = Self::decode(credential.as_str())?;
352 let (public_key, method_id) = Self::parse_jwk(&decoded, trusted_issuers, options)?;
353
354 let credential_token = Self::verify_decoded_signature(decoded, public_key, signature_verifier)?;
355
356 let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?;
359 if &issuer_id != method_id.did() {
360 return Err(JwtValidationError::IdentifierMismatch {
361 signer_ctx: SignerContext::Issuer,
362 });
363 };
364 Ok(credential_token)
365 }
366
367 fn verify_signature_with_verifier_v2<DOC, S, T>(
368 signature_verifier: &S,
369 credential: &JwtVcV2,
370 trusted_issuers: &[DOC],
371 options: &JwsVerificationOptions,
372 ) -> Result<DecodedJwtCredentialV2<T>, JwtValidationError>
373 where
374 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
375 DOC: AsRef<CoreDocument>,
376 S: JwsVerifier,
377 {
378 let decoded: JwsValidationItem<'_> = Self::decode(credential.as_str())?;
384 let (public_key, method_id) = Self::parse_jwk(&decoded, trusted_issuers, options)?;
385
386 let credential_token = Self::verify_decoded_signature_v2(decoded, public_key, signature_verifier)?;
387
388 let issuer_id: CoreDID = CoreDID::from_str(credential_token.credential.issuer.url().as_str()).map_err(|err| {
391 JwtValidationError::SignerUrl {
392 signer_ctx: SignerContext::Issuer,
393 source: err.into(),
394 }
395 })?;
396 if &issuer_id != method_id.did() {
397 return Err(JwtValidationError::IdentifierMismatch {
398 signer_ctx: SignerContext::Issuer,
399 });
400 };
401 Ok(credential_token)
402 }
403
404 pub(crate) fn decode(credential_jws: &str) -> Result<JwsValidationItem<'_>, JwtValidationError> {
406 let decoder: Decoder = Decoder::new();
407
408 decoder
409 .decode_compact_serialization(credential_jws.as_bytes(), None)
410 .map_err(JwtValidationError::JwsDecodingError)
411 }
412
413 pub(crate) fn verify_signature_raw<'a, S: JwsVerifier>(
414 decoded: JwsValidationItem<'a>,
415 public_key: &Jwk,
416 signature_verifier: &S,
417 ) -> Result<DecodedJws<'a>, JwtValidationError> {
418 decoded
419 .verify(signature_verifier, public_key)
420 .map_err(|err| JwtValidationError::Signature {
421 source: err,
422 signer_ctx: SignerContext::Issuer,
423 })
424 }
425
426 pub(crate) fn verify_decoded_signature<S: JwsVerifier, T>(
428 decoded: JwsValidationItem<'_>,
429 public_key: &Jwk,
430 signature_verifier: &S,
431 ) -> Result<DecodedJwtCredential<T>, JwtValidationError>
432 where
433 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
434 {
435 let DecodedJws { protected, claims, .. } = Self::verify_signature_raw(decoded, public_key, signature_verifier)?;
437
438 let credential_claims: CredentialJwtClaims<'_, T> =
439 CredentialJwtClaims::from_json_slice(&claims).map_err(|err| {
440 JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
441 })?;
442
443 let custom_claims = credential_claims.custom.clone();
444
445 let credential: Credential<T> = credential_claims
447 .try_into_credential()
448 .map_err(JwtValidationError::CredentialStructure)?;
449
450 Ok(DecodedJwtCredential {
451 credential,
452 header: Box::new(protected),
453 custom_claims,
454 })
455 }
456
457 pub(crate) fn verify_decoded_signature_v2<S: JwsVerifier, T>(
458 decoded: JwsValidationItem<'_>,
459 public_key: &Jwk,
460 signature_verifier: &S,
461 ) -> Result<DecodedJwtCredentialV2<T>, JwtValidationError>
462 where
463 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
464 {
465 let DecodedJws { protected, claims, .. } = Self::verify_signature_raw(decoded, public_key, signature_verifier)?;
467
468 let credential = serde_json::from_slice(&claims)
469 .map_err(|e| JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(e.into())))?;
470
471 Ok(DecodedJwtCredentialV2 {
472 credential,
473 header: Box::new(protected),
474 })
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use crate::credential::Subject;
481 use crate::validator::SubjectHolderRelationship;
482 use identity_core::common::Duration;
483 use identity_core::common::Url;
484 use once_cell::sync::Lazy;
485
486 use super::*;
488 use identity_core::common::Object;
489 use identity_core::common::Timestamp;
490 use proptest::proptest;
491 const LAST_RFC3339_COMPATIBLE_UNIX_TIMESTAMP: i64 = 253402300799; const FIRST_RFC3999_COMPATIBLE_UNIX_TIMESTAMP: i64 = -62167219200; const SIMPLE_CREDENTIAL_JSON: &str = r#"{
495 "@context": [
496 "https://www.w3.org/2018/credentials/v1",
497 "https://www.w3.org/2018/credentials/examples/v1"
498 ],
499 "id": "http://example.edu/credentials/3732",
500 "type": ["VerifiableCredential", "UniversityDegreeCredential"],
501 "issuer": "https://example.edu/issuers/14",
502 "issuanceDate": "2010-01-01T19:23:24Z",
503 "expirationDate": "2020-01-01T19:23:24Z",
504 "credentialSubject": {
505 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
506 "degree": {
507 "type": "BachelorDegree",
508 "name": "Bachelor of Science in Mechanical Engineering"
509 }
510 }
511 }"#;
512
513 static SIMPLE_CREDENTIAL: Lazy<Credential> =
515 Lazy::new(|| Credential::<Object>::from_json(SIMPLE_CREDENTIAL_JSON).unwrap());
516
517 #[test]
518 fn issued_on_or_before() {
519 assert!(JwtCredentialValidatorUtils::check_issued_on_or_before(
520 &*SIMPLE_CREDENTIAL,
521 SIMPLE_CREDENTIAL
522 .issuance_date
523 .checked_sub(Duration::minutes(1))
524 .unwrap()
525 )
526 .is_err());
527
528 assert!(JwtCredentialValidatorUtils::check_issued_on_or_before(
530 &*SIMPLE_CREDENTIAL,
531 SIMPLE_CREDENTIAL
532 .issuance_date
533 .checked_add(Duration::minutes(1))
534 .unwrap()
535 )
536 .is_ok());
537 }
538
539 #[test]
540 fn check_subject_holder_relationship() {
541 let mut credential: Credential = SIMPLE_CREDENTIAL.clone();
542
543 let actual_holder_url = credential.credential_subject.first().unwrap().id.clone().unwrap();
545 assert_eq!(credential.credential_subject.len(), 1);
546 credential.non_transferable = Some(true);
547
548 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
550 &credential,
551 &actual_holder_url,
552 SubjectHolderRelationship::AlwaysSubject
553 )
554 .is_ok());
555
556 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
557 &credential,
558 &actual_holder_url,
559 SubjectHolderRelationship::SubjectOnNonTransferable
560 )
561 .is_ok());
562
563 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
564 &credential,
565 &actual_holder_url,
566 SubjectHolderRelationship::Any
567 )
568 .is_ok());
569
570 let issuer_url = Url::parse("did:core:0x1234567890").unwrap();
572 assert!(actual_holder_url != issuer_url);
573
574 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
575 &credential,
576 &issuer_url,
577 SubjectHolderRelationship::AlwaysSubject
578 )
579 .is_err());
580
581 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
582 &credential,
583 &issuer_url,
584 SubjectHolderRelationship::SubjectOnNonTransferable
585 )
586 .is_err());
587
588 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
589 &credential,
590 &issuer_url,
591 SubjectHolderRelationship::Any
592 )
593 .is_ok());
594
595 let mut credential_transferable = credential.clone();
596
597 credential_transferable.non_transferable = Some(false);
598
599 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
600 &credential_transferable,
601 &issuer_url,
602 SubjectHolderRelationship::SubjectOnNonTransferable
603 )
604 .is_ok());
605
606 credential_transferable.non_transferable = None;
607
608 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
609 &credential_transferable,
610 &issuer_url,
611 SubjectHolderRelationship::SubjectOnNonTransferable
612 )
613 .is_ok());
614
615 let mut credential_duplicated_holder = credential;
617 credential_duplicated_holder
618 .credential_subject
619 .push(Subject::with_id(actual_holder_url));
620
621 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
622 &credential_duplicated_holder,
623 &issuer_url,
624 SubjectHolderRelationship::AlwaysSubject
625 )
626 .is_err());
627
628 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
629 &credential_duplicated_holder,
630 &issuer_url,
631 SubjectHolderRelationship::SubjectOnNonTransferable
632 )
633 .is_err());
634
635 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
636 &credential_duplicated_holder,
637 &issuer_url,
638 SubjectHolderRelationship::Any
639 )
640 .is_ok());
641 }
642
643 #[test]
644 fn simple_expires_on_or_after_with_expiration_date() {
645 let later_than_expiration_date = SIMPLE_CREDENTIAL
646 .expiration_date
647 .unwrap()
648 .checked_add(Duration::minutes(1))
649 .unwrap();
650 assert!(
651 JwtCredentialValidatorUtils::check_expires_on_or_after(&*SIMPLE_CREDENTIAL, later_than_expiration_date).is_err()
652 );
653 let earlier_date = Timestamp::parse("2019-12-27T11:35:30Z").unwrap();
655 assert!(JwtCredentialValidatorUtils::check_expires_on_or_after(&*SIMPLE_CREDENTIAL, earlier_date).is_ok());
656 }
657
658 proptest! {
660 #[test]
661 fn property_based_expires_after_with_expiration_date(seconds in 0..1_000_000_000_u32) {
662 let after_expiration_date = SIMPLE_CREDENTIAL.expiration_date.unwrap().checked_add(Duration::seconds(seconds)).unwrap();
663 let before_expiration_date = SIMPLE_CREDENTIAL.expiration_date.unwrap().checked_sub(Duration::seconds(seconds)).unwrap();
664 assert!(JwtCredentialValidatorUtils::check_expires_on_or_after(&*SIMPLE_CREDENTIAL, after_expiration_date).is_err());
665 assert!(JwtCredentialValidatorUtils::check_expires_on_or_after(&*SIMPLE_CREDENTIAL, before_expiration_date).is_ok());
666 }
667 }
668
669 proptest! {
670 #[test]
671 fn property_based_expires_after_no_expiration_date(seconds in FIRST_RFC3999_COMPATIBLE_UNIX_TIMESTAMP..LAST_RFC3339_COMPATIBLE_UNIX_TIMESTAMP) {
672 let mut credential = SIMPLE_CREDENTIAL.clone();
673 credential.expiration_date = None;
674 assert!(JwtCredentialValidatorUtils::check_expires_on_or_after(&credential, Timestamp::from_unix(seconds).unwrap()).is_ok());
676 }
677 }
678
679 proptest! {
680 #[test]
681 fn property_based_issued_before(seconds in 0 ..1_000_000_000_u32) {
682
683 let earlier_than_issuance_date = SIMPLE_CREDENTIAL.issuance_date.checked_sub(Duration::seconds(seconds)).unwrap();
684 let later_than_issuance_date = SIMPLE_CREDENTIAL.issuance_date.checked_add(Duration::seconds(seconds)).unwrap();
685 assert!(JwtCredentialValidatorUtils::check_issued_on_or_before(&*SIMPLE_CREDENTIAL, earlier_than_issuance_date).is_err());
686 assert!(JwtCredentialValidatorUtils::check_issued_on_or_before(&*SIMPLE_CREDENTIAL, later_than_issuance_date).is_ok());
687 }
688 }
689}