1use crate::credential::Credential;
5use crate::credential::Jwt;
6use crate::domain_linkage::DomainLinkageConfiguration;
7use crate::domain_linkage::DomainLinkageValidationError;
8use crate::domain_linkage::DomainLinkageValidationErrorCause;
9use crate::validator::FailFast;
10use crate::validator::JwtCredentialValidationOptions;
11use crate::validator::JwtCredentialValidator;
12use crate::validator::JwtCredentialValidatorUtils;
13use identity_core::common::OneOrMany;
14use identity_core::common::Url;
15use identity_did::CoreDID;
16use identity_document::document::CoreDocument;
17use identity_verification::jws::JwsVerifier;
18
19use crate::validator::DecodedJwtCredential;
20
21use super::DomainLinkageValidationErrorList;
22use super::DomainLinkageValidationResult;
23use crate::utils::url_only_includes_origin;
24pub struct JwtDomainLinkageValidator<V: JwsVerifier> {
26 validator: JwtCredentialValidator<V>,
27}
28
29impl<V: JwsVerifier> JwtDomainLinkageValidator<V> {
30 pub fn with_signature_verifier(signature_verifier: V) -> Self {
33 Self {
34 validator: JwtCredentialValidator::with_signature_verifier(signature_verifier),
35 }
36 }
37
38 pub fn validate_linkage<DOC: AsRef<CoreDocument>>(
56 &self,
57 issuer: &DOC,
58 configuration: &DomainLinkageConfiguration,
59 domain: &Url,
60 validation_options: &JwtCredentialValidationOptions,
61 ) -> DomainLinkageValidationResult {
62 let (ok_results, error_results): (Vec<_>, Vec<_>) = self
63 .validate_linkage_iter(issuer, configuration, domain, validation_options)?
64 .partition(Result::is_ok);
65
66 if !ok_results.is_empty() {
67 Ok(())
68 } else if !error_results.is_empty() {
69 let errors = error_results
70 .into_iter()
71 .map(Result::unwrap_err) .collect();
73 Err(DomainLinkageValidationError {
74 cause: DomainLinkageValidationErrorCause::List,
75 source: Some(DomainLinkageValidationErrorList::new(errors).into()),
76 })
77 } else {
78 Err(DomainLinkageValidationError {
80 cause: DomainLinkageValidationErrorCause::InvalidStructure,
81 source: None,
82 })
83 }
84 }
85
86 pub fn validate_linkage_iter<'a, DOC: AsRef<CoreDocument>>(
108 &'a self,
109 issuer: &'a DOC,
110 configuration: &'a DomainLinkageConfiguration,
111 domain: &'a Url,
112 validation_options: &'a JwtCredentialValidationOptions,
113 ) -> Result<impl Iterator<Item = DomainLinkageValidationResult> + use<'a, DOC, V>, DomainLinkageValidationError> {
114 let issuers: Vec<CoreDID> = configuration.issuers().map_err(|err| DomainLinkageValidationError {
117 cause: DomainLinkageValidationErrorCause::InvalidJwt,
118 source: Some(err.into()),
119 })?;
120 if issuers.iter().filter(|iss| *iss == issuer.as_ref().id()).count() < 1 {
122 return Err(DomainLinkageValidationError {
123 cause: DomainLinkageValidationErrorCause::InvalidStructure,
124 source: None,
125 });
126 };
127
128 let validation_iter = configuration
130 .linked_dids()
131 .iter()
132 .map(|v| JwtCredentialValidatorUtils::extract_issuer_from_jwt::<CoreDID>(v).unwrap())
133 .enumerate()
134 .filter_map(|(index, iss)| {
135 if iss == *issuer.as_ref().id() {
136 Some(index)
137 } else {
138 None
139 }
140 })
141 .map(move |index| {
142 configuration
143 .linked_dids()
144 .get(index)
145 .ok_or_else(|| DomainLinkageValidationError {
146 cause: DomainLinkageValidationErrorCause::InvalidIssuer,
147 source: None,
148 })
149 .and_then(|credential| self.validate_credential(&issuer, credential, domain, validation_options))
150 });
151
152 Ok(validation_iter)
153 }
154
155 pub fn validate_credential<DOC: AsRef<CoreDocument>>(
161 &self,
162 issuer: &DOC,
163 credential: &Jwt,
164 domain: &Url,
165 validation_options: &JwtCredentialValidationOptions,
166 ) -> DomainLinkageValidationResult {
167 let decoded_credential: DecodedJwtCredential = self
168 .validator
169 .validate(credential, issuer, validation_options, FailFast::AllErrors)
170 .map_err(|err| DomainLinkageValidationError {
171 cause: DomainLinkageValidationErrorCause::CredentialValidationError,
172 source: Some(Box::new(err)),
173 })?;
174
175 let credential: &Credential = &decoded_credential.credential;
176
177 let issuer_did: CoreDID =
178 CoreDID::parse(credential.issuer.url().as_str()).map_err(|err| DomainLinkageValidationError {
179 cause: DomainLinkageValidationErrorCause::InvalidIssuer,
180 source: Some(Box::new(err)),
181 })?;
182
183 if credential.id.is_some() {
184 return Err(DomainLinkageValidationError {
185 cause: DomainLinkageValidationErrorCause::ImpermissibleIdProperty,
186 source: None,
187 });
188 }
189
190 if !credential
192 .types
193 .iter()
194 .any(|type_| type_ == DomainLinkageConfiguration::domain_linkage_type())
195 {
196 return Err(DomainLinkageValidationError {
197 cause: DomainLinkageValidationErrorCause::InvalidTypeProperty,
198 source: None,
199 });
200 }
201
202 let OneOrMany::One(ref credential_subject) = credential.credential_subject else {
204 return Err(DomainLinkageValidationError {
205 cause: DomainLinkageValidationErrorCause::MultipleCredentialSubjects,
206 source: None,
207 });
208 };
209
210 {
212 let subject_id = credential_subject.id.as_deref().ok_or(DomainLinkageValidationError {
213 cause: DomainLinkageValidationErrorCause::MissingSubjectId,
214 source: None,
215 })?;
216 let subject_did = CoreDID::parse(subject_id.as_str()).map_err(|_| DomainLinkageValidationError {
217 cause: DomainLinkageValidationErrorCause::InvalidSubjectId,
218 source: None,
219 })?;
220 if issuer_did != subject_did {
221 return Err(DomainLinkageValidationError {
222 cause: DomainLinkageValidationErrorCause::IssuerSubjectMismatch,
223 source: None,
224 });
225 }
226 }
227
228 {
230 let origin: &str = credential_subject
231 .properties
232 .get("origin")
233 .and_then(|value| value.as_str())
234 .ok_or(DomainLinkageValidationError {
235 cause: DomainLinkageValidationErrorCause::InvalidSubjectOrigin,
236 source: None,
237 })?;
238 let origin_url: Url = match Url::parse(origin) {
239 Ok(url) => Ok(url),
240 Err(identity_core::Error::InvalidUrl(url::ParseError::RelativeUrlWithoutBase)) => {
241 Url::parse("https://".to_owned() + origin).map_err(|err| DomainLinkageValidationError {
242 cause: DomainLinkageValidationErrorCause::InvalidSubjectOrigin,
243 source: Some(Box::new(err)),
244 })
245 }
246 Err(other_error) => Err(DomainLinkageValidationError {
247 cause: DomainLinkageValidationErrorCause::InvalidSubjectOrigin,
248 source: Some(Box::new(other_error)),
249 }),
250 }?;
251 if !url_only_includes_origin(&origin_url) {
252 return Err(DomainLinkageValidationError {
253 cause: DomainLinkageValidationErrorCause::InvalidSubjectOrigin,
254 source: None,
255 });
256 }
257 if origin_url.origin() != domain.origin() {
258 return Err(DomainLinkageValidationError {
259 cause: DomainLinkageValidationErrorCause::OriginMismatch,
260 source: None,
261 });
262 }
263 }
264 Ok(())
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use crate::credential::Credential;
271 use crate::credential::Jws;
272 use crate::credential::Jwt;
273 use crate::domain_linkage::DomainLinkageConfiguration;
274 use crate::domain_linkage::DomainLinkageCredentialBuilder;
275 use crate::domain_linkage::DomainLinkageValidationErrorCause;
276 use crate::domain_linkage::DomainLinkageValidationResult;
277 use crate::domain_linkage::JwtDomainLinkageValidator;
278 use crate::validator::test_utils::generate_jwk_document_with_keys;
279 use crate::validator::JwtCredentialValidationOptions;
280
281 use crypto::signatures::ed25519::SecretKey;
282 use identity_core::common::Duration;
283 use identity_core::common::Object;
284 use identity_core::common::OneOrMany;
285 use identity_core::common::OrderedSet;
286 use identity_core::common::Timestamp;
287 use identity_core::common::Url;
288 use identity_did::CoreDID;
289 use identity_document::document::CoreDocument;
290 use identity_eddsa_verifier::EdDSAJwsVerifier;
291 use identity_verification::jws::CharSet;
292 use identity_verification::jws::CompactJwsEncoder;
293 use identity_verification::jws::CompactJwsEncodingOptions;
294 use identity_verification::jws::JwsAlgorithm;
295 use identity_verification::jws::JwsHeader;
296 use identity_verification::MethodData;
297 use identity_verification::VerificationMethod;
298 use once_cell::sync::Lazy;
299
300 static JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519: Lazy<JwtDomainLinkageValidator<EdDSAJwsVerifier>> =
301 Lazy::new(|| JwtDomainLinkageValidator::with_signature_verifier(EdDSAJwsVerifier::default()));
302
303 #[test]
304 pub(crate) fn test_valid_credential() {
305 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
306 let credential: Credential = create_domain_linkage_credential(document.id());
307 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
308
309 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
310 &document,
311 &jwt,
312 &url_foo(),
313 &JwtCredentialValidationOptions::default(),
314 );
315
316 assert!(validation_result.is_ok());
317 }
318
319 #[test]
320 pub(crate) fn test_invalid_credential_signature() {
321 let (document, _secret_key, fragment) = generate_jwk_document_with_keys();
322 let credential: Credential = create_domain_linkage_credential(document.id());
323 let other_secret_key: SecretKey = SecretKey::generate().unwrap();
324 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &other_secret_key);
326
327 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
328 &document,
329 &jwt,
330 &url_foo(),
331 &JwtCredentialValidationOptions::default(),
332 );
333 assert!(matches!(
334 validation_result.unwrap_err().cause,
335 DomainLinkageValidationErrorCause::CredentialValidationError
336 ));
337 }
338
339 #[test]
340 pub(crate) fn test_invalid_id_property() {
341 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
342 let mut credential: Credential = create_domain_linkage_credential(document.id());
343 credential.id = Some(Url::parse("http://random.credential.id").unwrap());
344 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
345
346 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
347 &document,
348 &jwt,
349 &url_foo(),
350 &JwtCredentialValidationOptions::default(),
351 );
352
353 assert!(matches!(
354 validation_result.unwrap_err().cause,
355 DomainLinkageValidationErrorCause::ImpermissibleIdProperty
356 ));
357 }
358
359 #[test]
360 pub(crate) fn test_domain_linkage_type_missing() {
361 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
362 let mut credential: Credential = create_domain_linkage_credential(document.id());
363 credential.types = OneOrMany::One(Credential::<Object>::base_type().to_owned());
364 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
365
366 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
367 &document,
368 &jwt,
369 &url_foo(),
370 &JwtCredentialValidationOptions::default(),
371 );
372
373 assert!(matches!(
374 validation_result.unwrap_err().cause,
375 DomainLinkageValidationErrorCause::InvalidTypeProperty
376 ));
377 }
378
379 #[test]
380 pub(crate) fn test_extra_type() {
381 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
382 let mut credential: Credential = create_domain_linkage_credential(document.id());
383 credential.types = OneOrMany::Many(vec![
384 Credential::<Object>::base_type().to_owned(),
385 DomainLinkageConfiguration::domain_linkage_type().to_owned(),
386 "not-allowed-type".to_owned(),
387 ]);
388 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
389
390 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
391 &document,
392 &jwt,
393 &url_foo(),
394 &JwtCredentialValidationOptions::default(),
395 );
396
397 assert!(validation_result.is_ok());
398 }
399
400 #[test]
401 pub(crate) fn test_origin_mismatch() {
402 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
403 let mut credential: Credential = create_domain_linkage_credential(document.id());
404
405 let mut properties: Object = Object::new();
406 properties.insert("origin".into(), "http://www.example-1.com".into());
407 if let OneOrMany::One(ref mut subject) = credential.credential_subject {
408 subject.properties = properties;
409 }
410 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
411
412 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
413 &document,
414 &jwt,
415 &url_foo(),
416 &JwtCredentialValidationOptions::default(),
417 );
418
419 assert!(matches!(
420 validation_result.unwrap_err().cause,
421 DomainLinkageValidationErrorCause::OriginMismatch
422 ));
423 }
424
425 #[test]
426 pub(crate) fn test_empty_origin() {
427 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
428 let mut credential: Credential = create_domain_linkage_credential(document.id());
429
430 let properties: Object = Object::new();
431 if let OneOrMany::One(ref mut subject) = credential.credential_subject {
432 subject.properties = properties;
433 }
434 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
435
436 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
437 &document,
438 &jwt,
439 &url_foo(),
440 &JwtCredentialValidationOptions::default(),
441 );
442
443 assert!(matches!(
444 validation_result.unwrap_err().cause,
445 DomainLinkageValidationErrorCause::InvalidSubjectOrigin
446 ));
447 }
448
449 #[test]
450 pub(crate) fn test_origin_without_scheme() {
451 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
452 let mut credential: Credential = create_domain_linkage_credential(document.id());
453
454 let mut properties: Object = Object::new();
455 properties.insert("origin".into(), "foo.example.com".into());
456 if let OneOrMany::One(ref mut subject) = credential.credential_subject {
457 subject.properties = properties;
458 }
459 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
460
461 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
462 &document,
463 &jwt,
464 &url_foo(),
465 &JwtCredentialValidationOptions::default(),
466 );
467
468 assert!(validation_result.is_ok());
469 }
470
471 #[test]
472 pub(crate) fn test_no_subject_id() {
473 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
474 let mut credential: Credential = create_domain_linkage_credential(document.id());
475
476 if let OneOrMany::One(ref mut subject) = credential.credential_subject {
477 subject.id = None;
478 }
479 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
480
481 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
482 &document,
483 &jwt,
484 &url_foo(),
485 &JwtCredentialValidationOptions::default(),
486 );
487
488 assert!(matches!(
489 validation_result.unwrap_err().cause,
490 DomainLinkageValidationErrorCause::MissingSubjectId
491 ));
492 }
493
494 #[test]
495 pub(crate) fn test_invalid_subject_id() {
496 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
497 let mut credential: Credential = create_domain_linkage_credential(document.id());
498
499 if let OneOrMany::One(ref mut subject) = credential.credential_subject {
500 subject.id = Some(Url::parse("http://invalid.did").unwrap());
501 }
502
503 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
504
505 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
506 &document,
507 &jwt,
508 &url_foo(),
509 &JwtCredentialValidationOptions::default(),
510 );
511
512 assert!(matches!(
513 validation_result.unwrap_err().cause,
514 DomainLinkageValidationErrorCause::InvalidSubjectId
515 ));
516 }
517
518 #[test]
519 pub(crate) fn test_issuer_subject_mismatch() {
520 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
521 let mut credential: Credential = create_domain_linkage_credential(document.id());
522
523 if let OneOrMany::One(ref mut subject) = credential.credential_subject {
524 subject.id = Some(Url::parse("did:abc:xyz").unwrap());
525 }
526 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
527
528 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_credential(
529 &document,
530 &jwt,
531 &url_foo(),
532 &JwtCredentialValidationOptions::default(),
533 );
534
535 assert!(matches!(
536 validation_result.unwrap_err().cause,
537 DomainLinkageValidationErrorCause::IssuerSubjectMismatch
538 ));
539 }
540
541 #[test]
542 pub(crate) fn test_multiple_credential_combinations_with_validate() {
543 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
544
545 let credential_1: Credential = create_domain_linkage_credential(document.id());
546 let jwt_valid: Jwt = sign_credential_jwt(&credential_1, &document, &fragment, &secret_key);
547
548 let mut credential_2: Credential = create_domain_linkage_credential(document.id());
549 if let OneOrMany::One(ref mut subject) = credential_2.credential_subject {
550 subject.id = Some(Url::parse("http://invalid.did").unwrap());
551 }
552 let jwt_invalid: Jwt = sign_credential_jwt(&credential_2, &document, &fragment, &secret_key);
553
554 let configurations: Vec<(DomainLinkageConfiguration, bool)> = vec![
555 (
556 DomainLinkageConfiguration::new(vec![jwt_valid.clone(), jwt_valid.clone()]),
557 true,
558 ),
559 (
560 DomainLinkageConfiguration::new(vec![jwt_invalid.clone(), jwt_valid.clone()]),
561 true,
562 ),
563 (
564 DomainLinkageConfiguration::new(vec![jwt_valid.clone(), jwt_invalid.clone()]),
565 true,
566 ),
567 (
568 DomainLinkageConfiguration::new(vec![jwt_invalid.clone(), jwt_invalid.clone()]),
569 false,
570 ),
571 ];
572
573 let validations: Vec<(bool, bool)> = configurations
574 .into_iter()
575 .map(|(configuration, expected)| {
576 (
577 JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519
578 .validate_linkage(
579 &document,
580 &configuration,
581 &url_foo(),
582 &JwtCredentialValidationOptions::default(),
583 )
584 .is_ok(),
585 expected,
586 )
587 })
588 .collect();
589
590 for (index, (outcome, expected)) in validations.iter().enumerate() {
591 assert_eq!(outcome, expected, "testing result of test config {index}");
592 }
593 }
594
595 #[test]
596 pub(crate) fn test_multiple_credential_combinations_with_validate_iter() {
597 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
598
599 let credential_1: Credential = create_domain_linkage_credential(document.id());
600 let jwt_valid: Jwt = sign_credential_jwt(&credential_1, &document, &fragment, &secret_key);
601
602 let mut credential_2: Credential = create_domain_linkage_credential(document.id());
603 if let OneOrMany::One(ref mut subject) = credential_2.credential_subject {
604 subject.id = Some(Url::parse("http://invalid.did").unwrap());
605 }
606 let jwt_invalid: Jwt = sign_credential_jwt(&credential_2, &document, &fragment, &secret_key);
607
608 let configurations: Vec<(DomainLinkageConfiguration, Vec<bool>)> = vec![
609 (
610 DomainLinkageConfiguration::new(vec![jwt_valid.clone(), jwt_valid.clone()]),
611 vec![true, true],
612 ),
613 (
614 DomainLinkageConfiguration::new(vec![jwt_invalid.clone(), jwt_valid.clone()]),
615 vec![false, true],
616 ),
617 (
618 DomainLinkageConfiguration::new(vec![jwt_valid.clone(), jwt_invalid.clone()]),
619 vec![true, false],
620 ),
621 (
622 DomainLinkageConfiguration::new(vec![jwt_invalid.clone(), jwt_invalid.clone()]),
623 vec![false, false],
624 ),
625 ];
626
627 let validations: Vec<(Vec<bool>, Vec<bool>)> = configurations
628 .into_iter()
629 .map(|(configuration, expected)| {
630 (
631 JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519
632 .validate_linkage_iter(
633 &document,
634 &configuration,
635 &url_foo(),
636 &JwtCredentialValidationOptions::default(),
637 )
638 .expect("validation iterator should be creatable")
639 .map(|r| r.is_ok())
640 .collect(),
641 expected,
642 )
643 })
644 .collect();
645
646 for (index, (outcome, expected)) in validations.iter().enumerate() {
647 assert_eq!(outcome, expected, "testing result of test config {index}");
648 }
649 }
650
651 #[test]
652 pub(crate) fn test_multiple_credential_combinations_with_validate_iter_counts() {
653 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
654
655 let credential_1: Credential = create_domain_linkage_credential(document.id());
656 let jwt_valid: Jwt = sign_credential_jwt(&credential_1, &document, &fragment, &secret_key);
657
658 let mut credential_2: Credential = create_domain_linkage_credential(document.id());
659 if let OneOrMany::One(ref mut subject) = credential_2.credential_subject {
660 subject.id = Some(Url::parse("http://invalid.did").unwrap());
661 }
662 let jwt_invalid: Jwt = sign_credential_jwt(&credential_2, &document, &fragment, &secret_key);
663
664 let configurations = vec![
665 (
666 DomainLinkageConfiguration::new(vec![jwt_valid.clone(), jwt_valid.clone()]),
667 (2, 0),
668 ),
669 (
670 DomainLinkageConfiguration::new(vec![jwt_invalid.clone(), jwt_valid.clone()]),
671 (1, 1),
672 ),
673 (
674 DomainLinkageConfiguration::new(vec![jwt_valid.clone(), jwt_invalid.clone()]),
675 (1, 1),
676 ),
677 (
678 DomainLinkageConfiguration::new(vec![jwt_invalid.clone(), jwt_invalid.clone()]),
679 (0, 2),
680 ),
681 ];
682
683 let validations: Vec<_> = configurations
684 .into_iter()
685 .map(|(configuration, expected)| {
686 let (oks, errors): (Vec<_>, Vec<_>) = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519
687 .validate_linkage_iter(
688 &document,
689 &configuration,
690 &url_foo(),
691 &JwtCredentialValidationOptions::default(),
692 )
693 .expect("validation iterator should be creatable")
694 .partition(Result::is_ok);
695 ((oks.len(), errors.len()), expected)
696 })
697 .collect();
698
699 for (index, (outcome, expected)) in validations.iter().enumerate() {
700 assert_eq!(outcome, expected, "testing result of test config {index}");
701 }
702 }
703
704 #[test]
705 pub(crate) fn test_valid_configuration() {
706 let (document, secret_key, fragment) = generate_jwk_document_with_keys();
707 let credential: Credential = create_domain_linkage_credential(document.id());
708 let jwt: Jwt = sign_credential_jwt(&credential, &document, &fragment, &secret_key);
709
710 let configuration: DomainLinkageConfiguration = DomainLinkageConfiguration::new(vec![jwt]);
711 let validation_result: DomainLinkageValidationResult = JWT_DOMAIN_LINKAGE_VALIDATOR_ED25519.validate_linkage(
712 &document,
713 &configuration,
714 &url_foo(),
715 &JwtCredentialValidationOptions::default(),
716 );
717
718 assert!(validation_result.is_ok());
719 }
720
721 fn url_foo() -> Url {
722 Url::parse("https://foo.example.com").unwrap()
723 }
724
725 fn create_domain_linkage_credential(did: &CoreDID) -> Credential {
726 let domain: Url = url_foo();
727
728 let mut domains: OrderedSet<Url> = OrderedSet::new();
729 domains.append(domain.clone());
730
731 let credential: Credential = DomainLinkageCredentialBuilder::new()
732 .issuer(did.clone())
733 .origin(domain)
734 .issuance_date(Timestamp::now_utc())
735 .expiration_date(Timestamp::now_utc().checked_add(Duration::days(365)).unwrap())
736 .build()
737 .unwrap();
738 credential
739 }
740
741 fn sign_credential_jwt(
742 credential: &Credential,
743 document: &CoreDocument,
744 fragment: &str,
745 secret_key: &SecretKey,
746 ) -> Jwt {
747 let payload: String = credential.serialize_jwt(None).unwrap();
748 Jwt::new(sign_bytes(document, fragment, payload.as_ref(), secret_key).into())
749 }
750
751 fn sign_bytes(document: &CoreDocument, fragment: &str, payload: &[u8], secret_key: &SecretKey) -> Jws {
752 let method: &VerificationMethod = document.resolve_method(fragment, None).unwrap();
753 let MethodData::PublicKeyJwk(ref jwk) = method.data() else {
754 panic!("not a jwk");
755 };
756 let alg: JwsAlgorithm = jwk.alg().unwrap_or("").parse().unwrap();
757
758 let header: JwsHeader = {
759 let mut header = JwsHeader::new();
760 header.set_alg(alg);
761 header.set_kid(method.id().to_string());
762 header
763 };
764
765 let encoding_options: CompactJwsEncodingOptions = CompactJwsEncodingOptions::NonDetached {
766 charset_requirements: CharSet::Default,
767 };
768
769 let jws_encoder: CompactJwsEncoder<'_> =
770 CompactJwsEncoder::new_with_options(payload, &header, encoding_options).unwrap();
771
772 let signature: [u8; 64] = secret_key.sign(jws_encoder.signing_input()).to_bytes();
773
774 Jws::new(jws_encoder.into_jws(&signature))
775 }
776}