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