1use crate::core::{
14 Attestation, DevicePublicKey, VerifiedAttestation, canonicalize_attestation_data,
15};
16use crate::error::AttestationError;
17use crate::types::{ChainLink, VerificationReport, VerificationStatus};
18#[cfg(feature = "native")]
19use crate::witness::WitnessVerifyConfig;
20use auths_crypto::CryptoProvider;
21use auths_keri::{Event, compute_said, find_seal_in_kel};
22use chrono::{DateTime, Duration, Utc};
23use log::debug;
24use serde::Serialize;
25
26const MAX_SKEW_SECS: i64 = 5 * 60;
28
29#[cfg(feature = "native")]
39pub async fn verify_with_keys(
40 att: &Attestation,
41 issuer_pk: &DevicePublicKey,
42) -> Result<VerifiedAttestation, AttestationError> {
43 crate::verifier::Verifier::native()
44 .verify_with_keys(att, issuer_pk)
45 .await
46}
47
48#[cfg(feature = "native")]
55pub async fn verify_at_time(
56 att: &Attestation,
57 issuer_pk: &DevicePublicKey,
58 at: DateTime<Utc>,
59) -> Result<VerifiedAttestation, AttestationError> {
60 crate::verifier::Verifier::native()
61 .verify_at_time(att, issuer_pk, at)
62 .await
63}
64
65#[cfg(feature = "native")]
72pub async fn verify_chain_with_witnesses(
73 attestations: &[Attestation],
74 root_pk: &DevicePublicKey,
75 witness_config: &WitnessVerifyConfig<'_>,
76) -> Result<VerificationReport, AttestationError> {
77 crate::verifier::Verifier::native()
78 .verify_chain_with_witnesses(attestations, root_pk, witness_config)
79 .await
80}
81
82#[cfg(feature = "native")]
88pub async fn verify_chain(
89 attestations: &[Attestation],
90 root_pk: &DevicePublicKey,
91) -> Result<VerificationReport, AttestationError> {
92 crate::verifier::Verifier::native()
93 .verify_chain(attestations, root_pk)
94 .await
95}
96
97#[cfg(feature = "native")]
105pub async fn verify_device_authorization(
106 identity_did: &str,
107 device_did: &crate::types::CanonicalDid,
108 attestations: &[Attestation],
109 identity_pk: &DevicePublicKey,
110) -> Result<VerificationReport, AttestationError> {
111 crate::verifier::Verifier::native()
112 .verify_device_authorization(identity_did, device_did, attestations, identity_pk)
113 .await
114}
115
116use crate::types::CanonicalDid;
117
118pub fn is_device_listed(
120 identity_did: &str,
121 device_did: &CanonicalDid,
122 attestations: &[VerifiedAttestation],
123 now: DateTime<Utc>,
124) -> bool {
125 let device_did_str = device_did.to_string();
126
127 attestations.iter().any(|verified| {
128 let att = verified.inner();
129 if att.issuer != identity_did {
130 return false;
131 }
132 if att.subject.to_string() != device_did_str {
133 return false;
134 }
135 if att.is_revoked() {
136 return false;
137 }
138 if let Some(exp) = att.expires_at
139 && now > exp
140 {
141 return false;
142 }
143 true
144 })
145}
146
147#[derive(Debug, Clone, Serialize)]
157pub struct DeviceLinkVerification {
158 pub valid: bool,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub error: Option<String>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub key_state: Option<auths_keri::KeyState>,
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub seal_sequence: Option<u128>,
169}
170
171impl DeviceLinkVerification {
172 fn success(key_state: auths_keri::KeyState, seal_sequence: Option<u128>) -> Self {
173 Self {
174 valid: true,
175 error: None,
176 key_state: Some(key_state),
177 seal_sequence,
178 }
179 }
180
181 fn failure(reason: impl Into<String>) -> Self {
182 Self {
183 valid: false,
184 error: Some(reason.into()),
185 key_state: None,
186 seal_sequence: None,
187 }
188 }
189}
190
191pub async fn verify_device_link(
209 events: &[Event],
210 attestation: &Attestation,
211 device_did: &str,
212 now: DateTime<Utc>,
213 provider: &dyn CryptoProvider,
214) -> DeviceLinkVerification {
215 let key_state = match auths_keri::TrustedKel::from_trusted_source(events).replay() {
217 Ok(ks) => ks,
218 Err(e) => return DeviceLinkVerification::failure(format!("KEL verification failed: {e}")),
219 };
220
221 if attestation.subject.as_str() != device_did {
222 return DeviceLinkVerification::failure(format!(
223 "Device DID mismatch: attestation subject is '{}', expected '{device_did}'",
224 attestation.subject
225 ));
226 }
227
228 let current_pk = match key_state.current_keys.first() {
229 Some(encoded) => match auths_keri::KeriPublicKey::parse(encoded.as_str()) {
230 Ok(keri_pk) => {
231 let curve = keri_pk.curve();
234 let bytes = keri_pk.into_bytes().to_vec();
235 match DevicePublicKey::try_new(curve, &bytes) {
236 Ok(dpk) => dpk,
237 Err(e) => {
238 return DeviceLinkVerification::failure(format!(
239 "Invalid current key: {e}"
240 ));
241 }
242 }
243 }
244 Err(e) => {
245 return DeviceLinkVerification::failure(format!("Invalid current key: {e}"));
246 }
247 },
248 None => return DeviceLinkVerification::failure("KEL has no current keys"),
249 };
250
251 if let Err(e) = verify_with_keys_at(attestation, ¤t_pk, now, true, provider).await {
252 return DeviceLinkVerification::failure(format!("Attestation verification failed: {e}"));
253 }
254
255 let seal_sequence = compute_attestation_seal_digest(attestation)
256 .ok()
257 .and_then(|digest| find_seal_in_kel(events, digest.as_str()));
258
259 DeviceLinkVerification::success(key_state, seal_sequence)
260}
261
262pub fn compute_attestation_seal_digest(
267 attestation: &Attestation,
268) -> Result<String, AttestationError> {
269 let canonical = canonicalize_attestation_data(&attestation.canonical_data())?;
270 let value: serde_json::Value = serde_json::from_slice(&canonical)
271 .map_err(|e| AttestationError::SerializationError(e.to_string()))?;
272 Ok(compute_said(&value)
273 .map_err(|e| AttestationError::SerializationError(e.to_string()))?
274 .into_inner())
275}
276
277pub(crate) async fn verify_with_keys_at(
282 att: &Attestation,
283 issuer_pk: &DevicePublicKey,
284 at: DateTime<Utc>,
285 check_skew: bool,
286 provider: &dyn CryptoProvider,
287) -> Result<(), AttestationError> {
288 let reference_time = at;
289
290 if let Some(revoked_at) = att.revoked_at
292 && revoked_at <= reference_time
293 {
294 return Err(AttestationError::AttestationRevoked);
295 }
296
297 if let Some(exp) = att.expires_at
299 && reference_time > exp
300 {
301 return Err(AttestationError::AttestationExpired {
302 at: exp.to_rfc3339(),
303 });
304 }
305
306 if check_skew
308 && let Some(ts) = att.timestamp
309 && ts > reference_time + Duration::seconds(MAX_SKEW_SECS)
310 {
311 return Err(AttestationError::TimestampInFuture {
312 at: ts.to_rfc3339(),
313 });
314 }
315
316 let canonical_json_bytes = canonicalize_attestation_data(&att.canonical_data())?;
318 let data_to_verify = canonical_json_bytes.as_slice();
319 debug!(
320 "(Verify) Canonical data: {}",
321 String::from_utf8_lossy(&canonical_json_bytes)
322 );
323
324 if !att.identity_signature.is_empty() {
326 verify_signature_by_curve(
327 issuer_pk,
328 data_to_verify,
329 att.identity_signature.as_bytes(),
330 provider,
331 SignatureRole::Issuer,
332 )
333 .await?;
334 debug!("(Verify) Issuer signature verified successfully.");
335 } else {
336 return Err(AttestationError::IssuerSignatureFailed(
341 "missing issuer signature".to_string(),
342 ));
343 }
344
345 verify_signature_by_curve(
347 &att.device_public_key,
348 data_to_verify,
349 att.device_signature.as_bytes(),
350 provider,
351 SignatureRole::Device,
352 )
353 .await?;
354 debug!("(Verify) Device signature verified successfully.");
355
356 Ok(())
357}
358
359#[derive(Clone, Copy)]
361enum SignatureRole {
362 Issuer,
363 Device,
364}
365
366async fn verify_signature_by_curve(
373 pk: &DevicePublicKey,
374 message: &[u8],
375 signature: &[u8],
376 provider: &dyn CryptoProvider,
377 role: SignatureRole,
378) -> Result<(), AttestationError> {
379 let map_err = |e: String| match role {
380 SignatureRole::Issuer => AttestationError::IssuerSignatureFailed(e),
381 SignatureRole::Device => AttestationError::DeviceSignatureFailed(e),
382 };
383
384 pk.verify(message, signature, provider)
385 .await
386 .map_err(|e| map_err(e.to_string()))
387}
388
389pub(crate) async fn verify_chain_inner(
390 attestations: &[Attestation],
391 root_pk: &DevicePublicKey,
392 provider: &dyn CryptoProvider,
393 now: DateTime<Utc>,
394) -> Result<VerificationReport, AttestationError> {
395 if attestations.is_empty() {
396 return Ok(VerificationReport::with_status(
397 VerificationStatus::BrokenChain {
398 missing_link: "empty chain".to_string(),
399 },
400 vec![],
401 ));
402 }
403
404 let mut chain_links: Vec<ChainLink> = Vec::with_capacity(attestations.len());
405
406 let first_att = &attestations[0];
407 match verify_single_attestation(first_att, root_pk, 0, provider, now).await {
408 Ok(link) => chain_links.push(link),
409 Err((status, link)) => {
410 chain_links.push(link);
411 return Ok(VerificationReport::with_status(status, chain_links));
412 }
413 }
414
415 for (idx, att) in attestations.iter().enumerate().skip(1) {
416 let prev_att = &attestations[idx - 1];
417
418 if att.issuer.as_str() != prev_att.subject.as_str() {
419 let link = ChainLink::invalid(
420 att.issuer.to_string(),
421 att.subject.to_string(),
422 format!(
423 "Chain broken: expected issuer '{}', got '{}'",
424 prev_att.subject, att.issuer
425 ),
426 );
427 chain_links.push(link);
428 return Ok(VerificationReport::with_status(
429 VerificationStatus::BrokenChain {
430 missing_link: format!(
431 "Issuer mismatch at step {}: expected '{}', got '{}'",
432 idx, prev_att.subject, att.issuer
433 ),
434 },
435 chain_links,
436 ));
437 }
438
439 let issuer_pk = &prev_att.device_public_key;
440
441 match verify_single_attestation(att, issuer_pk, idx, provider, now).await {
442 Ok(link) => chain_links.push(link),
443 Err((status, link)) => {
444 chain_links.push(link);
445 return Ok(VerificationReport::with_status(status, chain_links));
446 }
447 }
448 }
449
450 Ok(VerificationReport::valid(chain_links))
451}
452
453pub(crate) async fn verify_device_authorization_inner(
454 identity_did: &str,
455 device_did: &CanonicalDid,
456 attestations: &[Attestation],
457 identity_pk: &DevicePublicKey,
458 provider: &dyn CryptoProvider,
459 now: DateTime<Utc>,
460) -> Result<VerificationReport, AttestationError> {
461 let device_did_str = device_did.to_string();
462
463 let matching: Vec<&Attestation> = attestations
464 .iter()
465 .filter(|a| a.issuer == identity_did && a.subject.to_string() == device_did_str)
466 .collect();
467
468 if matching.is_empty() {
469 return Ok(VerificationReport::with_status(
470 VerificationStatus::BrokenChain {
471 missing_link: format!(
472 "No attestation found for device {} under {}",
473 device_did_str, identity_did
474 ),
475 },
476 vec![],
477 ));
478 }
479
480 match verify_single_attestation(matching[0], identity_pk, 0, provider, now).await {
481 Ok(link) => Ok(VerificationReport::valid(vec![link])),
482 Err((status, link)) => Ok(VerificationReport::with_status(status, vec![link])),
483 }
484}
485
486async fn verify_single_attestation(
487 att: &Attestation,
488 issuer_pk: &DevicePublicKey,
489 step: usize,
490 provider: &dyn CryptoProvider,
491 now: DateTime<Utc>,
492) -> Result<ChainLink, (VerificationStatus, ChainLink)> {
493 let issuer = att.issuer.to_string();
494 let subject = att.subject.to_string();
495
496 if att.is_revoked() {
497 return Err((
498 VerificationStatus::Revoked { at: att.revoked_at },
499 ChainLink::invalid(issuer, subject, "Attestation revoked".to_string()),
500 ));
501 }
502
503 if let Some(exp) = att.expires_at
504 && now > exp
505 {
506 return Err((
507 VerificationStatus::Expired { at: exp },
508 ChainLink::invalid(
509 issuer,
510 subject,
511 format!("Attestation expired on {}", exp.to_rfc3339()),
512 ),
513 ));
514 }
515
516 match verify_with_keys_at(att, issuer_pk, now, true, provider).await {
517 Ok(()) => Ok(ChainLink::valid(issuer, subject)),
518 Err(e) => Err((
519 VerificationStatus::InvalidSignature { step },
520 ChainLink::invalid(issuer, subject, e.to_string()),
521 )),
522 }
523}
524
525#[cfg(all(test, not(target_arch = "wasm32")))]
526#[allow(clippy::unwrap_used, clippy::expect_used, clippy::disallowed_methods)]
527mod tests {
528 use super::*;
529 use crate::clock::ClockProvider;
530 use crate::core::{Ed25519PublicKey, Ed25519Signature, ResourceId};
531 use crate::types::CanonicalDid;
532 use crate::verifier::Verifier;
533 use auths_crypto::RingCryptoProvider;
534 use auths_crypto::testing::create_test_keypair;
535 use auths_keri::Said;
536 use chrono::{DateTime, Duration, TimeZone, Utc};
537 use ring::signature::{Ed25519KeyPair, KeyPair};
538 use std::sync::Arc;
539
540 fn ed(pk: &[u8]) -> DevicePublicKey {
542 DevicePublicKey::try_new(auths_crypto::CurveType::Ed25519, pk).unwrap()
543 }
544
545 fn ed25519_did(pk: &[u8; 32]) -> String {
547 CanonicalDid::from_public_key_did_key(pk, auths_crypto::CurveType::Ed25519).to_string()
548 }
549
550 struct TestClock(DateTime<Utc>);
551 impl ClockProvider for TestClock {
552 fn now(&self) -> DateTime<Utc> {
553 self.0
554 }
555 }
556
557 fn fixed_now() -> DateTime<Utc> {
558 Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
559 }
560
561 fn test_verifier() -> Verifier {
562 Verifier::new(
563 Arc::new(RingCryptoProvider),
564 Arc::new(TestClock(fixed_now())),
565 )
566 }
567
568 fn create_signed_attestation(
570 issuer_kp: &Ed25519KeyPair,
571 device_kp: &Ed25519KeyPair,
572 issuer_did: &str,
573 subject_did: &str,
574 revoked_at: Option<DateTime<Utc>>,
575 expires_at: Option<DateTime<Utc>>,
576 ) -> Attestation {
577 let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
578
579 let mut att = Attestation {
580 version: 1,
581 rid: ResourceId::new("test-rid"),
582 issuer: CanonicalDid::new_unchecked(issuer_did),
583 subject: CanonicalDid::new_unchecked(subject_did),
584 device_public_key: Ed25519PublicKey::from_bytes(device_pk).into(),
585 identity_signature: Ed25519Signature::empty(),
586 device_signature: Ed25519Signature::empty(),
587 revoked_at,
588 expires_at,
589 timestamp: Some(fixed_now()),
590 note: None,
591 payload: None,
592 delegated_by: None,
593 signer_type: None,
594 environment_claim: None,
595 commit_sha: None,
596 commit_message: None,
597 author: None,
598 oidc_binding: None,
599 };
600
601 let canonical_bytes = canonicalize_attestation_data(&att.canonical_data()).unwrap();
602
603 att.identity_signature =
604 Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
605 att.device_signature =
606 Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
607
608 att
609 }
610
611 #[tokio::test]
612 async fn verify_chain_empty_returns_broken_chain() {
613 let result = test_verifier()
614 .verify_chain(&[], &ed(&[0u8; 32]))
615 .await
616 .unwrap();
617 assert!(!result.is_valid());
618 match result.status {
619 VerificationStatus::BrokenChain { missing_link } => {
620 assert_eq!(missing_link, "empty chain");
621 }
622 _ => panic!("Expected BrokenChain status"),
623 }
624 assert!(result.chain.is_empty());
625 }
626
627 #[tokio::test]
628 async fn verify_chain_single_valid_attestation() {
629 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
630 let root_did = ed25519_did(&root_pk);
631 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
632 let device_did = ed25519_did(&device_pk);
633
634 let att = create_signed_attestation(
635 &root_kp,
636 &device_kp,
637 &root_did,
638 &device_did,
639 None,
640 Some(fixed_now() + Duration::days(365)),
641 );
642
643 let result = test_verifier()
644 .verify_chain(&[att], &ed(&root_pk))
645 .await
646 .unwrap();
647 assert!(result.is_valid());
648 assert_eq!(result.chain.len(), 1);
649 assert!(result.chain[0].valid);
650 }
651
652 #[tokio::test]
653 async fn verify_chain_valid_report_is_offline_unknown_not_bare() {
654 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
658 let root_did = ed25519_did(&root_pk);
659 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
660 let device_did = ed25519_did(&device_pk);
661
662 let att = create_signed_attestation(
663 &root_kp,
664 &device_kp,
665 &root_did,
666 &device_did,
667 None,
668 Some(fixed_now() + Duration::days(365)),
669 );
670
671 let report = test_verifier()
672 .verify_chain(&[att], &ed(&root_pk))
673 .await
674 .unwrap();
675 assert!(report.is_valid());
676 assert_eq!(
677 report.freshness(),
678 crate::freshness::Freshness::Unknown,
679 "an offline chain verify must surface Unknown freshness, never a bare Valid"
680 );
681 }
682
683 #[tokio::test]
684 async fn verify_chain_revoked_attestation_returns_revoked() {
685 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
686 let root_did = ed25519_did(&root_pk);
687 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
688 let device_did = ed25519_did(&device_pk);
689
690 let att = create_signed_attestation(
691 &root_kp,
692 &device_kp,
693 &root_did,
694 &device_did,
695 Some(fixed_now()),
696 Some(fixed_now() + Duration::days(365)),
697 );
698
699 let result = test_verifier()
700 .verify_chain(&[att], &ed(&root_pk))
701 .await
702 .unwrap();
703 assert!(!result.is_valid());
704 match result.status {
705 VerificationStatus::Revoked { .. } => {}
706 _ => panic!("Expected Revoked status, got {:?}", result.status),
707 }
708 }
709
710 #[tokio::test]
711 async fn verify_chain_expired_attestation_returns_expired() {
712 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
713 let root_did = ed25519_did(&root_pk);
714 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
715 let device_did = ed25519_did(&device_pk);
716
717 let att = create_signed_attestation(
718 &root_kp,
719 &device_kp,
720 &root_did,
721 &device_did,
722 None,
723 Some(fixed_now() - Duration::days(1)),
724 );
725
726 let result = test_verifier()
727 .verify_chain(&[att], &ed(&root_pk))
728 .await
729 .unwrap();
730 assert!(!result.is_valid());
731 match result.status {
732 VerificationStatus::Expired { .. } => {}
733 _ => panic!("Expected Expired status, got {:?}", result.status),
734 }
735 }
736
737 #[tokio::test]
738 async fn verify_chain_invalid_signature_returns_invalid_signature() {
739 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
740 let root_did = ed25519_did(&root_pk);
741 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
742 let device_did = ed25519_did(&device_pk);
743
744 let mut att = create_signed_attestation(
745 &root_kp,
746 &device_kp,
747 &root_did,
748 &device_did,
749 None,
750 Some(fixed_now() + Duration::days(365)),
751 );
752 let mut tampered = *att.identity_signature.as_bytes();
753 tampered[0] ^= 0xFF;
754 att.identity_signature = Ed25519Signature::from_bytes(tampered);
755
756 let result = test_verifier()
757 .verify_chain(&[att], &ed(&root_pk))
758 .await
759 .unwrap();
760 assert!(!result.is_valid());
761 match result.status {
762 VerificationStatus::InvalidSignature { step } => {
763 assert_eq!(step, 0);
764 }
765 _ => panic!("Expected InvalidSignature status, got {:?}", result.status),
766 }
767 }
768
769 #[tokio::test]
770 async fn verify_chain_broken_link_returns_broken_chain() {
771 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
772 let root_did = ed25519_did(&root_pk);
773 let (device1_kp, device1_pk) = create_test_keypair(&[2u8; 32]);
774 let device1_did = ed25519_did(&device1_pk);
775 let (device2_kp, device2_pk) = create_test_keypair(&[3u8; 32]);
776 let device2_did = ed25519_did(&device2_pk);
777
778 let att1 = create_signed_attestation(
779 &root_kp,
780 &device1_kp,
781 &root_did,
782 &device1_did,
783 None,
784 Some(fixed_now() + Duration::days(365)),
785 );
786 let att2 = create_signed_attestation(
787 &device1_kp,
788 &device2_kp,
789 &root_did, &device2_did,
791 None,
792 Some(fixed_now() + Duration::days(365)),
793 );
794
795 let result = test_verifier()
796 .verify_chain(&[att1, att2], &ed(&root_pk))
797 .await
798 .unwrap();
799 assert!(!result.is_valid());
800 match result.status {
801 VerificationStatus::BrokenChain { missing_link } => {
802 assert!(missing_link.contains("Issuer mismatch"));
803 }
804 _ => panic!("Expected BrokenChain status, got {:?}", result.status),
805 }
806 }
807
808 #[tokio::test]
809 async fn verify_chain_valid_three_level_chain() {
810 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
811 let root_did = ed25519_did(&root_pk);
812 let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
813 let identity_did = ed25519_did(&identity_pk);
814 let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
815 let device_did = ed25519_did(&device_pk);
816
817 let att1 = create_signed_attestation(
818 &root_kp,
819 &identity_kp,
820 &root_did,
821 &identity_did,
822 None,
823 Some(fixed_now() + Duration::days(365)),
824 );
825 let att2 = create_signed_attestation(
826 &identity_kp,
827 &device_kp,
828 &identity_did,
829 &device_did,
830 None,
831 Some(fixed_now() + Duration::days(365)),
832 );
833
834 let result = test_verifier()
835 .verify_chain(&[att1, att2], &ed(&root_pk))
836 .await
837 .unwrap();
838 assert!(result.is_valid());
839 assert_eq!(result.chain.len(), 2);
840 assert!(result.chain[0].valid);
841 assert!(result.chain[1].valid);
842 }
843
844 #[tokio::test]
845 async fn verify_chain_revoked_intermediate_returns_revoked() {
846 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
847 let root_did = ed25519_did(&root_pk);
848 let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
849 let identity_did = ed25519_did(&identity_pk);
850 let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
851 let device_did = ed25519_did(&device_pk);
852
853 let att1 = create_signed_attestation(
854 &root_kp,
855 &identity_kp,
856 &root_did,
857 &identity_did,
858 None,
859 Some(fixed_now() + Duration::days(365)),
860 );
861 let att2 = create_signed_attestation(
862 &identity_kp,
863 &device_kp,
864 &identity_did,
865 &device_did,
866 Some(fixed_now()),
867 Some(fixed_now() + Duration::days(365)),
868 );
869
870 let result = test_verifier()
871 .verify_chain(&[att1, att2], &ed(&root_pk))
872 .await
873 .unwrap();
874 assert!(!result.is_valid());
875 match result.status {
876 VerificationStatus::Revoked { .. } => {}
877 _ => panic!("Expected Revoked status, got {:?}", result.status),
878 }
879 assert_eq!(result.chain.len(), 2);
880 assert!(result.chain[0].valid);
881 assert!(!result.chain[1].valid);
882 }
883
884 #[tokio::test]
885 async fn verify_at_time_valid_before_expiration() {
886 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
887 let root_did = ed25519_did(&root_pk);
888 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
889 let device_did = ed25519_did(&root_pk);
890
891 let expires = fixed_now() + Duration::days(30);
892 let att = create_signed_attestation(
893 &root_kp,
894 &device_kp,
895 &root_did,
896 &device_did,
897 None,
898 Some(expires),
899 );
900
901 let verification_time = fixed_now() + Duration::days(10);
902 let result = test_verifier()
903 .verify_at_time(&att, &ed(&root_pk), verification_time)
904 .await;
905 assert!(result.is_ok());
906 }
907
908 #[tokio::test]
909 async fn verify_at_time_expired_after_expiration() {
910 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
911 let root_did = ed25519_did(&root_pk);
912 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
913 let device_did = ed25519_did(&root_pk);
914
915 let expires = fixed_now() + Duration::days(30);
916 let att = create_signed_attestation(
917 &root_kp,
918 &device_kp,
919 &root_did,
920 &device_did,
921 None,
922 Some(expires),
923 );
924
925 let verification_time = fixed_now() + Duration::days(60);
926 let result = test_verifier()
927 .verify_at_time(&att, &ed(&root_pk), verification_time)
928 .await;
929 assert!(result.is_err());
930 assert!(result.unwrap_err().to_string().contains("expired"));
931 }
932
933 #[tokio::test]
934 async fn verify_at_time_signature_always_checked() {
935 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
936 let root_did = ed25519_did(&root_pk);
937 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
938 let device_did = ed25519_did(&root_pk);
939
940 let mut att = create_signed_attestation(
941 &root_kp,
942 &device_kp,
943 &root_did,
944 &device_did,
945 None,
946 Some(fixed_now() + Duration::days(365)),
947 );
948 let mut tampered = *att.identity_signature.as_bytes();
949 tampered[0] ^= 0xFF;
950 att.identity_signature = Ed25519Signature::from_bytes(tampered);
951
952 let verification_time = fixed_now() - Duration::days(10);
953 let result = test_verifier()
954 .verify_at_time(&att, &ed(&root_pk), verification_time)
955 .await;
956 assert!(result.is_err());
957 assert!(
958 result
959 .unwrap_err()
960 .to_string()
961 .contains("signature verification failed")
962 );
963 }
964
965 #[tokio::test]
966 async fn verify_at_time_with_past_time_skips_skew_check() {
967 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
968 let root_did = ed25519_did(&root_pk);
969 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
970 let device_did = ed25519_did(&root_pk);
971
972 let att = create_signed_attestation(
973 &root_kp,
974 &device_kp,
975 &root_did,
976 &device_did,
977 None,
978 Some(fixed_now() + Duration::days(365)),
979 );
980
981 let verification_time = fixed_now() - Duration::days(30);
982 let result = test_verifier()
983 .verify_at_time(&att, &ed(&root_pk), verification_time)
984 .await;
985 assert!(result.is_ok());
986 }
987
988 #[tokio::test]
989 async fn verify_with_keys_still_works() {
990 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
991 let root_did = ed25519_did(&root_pk);
992 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
993 let device_did = ed25519_did(&root_pk);
994
995 let att = create_signed_attestation(
996 &root_kp,
997 &device_kp,
998 &root_did,
999 &device_did,
1000 None,
1001 Some(fixed_now() + Duration::days(365)),
1002 );
1003
1004 let result = test_verifier().verify_with_keys(&att, &ed(&root_pk)).await;
1005 assert!(result.is_ok());
1006 }
1007
1008 #[tokio::test]
1009 async fn verify_chain_revoked_and_expired_attestation_is_rejected() {
1010 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1011 let root_did = ed25519_did(&root_pk);
1012 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1013 let device_did = ed25519_did(&device_pk);
1014
1015 let att = create_signed_attestation(
1018 &root_kp,
1019 &device_kp,
1020 &root_did,
1021 &device_did,
1022 Some(fixed_now()),
1023 Some(fixed_now() - Duration::days(1)),
1024 );
1025
1026 let result = test_verifier()
1027 .verify_chain(&[att], &ed(&root_pk))
1028 .await
1029 .unwrap();
1030 assert!(!result.is_valid(), "revoked+expired must not be valid");
1031 assert!(
1032 matches!(
1033 result.status,
1034 VerificationStatus::Revoked { .. } | VerificationStatus::Expired { .. }
1035 ),
1036 "expected a terminal revoked/expired status, got {:?}",
1037 result.status
1038 );
1039 }
1040
1041 #[tokio::test]
1042 async fn verify_with_keys_rejects_a_correct_signature_under_the_wrong_key() {
1043 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1044 let root_did = ed25519_did(&root_pk);
1045 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1046 let device_did = ed25519_did(&device_pk);
1047
1048 let att = create_signed_attestation(
1052 &root_kp,
1053 &device_kp,
1054 &root_did,
1055 &device_did,
1056 None,
1057 Some(fixed_now() + Duration::days(365)),
1058 );
1059 let (_other_kp, other_pk) = create_test_keypair(&[9u8; 32]);
1060
1061 let result = test_verifier().verify_with_keys(&att, &ed(&other_pk)).await;
1062 assert!(
1063 result.is_err(),
1064 "a signature that does not verify under the supplied issuer key must be rejected"
1065 );
1066 }
1067
1068 fn verified(att: Attestation) -> VerifiedAttestation {
1070 VerifiedAttestation::dangerous_from_unchecked(att)
1071 }
1072
1073 #[test]
1074 fn is_device_listed_returns_true_for_valid_attestation() {
1075 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1076 let root_did = ed25519_did(&root_pk);
1077 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1078 let device_did_str = ed25519_did(&device_pk);
1079 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1080
1081 let att = create_signed_attestation(
1082 &root_kp,
1083 &device_kp,
1084 &root_did,
1085 &device_did_str,
1086 None,
1087 Some(fixed_now() + Duration::days(365)),
1088 );
1089
1090 assert!(is_device_listed(
1091 &root_did,
1092 &device_did,
1093 &[verified(att)],
1094 fixed_now()
1095 ));
1096 }
1097
1098 #[test]
1099 fn is_device_listed_returns_false_for_no_attestations() {
1100 let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1101 let root_did = ed25519_did(&root_pk);
1102 let (_, device_pk) = create_test_keypair(&[2u8; 32]);
1103 let device_did_str = ed25519_did(&device_pk);
1104 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1105
1106 assert!(!is_device_listed(&root_did, &device_did, &[], fixed_now()));
1107 }
1108
1109 #[test]
1110 fn is_device_listed_returns_false_for_expired_attestation() {
1111 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1112 let root_did = ed25519_did(&root_pk);
1113 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1114 let device_did_str = ed25519_did(&device_pk);
1115 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1116
1117 let att = create_signed_attestation(
1118 &root_kp,
1119 &device_kp,
1120 &root_did,
1121 &device_did_str,
1122 None,
1123 Some(fixed_now() - Duration::days(1)),
1124 );
1125
1126 assert!(!is_device_listed(
1127 &root_did,
1128 &device_did,
1129 &[verified(att)],
1130 fixed_now()
1131 ));
1132 }
1133
1134 #[test]
1135 fn is_device_listed_returns_false_for_revoked_attestation() {
1136 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1137 let root_did = ed25519_did(&root_pk);
1138 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1139 let device_did_str = ed25519_did(&device_pk);
1140 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1141
1142 let att = create_signed_attestation(
1143 &root_kp,
1144 &device_kp,
1145 &root_did,
1146 &device_did_str,
1147 Some(fixed_now()),
1148 Some(fixed_now() + Duration::days(365)),
1149 );
1150
1151 assert!(!is_device_listed(
1152 &root_did,
1153 &device_did,
1154 &[verified(att)],
1155 fixed_now()
1156 ));
1157 }
1158
1159 #[test]
1160 fn is_device_listed_returns_true_if_one_valid_among_many() {
1161 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1162 let root_did = ed25519_did(&root_pk);
1163 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1164 let device_did_str = ed25519_did(&device_pk);
1165 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1166
1167 let att_expired = verified(create_signed_attestation(
1168 &root_kp,
1169 &device_kp,
1170 &root_did,
1171 &device_did_str,
1172 None,
1173 Some(fixed_now() - Duration::days(1)),
1174 ));
1175 let att_revoked = verified(create_signed_attestation(
1176 &root_kp,
1177 &device_kp,
1178 &root_did,
1179 &device_did_str,
1180 Some(fixed_now()),
1181 Some(fixed_now() + Duration::days(365)),
1182 ));
1183 let att_valid = verified(create_signed_attestation(
1184 &root_kp,
1185 &device_kp,
1186 &root_did,
1187 &device_did_str,
1188 None,
1189 Some(fixed_now() + Duration::days(365)),
1190 ));
1191
1192 assert!(is_device_listed(
1193 &root_did,
1194 &device_did,
1195 &[att_expired, att_revoked, att_valid],
1196 fixed_now()
1197 ));
1198 }
1199
1200 #[test]
1201 fn is_device_listed_returns_false_for_wrong_identity() {
1202 let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1203 let root_did = ed25519_did(&root_pk);
1204 let (other_kp, other_pk) = create_test_keypair(&[3u8; 32]);
1205 let other_did = ed25519_did(&other_pk);
1206 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1207 let device_did_str = ed25519_did(&device_pk);
1208 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1209
1210 let att = create_signed_attestation(
1211 &other_kp,
1212 &device_kp,
1213 &other_did,
1214 &device_did_str,
1215 None,
1216 Some(fixed_now() + Duration::days(365)),
1217 );
1218 assert!(!is_device_listed(
1219 &root_did,
1220 &device_did,
1221 &[verified(att)],
1222 fixed_now()
1223 ));
1224 }
1225
1226 #[test]
1227 fn is_device_listed_returns_false_for_wrong_device() {
1228 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1229 let root_did = ed25519_did(&root_pk);
1230 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1231 let device_did_str = ed25519_did(&device_pk);
1232 let (_, other_device_pk) = create_test_keypair(&[4u8; 32]);
1233 let other_device_did_str = ed25519_did(&other_device_pk);
1234 let other_device_did = CanonicalDid::new_unchecked(&other_device_did_str);
1235
1236 let att = create_signed_attestation(
1237 &root_kp,
1238 &device_kp,
1239 &root_did,
1240 &device_did_str,
1241 None,
1242 Some(fixed_now() + Duration::days(365)),
1243 );
1244 assert!(!is_device_listed(
1245 &root_did,
1246 &other_device_did,
1247 &[verified(att)],
1248 fixed_now()
1249 ));
1250 }
1251
1252 fn create_signed_attestation_with_delegation(
1254 issuer_kp: &Ed25519KeyPair,
1255 device_kp: &Ed25519KeyPair,
1256 issuer_did: &str,
1257 subject_did: &str,
1258 delegated_by: Option<CanonicalDid>,
1259 ) -> Attestation {
1260 let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
1261
1262 let mut att = Attestation {
1263 version: 1,
1264 rid: ResourceId::new("test-rid"),
1265 issuer: CanonicalDid::new_unchecked(issuer_did),
1266 subject: CanonicalDid::new_unchecked(subject_did),
1267 device_public_key: Ed25519PublicKey::from_bytes(device_pk).into(),
1268 identity_signature: Ed25519Signature::empty(),
1269 device_signature: Ed25519Signature::empty(),
1270 revoked_at: None,
1271 expires_at: Some(fixed_now() + Duration::days(365)),
1272 timestamp: Some(fixed_now()),
1273 note: None,
1274 payload: None,
1275 delegated_by,
1276 signer_type: None,
1277 environment_claim: None,
1278 commit_sha: None,
1279 commit_message: None,
1280 author: None,
1281 oidc_binding: None,
1282 };
1283
1284 let canonical_bytes = canonicalize_attestation_data(&att.canonical_data()).unwrap();
1285
1286 att.identity_signature =
1287 Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
1288 att.device_signature =
1289 Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1290
1291 att
1292 }
1293
1294 #[tokio::test]
1295 async fn verify_attestation_rejects_tampered_delegated_by() {
1296 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1297 let root_did = ed25519_did(&root_pk);
1298 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1299 let device_did = ed25519_did(&device_pk);
1300
1301 let mut att = create_signed_attestation_with_delegation(
1302 &root_kp,
1303 &device_kp,
1304 &root_did,
1305 &device_did,
1306 Some(CanonicalDid::new_unchecked("did:keri:Eadmin")),
1307 );
1308 assert!(
1309 test_verifier()
1310 .verify_with_keys(&att, &ed(&root_pk))
1311 .await
1312 .is_ok()
1313 );
1314
1315 att.delegated_by = Some(CanonicalDid::new_unchecked("did:keri:Eattacker"));
1316 let result = test_verifier().verify_with_keys(&att, &ed(&root_pk)).await;
1317 assert!(
1318 result.is_err(),
1319 "Attestation should reject tampered delegated_by"
1320 );
1321 }
1322
1323 #[tokio::test]
1324 async fn verify_attestation_valid_with_delegation() {
1325 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1326 let root_did = ed25519_did(&root_pk);
1327 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1328 let device_did = ed25519_did(&device_pk);
1329
1330 let att = create_signed_attestation_with_delegation(
1331 &root_kp,
1332 &device_kp,
1333 &root_did,
1334 &device_did,
1335 Some(CanonicalDid::new_unchecked("did:keri:Eadmin")),
1336 );
1337
1338 let result = test_verifier().verify_with_keys(&att, &ed(&root_pk)).await;
1339 assert!(
1340 result.is_ok(),
1341 "Attestation with delegation should verify: {:?}",
1342 result.err()
1343 );
1344 }
1345
1346 fn create_signed_attestation_with_timestamp(
1347 issuer_kp: &Ed25519KeyPair,
1348 device_kp: &Ed25519KeyPair,
1349 issuer_did: &str,
1350 subject_did: &str,
1351 timestamp: Option<DateTime<Utc>>,
1352 ) -> Attestation {
1353 let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
1354
1355 let mut att = Attestation {
1356 version: 1,
1357 rid: ResourceId::new("test-rid"),
1358 issuer: CanonicalDid::new_unchecked(issuer_did),
1359 subject: CanonicalDid::new_unchecked(subject_did),
1360 device_public_key: Ed25519PublicKey::from_bytes(device_pk).into(),
1361 identity_signature: Ed25519Signature::empty(),
1362 device_signature: Ed25519Signature::empty(),
1363 revoked_at: None,
1364 expires_at: Some(fixed_now() + Duration::days(365)),
1365 timestamp,
1366 note: None,
1367 payload: None,
1368 delegated_by: None,
1369 signer_type: None,
1370 environment_claim: None,
1371 commit_sha: None,
1372 commit_message: None,
1373 author: None,
1374 oidc_binding: None,
1375 };
1376
1377 let canonical_bytes = canonicalize_attestation_data(&att.canonical_data()).unwrap();
1378
1379 att.identity_signature =
1380 Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
1381 att.device_signature =
1382 Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1383
1384 att
1385 }
1386
1387 #[tokio::test]
1388 async fn verify_attestation_created_1_hour_ago_succeeds() {
1389 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1390 let root_did = ed25519_did(&root_pk);
1391 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1392 let device_did = ed25519_did(&device_pk);
1393
1394 let one_hour_ago = fixed_now() - Duration::hours(1);
1395 let att = create_signed_attestation_with_timestamp(
1396 &root_kp,
1397 &device_kp,
1398 &root_did,
1399 &device_did,
1400 Some(one_hour_ago),
1401 );
1402
1403 let result = test_verifier().verify_with_keys(&att, &ed(&root_pk)).await;
1404 assert!(
1405 result.is_ok(),
1406 "Attestation created 1 hour ago should verify: {:?}",
1407 result.err()
1408 );
1409 }
1410
1411 #[tokio::test]
1412 async fn verify_attestation_created_30_days_ago_succeeds() {
1413 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1414 let root_did = ed25519_did(&root_pk);
1415 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1416 let device_did = ed25519_did(&device_pk);
1417
1418 let thirty_days_ago = fixed_now() - Duration::days(30);
1419 let att = create_signed_attestation_with_timestamp(
1420 &root_kp,
1421 &device_kp,
1422 &root_did,
1423 &device_did,
1424 Some(thirty_days_ago),
1425 );
1426
1427 let result = test_verifier().verify_with_keys(&att, &ed(&root_pk)).await;
1428 assert!(
1429 result.is_ok(),
1430 "Attestation created 30 days ago should verify: {:?}",
1431 result.err()
1432 );
1433 }
1434
1435 #[tokio::test]
1436 async fn verify_attestation_with_future_timestamp_fails() {
1437 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1438 let root_did = ed25519_did(&root_pk);
1439 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1440 let device_did = ed25519_did(&device_pk);
1441
1442 let ten_minutes_future = fixed_now() + Duration::minutes(10);
1443 let att = create_signed_attestation_with_timestamp(
1444 &root_kp,
1445 &device_kp,
1446 &root_did,
1447 &device_did,
1448 Some(ten_minutes_future),
1449 );
1450
1451 let result = test_verifier().verify_with_keys(&att, &ed(&root_pk)).await;
1452 assert!(
1453 result.is_err(),
1454 "Attestation with future timestamp should fail"
1455 );
1456 let err_msg = result.unwrap_err().to_string();
1457 assert!(
1458 err_msg.contains("in the future"),
1459 "Error should mention 'in the future': {}",
1460 err_msg
1461 );
1462 }
1463
1464 #[tokio::test]
1465 async fn verify_device_authorization_returns_valid_for_signed_attestation() {
1466 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1467 let root_did = ed25519_did(&root_pk);
1468 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1469 let device_did_str = ed25519_did(&device_pk);
1470 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1471
1472 let att = create_signed_attestation(
1473 &root_kp,
1474 &device_kp,
1475 &root_did,
1476 &device_did_str,
1477 None,
1478 Some(fixed_now() + Duration::days(365)),
1479 );
1480
1481 let result = test_verifier()
1482 .verify_device_authorization(&root_did, &device_did, &[att], &ed(&root_pk))
1483 .await;
1484 assert!(result.is_ok());
1485 let report = result.unwrap();
1486 assert!(report.is_valid());
1487 assert_eq!(report.chain.len(), 1);
1488 assert!(report.chain[0].valid);
1489 }
1490
1491 #[tokio::test]
1492 async fn verify_device_authorization_returns_broken_chain_for_no_attestations() {
1493 let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1494 let root_did = ed25519_did(&root_pk);
1495 let (_, device_pk) = create_test_keypair(&[2u8; 32]);
1496 let device_did_str = ed25519_did(&device_pk);
1497 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1498
1499 let result = test_verifier()
1500 .verify_device_authorization(&root_did, &device_did, &[], &ed(&root_pk))
1501 .await;
1502 assert!(result.is_ok());
1503 let report = result.unwrap();
1504 assert!(!report.is_valid());
1505 match report.status {
1506 VerificationStatus::BrokenChain { missing_link } => {
1507 assert!(missing_link.contains("No attestation found"));
1508 }
1509 _ => panic!("Expected BrokenChain status, got {:?}", report.status),
1510 }
1511 }
1512
1513 #[tokio::test]
1514 async fn verify_device_authorization_fails_for_forged_signature() {
1515 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1516 let root_did = ed25519_did(&root_pk);
1517 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1518 let device_did_str = ed25519_did(&device_pk);
1519 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1520
1521 let mut att = create_signed_attestation(
1522 &root_kp,
1523 &device_kp,
1524 &root_did,
1525 &device_did_str,
1526 None,
1527 Some(fixed_now() + Duration::days(365)),
1528 );
1529 let mut tampered = *att.identity_signature.as_bytes();
1530 tampered[0] ^= 0xFF;
1531 att.identity_signature = Ed25519Signature::from_bytes(tampered);
1532
1533 let result = test_verifier()
1534 .verify_device_authorization(&root_did, &device_did, &[att], &ed(&root_pk))
1535 .await;
1536 assert!(result.is_ok());
1537 let report = result.unwrap();
1538 assert!(!report.is_valid());
1539 match report.status {
1540 VerificationStatus::InvalidSignature { step } => assert_eq!(step, 0),
1541 _ => panic!("Expected InvalidSignature status, got {:?}", report.status),
1542 }
1543 }
1544
1545 #[tokio::test]
1546 async fn verify_device_authorization_fails_for_wrong_issuer_key() {
1547 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1548 let root_did = ed25519_did(&root_pk);
1549 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1550 let device_did_str = ed25519_did(&device_pk);
1551 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1552 let (_, wrong_pk) = create_test_keypair(&[99u8; 32]);
1553
1554 let att = create_signed_attestation(
1555 &root_kp,
1556 &device_kp,
1557 &root_did,
1558 &device_did_str,
1559 None,
1560 Some(fixed_now() + Duration::days(365)),
1561 );
1562
1563 let result = test_verifier()
1564 .verify_device_authorization(&root_did, &device_did, &[att], &ed(&wrong_pk))
1565 .await;
1566 assert!(result.is_ok());
1567 let report = result.unwrap();
1568 assert!(!report.is_valid());
1569 match report.status {
1570 VerificationStatus::InvalidSignature { .. } => {}
1571 _ => panic!("Expected InvalidSignature status, got {:?}", report.status),
1572 }
1573 }
1574
1575 #[tokio::test]
1576 async fn verify_device_authorization_checks_expiry_and_revocation() {
1577 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1578 let root_did = ed25519_did(&root_pk);
1579 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1580 let device_did_str = ed25519_did(&device_pk);
1581 let device_did = CanonicalDid::new_unchecked(&device_did_str);
1582
1583 let att_expired = create_signed_attestation(
1584 &root_kp,
1585 &device_kp,
1586 &root_did,
1587 &device_did_str,
1588 None,
1589 Some(fixed_now() - Duration::days(1)),
1590 );
1591 let result = test_verifier()
1592 .verify_device_authorization(&root_did, &device_did, &[att_expired], &ed(&root_pk))
1593 .await;
1594 assert!(result.is_ok());
1595 let report = result.unwrap();
1596 assert!(!report.is_valid());
1597 match report.status {
1598 VerificationStatus::Expired { .. } => {}
1599 _ => panic!("Expected Expired status, got {:?}", report.status),
1600 }
1601
1602 let att_revoked = create_signed_attestation(
1603 &root_kp,
1604 &device_kp,
1605 &root_did,
1606 &device_did_str,
1607 Some(fixed_now()),
1608 Some(fixed_now() + Duration::days(365)),
1609 );
1610 let result = test_verifier()
1611 .verify_device_authorization(&root_did, &device_did, &[att_revoked], &ed(&root_pk))
1612 .await;
1613 assert!(result.is_ok());
1614 let report = result.unwrap();
1615 assert!(!report.is_valid());
1616 match report.status {
1617 VerificationStatus::Revoked { .. } => {}
1618 _ => panic!("Expected Revoked status, got {:?}", report.status),
1619 }
1620 }
1621
1622 fn create_witness_receipt(
1623 witness_kp: &Ed25519KeyPair,
1624 witness_did: &str,
1625 event_said: &str,
1626 seq: u128,
1627 ) -> auths_keri::witness::SignedReceipt {
1628 let receipt = auths_keri::witness::Receipt {
1629 v: auths_keri::VersionString::placeholder(),
1630 t: auths_keri::witness::ReceiptTag,
1631 d: Said::new_unchecked(event_said.to_string()),
1632 i: auths_keri::Prefix::new_unchecked(witness_did.to_string()),
1633 s: auths_keri::KeriSequence::new(seq),
1634 };
1635 let payload = serde_json::to_vec(&receipt).unwrap();
1636 let sig = witness_kp.sign(&payload).as_ref().to_vec();
1637 auths_keri::witness::SignedReceipt {
1638 receipt,
1639 signature: sig,
1640 }
1641 }
1642
1643 #[tokio::test]
1644 async fn verify_chain_with_witnesses_valid() {
1645 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1646 let root_did = ed25519_did(&root_pk);
1647 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1648 let device_did = ed25519_did(&device_pk);
1649
1650 let att = create_signed_attestation(
1651 &root_kp,
1652 &device_kp,
1653 &root_did,
1654 &device_did,
1655 None,
1656 Some(fixed_now() + Duration::days(365)),
1657 );
1658
1659 let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1660 let (w2_kp, w2_pk) = create_test_keypair(&[20u8; 32]);
1661
1662 let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1663 let r2 = create_witness_receipt(&w2_kp, "did:key:w2", "EEvent1", 1);
1664
1665 let witness_keys = vec![
1666 ("did:key:w1".into(), w1_pk.to_vec()),
1667 ("did:key:w2".into(), w2_pk.to_vec()),
1668 ];
1669
1670 let config = crate::witness::WitnessVerifyConfig {
1671 receipts: &[r1, r2],
1672 witness_keys: &witness_keys,
1673 threshold: 2,
1674 };
1675
1676 let report = test_verifier()
1677 .verify_chain_with_witnesses(&[att], &ed(&root_pk), &config)
1678 .await
1679 .unwrap();
1680 assert!(report.is_valid());
1681 assert!(report.witness_quorum.is_some());
1682 let quorum = report.witness_quorum.unwrap();
1683 assert_eq!(quorum.required, 2);
1684 assert_eq!(quorum.verified, 2);
1685 }
1686
1687 #[tokio::test]
1688 async fn verify_chain_with_witnesses_chain_fails() {
1689 let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1690 let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1691 let witness_keys = vec![("did:key:w1".into(), w1_pk.to_vec())];
1692 let config = crate::witness::WitnessVerifyConfig {
1693 receipts: &[r1],
1694 witness_keys: &witness_keys,
1695 threshold: 1,
1696 };
1697
1698 let report = test_verifier()
1699 .verify_chain_with_witnesses(&[], &ed(&[0u8; 32]), &config)
1700 .await
1701 .unwrap();
1702 assert!(!report.is_valid());
1703 match report.status {
1704 VerificationStatus::BrokenChain { .. } => {}
1705 _ => panic!("Expected BrokenChain, got {:?}", report.status),
1706 }
1707 assert!(report.witness_quorum.is_none());
1708 }
1709
1710 #[tokio::test]
1711 async fn verify_chain_with_witnesses_quorum_fails() {
1712 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1713 let root_did = ed25519_did(&root_pk);
1714 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1715 let device_did = ed25519_did(&device_pk);
1716
1717 let att = create_signed_attestation(
1718 &root_kp,
1719 &device_kp,
1720 &root_did,
1721 &device_did,
1722 None,
1723 Some(fixed_now() + Duration::days(365)),
1724 );
1725
1726 let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1727 let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1728 let witness_keys = vec![("did:key:w1".into(), w1_pk.to_vec())];
1729 let config = crate::witness::WitnessVerifyConfig {
1730 receipts: &[r1],
1731 witness_keys: &witness_keys,
1732 threshold: 2,
1733 };
1734
1735 let report = test_verifier()
1736 .verify_chain_with_witnesses(&[att], &ed(&root_pk), &config)
1737 .await
1738 .unwrap();
1739 assert!(!report.is_valid());
1740 match report.status {
1741 VerificationStatus::InsufficientWitnesses { required, verified } => {
1742 assert_eq!(required, 2);
1743 assert_eq!(verified, 1);
1744 }
1745 _ => panic!("Expected InsufficientWitnesses, got {:?}", report.status),
1746 }
1747 assert!(report.witness_quorum.is_some());
1748 assert!(!report.warnings.is_empty());
1749 }
1750
1751 #[tokio::test]
1757 async fn verify_p256_identity_signed_attestation() {
1758 use auths_crypto::RingCryptoProvider;
1759
1760 let (p256_seed, p256_pk_bytes) = RingCryptoProvider::p256_generate().unwrap();
1762 assert_eq!(
1763 p256_pk_bytes.len(),
1764 33,
1765 "P-256 compressed pubkey is 33 bytes"
1766 );
1767 let seed_arr: [u8; 32] = *p256_seed.as_bytes();
1768
1769 let issuer_did = "did:keri:Etest-p256-identity";
1772
1773 let (device_kp, device_pk) = create_test_keypair(&[42u8; 32]);
1775 let device_did = ed25519_did(&device_pk);
1776
1777 let mut att = Attestation {
1778 version: 1,
1779 rid: ResourceId::new("test-rid"),
1780 issuer: CanonicalDid::new_unchecked(issuer_did),
1781 subject: CanonicalDid::new_unchecked(&device_did),
1782 device_public_key: Ed25519PublicKey::from_bytes(device_pk).into(),
1783 identity_signature: Ed25519Signature::empty(),
1784 device_signature: Ed25519Signature::empty(),
1785 revoked_at: None,
1786 expires_at: Some(fixed_now() + Duration::days(365)),
1787 timestamp: Some(fixed_now()),
1788 note: None,
1789 payload: None,
1790 delegated_by: None,
1791 signer_type: None,
1792 environment_claim: None,
1793 commit_sha: None,
1794 commit_message: None,
1795 author: None,
1796 oidc_binding: None,
1797 };
1798
1799 let canonical_bytes = canonicalize_attestation_data(&att.canonical_data()).unwrap();
1800
1801 let p256_sig = RingCryptoProvider::p256_sign(&seed_arr, &canonical_bytes).unwrap();
1803 assert_eq!(p256_sig.len(), 64);
1805 att.identity_signature = Ed25519Signature::try_from_slice(&p256_sig).unwrap();
1806
1807 att.device_signature =
1809 Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1810
1811 let issuer_dpk =
1812 DevicePublicKey::try_new(auths_crypto::CurveType::P256, &p256_pk_bytes).unwrap();
1813
1814 let result = test_verifier().verify_with_keys(&att, &issuer_dpk).await;
1815 assert!(
1816 result.is_ok(),
1817 "P-256-signed attestation should verify with a P-256 DevicePublicKey: {:?}",
1818 result.err()
1819 );
1820 }
1821}