1#[cfg(feature = "native")]
4use crate::core::Capability;
5use crate::core::{
6 Attestation, CanonicalAttestationData, VerifiedAttestation, canonicalize_attestation_data,
7};
8use crate::error::AttestationError;
9use crate::types::{ChainLink, VerificationReport, VerificationStatus};
10#[cfg(feature = "native")]
11use crate::witness::WitnessVerifyConfig;
12use auths_crypto::{CryptoProvider, ED25519_PUBLIC_KEY_LEN};
13use chrono::{DateTime, Duration, Utc};
14use log::debug;
15use serde::Serialize;
16
17const MAX_SKEW_SECS: i64 = 5 * 60;
19
20#[cfg(feature = "native")]
30pub async fn verify_with_keys(
31 att: &Attestation,
32 issuer_pk_bytes: &[u8],
33) -> Result<VerifiedAttestation, AttestationError> {
34 crate::verifier::Verifier::native()
35 .verify_with_keys(att, issuer_pk_bytes)
36 .await
37}
38
39#[cfg(feature = "native")]
46pub async fn verify_with_capability(
47 att: &Attestation,
48 required: &Capability,
49 issuer_pk_bytes: &[u8],
50) -> Result<VerifiedAttestation, AttestationError> {
51 crate::verifier::Verifier::native()
52 .verify_with_capability(att, required, issuer_pk_bytes)
53 .await
54}
55
56#[cfg(feature = "native")]
63pub async fn verify_chain_with_capability(
64 attestations: &[Attestation],
65 required: &Capability,
66 root_pk: &[u8],
67) -> Result<VerificationReport, AttestationError> {
68 crate::verifier::Verifier::native()
69 .verify_chain_with_capability(attestations, required, root_pk)
70 .await
71}
72
73#[cfg(feature = "native")]
80pub async fn verify_at_time(
81 att: &Attestation,
82 issuer_pk_bytes: &[u8],
83 at: DateTime<Utc>,
84) -> Result<VerifiedAttestation, AttestationError> {
85 crate::verifier::Verifier::native()
86 .verify_at_time(att, issuer_pk_bytes, at)
87 .await
88}
89
90#[cfg(feature = "native")]
97pub async fn verify_chain_with_witnesses(
98 attestations: &[Attestation],
99 root_pk: &[u8],
100 witness_config: &WitnessVerifyConfig<'_>,
101) -> Result<VerificationReport, AttestationError> {
102 crate::verifier::Verifier::native()
103 .verify_chain_with_witnesses(attestations, root_pk, witness_config)
104 .await
105}
106
107#[cfg(feature = "native")]
113pub async fn verify_chain(
114 attestations: &[Attestation],
115 root_pk: &[u8],
116) -> Result<VerificationReport, AttestationError> {
117 crate::verifier::Verifier::native()
118 .verify_chain(attestations, root_pk)
119 .await
120}
121
122#[cfg(feature = "native")]
130pub async fn verify_device_authorization(
131 identity_did: &str,
132 device_did: &crate::types::DeviceDID,
133 attestations: &[Attestation],
134 identity_pk: &[u8],
135) -> Result<VerificationReport, AttestationError> {
136 crate::verifier::Verifier::native()
137 .verify_device_authorization(identity_did, device_did, attestations, identity_pk)
138 .await
139}
140
141pub fn did_to_ed25519(did: &str) -> Result<Vec<u8>, AttestationError> {
152 if did.starts_with("did:key:") {
153 auths_crypto::did_key_to_ed25519(did)
154 .map(|k| k.to_vec())
155 .map_err(|e| AttestationError::DidResolutionError(e.to_string()))
156 } else {
157 Err(AttestationError::DidResolutionError(format!(
158 "Cannot extract key from DID method (requires external resolution): {}",
159 did
160 )))
161 }
162}
163
164use crate::types::DeviceDID;
165
166pub fn is_device_listed(
168 identity_did: &str,
169 device_did: &DeviceDID,
170 attestations: &[VerifiedAttestation],
171 now: DateTime<Utc>,
172) -> bool {
173 let device_did_str = device_did.to_string();
174
175 attestations.iter().any(|verified| {
176 let att = verified.inner();
177 if att.issuer != identity_did {
178 return false;
179 }
180 if att.subject.to_string() != device_did_str {
181 return false;
182 }
183 if att.is_revoked() {
184 return false;
185 }
186 if let Some(exp) = att.expires_at
187 && now > exp
188 {
189 return false;
190 }
191 true
192 })
193}
194
195#[derive(Debug, Clone, Serialize)]
205pub struct DeviceLinkVerification {
206 pub valid: bool,
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub error: Option<String>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub key_state: Option<crate::keri::KeriKeyState>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub seal_sequence: Option<u64>,
217}
218
219impl DeviceLinkVerification {
220 fn success(key_state: crate::keri::KeriKeyState, seal_sequence: Option<u64>) -> Self {
221 Self {
222 valid: true,
223 error: None,
224 key_state: Some(key_state),
225 seal_sequence,
226 }
227 }
228
229 fn failure(reason: impl Into<String>) -> Self {
230 Self {
231 valid: false,
232 error: Some(reason.into()),
233 key_state: None,
234 seal_sequence: None,
235 }
236 }
237}
238
239pub async fn verify_device_link(
257 events: &[crate::keri::KeriEvent],
258 attestation: &Attestation,
259 device_did: &str,
260 now: DateTime<Utc>,
261 provider: &dyn CryptoProvider,
262) -> DeviceLinkVerification {
263 let key_state = match crate::keri::verify_kel(events, provider).await {
264 Ok(ks) => ks,
265 Err(e) => return DeviceLinkVerification::failure(format!("KEL verification failed: {e}")),
266 };
267
268 if attestation.subject.to_string() != device_did {
269 return DeviceLinkVerification::failure(format!(
270 "Device DID mismatch: attestation subject is '{}', expected '{device_did}'",
271 attestation.subject
272 ));
273 }
274
275 if let Err(e) =
276 verify_with_keys_at(attestation, &key_state.current_key, now, true, provider).await
277 {
278 return DeviceLinkVerification::failure(format!("Attestation verification failed: {e}"));
279 }
280
281 let seal_sequence = compute_attestation_seal_digest(attestation)
282 .ok()
283 .and_then(|digest| crate::keri::find_seal_in_kel(events, digest.as_str()));
284
285 DeviceLinkVerification::success(key_state, seal_sequence)
286}
287
288pub fn compute_attestation_seal_digest(
293 attestation: &Attestation,
294) -> Result<String, AttestationError> {
295 let data = CanonicalAttestationData {
296 version: attestation.version,
297 rid: &attestation.rid,
298 issuer: &attestation.issuer,
299 subject: &attestation.subject,
300 device_public_key: attestation.device_public_key.as_bytes(),
301 payload: &attestation.payload,
302 timestamp: &attestation.timestamp,
303 expires_at: &attestation.expires_at,
304 revoked_at: &attestation.revoked_at,
305 note: &attestation.note,
306 role: attestation.role.as_ref().map(|r| r.as_str()),
307 capabilities: if attestation.capabilities.is_empty() {
308 None
309 } else {
310 Some(&attestation.capabilities)
311 },
312 delegated_by: attestation.delegated_by.as_ref(),
313 signer_type: attestation.signer_type.as_ref(),
314 };
315 let canonical = canonicalize_attestation_data(&data)?;
316 Ok(crate::keri::compute_said(&canonical).to_string())
317}
318
319pub(crate) async fn verify_with_keys_at(
324 att: &Attestation,
325 issuer_pk_bytes: &[u8],
326 at: DateTime<Utc>,
327 check_skew: bool,
328 provider: &dyn CryptoProvider,
329) -> Result<(), AttestationError> {
330 let reference_time = at;
331
332 if let Some(revoked_at) = att.revoked_at
334 && revoked_at <= reference_time
335 {
336 return Err(AttestationError::VerificationError(
337 "Attestation revoked".to_string(),
338 ));
339 }
340
341 if let Some(exp) = att.expires_at
343 && reference_time > exp
344 {
345 return Err(AttestationError::VerificationError(format!(
346 "Attestation expired on {}",
347 exp.to_rfc3339()
348 )));
349 }
350
351 if check_skew
353 && let Some(ts) = att.timestamp
354 && ts > reference_time + Duration::seconds(MAX_SKEW_SECS)
355 {
356 return Err(AttestationError::VerificationError(format!(
357 "Attestation timestamp ({}) is in the future",
358 ts.to_rfc3339(),
359 )));
360 }
361
362 if !att.identity_signature.is_empty() && issuer_pk_bytes.len() != ED25519_PUBLIC_KEY_LEN {
364 return Err(AttestationError::VerificationError(format!(
365 "Provided issuer public key has invalid length: {}",
366 issuer_pk_bytes.len()
367 )));
368 }
369
370 let data_to_canonicalize = CanonicalAttestationData {
372 version: att.version,
373 rid: &att.rid,
374 issuer: &att.issuer,
375 subject: &att.subject,
376 device_public_key: att.device_public_key.as_bytes(),
377 payload: &att.payload,
378 timestamp: &att.timestamp,
379 expires_at: &att.expires_at,
380 revoked_at: &att.revoked_at,
381 note: &att.note,
382 role: att.role.as_ref().map(|r| r.as_str()),
383 capabilities: if att.capabilities.is_empty() {
384 None
385 } else {
386 Some(&att.capabilities)
387 },
388 delegated_by: att.delegated_by.as_ref(),
389 signer_type: att.signer_type.as_ref(),
390 };
391 let canonical_json_bytes = canonicalize_attestation_data(&data_to_canonicalize)?;
392 let data_to_verify = canonical_json_bytes.as_slice();
393 debug!(
394 "(Verify) Canonical data: {}",
395 String::from_utf8_lossy(&canonical_json_bytes)
396 );
397
398 if !att.identity_signature.is_empty() {
400 provider
401 .verify_ed25519(
402 issuer_pk_bytes,
403 data_to_verify,
404 att.identity_signature.as_bytes(),
405 )
406 .await
407 .map_err(|_| {
408 AttestationError::VerificationError(
409 "Issuer signature verification failed".to_string(),
410 )
411 })?;
412 debug!("(Verify) Issuer signature verified successfully.");
413 } else {
414 debug!(
415 "(Verify) No identity signature present (device-only attestation), skipping issuer check."
416 );
417 }
418
419 provider
421 .verify_ed25519(
422 att.device_public_key.as_bytes(),
423 data_to_verify,
424 att.device_signature.as_bytes(),
425 )
426 .await
427 .map_err(|_| {
428 AttestationError::VerificationError("Device signature verification failed".to_string())
429 })?;
430 debug!("(Verify) Device signature verified successfully.");
431
432 Ok(())
433}
434
435pub(crate) async fn verify_chain_inner(
436 attestations: &[Attestation],
437 root_pk: &[u8],
438 provider: &dyn CryptoProvider,
439 now: DateTime<Utc>,
440) -> Result<VerificationReport, AttestationError> {
441 if attestations.is_empty() {
442 return Ok(VerificationReport::with_status(
443 VerificationStatus::BrokenChain {
444 missing_link: "empty chain".to_string(),
445 },
446 vec![],
447 ));
448 }
449
450 let mut chain_links: Vec<ChainLink> = Vec::with_capacity(attestations.len());
451
452 let first_att = &attestations[0];
453 match verify_single_attestation(first_att, root_pk, 0, provider, now).await {
454 Ok(link) => chain_links.push(link),
455 Err((status, link)) => {
456 chain_links.push(link);
457 return Ok(VerificationReport::with_status(status, chain_links));
458 }
459 }
460
461 for (idx, att) in attestations.iter().enumerate().skip(1) {
462 let prev_att = &attestations[idx - 1];
463
464 if att.issuer.as_str() != prev_att.subject.as_str() {
465 let link = ChainLink::invalid(
466 att.issuer.to_string(),
467 att.subject.to_string(),
468 format!(
469 "Chain broken: expected issuer '{}', got '{}'",
470 prev_att.subject, att.issuer
471 ),
472 );
473 chain_links.push(link);
474 return Ok(VerificationReport::with_status(
475 VerificationStatus::BrokenChain {
476 missing_link: format!(
477 "Issuer mismatch at step {}: expected '{}', got '{}'",
478 idx, prev_att.subject, att.issuer
479 ),
480 },
481 chain_links,
482 ));
483 }
484
485 let issuer_pk = prev_att.device_public_key.as_bytes().as_slice();
486
487 match verify_single_attestation(att, issuer_pk, idx, provider, now).await {
488 Ok(link) => chain_links.push(link),
489 Err((status, link)) => {
490 chain_links.push(link);
491 return Ok(VerificationReport::with_status(status, chain_links));
492 }
493 }
494 }
495
496 Ok(VerificationReport::valid(chain_links))
497}
498
499pub(crate) async fn verify_device_authorization_inner(
500 identity_did: &str,
501 device_did: &DeviceDID,
502 attestations: &[Attestation],
503 identity_pk: &[u8],
504 provider: &dyn CryptoProvider,
505 now: DateTime<Utc>,
506) -> Result<VerificationReport, AttestationError> {
507 let device_did_str = device_did.to_string();
508
509 let matching: Vec<&Attestation> = attestations
510 .iter()
511 .filter(|a| a.issuer == identity_did && a.subject.to_string() == device_did_str)
512 .collect();
513
514 if matching.is_empty() {
515 return Ok(VerificationReport::with_status(
516 VerificationStatus::BrokenChain {
517 missing_link: format!(
518 "No attestation found for device {} under {}",
519 device_did_str, identity_did
520 ),
521 },
522 vec![],
523 ));
524 }
525
526 match verify_single_attestation(matching[0], identity_pk, 0, provider, now).await {
527 Ok(link) => Ok(VerificationReport::valid(vec![link])),
528 Err((status, link)) => Ok(VerificationReport::with_status(status, vec![link])),
529 }
530}
531
532async fn verify_single_attestation(
533 att: &Attestation,
534 issuer_pk: &[u8],
535 step: usize,
536 provider: &dyn CryptoProvider,
537 now: DateTime<Utc>,
538) -> Result<ChainLink, (VerificationStatus, ChainLink)> {
539 let issuer = att.issuer.to_string();
540 let subject = att.subject.to_string();
541
542 if att.is_revoked() {
543 return Err((
544 VerificationStatus::Revoked { at: att.revoked_at },
545 ChainLink::invalid(issuer, subject, "Attestation revoked".to_string()),
546 ));
547 }
548
549 if let Some(exp) = att.expires_at
550 && now > exp
551 {
552 return Err((
553 VerificationStatus::Expired { at: exp },
554 ChainLink::invalid(
555 issuer,
556 subject,
557 format!("Attestation expired on {}", exp.to_rfc3339()),
558 ),
559 ));
560 }
561
562 match verify_with_keys_at(att, issuer_pk, now, true, provider).await {
563 Ok(()) => Ok(ChainLink::valid(issuer, subject)),
564 Err(e) => Err((
565 VerificationStatus::InvalidSignature { step },
566 ChainLink::invalid(issuer, subject, e.to_string()),
567 )),
568 }
569}
570
571#[cfg(all(test, not(target_arch = "wasm32")))]
572mod tests {
573 use super::*;
574 use crate::clock::ClockProvider;
575 use crate::core::{Capability, Ed25519PublicKey, Ed25519Signature, ResourceId, Role};
576 use crate::keri::Said;
577 use crate::types::{DeviceDID, IdentityDID};
578 use crate::verifier::Verifier;
579 use auths_crypto::RingCryptoProvider;
580 use auths_crypto::testing::create_test_keypair;
581 use chrono::{DateTime, Duration, TimeZone, Utc};
582 use ring::signature::{Ed25519KeyPair, KeyPair};
583 use std::sync::Arc;
584
585 struct TestClock(DateTime<Utc>);
586 impl ClockProvider for TestClock {
587 fn now(&self) -> DateTime<Utc> {
588 self.0
589 }
590 }
591
592 fn fixed_now() -> DateTime<Utc> {
593 Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
594 }
595
596 fn test_verifier() -> Verifier {
597 Verifier::new(
598 Arc::new(RingCryptoProvider),
599 Arc::new(TestClock(fixed_now())),
600 )
601 }
602
603 fn create_signed_attestation(
605 issuer_kp: &Ed25519KeyPair,
606 device_kp: &Ed25519KeyPair,
607 issuer_did: &str,
608 subject_did: &str,
609 revoked_at: Option<DateTime<Utc>>,
610 expires_at: Option<DateTime<Utc>>,
611 ) -> Attestation {
612 let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
613
614 let mut att = Attestation {
615 version: 1,
616 rid: ResourceId::new("test-rid"),
617 issuer: IdentityDID::new(issuer_did),
618 subject: DeviceDID::new(subject_did),
619 device_public_key: Ed25519PublicKey::from_bytes(device_pk),
620 identity_signature: Ed25519Signature::empty(),
621 device_signature: Ed25519Signature::empty(),
622 revoked_at,
623 expires_at,
624 timestamp: Some(fixed_now()),
625 note: None,
626 payload: None,
627 role: None,
628 capabilities: vec![],
629 delegated_by: None,
630 signer_type: None,
631 };
632
633 let data = CanonicalAttestationData {
634 version: att.version,
635 rid: &att.rid,
636 issuer: &att.issuer,
637 subject: &att.subject,
638 device_public_key: att.device_public_key.as_bytes(),
639 payload: &att.payload,
640 timestamp: &att.timestamp,
641 expires_at: &att.expires_at,
642 revoked_at: &att.revoked_at,
643 note: &att.note,
644 role: att.role.as_ref().map(|r| r.as_str()),
645 capabilities: if att.capabilities.is_empty() {
646 None
647 } else {
648 Some(&att.capabilities)
649 },
650 delegated_by: att.delegated_by.as_ref(),
651 signer_type: att.signer_type.as_ref(),
652 };
653 let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
654
655 att.identity_signature =
656 Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
657 att.device_signature =
658 Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
659
660 att
661 }
662
663 #[tokio::test]
664 async fn verify_chain_empty_returns_broken_chain() {
665 let result = test_verifier().verify_chain(&[], &[0u8; 32]).await.unwrap();
666 assert!(!result.is_valid());
667 match result.status {
668 VerificationStatus::BrokenChain { missing_link } => {
669 assert_eq!(missing_link, "empty chain");
670 }
671 _ => panic!("Expected BrokenChain status"),
672 }
673 assert!(result.chain.is_empty());
674 }
675
676 #[tokio::test]
677 async fn verify_chain_single_valid_attestation() {
678 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
679 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
680 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
681 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
682
683 let att = create_signed_attestation(
684 &root_kp,
685 &device_kp,
686 &root_did,
687 &device_did,
688 None,
689 Some(fixed_now() + Duration::days(365)),
690 );
691
692 let result = test_verifier()
693 .verify_chain(&[att], &root_pk)
694 .await
695 .unwrap();
696 assert!(result.is_valid());
697 assert_eq!(result.chain.len(), 1);
698 assert!(result.chain[0].valid);
699 }
700
701 #[tokio::test]
702 async fn verify_chain_revoked_attestation_returns_revoked() {
703 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
704 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
705 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
706 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
707
708 let att = create_signed_attestation(
709 &root_kp,
710 &device_kp,
711 &root_did,
712 &device_did,
713 Some(fixed_now()),
714 Some(fixed_now() + Duration::days(365)),
715 );
716
717 let result = test_verifier()
718 .verify_chain(&[att], &root_pk)
719 .await
720 .unwrap();
721 assert!(!result.is_valid());
722 match result.status {
723 VerificationStatus::Revoked { .. } => {}
724 _ => panic!("Expected Revoked status, got {:?}", result.status),
725 }
726 }
727
728 #[tokio::test]
729 async fn verify_chain_expired_attestation_returns_expired() {
730 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
731 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
732 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
733 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
734
735 let att = create_signed_attestation(
736 &root_kp,
737 &device_kp,
738 &root_did,
739 &device_did,
740 None,
741 Some(fixed_now() - Duration::days(1)),
742 );
743
744 let result = test_verifier()
745 .verify_chain(&[att], &root_pk)
746 .await
747 .unwrap();
748 assert!(!result.is_valid());
749 match result.status {
750 VerificationStatus::Expired { .. } => {}
751 _ => panic!("Expected Expired status, got {:?}", result.status),
752 }
753 }
754
755 #[tokio::test]
756 async fn verify_chain_invalid_signature_returns_invalid_signature() {
757 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
758 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
759 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
760 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
761
762 let mut att = create_signed_attestation(
763 &root_kp,
764 &device_kp,
765 &root_did,
766 &device_did,
767 None,
768 Some(fixed_now() + Duration::days(365)),
769 );
770 let mut tampered = *att.identity_signature.as_bytes();
771 tampered[0] ^= 0xFF;
772 att.identity_signature = Ed25519Signature::from_bytes(tampered);
773
774 let result = test_verifier()
775 .verify_chain(&[att], &root_pk)
776 .await
777 .unwrap();
778 assert!(!result.is_valid());
779 match result.status {
780 VerificationStatus::InvalidSignature { step } => {
781 assert_eq!(step, 0);
782 }
783 _ => panic!("Expected InvalidSignature status, got {:?}", result.status),
784 }
785 }
786
787 #[tokio::test]
788 async fn verify_chain_broken_link_returns_broken_chain() {
789 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
790 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
791 let (device1_kp, device1_pk) = create_test_keypair(&[2u8; 32]);
792 let device1_did = auths_crypto::ed25519_pubkey_to_did_key(&device1_pk);
793 let (device2_kp, device2_pk) = create_test_keypair(&[3u8; 32]);
794 let device2_did = auths_crypto::ed25519_pubkey_to_did_key(&device2_pk);
795
796 let att1 = create_signed_attestation(
797 &root_kp,
798 &device1_kp,
799 &root_did,
800 &device1_did,
801 None,
802 Some(fixed_now() + Duration::days(365)),
803 );
804 let att2 = create_signed_attestation(
805 &device1_kp,
806 &device2_kp,
807 &root_did, &device2_did,
809 None,
810 Some(fixed_now() + Duration::days(365)),
811 );
812
813 let result = test_verifier()
814 .verify_chain(&[att1, att2], &root_pk)
815 .await
816 .unwrap();
817 assert!(!result.is_valid());
818 match result.status {
819 VerificationStatus::BrokenChain { missing_link } => {
820 assert!(missing_link.contains("Issuer mismatch"));
821 }
822 _ => panic!("Expected BrokenChain status, got {:?}", result.status),
823 }
824 }
825
826 #[tokio::test]
827 async fn verify_chain_valid_three_level_chain() {
828 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
829 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
830 let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
831 let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
832 let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
833 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
834
835 let att1 = create_signed_attestation(
836 &root_kp,
837 &identity_kp,
838 &root_did,
839 &identity_did,
840 None,
841 Some(fixed_now() + Duration::days(365)),
842 );
843 let att2 = create_signed_attestation(
844 &identity_kp,
845 &device_kp,
846 &identity_did,
847 &device_did,
848 None,
849 Some(fixed_now() + Duration::days(365)),
850 );
851
852 let result = test_verifier()
853 .verify_chain(&[att1, att2], &root_pk)
854 .await
855 .unwrap();
856 assert!(result.is_valid());
857 assert_eq!(result.chain.len(), 2);
858 assert!(result.chain[0].valid);
859 assert!(result.chain[1].valid);
860 }
861
862 #[tokio::test]
863 async fn verify_chain_revoked_intermediate_returns_revoked() {
864 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
865 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
866 let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
867 let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
868 let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
869 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
870
871 let att1 = create_signed_attestation(
872 &root_kp,
873 &identity_kp,
874 &root_did,
875 &identity_did,
876 None,
877 Some(fixed_now() + Duration::days(365)),
878 );
879 let att2 = create_signed_attestation(
880 &identity_kp,
881 &device_kp,
882 &identity_did,
883 &device_did,
884 Some(fixed_now()),
885 Some(fixed_now() + Duration::days(365)),
886 );
887
888 let result = test_verifier()
889 .verify_chain(&[att1, att2], &root_pk)
890 .await
891 .unwrap();
892 assert!(!result.is_valid());
893 match result.status {
894 VerificationStatus::Revoked { .. } => {}
895 _ => panic!("Expected Revoked status, got {:?}", result.status),
896 }
897 assert_eq!(result.chain.len(), 2);
898 assert!(result.chain[0].valid);
899 assert!(!result.chain[1].valid);
900 }
901
902 #[tokio::test]
903 async fn verify_at_time_valid_before_expiration() {
904 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
905 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
906 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
907 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
908
909 let expires = fixed_now() + Duration::days(30);
910 let att = create_signed_attestation(
911 &root_kp,
912 &device_kp,
913 &root_did,
914 &device_did,
915 None,
916 Some(expires),
917 );
918
919 let verification_time = fixed_now() + Duration::days(10);
920 let result = test_verifier()
921 .verify_at_time(&att, &root_pk, verification_time)
922 .await;
923 assert!(result.is_ok());
924 }
925
926 #[tokio::test]
927 async fn verify_at_time_expired_after_expiration() {
928 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
929 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
930 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
931 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
932
933 let expires = fixed_now() + Duration::days(30);
934 let att = create_signed_attestation(
935 &root_kp,
936 &device_kp,
937 &root_did,
938 &device_did,
939 None,
940 Some(expires),
941 );
942
943 let verification_time = fixed_now() + Duration::days(60);
944 let result = test_verifier()
945 .verify_at_time(&att, &root_pk, verification_time)
946 .await;
947 assert!(result.is_err());
948 assert!(result.unwrap_err().to_string().contains("expired"));
949 }
950
951 #[tokio::test]
952 async fn verify_at_time_signature_always_checked() {
953 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
954 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
955 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
956 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
957
958 let mut att = create_signed_attestation(
959 &root_kp,
960 &device_kp,
961 &root_did,
962 &device_did,
963 None,
964 Some(fixed_now() + Duration::days(365)),
965 );
966 let mut tampered = *att.identity_signature.as_bytes();
967 tampered[0] ^= 0xFF;
968 att.identity_signature = Ed25519Signature::from_bytes(tampered);
969
970 let verification_time = fixed_now() - Duration::days(10);
971 let result = test_verifier()
972 .verify_at_time(&att, &root_pk, verification_time)
973 .await;
974 assert!(result.is_err());
975 assert!(
976 result
977 .unwrap_err()
978 .to_string()
979 .contains("signature verification failed")
980 );
981 }
982
983 #[tokio::test]
984 async fn verify_at_time_with_past_time_skips_skew_check() {
985 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
986 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
987 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
988 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
989
990 let att = create_signed_attestation(
991 &root_kp,
992 &device_kp,
993 &root_did,
994 &device_did,
995 None,
996 Some(fixed_now() + Duration::days(365)),
997 );
998
999 let verification_time = fixed_now() - Duration::days(30);
1000 let result = test_verifier()
1001 .verify_at_time(&att, &root_pk, verification_time)
1002 .await;
1003 assert!(result.is_ok());
1004 }
1005
1006 #[tokio::test]
1007 async fn verify_with_keys_still_works() {
1008 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1009 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1010 let (device_kp, _) = create_test_keypair(&[2u8; 32]);
1011 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1012
1013 let att = create_signed_attestation(
1014 &root_kp,
1015 &device_kp,
1016 &root_did,
1017 &device_did,
1018 None,
1019 Some(fixed_now() + Duration::days(365)),
1020 );
1021
1022 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1023 assert!(result.is_ok());
1024 }
1025
1026 fn verified(att: Attestation) -> VerifiedAttestation {
1028 VerifiedAttestation::dangerous_from_unchecked(att)
1029 }
1030
1031 #[test]
1032 fn is_device_listed_returns_true_for_valid_attestation() {
1033 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1034 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1035 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1036 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1037 let device_did = DeviceDID::new(&device_did_str);
1038
1039 let att = create_signed_attestation(
1040 &root_kp,
1041 &device_kp,
1042 &root_did,
1043 &device_did_str,
1044 None,
1045 Some(fixed_now() + Duration::days(365)),
1046 );
1047
1048 assert!(is_device_listed(
1049 &root_did,
1050 &device_did,
1051 &[verified(att)],
1052 fixed_now()
1053 ));
1054 }
1055
1056 #[test]
1057 fn is_device_listed_returns_false_for_no_attestations() {
1058 let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1059 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1060 let (_, device_pk) = create_test_keypair(&[2u8; 32]);
1061 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1062 let device_did = DeviceDID::new(&device_did_str);
1063
1064 assert!(!is_device_listed(&root_did, &device_did, &[], fixed_now()));
1065 }
1066
1067 #[test]
1068 fn is_device_listed_returns_false_for_expired_attestation() {
1069 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1070 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1071 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1072 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1073 let device_did = DeviceDID::new(&device_did_str);
1074
1075 let att = create_signed_attestation(
1076 &root_kp,
1077 &device_kp,
1078 &root_did,
1079 &device_did_str,
1080 None,
1081 Some(fixed_now() - Duration::days(1)),
1082 );
1083
1084 assert!(!is_device_listed(
1085 &root_did,
1086 &device_did,
1087 &[verified(att)],
1088 fixed_now()
1089 ));
1090 }
1091
1092 #[test]
1093 fn is_device_listed_returns_false_for_revoked_attestation() {
1094 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1095 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1096 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1097 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1098 let device_did = DeviceDID::new(&device_did_str);
1099
1100 let att = create_signed_attestation(
1101 &root_kp,
1102 &device_kp,
1103 &root_did,
1104 &device_did_str,
1105 Some(fixed_now()),
1106 Some(fixed_now() + Duration::days(365)),
1107 );
1108
1109 assert!(!is_device_listed(
1110 &root_did,
1111 &device_did,
1112 &[verified(att)],
1113 fixed_now()
1114 ));
1115 }
1116
1117 #[test]
1118 fn is_device_listed_returns_true_if_one_valid_among_many() {
1119 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1120 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1121 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1122 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1123 let device_did = DeviceDID::new(&device_did_str);
1124
1125 let att_expired = verified(create_signed_attestation(
1126 &root_kp,
1127 &device_kp,
1128 &root_did,
1129 &device_did_str,
1130 None,
1131 Some(fixed_now() - Duration::days(1)),
1132 ));
1133 let att_revoked = verified(create_signed_attestation(
1134 &root_kp,
1135 &device_kp,
1136 &root_did,
1137 &device_did_str,
1138 Some(fixed_now()),
1139 Some(fixed_now() + Duration::days(365)),
1140 ));
1141 let att_valid = verified(create_signed_attestation(
1142 &root_kp,
1143 &device_kp,
1144 &root_did,
1145 &device_did_str,
1146 None,
1147 Some(fixed_now() + Duration::days(365)),
1148 ));
1149
1150 assert!(is_device_listed(
1151 &root_did,
1152 &device_did,
1153 &[att_expired, att_revoked, att_valid],
1154 fixed_now()
1155 ));
1156 }
1157
1158 #[test]
1159 fn is_device_listed_returns_false_for_wrong_identity() {
1160 let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1161 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1162 let (other_kp, other_pk) = create_test_keypair(&[3u8; 32]);
1163 let other_did = auths_crypto::ed25519_pubkey_to_did_key(&other_pk);
1164 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1165 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1166 let device_did = DeviceDID::new(&device_did_str);
1167
1168 let att = create_signed_attestation(
1169 &other_kp,
1170 &device_kp,
1171 &other_did,
1172 &device_did_str,
1173 None,
1174 Some(fixed_now() + Duration::days(365)),
1175 );
1176 assert!(!is_device_listed(
1177 &root_did,
1178 &device_did,
1179 &[verified(att)],
1180 fixed_now()
1181 ));
1182 }
1183
1184 #[test]
1185 fn is_device_listed_returns_false_for_wrong_device() {
1186 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1187 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1188 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1189 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1190 let (_, other_device_pk) = create_test_keypair(&[4u8; 32]);
1191 let other_device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&other_device_pk);
1192 let other_device_did = DeviceDID::new(&other_device_did_str);
1193
1194 let att = create_signed_attestation(
1195 &root_kp,
1196 &device_kp,
1197 &root_did,
1198 &device_did_str,
1199 None,
1200 Some(fixed_now() + Duration::days(365)),
1201 );
1202 assert!(!is_device_listed(
1203 &root_did,
1204 &other_device_did,
1205 &[verified(att)],
1206 fixed_now()
1207 ));
1208 }
1209
1210 fn create_signed_attestation_with_org_fields(
1212 issuer_kp: &Ed25519KeyPair,
1213 device_kp: &Ed25519KeyPair,
1214 issuer_did: &str,
1215 subject_did: &str,
1216 role: Option<Role>,
1217 capabilities: Vec<Capability>,
1218 ) -> Attestation {
1219 let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
1220
1221 let mut att = Attestation {
1222 version: 1,
1223 rid: ResourceId::new("test-rid"),
1224 issuer: IdentityDID::new(issuer_did),
1225 subject: DeviceDID::new(subject_did),
1226 device_public_key: Ed25519PublicKey::from_bytes(device_pk),
1227 identity_signature: Ed25519Signature::empty(),
1228 device_signature: Ed25519Signature::empty(),
1229 revoked_at: None,
1230 expires_at: Some(fixed_now() + Duration::days(365)),
1231 timestamp: Some(fixed_now()),
1232 note: None,
1233 payload: None,
1234 role,
1235 capabilities: capabilities.clone(),
1236 delegated_by: None,
1237 signer_type: None,
1238 };
1239
1240 let caps_ref = if att.capabilities.is_empty() {
1241 None
1242 } else {
1243 Some(&att.capabilities)
1244 };
1245 let data = CanonicalAttestationData {
1246 version: att.version,
1247 rid: &att.rid,
1248 issuer: &att.issuer,
1249 subject: &att.subject,
1250 device_public_key: att.device_public_key.as_bytes(),
1251 payload: &att.payload,
1252 timestamp: &att.timestamp,
1253 expires_at: &att.expires_at,
1254 revoked_at: &att.revoked_at,
1255 note: &att.note,
1256 role: att.role.as_ref().map(|r| r.as_str()),
1257 capabilities: caps_ref,
1258 delegated_by: att.delegated_by.as_ref(),
1259 signer_type: att.signer_type.as_ref(),
1260 };
1261 let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
1262
1263 att.identity_signature =
1264 Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
1265 att.device_signature =
1266 Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1267
1268 att
1269 }
1270
1271 fn create_signed_attestation_with_caps(
1272 issuer_kp: &Ed25519KeyPair,
1273 device_kp: &Ed25519KeyPair,
1274 issuer_did: &str,
1275 subject_did: &str,
1276 capabilities: Vec<Capability>,
1277 ) -> Attestation {
1278 create_signed_attestation_with_org_fields(
1279 issuer_kp,
1280 device_kp,
1281 issuer_did,
1282 subject_did,
1283 None,
1284 capabilities,
1285 )
1286 }
1287
1288 #[tokio::test]
1289 async fn verify_with_capability_succeeds_when_capability_present() {
1290 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1291 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1292 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1293 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1294
1295 let att = create_signed_attestation_with_caps(
1296 &root_kp,
1297 &device_kp,
1298 &root_did,
1299 &device_did,
1300 vec![Capability::sign_commit(), Capability::sign_release()],
1301 );
1302
1303 let result = test_verifier()
1304 .verify_with_capability(&att, &Capability::sign_commit(), &root_pk)
1305 .await;
1306 assert!(result.is_ok());
1307 }
1308
1309 #[tokio::test]
1310 async fn verify_with_capability_fails_when_capability_missing() {
1311 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1312 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1313 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1314 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1315
1316 let att = create_signed_attestation_with_caps(
1317 &root_kp,
1318 &device_kp,
1319 &root_did,
1320 &device_did,
1321 vec![Capability::sign_commit()],
1322 );
1323
1324 let result = test_verifier()
1325 .verify_with_capability(&att, &Capability::manage_members(), &root_pk)
1326 .await;
1327 assert!(result.is_err());
1328 match result {
1329 Err(AttestationError::MissingCapability {
1330 required,
1331 available,
1332 }) => {
1333 assert_eq!(required, Capability::manage_members());
1334 assert_eq!(available, vec![Capability::sign_commit()]);
1335 }
1336 _ => panic!("Expected MissingCapability error"),
1337 }
1338 }
1339
1340 #[tokio::test]
1341 async fn verify_with_capability_fails_for_invalid_signature() {
1342 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1343 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1344 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1345 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1346
1347 let mut att = create_signed_attestation_with_caps(
1348 &root_kp,
1349 &device_kp,
1350 &root_did,
1351 &device_did,
1352 vec![Capability::sign_commit()],
1353 );
1354 let mut tampered = *att.identity_signature.as_bytes();
1355 tampered[0] ^= 0xFF;
1356 att.identity_signature = Ed25519Signature::from_bytes(tampered);
1357
1358 let result = test_verifier()
1359 .verify_with_capability(&att, &Capability::sign_commit(), &root_pk)
1360 .await;
1361 assert!(result.is_err());
1362 match result {
1363 Err(AttestationError::VerificationError(_)) => {}
1364 _ => panic!("Expected VerificationError, got {:?}", result),
1365 }
1366 }
1367
1368 #[tokio::test]
1369 async fn verify_chain_with_capability_succeeds_for_single_link() {
1370 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1371 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1372 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1373 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1374
1375 let att = create_signed_attestation_with_caps(
1376 &root_kp,
1377 &device_kp,
1378 &root_did,
1379 &device_did,
1380 vec![Capability::sign_commit(), Capability::sign_release()],
1381 );
1382
1383 let result = test_verifier()
1384 .verify_chain_with_capability(&[att], &Capability::sign_commit(), &root_pk)
1385 .await;
1386 assert!(result.is_ok());
1387 assert!(result.unwrap().is_valid());
1388 }
1389
1390 #[tokio::test]
1391 async fn verify_chain_with_capability_uses_intersection() {
1392 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1393 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1394 let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
1395 let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
1396 let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
1397 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1398
1399 let att1 = create_signed_attestation_with_org_fields(
1400 &root_kp,
1401 &identity_kp,
1402 &root_did,
1403 &identity_did,
1404 None,
1405 vec![Capability::sign_commit(), Capability::manage_members()],
1406 );
1407 let att2 = create_signed_attestation_with_org_fields(
1408 &identity_kp,
1409 &device_kp,
1410 &identity_did,
1411 &device_did,
1412 None,
1413 vec![Capability::sign_commit(), Capability::sign_release()],
1414 );
1415
1416 let result = test_verifier()
1417 .verify_chain_with_capability(
1418 &[att1.clone(), att2.clone()],
1419 &Capability::sign_commit(),
1420 &root_pk,
1421 )
1422 .await;
1423 assert!(result.is_ok());
1424
1425 let result = test_verifier()
1426 .verify_chain_with_capability(&[att1, att2], &Capability::manage_members(), &root_pk)
1427 .await;
1428 assert!(result.is_err());
1429 match result {
1430 Err(AttestationError::MissingCapability { required, .. }) => {
1431 assert_eq!(required, Capability::manage_members());
1432 }
1433 _ => panic!("Expected MissingCapability error"),
1434 }
1435 }
1436
1437 #[tokio::test]
1438 async fn verify_chain_with_capability_returns_report_on_invalid_chain() {
1439 let result = test_verifier()
1440 .verify_chain_with_capability(&[], &Capability::sign_commit(), &[0u8; 32])
1441 .await;
1442 assert!(result.is_ok());
1443 let report = result.unwrap();
1444 assert!(!report.is_valid());
1445 }
1446
1447 #[tokio::test]
1448 async fn verify_attestation_rejects_tampered_role() {
1449 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1450 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1451 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1452 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1453
1454 let mut att = create_signed_attestation_with_org_fields(
1455 &root_kp,
1456 &device_kp,
1457 &root_did,
1458 &device_did,
1459 Some(Role::Member),
1460 vec![Capability::sign_commit()],
1461 );
1462
1463 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1464 assert!(result.is_ok(), "Attestation should verify before tampering");
1465
1466 att.role = Some(Role::Admin);
1467 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1468 assert!(result.is_err(), "Attestation should reject tampered role");
1469 let err_msg = result.unwrap_err().to_string();
1470 assert!(
1471 err_msg.contains("signature"),
1472 "Error should mention signature failure: {}",
1473 err_msg
1474 );
1475 }
1476
1477 #[tokio::test]
1478 async fn verify_attestation_rejects_tampered_capabilities() {
1479 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1480 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1481 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1482 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1483
1484 let mut att = create_signed_attestation_with_org_fields(
1485 &root_kp,
1486 &device_kp,
1487 &root_did,
1488 &device_did,
1489 Some(Role::Member),
1490 vec![Capability::sign_commit()],
1491 );
1492 assert!(
1493 test_verifier()
1494 .verify_with_keys(&att, &root_pk)
1495 .await
1496 .is_ok()
1497 );
1498
1499 att.capabilities.push(Capability::manage_members());
1500 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1501 assert!(
1502 result.is_err(),
1503 "Attestation should reject tampered capabilities"
1504 );
1505 }
1506
1507 #[tokio::test]
1508 async fn verify_attestation_rejects_tampered_delegated_by() {
1509 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1510 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1511 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1512 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1513
1514 let mut att = create_signed_attestation_with_org_fields(
1515 &root_kp,
1516 &device_kp,
1517 &root_did,
1518 &device_did,
1519 Some(Role::Member),
1520 vec![Capability::sign_commit()],
1521 );
1522 assert!(
1523 test_verifier()
1524 .verify_with_keys(&att, &root_pk)
1525 .await
1526 .is_ok()
1527 );
1528
1529 att.delegated_by = Some(IdentityDID::new("did:key:attacker"));
1530 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1531 assert!(
1532 result.is_err(),
1533 "Attestation should reject tampered delegated_by"
1534 );
1535 }
1536
1537 #[tokio::test]
1538 async fn verify_attestation_valid_with_org_fields() {
1539 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1540 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1541 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1542 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1543
1544 let att = create_signed_attestation_with_org_fields(
1545 &root_kp,
1546 &device_kp,
1547 &root_did,
1548 &device_did,
1549 Some(Role::Admin),
1550 vec![Capability::sign_commit(), Capability::manage_members()],
1551 );
1552
1553 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1554 assert!(
1555 result.is_ok(),
1556 "Attestation with org fields should verify: {:?}",
1557 result.err()
1558 );
1559 }
1560
1561 fn create_signed_attestation_with_timestamp(
1562 issuer_kp: &Ed25519KeyPair,
1563 device_kp: &Ed25519KeyPair,
1564 issuer_did: &str,
1565 subject_did: &str,
1566 timestamp: Option<DateTime<Utc>>,
1567 ) -> Attestation {
1568 let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
1569
1570 let mut att = Attestation {
1571 version: 1,
1572 rid: ResourceId::new("test-rid"),
1573 issuer: IdentityDID::new(issuer_did),
1574 subject: DeviceDID::new(subject_did),
1575 device_public_key: Ed25519PublicKey::from_bytes(device_pk),
1576 identity_signature: Ed25519Signature::empty(),
1577 device_signature: Ed25519Signature::empty(),
1578 revoked_at: None,
1579 expires_at: Some(fixed_now() + Duration::days(365)),
1580 timestamp,
1581 note: None,
1582 payload: None,
1583 role: None,
1584 capabilities: vec![],
1585 delegated_by: None,
1586 signer_type: None,
1587 };
1588
1589 let data = CanonicalAttestationData {
1590 version: att.version,
1591 rid: &att.rid,
1592 issuer: &att.issuer,
1593 subject: &att.subject,
1594 device_public_key: att.device_public_key.as_bytes(),
1595 payload: &att.payload,
1596 timestamp: &att.timestamp,
1597 expires_at: &att.expires_at,
1598 revoked_at: &att.revoked_at,
1599 note: &att.note,
1600 role: att.role.as_ref().map(|r| r.as_str()),
1601 capabilities: if att.capabilities.is_empty() {
1602 None
1603 } else {
1604 Some(&att.capabilities)
1605 },
1606 delegated_by: att.delegated_by.as_ref(),
1607 signer_type: att.signer_type.as_ref(),
1608 };
1609 let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
1610
1611 att.identity_signature =
1612 Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
1613 att.device_signature =
1614 Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1615
1616 att
1617 }
1618
1619 #[tokio::test]
1620 async fn verify_attestation_created_1_hour_ago_succeeds() {
1621 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1622 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1623 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1624 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1625
1626 let one_hour_ago = fixed_now() - Duration::hours(1);
1627 let att = create_signed_attestation_with_timestamp(
1628 &root_kp,
1629 &device_kp,
1630 &root_did,
1631 &device_did,
1632 Some(one_hour_ago),
1633 );
1634
1635 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1636 assert!(
1637 result.is_ok(),
1638 "Attestation created 1 hour ago should verify: {:?}",
1639 result.err()
1640 );
1641 }
1642
1643 #[tokio::test]
1644 async fn verify_attestation_created_30_days_ago_succeeds() {
1645 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1646 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1647 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1648 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1649
1650 let thirty_days_ago = fixed_now() - Duration::days(30);
1651 let att = create_signed_attestation_with_timestamp(
1652 &root_kp,
1653 &device_kp,
1654 &root_did,
1655 &device_did,
1656 Some(thirty_days_ago),
1657 );
1658
1659 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1660 assert!(
1661 result.is_ok(),
1662 "Attestation created 30 days ago should verify: {:?}",
1663 result.err()
1664 );
1665 }
1666
1667 #[tokio::test]
1668 async fn verify_attestation_with_future_timestamp_fails() {
1669 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1670 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1671 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1672 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1673
1674 let ten_minutes_future = fixed_now() + Duration::minutes(10);
1675 let att = create_signed_attestation_with_timestamp(
1676 &root_kp,
1677 &device_kp,
1678 &root_did,
1679 &device_did,
1680 Some(ten_minutes_future),
1681 );
1682
1683 let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1684 assert!(
1685 result.is_err(),
1686 "Attestation with future timestamp should fail"
1687 );
1688 let err_msg = result.unwrap_err().to_string();
1689 assert!(
1690 err_msg.contains("in the future"),
1691 "Error should mention 'in the future': {}",
1692 err_msg
1693 );
1694 }
1695
1696 #[tokio::test]
1697 async fn verify_device_authorization_returns_valid_for_signed_attestation() {
1698 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1699 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1700 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1701 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1702 let device_did = DeviceDID::new(&device_did_str);
1703
1704 let att = create_signed_attestation(
1705 &root_kp,
1706 &device_kp,
1707 &root_did,
1708 &device_did_str,
1709 None,
1710 Some(fixed_now() + Duration::days(365)),
1711 );
1712
1713 let result = test_verifier()
1714 .verify_device_authorization(&root_did, &device_did, &[att], &root_pk)
1715 .await;
1716 assert!(result.is_ok());
1717 let report = result.unwrap();
1718 assert!(report.is_valid());
1719 assert_eq!(report.chain.len(), 1);
1720 assert!(report.chain[0].valid);
1721 }
1722
1723 #[tokio::test]
1724 async fn verify_device_authorization_returns_broken_chain_for_no_attestations() {
1725 let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1726 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1727 let (_, device_pk) = create_test_keypair(&[2u8; 32]);
1728 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1729 let device_did = DeviceDID::new(&device_did_str);
1730
1731 let result = test_verifier()
1732 .verify_device_authorization(&root_did, &device_did, &[], &root_pk)
1733 .await;
1734 assert!(result.is_ok());
1735 let report = result.unwrap();
1736 assert!(!report.is_valid());
1737 match report.status {
1738 VerificationStatus::BrokenChain { missing_link } => {
1739 assert!(missing_link.contains("No attestation found"));
1740 }
1741 _ => panic!("Expected BrokenChain status, got {:?}", report.status),
1742 }
1743 }
1744
1745 #[tokio::test]
1746 async fn verify_device_authorization_fails_for_forged_signature() {
1747 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1748 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1749 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1750 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1751 let device_did = DeviceDID::new(&device_did_str);
1752
1753 let mut att = create_signed_attestation(
1754 &root_kp,
1755 &device_kp,
1756 &root_did,
1757 &device_did_str,
1758 None,
1759 Some(fixed_now() + Duration::days(365)),
1760 );
1761 let mut tampered = *att.identity_signature.as_bytes();
1762 tampered[0] ^= 0xFF;
1763 att.identity_signature = Ed25519Signature::from_bytes(tampered);
1764
1765 let result = test_verifier()
1766 .verify_device_authorization(&root_did, &device_did, &[att], &root_pk)
1767 .await;
1768 assert!(result.is_ok());
1769 let report = result.unwrap();
1770 assert!(!report.is_valid());
1771 match report.status {
1772 VerificationStatus::InvalidSignature { step } => assert_eq!(step, 0),
1773 _ => panic!("Expected InvalidSignature status, got {:?}", report.status),
1774 }
1775 }
1776
1777 #[tokio::test]
1778 async fn verify_device_authorization_fails_for_wrong_issuer_key() {
1779 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1780 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1781 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1782 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1783 let device_did = DeviceDID::new(&device_did_str);
1784 let (_, wrong_pk) = create_test_keypair(&[99u8; 32]);
1785
1786 let att = create_signed_attestation(
1787 &root_kp,
1788 &device_kp,
1789 &root_did,
1790 &device_did_str,
1791 None,
1792 Some(fixed_now() + Duration::days(365)),
1793 );
1794
1795 let result = test_verifier()
1796 .verify_device_authorization(&root_did, &device_did, &[att], &wrong_pk)
1797 .await;
1798 assert!(result.is_ok());
1799 let report = result.unwrap();
1800 assert!(!report.is_valid());
1801 match report.status {
1802 VerificationStatus::InvalidSignature { .. } => {}
1803 _ => panic!("Expected InvalidSignature status, got {:?}", report.status),
1804 }
1805 }
1806
1807 #[tokio::test]
1808 async fn verify_device_authorization_checks_expiry_and_revocation() {
1809 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1810 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1811 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1812 let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1813 let device_did = DeviceDID::new(&device_did_str);
1814
1815 let att_expired = create_signed_attestation(
1816 &root_kp,
1817 &device_kp,
1818 &root_did,
1819 &device_did_str,
1820 None,
1821 Some(fixed_now() - Duration::days(1)),
1822 );
1823 let result = test_verifier()
1824 .verify_device_authorization(&root_did, &device_did, &[att_expired], &root_pk)
1825 .await;
1826 assert!(result.is_ok());
1827 let report = result.unwrap();
1828 assert!(!report.is_valid());
1829 match report.status {
1830 VerificationStatus::Expired { .. } => {}
1831 _ => panic!("Expected Expired status, got {:?}", report.status),
1832 }
1833
1834 let att_revoked = create_signed_attestation(
1835 &root_kp,
1836 &device_kp,
1837 &root_did,
1838 &device_did_str,
1839 Some(fixed_now()),
1840 Some(fixed_now() + Duration::days(365)),
1841 );
1842 let result = test_verifier()
1843 .verify_device_authorization(&root_did, &device_did, &[att_revoked], &root_pk)
1844 .await;
1845 assert!(result.is_ok());
1846 let report = result.unwrap();
1847 assert!(!report.is_valid());
1848 match report.status {
1849 VerificationStatus::Revoked { .. } => {}
1850 _ => panic!("Expected Revoked status, got {:?}", report.status),
1851 }
1852 }
1853
1854 fn create_witness_receipt(
1855 witness_kp: &Ed25519KeyPair,
1856 witness_did: &str,
1857 event_said: &str,
1858 seq: u64,
1859 ) -> crate::witness::WitnessReceipt {
1860 let mut receipt = crate::witness::WitnessReceipt {
1861 v: "KERI10JSON000000_".into(),
1862 t: "rct".into(),
1863 d: Said::new_unchecked(format!("EReceipt_{}", seq)),
1864 i: witness_did.into(),
1865 s: seq,
1866 a: Said::new_unchecked(event_said.to_string()),
1867 sig: vec![],
1868 };
1869 let payload = receipt.signing_payload().unwrap();
1870 receipt.sig = witness_kp.sign(&payload).as_ref().to_vec();
1871 receipt
1872 }
1873
1874 #[tokio::test]
1875 async fn verify_chain_with_witnesses_valid() {
1876 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1877 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1878 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1879 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1880
1881 let att = create_signed_attestation(
1882 &root_kp,
1883 &device_kp,
1884 &root_did,
1885 &device_did,
1886 None,
1887 Some(fixed_now() + Duration::days(365)),
1888 );
1889
1890 let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1891 let (w2_kp, w2_pk) = create_test_keypair(&[20u8; 32]);
1892
1893 let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1894 let r2 = create_witness_receipt(&w2_kp, "did:key:w2", "EEvent1", 1);
1895
1896 let witness_keys = vec![
1897 ("did:key:w1".into(), w1_pk.to_vec()),
1898 ("did:key:w2".into(), w2_pk.to_vec()),
1899 ];
1900
1901 let config = crate::witness::WitnessVerifyConfig {
1902 receipts: &[r1, r2],
1903 witness_keys: &witness_keys,
1904 threshold: 2,
1905 };
1906
1907 let report = test_verifier()
1908 .verify_chain_with_witnesses(&[att], &root_pk, &config)
1909 .await
1910 .unwrap();
1911 assert!(report.is_valid());
1912 assert!(report.witness_quorum.is_some());
1913 let quorum = report.witness_quorum.unwrap();
1914 assert_eq!(quorum.required, 2);
1915 assert_eq!(quorum.verified, 2);
1916 }
1917
1918 #[tokio::test]
1919 async fn verify_chain_with_witnesses_chain_fails() {
1920 let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1921 let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1922 let witness_keys = vec![("did:key:w1".into(), w1_pk.to_vec())];
1923 let config = crate::witness::WitnessVerifyConfig {
1924 receipts: &[r1],
1925 witness_keys: &witness_keys,
1926 threshold: 1,
1927 };
1928
1929 let report = test_verifier()
1930 .verify_chain_with_witnesses(&[], &[0u8; 32], &config)
1931 .await
1932 .unwrap();
1933 assert!(!report.is_valid());
1934 match report.status {
1935 VerificationStatus::BrokenChain { .. } => {}
1936 _ => panic!("Expected BrokenChain, got {:?}", report.status),
1937 }
1938 assert!(report.witness_quorum.is_none());
1939 }
1940
1941 #[tokio::test]
1942 async fn verify_chain_with_witnesses_quorum_fails() {
1943 let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1944 let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1945 let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1946 let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1947
1948 let att = create_signed_attestation(
1949 &root_kp,
1950 &device_kp,
1951 &root_did,
1952 &device_did,
1953 None,
1954 Some(fixed_now() + Duration::days(365)),
1955 );
1956
1957 let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1958 let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1959 let witness_keys = vec![("did:key:w1".into(), w1_pk.to_vec())];
1960 let config = crate::witness::WitnessVerifyConfig {
1961 receipts: &[r1],
1962 witness_keys: &witness_keys,
1963 threshold: 2,
1964 };
1965
1966 let report = test_verifier()
1967 .verify_chain_with_witnesses(&[att], &root_pk, &config)
1968 .await
1969 .unwrap();
1970 assert!(!report.is_valid());
1971 match report.status {
1972 VerificationStatus::InsufficientWitnesses { required, verified } => {
1973 assert_eq!(required, 2);
1974 assert_eq!(verified, 1);
1975 }
1976 _ => panic!("Expected InsufficientWitnesses, got {:?}", report.status),
1977 }
1978 assert!(report.witness_quorum.is_some());
1979 assert!(!report.warnings.is_empty());
1980 }
1981}