1extern crate alloc;
42
43use alloc::string::{String, ToString};
44use alloc::vec::Vec;
45
46use ring::rand::SystemRandom;
47use ring::signature::{
48 self, ECDSA_P256_SHA256_FIXED, ECDSA_P384_SHA384_FIXED, ED25519, RSA_PSS_2048_8192_SHA256,
49 UnparsedPublicKey,
50};
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57#[non_exhaustive]
58pub enum SignatureAlgorithm {
59 EcdsaP256,
61 EcdsaP384,
63 RsaPss2048,
65 Ed25519,
67}
68
69impl SignatureAlgorithm {
70 #[must_use]
72 pub const fn wire_id(self) -> u8 {
73 match self {
74 Self::EcdsaP256 => 1,
75 Self::EcdsaP384 => 2,
76 Self::RsaPss2048 => 3,
77 Self::Ed25519 => 4,
78 }
79 }
80
81 #[must_use]
83 pub const fn from_wire_id(id: u8) -> Option<Self> {
84 match id {
85 1 => Some(Self::EcdsaP256),
86 2 => Some(Self::EcdsaP384),
87 3 => Some(Self::RsaPss2048),
88 4 => Some(Self::Ed25519),
89 _ => None,
90 }
91 }
92
93 #[must_use]
97 pub const fn expected_signature_len(self) -> Option<usize> {
98 match self {
99 Self::EcdsaP256 | Self::Ed25519 => Some(64),
100 Self::EcdsaP384 => Some(96),
101 Self::RsaPss2048 => Some(256),
102 }
103 }
104}
105
106pub const DELEGATION_MAGIC: &[u8; 8] = b"ZERODDSD";
108
109pub const DELEGATION_VERSION: u8 = 1;
111
112pub const MAX_TOPIC_PATTERNS: usize = 64;
114pub const MAX_PARTITION_PATTERNS: usize = 64;
116pub const MAX_PATTERN_LEN: usize = 256;
118
119#[derive(Debug, Clone, PartialEq, Eq)]
121#[non_exhaustive]
122pub enum DelegationError {
123 TooManyPatterns {
125 kind: &'static str,
127 count: usize,
129 max: usize,
131 },
132 PatternTooLong {
134 len: usize,
136 max: usize,
138 },
139 SignFailed(String),
141 VerifyFailed(String),
143 Malformed(String),
145 UnknownAlgorithm(u8),
147 InvalidTimeWindow,
149 BadMagic,
151 UnsupportedVersion(u8),
153}
154
155impl core::fmt::Display for DelegationError {
156 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157 match self {
158 Self::TooManyPatterns { kind, count, max } => {
159 write!(f, "{kind} patterns: {count} > max {max}")
160 }
161 Self::PatternTooLong { len, max } => {
162 write!(f, "pattern length {len} > max {max}")
163 }
164 Self::SignFailed(s) => write!(f, "sign failed: {s}"),
165 Self::VerifyFailed(s) => write!(f, "verify failed: {s}"),
166 Self::Malformed(s) => write!(f, "malformed delegation: {s}"),
167 Self::UnknownAlgorithm(id) => write!(f, "unknown algorithm id: {id}"),
168 Self::InvalidTimeWindow => write!(f, "not_before > not_after"),
169 Self::BadMagic => write!(f, "bad magic bytes"),
170 Self::UnsupportedVersion(v) => write!(f, "unsupported version: {v}"),
171 }
172 }
173}
174
175#[cfg(feature = "std")]
176impl std::error::Error for DelegationError {}
177
178pub type DelegationResult<T> = Result<T, DelegationError>;
180
181#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct DelegationLink {
186 pub delegator_guid: [u8; 16],
190 pub delegatee_guid: [u8; 16],
193 pub allowed_topic_patterns: Vec<String>,
196 pub allowed_partition_patterns: Vec<String>,
198 pub not_before: i64,
200 pub not_after: i64,
202 pub algorithm: SignatureAlgorithm,
204 pub signature: Vec<u8>,
206}
207
208impl DelegationLink {
209 pub fn new(
220 delegator_guid: [u8; 16],
221 delegatee_guid: [u8; 16],
222 allowed_topic_patterns: Vec<String>,
223 allowed_partition_patterns: Vec<String>,
224 not_before: i64,
225 not_after: i64,
226 algorithm: SignatureAlgorithm,
227 ) -> DelegationResult<Self> {
228 if allowed_topic_patterns.len() > MAX_TOPIC_PATTERNS {
229 return Err(DelegationError::TooManyPatterns {
230 kind: "topic",
231 count: allowed_topic_patterns.len(),
232 max: MAX_TOPIC_PATTERNS,
233 });
234 }
235 if allowed_partition_patterns.len() > MAX_PARTITION_PATTERNS {
236 return Err(DelegationError::TooManyPatterns {
237 kind: "partition",
238 count: allowed_partition_patterns.len(),
239 max: MAX_PARTITION_PATTERNS,
240 });
241 }
242 for p in allowed_topic_patterns
243 .iter()
244 .chain(allowed_partition_patterns.iter())
245 {
246 if p.len() > MAX_PATTERN_LEN {
247 return Err(DelegationError::PatternTooLong {
248 len: p.len(),
249 max: MAX_PATTERN_LEN,
250 });
251 }
252 }
253 if not_before > not_after {
254 return Err(DelegationError::InvalidTimeWindow);
255 }
256 Ok(Self {
257 delegator_guid,
258 delegatee_guid,
259 allowed_topic_patterns,
260 allowed_partition_patterns,
261 not_before,
262 not_after,
263 algorithm,
264 signature: Vec::new(),
265 })
266 }
267
268 #[must_use]
270 pub fn signing_bytes(&self) -> Vec<u8> {
271 let mut buf = Vec::with_capacity(64 + 32 * self.allowed_topic_patterns.len());
272 buf.extend_from_slice(DELEGATION_MAGIC);
273 buf.push(DELEGATION_VERSION);
274 buf.extend_from_slice(&self.delegator_guid);
275 buf.extend_from_slice(&self.delegatee_guid);
276 buf.extend_from_slice(&self.not_before.to_be_bytes());
277 buf.extend_from_slice(&self.not_after.to_be_bytes());
278 buf.push(self.algorithm.wire_id());
279
280 let n_topic = u32::try_from(self.allowed_topic_patterns.len()).unwrap_or(u32::MAX);
282 buf.extend_from_slice(&n_topic.to_be_bytes());
283 for p in &self.allowed_topic_patterns {
284 let len = u32::try_from(p.len()).unwrap_or(u32::MAX);
285 buf.extend_from_slice(&len.to_be_bytes());
286 buf.extend_from_slice(p.as_bytes());
287 }
288
289 let n_part = u32::try_from(self.allowed_partition_patterns.len()).unwrap_or(u32::MAX);
291 buf.extend_from_slice(&n_part.to_be_bytes());
292 for p in &self.allowed_partition_patterns {
293 let len = u32::try_from(p.len()).unwrap_or(u32::MAX);
294 buf.extend_from_slice(&len.to_be_bytes());
295 buf.extend_from_slice(p.as_bytes());
296 }
297
298 buf
299 }
300
301 pub fn sign(&mut self, signing_key_pkcs8: &[u8]) -> DelegationResult<()> {
312 let input = self.signing_bytes();
313 let sig = match self.algorithm {
314 SignatureAlgorithm::EcdsaP256 => sign_ecdsa(
315 &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
316 signing_key_pkcs8,
317 &input,
318 )?,
319 SignatureAlgorithm::EcdsaP384 => sign_ecdsa(
320 &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
321 signing_key_pkcs8,
322 &input,
323 )?,
324 SignatureAlgorithm::RsaPss2048 => sign_rsa_pss(signing_key_pkcs8, &input)?,
325 SignatureAlgorithm::Ed25519 => sign_ed25519(signing_key_pkcs8, &input)?,
326 };
327 self.signature = sig;
328 Ok(())
329 }
330
331 pub fn verify(&self, verify_public_key: &[u8]) -> DelegationResult<()> {
346 if self.signature.is_empty() {
347 return Err(DelegationError::SignFailed("empty signature".to_string()));
348 }
349 if let Some(expected_len) = self.algorithm.expected_signature_len() {
350 if self.signature.len() != expected_len {
351 return Err(DelegationError::VerifyFailed(alloc::format!(
352 "sig len {} != expected {}",
353 self.signature.len(),
354 expected_len
355 )));
356 }
357 }
358 let input = self.signing_bytes();
359 let alg: &dyn signature::VerificationAlgorithm = match self.algorithm {
360 SignatureAlgorithm::EcdsaP256 => &ECDSA_P256_SHA256_FIXED,
361 SignatureAlgorithm::EcdsaP384 => &ECDSA_P384_SHA384_FIXED,
362 SignatureAlgorithm::RsaPss2048 => &RSA_PSS_2048_8192_SHA256,
363 SignatureAlgorithm::Ed25519 => &ED25519,
364 };
365 let pk = UnparsedPublicKey::new(alg, verify_public_key);
366 pk.verify(&input, &self.signature)
367 .map_err(|_| DelegationError::VerifyFailed("ring::verify".to_string()))
368 }
369
370 #[must_use]
374 pub fn encode(&self) -> Vec<u8> {
375 let mut buf = self.signing_bytes();
376 let sig_len = u16::try_from(self.signature.len()).unwrap_or(u16::MAX);
377 buf.extend_from_slice(&sig_len.to_be_bytes());
378 buf.extend_from_slice(&self.signature);
379 buf
380 }
381
382 pub fn decode(buf: &[u8]) -> DelegationResult<(Self, &[u8])> {
391 let mut p = 0usize;
392 let need = |needed: usize, p: usize, buf: &[u8]| -> DelegationResult<()> {
393 if buf.len() < p + needed {
394 return Err(DelegationError::Malformed(alloc::format!(
395 "truncated at offset {p}, needed {needed} bytes"
396 )));
397 }
398 Ok(())
399 };
400
401 need(8, p, buf)?;
402 if &buf[p..p + 8] != DELEGATION_MAGIC {
403 return Err(DelegationError::BadMagic);
404 }
405 p += 8;
406
407 need(1, p, buf)?;
408 let version = buf[p];
409 if version != DELEGATION_VERSION {
410 return Err(DelegationError::UnsupportedVersion(version));
411 }
412 p += 1;
413
414 need(16, p, buf)?;
415 let mut delegator_guid = [0u8; 16];
416 delegator_guid.copy_from_slice(&buf[p..p + 16]);
417 p += 16;
418
419 need(16, p, buf)?;
420 let mut delegatee_guid = [0u8; 16];
421 delegatee_guid.copy_from_slice(&buf[p..p + 16]);
422 p += 16;
423
424 need(8, p, buf)?;
425 let not_before = i64::from_be_bytes(buf[p..p + 8].try_into().unwrap_or([0u8; 8]));
426 p += 8;
427
428 need(8, p, buf)?;
429 let not_after = i64::from_be_bytes(buf[p..p + 8].try_into().unwrap_or([0u8; 8]));
430 p += 8;
431
432 need(1, p, buf)?;
433 let algo_id = buf[p];
434 p += 1;
435 let algorithm = SignatureAlgorithm::from_wire_id(algo_id)
436 .ok_or(DelegationError::UnknownAlgorithm(algo_id))?;
437
438 need(4, p, buf)?;
440 let n_topic = u32::from_be_bytes(buf[p..p + 4].try_into().unwrap_or([0u8; 4])) as usize;
441 p += 4;
442 if n_topic > MAX_TOPIC_PATTERNS {
443 return Err(DelegationError::TooManyPatterns {
444 kind: "topic",
445 count: n_topic,
446 max: MAX_TOPIC_PATTERNS,
447 });
448 }
449 let mut allowed_topic_patterns = Vec::with_capacity(n_topic);
450 for _ in 0..n_topic {
451 need(4, p, buf)?;
452 let len = u32::from_be_bytes(buf[p..p + 4].try_into().unwrap_or([0u8; 4])) as usize;
453 p += 4;
454 if len > MAX_PATTERN_LEN {
455 return Err(DelegationError::PatternTooLong {
456 len,
457 max: MAX_PATTERN_LEN,
458 });
459 }
460 need(len, p, buf)?;
461 let s = core::str::from_utf8(&buf[p..p + len])
462 .map_err(|e| DelegationError::Malformed(alloc::format!("utf8 topic: {e}")))?;
463 allowed_topic_patterns.push(s.to_string());
464 p += len;
465 }
466
467 need(4, p, buf)?;
469 let n_part = u32::from_be_bytes(buf[p..p + 4].try_into().unwrap_or([0u8; 4])) as usize;
470 p += 4;
471 if n_part > MAX_PARTITION_PATTERNS {
472 return Err(DelegationError::TooManyPatterns {
473 kind: "partition",
474 count: n_part,
475 max: MAX_PARTITION_PATTERNS,
476 });
477 }
478 let mut allowed_partition_patterns = Vec::with_capacity(n_part);
479 for _ in 0..n_part {
480 need(4, p, buf)?;
481 let len = u32::from_be_bytes(buf[p..p + 4].try_into().unwrap_or([0u8; 4])) as usize;
482 p += 4;
483 if len > MAX_PATTERN_LEN {
484 return Err(DelegationError::PatternTooLong {
485 len,
486 max: MAX_PATTERN_LEN,
487 });
488 }
489 need(len, p, buf)?;
490 let s = core::str::from_utf8(&buf[p..p + len])
491 .map_err(|e| DelegationError::Malformed(alloc::format!("utf8 part: {e}")))?;
492 allowed_partition_patterns.push(s.to_string());
493 p += len;
494 }
495
496 need(2, p, buf)?;
498 let sig_len = u16::from_be_bytes(buf[p..p + 2].try_into().unwrap_or([0u8; 2])) as usize;
499 p += 2;
500 need(sig_len, p, buf)?;
501 let signature = buf[p..p + sig_len].to_vec();
502 p += sig_len;
503
504 let link = Self {
505 delegator_guid,
506 delegatee_guid,
507 allowed_topic_patterns,
508 allowed_partition_patterns,
509 not_before,
510 not_after,
511 algorithm,
512 signature,
513 };
514 Ok((link, &buf[p..]))
515 }
516}
517
518#[derive(Debug, Clone, PartialEq, Eq)]
522pub struct DelegationChain {
523 pub origin_guid: [u8; 16],
526 pub links: Vec<DelegationLink>,
529}
530
531pub const MAX_CHAIN_DEPTH_HARD_CAP: usize = 8;
534
535impl DelegationChain {
536 pub fn new(origin_guid: [u8; 16], links: Vec<DelegationLink>) -> DelegationResult<Self> {
542 if links.len() > MAX_CHAIN_DEPTH_HARD_CAP {
543 return Err(DelegationError::TooManyPatterns {
544 kind: "chain",
545 count: links.len(),
546 max: MAX_CHAIN_DEPTH_HARD_CAP,
547 });
548 }
549 Ok(Self { origin_guid, links })
550 }
551
552 #[must_use]
554 pub fn depth(&self) -> usize {
555 self.links.len()
556 }
557
558 #[must_use]
560 pub fn edge_guid(&self) -> Option<[u8; 16]> {
561 self.links.last().map(|l| l.delegatee_guid)
562 }
563
564 #[must_use]
574 pub fn encode(&self) -> Vec<u8> {
575 let mut buf = Vec::with_capacity(32 + 256 * self.links.len());
576 buf.push(DELEGATION_VERSION);
577 buf.extend_from_slice(&self.origin_guid);
578 let n = u8::try_from(self.links.len()).unwrap_or(u8::MAX);
579 buf.push(n);
580 for link in &self.links {
581 buf.extend_from_slice(&link.encode());
582 }
583 buf
584 }
585
586 pub fn decode(buf: &[u8]) -> DelegationResult<Self> {
591 if buf.len() < 1 + 16 + 1 {
592 return Err(DelegationError::Malformed(
593 "chain header truncated".to_string(),
594 ));
595 }
596 let version = buf[0];
597 if version != DELEGATION_VERSION {
598 return Err(DelegationError::UnsupportedVersion(version));
599 }
600 let mut origin_guid = [0u8; 16];
601 origin_guid.copy_from_slice(&buf[1..17]);
602 let n_links = buf[17] as usize;
603 if n_links > MAX_CHAIN_DEPTH_HARD_CAP {
604 return Err(DelegationError::TooManyPatterns {
605 kind: "chain",
606 count: n_links,
607 max: MAX_CHAIN_DEPTH_HARD_CAP,
608 });
609 }
610 let mut tail = &buf[18..];
611 let mut links = Vec::with_capacity(n_links);
612 for _ in 0..n_links {
613 let (link, rest) = DelegationLink::decode(tail)?;
614 links.push(link);
615 tail = rest;
616 }
617 Ok(Self { origin_guid, links })
618 }
619}
620
621fn sign_ecdsa(
624 alg: &'static signature::EcdsaSigningAlgorithm,
625 pkcs8: &[u8],
626 input: &[u8],
627) -> DelegationResult<Vec<u8>> {
628 let rng = SystemRandom::new();
629 let key_pair = signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8, &rng)
630 .map_err(|e| DelegationError::SignFailed(alloc::format!("ecdsa key parse: {e}")))?;
631 let sig = key_pair
632 .sign(&rng, input)
633 .map_err(|e| DelegationError::SignFailed(alloc::format!("ecdsa sign: {e}")))?;
634 Ok(sig.as_ref().to_vec())
635}
636
637fn sign_rsa_pss(pkcs8: &[u8], input: &[u8]) -> DelegationResult<Vec<u8>> {
638 let key_pair = signature::RsaKeyPair::from_pkcs8(pkcs8)
639 .map_err(|e| DelegationError::SignFailed(alloc::format!("rsa key parse: {e}")))?;
640 if key_pair.public().modulus_len() != 256 {
641 return Err(DelegationError::SignFailed(alloc::format!(
642 "rsa key is {} bits, expected 2048",
643 key_pair.public().modulus_len() * 8
644 )));
645 }
646 let mut sig = alloc::vec![0u8; key_pair.public().modulus_len()];
647 let rng = SystemRandom::new();
648 key_pair
649 .sign(&signature::RSA_PSS_SHA256, &rng, input, &mut sig)
650 .map_err(|e| DelegationError::SignFailed(alloc::format!("rsa sign: {e}")))?;
651 Ok(sig)
652}
653
654fn sign_ed25519(pkcs8: &[u8], input: &[u8]) -> DelegationResult<Vec<u8>> {
655 let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8)
656 .map_err(|e| DelegationError::SignFailed(alloc::format!("ed25519 key parse: {e}")))?;
657 let sig = key_pair.sign(input);
658 Ok(sig.as_ref().to_vec())
659}
660
661#[cfg(test)]
662#[allow(clippy::expect_used, clippy::unwrap_used)]
663mod tests {
664 use super::*;
665 use ring::rand::SystemRandom;
666 use ring::signature::{
667 ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, EcdsaKeyPair,
668 Ed25519KeyPair, KeyPair,
669 };
670
671 fn link_skeleton() -> DelegationLink {
672 DelegationLink::new(
673 [0xAA; 16],
674 [0xBB; 16],
675 alloc::vec!["sensor/*".to_string()],
676 alloc::vec!["public".to_string()],
677 1_700_000_000,
678 1_800_000_000,
679 SignatureAlgorithm::EcdsaP256,
680 )
681 .expect("valid skeleton")
682 }
683
684 fn ecdsa_key(alg: &'static signature::EcdsaSigningAlgorithm) -> (Vec<u8>, Vec<u8>) {
685 let rng = SystemRandom::new();
686 let pkcs8 = EcdsaKeyPair::generate_pkcs8(alg, &rng).expect("gen ecdsa");
687 let pkcs8_vec = pkcs8.as_ref().to_vec();
688 let key = EcdsaKeyPair::from_pkcs8(alg, &pkcs8_vec, &rng).expect("parse");
689 let pub_key = key.public_key().as_ref().to_vec();
690 (pkcs8_vec, pub_key)
691 }
692
693 fn ed25519_key() -> (Vec<u8>, Vec<u8>) {
694 let rng = SystemRandom::new();
695 let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rng).expect("gen");
696 let kp = Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).expect("parse");
697 (
698 pkcs8_bytes.as_ref().to_vec(),
699 kp.public_key().as_ref().to_vec(),
700 )
701 }
702
703 #[test]
704 fn signing_bytes_deterministic() {
705 let l = link_skeleton();
706 let a = l.signing_bytes();
707 let b = l.signing_bytes();
708 assert_eq!(a, b);
709 assert_eq!(&a[..8], DELEGATION_MAGIC);
711 assert_eq!(a[8], DELEGATION_VERSION);
712 }
713
714 #[test]
715 fn ecdsa_p256_sign_verify_roundtrip() {
716 let (pkcs8, pub_key) = ecdsa_key(&ECDSA_P256_SHA256_FIXED_SIGNING);
717 let mut l = link_skeleton();
718 l.algorithm = SignatureAlgorithm::EcdsaP256;
719 l.sign(&pkcs8).expect("sign");
720 l.verify(&pub_key).expect("verify");
721 assert_eq!(l.signature.len(), 64);
722 }
723
724 #[test]
725 fn ecdsa_p384_sign_verify_roundtrip() {
726 let (pkcs8, pub_key) = ecdsa_key(&ECDSA_P384_SHA384_FIXED_SIGNING);
727 let mut l = link_skeleton();
728 l.algorithm = SignatureAlgorithm::EcdsaP384;
729 l.sign(&pkcs8).expect("sign");
730 l.verify(&pub_key).expect("verify");
731 assert_eq!(l.signature.len(), 96);
732 }
733
734 #[test]
735 fn ed25519_sign_verify_roundtrip() {
736 let (pkcs8, pub_key) = ed25519_key();
737 let mut l = link_skeleton();
738 l.algorithm = SignatureAlgorithm::Ed25519;
739 l.sign(&pkcs8).expect("sign");
740 l.verify(&pub_key).expect("verify");
741 assert_eq!(l.signature.len(), 64);
742 }
743
744 const TEST_RSA_2048_PKCS8: &[u8] = include_bytes!("../tests/fixtures/rsa_2048_test_pkcs8.der");
749
750 #[test]
755 fn rsa_pss_2048_sign_succeeds_with_2048_bit_key() {
756 let mut l = link_skeleton();
757 l.algorithm = SignatureAlgorithm::RsaPss2048;
758 l.sign(TEST_RSA_2048_PKCS8).expect("RSA-PSS-2048 sign");
759 assert_eq!(l.signature.len(), 256);
761 }
762
763 #[test]
764 fn tampered_byte_breaks_verify() {
765 let (pkcs8, pub_key) = ecdsa_key(&ECDSA_P256_SHA256_FIXED_SIGNING);
766 let mut l = link_skeleton();
767 l.sign(&pkcs8).expect("sign");
768 l.allowed_topic_patterns[0] = "/different/*".to_string();
771 let err = l.verify(&pub_key).expect_err("must fail");
772 assert!(matches!(err, DelegationError::VerifyFailed(_)));
773 }
774
775 #[test]
776 fn wrong_pubkey_breaks_verify() {
777 let (pkcs8_a, _pub_a) = ecdsa_key(&ECDSA_P256_SHA256_FIXED_SIGNING);
778 let (_pkcs8_b, pub_b) = ecdsa_key(&ECDSA_P256_SHA256_FIXED_SIGNING);
779 let mut l = link_skeleton();
780 l.sign(&pkcs8_a).expect("sign");
781 let err = l.verify(&pub_b).expect_err("must fail");
782 assert!(matches!(err, DelegationError::VerifyFailed(_)));
783 }
784
785 #[test]
786 fn empty_signature_rejects_verify() {
787 let (_pkcs8, pub_key) = ecdsa_key(&ECDSA_P256_SHA256_FIXED_SIGNING);
788 let l = link_skeleton(); let err = l.verify(&pub_key).expect_err("must fail");
790 assert!(matches!(err, DelegationError::SignFailed(_)));
791 }
792
793 #[test]
794 fn link_encode_decode_roundtrip() {
795 let (pkcs8, _) = ecdsa_key(&ECDSA_P256_SHA256_FIXED_SIGNING);
796 let mut l = link_skeleton();
797 l.sign(&pkcs8).expect("sign");
798 let wire = l.encode();
799 let (decoded, tail) = DelegationLink::decode(&wire).expect("decode");
800 assert!(tail.is_empty());
801 assert_eq!(decoded, l);
802 }
803
804 #[test]
805 fn link_decode_bad_magic_rejects() {
806 let mut bad = alloc::vec![0u8; 64];
807 bad[..8].copy_from_slice(b"NOTMAGIC");
808 let err = DelegationLink::decode(&bad).expect_err("must fail");
809 assert!(matches!(err, DelegationError::BadMagic));
810 }
811
812 #[test]
813 fn link_decode_bad_version_rejects() {
814 let mut wire = link_skeleton().encode();
815 wire[8] = 99; let err = DelegationLink::decode(&wire).expect_err("must fail");
817 assert!(matches!(err, DelegationError::UnsupportedVersion(99)));
818 }
819
820 #[test]
821 fn link_decode_unknown_algorithm_rejects() {
822 let mut l = link_skeleton();
823 l.signature = alloc::vec![0u8; 64];
824 let mut wire = l.encode();
825 wire[57] = 99;
828 let err = DelegationLink::decode(&wire).expect_err("must fail");
829 assert!(matches!(err, DelegationError::UnknownAlgorithm(99)));
830 }
831
832 #[test]
833 fn link_new_rejects_too_many_topics() {
834 let topics = (0..MAX_TOPIC_PATTERNS + 1)
835 .map(|i| alloc::format!("topic_{i}"))
836 .collect();
837 let err = DelegationLink::new(
838 [0; 16],
839 [0; 16],
840 topics,
841 alloc::vec![],
842 0,
843 1,
844 SignatureAlgorithm::EcdsaP256,
845 )
846 .expect_err("must fail");
847 assert!(matches!(
848 err,
849 DelegationError::TooManyPatterns { kind: "topic", .. }
850 ));
851 }
852
853 #[test]
854 fn link_new_rejects_inverted_window() {
855 let err = DelegationLink::new(
856 [0; 16],
857 [0; 16],
858 alloc::vec![],
859 alloc::vec![],
860 100,
861 50,
862 SignatureAlgorithm::EcdsaP256,
863 )
864 .expect_err("must fail");
865 assert!(matches!(err, DelegationError::InvalidTimeWindow));
866 }
867
868 #[test]
869 fn chain_encode_decode_roundtrip() {
870 let (pkcs8, _) = ecdsa_key(&ECDSA_P256_SHA256_FIXED_SIGNING);
871 let mut l1 = link_skeleton();
872 l1.sign(&pkcs8).expect("sign1");
873 let mut l2 = DelegationLink::new(
874 [0xBB; 16],
875 [0xCC; 16],
876 alloc::vec!["sensor/lidar".to_string()],
877 alloc::vec![],
878 1_700_000_000,
879 1_800_000_000,
880 SignatureAlgorithm::EcdsaP256,
881 )
882 .expect("l2 new");
883 l2.sign(&pkcs8).expect("sign2");
884
885 let chain =
886 DelegationChain::new([0xAA; 16], alloc::vec![l1.clone(), l2.clone()]).expect("chain");
887 assert_eq!(chain.depth(), 2);
888 assert_eq!(chain.edge_guid(), Some([0xCC; 16]));
889 let wire = chain.encode();
890 let decoded = DelegationChain::decode(&wire).expect("decode");
891 assert_eq!(decoded, chain);
892 }
893
894 #[test]
895 fn chain_new_rejects_too_deep() {
896 let dummy = link_skeleton();
897 let too_deep = alloc::vec![dummy; MAX_CHAIN_DEPTH_HARD_CAP + 1];
898 let err = DelegationChain::new([0; 16], too_deep).expect_err("must fail");
899 assert!(matches!(
900 err,
901 DelegationError::TooManyPatterns { kind: "chain", .. }
902 ));
903 }
904
905 #[test]
906 fn algorithm_wire_id_roundtrip() {
907 for a in [
908 SignatureAlgorithm::EcdsaP256,
909 SignatureAlgorithm::EcdsaP384,
910 SignatureAlgorithm::RsaPss2048,
911 SignatureAlgorithm::Ed25519,
912 ] {
913 let id = a.wire_id();
914 assert_eq!(SignatureAlgorithm::from_wire_id(id), Some(a));
915 }
916 assert_eq!(SignatureAlgorithm::from_wire_id(0), None);
917 assert_eq!(SignatureAlgorithm::from_wire_id(255), None);
918 }
919
920 #[test]
927 fn expected_signature_len_per_algorithm() {
928 assert_eq!(
929 SignatureAlgorithm::EcdsaP256.expected_signature_len(),
930 Some(64)
931 );
932 assert_eq!(
933 SignatureAlgorithm::EcdsaP384.expected_signature_len(),
934 Some(96)
935 );
936 assert_eq!(
937 SignatureAlgorithm::Ed25519.expected_signature_len(),
938 Some(64)
939 );
940 assert_eq!(
941 SignatureAlgorithm::RsaPss2048.expected_signature_len(),
942 Some(256)
943 );
944 }
945
946 #[test]
949 fn delegation_error_display_messages_specific() {
950 assert_eq!(
951 alloc::format!(
952 "{}",
953 DelegationError::TooManyPatterns {
954 kind: "topic",
955 count: 100,
956 max: 64
957 }
958 ),
959 "topic patterns: 100 > max 64"
960 );
961 assert_eq!(
962 alloc::format!("{}", DelegationError::PatternTooLong { len: 500, max: 256 }),
963 "pattern length 500 > max 256"
964 );
965 assert_eq!(
966 alloc::format!("{}", DelegationError::SignFailed("bad".into())),
967 "sign failed: bad"
968 );
969 assert_eq!(
970 alloc::format!("{}", DelegationError::VerifyFailed("nope".into())),
971 "verify failed: nope"
972 );
973 assert_eq!(
974 alloc::format!("{}", DelegationError::Malformed("hdr".into())),
975 "malformed delegation: hdr"
976 );
977 assert_eq!(
978 alloc::format!("{}", DelegationError::UnknownAlgorithm(42)),
979 "unknown algorithm id: 42"
980 );
981 assert_eq!(
982 alloc::format!("{}", DelegationError::InvalidTimeWindow),
983 "not_before > not_after"
984 );
985 assert_eq!(
986 alloc::format!("{}", DelegationError::BadMagic),
987 "bad magic bytes"
988 );
989 assert_eq!(
990 alloc::format!("{}", DelegationError::UnsupportedVersion(2)),
991 "unsupported version: 2"
992 );
993 }
994
995 fn build_link_wire(
1000 n_topic: u32,
1001 n_part: u32,
1002 topic_lens: &[u32],
1003 part_lens: &[u32],
1004 ) -> Vec<u8> {
1005 let mut buf = Vec::new();
1006 buf.extend_from_slice(DELEGATION_MAGIC);
1007 buf.push(DELEGATION_VERSION);
1008 buf.extend_from_slice(&[0u8; 16]); buf.extend_from_slice(&[0u8; 16]); buf.extend_from_slice(&0i64.to_be_bytes()); buf.extend_from_slice(&0i64.to_be_bytes()); buf.push(1); buf.extend_from_slice(&n_topic.to_be_bytes());
1015 for &len in topic_lens {
1016 buf.extend_from_slice(&len.to_be_bytes());
1017 buf.extend(std::iter::repeat_n(b'a', len as usize));
1018 }
1019 buf.extend_from_slice(&n_part.to_be_bytes());
1021 for &len in part_lens {
1022 buf.extend_from_slice(&len.to_be_bytes());
1023 buf.extend(std::iter::repeat_n(b'b', len as usize));
1024 }
1025 buf.extend_from_slice(&0u16.to_be_bytes());
1027 buf
1028 }
1029
1030 #[test]
1033 fn link_decode_n_topic_at_and_over_cap() {
1034 let topic_lens = vec![1u32; MAX_TOPIC_PATTERNS];
1035 let wire = build_link_wire(MAX_TOPIC_PATTERNS as u32, 0, &topic_lens, &[]);
1036 let res = DelegationLink::decode(&wire);
1037 assert!(res.is_ok(), "n_topic=MAX must succeed, got {res:?}");
1038
1039 let topic_lens_over = vec![1u32; MAX_TOPIC_PATTERNS + 1];
1040 let wire_over = build_link_wire((MAX_TOPIC_PATTERNS + 1) as u32, 0, &topic_lens_over, &[]);
1041 let err = DelegationLink::decode(&wire_over).unwrap_err();
1042 assert!(matches!(err, DelegationError::TooManyPatterns { .. }));
1043 }
1044
1045 #[test]
1047 fn link_decode_topic_pattern_len_at_and_over_cap() {
1048 let wire = build_link_wire(1, 0, &[MAX_PATTERN_LEN as u32], &[]);
1049 assert!(DelegationLink::decode(&wire).is_ok());
1050
1051 let wire_over = build_link_wire(1, 0, &[(MAX_PATTERN_LEN + 1) as u32], &[]);
1052 let err = DelegationLink::decode(&wire_over).unwrap_err();
1053 assert!(matches!(err, DelegationError::PatternTooLong { .. }));
1054 }
1055
1056 #[test]
1058 fn link_decode_n_part_at_and_over_cap() {
1059 let part_lens = vec![1u32; MAX_PARTITION_PATTERNS];
1060 let wire = build_link_wire(0, MAX_PARTITION_PATTERNS as u32, &[], &part_lens);
1061 assert!(DelegationLink::decode(&wire).is_ok());
1062
1063 let part_lens_over = vec![1u32; MAX_PARTITION_PATTERNS + 1];
1064 let wire_over =
1065 build_link_wire(0, (MAX_PARTITION_PATTERNS + 1) as u32, &[], &part_lens_over);
1066 let err = DelegationLink::decode(&wire_over).unwrap_err();
1067 assert!(matches!(err, DelegationError::TooManyPatterns { .. }));
1068 }
1069
1070 #[test]
1072 fn link_decode_part_pattern_len_at_and_over_cap() {
1073 let wire = build_link_wire(0, 1, &[], &[MAX_PATTERN_LEN as u32]);
1074 assert!(DelegationLink::decode(&wire).is_ok());
1075
1076 let wire_over = build_link_wire(0, 1, &[], &[(MAX_PATTERN_LEN + 1) as u32]);
1077 let err = DelegationLink::decode(&wire_over).unwrap_err();
1078 assert!(matches!(err, DelegationError::PatternTooLong { .. }));
1079 }
1080
1081 #[test]
1089 fn chain_decode_header_at_minimum_size() {
1090 let mut buf = Vec::new();
1091 buf.push(DELEGATION_VERSION); buf.extend_from_slice(&[0u8; 16]); buf.push(0); assert_eq!(buf.len(), 18);
1095 let chain = DelegationChain::decode(&buf).expect("18-byte header must decode");
1096 assert_eq!(chain.links.len(), 0);
1097 }
1098
1099 #[test]
1100 fn chain_decode_header_one_byte_too_short() {
1101 let mut buf = Vec::new();
1102 buf.push(DELEGATION_VERSION);
1103 buf.extend_from_slice(&[0u8; 16]);
1104 assert_eq!(buf.len(), 17);
1106 let err = DelegationChain::decode(&buf).unwrap_err();
1107 assert!(matches!(err, DelegationError::Malformed(_)));
1108 }
1109
1110 #[test]
1114 fn chain_decode_n_links_over_hard_cap_rejected() {
1115 let mut buf = Vec::new();
1116 buf.push(DELEGATION_VERSION);
1117 buf.extend_from_slice(&[0u8; 16]);
1118 buf.push((MAX_CHAIN_DEPTH_HARD_CAP + 1) as u8);
1119 let err = DelegationChain::decode(&buf).unwrap_err();
1120 assert!(matches!(
1121 err,
1122 DelegationError::TooManyPatterns { kind: "chain", .. }
1123 ));
1124 }
1125
1126 #[test]
1133 fn chain_decode_n_links_at_hard_cap_passes_cap_check() {
1134 let mut buf = Vec::new();
1135 buf.push(DELEGATION_VERSION);
1136 buf.extend_from_slice(&[0u8; 16]);
1137 buf.push(MAX_CHAIN_DEPTH_HARD_CAP as u8);
1138 let err = DelegationChain::decode(&buf).unwrap_err();
1139 assert!(
1143 matches!(err, DelegationError::Malformed(_)),
1144 "expected Malformed (loop failure), got {err:?}"
1145 );
1146 }
1147}