diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2024-06-24 13:21:40 +0200 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2024-07-17 11:48:13 +0200 |
| commit | 2df280fbf02fa1b39efffb0b27b297d39d6369c0 (patch) | |
| tree | 1b6a657bb8c594e82a50bd4dfcbfb85d34395fa1 /ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift | |
| parent | b6fe08388dcbfbe1fb54a1c89322c329be2f54f9 (diff) | |
| download | mullvadvpn-2df280fbf02fa1b39efffb0b27b297d39d6369c0.tar.xz mullvadvpn-2df280fbf02fa1b39efffb0b27b297d39d6369c0.zip | |
Add a Rust FFI, Disable sandboxing for scripts
Diffstat (limited to 'ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift')
| -rw-r--r-- | ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift b/ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift new file mode 100644 index 0000000000..8028c1ba3e --- /dev/null +++ b/ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift @@ -0,0 +1,137 @@ +// +// PostQuantumKeyExchangeActor.swift +// PacketTunnel +// +// Created by Marco Nikic on 2024-04-12. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadRustRuntimeProxy +import MullvadTypes +import NetworkExtension +import WireGuardKitTypes + +public class PostQuantumKeyExchangeActor { + struct Negotiation { + var negotiator: PostQuantumKeyNegotiating + var inTunnelTCPConnection: NWTCPConnection + var tcpConnectionObserver: NSKeyValueObservation + + func cancel() { + negotiator.cancelKeyNegotiation() + tcpConnectionObserver.invalidate() + inTunnelTCPConnection.cancel() + } + } + + unowned let packetTunnel: any TunnelProvider + internal var negotiation: Negotiation? + private var timer: DispatchSourceTimer? + private var keyExchangeRetriesIterator: AnyIterator<Duration>! + private let iteratorProvider: () -> AnyIterator<Duration> + private let negotiationProvider: PostQuantumKeyNegotiating.Type + + // Callback in the event of the negotiation failing on startup + var onFailure: () -> Void + + public init( + packetTunnel: any TunnelProvider, + onFailure: @escaping (() -> Void), + negotiationProvider: PostQuantumKeyNegotiating.Type = PostQuantumKeyNegotiator.self, + iteratorProvider: @escaping () -> AnyIterator<Duration> + ) { + self.packetTunnel = packetTunnel + self.onFailure = onFailure + self.negotiationProvider = negotiationProvider + self.iteratorProvider = iteratorProvider + self.keyExchangeRetriesIterator = iteratorProvider() + } + + private func createTCPConnection(_ gatewayEndpoint: NWHostEndpoint) -> NWTCPConnection { + self.packetTunnel.createTCPConnectionThroughTunnel( + to: gatewayEndpoint, + enableTLS: false, + tlsParameters: nil, + delegate: nil + ) + } + + /// Starts a new key exchange. + /// + /// Any ongoing key negotiation is stopped before starting a new one. + /// An exponential backoff timer is used to stop the exchange if it takes too long, + /// or if the TCP connection takes too long to become ready. + /// It is reset after every successful key exchange. + /// + /// - Parameter privateKey: The device's current private key + public func startNegotiation(with privateKey: PrivateKey) { + endCurrentNegotiation() + let negotiator = negotiationProvider.init() + + let gatewayAddress = "10.64.0.1" + let IPv4Gateway = IPv4Address(gatewayAddress)! + let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "\(CONFIG_SERVICE_PORT)") + let inTunnelTCPConnection = createTCPConnection(endpoint) + + // This will become the new private key of the device + let ephemeralSharedKey = PrivateKey() + + let tcpConnectionTimeout = keyExchangeRetriesIterator.next() ?? .seconds(10) + // If the connection never becomes viable, force a reconnection after 10 seconds + scheduleInTunnelConnectionTimeout(startTime: .now() + tcpConnectionTimeout) + + let tcpConnectionObserver = inTunnelTCPConnection.observe(\.isViable, options: [ + .initial, + .new, + ]) { [weak self] observedConnection, _ in + guard let self, observedConnection.isViable else { return } + self.negotiation?.tcpConnectionObserver.invalidate() + self.timer?.cancel() + + if !negotiator.startNegotiation( + gatewayIP: IPv4Gateway, + devicePublicKey: privateKey.publicKey, + presharedKey: ephemeralSharedKey, + postQuantumKeyReceiver: packetTunnel, + tcpConnection: inTunnelTCPConnection, + postQuantumKeyExchangeTimeout: tcpConnectionTimeout + ) { + self.negotiation = nil + self.onFailure() + } + } + negotiation = Negotiation( + negotiator: negotiator, + inTunnelTCPConnection: inTunnelTCPConnection, + tcpConnectionObserver: tcpConnectionObserver + ) + } + + /// Cancels the ongoing key exchange. + public func endCurrentNegotiation() { + negotiation?.cancel() + negotiation = nil + } + + /// Resets the exponential timeout for successful key exchanges, and ends the current key exchange. + public func reset() { + keyExchangeRetriesIterator = iteratorProvider() + endCurrentNegotiation() + } + + private func scheduleInTunnelConnectionTimeout(startTime: DispatchWallTime) { + let newTimer = DispatchSource.makeTimerSource() + + newTimer.setEventHandler { [weak self] in + self?.onFailure() + self?.timer?.cancel() + } + + newTimer.schedule(wallDeadline: startTime) + newTimer.activate() + + timer?.cancel() + timer = newTimer + } +} |
