identity_credential/validator/jwt_credential_validation/
jwt_credential_validator.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use 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/// A type for decoding and validating [`Credential`]s.
32#[non_exhaustive]
33pub struct JwtCredentialValidator<V: JwsVerifier>(V);
34
35impl<V: JwsVerifier> JwtCredentialValidator<V> {
36  /// Create a new [`JwtCredentialValidator`] that delegates cryptographic signature verification to the given
37  /// `signature_verifier`.
38  pub fn with_signature_verifier(signature_verifier: V) -> Self {
39    Self(signature_verifier)
40  }
41
42  /// Decodes and validates a [`Credential`] issued as a JWT. A [`DecodedJwtCredential`] is returned upon success.
43  ///
44  /// The following properties are validated according to `options`:
45  /// - the issuer's signature on the JWS,
46  /// - the expiration date,
47  /// - the issuance date,
48  /// - the semantic structure.
49  ///
50  /// # Warning
51  /// The lack of an error returned from this method is in of itself not enough to conclude that the credential can be
52  /// trusted. This section contains more information on additional checks that should be carried out before and after
53  /// calling this method.
54  ///
55  /// ## The state of the issuer's DID Document
56  /// The caller must ensure that `issuer` represents an up-to-date DID Document.
57  ///
58  /// ## Properties that are not validated
59  ///  There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as:
60  /// `proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**.
61  /// These should be manually checked after validation, according to your requirements.
62  ///
63  /// # Errors
64  /// An error is returned whenever a validated condition is not satisfied.
65  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  /// Decodes and validates a [CredentialV2](crate::credential::CredentialV2) issued as a JWT.
97  /// A [`DecodedJwtCredentialV2`] is returned upon success.
98  ///
99  /// The following properties are validated according to `options`:
100  /// - the issuer's signature on the JWS,
101  /// - the date and time the credential becomes valid,
102  /// - the date and time the credential ceases to be valid,
103  /// - the semantic structure.
104  ///
105  /// # Warning
106  /// The lack of an error returned from this method is in of itself not enough to conclude that the credential can be
107  /// trusted. This section contains more information on additional checks that should be carried out before and after
108  /// calling this method.
109  ///
110  /// ## The state of the issuer's DID Document
111  /// The caller must ensure that `issuer` represents an up-to-date DID Document.
112  ///
113  /// ## Properties that are not validated
114  ///  There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as:
115  /// `proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**.
116  /// These should be manually checked after validation, according to your requirements.
117  ///
118  /// # Errors
119  /// An error is returned whenever a validated condition is not satisfied.
120  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  /// Decode and verify the JWS signature of a [`Credential`] issued as a JWT using the DID Document of a trusted
152  /// issuer.
153  ///
154  /// A [`DecodedJwtCredential`] is returned upon success.
155  ///
156  /// # Warning
157  /// The caller must ensure that the DID Documents of the trusted issuers are up-to-date.
158  ///
159  /// ## Proofs
160  ///  Only the JWS signature is verified. If the [`Credential`] contains a `proof` property this will not be verified
161  /// by this method.
162  ///
163  /// # Errors
164  /// This method immediately returns an error if
165  /// the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt
166  /// to verify the credential's signature will be made and an error is returned upon failure.
167  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  /// Decode and verify the JWS signature of a [Credential](crate::credential::credential_v2::Credential) issued as a
181  /// JWT using the DID Document of a trusted issuer.
182  ///
183  /// A [`DecodedJwtCredentialV2`] is returned upon success.
184  ///
185  /// # Warning
186  /// The caller must ensure that the DID Documents of the trusted issuers are up-to-date.
187  ///
188  /// ## Proofs
189  ///  Only the JWS signature is verified. If the [Credential](crate::credential::credential_v2::Credential) contains a
190  /// `proof` property this will not be verified by this method.
191  ///
192  /// # Errors
193  /// This method immediately returns an error if
194  /// the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt
195  /// to verify the credential's signature will be made and an error is returned upon failure.
196  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  // This method takes a slice of issuer's instead of a single issuer in order to better accommodate presentation
210  // validation. It also validates the relationship between a holder and the credential subjects when
211  // `relationship_criterion` is Some.
212  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    // Run all single concern Credential validations in turn and fail immediately if `fail_fast` is true.
223
224    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    // Validate the nonce
286    if jws.nonce() != nonce {
287      return Err(JwtValidationError::JwsDecodingError(
288        identity_verification::jose::error::Error::InvalidParam("invalid nonce value"),
289      ));
290    }
291
292    // If no method_url is set, parse the `kid` to a DID Url which should be the identifier
293    // of a verification method in a trusted issuer's DID document.
294    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          // Convert kid to DIDUrl
307          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    // locate the corresponding issuer
316    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    // Obtain the public key from the issuer's DID document
323    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  /// Stateless version of [`Self::verify_signature`]
335  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    // Note the below steps are necessary because `CoreDocument::verify_jws` decodes the JWS and then searches for a
347    // method with a fragment (or full DID Url) matching `kid` in the given document. We do not want to carry out
348    // that process for potentially every document in `trusted_issuers`.
349
350    // Start decoding the credential
351    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    // Check that the DID component of the parsed `kid` does indeed correspond to the issuer in the credential before
357    // returning.
358    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    // Note the below steps are necessary because `CoreDocument::verify_jws` decodes the JWS and then searches for a
379    // method with a fragment (or full DID Url) matching `kid` in the given document. We do not want to carry out
380    // that process for potentially every document in `trusted_issuers`.
381
382    // Start decoding the credential
383    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    // Check that the DID component of the parsed `kid` does indeed correspond to the issuer in the credential before
389    // returning.
390    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  /// Decode the credential into a [`JwsValidationItem`].
405  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  /// Verify the signature using the given `public_key` and `signature_verifier`.
427  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    // Verify the JWS signature and obtain the decoded token containing the protected header and raw claims
436    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    // Construct the credential token containing the credential and the protected header.
446    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    // Verify the JWS signature and obtain the decoded token containing the protected header and raw claims
466    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  // All tests here are essentially adaptations of the old JwtCredentialValidator tests.
487  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; // 9999-12-31T23:59:59Z
492  const FIRST_RFC3999_COMPATIBLE_UNIX_TIMESTAMP: i64 = -62167219200; // 0000-01-01T00:00:00Z
493
494  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  /// A simple credential shared by some of the tests in this module
514  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    // and now with a later timestamp
529    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    // first ensure that holder_url is the subject and set the nonTransferable property
544    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    // checking with holder = subject passes for all defined subject holder relationships:
549    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    // check with a holder different from the subject of the credential:
571    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    // two subjects (even when they are both the holder) should fail for all defined values except "Any"
616    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    // and now with an earlier date
654    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  // test with a few timestamps that should be RFC3339 compatible
659  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      // expires after whatever the timestamp may be because the expires_after field is None.
675      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}