1#![no_std]
42#![forbid(unsafe_code)]
43
44use hybrid_array::Array;
45use hkdf::Hkdf;
46use ml_dsa::{EncodedSignature, EncodedVerifyingKey, KeyGen, MlDsa65, SigningKey, VerifyingKey};
47use ml_dsa::signature::Verifier;
48use sha3::Sha3_512;
49use zeroize::Zeroizing;
50
51pub const SEED_SIZE: usize = 32;
55
56pub const PK_SIZE: usize = 1952;
58
59pub const SIG_SIZE: usize = 3309;
61
62pub const RESOURCE_LEN: usize = 255;
65
66pub const VERB_LEN: usize = 255;
69
70pub const PERM_TLV_MAX: usize = 1 + RESOURCE_LEN + 1 + VERB_LEN;
73
74pub const CAVEAT_SIZE: usize = 9;
76
77pub const MAX_SCOPE_PERMS: usize = 64;
79
80pub const MAX_CAVEATS: usize = 64;
82
83pub const MAX_DEPTH: u32 = 16;
86
87pub const MAX_PAYLOAD_SIZE: usize =
93 PK_SIZE + 1 + 4 + 4 + PERM_TLV_MAX * MAX_SCOPE_PERMS + 4 + CAVEAT_SIZE * MAX_CAVEATS;
94
95pub const CREDENTIAL_FIXED_SIZE: usize = PK_SIZE + SIG_SIZE + 4;
98
99const SALT: &[u8] = b"QHermes-Kernel-v1";
101
102const WIRE_VERSION: u8 = 0x01;
104
105const CAVEAT_NOT_BEFORE: u8 = 0x01;
106const CAVEAT_NOT_AFTER: u8 = 0x02;
107
108#[non_exhaustive]
115#[derive(Debug, PartialEq, Eq)]
116pub enum KernelError {
117 HkdfExpandFailed,
119 SigningFailed,
121 SignatureInvalid,
123 KeyDecodingFailed,
125 SignatureDecodingFailed,
127 NotBeforeViolation,
129 NotAfterViolation,
131 MalformedCaveatBuffer,
133 ScopeEscalation,
135 ScopeEmpty,
137 ScopeTooLarge,
139 CaveatsTooLarge,
141 EmptyChain,
143 ChainTooDeep,
145 ParentKeyMismatch,
147 LeafCannotDelegate,
149 DepthMismatch,
151 InvalidRoleByte,
153 WireVersionMismatch,
155 WireTruncated,
157 WireInvalid,
160 ResourceTooLong,
162 VerbTooLong,
164}
165
166#[derive(Clone, Copy, PartialEq, Eq, Debug)]
170pub struct PublicKey<'a>(pub &'a [u8; PK_SIZE]);
171
172#[derive(Clone, Copy, PartialEq, Eq, Debug)]
174pub struct Signature<'a>(pub &'a [u8; SIG_SIZE]);
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub enum Role {
182 Leaf = 0,
183 Node = 1,
184}
185
186impl TryFrom<u8> for Role {
187 type Error = KernelError;
188 fn try_from(b: u8) -> Result<Self, Self::Error> {
189 match b {
190 0 => Ok(Role::Leaf),
191 1 => Ok(Role::Node),
192 _ => Err(KernelError::InvalidRoleByte),
193 }
194 }
195}
196
197#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
202pub struct Timestamp(pub u64);
203
204#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
207pub struct Depth(pub u32);
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq)]
225pub struct BoundedScope<'a>(&'a [u8]);
226
227impl<'a> BoundedScope<'a> {
228 pub fn iter(&self) -> ScopeIter<'a> {
230 ScopeIter { raw: self.0, pos: 0 }
231 }
232
233 pub fn try_new(raw: &'a [u8]) -> Result<Self, KernelError> {
239 if raw.is_empty() {
240 return Err(KernelError::ScopeEmpty);
241 }
242 let mut pos = 0;
243 let mut count = 0usize;
244 while pos < raw.len() {
245 if count >= MAX_SCOPE_PERMS {
246 return Err(KernelError::ScopeTooLarge);
247 }
248 tlv_next_field(raw, &mut pos).ok_or(KernelError::WireInvalid)?; tlv_next_field(raw, &mut pos).ok_or(KernelError::WireInvalid)?; count += 1;
251 }
252 Ok(Self(raw))
253 }
254}
255
256#[derive(Clone, Copy, Debug, PartialEq, Eq)]
265pub struct BoundedCaveats<'a>(&'a [u8]);
266
267impl<'a> BoundedCaveats<'a> {
268 pub fn try_new(raw: &'a [u8]) -> Result<Self, KernelError> {
274 if raw.len() > CAVEAT_SIZE * MAX_CAVEATS {
275 return Err(KernelError::CaveatsTooLarge);
276 }
277 if raw.len() % CAVEAT_SIZE != 0 {
278 return Err(KernelError::MalformedCaveatBuffer);
279 }
280 for c in raw.chunks_exact(CAVEAT_SIZE) {
281 match c[0] {
282 CAVEAT_NOT_BEFORE | CAVEAT_NOT_AFTER => {}
283 _ => return Err(KernelError::MalformedCaveatBuffer),
284 }
285 }
286 Ok(Self(raw))
287 }
288}
289
290pub struct DelegationManifest<'a> {
292 pub child_pk: PublicKey<'a>,
294 pub role: Role,
296 pub depth: Depth,
299 pub scope: BoundedScope<'a>,
301 pub caveats: BoundedCaveats<'a>,
303}
304
305pub struct Credential<'a> {
307 pub issuer_pk: PublicKey<'a>,
309 pub signature: Signature<'a>,
311 pub payload: &'a [u8],
313}
314
315pub trait IdentitySigner {
319 fn public_key(&self) -> PublicKey<'_>;
321
322 fn sign_into(&self, payload: &[u8], out: &mut [u8; SIG_SIZE]) -> Result<(), KernelError>;
326}
327
328pub struct IdentityIsland {
332 pk: [u8; PK_SIZE],
333 sk: SigningKey<MlDsa65>,
334}
335
336impl IdentityIsland {
337 pub fn derive(
344 master: &[u8; SEED_SIZE],
345 deployment: &[u8],
346 context: &[u8],
347 ) -> Result<Self, KernelError> {
348 let mut seed = Zeroizing::new([0u8; SEED_SIZE]);
349 Hkdf::<Sha3_512>::new(Some(SALT), master)
350 .expand_multi_info(&[deployment, b":", context], seed.as_mut())
351 .map_err(|_| KernelError::HkdfExpandFailed)?;
352
353 let kp = MlDsa65::from_seed(&Array::<u8, hybrid_array::typenum::U32>::from(*seed));
354 let mut pk = [0u8; PK_SIZE];
355 pk.copy_from_slice(kp.verifying_key().encode().as_slice());
356
357 Ok(Self { pk, sk: kp.signing_key().clone() })
358 }
359}
360
361impl IdentitySigner for IdentityIsland {
362 fn public_key(&self) -> PublicKey<'_> {
363 PublicKey(&self.pk)
364 }
365
366 fn sign_into(&self, payload: &[u8], out: &mut [u8; SIG_SIZE]) -> Result<(), KernelError> {
367 let sig = self.sk.sign_deterministic(payload, &[])
368 .map_err(|_| KernelError::SigningFailed)?;
369 out.copy_from_slice(&sig.encode());
370 Ok(())
371 }
372}
373
374pub fn verify_signature(
379 pk: PublicKey<'_>,
380 payload: &[u8],
381 sig: Signature<'_>,
382) -> Result<(), KernelError> {
383 let vk = VerifyingKey::<MlDsa65>::decode(
384 &EncodedVerifyingKey::<MlDsa65>::try_from(pk.0.as_slice())
385 .map_err(|_| KernelError::KeyDecodingFailed)?,
386 );
387 let decoded = ml_dsa::Signature::<MlDsa65>::decode(
388 &EncodedSignature::<MlDsa65>::try_from(sig.0.as_slice())
389 .map_err(|_| KernelError::SignatureDecodingFailed)?,
390 )
391 .ok_or(KernelError::SignatureDecodingFailed)?;
392
393 vk.verify(payload, &decoded).map_err(|_| KernelError::SignatureInvalid)
394}
395
396pub fn perm_tlv(resource: &[u8], verb: &[u8], out: &mut [u8]) -> Result<usize, KernelError> {
407 if resource.len() > RESOURCE_LEN {
408 return Err(KernelError::ResourceTooLong);
409 }
410 if verb.len() > VERB_LEN {
411 return Err(KernelError::VerbTooLong);
412 }
413 let need = 1 + resource.len() + 1 + verb.len();
414 if out.len() < need {
415 return Err(KernelError::WireTruncated);
416 }
417 out[0] = resource.len() as u8;
418 out[1..1 + resource.len()].copy_from_slice(resource);
419 let v = 1 + resource.len();
420 out[v] = verb.len() as u8;
421 out[v + 1..v + 1 + verb.len()].copy_from_slice(verb);
422 Ok(need)
423}
424
425pub fn not_before(t: Timestamp) -> [u8; CAVEAT_SIZE] {
430 build_caveat(CAVEAT_NOT_BEFORE, t)
431}
432
433pub fn not_after(t: Timestamp) -> [u8; CAVEAT_SIZE] {
438 build_caveat(CAVEAT_NOT_AFTER, t)
439}
440
441fn build_caveat(tag: u8, t: Timestamp) -> [u8; CAVEAT_SIZE] {
442 let mut c = [0u8; CAVEAT_SIZE];
443 c[0] = tag;
444 c[1..].copy_from_slice(&t.0.to_le_bytes());
445 c
446}
447
448fn tlv_next_field<'a>(raw: &'a [u8], pos: &mut usize) -> Option<&'a [u8]> {
449 let len = *raw.get(*pos)? as usize;
450 *pos += 1;
451 let end = pos.checked_add(len).filter(|&e| e <= raw.len())?;
452 let field = &raw[*pos..end];
453 *pos = end;
454 Some(field)
455}
456
457pub struct ScopeIter<'a> {
458 raw: &'a [u8],
459 pos: usize,
460}
461
462impl<'a> Iterator for ScopeIter<'a> {
463 type Item = (&'a [u8], &'a [u8]);
464
465 fn next(&mut self) -> Option<Self::Item> {
466 let resource = tlv_next_field(self.raw, &mut self.pos)?;
467 let verb = tlv_next_field(self.raw, &mut self.pos)?;
468 Some((resource, verb))
469 }
470}
471
472pub fn enforce_scope_subset(
494 child: &BoundedScope<'_>,
495 parent: &BoundedScope<'_>,
496) -> Result<(), KernelError> {
497 for (cr, cv) in child.iter() {
498 let covered = parent.iter().any(|(pr, pv)| {
499 (pr == b"*" || pr == cr) && (pv == b"*" || pv == cv)
500 });
501 if !covered {
502 return Err(KernelError::ScopeEscalation);
503 }
504 }
505 Ok(())
506}
507
508pub fn evaluate_caveats(
515 caveats: &BoundedCaveats<'_>,
516 now: Timestamp,
517) -> Result<(), KernelError> {
518 for c in caveats.0.chunks_exact(CAVEAT_SIZE) {
519 let ts = Timestamp(u64::from_le_bytes(c[1..].try_into().unwrap()));
520 match c[0] {
521 CAVEAT_NOT_BEFORE if now < ts => return Err(KernelError::NotBeforeViolation),
522 CAVEAT_NOT_AFTER if now > ts => return Err(KernelError::NotAfterViolation),
523 _ => {}
524 }
525 }
526 Ok(())
527}
528
529pub fn issue_credential(
547 issuer: &impl IdentitySigner,
548 manifest: &DelegationManifest<'_>,
549 payload_out: &mut [u8; MAX_PAYLOAD_SIZE],
550 sig_out: &mut [u8; SIG_SIZE],
551) -> Result<usize, KernelError> {
552 if manifest.depth.0 > MAX_DEPTH {
553 return Err(KernelError::ChainTooDeep);
554 }
555 let scope = manifest.scope.0;
556 let caveats = manifest.caveats.0;
557 let mut pos = 0;
558
559 payload_out[pos..pos + PK_SIZE].copy_from_slice(manifest.child_pk.0); pos += PK_SIZE;
560 payload_out[pos] = manifest.role as u8; pos += 1;
561 payload_out[pos..pos + 4].copy_from_slice(&manifest.depth.0.to_le_bytes()); pos += 4;
562 payload_out[pos..pos + 4].copy_from_slice(&(scope.len() as u32).to_le_bytes()); pos += 4;
563 payload_out[pos..pos + scope.len()].copy_from_slice(scope); pos += scope.len();
564 payload_out[pos..pos + 4].copy_from_slice(&(caveats.len() as u32).to_le_bytes()); pos += 4;
565 payload_out[pos..pos + caveats.len()].copy_from_slice(caveats); pos += caveats.len();
566
567 issuer.sign_into(&payload_out[..pos], sig_out)?;
568 Ok(pos)
569}
570
571pub fn verify_delegation(
576 root_pk: PublicKey<'_>,
577 chain: &[Credential<'_>],
578 timestamp: Timestamp,
579) -> Result<(), KernelError> {
580 if chain.len() > MAX_DEPTH as usize {
581 return Err(KernelError::ChainTooDeep);
582 }
583 let (first, rest) = chain.split_first().ok_or(KernelError::EmptyChain)?;
584
585 if first.issuer_pk != root_pk {
586 return Err(KernelError::ParentKeyMismatch);
587 }
588 verify_signature(first.issuer_pk, first.payload, first.signature)?;
589 let m0 = parse_payload(first.payload)?;
590 if m0.depth != Depth(1) {
591 return Err(KernelError::DepthMismatch);
592 }
593 evaluate_caveats(&m0.caveats, timestamp)?;
594
595 let mut prev_child_pk = m0.child_pk;
596 let mut prev_child_scope = m0.scope;
597 let mut prev_child_role = m0.role;
598
599 for (i, cred) in rest.iter().enumerate() {
600 if cred.issuer_pk != prev_child_pk {
601 return Err(KernelError::ParentKeyMismatch);
602 }
603 if prev_child_role == Role::Leaf {
604 return Err(KernelError::LeafCannotDelegate);
605 }
606 verify_signature(cred.issuer_pk, cred.payload, cred.signature)?;
607 let m = parse_payload(cred.payload)?;
608 if m.depth != Depth(i as u32 + 2) {
609 return Err(KernelError::DepthMismatch);
610 }
611 enforce_scope_subset(&m.scope, &prev_child_scope)?;
612 evaluate_caveats(&m.caveats, timestamp)?;
613 prev_child_pk = m.child_pk;
614 prev_child_scope = m.scope;
615 prev_child_role = m.role;
616 }
617 Ok(())
618}
619
620pub fn write_credential_chain(
624 out: &mut [u8],
625 chain: &[Credential<'_>],
626) -> Result<usize, KernelError> {
627 if out.len() < 5 {
628 return Err(KernelError::WireTruncated);
629 }
630 out[0] = WIRE_VERSION;
631 let count32 = u32::try_from(chain.len()).map_err(|_| KernelError::WireTruncated)?;
632 out[1..5].copy_from_slice(&count32.to_le_bytes());
633 let mut pos: usize = 5;
634
635 for cred in chain {
636 let plen = cred.payload.len();
637 let need = pos.saturating_add(CREDENTIAL_FIXED_SIZE).saturating_add(plen);
638 if out.len() < need {
639 return Err(KernelError::WireTruncated);
640 }
641 let plen32 = u32::try_from(plen).map_err(|_| KernelError::WireTruncated)?;
642 out[pos..pos + PK_SIZE].copy_from_slice(cred.issuer_pk.0); pos += PK_SIZE;
643 out[pos..pos + SIG_SIZE].copy_from_slice(cred.signature.0); pos += SIG_SIZE;
644 out[pos..pos + 4].copy_from_slice(&plen32.to_le_bytes()); pos += 4;
645 out[pos..pos + plen].copy_from_slice(cred.payload); pos += plen;
646 }
647 Ok(pos)
648}
649
650pub fn read_credential_chain<'a>(
667 wire: &'a [u8],
668 chain: &mut [Credential<'a>],
669) -> Result<usize, KernelError> {
670 let ([version], rest) = split_array::<1>(wire)?;
671 if *version != WIRE_VERSION {
672 return Err(KernelError::WireVersionMismatch);
673 }
674 let (count_bytes, rest) = split_array::<4>(rest)?;
675 let num = usize::try_from(u32::from_le_bytes(*count_bytes)).map_err(|_| KernelError::WireInvalid)?;
676 if num == 0 {
677 return Err(KernelError::EmptyChain);
678 }
679 if num > MAX_DEPTH as usize {
680 return Err(KernelError::ChainTooDeep);
681 }
682 if num > chain.len() {
683 return Err(KernelError::WireInvalid);
684 }
685 let mut rest = rest;
686
687 for slot in chain[..num].iter_mut() {
688 let (pk_bytes, tail) = split_array::<PK_SIZE>(rest)?;
689 let (sig_bytes, tail) = split_array::<SIG_SIZE>(tail)?;
690 let (len_bytes, tail) = split_array::<4>(tail)?;
691 let pl_len = usize::try_from(u32::from_le_bytes(*len_bytes)).map_err(|_| KernelError::WireInvalid)?;
692 let (payload, tail) = split_at_checked(tail, pl_len)?;
693
694 *slot = Credential {
695 issuer_pk: PublicKey(pk_bytes),
696 signature: Signature(sig_bytes),
697 payload,
698 };
699 rest = tail;
700 }
701 Ok(num)
702}
703
704fn split_at_checked(buf: &[u8], n: usize) -> Result<(&[u8], &[u8]), KernelError> {
705 if buf.len() < n {
706 return Err(KernelError::WireTruncated);
707 }
708 Ok(buf.split_at(n))
709}
710
711fn split_array<const N: usize>(buf: &[u8]) -> Result<(&[u8; N], &[u8]), KernelError> {
712 buf.split_first_chunk::<N>().ok_or(KernelError::WireTruncated)
713}
714
715#[cfg(test)]
717pub(crate) fn parse_payload_pub(p: &[u8]) -> Result<DelegationManifest<'_>, KernelError> {
718 parse_payload(p)
719}
720
721fn parse_payload(p: &[u8]) -> Result<DelegationManifest<'_>, KernelError> {
722 let (child_pk, rest) = split_array::<PK_SIZE>(p)?;
723
724 let (role_byte, rest) = split_array::<1>(rest)?;
725 let role = Role::try_from(role_byte[0])?;
726
727 let (depth_bytes, rest) = split_array::<4>(rest)?;
728 let depth = u32::from_le_bytes(*depth_bytes);
729
730 let (scope_len_bytes, rest) = split_array::<4>(rest)?;
731 let scope_len = usize::try_from(u32::from_le_bytes(*scope_len_bytes)).map_err(|_| KernelError::WireInvalid)?;
732 let (scope_bytes, rest) = split_at_checked(rest, scope_len)?;
733
734 let (cav_len_bytes, rest) = split_array::<4>(rest)?;
735 let cav_len = usize::try_from(u32::from_le_bytes(*cav_len_bytes)).map_err(|_| KernelError::WireInvalid)?;
736 let (cav_bytes, tail) = split_at_checked(rest, cav_len)?;
737
738 if !tail.is_empty() {
739 return Err(KernelError::WireInvalid);
740 }
741
742 Ok(DelegationManifest {
743 child_pk: PublicKey(child_pk),
744 role,
745 depth: Depth(depth),
746 scope: BoundedScope::try_new(scope_bytes)?,
747 caveats: BoundedCaveats::try_new(cav_bytes)?,
748 })
749}
750
751#[cfg(test)]
754mod tests {
755 use super::*;
756
757 extern crate std;
758 use std::vec;
759 use std::vec::Vec;
760
761 fn make_test_identity(context: &[u8]) -> IdentityIsland {
765 let master = [0x42u8; SEED_SIZE];
766 IdentityIsland::derive(&master, b"test-deploy", context).unwrap()
767 }
768
769 fn make_test_scope() -> Vec<u8> {
771 let mut buf = vec![0u8; PERM_TLV_MAX];
772 let n = perm_tlv(b"/test", b"GET", &mut buf).unwrap();
773 buf.truncate(n);
774 buf
775 }
776
777 fn make_test_caveats() -> Vec<u8> {
779 vec![]
780 }
781
782 #[test]
785 fn derive_same_inputs_same_pubkey() {
786 let a = make_test_identity(b"ctx-alpha");
787 let b = make_test_identity(b"ctx-alpha");
788 assert_eq!(a.public_key(), b.public_key());
789 }
790
791 #[test]
792 fn derive_different_context_different_pubkey() {
793 let a = make_test_identity(b"ctx-alpha");
794 let b = make_test_identity(b"ctx-beta");
795 assert_ne!(a.public_key(), b.public_key());
796 }
797
798 #[test]
799 fn derive_different_deployment_different_pubkey() {
800 let master = [0x42u8; SEED_SIZE];
801 let a = IdentityIsland::derive(&master, b"deploy-one", b"ctx").unwrap();
802 let b = IdentityIsland::derive(&master, b"deploy-two", b"ctx").unwrap();
803 assert_ne!(a.public_key(), b.public_key());
804 }
805
806 #[test]
807 fn derive_different_master_different_pubkey() {
808 let master_a = [0x42u8; SEED_SIZE];
809 let master_b = [0x43u8; SEED_SIZE];
810 let a = IdentityIsland::derive(&master_a, b"dep", b"ctx").unwrap();
811 let b = IdentityIsland::derive(&master_b, b"dep", b"ctx").unwrap();
812 assert_ne!(a.public_key(), b.public_key());
813 }
814
815 #[test]
816 fn derive_valid_master_returns_ok() {
817 let master = [0x11u8; SEED_SIZE];
818 assert!(IdentityIsland::derive(&master, b"dep", b"ctx").is_ok());
819 }
820
821 #[test]
824 fn perm_tlv_single_byte_resource_and_verb() {
825 let mut buf = [0u8; 64];
826 let n = perm_tlv(b"R", b"V", &mut buf).unwrap();
827 assert_eq!(n, 4);
829 assert_eq!(buf[0], 1);
830 assert_eq!(buf[1], b'R');
831 assert_eq!(buf[2], 1);
832 assert_eq!(buf[3], b'V');
833 }
834
835 #[test]
836 fn perm_tlv_max_resource_succeeds() {
837 let resource = vec![0xAAu8; RESOURCE_LEN];
838 let mut buf = vec![0u8; PERM_TLV_MAX];
839 let n = perm_tlv(&resource, b"V", &mut buf).unwrap();
840 assert_eq!(n, 1 + RESOURCE_LEN + 1 + 1);
841 }
842
843 #[test]
844 fn perm_tlv_max_verb_succeeds() {
845 let verb = vec![0xBBu8; VERB_LEN];
846 let mut buf = vec![0u8; PERM_TLV_MAX];
847 let n = perm_tlv(b"R", &verb, &mut buf).unwrap();
848 assert_eq!(n, 1 + 1 + 1 + VERB_LEN);
849 }
850
851 #[test]
852 fn perm_tlv_resource_256_returns_resource_too_long() {
853 let resource = vec![0u8; 256];
854 let mut buf = vec![0u8; PERM_TLV_MAX + 4];
855 assert!(matches!(
856 perm_tlv(&resource, b"V", &mut buf),
857 Err(KernelError::ResourceTooLong)
858 ));
859 }
860
861 #[test]
862 fn perm_tlv_verb_256_returns_verb_too_long() {
863 let verb = vec![0u8; 256];
864 let mut buf = vec![0u8; PERM_TLV_MAX + 4];
865 assert!(matches!(
866 perm_tlv(b"R", &verb, &mut buf),
867 Err(KernelError::VerbTooLong)
868 ));
869 }
870
871 #[test]
872 fn perm_tlv_empty_resource_and_verb() {
873 let mut buf = [0u8; 8];
874 let n = perm_tlv(b"", b"", &mut buf).unwrap();
875 assert_eq!(n, 2);
877 assert_eq!(buf[0], 0);
878 assert_eq!(buf[1], 0);
879 }
880
881 #[test]
882 fn perm_tlv_output_too_small_returns_wire_truncated() {
883 let mut buf = [0u8; 3];
885 assert!(matches!(
886 perm_tlv(b"R", b"V", &mut buf),
887 Err(KernelError::WireTruncated)
888 ));
889 }
890
891 #[test]
894 fn not_before_returns_nine_bytes() {
895 let c = not_before(Timestamp(1_000_000));
896 assert_eq!(c.len(), 9);
897 }
898
899 #[test]
900 fn not_before_first_byte_is_0x01() {
901 let c = not_before(Timestamp(42));
902 assert_eq!(c[0], 0x01);
903 }
904
905 #[test]
906 fn not_before_timestamp_little_endian() {
907 let ts = 0x0102_0304_0506_0708u64;
908 let c = not_before(Timestamp(ts));
909 assert_eq!(&c[1..9], &ts.to_le_bytes());
910 }
911
912 #[test]
913 fn not_after_returns_nine_bytes() {
914 let c = not_after(Timestamp(9_999_999));
915 assert_eq!(c.len(), 9);
916 }
917
918 #[test]
919 fn not_after_first_byte_is_0x02() {
920 let c = not_after(Timestamp(99));
921 assert_eq!(c[0], 0x02);
922 }
923
924 #[test]
925 fn not_after_timestamp_little_endian() {
926 let ts = 0xDEAD_BEEF_CAFE_BABEu64;
927 let c = not_after(Timestamp(ts));
928 assert_eq!(&c[1..9], &ts.to_le_bytes());
929 }
930
931 #[test]
934 fn bounded_scope_empty_slice_returns_scope_empty() {
935 assert!(matches!(
936 BoundedScope::try_new(&[]),
937 Err(KernelError::ScopeEmpty)
938 ));
939 }
940
941 #[test]
942 fn bounded_scope_single_permission_succeeds() {
943 let scope_bytes = make_test_scope();
944 assert!(BoundedScope::try_new(&scope_bytes).is_ok());
945 }
946
947 #[test]
948 fn bounded_scope_multi_permission_succeeds() {
949 let mut buf = vec![0u8; PERM_TLV_MAX * 2];
950 let n1 = perm_tlv(b"/a", b"GET", &mut buf).unwrap();
951 let n2 = perm_tlv(b"/b", b"POST", &mut buf[n1..]).unwrap();
952 let total = n1 + n2;
953 assert!(BoundedScope::try_new(&buf[..total]).is_ok());
954 }
955
956 #[test]
957 fn bounded_scope_truncated_tlv_returns_wire_invalid() {
958 let mut buf = vec![0u8; PERM_TLV_MAX];
960 let n = perm_tlv(b"hello", b"GET", &mut buf).unwrap();
961 assert!(matches!(
963 BoundedScope::try_new(&buf[..1]),
964 Err(KernelError::WireInvalid)
965 ));
966 let _ = n; }
968
969 #[test]
970 fn bounded_scope_too_many_perms_returns_scope_too_large() {
971 let count = MAX_SCOPE_PERMS + 1;
973 let raw = vec![0u8; count * 2]; assert!(matches!(
975 BoundedScope::try_new(&raw),
976 Err(KernelError::ScopeTooLarge)
977 ));
978 }
979
980 #[test]
983 fn bounded_caveats_empty_slice_succeeds() {
984 assert!(BoundedCaveats::try_new(&[]).is_ok());
985 }
986
987 #[test]
988 fn bounded_caveats_single_not_before_succeeds() {
989 let c = not_before(Timestamp(100));
990 assert!(BoundedCaveats::try_new(&c).is_ok());
991 }
992
993 #[test]
994 fn bounded_caveats_single_not_after_succeeds() {
995 let c = not_after(Timestamp(200));
996 assert!(BoundedCaveats::try_new(&c).is_ok());
997 }
998
999 #[test]
1000 fn bounded_caveats_length_not_multiple_of_9_returns_malformed() {
1001 let raw = [0x01u8; 10];
1003 assert!(matches!(
1004 BoundedCaveats::try_new(&raw),
1005 Err(KernelError::MalformedCaveatBuffer)
1006 ));
1007 }
1008
1009 #[test]
1010 fn bounded_caveats_unknown_tag_returns_malformed() {
1011 let mut raw = [0u8; CAVEAT_SIZE];
1013 raw[0] = 0xFF;
1014 assert!(matches!(
1015 BoundedCaveats::try_new(&raw),
1016 Err(KernelError::MalformedCaveatBuffer)
1017 ));
1018 }
1019
1020 #[test]
1021 fn bounded_caveats_too_large_returns_caveats_too_large() {
1022 let raw = vec![0x01u8; CAVEAT_SIZE * MAX_CAVEATS + CAVEAT_SIZE];
1024 assert!(matches!(
1025 BoundedCaveats::try_new(&raw),
1026 Err(KernelError::CaveatsTooLarge)
1027 ));
1028 }
1029
1030 #[test]
1033 fn enforce_scope_subset_exact_match_passes() {
1034 let scope_bytes = make_test_scope();
1035 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1036 assert!(enforce_scope_subset(&scope, &scope).is_ok());
1037 }
1038
1039 #[test]
1040 fn enforce_scope_subset_wildcard_resource_covers_child() {
1041 let mut parent_buf = [0u8; PERM_TLV_MAX];
1043 let pn = perm_tlv(b"*", b"GET", &mut parent_buf).unwrap();
1044 let parent = BoundedScope::try_new(&parent_buf[..pn]).unwrap();
1045
1046 let mut child_buf = [0u8; PERM_TLV_MAX];
1047 let cn = perm_tlv(b"/specific", b"GET", &mut child_buf).unwrap();
1048 let child = BoundedScope::try_new(&child_buf[..cn]).unwrap();
1049
1050 assert!(enforce_scope_subset(&child, &parent).is_ok());
1051 }
1052
1053 #[test]
1054 fn enforce_scope_subset_wildcard_verb_covers_child() {
1055 let mut parent_buf = [0u8; PERM_TLV_MAX];
1057 let pn = perm_tlv(b"/res", b"*", &mut parent_buf).unwrap();
1058 let parent = BoundedScope::try_new(&parent_buf[..pn]).unwrap();
1059
1060 let mut child_buf = [0u8; PERM_TLV_MAX];
1061 let cn = perm_tlv(b"/res", b"DELETE", &mut child_buf).unwrap();
1062 let child = BoundedScope::try_new(&child_buf[..cn]).unwrap();
1063
1064 assert!(enforce_scope_subset(&child, &parent).is_ok());
1065 }
1066
1067 #[test]
1068 fn enforce_scope_subset_child_has_extra_perm_returns_scope_escalation() {
1069 let scope_bytes = make_test_scope(); let parent = BoundedScope::try_new(&scope_bytes).unwrap();
1072
1073 let mut child_buf = [0u8; PERM_TLV_MAX];
1074 let cn = perm_tlv(b"/test", b"POST", &mut child_buf).unwrap();
1075 let child = BoundedScope::try_new(&child_buf[..cn]).unwrap();
1076
1077 assert!(matches!(
1078 enforce_scope_subset(&child, &parent),
1079 Err(KernelError::ScopeEscalation)
1080 ));
1081 }
1082
1083 #[test]
1086 fn evaluate_caveats_not_before_now_gte_ts_passes() {
1087 let c = not_before(Timestamp(1000));
1088 let caveats = BoundedCaveats::try_new(&c).unwrap();
1089 assert!(evaluate_caveats(&caveats, Timestamp(1000)).is_ok());
1091 assert!(evaluate_caveats(&caveats, Timestamp(2000)).is_ok());
1093 }
1094
1095 #[test]
1096 fn evaluate_caveats_not_before_now_lt_ts_returns_violation() {
1097 let c = not_before(Timestamp(5000));
1098 let caveats = BoundedCaveats::try_new(&c).unwrap();
1099 assert!(matches!(
1100 evaluate_caveats(&caveats, Timestamp(4999)),
1101 Err(KernelError::NotBeforeViolation)
1102 ));
1103 }
1104
1105 #[test]
1106 fn evaluate_caveats_not_after_now_lte_ts_passes() {
1107 let c = not_after(Timestamp(3000));
1108 let caveats = BoundedCaveats::try_new(&c).unwrap();
1109 assert!(evaluate_caveats(&caveats, Timestamp(3000)).is_ok());
1111 assert!(evaluate_caveats(&caveats, Timestamp(1000)).is_ok());
1113 }
1114
1115 #[test]
1116 fn evaluate_caveats_not_after_now_gt_ts_returns_violation() {
1117 let c = not_after(Timestamp(3000));
1118 let caveats = BoundedCaveats::try_new(&c).unwrap();
1119 assert!(matches!(
1120 evaluate_caveats(&caveats, Timestamp(3001)),
1121 Err(KernelError::NotAfterViolation)
1122 ));
1123 }
1124
1125 #[test]
1126 fn evaluate_caveats_empty_always_passes() {
1127 let caveats = BoundedCaveats::try_new(&[]).unwrap();
1128 assert!(evaluate_caveats(&caveats, Timestamp(u64::MAX)).is_ok());
1129 }
1130
1131 #[test]
1134 fn issue_credential_round_trip_leaf() {
1135 let issuer = make_test_identity(b"issuer");
1136 let child = make_test_identity(b"child");
1137 let scope_bytes = make_test_scope();
1138 let caveat_bytes = make_test_caveats();
1139 let child_pk = *child.public_key().0;
1140
1141 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1142 let caveats = BoundedCaveats::try_new(&caveat_bytes).unwrap();
1143 let manifest = DelegationManifest {
1144 child_pk: PublicKey(&child_pk),
1145 role: Role::Leaf,
1146 depth: Depth(1),
1147 scope,
1148 caveats,
1149 };
1150 let mut payload_buf = [0u8; MAX_PAYLOAD_SIZE];
1151 let mut sig_buf = [0u8; SIG_SIZE];
1152 let n = issue_credential(&issuer, &manifest, &mut payload_buf, &mut sig_buf).unwrap();
1153
1154 let parsed = parse_payload_pub(&payload_buf[..n]).unwrap();
1155 assert_eq!(parsed.role, Role::Leaf);
1156 assert_eq!(parsed.depth, Depth(1));
1157 assert_eq!(parsed.child_pk.0, &child_pk);
1158 }
1159
1160 #[test]
1161 fn issue_credential_round_trip_node() {
1162 let issuer = make_test_identity(b"issuer");
1163 let child = make_test_identity(b"child-node");
1164 let scope_bytes = make_test_scope();
1165 let caveat_bytes = make_test_caveats();
1166 let child_pk = *child.public_key().0;
1167
1168 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1169 let caveats = BoundedCaveats::try_new(&caveat_bytes).unwrap();
1170 let manifest = DelegationManifest {
1171 child_pk: PublicKey(&child_pk),
1172 role: Role::Node,
1173 depth: Depth(1),
1174 scope,
1175 caveats,
1176 };
1177 let mut payload_buf = [0u8; MAX_PAYLOAD_SIZE];
1178 let mut sig_buf = [0u8; SIG_SIZE];
1179 let n = issue_credential(&issuer, &manifest, &mut payload_buf, &mut sig_buf).unwrap();
1180
1181 let parsed = parse_payload_pub(&payload_buf[..n]).unwrap();
1182 assert_eq!(parsed.role, Role::Node);
1183 }
1184
1185 #[test]
1186 fn issue_credential_depth_too_large_returns_chain_too_deep() {
1187 let issuer = make_test_identity(b"issuer");
1188 let child = make_test_identity(b"child");
1189 let scope_bytes = make_test_scope();
1190 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1191 let caveats = BoundedCaveats::try_new(&[]).unwrap();
1192 let manifest = DelegationManifest {
1193 child_pk: child.public_key(),
1194 role: Role::Leaf,
1195 depth: Depth(MAX_DEPTH + 1),
1196 scope,
1197 caveats,
1198 };
1199 let mut payload_out = [0u8; MAX_PAYLOAD_SIZE];
1200 let mut sig_out = [0u8; SIG_SIZE];
1201 assert!(matches!(
1202 issue_credential(&issuer, &manifest, &mut payload_out, &mut sig_out),
1203 Err(KernelError::ChainTooDeep)
1204 ));
1205 }
1206
1207 #[test]
1208 fn issue_credential_leaf_role_encoded_as_zero() {
1209 let issuer = make_test_identity(b"issuer");
1210 let child = make_test_identity(b"child");
1211 let scope_bytes = make_test_scope();
1212 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1213 let caveats = BoundedCaveats::try_new(&[]).unwrap();
1214 let manifest = DelegationManifest {
1215 child_pk: child.public_key(),
1216 role: Role::Leaf,
1217 depth: Depth(1),
1218 scope,
1219 caveats,
1220 };
1221 let mut payload_out = [0u8; MAX_PAYLOAD_SIZE];
1222 let mut sig_out = [0u8; SIG_SIZE];
1223 let n = issue_credential(&issuer, &manifest, &mut payload_out, &mut sig_out).unwrap();
1224 assert_eq!(payload_out[PK_SIZE], 0u8);
1226 let _ = n;
1227 }
1228
1229 #[test]
1230 fn issue_credential_node_role_encoded_as_one() {
1231 let issuer = make_test_identity(b"issuer");
1232 let child = make_test_identity(b"child");
1233 let scope_bytes = make_test_scope();
1234 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1235 let caveats = BoundedCaveats::try_new(&[]).unwrap();
1236 let manifest = DelegationManifest {
1237 child_pk: child.public_key(),
1238 role: Role::Node,
1239 depth: Depth(1),
1240 scope,
1241 caveats,
1242 };
1243 let mut payload_out = [0u8; MAX_PAYLOAD_SIZE];
1244 let mut sig_out = [0u8; SIG_SIZE];
1245 let n = issue_credential(&issuer, &manifest, &mut payload_out, &mut sig_out).unwrap();
1246 assert_eq!(payload_out[PK_SIZE], 1u8);
1247 let _ = n;
1248 }
1249
1250 fn build_single_hop_wire() -> (Vec<u8>, [u8; PK_SIZE], [u8; SIG_SIZE], Vec<u8>) {
1255 let issuer = make_test_identity(b"root");
1256 let child = make_test_identity(b"child");
1257 let scope_bytes = make_test_scope();
1258 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1259 let caveats = BoundedCaveats::try_new(&[]).unwrap();
1260 let manifest = DelegationManifest {
1261 child_pk: child.public_key(),
1262 role: Role::Leaf,
1263 depth: Depth(1),
1264 scope,
1265 caveats,
1266 };
1267 let mut payload_buf = [0u8; MAX_PAYLOAD_SIZE];
1268 let mut sig_buf = [0u8; SIG_SIZE];
1269 let payload_len = issue_credential(&issuer, &manifest, &mut payload_buf, &mut sig_buf).unwrap();
1270
1271 let issuer_pk_copy = *issuer.public_key().0;
1272 let sig_copy = sig_buf;
1273 let payload_copy = payload_buf[..payload_len].to_vec();
1274
1275 let cred = Credential {
1276 issuer_pk: PublicKey(&issuer_pk_copy),
1277 signature: Signature(&sig_copy),
1278 payload: &payload_copy,
1279 };
1280
1281 let chain = [cred];
1282 let mut wire = vec![0u8; 5 + CREDENTIAL_FIXED_SIZE + MAX_PAYLOAD_SIZE];
1284 let n = write_credential_chain(&mut wire, &chain).unwrap();
1285 wire.truncate(n);
1286 (wire, issuer_pk_copy, sig_copy, payload_copy)
1287 }
1288
1289 #[test]
1290 fn wire_round_trip_single_credential() {
1291 let (wire, expected_pk, expected_sig, expected_payload) = build_single_hop_wire();
1292
1293 let mut chain_buf: [Credential; 16] = core::array::from_fn(|_| Credential {
1294 issuer_pk: PublicKey(&[0u8; PK_SIZE]),
1295 signature: Signature(&[0u8; SIG_SIZE]),
1296 payload: &[],
1297 });
1298 let n = read_credential_chain(&wire, &mut chain_buf).unwrap();
1300 assert_eq!(n, 1);
1301 assert_eq!(chain_buf[0].issuer_pk.0, &expected_pk);
1302 assert_eq!(chain_buf[0].signature.0, &expected_sig);
1303 assert_eq!(chain_buf[0].payload, expected_payload.as_slice());
1304 }
1305
1306 #[test]
1307 fn wire_empty_chain_write_then_read_returns_empty_chain() {
1308 let mut wire = vec![0u8; 16];
1310 let n = write_credential_chain(&mut wire, &[]).unwrap();
1311 wire.truncate(n);
1312
1313 let mut chain_buf: [Credential; 16] = core::array::from_fn(|_| Credential {
1314 issuer_pk: PublicKey(&[0u8; PK_SIZE]),
1315 signature: Signature(&[0u8; SIG_SIZE]),
1316 payload: &[],
1317 });
1318 assert!(matches!(
1319 read_credential_chain(&wire, &mut chain_buf),
1320 Err(KernelError::EmptyChain)
1321 ));
1322 }
1323
1324 #[test]
1325 fn wire_count_exceeds_max_depth_returns_chain_too_deep() {
1326 let mut wire = vec![0u8; 16];
1328 wire[0] = 0x01; let bad_count = (MAX_DEPTH + 1).to_le_bytes();
1330 wire[1..5].copy_from_slice(&bad_count);
1331
1332 let mut chain_buf: [Credential; 32] = core::array::from_fn(|_| Credential {
1333 issuer_pk: PublicKey(&[0u8; PK_SIZE]),
1334 signature: Signature(&[0u8; SIG_SIZE]),
1335 payload: &[],
1336 });
1337 assert!(matches!(
1338 read_credential_chain(&wire, &mut chain_buf),
1339 Err(KernelError::ChainTooDeep)
1340 ));
1341 }
1342
1343 #[test]
1344 fn wire_wrong_version_returns_version_mismatch() {
1345 let (mut wire, _, _, _) = build_single_hop_wire();
1346 wire[0] = 0xFF; let mut chain_buf: [Credential; 16] = core::array::from_fn(|_| Credential {
1348 issuer_pk: PublicKey(&[0u8; PK_SIZE]),
1349 signature: Signature(&[0u8; SIG_SIZE]),
1350 payload: &[],
1351 });
1352 assert!(matches!(
1353 read_credential_chain(&wire, &mut chain_buf),
1354 Err(KernelError::WireVersionMismatch)
1355 ));
1356 }
1357
1358 #[test]
1359 fn wire_truncated_buffer_returns_wire_truncated() {
1360 let (wire, _, _, _) = build_single_hop_wire();
1361 let truncated = &wire[..5];
1363 let mut chain_buf: [Credential; 16] = core::array::from_fn(|_| Credential {
1364 issuer_pk: PublicKey(&[0u8; PK_SIZE]),
1365 signature: Signature(&[0u8; SIG_SIZE]),
1366 payload: &[],
1367 });
1368 assert!(matches!(
1369 read_credential_chain(truncated, &mut chain_buf),
1370 Err(KernelError::WireTruncated)
1371 ));
1372 }
1373
1374 #[test]
1375 fn wire_trailing_bytes_after_valid_chain() {
1376 let (wire, _, _, _) = build_single_hop_wire();
1381 let mut extended = wire.clone();
1382 extended.extend_from_slice(&[0xFFu8; 8]);
1383
1384 let mut chain_buf: [Credential; 16] = core::array::from_fn(|_| Credential {
1385 issuer_pk: PublicKey(&[0u8; PK_SIZE]),
1386 signature: Signature(&[0u8; SIG_SIZE]),
1387 payload: &[],
1388 });
1389 let result = read_credential_chain(&extended, &mut chain_buf);
1392 assert!(result.is_ok());
1393 assert_eq!(result.unwrap(), 1);
1394 }
1395
1396 fn build_credential<'a>(
1402 issuer: &'a impl IdentitySigner,
1403 child_pk_bytes: &'a [u8; PK_SIZE],
1404 role: Role,
1405 depth: Depth,
1406 scope_bytes: &[u8],
1407 caveat_bytes: &[u8],
1408 payload_store: &'a mut [u8; MAX_PAYLOAD_SIZE],
1409 sig_store: &'a mut [u8; SIG_SIZE],
1410 ) -> Credential<'a> {
1411 let scope = BoundedScope::try_new(scope_bytes).unwrap();
1412 let caveats = BoundedCaveats::try_new(caveat_bytes).unwrap();
1413 let manifest = DelegationManifest {
1414 child_pk: PublicKey(child_pk_bytes),
1415 role,
1416 depth,
1417 scope,
1418 caveats,
1419 };
1420 let payload_len =
1421 issue_credential(issuer, &manifest, payload_store, sig_store).unwrap();
1422 Credential {
1423 issuer_pk: issuer.public_key(),
1424 signature: Signature(sig_store),
1425 payload: &payload_store[..payload_len],
1426 }
1427 }
1428
1429 #[test]
1430 fn verify_delegation_single_hop_ok() {
1431 let root = make_test_identity(b"root");
1432 let child = make_test_identity(b"child");
1433 let scope_bytes = make_test_scope();
1434 let caveat_bytes = make_test_caveats();
1435 let child_pk = *child.public_key().0;
1436
1437 let mut payload_store = [0u8; MAX_PAYLOAD_SIZE];
1438 let mut sig_store = [0u8; SIG_SIZE];
1439
1440 let cred = build_credential(
1441 &root, &child_pk, Role::Leaf, Depth(1),
1442 &scope_bytes, &caveat_bytes,
1443 &mut payload_store, &mut sig_store,
1444 );
1445
1446 let chain = [cred];
1447 assert!(verify_delegation(root.public_key(), &chain, Timestamp(0)).is_ok());
1448 }
1449
1450 #[test]
1451 fn verify_delegation_two_hop_ok() {
1452 let root = make_test_identity(b"root");
1453 let node = make_test_identity(b"node");
1454 let leaf = make_test_identity(b"leaf");
1455 let scope_bytes = make_test_scope();
1456 let caveat_bytes = make_test_caveats();
1457
1458 let node_pk = *node.public_key().0;
1459 let leaf_pk = *leaf.public_key().0;
1460
1461 let mut p1 = [0u8; MAX_PAYLOAD_SIZE];
1463 let mut s1 = [0u8; SIG_SIZE];
1464 let cred1 = build_credential(
1465 &root, &node_pk, Role::Node, Depth(1),
1466 &scope_bytes, &caveat_bytes, &mut p1, &mut s1,
1467 );
1468
1469 let mut p2 = [0u8; MAX_PAYLOAD_SIZE];
1471 let mut s2 = [0u8; SIG_SIZE];
1472 let cred2 = build_credential(
1473 &node, &leaf_pk, Role::Leaf, Depth(2),
1474 &scope_bytes, &caveat_bytes, &mut p2, &mut s2,
1475 );
1476
1477 let chain = [cred1, cred2];
1478 assert!(verify_delegation(root.public_key(), &chain, Timestamp(0)).is_ok());
1479 }
1480
1481 #[test]
1482 fn verify_delegation_empty_chain_returns_empty_chain() {
1483 let root = make_test_identity(b"root");
1484 assert!(matches!(
1485 verify_delegation(root.public_key(), &[], Timestamp(0)),
1486 Err(KernelError::EmptyChain)
1487 ));
1488 }
1489
1490 #[test]
1491 fn verify_delegation_wrong_root_pk_returns_parent_key_mismatch() {
1492 let root = make_test_identity(b"root");
1493 let wrong = make_test_identity(b"wrong-root");
1494 let child = make_test_identity(b"child");
1495 let scope_bytes = make_test_scope();
1496 let caveat_bytes = make_test_caveats();
1497 let child_pk = *child.public_key().0;
1498
1499 let mut payload_store = [0u8; MAX_PAYLOAD_SIZE];
1500 let mut sig_store = [0u8; SIG_SIZE];
1501
1502 let cred = build_credential(
1503 &root, &child_pk, Role::Leaf, Depth(1),
1504 &scope_bytes, &caveat_bytes,
1505 &mut payload_store, &mut sig_store,
1506 );
1507 let chain = [cred];
1508 assert!(matches!(
1509 verify_delegation(wrong.public_key(), &chain, Timestamp(0)),
1510 Err(KernelError::ParentKeyMismatch)
1511 ));
1512 }
1513
1514 #[test]
1515 fn verify_delegation_tampered_signature_returns_error() {
1516 let root = make_test_identity(b"root");
1517 let child = make_test_identity(b"child");
1518 let scope_bytes = make_test_scope();
1519 let caveat_bytes = make_test_caveats();
1520 let child_pk = *child.public_key().0;
1521
1522 let mut payload_store = [0u8; MAX_PAYLOAD_SIZE];
1523 let mut sig_store = [0u8; SIG_SIZE];
1524
1525 build_credential(
1526 &root, &child_pk, Role::Leaf, Depth(1),
1527 &scope_bytes, &caveat_bytes,
1528 &mut payload_store, &mut sig_store,
1529 );
1530
1531 sig_store[100] ^= 0xFF;
1533
1534 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1535 let caveats = BoundedCaveats::try_new(&caveat_bytes).unwrap();
1536 let manifest = DelegationManifest {
1537 child_pk: PublicKey(&child_pk),
1538 role: Role::Leaf,
1539 depth: Depth(1),
1540 scope,
1541 caveats,
1542 };
1543 let mut payload_buf = [0u8; MAX_PAYLOAD_SIZE];
1544 let mut dummy_sig = [0u8; SIG_SIZE];
1545 let payload_len =
1546 issue_credential(&root, &manifest, &mut payload_buf, &mut dummy_sig).unwrap();
1547
1548 let tampered_cred = Credential {
1549 issuer_pk: root.public_key(),
1550 signature: Signature(&sig_store),
1551 payload: &payload_buf[..payload_len],
1552 };
1553 let chain = [tampered_cred];
1554 let result = verify_delegation(root.public_key(), &chain, Timestamp(0));
1555 assert!(
1556 matches!(result, Err(KernelError::SignatureInvalid))
1557 || matches!(result, Err(KernelError::SignatureDecodingFailed)),
1558 "expected SignatureInvalid or SignatureDecodingFailed, got {:?}",
1559 result
1560 );
1561 }
1562
1563 #[test]
1564 fn verify_delegation_expired_caveat_returns_not_after_violation() {
1565 let root = make_test_identity(b"root");
1566 let child = make_test_identity(b"child");
1567 let scope_bytes = make_test_scope();
1568
1569 let caveat = not_after(Timestamp(1000));
1571 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1572 let caveats = BoundedCaveats::try_new(&caveat).unwrap();
1573 let child_pk = *child.public_key().0;
1574 let manifest = DelegationManifest {
1575 child_pk: PublicKey(&child_pk),
1576 role: Role::Leaf,
1577 depth: Depth(1),
1578 scope,
1579 caveats,
1580 };
1581
1582 let mut payload_store = [0u8; MAX_PAYLOAD_SIZE];
1583 let mut sig_store = [0u8; SIG_SIZE];
1584 let payload_len =
1585 issue_credential(&root, &manifest, &mut payload_store, &mut sig_store).unwrap();
1586
1587 let cred = Credential {
1588 issuer_pk: root.public_key(),
1589 signature: Signature(&sig_store),
1590 payload: &payload_store[..payload_len],
1591 };
1592 let chain = [cred];
1593 assert!(matches!(
1595 verify_delegation(root.public_key(), &chain, Timestamp(1001)),
1596 Err(KernelError::NotAfterViolation)
1597 ));
1598 }
1599
1600 #[test]
1601 fn verify_delegation_not_yet_valid_returns_not_before_violation() {
1602 let root = make_test_identity(b"root");
1603 let child = make_test_identity(b"child");
1604 let scope_bytes = make_test_scope();
1605
1606 let caveat = not_before(Timestamp(5000));
1608 let scope = BoundedScope::try_new(&scope_bytes).unwrap();
1609 let caveats = BoundedCaveats::try_new(&caveat).unwrap();
1610 let child_pk = *child.public_key().0;
1611 let manifest = DelegationManifest {
1612 child_pk: PublicKey(&child_pk),
1613 role: Role::Leaf,
1614 depth: Depth(1),
1615 scope,
1616 caveats,
1617 };
1618
1619 let mut payload_store = [0u8; MAX_PAYLOAD_SIZE];
1620 let mut sig_store = [0u8; SIG_SIZE];
1621 let payload_len =
1622 issue_credential(&root, &manifest, &mut payload_store, &mut sig_store).unwrap();
1623
1624 let cred = Credential {
1625 issuer_pk: root.public_key(),
1626 signature: Signature(&sig_store),
1627 payload: &payload_store[..payload_len],
1628 };
1629 let chain = [cred];
1630 assert!(matches!(
1632 verify_delegation(root.public_key(), &chain, Timestamp(4999)),
1633 Err(KernelError::NotBeforeViolation)
1634 ));
1635 }
1636
1637 #[test]
1638 fn verify_delegation_leaf_cannot_delegate() {
1639 let root = make_test_identity(b"root");
1640 let node = make_test_identity(b"node");
1641 let leaf = make_test_identity(b"leaf");
1642 let scope_bytes = make_test_scope();
1643 let caveat_bytes = make_test_caveats();
1644
1645 let node_pk = *node.public_key().0;
1646 let leaf_pk = *leaf.public_key().0;
1647
1648 let mut p1 = [0u8; MAX_PAYLOAD_SIZE];
1650 let mut s1 = [0u8; SIG_SIZE];
1651 let cred1 = build_credential(
1652 &root, &node_pk, Role::Leaf, Depth(1),
1653 &scope_bytes, &caveat_bytes, &mut p1, &mut s1,
1654 );
1655
1656 let mut p2 = [0u8; MAX_PAYLOAD_SIZE];
1658 let mut s2 = [0u8; SIG_SIZE];
1659 let cred2 = build_credential(
1660 &node, &leaf_pk, Role::Leaf, Depth(2),
1661 &scope_bytes, &caveat_bytes, &mut p2, &mut s2,
1662 );
1663
1664 let chain = [cred1, cred2];
1665 assert!(matches!(
1666 verify_delegation(root.public_key(), &chain, Timestamp(0)),
1667 Err(KernelError::LeafCannotDelegate)
1668 ));
1669 }
1670
1671 #[test]
1672 fn verify_delegation_depth_out_of_sequence_returns_depth_mismatch() {
1673 let root = make_test_identity(b"root");
1674 let child = make_test_identity(b"child");
1675 let scope_bytes = make_test_scope();
1676 let caveat_bytes = make_test_caveats();
1677 let child_pk = *child.public_key().0;
1678
1679 let mut payload_store = [0u8; MAX_PAYLOAD_SIZE];
1681 let mut sig_store = [0u8; SIG_SIZE];
1682 let cred = build_credential(
1683 &root, &child_pk, Role::Leaf, Depth(2),
1684 &scope_bytes, &caveat_bytes,
1685 &mut payload_store, &mut sig_store,
1686 );
1687 let chain = [cred];
1688 assert!(matches!(
1689 verify_delegation(root.public_key(), &chain, Timestamp(0)),
1690 Err(KernelError::DepthMismatch)
1691 ));
1692 }
1693}