# iOS Agent — Technical Design Document **Version**: 1.0.0 **Date**: 2026-07-06 **Status**: Draft **Mirror**: Android Agent (`apps/android-agent/`) ## 1. Executive Summary NexusGuard iOS Agent is an iOS-based WireGuard client for mobile devices. It mirrors the Android agent's architecture, UI design, and heartbeat flow, using iOS-native APIs (NetworkExtension, CryptoKit, Keychain). ## 2. Goals & Non-Goals ### Goals - iOS-based WireGuard tunnel (NetworkExtension) - Auto-provisioning via HTTP (same API as Android/ESP32) - Heartbeat monitoring with config sync (30s interval) - Keychain-based config storage (hardware-backed encryption) - SwiftUI UI matching Android agent design - Background operation via NetworkExtension ### Non-Goals - gRPC support (not needed for mobile) - Port forwarding (userspace agent only) - Jailbroken device support - macOS/iPadOS support (iPhone only for v1) ## 3. Architecture ### 3.1 System Architecture ``` ┌─────────────────────────────────────────────────────────┐ │ iOS (SwiftUI) │ ├─────────────────────────────────────────────────────────┤ │ App Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ContentView│ │SettingsView│ │LogView │ │PortView │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ │ ┌────┴─────────────┴────────────┴─────────────┴────┐ │ │ │ AgentManager │ │ │ │ (orchestrates all components) │ │ │ └──────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ Service Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │Provisioning│ │Heartbeat│ │TunnelMgr │ │ConfigMgr│ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ │ ┌────┴─────────────┴────────────┴─────────────┴────┐ │ │ │ CryptoManager │ │ │ │ AES-256-GCM (CryptoKit) │ │ │ └──────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ NetworkExtension Layer │ │ ┌──────────────────────────────────────────────────┐ │ │ │ PacketTunnelProvider │ │ │ │ (WireGuard tunnel via wireguard-apple) │ │ │ └──────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ Storage Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Keychain │ │ UserDefaults│ │ CoreData│ │ FileMgr │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────────────────┘ ``` ### 3.2 Task Architecture ``` iOS Threads/Tasks ├── Main Thread # UI updates ├── AgentManager # Orchestration (async/await) ├── HeartbeatTask # HTTP heartbeat (30s interval) ├── TunnelManager # WireGuard tunnel management └── BackgroundTask # BGAppRefreshTask (background refresh) ``` ### 3.3 State Machine ``` ┌─────────────┐ │ BOOT │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ APP_INIT │ ◄──────────────────────────────┐ └──────┬──────┘ │ │ loaded │ ▼ │ ┌─────────────┐ fail ┌─────────────┐│ │ PROVISION │───────────────►│ APP_RETRY ││ └──────┬──────┘ └──────┬──────┘│ │ success │ │ ▼ └───────┘ ┌─────────────┐ │ TUNNEL_UP │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ HEARTBEAT │ ◄─── 30s interval └──────┬──────┘ │ config_changed ▼ ┌─────────────┐ │ TUNNEL_REBUILD │ └──────┬──────┘ │ └──► HEARTBEAT ``` ## 4. Module Design ### 4.1 AgentManager **Responsibilities**: - Orchestrate all agent components - Manage agent lifecycle (start/stop) - Handle state transitions **Key Properties**: ```swift class AgentManager: ObservableObject { @Published var state: AgentState = .disconnected @Published var tunnelInfo: TunnelInfo? @Published var logs: [LogEntry] = [] private let provisioning: ProvisioningService private let heartbeat: HeartbeatService private let tunnel: TunnelManager private let config: KeychainStorage } ``` **Key Methods**: ```swift func startAgent() async func stopAgent() func rebuildTunnel(with config: WireGuardConfig) async ``` ### 4.2 PacketTunnelProvider **Responsibilities**: - Handle NetworkExtension tunnel lifecycle - Start/stop WireGuard tunnel - Report tunnel status **Key Methods**: ```swift override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) ``` ### 4.3 TunnelManager **Responsibilities**: - Build WireGuard config from stored keys - Start/stop tunnel via PacketTunnelProvider - Monitor tunnel state - Track last handshake time **Key Methods**: ```swift func startTunnel(config: WireGuardConfig) async throws func stopTunnel() func isTunnelUp() -> Bool func getLastHandshake() -> Date? ``` ### 4.4 HeartbeatService **Responsibilities**: - HTTP POST to `/api/v1/heartbeat` every 30s - Send device status + tunnel state - Receive config sync (detect changes) - Trigger tunnel rebuild if config changed **Key Methods**: ```swift func startHeartbeatLoop() func stopHeartbeat() func sendHeartbeat(config: WireGuardConfig, tunnelUp: Bool, lastHandshake: Date?) async -> WireGuardConfig? ``` ### 4.5 ProvisioningService **Responsibilities**: - HTTP POST to `/api/v1/provision` - Parse encrypted config response - Decrypt config via CryptoManager - Store config in Keychain **Key Methods**: ```swift func provision(serverUrl: String, token: String, hwid: String) async -> WireGuardConfig? func decryptConfig(encrypted: String, hwid: String, salt: String) -> WireGuardConfig? ``` ### 4.6 CryptoManager **Responsibilities**: - Key derivation: SHA-256(hwid + salt) - AES-256-GCM decryption - Secure memory handling **Key Methods**: ```swift func deriveKey(token: String, hwid: String) -> SymmetricKey func decrypt(encoded: String, key: SymmetricKey) -> Data? ``` ### 4.7 KeychainStorage **Responsibilities**: - Store WireGuard keys in Keychain - Store agent config in UserDefaults - Secure key access **Key Methods**: ```swift func saveConfig(_ config: AgentConfig) func loadConfig() -> AgentConfig? func saveWireGuardConfig(_ config: WireGuardConfig) func loadWireGuardConfig() -> WireGuardConfig? func clearAll() ``` ### 4.8 LogStore **Responsibilities**: - Buffer log entries in memory - Persist to CoreData (optional) - Provide log stream for UI **Key Methods**: ```swift func add(level: LogLevel, tag: String, message: String) func clear() func getLogs() -> [LogEntry] ``` ## 5. Data Structures ### 5.1 WireGuardConfig ```swift struct WireGuardConfig: Codable { let deviceId: String let privateKey: String let presharedKey: String let internalIp: String let serverPub: String let endpoint: String let dns: String let allowedIps: String let serverWgIp: String let configHash: String let forwardsHash: String } ``` ### 5.2 AgentConfig ```swift struct AgentConfig: Codable { let serverUrl: String let registrationToken: String let isProvisioned: Bool } ``` ### 5.3 AgentState ```swift enum AgentState: String { case disconnected = "disconnected" case connecting = "connecting" case connected = "connected" case failed = "failed" } ``` ### 5.4 LogEntry ```swift struct LogEntry: Identifiable { let id = UUID() let timestamp: Date let level: LogLevel let tag: String let message: String } ``` ## 6. Error Handling ### 6.1 Error Types ```swift enum AgentError: Error { case wifiNotConnected case provisioningFailed(String) case heartbeatFailed(String) case cryptoDecryptionFailed case configInvalid case tunnelInitFailed case keychainError(OSStatus) } ``` ### 6.2 Recovery Strategy | Error | Recovery | |-------|----------| | WiFi not connected | Retry 3x, then pause heartbeat | | HTTP provision fail | Retry 3x, then notify user | | HTTP heartbeat fail | Log only, continue | | Crypto decrypt fail | Clear config, re-provision | | Tunnel init fail | Retry 3x, then notify user | | Keychain error | Use UserDefaults fallback | ## 7. Background Tasks ### 7.1 BGAppRefreshTask iOS doesn't have boot receiver like Android. Use Background Tasks: ```swift // Register task BGAppRefreshTaskRequest.register(forTaskWithIdentifier: "com.nexusguard.agent.refresh", using: nil) { task in self.handleBackgroundRefresh(task: task as! BGAppRefreshTask) } // Schedule task let request = BGAppRefreshTaskRequest(identifier: "com.nexusguard.agent.refresh") request.earliestBeginDate = Date(timeIntervalSinceNow: 30 * 60) // 30 minutes try BGTaskScheduler.shared.submit(request) ``` ### 7.2 Background Modes Add to `Info.plist`: ```xml UIBackgroundModes fetch processing vpn-api ``` ## 8. Security Considerations ### 8.1 Key Storage - WireGuard private keys stored in iOS Keychain (hardware-backed) - Non-sensitive config in UserDefaults - Never log keys or tokens ### 8.2 TLS - Server communication over HTTPS (TLS 1.2+) - Use `URLSession` with default TLS settings - Certificate pinning (optional, future) ### 8.3 Hardware ID - Use `identifierForVendor` for device identification - Persists across app reinstalls until device factory reset - Used for key derivation (HWID + salt) ## 9. Testing Strategy ### 9.1 Unit Tests - Crypto: Decrypt known ciphertext with known key - Config: Keychain read/write cycle - Heartbeat: Parse server response ### 9.2 Integration Tests - Provision → Tunnel → Heartbeat cycle - Config change detection - Tunnel rebuild on config change ### 9.3 UI Tests - VPN toggle interaction - Tab switching - Settings save/load ### 9.4 Device Tests - iPhone 12+ running iOS 16+ - NetworkExtension background operation - Keychain access in background ## 10. Future Enhancements ### 10.1 Phase 2 - iPadOS support - Widget for quick status - Shortcuts integration - Siri commands ### 10.2 Phase 3 - macOS Catalyst support - Apple Watch companion - CarPlay integration ## 11. References - [Apple NetworkExtension Documentation](https://developer.apple.com/documentation/networkextension) - [WireGuard for iOS](https://github.com/WireGuard/wireguard-apple) - [CryptoKit Documentation](https://developer.apple.com/cryptokit/) - [Background Tasks](https://developer.apple.com/documentation/backgroundtasks) - [NexusGuard Server API](../../server-core/docs/)