identity_credential/validator/jwt_credential_validation/
jwt_credential_validator.rs1use identity_core::convert::FromJson;
5use identity_did::CoreDID;
6use identity_did::DIDUrl;
7use identity_document::document::CoreDocument;
8use identity_document::verifiable::JwsVerificationOptions;
9use identity_verification::jwk::Jwk;
10use identity_verification::jws::DecodedJws;
11use identity_verification::jws::Decoder;
12use identity_verification::jws::JwsValidationItem;
13use identity_verification::jws::JwsVerifier;
14
15use super::CompoundCredentialValidationError;
16use super::DecodedJwtCredential;
17use super::JwtCredentialValidationOptions;
18use super::JwtCredentialValidatorUtils;
19use super::JwtValidationError;
20use super::SignerContext;
21use crate::credential::Credential;
22use crate::credential::CredentialJwtClaims;
23use crate::credential::Jwt;
24use crate::validator::FailFast;
25
26#[non_exhaustive]
28pub struct JwtCredentialValidator<V: JwsVerifier>(V);
29
30impl<V: JwsVerifier> JwtCredentialValidator<V> {
31 pub fn with_signature_verifier(signature_verifier: V) -> Self {
34 Self(signature_verifier)
35 }
36
37 pub fn validate<DOC, T>(
61 &self,
62 credential_jwt: &Jwt,
63 issuer: &DOC,
64 options: &JwtCredentialValidationOptions,
65 fail_fast: FailFast,
66 ) -> Result<DecodedJwtCredential<T>, CompoundCredentialValidationError>
67 where
68 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
69 DOC: AsRef<CoreDocument>,
70 {
71 let credential_token = self
72 .verify_signature(
73 credential_jwt,
74 std::slice::from_ref(issuer.as_ref()),
75 &options.verification_options,
76 )
77 .map_err(|err| CompoundCredentialValidationError {
78 validation_errors: [err].into(),
79 })?;
80
81 Self::validate_decoded_credential::<CoreDocument, T>(
82 credential_token,
83 std::slice::from_ref(issuer.as_ref()),
84 options,
85 fail_fast,
86 )
87 }
88
89 pub fn verify_signature<DOC, T>(
106 &self,
107 credential: &Jwt,
108 trusted_issuers: &[DOC],
109 options: &JwsVerificationOptions,
110 ) -> Result<DecodedJwtCredential<T>, JwtValidationError>
111 where
112 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
113 DOC: AsRef<CoreDocument>,
114 {
115 Self::verify_signature_with_verifier(&self.0, credential, trusted_issuers, options)
116 }
117
118 pub(crate) fn validate_decoded_credential<DOC, T>(
122 credential_token: DecodedJwtCredential<T>,
123 issuers: &[DOC],
124 options: &JwtCredentialValidationOptions,
125 fail_fast: FailFast,
126 ) -> Result<DecodedJwtCredential<T>, CompoundCredentialValidationError>
127 where
128 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
129 DOC: AsRef<CoreDocument>,
130 {
131 let credential: &Credential<T> = &credential_token.credential;
132 let expiry_date_validation = std::iter::once_with(|| {
135 JwtCredentialValidatorUtils::check_expires_on_or_after(
136 &credential_token.credential,
137 options.earliest_expiry_date.unwrap_or_default(),
138 )
139 });
140
141 let issuance_date_validation = std::iter::once_with(|| {
142 JwtCredentialValidatorUtils::check_issued_on_or_before(
143 credential,
144 options.latest_issuance_date.unwrap_or_default(),
145 )
146 });
147
148 let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential));
149
150 let subject_holder_validation = std::iter::once_with(|| {
151 options
152 .subject_holder_relationship
153 .as_ref()
154 .map(|(holder, relationship)| {
155 JwtCredentialValidatorUtils::check_subject_holder_relationship(credential, holder, *relationship)
156 })
157 .unwrap_or(Ok(()))
158 });
159
160 let validation_units_iter = issuance_date_validation
161 .chain(expiry_date_validation)
162 .chain(structure_validation)
163 .chain(subject_holder_validation);
164
165 #[cfg(feature = "revocation-bitmap")]
166 let validation_units_iter = {
167 let revocation_validation =
168 std::iter::once_with(|| JwtCredentialValidatorUtils::check_status(credential, issuers, options.status));
169 validation_units_iter.chain(revocation_validation)
170 };
171
172 let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err());
173 let validation_errors: Vec<JwtValidationError> = match fail_fast {
174 FailFast::FirstError => validation_units_error_iter.take(1).collect(),
175 FailFast::AllErrors => validation_units_error_iter.collect(),
176 };
177
178 if validation_errors.is_empty() {
179 Ok(credential_token)
180 } else {
181 Err(CompoundCredentialValidationError { validation_errors })
182 }
183 }
184
185 pub(crate) fn parse_jwk<'a, 'i, DOC>(
186 jws: &JwsValidationItem<'a>,
187 trusted_issuers: &'i [DOC],
188 options: &JwsVerificationOptions,
189 ) -> Result<(&'a Jwk, DIDUrl), JwtValidationError>
190 where
191 DOC: AsRef<CoreDocument>,
192 'i: 'a,
193 {
194 let nonce: Option<&str> = options.nonce.as_deref();
195 if jws.nonce() != nonce {
197 return Err(JwtValidationError::JwsDecodingError(
198 identity_verification::jose::error::Error::InvalidParam("invalid nonce value"),
199 ));
200 }
201
202 let method_id: DIDUrl =
205 match &options.method_id {
206 Some(method_id) => method_id.clone(),
207 None => {
208 let kid: &str = jws.protected_header().and_then(|header| header.kid()).ok_or(
209 JwtValidationError::MethodDataLookupError {
210 source: None,
211 message: "could not extract kid from protected header",
212 signer_ctx: SignerContext::Issuer,
213 },
214 )?;
215
216 DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError {
218 source: Some(err.into()),
219 message: "could not parse kid as a DID Url",
220 signer_ctx: SignerContext::Issuer,
221 })?
222 }
223 };
224
225 let issuer: &CoreDocument = trusted_issuers
227 .iter()
228 .map(AsRef::as_ref)
229 .find(|issuer_doc| <CoreDocument>::id(issuer_doc) == method_id.did())
230 .ok_or(JwtValidationError::DocumentMismatch(SignerContext::Issuer))?;
231
232 issuer
234 .resolve_method(&method_id, options.method_scope)
235 .and_then(|method| method.data().public_key_jwk())
236 .ok_or_else(|| JwtValidationError::MethodDataLookupError {
237 source: None,
238 message: "could not extract JWK from a method identified by kid",
239 signer_ctx: SignerContext::Issuer,
240 })
241 .map(move |jwk| (jwk, method_id))
242 }
243
244 fn verify_signature_with_verifier<DOC, S, T>(
246 signature_verifier: &S,
247 credential: &Jwt,
248 trusted_issuers: &[DOC],
249 options: &JwsVerificationOptions,
250 ) -> Result<DecodedJwtCredential<T>, JwtValidationError>
251 where
252 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
253 DOC: AsRef<CoreDocument>,
254 S: JwsVerifier,
255 {
256 let decoded: JwsValidationItem<'_> = Self::decode(credential.as_str())?;
262 let (public_key, method_id) = Self::parse_jwk(&decoded, trusted_issuers, options)?;
263
264 let credential_token = Self::verify_decoded_signature(decoded, public_key, signature_verifier)?;
265
266 let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?;
269 if &issuer_id != method_id.did() {
270 return Err(JwtValidationError::IdentifierMismatch {
271 signer_ctx: SignerContext::Issuer,
272 });
273 };
274 Ok(credential_token)
275 }
276
277 pub(crate) fn decode(credential_jws: &str) -> Result<JwsValidationItem<'_>, JwtValidationError> {
279 let decoder: Decoder = Decoder::new();
280
281 decoder
282 .decode_compact_serialization(credential_jws.as_bytes(), None)
283 .map_err(JwtValidationError::JwsDecodingError)
284 }
285
286 pub(crate) fn verify_signature_raw<'a, S: JwsVerifier>(
287 decoded: JwsValidationItem<'a>,
288 public_key: &Jwk,
289 signature_verifier: &S,
290 ) -> Result<DecodedJws<'a>, JwtValidationError> {
291 decoded
292 .verify(signature_verifier, public_key)
293 .map_err(|err| JwtValidationError::Signature {
294 source: err,
295 signer_ctx: SignerContext::Issuer,
296 })
297 }
298
299 pub(crate) fn verify_decoded_signature<S: JwsVerifier, T>(
301 decoded: JwsValidationItem<'_>,
302 public_key: &Jwk,
303 signature_verifier: &S,
304 ) -> Result<DecodedJwtCredential<T>, JwtValidationError>
305 where
306 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
307 {
308 let DecodedJws { protected, claims, .. } = Self::verify_signature_raw(decoded, public_key, signature_verifier)?;
310
311 let credential_claims: CredentialJwtClaims<'_, T> =
312 CredentialJwtClaims::from_json_slice(&claims).map_err(|err| {
313 JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
314 })?;
315
316 let custom_claims = credential_claims.custom.clone();
317
318 let credential: Credential<T> = credential_claims
320 .try_into_credential()
321 .map_err(JwtValidationError::CredentialStructure)?;
322
323 Ok(DecodedJwtCredential {
324 credential,
325 header: Box::new(protected),
326 custom_claims,
327 })
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use crate::credential::Subject;
334 use crate::validator::SubjectHolderRelationship;
335 use identity_core::common::Duration;
336 use identity_core::common::Url;
337 use once_cell::sync::Lazy;
338
339 use super::*;
341 use identity_core::common::Object;
342 use identity_core::common::Timestamp;
343 use proptest::proptest;
344 const LAST_RFC3339_COMPATIBLE_UNIX_TIMESTAMP: i64 = 253402300799; const FIRST_RFC3999_COMPATIBLE_UNIX_TIMESTAMP: i64 = -62167219200; const SIMPLE_CREDENTIAL_JSON: &str = r#"{
348 "@context": [
349 "https://www.w3.org/2018/credentials/v1",
350 "https://www.w3.org/2018/credentials/examples/v1"
351 ],
352 "id": "http://example.edu/credentials/3732",
353 "type": ["VerifiableCredential", "UniversityDegreeCredential"],
354 "issuer": "https://example.edu/issuers/14",
355 "issuanceDate": "2010-01-01T19:23:24Z",
356 "expirationDate": "2020-01-01T19:23:24Z",
357 "credentialSubject": {
358 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
359 "degree": {
360 "type": "BachelorDegree",
361 "name": "Bachelor of Science in Mechanical Engineering"
362 }
363 }
364 }"#;
365
366 static SIMPLE_CREDENTIAL: Lazy<Credential> =
368 Lazy::new(|| Credential::<Object>::from_json(SIMPLE_CREDENTIAL_JSON).unwrap());
369
370 #[test]
371 fn issued_on_or_before() {
372 assert!(JwtCredentialValidatorUtils::check_issued_on_or_before(
373 &SIMPLE_CREDENTIAL,
374 SIMPLE_CREDENTIAL
375 .issuance_date
376 .checked_sub(Duration::minutes(1))
377 .unwrap()
378 )
379 .is_err());
380
381 assert!(JwtCredentialValidatorUtils::check_issued_on_or_before(
383 &SIMPLE_CREDENTIAL,
384 SIMPLE_CREDENTIAL
385 .issuance_date
386 .checked_add(Duration::minutes(1))
387 .unwrap()
388 )
389 .is_ok());
390 }
391
392 #[test]
393 fn check_subject_holder_relationship() {
394 let mut credential: Credential = SIMPLE_CREDENTIAL.clone();
395
396 let actual_holder_url = credential.credential_subject.first().unwrap().id.clone().unwrap();
398 assert_eq!(credential.credential_subject.len(), 1);
399 credential.non_transferable = Some(true);
400
401 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
403 &credential,
404 &actual_holder_url,
405 SubjectHolderRelationship::AlwaysSubject
406 )
407 .is_ok());
408
409 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
410 &credential,
411 &actual_holder_url,
412 SubjectHolderRelationship::SubjectOnNonTransferable
413 )
414 .is_ok());
415
416 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
417 &credential,
418 &actual_holder_url,
419 SubjectHolderRelationship::Any
420 )
421 .is_ok());
422
423 let issuer_url = Url::parse("did:core:0x1234567890").unwrap();
425 assert!(actual_holder_url != issuer_url);
426
427 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
428 &credential,
429 &issuer_url,
430 SubjectHolderRelationship::AlwaysSubject
431 )
432 .is_err());
433
434 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
435 &credential,
436 &issuer_url,
437 SubjectHolderRelationship::SubjectOnNonTransferable
438 )
439 .is_err());
440
441 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
442 &credential,
443 &issuer_url,
444 SubjectHolderRelationship::Any
445 )
446 .is_ok());
447
448 let mut credential_transferable = credential.clone();
449
450 credential_transferable.non_transferable = Some(false);
451
452 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
453 &credential_transferable,
454 &issuer_url,
455 SubjectHolderRelationship::SubjectOnNonTransferable
456 )
457 .is_ok());
458
459 credential_transferable.non_transferable = None;
460
461 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
462 &credential_transferable,
463 &issuer_url,
464 SubjectHolderRelationship::SubjectOnNonTransferable
465 )
466 .is_ok());
467
468 let mut credential_duplicated_holder = credential;
470 credential_duplicated_holder
471 .credential_subject
472 .push(Subject::with_id(actual_holder_url));
473
474 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
475 &credential_duplicated_holder,
476 &issuer_url,
477 SubjectHolderRelationship::AlwaysSubject
478 )
479 .is_err());
480
481 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
482 &credential_duplicated_holder,
483 &issuer_url,
484 SubjectHolderRelationship::SubjectOnNonTransferable
485 )
486 .is_err());
487
488 assert!(JwtCredentialValidatorUtils::check_subject_holder_relationship(
489 &credential_duplicated_holder,
490 &issuer_url,
491 SubjectHolderRelationship::Any
492 )
493 .is_ok());
494 }
495
496 #[test]
497 fn simple_expires_on_or_after_with_expiration_date() {
498 let later_than_expiration_date = SIMPLE_CREDENTIAL
499 .expiration_date
500 .unwrap()
501 .checked_add(Duration::minutes(1))
502 .unwrap();
503 assert!(
504 JwtCredentialValidatorUtils::check_expires_on_or_after(&SIMPLE_CREDENTIAL, later_than_expiration_date).is_err()
505 );
506 let earlier_date = Timestamp::parse("2019-12-27T11:35:30Z").unwrap();
508 assert!(JwtCredentialValidatorUtils::check_expires_on_or_after(&SIMPLE_CREDENTIAL, earlier_date).is_ok());
509 }
510
511 proptest! {
513 #[test]
514 fn property_based_expires_after_with_expiration_date(seconds in 0..1_000_000_000_u32) {
515 let after_expiration_date = SIMPLE_CREDENTIAL.expiration_date.unwrap().checked_add(Duration::seconds(seconds)).unwrap();
516 let before_expiration_date = SIMPLE_CREDENTIAL.expiration_date.unwrap().checked_sub(Duration::seconds(seconds)).unwrap();
517 assert!(JwtCredentialValidatorUtils::check_expires_on_or_after(&SIMPLE_CREDENTIAL, after_expiration_date).is_err());
518 assert!(JwtCredentialValidatorUtils::check_expires_on_or_after(&SIMPLE_CREDENTIAL, before_expiration_date).is_ok());
519 }
520 }
521
522 proptest! {
523 #[test]
524 fn property_based_expires_after_no_expiration_date(seconds in FIRST_RFC3999_COMPATIBLE_UNIX_TIMESTAMP..LAST_RFC3339_COMPATIBLE_UNIX_TIMESTAMP) {
525 let mut credential = SIMPLE_CREDENTIAL.clone();
526 credential.expiration_date = None;
527 assert!(JwtCredentialValidatorUtils::check_expires_on_or_after(&credential, Timestamp::from_unix(seconds).unwrap()).is_ok());
529 }
530 }
531
532 proptest! {
533 #[test]
534 fn property_based_issued_before(seconds in 0 ..1_000_000_000_u32) {
535
536 let earlier_than_issuance_date = SIMPLE_CREDENTIAL.issuance_date.checked_sub(Duration::seconds(seconds)).unwrap();
537 let later_than_issuance_date = SIMPLE_CREDENTIAL.issuance_date.checked_add(Duration::seconds(seconds)).unwrap();
538 assert!(JwtCredentialValidatorUtils::check_issued_on_or_before(&SIMPLE_CREDENTIAL, earlier_than_issuance_date).is_err());
539 assert!(JwtCredentialValidatorUtils::check_issued_on_or_before(&SIMPLE_CREDENTIAL, later_than_issuance_date).is_ok());
540 }
541 }
542}