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