1extern crate alloc;
30
31use alloc::collections::BTreeMap;
32use alloc::string::String;
33use alloc::vec::Vec;
34
35use zerodds_security_permissions::EdgeIdentityConfig;
36use zerodds_security_pki::{DelegationChain, DelegationError, DelegationLink, SignatureAlgorithm};
37
38#[derive(Debug, Clone)]
48pub struct GatewayBridgeConfig {
49 pub gateway_guid: [u8; 16],
51 pub signing_key: Vec<u8>,
53 pub algorithm: SignatureAlgorithm,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59#[non_exhaustive]
60pub enum GatewayBridgeError {
61 UnknownEdge {
63 edge_guid: [u8; 16],
65 },
66 DelegationFailed(DelegationError),
68}
69
70impl core::fmt::Display for GatewayBridgeError {
71 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72 match self {
73 Self::UnknownEdge { edge_guid } => {
74 write!(f, "no active delegation for edge {edge_guid:?}")
75 }
76 Self::DelegationFailed(e) => write!(f, "delegation failed: {e}"),
77 }
78 }
79}
80
81#[cfg(feature = "std")]
82impl std::error::Error for GatewayBridgeError {}
83
84impl From<DelegationError> for GatewayBridgeError {
85 fn from(e: DelegationError) -> Self {
86 Self::DelegationFailed(e)
87 }
88}
89
90pub type GatewayBridgeResult<T> = Result<T, GatewayBridgeError>;
92
93#[derive(Debug, Clone)]
105pub struct GatewayBridge {
106 config: GatewayBridgeConfig,
107 upstream: Option<DelegationChain>,
111 active: BTreeMap<[u8; 16], DelegationLink>,
113 revocations: Vec<[u8; 16]>,
118}
119
120impl GatewayBridge {
121 #[must_use]
123 pub fn new(config: GatewayBridgeConfig) -> Self {
124 Self {
125 config,
126 upstream: None,
127 active: BTreeMap::new(),
128 revocations: Vec::new(),
129 }
130 }
131
132 pub fn with_upstream(&mut self, upstream_chain: DelegationChain) {
140 self.upstream = Some(upstream_chain);
141 }
142
143 #[must_use]
145 pub fn gateway_guid(&self) -> [u8; 16] {
146 self.config.gateway_guid
147 }
148
149 pub fn delegate_for(
161 &mut self,
162 edge_guid: [u8; 16],
163 topic_patterns: Vec<String>,
164 partition_patterns: Vec<String>,
165 not_before: i64,
166 not_after: i64,
167 ) -> GatewayBridgeResult<&DelegationLink> {
168 let mut link = DelegationLink::new(
169 self.config.gateway_guid,
170 edge_guid,
171 topic_patterns,
172 partition_patterns,
173 not_before,
174 not_after,
175 self.config.algorithm,
176 )?;
177 link.sign(&self.config.signing_key)?;
178 self.active.insert(edge_guid, link);
179 self.revocations.retain(|g| g != &edge_guid);
182 self.active
186 .get(&edge_guid)
187 .ok_or(GatewayBridgeError::UnknownEdge { edge_guid })
188 }
189
190 pub fn revoke_delegation(&mut self, edge_guid: [u8; 16]) -> GatewayBridgeResult<()> {
197 if self.active.remove(&edge_guid).is_some() {
198 if !self.revocations.contains(&edge_guid) {
199 self.revocations.push(edge_guid);
200 }
201 Ok(())
202 } else {
203 Err(GatewayBridgeError::UnknownEdge { edge_guid })
204 }
205 }
206
207 #[must_use]
216 pub fn chain_for(&self, edge_guid: &[u8; 16]) -> Option<DelegationChain> {
217 let edge_link = self.active.get(edge_guid)?.clone();
218 match &self.upstream {
219 None => DelegationChain::new(self.config.gateway_guid, alloc::vec![edge_link]).ok(),
220 Some(up) => {
221 let mut links = up.links.clone();
222 links.push(edge_link);
223 DelegationChain::new(up.origin_guid, links).ok()
224 }
225 }
226 }
227
228 #[must_use]
230 pub fn active_count(&self) -> usize {
231 self.active.len()
232 }
233
234 #[must_use]
236 pub fn has_edge(&self, edge_guid: &[u8; 16]) -> bool {
237 self.active.contains_key(edge_guid)
238 }
239
240 pub fn iter_active(&self) -> impl Iterator<Item = (&[u8; 16], &DelegationLink)> {
242 self.active.iter()
243 }
244
245 pub fn take_revocations(&mut self) -> Vec<[u8; 16]> {
248 core::mem::take(&mut self.revocations)
249 }
250
251 #[must_use]
254 pub fn upstream(&self) -> Option<&DelegationChain> {
255 self.upstream.as_ref()
256 }
257
258 pub fn rotate_ephemerals<F>(
281 &mut self,
282 identities: &[EdgeIdentityConfig],
283 now: i64,
284 topic_patterns: Vec<String>,
285 partition_patterns: Vec<String>,
286 mut prefix_generator: F,
287 ) -> (Vec<String>, Vec<(String, GatewayBridgeError)>)
288 where
289 F: FnMut(&str) -> [u8; 12],
290 {
291 let mut rotated = Vec::new();
292 let mut failed = Vec::new();
293 for cfg in identities.iter().filter(|c| c.is_ephemeral()) {
294 let new_prefix = prefix_generator(&cfg.name);
298 let mut edge_guid = [0u8; 16];
301 edge_guid[..12].copy_from_slice(&new_prefix);
302 edge_guid[12..].copy_from_slice(&[0x00, 0x00, 0x01, 0xC1]);
303
304 let lifetime = i64::from(cfg.effective_lifetime());
310 let new_not_after = now.saturating_add(lifetime);
311 match self.delegate_for(
312 edge_guid,
313 topic_patterns.clone(),
314 partition_patterns.clone(),
315 now,
316 new_not_after,
317 ) {
318 Ok(_) => rotated.push(cfg.name.clone()),
319 Err(e) => failed.push((cfg.name.clone(), e)),
320 }
321 }
322 (rotated, failed)
323 }
324}
325
326#[cfg(test)]
327#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
328mod tests {
329 use super::*;
330 use alloc::string::ToString;
331 use ring::rand::SystemRandom;
332 use ring::signature::{ECDSA_P256_SHA256_FIXED_SIGNING, EcdsaKeyPair, KeyPair};
333 use zerodds_security_permissions::EdgeIdentityMode;
334
335 fn ecdsa_p256_keypair() -> (Vec<u8>, Vec<u8>) {
336 let rng = SystemRandom::new();
337 let pkcs8 =
338 EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng).expect("gen");
339 let sk = pkcs8.as_ref().to_vec();
340 let kp = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &sk, &rng).expect("p");
341 (sk, kp.public_key().as_ref().to_vec())
342 }
343
344 fn make_bridge(gw_guid: [u8; 16]) -> (GatewayBridge, Vec<u8>) {
345 let (sk, pk) = ecdsa_p256_keypair();
346 let cfg = GatewayBridgeConfig {
347 gateway_guid: gw_guid,
348 signing_key: sk,
349 algorithm: SignatureAlgorithm::EcdsaP256,
350 };
351 (GatewayBridge::new(cfg), pk)
352 }
353
354 #[test]
355 fn delegate_for_creates_signed_link() {
356 let gw = [0xAA; 16];
357 let edge = [0xBB; 16];
358 let (mut bridge, pk) = make_bridge(gw);
359 let link = bridge
360 .delegate_for(
361 edge,
362 alloc::vec!["sensor/*".to_string()],
363 alloc::vec![],
364 1_000,
365 9_000,
366 )
367 .expect("delegate")
368 .clone();
369 assert_eq!(link.delegator_guid, gw);
370 assert_eq!(link.delegatee_guid, edge);
371 assert_eq!(link.signature.len(), 64); link.verify(&pk).expect("verify");
373 assert_eq!(bridge.active_count(), 1);
374 assert!(bridge.has_edge(&edge));
375 }
376
377 #[test]
378 fn one_hop_chain_for_edge() {
379 let gw = [0xAA; 16];
380 let edge = [0xBB; 16];
381 let (mut bridge, _pk) = make_bridge(gw);
382 bridge
383 .delegate_for(
384 edge,
385 alloc::vec!["sensor/*".to_string()],
386 alloc::vec![],
387 0,
388 9_000,
389 )
390 .expect("delegate");
391 let chain = bridge.chain_for(&edge).expect("chain");
392 assert_eq!(chain.depth(), 1);
393 assert_eq!(chain.origin_guid, gw);
394 assert_eq!(chain.edge_guid(), Some(edge));
395 }
396
397 #[test]
398 fn chain_for_missing_edge_is_none() {
399 let gw = [0xAA; 16];
400 let (bridge, _) = make_bridge(gw);
401 assert!(bridge.chain_for(&[0xCC; 16]).is_none());
402 }
403
404 #[test]
405 fn revoke_delegation_removes_active_and_records_revocation() {
406 let gw = [0xAA; 16];
407 let edge = [0xBB; 16];
408 let (mut bridge, _pk) = make_bridge(gw);
409 bridge
410 .delegate_for(edge, alloc::vec![], alloc::vec![], 0, 9_000)
411 .expect("delegate");
412 bridge.revoke_delegation(edge).expect("revoke");
413 assert_eq!(bridge.active_count(), 0);
414 assert!(!bridge.has_edge(&edge));
415 let revocations = bridge.take_revocations();
416 assert_eq!(revocations, alloc::vec![edge]);
417 assert!(bridge.take_revocations().is_empty());
419 }
420
421 #[test]
422 fn revoke_unknown_edge_is_error() {
423 let gw = [0xAA; 16];
424 let (mut bridge, _) = make_bridge(gw);
425 let err = bridge.revoke_delegation([0xFF; 16]).expect_err("must fail");
426 assert!(matches!(err, GatewayBridgeError::UnknownEdge { .. }));
427 }
428
429 #[test]
430 fn re_delegate_clears_pending_revocation() {
431 let gw = [0xAA; 16];
432 let edge = [0xBB; 16];
433 let (mut bridge, _) = make_bridge(gw);
434 bridge
435 .delegate_for(edge, alloc::vec![], alloc::vec![], 0, 9_000)
436 .expect("delegate");
437 bridge.revoke_delegation(edge).expect("revoke");
438 bridge
440 .delegate_for(edge, alloc::vec![], alloc::vec![], 100, 10_000)
441 .expect("redelegate");
442 assert!(bridge.take_revocations().is_empty());
443 assert!(bridge.has_edge(&edge));
444 }
445
446 #[test]
447 fn sub_gateway_chaining_two_hops() {
448 let gw1 = [0x11; 16];
451 let gw2 = [0x22; 16];
452 let edge = [0x33; 16];
453
454 let (sk1, _pk1) = ecdsa_p256_keypair();
456 let mut upstream_link = DelegationLink::new(
457 gw1,
458 gw2,
459 alloc::vec!["*".to_string()],
460 alloc::vec![],
461 0,
462 9_000,
463 SignatureAlgorithm::EcdsaP256,
464 )
465 .expect("upstream link");
466 upstream_link.sign(&sk1).expect("sign upstream");
467 let upstream_chain =
468 DelegationChain::new(gw1, alloc::vec![upstream_link]).expect("upstream chain");
469
470 let (mut turm_bridge, _pk2) = make_bridge(gw2);
472 turm_bridge.with_upstream(upstream_chain);
473
474 turm_bridge
475 .delegate_for(
476 edge,
477 alloc::vec!["sensor/imu".to_string()],
478 alloc::vec![],
479 100,
480 8_000,
481 )
482 .expect("turm delegate");
483 let chain = turm_bridge.chain_for(&edge).expect("chain");
484 assert_eq!(chain.depth(), 2);
485 assert_eq!(chain.origin_guid, gw1);
486 assert_eq!(chain.edge_guid(), Some(edge));
487 assert_eq!(chain.links.last().unwrap().delegator_guid, gw2);
489 assert_eq!(chain.links.last().unwrap().delegatee_guid, edge);
490 }
491
492 #[test]
493 fn iter_active_lists_all_delegations() {
494 let gw = [0xAA; 16];
495 let (mut bridge, _) = make_bridge(gw);
496 for i in 0..5u8 {
497 let mut edge = [0u8; 16];
498 edge[0] = i;
499 bridge
500 .delegate_for(edge, alloc::vec![], alloc::vec![], 0, 9_000)
501 .expect("delegate");
502 }
503 assert_eq!(bridge.active_count(), 5);
504 let collected: Vec<[u8; 16]> = bridge.iter_active().map(|(g, _)| *g).collect();
505 assert_eq!(collected.len(), 5);
506 }
507
508 #[test]
509 fn upstream_accessor_reflects_state() {
510 let gw = [0xAA; 16];
511 let (mut bridge, _) = make_bridge(gw);
512 assert!(bridge.upstream().is_none());
513 let (sk, _pk) = ecdsa_p256_keypair();
514 let mut up_link = DelegationLink::new(
515 [0x11; 16],
516 gw,
517 alloc::vec!["*".to_string()],
518 alloc::vec![],
519 0,
520 9_000,
521 SignatureAlgorithm::EcdsaP256,
522 )
523 .unwrap();
524 up_link.sign(&sk).unwrap();
525 let chain = DelegationChain::new([0x11; 16], alloc::vec![up_link]).unwrap();
526 bridge.with_upstream(chain.clone());
527 assert_eq!(bridge.upstream(), Some(&chain));
528 }
529
530 #[test]
531 fn bridge_two_hop_chain_validates_via_chain_check() {
532 use alloc::collections::BTreeSet;
533 use zerodds_security_permissions::{
534 DelegationProfile, TrustAnchor, TrustPolicy, validate_chain,
535 };
536
537 let gw1 = [0x11; 16];
539 let gw2 = [0x22; 16];
540 let edge = [0x33; 16];
541
542 let (sk1, pk1) = ecdsa_p256_keypair();
544 let (sk2, pk2) = ecdsa_p256_keypair();
546
547 let mut upstream_link = DelegationLink::new(
549 gw1,
550 gw2,
551 alloc::vec!["*".to_string()],
552 alloc::vec![],
553 0,
554 9_000,
555 SignatureAlgorithm::EcdsaP256,
556 )
557 .unwrap();
558 upstream_link.sign(&sk1).unwrap();
559 let upstream = DelegationChain::new(gw1, alloc::vec![upstream_link]).unwrap();
560
561 let cfg = GatewayBridgeConfig {
563 gateway_guid: gw2,
564 signing_key: sk2,
565 algorithm: SignatureAlgorithm::EcdsaP256,
566 };
567 let mut turm_bridge = GatewayBridge::new(cfg);
568 turm_bridge.with_upstream(upstream);
569 turm_bridge
570 .delegate_for(
571 edge,
572 alloc::vec!["sensor/imu".to_string()],
573 alloc::vec![],
574 100,
575 8_000,
576 )
577 .unwrap();
578
579 let chain = turm_bridge.chain_for(&edge).expect("chain");
580
581 let mut algos = BTreeSet::new();
583 algos.insert(SignatureAlgorithm::EcdsaP256.wire_id());
584 let profile = DelegationProfile {
585 name: "vehicle".to_string(),
586 trust_policy: TrustPolicy::DirectOrDelegated,
587 trust_anchors: alloc::vec![TrustAnchor {
588 subject_guid: gw1,
589 verify_public_key: pk1,
590 algorithm: SignatureAlgorithm::EcdsaP256,
591 }],
592 max_chain_depth: 3,
593 allowed_algorithms: algos,
594 require_ocsp: false,
595 };
596
597 let resolver = move |g: &[u8; 16]| -> Option<(Vec<u8>, SignatureAlgorithm)> {
599 if g == &gw2 {
600 Some((pk2.clone(), SignatureAlgorithm::EcdsaP256))
601 } else {
602 None
603 }
604 };
605
606 let validated = validate_chain(&chain, &profile, 5_000, resolver).expect("validate");
607 assert_eq!(validated.chain_depth, 2);
608 assert_eq!(validated.edge_guid, edge);
609 assert!(
611 validated
612 .effective_topic_patterns
613 .contains(&"sensor/imu".to_string())
614 );
615 }
616
617 #[test]
620 fn rotate_ephemerals_creates_delegations_for_ephemeral_only() {
621 let gw = [0xAA; 16];
622 let (mut bridge, _pk) = make_bridge(gw);
623
624 let identities = alloc::vec![
625 EdgeIdentityConfig {
626 name: "static-edge".into(),
627 mode: EdgeIdentityMode::Static,
628 guid_prefix: Some([0x01; 12]),
629 lifetime_seconds: None,
630 },
631 EdgeIdentityConfig {
632 name: "ephemeral-edge".into(),
633 mode: EdgeIdentityMode::Ephemeral,
634 guid_prefix: None,
635 lifetime_seconds: Some(60),
636 },
637 ];
638
639 let mut counter = 0u8;
640 let prefix_gen = |_name: &str| -> [u8; 12] {
641 counter += 1;
642 [counter; 12]
643 };
644 let (rotated, failed) = bridge.rotate_ephemerals(
645 &identities,
646 1_000,
647 alloc::vec!["sensor/*".to_string()],
648 alloc::vec![],
649 prefix_gen,
650 );
651 assert_eq!(rotated, alloc::vec!["ephemeral-edge".to_string()]);
652 assert!(failed.is_empty());
653 assert_eq!(bridge.active_count(), 1);
656 }
657
658 #[test]
659 fn rotate_ephemerals_uses_provided_prefix_generator() {
660 let gw = [0xAA; 16];
661 let (mut bridge, _) = make_bridge(gw);
662
663 let identities = alloc::vec![EdgeIdentityConfig {
664 name: "rot-edge".into(),
665 mode: EdgeIdentityMode::Ephemeral,
666 guid_prefix: None,
667 lifetime_seconds: Some(120),
668 }];
669
670 let captured_name: alloc::sync::Arc<core::sync::atomic::AtomicBool> =
671 alloc::sync::Arc::new(core::sync::atomic::AtomicBool::new(false));
672 let captured_clone = captured_name.clone();
673 let prefix_gen = move |name: &str| -> [u8; 12] {
674 if name == "rot-edge" {
675 captured_clone.store(true, core::sync::atomic::Ordering::SeqCst);
676 }
677 [0xDE; 12]
678 };
679
680 let (rotated, _) =
681 bridge.rotate_ephemerals(&identities, 5_000, alloc::vec![], alloc::vec![], prefix_gen);
682 assert_eq!(rotated.len(), 1);
683 assert!(captured_name.load(core::sync::atomic::Ordering::SeqCst));
684
685 let mut expected_guid = [0u8; 16];
687 expected_guid[..12].copy_from_slice(&[0xDE; 12]);
688 expected_guid[12..].copy_from_slice(&[0x00, 0x00, 0x01, 0xC1]);
689 assert!(bridge.has_edge(&expected_guid));
690 }
691
692 #[test]
693 fn rotate_ephemerals_uses_lifetime_for_not_after() {
694 let gw = [0xAA; 16];
695 let (mut bridge, _) = make_bridge(gw);
696 let identities = alloc::vec![EdgeIdentityConfig {
697 name: "imu".into(),
698 mode: EdgeIdentityMode::Ephemeral,
699 guid_prefix: None,
700 lifetime_seconds: Some(300),
701 }];
702 let (_rotated, _) =
703 bridge.rotate_ephemerals(&identities, 1_000, alloc::vec![], alloc::vec![], |_| {
704 [0xAB; 12]
705 });
706 let mut edge_guid = [0u8; 16];
707 edge_guid[..12].copy_from_slice(&[0xAB; 12]);
708 edge_guid[12..].copy_from_slice(&[0x00, 0x00, 0x01, 0xC1]);
709 let link = bridge.iter_active().find(|(g, _)| *g == &edge_guid);
710 assert!(link.is_some());
711 let (_g, l) = link.unwrap();
712 assert_eq!(l.not_before, 1_000);
713 assert_eq!(l.not_after, 1_300);
714 }
715
716 #[test]
717 fn rotate_ephemerals_repeated_calls_replace_old_delegation() {
718 let gw = [0xAA; 16];
719 let (mut bridge, _) = make_bridge(gw);
720 let identities = alloc::vec![EdgeIdentityConfig {
721 name: "ecu".into(),
722 mode: EdgeIdentityMode::Ephemeral,
723 guid_prefix: None,
724 lifetime_seconds: Some(60),
725 }];
726
727 let (_, _) =
729 bridge.rotate_ephemerals(&identities, 1_000, alloc::vec![], alloc::vec![], |_| {
730 [0x11; 12]
731 });
732 let (_, _) =
737 bridge.rotate_ephemerals(&identities, 2_000, alloc::vec![], alloc::vec![], |_| {
738 [0x22; 12]
739 });
740 assert_eq!(bridge.active_count(), 2);
741 }
742
743 #[test]
744 fn delegation_link_too_many_topics_propagates_as_bridge_error() {
745 let gw = [0xAA; 16];
746 let edge = [0xBB; 16];
747 let (mut bridge, _) = make_bridge(gw);
748 let topics: Vec<String> = (0..200).map(|i| alloc::format!("t{i}")).collect();
749 let err = bridge
750 .delegate_for(edge, topics, alloc::vec![], 0, 9_000)
751 .expect_err("must fail");
752 assert!(matches!(err, GatewayBridgeError::DelegationFailed(_)));
753 }
754}