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