/ abzu-core / src / config.rs
config.rs
  1  //! Abzu Security Configuration
  2  //!
  3  //! Tiered security model for configurable DPI resistance.
  4  //! - Tier 0 (Off): No stealth, direct connections
  5  //! - Tier 1 (Blend): FakeTLS handshake, basic DPI evasion (default)
  6  //! - Tier 2 (Shadow): Full TLS mimicry, padding, jitter
  7  //! - Tier 3 (Ghost): Adaptive cover traffic, full anonymity
  8  
  9  use serde::{Deserialize, Serialize};
 10  
 11  /// Security tier levels
 12  #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
 13  #[repr(u8)]
 14  pub enum SecurityTier {
 15      /// No stealth, direct connections
 16      Off = 0,
 17      /// FakeTLS handshake, basic DPI evasion
 18      #[default]
 19      Blend = 1,
 20      /// Full TLS mimicry, padding, jitter
 21      Shadow = 2,
 22      /// Adaptive cover traffic, full anonymity
 23      Ghost = 3,
 24  }
 25  
 26  impl SecurityTier {
 27      /// Whether this tier uses FakeTLS handshake
 28      pub fn uses_fake_tls(&self) -> bool {
 29          matches!(self, Self::Blend | Self::Shadow | Self::Ghost)
 30      }
 31  
 32      /// Whether this tier adds packet padding
 33      pub fn uses_padding(&self) -> bool {
 34          matches!(self, Self::Shadow | Self::Ghost)
 35      }
 36  
 37      /// Whether this tier adds timing jitter
 38      pub fn uses_jitter(&self) -> bool {
 39          matches!(self, Self::Shadow | Self::Ghost)
 40      }
 41  
 42      /// Whether this tier uses cover traffic
 43      /// Returns false if ghost feature not compiled (mobile builds)
 44      pub fn uses_cover_traffic(&self) -> bool {
 45          #[cfg(feature = "ghost")]
 46          {
 47              matches!(self, Self::Ghost)
 48          }
 49          #[cfg(not(feature = "ghost"))]
 50          {
 51              false
 52          }
 53      }
 54  }
 55  
 56  /// Node role in the mesh network
 57  ///
 58  /// Defines what a node can and cannot do based on its deployment context.
 59  #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
 60  pub enum NodeRole {
 61      /// Lightweight client (mobile/WASM)
 62      /// - No routing for others
 63      /// - No DHT participation
 64      /// - No background services
 65      /// - No cover traffic
 66      Edge,
 67      
 68      /// Full mesh participant (desktop)
 69      /// - Routes packets for others
 70      /// - Participates in DHT
 71      /// - Can run as background daemon
 72      /// - Supports all security tiers
 73      #[default]
 74      Desktop,
 75      
 76      /// Always-on infrastructure (home node/VPS)
 77      /// - All Desktop capabilities
 78      /// - Store-and-forward for offline clients
 79      /// - Bootstrap endpoint
 80      /// - Higher connection limits
 81      Infrastructure,
 82  }
 83  
 84  impl NodeRole {
 85      /// Whether this role routes packets for other nodes
 86      pub fn can_route(&self) -> bool {
 87          matches!(self, Self::Desktop | Self::Infrastructure)
 88      }
 89      
 90      /// Whether this role participates in DHT maintenance
 91      pub fn participates_in_dht(&self) -> bool {
 92          matches!(self, Self::Desktop | Self::Infrastructure)
 93      }
 94      
 95      /// Whether this role can run as a background service
 96      pub fn can_run_background(&self) -> bool {
 97          matches!(self, Self::Desktop | Self::Infrastructure)
 98      }
 99      
100      /// Whether this role supports Ghost Mode (cover traffic)
101      pub fn supports_ghost(&self) -> bool {
102          matches!(self, Self::Desktop | Self::Infrastructure)
103      }
104      
105      /// Whether this role can act as a bootstrap node
106      pub fn can_bootstrap(&self) -> bool {
107          matches!(self, Self::Infrastructure)
108      }
109      
110      /// Whether this role provides store-and-forward for offline peers
111      pub fn provides_store_forward(&self) -> bool {
112          matches!(self, Self::Infrastructure)
113      }
114      
115      /// Maximum concurrent peer connections for this role
116      pub fn max_peers(&self) -> usize {
117          match self {
118              Self::Edge => 8,          // Minimal connections
119              Self::Desktop => 32,       // Standard mesh participation
120              Self::Infrastructure => 128, // High capacity relay
121          }
122      }
123      
124      /// K-bucket size for DHT (0 = no DHT participation)
125      pub fn k_bucket_size(&self) -> usize {
126          match self {
127              Self::Edge => 0,           // No DHT
128              Self::Desktop => 20,       // Standard K
129              Self::Infrastructure => 40, // High capacity
130          }
131      }
132  }
133  
134  /// Tier-specific configuration options
135  #[derive(Debug, Clone, Serialize, Deserialize)]
136  pub struct TierOptions {
137      // Tier 1+: FakeTLS options
138      /// SNI domain for FakeTLS handshake. None = random popular domain.
139      pub fake_tls_sni: Option<String>,
140  
141      // Tier 2+: Traffic shaping
142      /// Pad all packets to this size (0 = no padding)
143      pub padding_size: usize,
144      /// Timing jitter range in milliseconds (min, max)
145      pub jitter_range_ms: (u32, u32),
146  
147      // Tier 3: Cover traffic
148      /// Enable passive traffic observation for pattern learning
149      pub pattern_observation: bool,
150      /// Hours to retain pattern data (0 = session only)
151      pub pattern_retention_hours: u32,
152  }
153  
154  impl Default for TierOptions {
155      fn default() -> Self {
156          Self {
157              fake_tls_sni: None, // Random popular domain
158              padding_size: 1400, // MTU
159              jitter_range_ms: (50, 500),
160              pattern_observation: false,
161              pattern_retention_hours: 24,
162          }
163      }
164  }
165  
166  /// Complete Abzu security configuration
167  #[derive(Debug, Clone, Serialize, Deserialize)]
168  pub struct SecurityConfig {
169      /// Active security tier
170      pub tier: SecurityTier,
171      /// Tier-specific options
172      pub options: TierOptions,
173  }
174  
175  impl Default for SecurityConfig {
176      fn default() -> Self {
177          Self {
178              tier: SecurityTier::Blend,
179              options: TierOptions::default(),
180          }
181      }
182  }
183  
184  impl SecurityConfig {
185      /// Create config for specific tier with default options
186      pub fn with_tier(tier: SecurityTier) -> Self {
187          Self {
188              tier,
189              options: TierOptions::default(),
190          }
191      }
192  
193      /// Convenience: Off tier (no stealth)
194      pub fn off() -> Self {
195          Self::with_tier(SecurityTier::Off)
196      }
197  
198      /// Convenience: Blend tier (default)
199      pub fn blend() -> Self {
200          Self::with_tier(SecurityTier::Blend)
201      }
202  
203      /// Convenience: Shadow tier (high stealth)
204      pub fn shadow() -> Self {
205          Self::with_tier(SecurityTier::Shadow)
206      }
207  
208      /// Convenience: Ghost tier (maximum anonymity)
209      /// Note: Requires explicit user consent for pattern observation
210      pub fn ghost() -> Self {
211          let mut config = Self::with_tier(SecurityTier::Ghost);
212          config.options.pattern_observation = true;
213          config
214      }
215  }
216  
217  /// Popular domains for SNI rotation (Tier 2+)
218  pub const POPULAR_SNIS: &[&str] = &[
219      "cloudflare.com",
220      "googleapis.com",
221      "amazon.com",
222      "microsoft.com",
223      "apple.com",
224      "cdn.jsdelivr.net",
225      "fastly.net",
226      "akamaihd.net",
227  ];
228  
229  /// Select a random SNI from the popular list
230  pub fn random_sni() -> &'static str {
231      use rand::seq::SliceRandom;
232      POPULAR_SNIS
233          .choose(&mut rand::thread_rng())
234          .unwrap_or(&"cloudflare.com")
235  }
236  
237  // ============================================================================
238  // STUN Configuration (Sovereign NAT Discovery)
239  // ============================================================================
240  
241  /// Configuration for STUN-based NAT discovery
242  ///
243  /// ## Sovereignty Principle
244  ///
245  /// By default, this configuration uses **no external STUN servers**.
246  /// This is intentional: a sovereign stack should not leak metadata
247  /// (IP address, uptime patterns) to third-party infrastructure on startup.
248  ///
249  /// Users who need NAT traversal must explicitly configure STUN servers:
250  /// - Self-hosted STUN server (recommended for full sovereignty)
251  /// - Community-run servers (e.g., from the Abzu network)
252  /// - Public servers (only if privacy tradeoff is acceptable)
253  ///
254  /// ## Example
255  ///
256  /// ```rust
257  /// use abzu_core::config::StunConfig;
258  ///
259  /// // Sovereign: self-hosted only
260  /// let sovereign = StunConfig::default()
261  ///     .with_server("stun.myhome.local:3478");
262  ///
263  /// // Community: privacy-respecting public servers
264  /// let community = StunConfig::community();
265  ///
266  /// // Fallback: includes major providers (leaks metadata)
267  /// let fallback = StunConfig::with_public_fallbacks();
268  /// ```
269  #[derive(Debug, Clone, Serialize, Deserialize)]
270  pub struct StunConfig {
271      /// STUN servers to query for NAT discovery
272      /// Empty = no external NAT discovery (sovereign default)
273      pub servers: Vec<String>,
274      
275      /// Request timeout per server
276      pub timeout_secs: u32,
277      
278      /// Number of retries per server
279      pub retries: u32,
280  }
281  
282  impl Default for StunConfig {
283      fn default() -> Self {
284          Self {
285              // SOVEREIGN DEFAULT: No external servers
286              // Users must explicitly configure if they need NAT traversal
287              servers: Vec::new(),
288              timeout_secs: 3,
289              retries: 2,
290          }
291      }
292  }
293  
294  impl StunConfig {
295      /// Add a STUN server to the configuration
296      pub fn with_server(mut self, server: impl Into<String>) -> Self {
297          self.servers.push(server.into());
298          self
299      }
300      
301      /// Add multiple servers
302      pub fn with_servers(mut self, servers: impl IntoIterator<Item = impl Into<String>>) -> Self {
303          self.servers.extend(servers.into_iter().map(Into::into));
304          self
305      }
306      
307      /// Community-run privacy-respecting STUN servers
308      ///
309      /// These are operated by the community and do not log traffic.
310      /// Still involves external contact — use only if needed.
311      pub fn community() -> Self {
312          Self {
313              servers: vec![
314                  // TODO: Add community-run servers when available
315                  // For now, this is a placeholder that returns empty
316              ],
317              ..Default::default()
318          }
319      }
320      
321      /// Configuration with public fallbacks
322      ///
323      /// ⚠️ **Privacy Warning**: These servers will see your IP address
324      /// and can infer uptime patterns. Use only if NAT traversal is
325      /// required and self-hosting is not feasible.
326      pub fn with_public_fallbacks() -> Self {
327          Self {
328              servers: vec![
329                  // Include localhost first (sovereign STUN if running abzu-daemon)
330                  "127.0.0.1:3478".into(),
331                  // Public fallbacks (metadata leak accepted)
332                  "stun.cloudflare.com:3478".into(),
333                  "stun.l.google.com:19302".into(),
334              ],
335              ..Default::default()
336          }
337      }
338      
339      /// Check if any external servers are configured
340      pub fn has_external_servers(&self) -> bool {
341          self.servers.iter().any(|s| !s.starts_with("127.0.0.1") && !s.starts_with("localhost"))
342      }
343  }
344  
345  #[cfg(test)]
346  mod tests {
347      use super::*;
348  
349      #[test]
350      fn test_tier_capabilities() {
351          assert!(!SecurityTier::Off.uses_fake_tls());
352          assert!(SecurityTier::Blend.uses_fake_tls());
353          assert!(SecurityTier::Shadow.uses_padding());
354          assert!(SecurityTier::Ghost.uses_cover_traffic());
355      }
356  
357      #[test]
358      fn test_default_is_blend() {
359          let config = SecurityConfig::default();
360          assert_eq!(config.tier, SecurityTier::Blend);
361      }
362  
363      #[test]
364      fn test_random_sni() {
365          let sni = random_sni();
366          assert!(POPULAR_SNIS.contains(&sni));
367      }
368      
369      #[test]
370      fn test_node_role_capabilities() {
371          // Edge: no routing, no DHT, no background
372          assert!(!NodeRole::Edge.can_route());
373          assert!(!NodeRole::Edge.participates_in_dht());
374          assert!(!NodeRole::Edge.can_run_background());
375          assert!(!NodeRole::Edge.supports_ghost());
376          assert_eq!(NodeRole::Edge.k_bucket_size(), 0);
377          
378          // Desktop: full participant
379          assert!(NodeRole::Desktop.can_route());
380          assert!(NodeRole::Desktop.participates_in_dht());
381          assert!(NodeRole::Desktop.can_run_background());
382          assert!(NodeRole::Desktop.supports_ghost());
383          assert!(!NodeRole::Desktop.can_bootstrap());
384          
385          // Infrastructure: all capabilities
386          assert!(NodeRole::Infrastructure.can_route());
387          assert!(NodeRole::Infrastructure.can_bootstrap());
388          assert!(NodeRole::Infrastructure.provides_store_forward());
389          assert_eq!(NodeRole::Infrastructure.max_peers(), 128);
390      }
391      
392      #[test]
393      fn test_node_role_default_is_desktop() {
394          assert_eq!(NodeRole::default(), NodeRole::Desktop);
395      }
396      
397      #[test]
398      fn test_stun_config_sovereign_default() {
399          // CRITICAL: Default must not contact external infrastructure
400          let config = StunConfig::default();
401          assert!(config.servers.is_empty(), "Sovereign default must not include external STUN servers");
402          assert!(!config.has_external_servers());
403      }
404      
405      #[test]
406      fn test_stun_config_with_servers() {
407          let config = StunConfig::default()
408              .with_server("stun.myhome.local:3478")
409              .with_server("127.0.0.1:3478");
410          
411          assert_eq!(config.servers.len(), 2);
412          // localhost doesn't count as external
413          assert!(config.has_external_servers()); // myhome.local is external
414      }
415      
416      #[test]
417      fn test_stun_config_public_fallbacks() {
418          let config = StunConfig::with_public_fallbacks();
419          assert!(!config.servers.is_empty());
420          assert!(config.has_external_servers());
421          // Verify it includes localhost first (sovereign priority)
422          assert!(config.servers[0].starts_with("127.0.0.1"));
423      }
424  }
425  
426  // ============================================================================
427  // Home Node Configuration (Edge Sovereignty)
428  // ============================================================================
429  
430  /// Configuration for delegating storage and mailbox to a Home Node
431  ///
432  /// Edge nodes can delegate two functions to their Home Node:
433  /// - **Storage Sync**: Replicate content to Home for persistence
434  /// - **Mailbox**: Home accepts messages when Edge is offline
435  ///
436  /// Routing remains fully decentralized through the mesh.
437  #[derive(Debug, Clone, Serialize, Deserialize)]
438  pub struct HomeNodeConfig {
439      /// Home Node's identity (Ed25519 public key)
440      pub peer_id: [u8; 32],
441      
442      /// LAN addresses to try first (e.g., "192.168.1.5:9000")
443      /// Faster for heavy syncing, saves cellular data
444      pub lan_addrs: Vec<String>,
445      
446      /// Public addresses as fallback (e.g., "home.example.com:9000")
447      pub public_addrs: Vec<String>,
448      
449      /// Shared secret proving ownership of this Home Node
450      /// Derived from passkey or out-of-band exchange
451      pub auth_token: [u8; 32],
452      
453      /// Enable automatic content replication to Home
454      pub enable_storage_sync: bool,
455      
456      /// Register Home as mailbox delegate in DHT
457      pub enable_mailbox: bool,
458  }
459  
460  impl HomeNodeConfig {
461      /// Create config with all features enabled
462      pub fn new(peer_id: [u8; 32], auth_token: [u8; 32]) -> Self {
463          Self {
464              peer_id,
465              lan_addrs: Vec::new(),
466              public_addrs: Vec::new(),
467              auth_token,
468              enable_storage_sync: true,
469              enable_mailbox: true,
470          }
471      }
472      
473      /// Add a LAN address (tried first)
474      pub fn with_lan_addr(mut self, addr: impl Into<String>) -> Self {
475          self.lan_addrs.push(addr.into());
476          self
477      }
478      
479      /// Add a public address (fallback)
480      pub fn with_public_addr(mut self, addr: impl Into<String>) -> Self {
481          self.public_addrs.push(addr.into());
482          self
483      }
484      
485      /// Get all addresses in priority order (LAN first)
486      pub fn all_addrs(&self) -> impl Iterator<Item = &str> {
487          self.lan_addrs.iter()
488              .chain(self.public_addrs.iter())
489              .map(|s| s.as_str())
490      }
491  }
492