From 07152f362a6959d39dceba6245af8e30339bf87e Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 8 May 2024 14:08:12 +0200 Subject: [PATCH] Add a connection timeout check for the in-tunnel TCP connection Signed-off-by: Bug Magnet --- ios/MullvadPostQuantum/MullvadPostQuantum.h | 11 +-- .../PacketTunnelProvider+TCPConnection.swift | 68 ++++++++++---- .../PostQuantumKeyNegotiator.swift | 12 ++- .../include/talpid_tunnel_config_client.h | 39 ++++++-- ios/MullvadVPN.xcodeproj/project.pbxproj | 4 + .../TunnelManager/TunnelManager.swift | 6 +- .../PacketTunnelProvider.swift | 24 ++--- .../PostQuantumKeyExchangeActor.swift | 43 +++++++-- .../Actor/ConfigurationBuilder.swift | 1 - .../Actor/PacketTunnelActor+PostQuantum.swift | 84 +++++++++++++++++ .../Actor/PacketTunnelActor.swift | 94 ++++++------------- talpid-routing/src/unix/mod.rs | 2 +- .../src/ios_ffi/ios_runtime.rs | 23 ++--- .../src/ios_ffi/ios_tcp_connection.rs | 68 ++++++++------ .../src/ios_ffi/mod.rs | 85 ++++++++++------- talpid-tunnel-config-client/src/lib.rs | 42 ++++++--- 16 files changed, 390 insertions(+), 216 deletions(-) create mode 100644 ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift diff --git a/ios/MullvadPostQuantum/MullvadPostQuantum.h b/ios/MullvadPostQuantum/MullvadPostQuantum.h index f48ca77519ac..c196b0527bc9 100644 --- a/ios/MullvadPostQuantum/MullvadPostQuantum.h +++ b/ios/MullvadPostQuantum/MullvadPostQuantum.h @@ -7,13 +7,4 @@ // #import - -//! Project version number for MullvadPostQuantum. -FOUNDATION_EXPORT double MullvadPostQuantumVersionNumber; - -//! Project version string for MullvadPostQuantum. -FOUNDATION_EXPORT const unsigned char MullvadPostQuantumVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - +#import "talpid_tunnel_config_client.h" diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift index 8b06c7b178c1..b3bf815b1bb5 100644 --- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -12,46 +12,82 @@ import NetworkExtension import TalpidTunnelConfigClientProxy import WireGuardKitTypes +/// Writes data to the in-tunnel TCP connection +/// +/// This FFI function is called by Rust whenever there is data to be written to the in-tunnel TCP connection when exchanging +/// quantum-resistant pre shared keys. +/// +/// Whenever the flow control is given back from the connection, acknowledge that data was written using `rawWriteAcknowledgement`. +/// - Parameters: +/// - rawConnection: A raw pointer to the in-tunnel TCP connection +/// - rawData: A raw pointer to the data to write in the connection +/// - dataLength: The length of data to write in the connection +/// - rawWriteAcknowledgement: An opaque pointer needed for write acknowledgement @_cdecl("swift_nw_tcp_connection_send") func tcpConnectionSend( - connection: UnsafeMutableRawPointer?, - data: UnsafeMutableRawPointer, + rawConnection: UnsafeMutableRawPointer?, + rawData: UnsafeMutableRawPointer, dataLength: UInt, - sender: UnsafeMutableRawPointer? + rawWriteAcknowledgement: UnsafeMutableRawPointer? ) { - guard let connection, let sender else { return } - let tcpConnection = Unmanaged.fromOpaque(connection).takeUnretainedValue() - let rawData = Data(bytes: data, count: Int(dataLength)) + guard let rawConnection, let rawWriteAcknowledgement else { + handle_sent(0, rawWriteAcknowledgement) + return + } + let tcpConnection = Unmanaged.fromOpaque(rawConnection).takeUnretainedValue() + let data = Data(bytes: rawData, count: Int(dataLength)) - // The guarantee that no more than 2 writes happen in parallel is done by virtue of not returning the execution context + // The guarantee that all writes are sequential is done by virtue of not returning the execution context // to Rust before this closure is done executing. - tcpConnection.write(rawData, completionHandler: { maybeError in + tcpConnection.write(data, completionHandler: { maybeError in if maybeError != nil { - handle_sent(0, sender) + handle_sent(0, rawWriteAcknowledgement) } else { - handle_sent(dataLength, sender) + handle_sent(dataLength, rawWriteAcknowledgement) } }) } +/// Reads data to the in-tunnel TCP connection +/// +/// This FFI function is called by Rust whenever there is data to be read from the in-tunnel TCP connection when exchanging +/// quantum-resistant pre shared keys. +/// +/// Whenever the flow control is given back from the connection, acknowledge that data was read using `rawReadAcknowledgement`. +/// - Parameters: +/// - rawConnection: A raw pointer to the in-tunnel TCP connection +/// - rawReadAcknowledgement: An opaque pointer needed for read acknowledgement @_cdecl("swift_nw_tcp_connection_read") func tcpConnectionReceive( - connection: UnsafeMutableRawPointer?, - sender: UnsafeMutableRawPointer? + rawConnection: UnsafeMutableRawPointer?, + rawReadAcknowledgement: UnsafeMutableRawPointer? ) { - guard let connection, let sender else { return } - let tcpConnection = Unmanaged.fromOpaque(connection).takeUnretainedValue() + guard let rawConnection, let rawReadAcknowledgement else { + handle_recv(nil, 0, rawReadAcknowledgement) + return + } + let tcpConnection = Unmanaged.fromOpaque(rawConnection).takeUnretainedValue() tcpConnection.readMinimumLength(0, maximumLength: Int(UInt16.max)) { data, maybeError in if let data { if maybeError != nil { - handle_recv(Data().map { $0 }, 0, sender) + handle_recv(nil, 0, rawReadAcknowledgement) } else { - handle_recv(data.map { $0 }, UInt(data.count), sender) + handle_recv(data.map { $0 }, UInt(data.count), rawReadAcknowledgement) } } } } +/// End sequence of a quantum-secure pre shared key exchange. +/// +/// This FFI function is called by Rust when the quantum-secure pre shared key exchange has either failed, or succeeded. +/// When both the `rawPresharedKey` and the `rawEphemeralKey` are raw pointers to 32 bytes data arrays, +/// the quantum-secure key exchange is considered successful. In any other case, the exchange is considered failed. +/// +/// - Parameters: +/// - rawPacketTunnel: A raw pointer to the running instance of `NEPacketTunnelProvider` +/// - rawPresharedKey: A raw pointer to the quantum-secure pre shared key +/// - rawEphemeralKey: A raw pointer to the ephemeral private key of the device @_cdecl("swift_post_quantum_key_ready") func receivePostQuantumKey( rawPacketTunnel: UnsafeMutableRawPointer?, diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift index 8e813f47df90..16f7a24931ea 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift @@ -11,23 +11,25 @@ import NetworkExtension import TalpidTunnelConfigClientProxy import WireGuardKitTypes +/** + Attempt to start the asynchronous process of key negotiation. Returns true if successfully started, false if failed. + */ public class PostQuantumKeyNegotiator { public init() {} var cancelToken: PostQuantumCancelToken? - public func negotiateKey( + public func startNegotiation( gatewayIP: IPv4Address, devicePublicKey: PublicKey, presharedKey: PrivateKey, packetTunnel: NEPacketTunnelProvider, tcpConnection: NWTCPConnection - ) { + ) -> Bool { let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque() let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque() var cancelToken = PostQuantumCancelToken() - // TODO: Any non 0 return is considered a failure, and should be handled gracefully let result = negotiate_post_quantum_key( devicePublicKey.rawValue.map { $0 }, presharedKey.rawValue.map { $0 }, @@ -36,10 +38,10 @@ public class PostQuantumKeyNegotiator { &cancelToken ) guard result == 0 else { - // Handle failure here - return + return false } self.cancelToken = cancelToken + return true } public func cancelKeyNegotiation() { diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h b/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h index 7b9315c12fb7..04180db289d4 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h @@ -13,23 +13,44 @@ typedef struct PostQuantumCancelToken { } PostQuantumCancelToken; /** - * * # Safety * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider` + * Called by the Swift side to signal that the quantum-secure key exchange should be cancelled. + * + * # Safety + * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`. */ void cancel_post_quantum_key_exchange(const struct PostQuantumCancelToken *sender); /** - * * # Safety * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`. + * Called by the Swift side to signal that the Rust `PostQuantumCancelToken` can be safely dropped from memory. + * + * # Safety + * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`. */ void drop_post_quantum_key_exchange_token(const struct PostQuantumCancelToken *sender); /** - * * # Safety * `sender` must be pointing to a valid instance of a `write_tx` created by the `IosTcpProvider` * * Callback to call when the TCP connection has written data. + * Called by Swift whenever data has been written to the in-tunnel TCP connection when exchanging + * quantum-resistant pre shared keys. + * + * If `bytes_sent` is 0, this indicates that the connection was closed or that an error occurred. + * + * # Safety + * `sender` must be pointing to a valid instance of a `write_tx` created by the `IosTcpProvider` + * Callback to call when the TCP connection has written data. */ -void handle_sent(uintptr_t bytes_sent, - const void *sender); +void handle_sent(uintptr_t bytes_sent, const void *sender); /** - * * # Safety * `sender` must be pointing to a valid instance of a `read_tx` created by the `IosTcpProvider` * * Callback to call when the TCP connection has received data. + * Called by Swift whenever data has been read from the in-tunnel TCP connection when exchanging + * quantum-resistant pre shared keys. + * + * If `data` is null or empty, this indicates that the connection was closed or that an error occurred. + * An empty buffer is sent to the underlying reader to signal EOF. + * + * # Safety + * `sender` must be pointing to a valid instance of a `read_tx` created by the `IosTcpProvider` + * + * Callback to call when the TCP connection has received data. */ void handle_recv(const uint8_t *data, uintptr_t data_len, @@ -39,7 +60,11 @@ void handle_recv(const uint8_t *data, * Entry point for exchanging post quantum keys on iOS. * The TCP connection must be created to go through the tunnel. * # Safety - * This function is safe to call + * `public_key` and `ephemeral_key` must be valid respective `PublicKey` and `PrivateKey` types. + * They will not be valid after this function is called, and thus must be copied here. + * `packet_tunnel` and `tcp_connection` must be valid pointers to a packet tunnel and a TCP connection + * instances. + * `cancel_token` should be owned by the caller of this function. */ int32_t negotiate_post_quantum_key(const uint8_t *public_key, const uint8_t *ephemeral_key, diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index c5b6fb90426b..ad0f0ed740a8 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */; }; 44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; }; 44DD7D2D2B74E44A0005F67F /* QuantumResistanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */; }; + 44DF8AC42BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DF8AC32BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift */; }; 5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */; }; 5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4B12940A48700C23744 /* TunnelStore.swift */; }; 5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; }; @@ -1429,6 +1430,7 @@ 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = ""; }; 44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = ""; }; 44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuantumResistanceSettings.swift; sourceTree = ""; }; + 44DF8AC32BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+PostQuantum.swift"; sourceTree = ""; }; 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; }; 5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteProtocol.swift; sourceTree = ""; }; 5802EBC82A8E45BA00E5CE4C /* ApplicationRouterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRouterDelegate.swift; sourceTree = ""; }; @@ -3122,6 +3124,7 @@ 58342C032AAB61FB003BA12D /* State+Extensions.swift */, 586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */, 58DDA18E2ABC32380039C360 /* Timings.swift */, + 44DF8AC32BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift */, ); path = Actor; sourceTree = ""; @@ -5483,6 +5486,7 @@ 58FE25DA2AA72A8F003D1918 /* PacketTunnelActor.swift in Sources */, 587A5E522ADD7569003A70F1 /* ObservedState+Extensions.swift in Sources */, 58FE25E62AA738E8003D1918 /* TunnelAdapterProtocol.swift in Sources */, + 44DF8AC42BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift in Sources */, 583832252AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift in Sources */, 58C7A4552A863FB90060C66F /* TunnelMonitor.swift in Sources */, 58C7AF182ABD84AB007EDD7A /* ProxyURLResponse.swift in Sources */, diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index 897f0074ac85..f16281019912 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -670,15 +670,11 @@ final class TunnelManager: StorePaymentObserver { refreshDeviceState() } switch newTunnelStatus.state { - case .connecting, .reconnecting: + case .connecting, .reconnecting, .negotiatingPostQuantumKey: // Start polling tunnel status to keep the relay information up to date // while the tunnel process is trying to connect. startPollingTunnelStatus(interval: establishingTunnelStatusPollInterval) - case .negotiatingPostQuantumKey: - // No need to poll the tunnel while negotiating post quantum keys, assume the connection will work - break - case .connected, .waitingForConnectivity(.noConnection): // Start polling tunnel status to keep connectivity status up to date. startPollingTunnelStatus(interval: establishedTunnelStatusPollInterval) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index ab64040f0aa9..6b27aa59f83d 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -82,7 +82,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider { protocolObfuscator: ProtocolObfuscator() ) - postQuantumActor = PostQuantumKeyExchangeActor(packetTunnel: self) + postQuantumActor = PostQuantumKeyExchangeActor( + packetTunnel: self, + onFailure: self.keyExchangeFailed + ) let urlRequestProxy = URLRequestProxy(dispatchQueue: internalQueue, transportProvider: transportProvider) @@ -249,6 +252,7 @@ extension PacketTunnelProvider { lastConnectionAttempt = connectionAttempt case let .negotiatingPostQuantumKey(_, privateKey): + postQuantumActor.endCurrentNegotiation() postQuantumActor.startNegotiation(with: privateKey) case .initial, .connected, .disconnecting, .disconnected, .error: @@ -258,16 +262,6 @@ extension PacketTunnelProvider { } } - func createTCPConnectionForPQPSK(_ gatewayAddress: String) -> NWTCPConnection { - let gatewayEndpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337") - return createTCPConnectionThroughTunnel( - to: gatewayEndpoint, - enableTLS: false, - tlsParameters: nil, - delegate: nil - ) - } - private func stopObservingActorState() { stateObserverTask?.cancel() stateObserverTask = nil @@ -306,11 +300,13 @@ extension PacketTunnelProvider { extension PacketTunnelProvider: PostQuantumKeyReceiving { func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) { actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) - postQuantumActor.acknowledgeNegotiationConcluded() + postQuantumActor.endCurrentNegotiation() } func keyExchangeFailed() { - postQuantumActor.acknowledgeNegotiationConcluded() - actor.reconnect(to: .current) + postQuantumActor.endCurrentNegotiation() + // Do not try reconnecting to the `.current` relay, else the actor's `State` equality check will fail + // and it will not try to reconnect + actor.reconnect(to: .random) } } diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index e9189d947c65..33291d8ea591 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -26,9 +26,14 @@ class PostQuantumKeyExchangeActor { unowned let packetTunnel: PacketTunnelProvider private var negotiation: Negotiation? + private var timer: DispatchSourceTimer? - init(packetTunnel: PacketTunnelProvider) { + // Callback in the event of the negotiation failing on startup + var onFailure: () -> Void + + init(packetTunnel: PacketTunnelProvider, onFailure: @escaping (() -> Void)) { self.packetTunnel = packetTunnel + self.onFailure = onFailure } private func createTCPConnection(_ gatewayEndpoint: NWHostEndpoint) -> NWTCPConnection { @@ -45,24 +50,33 @@ class PostQuantumKeyExchangeActor { let gatewayAddress = "10.64.0.1" let IPv4Gateway = IPv4Address(gatewayAddress)! - let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337") + let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "\(CONFIG_SERVICE_PORT)") let inTunnelTCPConnection = createTCPConnection(endpoint) - let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device + // This will become the new private key of the device + let ephemeralSharedKey = PrivateKey() + + // If the connection never becomes viable, force a reconnection after 10 seconds + scheduleInTunnelConnectionTimeout(startTime: .now() + 10) let tcpConnectionObserver = inTunnelTCPConnection.observe(\.isViable, options: [ .initial, .new, ]) { [weak self] observedConnection, _ in guard let self, observedConnection.isViable else { return } - negotiator.negotiateKey( + self.negotiation?.tcpConnectionObserver.invalidate() + self.timer?.cancel() + + if !negotiator.startNegotiation( gatewayIP: IPv4Gateway, devicePublicKey: privateKey.publicKey, presharedKey: ephemeralSharedKey, packetTunnel: packetTunnel, tcpConnection: inTunnelTCPConnection - ) - self.negotiation?.tcpConnectionObserver.invalidate() + ) { + self.negotiation = nil + self.onFailure() + } } negotiation = Negotiation( negotiator: negotiator, @@ -71,8 +85,23 @@ class PostQuantumKeyExchangeActor { ) } - func acknowledgeNegotiationConcluded() { + func endCurrentNegotiation() { negotiation?.cancel() negotiation = nil } + + 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 + } } diff --git a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift index 79833d16c2aa..226b8b05fe37 100644 --- a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift +++ b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift @@ -27,7 +27,6 @@ public struct ConfigurationBuilder { var dns: SelectedDNSServers? var endpoint: MullvadEndpoint? var allowedIPs: [IPAddressRange] - // or should this go in MullvadEndpoint? var preSharedKey: PreSharedKey? public init( diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift new file mode 100644 index 000000000000..53066ce4374d --- /dev/null +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift @@ -0,0 +1,84 @@ +// +// PacketTunnelActor+PostQuantum.swift +// PacketTunnelCore +// +// Created by Andrew Bulhak on 2024-05-13. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import WireGuardKitTypes + +extension PacketTunnelActor { + /** + Attempt to start the process of negotiating a post-quantum secure key, setting up an initial + connection restricted to the negotiation host and entering the negotiating state. + */ + internal func tryStartPostQuantumNegotiation( + withSettings settings: Settings, + nextRelay: NextRelay, + reason: ReconnectReason + ) async throws { + if let connectionState = try makeConnectionState(nextRelay: nextRelay, settings: settings, reason: reason) { + let selectedEndpoint = connectionState.selectedRelay.endpoint + let activeKey = activeKey(from: connectionState, in: settings) + + let configurationBuilder = ConfigurationBuilder( + privateKey: activeKey, + interfaceAddresses: settings.interfaceAddresses, + dns: settings.dnsServers, + endpoint: selectedEndpoint, + allowedIPs: [ + IPAddressRange(from: "10.64.0.1/32")!, + ] + ) + + try await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration()) + state = .negotiatingPostQuantumKey(connectionState, activeKey) + } + } + + /** + Called on receipt of the new PQ-negotiated key, to reconnect to the relay, in PQ-secure mode. + */ + internal func postQuantumConnect(with key: PreSharedKey, privateKey: PrivateKey) async { + guard + // It is important to select the same relay that was saved in the connection state as the key negotiation happened with this specific relay. + let selectedRelay = state.connectionData?.selectedRelay, + let settings: Settings = try? settingsReader.read(), + let connectionState = try? obfuscateConnection( + nextRelay: .preSelected(selectedRelay), + settings: settings, + reason: .userInitiated + ) + else { + logger.error("Could not create connection state in PostQuantumConnect") + + let nextRelay: NextRelay = (state.connectionData?.selectedRelay).map { .preSelected($0) } ?? .current + commandChannel.send(.reconnect(nextRelay)) + return + } + + let configurationBuilder = ConfigurationBuilder( + privateKey: privateKey, + interfaceAddresses: settings.interfaceAddresses, + dns: settings.dnsServers, + endpoint: connectionState.connectedEndpoint, + allowedIPs: [ + IPAddressRange(from: "0.0.0.0/0")!, + IPAddressRange(from: "::/0")!, + ], + preSharedKey: key + ) + stopDefaultPathObserver() + + state = .connecting(connectionState) + + try? await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration()) + // Resume tunnel monitoring and use IPv4 gateway as a probe address. + tunnelMonitor.start(probeAddress: connectionState.selectedRelay.endpoint.ipv4Gateway) + // Restart default path observer and notify the observer with the current path that might have changed while + // path observer was paused. + startDefaultPathObserver(notifyObserverWithCurrentPath: false) + } +} diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 552624d504c7..153c1c043058 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -224,48 +224,28 @@ extension PacketTunnelActor { } } - private func postQuantumConnect(with key: PreSharedKey, privateKey: PrivateKey) async { - guard - // It is important to select the same relay that was saved in the connection state as the key negotiation happened with this specific relay. - let selectedRelay = state.connectionData?.selectedRelay, - let settings: Settings = try? settingsReader.read(), - let connectionState = try? obfuscateConnection( - nextRelay: .preSelected(selectedRelay), - settings: settings, - reason: .userInitiated - ) - else { return } - - let configurationBuilder = ConfigurationBuilder( - privateKey: privateKey, - interfaceAddresses: settings.interfaceAddresses, - dns: settings.dnsServers, - endpoint: connectionState.connectedEndpoint, - allowedIPs: [ - IPAddressRange(from: "0.0.0.0/0")!, - IPAddressRange(from: "::/0")!, - ], - preSharedKey: key - ) - stopDefaultPathObserver() + /** + Entry point for attempting to start the tunnel by performing the following steps: - state = .connecting(connectionState) + - Read settings + - Start either a direct connection or the post-quantum key negotiation process, depending on settings. + */ + private func tryStart( + nextRelay: NextRelay, + reason: ReconnectReason = .userInitiated + ) async throws { + let settings: Settings = try settingsReader.read() - defer { - // Restart default path observer and notify the observer with the current path that might have changed while - // path observer was paused. - startDefaultPathObserver(notifyObserverWithCurrentPath: false) + if settings.quantumResistance.isEnabled { + try await tryStartPostQuantumNegotiation(withSettings: settings, nextRelay: nextRelay, reason: reason) + } else { + try await tryStartConnection(withSettings: settings, nextRelay: nextRelay, reason: reason) } - - try? await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration()) - // Resume tunnel monitoring and use IPv4 gateway as a probe address. - tunnelMonitor.start(probeAddress: connectionState.selectedRelay.endpoint.ipv4Gateway) } /** - Attempt to start the tunnel by performing the following steps: + Attempt to start a direct (non-quantum) connection to the tunnel by performing the following steps: - - Read settings. - Determine target state, it can either be `.connecting` or `.reconnecting`. (See `TargetStateForReconnect`) - Bail if target state cannot be determined. That means that the actor is past the point when it could logically connect or reconnect, i.e it can already be in `.disconnecting` state. @@ -277,33 +257,11 @@ extension PacketTunnelActor { - nextRelay: which relay should be selected next. - reason: reason for reconnect */ - private func tryStart( + private func tryStartConnection( + withSettings settings: Settings, nextRelay: NextRelay, - reason: ReconnectReason = .userInitiated + reason: ReconnectReason ) async throws { - let settings: Settings = try settingsReader.read() - - if settings.quantumResistance.isEnabled { - if let connectionState = try makeConnectionState(nextRelay: nextRelay, settings: settings, reason: reason) { - let selectedEndpoint = connectionState.selectedRelay.endpoint - let activeKey = activeKey(from: connectionState, in: settings) - - let configurationBuilder = ConfigurationBuilder( - privateKey: activeKey, - interfaceAddresses: settings.interfaceAddresses, - dns: settings.dnsServers, - endpoint: selectedEndpoint, - allowedIPs: [ - IPAddressRange(from: "10.64.0.1/32")!, - ] - ) - - try await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration()) - state = .negotiatingPostQuantumKey(connectionState, activeKey) - } - return - } - guard let connectionState = try obfuscateConnection(nextRelay: nextRelay, settings: settings, reason: reason), let targetState = state.targetStateForReconnect else { return } @@ -356,7 +314,7 @@ extension PacketTunnelActor { - Returns: New connection state or `nil` if current state is at or past `.disconnecting` phase. */ - private func makeConnectionState( + internal func makeConnectionState( nextRelay: NextRelay, settings: Settings, reason: ReconnectReason @@ -382,7 +340,13 @@ extension PacketTunnelActor { connectionState.incrementAttemptCount() } fallthrough - case let .negotiatingPostQuantumKey(connectionState, _): + case var .negotiatingPostQuantumKey(connectionState, _): + let selectedRelay = try callRelaySelector( + connectionState.selectedRelay, + connectionState.connectionAttemptCount + ) + connectionState.selectedRelay = selectedRelay + connectionState.relayConstraints = settings.relayConstraints return connectionState case var .connected(connectionState): let selectedRelay = try callRelaySelector( @@ -416,7 +380,7 @@ extension PacketTunnelActor { ) } - private func activeKey(from state: State.ConnectionData, in settings: Settings) -> PrivateKey { + internal func activeKey(from state: State.ConnectionData, in settings: Settings) -> PrivateKey { switch state.keyPolicy { case .useCurrent: settings.privateKey @@ -425,7 +389,7 @@ extension PacketTunnelActor { } } - private func obfuscateConnection( + internal func obfuscateConnection( nextRelay: NextRelay, settings: Settings, reason: ReconnectReason @@ -451,7 +415,7 @@ extension PacketTunnelActor { connectedEndpoint: obfuscatedEndpoint, transportLayer: transportLayer, remotePort: protocolObfuscator.remotePort, - isPostQuantum: connectionState.isPostQuantum + isPostQuantum: settings.quantumResistance.isEnabled ) } diff --git a/talpid-routing/src/unix/mod.rs b/talpid-routing/src/unix/mod.rs index ca3f44093540..5f14d88d67ac 100644 --- a/talpid-routing/src/unix/mod.rs +++ b/talpid-routing/src/unix/mod.rs @@ -18,7 +18,7 @@ use futures::stream::Stream; use std::net::IpAddr; #[allow(clippy::module_inception)] -#[cfg(any(target_os = "macos", target_os = "ios"))] +#[cfg(target_os = "macos")] #[path = "macos/mod.rs"] pub mod imp; diff --git a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs index ce0faf4ddb40..37fdb0d16d17 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs @@ -1,5 +1,5 @@ use super::{ios_tcp_connection::*, PostQuantumCancelToken}; -use crate::{request_ephemeral_peer, Error, RelayConfigService}; +use crate::{request_ephemeral_peer_with, Error, RelayConfigService}; use libc::c_void; use std::{future::Future, io, pin::Pin, ptr, sync::Arc}; use talpid_types::net::wireguard::{PrivateKey, PublicKey}; @@ -10,12 +10,12 @@ use tower::util::service_fn; /// # Safety /// packet_tunnel and tcp_connection must be valid pointers to a packet tunnel and a TCP connection /// instances. -pub unsafe fn run_ios_runtime( +pub unsafe fn run_post_quantum_psk_exchange( pub_key: [u8; 32], ephemeral_key: [u8; 32], packet_tunnel: *const c_void, tcp_connection: *const c_void, -) -> Result { +) -> Result { match unsafe { IOSRuntime::new(pub_key, ephemeral_key, packet_tunnel, tcp_connection) } { Ok(runtime) => { let token = runtime.cancel_token_tx.clone(); @@ -27,7 +27,7 @@ pub unsafe fn run_ios_runtime( } Err(err) => { log::error!("Failed to create runtime {}", err); - Err(-1) + Err(Error::UnableToCreateRuntime) } } } @@ -85,7 +85,10 @@ impl IOSRuntime { }); } - pub async fn ios_tcp_client( + /// Creates a `RelayConfigService` using the in-tunnel TCP Connection provided by the Packet Tunnel Provider + /// # Safety + /// It is unsafe to call this with an already used `SwiftContext` + async unsafe fn ios_tcp_client( ctx: SwiftContext, ) -> Result<(RelayConfigService, IosTcpShutdownHandle), Error> { let endpoint = Endpoint::from_static("tcp://0.0.0.0:0"); @@ -117,25 +120,23 @@ impl IOSRuntime { let packet_tunnel_ptr = self.packet_tunnel.packet_tunnel; runtime.block_on(async move { - let (async_provider, shutdown_handle) = match Self::ios_tcp_client(self.packet_tunnel).await { + let (async_provider, shutdown_handle) = unsafe { match Self::ios_tcp_client(self.packet_tunnel).await { Ok(result) => result, Err(error) => { log::error!("Failed to create iOS TCP client: {error}"); - unsafe { swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); - } return; } - }; + }}; let ephemeral_pub_key = PrivateKey::from(self.ephemeral_key).public_key(); tokio::select! { - ephemeral_peer = request_ephemeral_peer( + ephemeral_peer = request_ephemeral_peer_with( + async_provider, PublicKey::from(self.pub_key), ephemeral_pub_key, true, false, - async_provider, ) => { shutdown_handle.shutdown(); match ephemeral_peer { diff --git a/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs b/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs index 7609511b535f..86a3114edff3 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs @@ -3,9 +3,9 @@ use std::{ io::{self, Result}, sync::{ atomic::{self, AtomicBool}, - Arc, + Arc, Mutex, Weak, }, - task::Poll, + task::{Poll, Waker}, }; use tokio::{ io::{AsyncRead, AsyncWrite}, @@ -45,18 +45,20 @@ extern "C" { unsafe impl Send for IosTcpProvider {} pub struct IosTcpProvider { - write_tx: mpsc::UnboundedSender, + write_tx: Arc>, write_rx: mpsc::UnboundedReceiver, - read_tx: mpsc::UnboundedSender>, + read_tx: Arc>>, read_rx: mpsc::UnboundedReceiver>, tcp_connection: *const c_void, read_in_progress: bool, write_in_progress: bool, shutdown: Arc, + waker: Arc>>, } pub struct IosTcpShutdownHandle { shutdown: Arc, + waker: Arc>>, } impl IosTcpProvider { @@ -68,30 +70,41 @@ impl IosTcpProvider { let (tx, rx) = mpsc::unbounded_channel(); let (recv_tx, recv_rx) = mpsc::unbounded_channel(); let shutdown = Arc::new(AtomicBool::new(false)); + let waker = Arc::new(Mutex::new(None)); ( Self { - write_tx: tx, + write_tx: Arc::new(tx), write_rx: rx, - read_tx: recv_tx, + read_tx: Arc::new(recv_tx), read_rx: recv_rx, tcp_connection, read_in_progress: false, write_in_progress: false, shutdown: shutdown.clone(), + waker: waker.clone(), }, - IosTcpShutdownHandle { shutdown }, + IosTcpShutdownHandle { shutdown, waker }, ) } fn is_shutdown(&self) -> bool { self.shutdown.load(atomic::Ordering::SeqCst) } + + fn maybe_set_waker(&self, new_waker: Waker) { + if let Ok(mut waker) = self.waker.lock() { + *waker = Some(new_waker); + } + } } impl IosTcpShutdownHandle { pub fn shutdown(&self) { self.shutdown.store(true, atomic::Ordering::SeqCst); + if let Some(waker) = self.waker.lock().ok().and_then(|mut waker| waker.take()) { + waker.wake(); + } } } @@ -101,8 +114,7 @@ impl AsyncWrite for IosTcpProvider { cx: &mut std::task::Context<'_>, buf: &[u8], ) -> std::task::Poll> { - let raw_sender = Box::into_raw(Box::new(self.write_tx.clone())); - + self.maybe_set_waker(cx.waker().clone()); match self.write_rx.poll_recv(cx) { std::task::Poll::Ready(Some(bytes_sent)) => { self.write_in_progress = false; @@ -116,17 +128,17 @@ impl AsyncWrite for IosTcpProvider { if self.is_shutdown() { return Poll::Ready(Err(connection_closed_err())); } - if self.write_in_progress { - return std::task::Poll::Pending; - } - self.write_in_progress = true; - unsafe { - swift_nw_tcp_connection_send( - self.tcp_connection, - buf.as_ptr() as _, - buf.len(), - raw_sender as _, - ); + if !self.write_in_progress { + let raw_sender = Weak::into_raw(Arc::downgrade(&self.write_tx)); + unsafe { + swift_nw_tcp_connection_send( + self.tcp_connection, + buf.as_ptr() as _, + buf.len(), + raw_sender as _, + ); + } + self.write_in_progress = true; } std::task::Poll::Pending } @@ -153,7 +165,6 @@ impl AsyncRead for IosTcpProvider { cx: &mut std::task::Context<'_>, buf: &mut tokio::io::ReadBuf<'_>, ) -> std::task::Poll> { - let raw_sender = Box::into_raw(Box::new(self.read_tx.clone())); if self.is_shutdown() { return Poll::Ready(Err(connection_closed_err())); } @@ -169,15 +180,14 @@ impl AsyncRead for IosTcpProvider { Poll::Ready(Err(connection_closed_err())) } std::task::Poll::Pending => { - if self.read_in_progress { - return std::task::Poll::Pending; - } - self.read_in_progress = true; - unsafe { - swift_nw_tcp_connection_read(self.tcp_connection, raw_sender as _); + if !self.read_in_progress { + let raw_sender = Weak::into_raw(Arc::downgrade(&self.read_tx)); + unsafe { + swift_nw_tcp_connection_read(self.tcp_connection, raw_sender as _); + } + self.read_in_progress = true; } - - std::task::Poll::Pending + Poll::Pending } } } diff --git a/talpid-tunnel-config-client/src/ios_ffi/mod.rs b/talpid-tunnel-config-client/src/ios_ffi/mod.rs index 9941bf434060..94dcf95b6f89 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/mod.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/mod.rs @@ -1,9 +1,9 @@ pub mod ios_runtime; pub mod ios_tcp_connection; -use crate::ios_ffi::ios_runtime::run_ios_runtime; +use crate::ios_ffi::ios_runtime::run_post_quantum_psk_exchange; use libc::c_void; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use tokio::sync::mpsc; use std::sync::Once; @@ -16,12 +16,14 @@ pub struct PostQuantumCancelToken { } impl PostQuantumCancelToken { - /// #Safety + /// # Safety /// This function can only be called when the context pointer is valid. unsafe fn cancel(&self) { + // # Safety // Try to take the value, if there is a value, we can safely send the message, otherwise, assume it has been dropped and nothing happens let send_tx: Arc> = unsafe { Arc::from_raw(self.context as _) }; let _ = send_tx.send(()); + // Call std::mem::forget here to avoid dropping the channel. std::mem::forget(send_tx); } } @@ -33,20 +35,19 @@ impl Drop for PostQuantumCancelToken { } unsafe impl Send for PostQuantumCancelToken {} +/// Called by the Swift side to signal that the quantum-secure key exchange should be cancelled. +/// +/// # Safety +/// `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`. #[no_mangle] -/** - * # Safety - * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider` - */ pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const PostQuantumCancelToken) { let sender = unsafe { &*sender }; sender.cancel(); } - -/** - * # Safety - * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`. - */ +/// Called by the Swift side to signal that the Rust `PostQuantumCancelToken` can be safely dropped from memory. +/// +/// # Safety +/// `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`. #[no_mangle] pub unsafe extern "C" fn drop_post_quantum_key_exchange_token( sender: *const PostQuantumCancelToken, @@ -54,38 +55,56 @@ pub unsafe extern "C" fn drop_post_quantum_key_exchange_token( let _sender = unsafe { std::ptr::read(sender) }; } -/** - * # Safety - * `sender` must be pointing to a valid instance of a `write_tx` created by the `IosTcpProvider` - * - * Callback to call when the TCP connection has written data. - */ +/// Called by Swift whenever data has been written to the in-tunnel TCP connection when exchanging +/// quantum-resistant pre shared keys. +/// +/// If `bytes_sent` is 0, this indicates that the connection was closed or that an error occurred. +/// +/// # Safety +/// `sender` must be pointing to a valid instance of a `write_tx` created by the `IosTcpProvider` +/// Callback to call when the TCP connection has written data. #[no_mangle] pub unsafe extern "C" fn handle_sent(bytes_sent: usize, sender: *const c_void) { - let send_tx: Box> = unsafe { Box::from_raw(sender as _) }; - _ = send_tx.send(bytes_sent); + let weak_tx: Weak> = unsafe { Weak::from_raw(sender as _) }; + if let Some(send_tx) = weak_tx.upgrade() { + _ = send_tx.send(bytes_sent); + } } -/** - * # Safety - * `sender` must be pointing to a valid instance of a `read_tx` created by the `IosTcpProvider` - * - * Callback to call when the TCP connection has received data. - */ +/// Called by Swift whenever data has been read from the in-tunnel TCP connection when exchanging +/// quantum-resistant pre shared keys. +/// +/// If `data` is null or empty, this indicates that the connection was closed or that an error occurred. +/// An empty buffer is sent to the underlying reader to signal EOF. +/// +/// # Safety +/// `sender` must be pointing to a valid instance of a `read_tx` created by the `IosTcpProvider` +/// +/// Callback to call when the TCP connection has received data. #[no_mangle] -pub unsafe extern "C" fn handle_recv(data: *const u8, data_len: usize, sender: *const c_void) { - let read_tx: Box>> = unsafe { Box::from_raw(sender as _) }; +pub unsafe extern "C" fn handle_recv(data: *const u8, mut data_len: usize, sender: *const c_void) { + let weak_tx: Weak>> = unsafe { Weak::from_raw(sender as _) }; + + if data.is_null() { + data_len = 0; + } let mut bytes = vec![0u8; data_len]; if !data.is_null() { std::ptr::copy_nonoverlapping(data, bytes.as_mut_ptr(), data_len); } - _ = read_tx.send(bytes.into_boxed_slice()); + if let Some(read_tx) = weak_tx.upgrade() { + _ = read_tx.send(bytes.into_boxed_slice()); + } } /// Entry point for exchanging post quantum keys on iOS. /// The TCP connection must be created to go through the tunnel. /// # Safety -/// This function is safe to call +/// `public_key` and `ephemeral_key` must be valid respective `PublicKey` and `PrivateKey` types. +/// They will not be valid after this function is called, and thus must be copied here. +/// `packet_tunnel` and `tcp_connection` must be valid pointers to a packet tunnel and a TCP connection +/// instances. +/// `cancel_token` should be owned by the caller of this function. #[no_mangle] pub unsafe extern "C" fn negotiate_post_quantum_key( public_key: *const u8, @@ -103,11 +122,13 @@ pub unsafe extern "C" fn negotiate_post_quantum_key( let pub_key_copy: [u8; 32] = unsafe { std::ptr::read(public_key as *const [u8; 32]) }; let eph_key_copy: [u8; 32] = unsafe { std::ptr::read(ephemeral_key as *const [u8; 32]) }; - match unsafe { run_ios_runtime(pub_key_copy, eph_key_copy, packet_tunnel, tcp_connection) } { + match unsafe { + run_post_quantum_psk_exchange(pub_key_copy, eph_key_copy, packet_tunnel, tcp_connection) + } { Ok(token) => { unsafe { std::ptr::write(cancel_token, token) }; 0 } - Err(err) => err, + Err(_) => -1, } } diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs index 04a81d924bd2..18ad9a49f87c 100644 --- a/talpid-tunnel-config-client/src/lib.rs +++ b/talpid-tunnel-config-client/src/lib.rs @@ -24,8 +24,6 @@ mod proto { #[cfg(target_os = "ios")] pub mod ios_ffi; -#[cfg(target_os = "ios")] -use proto::ephemeral_peer_client::EphemeralPeerClient; #[cfg(not(target_os = "ios"))] use libc::setsockopt; @@ -61,6 +59,8 @@ pub enum Error { FailedDecapsulateKyber(kyber::KyberError), #[cfg(target_os = "ios")] TcpConnectionExpired, + #[cfg(target_os = "ios")] + UnableToCreateRuntime, } impl std::fmt::Display for Error { @@ -84,6 +84,8 @@ impl std::fmt::Display for Error { FailedDecapsulateKyber(_) => "Failed to decapsulate Kyber1024 ciphertext".fmt(f), #[cfg(target_os = "ios")] TcpConnectionExpired => "TCP connection is already shut down".fmt(f), + #[cfg(target_os = "ios")] + UnableToCreateRuntime => "Unable to create iOS PQ PSK runtime".fmt(f), } } } @@ -119,24 +121,18 @@ pub struct EphemeralPeer { pub psk: Option, } -/// Negotiate a short-lived peer with a PQ-safe PSK or with DAITA enabled. -pub async fn request_ephemeral_peer( - #[cfg(not(target_os = "ios"))] service_address: IpAddr, +pub async fn request_ephemeral_peer_with( + mut client: RelayConfigService, parent_pubkey: PublicKey, ephemeral_pubkey: PublicKey, enable_post_quantum: bool, enable_daita: bool, - #[cfg(target_os = "ios")] mut client: EphemeralPeerClient, ) -> Result { let (pq_request, kem_secrets) = post_quantum_secrets(enable_post_quantum).await; - let daita = Some(proto::DaitaRequestV1 { activate_daita: enable_daita, }); - #[cfg(not(target_os = "ios"))] - let mut client = new_client(service_address).await?; - let response = client .register_peer_v1(proto::EphemeralPeerRequestV1 { wg_parent_pubkey: parent_pubkey.as_bytes().to_vec(), @@ -194,13 +190,34 @@ pub async fn request_ephemeral_peer( Ok(EphemeralPeer { psk }) } +/// Negotiate a short-lived peer with a PQ-safe PSK or with DAITA enabled. +#[cfg(not(target_os = "ios"))] +pub async fn request_ephemeral_peer( + service_address: IpAddr, + parent_pubkey: PublicKey, + ephemeral_pubkey: PublicKey, + enable_post_quantum: bool, + enable_daita: bool, +) -> Result { + let client = new_client(service_address).await?; + + request_ephemeral_peer_with( + client, + parent_pubkey, + ephemeral_pubkey, + enable_post_quantum, + enable_daita, + ) + .await +} + async fn post_quantum_secrets( enable_post_quantum: bool, ) -> ( Option, Option<(classic_mceliece_rust::SecretKey<'static>, [u8; 3168])>, ) { - let (pq_request, kem_secrets) = if enable_post_quantum { + if enable_post_quantum { let (cme_kem_pubkey, cme_kem_secret) = classic_mceliece::generate_keys().await; let kyber_keypair = kyber::keypair(&mut rand::thread_rng()); @@ -221,8 +238,7 @@ async fn post_quantum_secrets( ) } else { (None, None) - }; - (pq_request, kem_secrets) + } } /// Performs `dst = dst ^ src`.