From 72ccf7931ae22948cb85f147531aaf5528b448ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Em=C4=ABls?= Date: Tue, 4 Jun 2024 14:28:28 +0200 Subject: [PATCH 1/5] Add a timeout when exchanging post quantum keys --- talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs | 5 +++++ 1 file changed, 5 insertions(+) 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 37fdb0d16d17..2989b4fb4843 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs @@ -158,6 +158,11 @@ impl IOSRuntime { } } + _ = tokio::time::sleep(std::time::Duration::from_secs(5)) => { + unsafe { swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); } + shutdown_handle.shutdown() + } + _ = cancel_token_rx.recv() => { shutdown_handle.shutdown() // The swift runtime pre emptively cancelled the key exchange, nothing to do here. From 9a0d54bced736aab9394b804aad10c4e6fdc20f8 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 5 Jun 2024 17:10:04 +0200 Subject: [PATCH 2/5] Add a backing off timeout when negotiating PQ PSK --- .../PostQuantumKeyNegotiator.swift | 7 +- .../include/talpid_tunnel_config_client.h | 24 ++-- .../RetryStrategy/RetryStrategy.swift | 10 ++ .../PacketTunnelProvider.swift | 7 +- .../PostQuantumKeyExchangeActor.swift | 30 ++++- .../src/ios_ffi/ios_runtime.rs | 58 +++++----- .../src/ios_ffi/ios_tcp_connection.rs | 107 +++++++++++------- .../src/ios_ffi/mod.rs | 47 +++++--- 8 files changed, 186 insertions(+), 104 deletions(-) diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift index 16f7a24931ea..ebc494cf0fcb 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift @@ -7,6 +7,7 @@ // import Foundation +import MullvadTypes import NetworkExtension import TalpidTunnelConfigClientProxy import WireGuardKitTypes @@ -24,7 +25,8 @@ public class PostQuantumKeyNegotiator { devicePublicKey: PublicKey, presharedKey: PrivateKey, packetTunnel: NEPacketTunnelProvider, - tcpConnection: NWTCPConnection + tcpConnection: NWTCPConnection, + postQuantumKeyExchangeTimeout: Duration ) -> Bool { let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque() let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque() @@ -35,7 +37,8 @@ public class PostQuantumKeyNegotiator { presharedKey.rawValue.map { $0 }, packetTunnelPointer, opaqueConnection, - &cancelToken + &cancelToken, + UInt64(postQuantumKeyExchangeTimeout.timeInterval) ) guard result == 0 else { return false 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 04180db289d4..31d31748ae97 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 @@ -16,15 +16,18 @@ typedef struct 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`. + * `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); /** - * Called by the Swift side to signal that the Rust `PostQuantumCancelToken` can be safely dropped from memory. + * 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`. + * `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); @@ -44,17 +47,15 @@ void handle_sent(uintptr_t bytes_sent, const void *sender); * 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. + * 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, - const void *sender); +void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender); /** * Entry point for exchanging post quantum keys on iOS. @@ -62,15 +63,16 @@ void handle_recv(const uint8_t *data, * # Safety * `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. + * `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, const void *packet_tunnel, const void *tcp_connection, - struct PostQuantumCancelToken *cancel_token); + struct PostQuantumCancelToken *cancel_token, + uint64_t post_quantum_key_exchange_timeout); /** * Called when there is data to send on the TCP connection. diff --git a/ios/MullvadREST/RetryStrategy/RetryStrategy.swift b/ios/MullvadREST/RetryStrategy/RetryStrategy.swift index 18e3cd69f304..82e31abd2b31 100644 --- a/ios/MullvadREST/RetryStrategy/RetryStrategy.swift +++ b/ios/MullvadREST/RetryStrategy/RetryStrategy.swift @@ -68,6 +68,16 @@ extension REST { multiplier: 2, maxDelay: .seconds(8) ) + + public static var postQuantumKeyExchange = RetryStrategy( + maxRetryCount: 10, + delay: .exponentialBackoff( + initial: .seconds(10), + multiplier: UInt64(2), + maxDelay: .seconds(30) + ), + applyJitter: true + ) } public enum RetryDelay: Equatable { diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index aae7677d9a96..ab464a22744e 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -35,7 +35,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { override init() { Self.configureLogging() - providerLogger = Logger(label: "PacketTunnelProvider") let containerURL = ApplicationConfiguration.containerURL @@ -46,7 +45,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { relayCache: RelayCache(cacheDirectory: containerURL), ipOverrideRepository: IPOverrideRepository() ) - multihopUpdater = MultihopUpdater(listener: multihopStateListener) super.init() @@ -100,7 +98,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { ) let urlRequestProxy = URLRequestProxy(dispatchQueue: internalQueue, transportProvider: transportProvider) - appMessageHandler = AppMessageHandler(packetTunnelActor: actor, urlRequestProxy: urlRequestProxy) } @@ -264,7 +261,6 @@ extension PacketTunnelProvider { lastConnectionAttempt = connectionAttempt case let .negotiatingPostQuantumKey(_, privateKey): - postQuantumActor.endCurrentNegotiation() postQuantumActor.startNegotiation(with: privateKey) case .initial, .connected, .disconnecting, .disconnected, .error: @@ -311,12 +307,11 @@ extension PacketTunnelProvider { extension PacketTunnelProvider: PostQuantumKeyReceiving { func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) { + postQuantumActor.reset() actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) - postQuantumActor.endCurrentNegotiation() } func keyExchangeFailed() { - 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, reconnectReason: .connectionLoss) diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index 33291d8ea591..2e52eefce3f3 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -8,6 +8,8 @@ import Foundation import MullvadPostQuantum +import MullvadREST +import MullvadTypes import NetworkExtension import WireGuardKitTypes @@ -27,11 +29,15 @@ class PostQuantumKeyExchangeActor { unowned let packetTunnel: PacketTunnelProvider private var negotiation: Negotiation? private var timer: DispatchSourceTimer? + private var keyExchangeRetriesIterator = REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() // Callback in the event of the negotiation failing on startup var onFailure: () -> Void - init(packetTunnel: PacketTunnelProvider, onFailure: @escaping (() -> Void)) { + init( + packetTunnel: PacketTunnelProvider, + onFailure: @escaping (() -> Void) + ) { self.packetTunnel = packetTunnel self.onFailure = onFailure } @@ -45,7 +51,16 @@ class PostQuantumKeyExchangeActor { ) } + /// 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 func startNegotiation(with privateKey: PrivateKey) { + endCurrentNegotiation() let negotiator = PostQuantumKeyNegotiator() let gatewayAddress = "10.64.0.1" @@ -56,8 +71,9 @@ class PostQuantumKeyExchangeActor { // 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() + 10) + scheduleInTunnelConnectionTimeout(startTime: .now() + tcpConnectionTimeout) let tcpConnectionObserver = inTunnelTCPConnection.observe(\.isViable, options: [ .initial, @@ -72,7 +88,8 @@ class PostQuantumKeyExchangeActor { devicePublicKey: privateKey.publicKey, presharedKey: ephemeralSharedKey, packetTunnel: packetTunnel, - tcpConnection: inTunnelTCPConnection + tcpConnection: inTunnelTCPConnection, + postQuantumKeyExchangeTimeout: tcpConnectionTimeout ) { self.negotiation = nil self.onFailure() @@ -85,11 +102,18 @@ class PostQuantumKeyExchangeActor { ) } + /// Cancels the ongoing key exchange. func endCurrentNegotiation() { negotiation?.cancel() negotiation = nil } + /// Resets the exponential timeout for successful key exchanges, and ends the current key exchange. + func reset() { + keyExchangeRetriesIterator = REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() + endCurrentNegotiation() + } + private func scheduleInTunnelConnectionTimeout(startTime: DispatchWallTime) { let newTimer = DispatchSource.makeTimerSource() 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 2989b4fb4843..ba93ae2ecec6 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs @@ -1,9 +1,17 @@ use super::{ios_tcp_connection::*, PostQuantumCancelToken}; use crate::{request_ephemeral_peer_with, Error, RelayConfigService}; use libc::c_void; -use std::{future::Future, io, pin::Pin, ptr, sync::Arc}; +use std::{ + future::Future, + io, + pin::Pin, + ptr, + sync::{Arc, Mutex}, +}; use talpid_types::net::wireguard::{PrivateKey, PublicKey}; -use tokio::{runtime::Builder, sync::mpsc}; +use tokio::{ + runtime::Builder, +}; use tonic::transport::channel::Endpoint; use tower::util::service_fn; @@ -15,10 +23,19 @@ pub unsafe fn run_post_quantum_psk_exchange( ephemeral_key: [u8; 32], packet_tunnel: *const c_void, tcp_connection: *const c_void, + post_quantum_key_exchange_timeout: u64, ) -> Result { - match unsafe { IOSRuntime::new(pub_key, ephemeral_key, packet_tunnel, tcp_connection) } { + match unsafe { + IOSRuntime::new( + pub_key, + ephemeral_key, + packet_tunnel, + tcp_connection, + post_quantum_key_exchange_timeout, + ) + } { Ok(runtime) => { - let token = runtime.cancel_token_tx.clone(); + let token = runtime.packet_tunnel.tcp_connection.clone(); runtime.run(); Ok(PostQuantumCancelToken { @@ -35,7 +52,7 @@ pub unsafe fn run_post_quantum_psk_exchange( #[derive(Clone)] pub struct SwiftContext { pub packet_tunnel: *const c_void, - pub tcp_connection: *const c_void, + pub tcp_connection: Arc>, } unsafe impl Send for SwiftContext {} @@ -46,8 +63,7 @@ struct IOSRuntime { pub_key: [u8; 32], ephemeral_key: [u8; 32], packet_tunnel: SwiftContext, - cancel_token_tx: Arc>, - cancel_token_rx: mpsc::UnboundedReceiver<()>, + post_quantum_key_exchange_timeout: u64, } impl IOSRuntime { @@ -56,6 +72,7 @@ impl IOSRuntime { ephemeral_key: [u8; 32], packet_tunnel: *const libc::c_void, tcp_connection: *const c_void, + post_quantum_key_exchange_timeout: u64, ) -> io::Result { let runtime = Builder::new_multi_thread() .enable_all() @@ -64,18 +81,15 @@ impl IOSRuntime { let context = SwiftContext { packet_tunnel, - tcp_connection, + tcp_connection: Arc::new(Mutex::new(ConnectionContext::new(tcp_connection))), }; - let (tx, rx) = mpsc::unbounded_channel(); - Ok(Self { runtime, pub_key, ephemeral_key, packet_tunnel: context, - cancel_token_tx: Arc::new(tx), - cancel_token_rx: rx, + post_quantum_key_exchange_timeout, }) } @@ -84,9 +98,8 @@ impl IOSRuntime { self.run_service_inner(); }); } - - /// Creates a `RelayConfigService` using the in-tunnel TCP Connection provided by the Packet Tunnel Provider - /// # Safety + /// 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, @@ -112,11 +125,7 @@ impl IOSRuntime { } fn run_service_inner(self) { - let Self { - runtime, - mut cancel_token_rx, - .. - } = self; + let Self { runtime, .. } = self; let packet_tunnel_ptr = self.packet_tunnel.packet_tunnel; runtime.block_on(async move { @@ -158,14 +167,9 @@ impl IOSRuntime { } } - _ = tokio::time::sleep(std::time::Duration::from_secs(5)) => { + _ = tokio::time::sleep(std::time::Duration::from_secs(self.post_quantum_key_exchange_timeout)) => { + shutdown_handle.shutdown(); unsafe { swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); } - shutdown_handle.shutdown() - } - - _ = cancel_token_rx.recv() => { - shutdown_handle.shutdown() - // The swift runtime pre emptively cancelled the key exchange, nothing to do here. } } }); 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 86a3114edff3..656bb142c85e 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 @@ -1,10 +1,7 @@ use libc::c_void; use std::{ io::{self, Result}, - sync::{ - atomic::{self, AtomicBool}, - Arc, Mutex, Weak, - }, + sync::{Arc, Mutex, MutexGuard, Weak}, task::{Poll, Waker}, }; use tokio::{ @@ -49,28 +46,29 @@ pub struct IosTcpProvider { write_rx: mpsc::UnboundedReceiver, read_tx: Arc>>, read_rx: mpsc::UnboundedReceiver>, - tcp_connection: *const c_void, + tcp_connection: Arc>, read_in_progress: bool, write_in_progress: bool, - shutdown: Arc, - waker: Arc>>, } pub struct IosTcpShutdownHandle { - shutdown: Arc, - waker: Arc>>, + context: Arc>, } +pub struct ConnectionContext { + waker: Option, + tcp_connection: Option<*const c_void>, +} + +unsafe impl Send for ConnectionContext {} + impl IosTcpProvider { - /** - * # Safety - * `tcp_connection` must be pointing to a valid instance of a `NWTCPConnection`, created by the `PacketTunnelProvider` - */ - pub unsafe fn new(tcp_connection: *const c_void) -> (Self, IosTcpShutdownHandle) { + /// # Safety + /// `tcp_connection` must be pointing to a valid instance of a `NWTCPConnection`, created by the + /// `PacketTunnelProvider` + pub unsafe fn new(connection: Arc>) -> (Self, IosTcpShutdownHandle) { 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 { @@ -78,32 +76,33 @@ impl IosTcpProvider { write_rx: rx, read_tx: Arc::new(recv_tx), read_rx: recv_rx, - tcp_connection, + tcp_connection: connection.clone(), read_in_progress: false, write_in_progress: false, - shutdown: shutdown.clone(), - waker: waker.clone(), }, - IosTcpShutdownHandle { shutdown, waker }, + IosTcpShutdownHandle { + context: connection, + }, ) } - 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); - } + fn maybe_set_waker(new_waker: Waker, connection: &mut MutexGuard<'_, ConnectionContext>) { + connection.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(); + pub fn shutdown(self) { + { + let Ok(mut context) = self.context.lock() else { + return; + }; + + context.tcp_connection = None; + if let Some(waker) = context.waker.take() { + waker.wake(); + } + std::mem::drop(context); } } } @@ -114,7 +113,15 @@ impl AsyncWrite for IosTcpProvider { cx: &mut std::task::Context<'_>, buf: &[u8], ) -> std::task::Poll> { - self.maybe_set_waker(cx.waker().clone()); + let connection_lock = self.tcp_connection.clone(); + let Ok(mut connection) = connection_lock.lock() else { + return Poll::Ready(Err(connection_closed_err())); + }; + let Some(tcp_ptr) = connection.tcp_connection else { + return Poll::Ready(Err(connection_closed_err())); + }; + Self::maybe_set_waker(cx.waker().clone(), &mut connection); + match self.write_rx.poll_recv(cx) { std::task::Poll::Ready(Some(bytes_sent)) => { self.write_in_progress = false; @@ -125,14 +132,12 @@ impl AsyncWrite for IosTcpProvider { Poll::Ready(Err(connection_closed_err())) } std::task::Poll::Pending => { - if self.is_shutdown() { - return Poll::Ready(Err(connection_closed_err())); - } 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, + // self.tcp_connection, + tcp_ptr, buf.as_ptr() as _, buf.len(), raw_sender as _, @@ -165,9 +170,14 @@ impl AsyncRead for IosTcpProvider { cx: &mut std::task::Context<'_>, buf: &mut tokio::io::ReadBuf<'_>, ) -> std::task::Poll> { - if self.is_shutdown() { + let connection_lock = self.tcp_connection.clone(); + let Ok(mut connection) = connection_lock.lock() else { return Poll::Ready(Err(connection_closed_err())); - } + }; + let Some(tcp_ptr) = connection.tcp_connection else { + return Poll::Ready(Err(connection_closed_err())); + }; + Self::maybe_set_waker(cx.waker().clone(), &mut connection); match self.read_rx.poll_recv(cx) { std::task::Poll::Ready(Some(data)) => { @@ -183,7 +193,8 @@ impl AsyncRead for IosTcpProvider { 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 _); + // TODO + swift_nw_tcp_connection_read(tcp_ptr, raw_sender as _); } self.read_in_progress = true; } @@ -192,3 +203,19 @@ impl AsyncRead for IosTcpProvider { } } } + +impl ConnectionContext { + pub fn new(tcp_connection: *const c_void) -> Self { + Self { + tcp_connection: Some(tcp_connection), + waker: None, + } + } + + pub fn shutdown(&mut self) { + self.tcp_connection = None; + if let Some(waker) = self.waker.take() { + waker.wake(); + } + } +} diff --git a/talpid-tunnel-config-client/src/ios_ffi/mod.rs b/talpid-tunnel-config-client/src/ios_ffi/mod.rs index 94dcf95b6f89..704332a7bf6b 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/mod.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/mod.rs @@ -2,8 +2,9 @@ pub mod ios_runtime; pub mod ios_tcp_connection; use crate::ios_ffi::ios_runtime::run_post_quantum_psk_exchange; +use ios_tcp_connection::ConnectionContext; use libc::c_void; -use std::sync::{Arc, Weak}; +use std::sync::{Arc, Mutex, Weak}; use tokio::sync::mpsc; use std::sync::Once; @@ -20,34 +21,43 @@ impl PostQuantumCancelToken { /// 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(()); + // 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 connection_context: Arc> = + unsafe { Arc::from_raw(self.context as _) }; + if let Ok(mut connection) = connection_context.lock() { + connection.shutdown(); + } + // Call std::mem::forget here to avoid dropping the channel. - std::mem::forget(send_tx); + std::mem::forget(connection_context); } } impl Drop for PostQuantumCancelToken { fn drop(&mut self) { - let _: Arc> = unsafe { Arc::from_raw(self.context as _) }; + let _: Arc> = unsafe { Arc::from_raw(self.context as _) }; } } + 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`. +/// `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the +/// `PacketTunnelProvider`. #[no_mangle] pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const PostQuantumCancelToken) { let sender = unsafe { &*sender }; sender.cancel(); } -/// Called by the Swift side to signal that the Rust `PostQuantumCancelToken` can be safely dropped from memory. +/// 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`. +/// `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, @@ -74,8 +84,8 @@ pub unsafe extern "C" fn handle_sent(bytes_sent: usize, sender: *const c_void) { /// 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. +/// 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` @@ -102,8 +112,8 @@ pub unsafe extern "C" fn handle_recv(data: *const u8, mut data_len: usize, sende /// # Safety /// `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. +/// `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( @@ -112,10 +122,11 @@ pub unsafe extern "C" fn negotiate_post_quantum_key( packet_tunnel: *const c_void, tcp_connection: *const c_void, cancel_token: *mut PostQuantumCancelToken, + post_quantum_key_exchange_timeout: u64, ) -> i32 { INIT_LOGGING.call_once(|| { let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.TTCC") - .level_filter(log::LevelFilter::Trace) + .level_filter(log::LevelFilter::Debug) .init(); }); @@ -123,7 +134,13 @@ pub unsafe extern "C" fn negotiate_post_quantum_key( let eph_key_copy: [u8; 32] = unsafe { std::ptr::read(ephemeral_key as *const [u8; 32]) }; match unsafe { - run_post_quantum_psk_exchange(pub_key_copy, eph_key_copy, packet_tunnel, tcp_connection) + run_post_quantum_psk_exchange( + pub_key_copy, + eph_key_copy, + packet_tunnel, + tcp_connection, + post_quantum_key_exchange_timeout, + ) } { Ok(token) => { unsafe { std::ptr::write(cancel_token, token) }; From fc016433b51103badb74d1743c6ec61175b4aaef Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 12 Jun 2024 13:05:37 +0200 Subject: [PATCH 3/5] Add tests --- .../PostQuantumKeyNegotiator.swift | 28 +- .../MullvadPostQuantum+Stubs.swift | 95 +++++++ .../PostQuantumKeyExchangeActorTests.swift | 91 +++++++ ios/MullvadVPN.xcodeproj/project.pbxproj | 241 +++++++++++++++++- .../MullvadPostQuantumTests.xcscheme | 54 ++++ .../xcschemes/MullvadVPN.xcscheme | 14 + .../PostQuantumKeyExchangeActor.swift | 32 ++- .../NEPacketTunnelProvider+Extensions.swift | 21 ++ ios/TestPlans/MullvadVPNApp.xctestplan | 7 + ios/TestPlans/MullvadVPNCI.xctestplan | 7 + .../src/ios_ffi/ios_runtime.rs | 8 +- 11 files changed, 568 insertions(+), 30 deletions(-) create mode 100644 ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift create mode 100644 ios/MullvadPostQuantumTests/PostQuantumKeyExchangeActorTests.swift create mode 100644 ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantumTests.xcscheme create mode 100644 ios/PacketTunnelCore/NEPacketTunnelProvider+Extensions.swift diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift index ebc494cf0fcb..4765ba76802c 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift @@ -9,14 +9,31 @@ import Foundation import MullvadTypes import NetworkExtension +import PacketTunnelCore import TalpidTunnelConfigClientProxy import WireGuardKitTypes +// swiftlint:disable function_parameter_count +public protocol PostQuantumKeyNegotiation { + func startNegotiation( + gatewayIP: IPv4Address, + devicePublicKey: PublicKey, + presharedKey: PrivateKey, + packetTunnel: any TunnelProvider, + tcpConnection: NWTCPConnection, + postQuantumKeyExchangeTimeout: Duration + ) -> Bool + + func cancelKeyNegotiation() + + init() +} + /** Attempt to start the asynchronous process of key negotiation. Returns true if successfully started, false if failed. */ -public class PostQuantumKeyNegotiator { - public init() {} +public class PostQuantumKeyNegotiator: PostQuantumKeyNegotiation { + required public init() {} var cancelToken: PostQuantumCancelToken? @@ -24,11 +41,12 @@ public class PostQuantumKeyNegotiator { gatewayIP: IPv4Address, devicePublicKey: PublicKey, presharedKey: PrivateKey, - packetTunnel: NEPacketTunnelProvider, + packetTunnel: any TunnelProvider, tcpConnection: NWTCPConnection, postQuantumKeyExchangeTimeout: Duration ) -> Bool { - let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque() + // swiftlint:disable:next force_cast + let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel as! NEPacketTunnelProvider).toOpaque() let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque() var cancelToken = PostQuantumCancelToken() @@ -57,3 +75,5 @@ public class PostQuantumKeyNegotiator { drop_post_quantum_key_exchange_token(&cancelToken) } } + +// swiftlint:enable function_parameter_count diff --git a/ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift b/ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift new file mode 100644 index 000000000000..8d4e2f30e31b --- /dev/null +++ b/ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift @@ -0,0 +1,95 @@ +// +// MullvadPostQuantum+Stubs.swift +// MullvadPostQuantumTests +// +// Created by Marco Nikic on 2024-06-12. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +@testable import MullvadPostQuantum +@testable import MullvadTypes +import NetworkExtension +@testable import PacketTunnelCore +@testable import WireGuardKitTypes + +// swiftlint:disable function_parameter_count +class NWTCPConnectionStub: NWTCPConnection { + var _isViable = false + override var isViable: Bool { + _isViable + } + + func becomeViable() { + willChangeValue(for: \.isViable) + _isViable = true + didChangeValue(for: \.isViable) + } +} + +class TunnelProviderStub: TunnelProvider { + let tcpConnection: NWTCPConnectionStub + + init(tcpConnection: NWTCPConnectionStub) { + self.tcpConnection = tcpConnection + } + + func createTCPConnectionThroughTunnel( + to remoteEndpoint: NWEndpoint, + enableTLS: Bool, + tlsParameters TLSParameters: NWTLSParameters?, + delegate: Any? + ) -> NWTCPConnection { + tcpConnection + } +} + +class FailedNegotiatorStub: PostQuantumKeyNegotiation { + var onCancelKeyNegotiation: (() -> Void)? + + required init() { + onCancelKeyNegotiation = nil + } + + init(onCancelKeyNegotiation: (() -> Void)? = nil) { + self.onCancelKeyNegotiation = onCancelKeyNegotiation + } + + func startNegotiation( + gatewayIP: IPv4Address, + devicePublicKey: WireGuardKitTypes.PublicKey, + presharedKey: WireGuardKitTypes.PrivateKey, + packetTunnel: PacketTunnelCore.TunnelProvider, + tcpConnection: NWTCPConnection, + postQuantumKeyExchangeTimeout: MullvadTypes.Duration + ) -> Bool { false } + + func cancelKeyNegotiation() { + onCancelKeyNegotiation?() + } +} + +class SuccessfulNegotiatorStub: PostQuantumKeyNegotiation { + var onCancelKeyNegotiation: (() -> Void)? + required init() { + onCancelKeyNegotiation = nil + } + + init(onCancelKeyNegotiation: (() -> Void)? = nil) { + self.onCancelKeyNegotiation = onCancelKeyNegotiation + } + + func startNegotiation( + gatewayIP: IPv4Address, + devicePublicKey: WireGuardKitTypes.PublicKey, + presharedKey: WireGuardKitTypes.PrivateKey, + packetTunnel: PacketTunnelCore.TunnelProvider, + tcpConnection: NWTCPConnection, + postQuantumKeyExchangeTimeout: MullvadTypes.Duration + ) -> Bool { true } + + func cancelKeyNegotiation() { + onCancelKeyNegotiation?() + } +} + +// swiftlint:enable function_parameter_count diff --git a/ios/MullvadPostQuantumTests/PostQuantumKeyExchangeActorTests.swift b/ios/MullvadPostQuantumTests/PostQuantumKeyExchangeActorTests.swift new file mode 100644 index 000000000000..ca485a0c198e --- /dev/null +++ b/ios/MullvadPostQuantumTests/PostQuantumKeyExchangeActorTests.swift @@ -0,0 +1,91 @@ +// +// PostQuantumKeyExchangeActorTests.swift +// PostQuantumKeyExchangeActorTests +// +// Created by Marco Nikic on 2024-06-12. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +@testable import MullvadPostQuantum +@testable import MullvadTypes +import NetworkExtension +@testable import PacketTunnelCore +@testable import WireGuardKitTypes +import XCTest + +class MullvadPostQuantumTests: XCTestCase { + var tcpConnection: NWTCPConnectionStub! + var tunnelProvider: TunnelProviderStub! + + override func setUpWithError() throws { + tcpConnection = NWTCPConnectionStub() + tunnelProvider = TunnelProviderStub(tcpConnection: tcpConnection) + } + + func testKeyExchangeFailsWhenNegotiationCannotStart() { + let negotiationFailure = expectation(description: "Negotiation failed") + + let keyExchangeActor = PostQuantumKeyExchangeActor( + packetTunnel: tunnelProvider, + onFailure: { + negotiationFailure.fulfill() + }, + negotiationProvider: FailedNegotiatorStub.self + ) + + let privateKey = PrivateKey() + keyExchangeActor.startNegotiation(with: privateKey) + tcpConnection.becomeViable() + + wait(for: [negotiationFailure]) + } + + func testKeyExchangeFailsWhenTCPConnectionIsNotReadyInTime() { + let negotiationFailure = expectation(description: "Negotiation failed") + + // Setup the actor to wait only 10 milliseconds before declaring the TCP connection is not ready in time. + let keyExchangeActor = PostQuantumKeyExchangeActor( + packetTunnel: tunnelProvider, + onFailure: { + negotiationFailure.fulfill() + }, + negotiationProvider: FailedNegotiatorStub.self, + keyExchangeRetriesIterator: AnyIterator { .milliseconds(10) } + ) + + let privateKey = PrivateKey() + keyExchangeActor.startNegotiation(with: privateKey) + + wait(for: [negotiationFailure]) + } + + func testResetEndsTheCurrentNegotiation() throws { + let unexpectedNegotiationFailure = expectation(description: "Unexpected negotiation failure") + unexpectedNegotiationFailure.isInverted = true + + let keyExchangeActor = PostQuantumKeyExchangeActor( + packetTunnel: tunnelProvider, + onFailure: { + unexpectedNegotiationFailure.fulfill() + }, + negotiationProvider: SuccessfulNegotiatorStub.self + ) + + let privateKey = PrivateKey() + keyExchangeActor.startNegotiation(with: privateKey) + + let negotiationProvider = try XCTUnwrap( + keyExchangeActor.negotiation? + .negotiator as? SuccessfulNegotiatorStub + ) + + let negotationCancelledExpectation = expectation(description: "Negotiation cancelled") + negotiationProvider.onCancelKeyNegotiation = { + negotationCancelledExpectation.fulfill() + } + + keyExchangeActor.reset() + + wait(for: [negotationCancelledExpectation, unexpectedNegotiationFailure], timeout: 0.5) + } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 60b89d512da5..faa02954e7ec 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -699,9 +699,10 @@ A944F2632B8DEFDB00473F4C /* MullvadPostQuantum.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiator.swift */; }; A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client.a */; }; - A948809B2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */; }; A94D691A2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E22AA72AE9003D1918 /* WireGuardKitTypes */; }; A94D691B2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E72AA7399D003D1918 /* WireGuardKitTypes */; }; + A959FC4E2C19CF70009733CD /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = A959FC4D2C19CF70009733CD /* WireGuardKitTypes */; }; + A959FC502C19CF70009733CD /* WireGuardKitTypes in Embed Frameworks */ = {isa = PBXBuildFile; productRef = A959FC4D2C19CF70009733CD /* WireGuardKitTypes */; }; A95EEE362B722CD600A8A39B /* TunnelMonitorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95EEE352B722CD600A8A39B /* TunnelMonitorState.swift */; }; A95EEE382B722DFC00A8A39B /* PingStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95EEE372B722DFC00A8A39B /* PingStats.swift */; }; A9630E3C2B8E0E7B00A65999 /* talpid_tunnel_config_client.h in Headers */ = {isa = PBXBuildFile; fileRef = A9630E3B2B8E0E7B00A65999 /* talpid_tunnel_config_client.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -719,6 +720,10 @@ A988A3E22AFE54AC0008D2C7 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; }; A988DF272ADE86ED00D807EF /* WireGuardObfuscationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */; }; A988DF2A2ADE880300D807EF /* TunnelSettingsV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */; }; + A98F1B452C19BB83003C869E /* NEPacketTunnelProvider+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98F1B442C19BB83003C869E /* NEPacketTunnelProvider+Extensions.swift */; }; + A98F1B512C19C48D003C869E /* PostQuantumKeyExchangeActorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98F1B502C19C48D003C869E /* PostQuantumKeyExchangeActorTests.swift */; }; + A98F1B522C19C48D003C869E /* MullvadPostQuantum.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; platformFilter = ios; }; + A98F1B5A2C19C4C4003C869E /* PostQuantumKeyExchangeActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */; }; A998DA812BD147AD001D61A2 /* ListCustomListsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */; }; A998DA832BD2B055001D61A2 /* EditCustomListLocationsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A998DA822BD2B055001D61A2 /* EditCustomListLocationsPage.swift */; }; A99E5EE02B7628150033F241 /* ProblemReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99E5EDF2B7628150033F241 /* ProblemReportViewModel.swift */; }; @@ -827,6 +832,7 @@ A9BA08322BA32FB6005A7A2D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A92962582B1F4FDB00DFB93B /* PrivacyInfo.xcprivacy */; }; A9BFAFFF2BD004ED00F2BCA1 /* CustomListsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */; }; A9BFB0012BD00B7F00F2BCA1 /* CustomListPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */; }; + A9C3083A2C19DDA7008715F1 /* MullvadPostQuantum+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C308392C19DDA7008715F1 /* MullvadPostQuantum+Stubs.swift */; }; A9C342C12ACC37E30045F00E /* TunnelStatusBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */; }; A9C342C32ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */; }; A9D7E43C2BFCE43200213D55 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = A906F9492BA1E09A002BF22E /* WireGuardKitTypes */; }; @@ -1232,6 +1238,20 @@ remoteGlobalIDString = 58D223D4294C8E5E0029F5F8; remoteInfo = MullvadTypes; }; + A98F1B462C19BCFF003C869E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 58CE5E58224146200008646E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 58C7A4352A863F440060C66F; + remoteInfo = PacketTunnelCore; + }; + A98F1B532C19C48D003C869E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 58CE5E58224146200008646E /* Project object */; + proxyType = 1; + remoteGlobalIDString = A944F25B2B8DEFDB00473F4C; + remoteInfo = MullvadPostQuantum; + }; A9EC20F12A5D79ED0040D56E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 58CE5E58224146200008646E /* Project object */; @@ -1350,6 +1370,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + A959FC4F2C19CF70009733CD /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + A959FC502C19CF70009733CD /* WireGuardKitTypes in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; F0ACE3292BE4E712006D5333 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -2063,6 +2094,9 @@ A98502022B627B120061901E /* LocalNetworkProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkProbe.swift; sourceTree = ""; }; A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardObfuscationSettings.swift; sourceTree = ""; }; A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV3.swift; sourceTree = ""; }; + A98F1B442C19BB83003C869E /* NEPacketTunnelProvider+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEPacketTunnelProvider+Extensions.swift"; sourceTree = ""; }; + A98F1B4E2C19C48D003C869E /* MullvadPostQuantumTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadPostQuantumTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A98F1B502C19C48D003C869E /* PostQuantumKeyExchangeActorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangeActorTests.swift; sourceTree = ""; }; A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCustomListsPage.swift; sourceTree = ""; }; A998DA822BD2B055001D61A2 /* EditCustomListLocationsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomListLocationsPage.swift; sourceTree = ""; }; A99E5EDF2B7628150033F241 /* ProblemReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportViewModel.swift; sourceTree = ""; }; @@ -2075,6 +2109,7 @@ A9B6AC192ADE8FBB00F7802A /* InMemorySettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemorySettingsStore.swift; sourceTree = ""; }; A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsTests.swift; sourceTree = ""; }; A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListPage.swift; sourceTree = ""; }; + A9C308392C19DDA7008715F1 /* MullvadPostQuantum+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MullvadPostQuantum+Stubs.swift"; sourceTree = ""; }; A9C342C22ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelayCacheTracker+Stubs.swift"; sourceTree = ""; }; A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = ""; }; A9D96B192A8247C100A5C673 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; @@ -2357,6 +2392,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A98F1B4B2C19C48D003C869E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A959FC4E2C19CF70009733CD /* WireGuardKitTypes in Frameworks */, + A98F1B522C19C48D003C869E /* MullvadPostQuantum.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F0ACE3052BE4E478006D5333 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -3383,6 +3427,7 @@ 5864AF802A9F52E3008BC928 /* Actor */, 58C7A42E2A85091B0060C66F /* Pinger */, 58E072A228814B96008902F8 /* TunnelMonitor */, + A98F1B442C19BB83003C869E /* NEPacketTunnelProvider+Extensions.swift */, ); path = PacketTunnelCore; sourceTree = ""; @@ -3482,6 +3527,7 @@ 584023202A406BF5007B27AC /* TunnelObfuscation */, 58695A9E2A4ADA9200328DB3 /* TunnelObfuscationTests */, A944F25D2B8DEFDB00473F4C /* MullvadPostQuantum */, + A98F1B4F2C19C48D003C869E /* MullvadPostQuantumTests */, 58CE5E61224146200008646E /* Products */, 584F991F2902CBDD001F858D /* Frameworks */, ); @@ -3509,6 +3555,7 @@ 852969252B4D9C1F007EAD4C /* MullvadVPNUITests.xctest */, F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */, A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */, + A98F1B4E2C19C48D003C869E /* MullvadPostQuantumTests.xctest */, ); name = Products; sourceTree = ""; @@ -3913,7 +3960,6 @@ 852969262B4D9C1F007EAD4C /* MullvadVPNUITests */ = { isa = PBXGroup; children = ( - 8518F6392B601910009EB113 /* Base */, 85557B0C2B591B0F00795FE1 /* Networking */, 852969312B4E9220007EAD4C /* Pages */, 7A45CFCD2C08697100D80B21 /* Screenshots */, @@ -3926,7 +3972,7 @@ 850201DA2B503D7700EF8C96 /* RelayTests.swift */, 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */, 85C7A2E82B89024B00035D5A /* SettingsTests.swift */, - 8518F6392B601910009EB113 /* Test base classes */, + 8518F6392B601910009EB113 /* Base */, 856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */, 85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */, ); @@ -3938,15 +3984,12 @@ children = ( 85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */, 85557B1F2B5FBBD700795FE1 /* AccountPage.swift */, - 852D054E2BC43DF7008578D2 /* AddAccessMethodPage.swift */, 7ACD79382C0DAADC00DBEE14 /* AddCustomListLocationsPage.swift */, 8529693B2B4F0257007EAD4C /* Alert.swift */, - 852D054C2BC3DE3A008578D2 /* APIAccessPage.swift */, 8587A05C2B84D43100152938 /* ChangeLogAlert.swift */, A9BFB0002BD00B7F00F2BCA1 /* CustomListPage.swift */, 85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */, 852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */, - 8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */, A998DA822BD2B055001D61A2 /* EditCustomListLocationsPage.swift */, 85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */, A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */, @@ -3957,7 +4000,6 @@ 855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */, 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */, 8556EB552B9B0AC500D26DD4 /* RevokedDevicePage.swift */, - 8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */, 850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */, 850201E22B51A93C00EF8C96 /* SettingsPage.swift */, 852969392B4F0238007EAD4C /* TermsOfServicePage.swift */, @@ -4024,6 +4066,15 @@ path = MullvadPostQuantum; sourceTree = ""; }; + A98F1B4F2C19C48D003C869E /* MullvadPostQuantumTests */ = { + isa = PBXGroup; + children = ( + A98F1B502C19C48D003C869E /* PostQuantumKeyExchangeActorTests.swift */, + A9C308392C19DDA7008715F1 /* MullvadPostQuantum+Stubs.swift */, + ); + path = MullvadPostQuantumTests; + sourceTree = ""; + }; F028A5472A336E1900C0CAA3 /* RedeemVoucher */ = { isa = PBXGroup; children = ( @@ -4746,6 +4797,7 @@ buildRules = ( ); dependencies = ( + A98F1B472C19BCFF003C869E /* PBXTargetDependency */, A9630E422B8E10F700A65999 /* PBXTargetDependency */, ); name = MullvadPostQuantum; @@ -4756,6 +4808,28 @@ productReference = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; productType = "com.apple.product-type.framework"; }; + A98F1B4D2C19C48D003C869E /* MullvadPostQuantumTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A98F1B552C19C48D003C869E /* Build configuration list for PBXNativeTarget "MullvadPostQuantumTests" */; + buildPhases = ( + A98F1B4A2C19C48D003C869E /* Sources */, + A98F1B4B2C19C48D003C869E /* Frameworks */, + A98F1B4C2C19C48D003C869E /* Resources */, + A959FC4F2C19CF70009733CD /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + A98F1B542C19C48D003C869E /* PBXTargetDependency */, + ); + name = MullvadPostQuantumTests; + packageProductDependencies = ( + A959FC4D2C19CF70009733CD /* WireGuardKitTypes */, + ); + productName = MullvadPostQuantumTests; + productReference = A98F1B4E2C19C48D003C869E /* MullvadPostQuantumTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; F0ACE3072BE4E478006D5333 /* MullvadMockData */ = { isa = PBXNativeTarget; buildConfigurationList = F0ACE3162BE4E479006D5333 /* Build configuration list for PBXNativeTarget "MullvadMockData" */; @@ -4785,7 +4859,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1510; + LastSwiftUpdateCheck = 1520; LastUpgradeCheck = 1430; ORGANIZATIONNAME = "Mullvad VPN AB"; TargetAttributes = { @@ -4863,6 +4937,9 @@ A944F25B2B8DEFDB00473F4C = { CreatedOnToolsVersion = 15.2; }; + A98F1B4D2C19C48D003C869E = { + CreatedOnToolsVersion = 15.2; + }; F0ACE3072BE4E478006D5333 = { CreatedOnToolsVersion = 15.2; }; @@ -4905,6 +4982,7 @@ 7A88DCD62A8FABBE00D2FF0E /* RoutingTests */, F0ACE3072BE4E478006D5333 /* MullvadMockData */, A944F25B2B8DEFDB00473F4C /* MullvadPostQuantum */, + A98F1B4D2C19C48D003C869E /* MullvadPostQuantumTests */, ); }; /* End PBXProject section */ @@ -5036,6 +5114,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A98F1B4C2C19C48D003C869E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; F0ACE3062BE4E478006D5333 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -5528,6 +5613,7 @@ 586C145A2AC4735F00245C01 /* PacketTunnelActor+Public.swift in Sources */, F0DAC8AD2C16EFE400F80144 /* TunnelSettingsManager.swift in Sources */, 58342C042AAB61FB003BA12D /* State+Extensions.swift in Sources */, + A98F1B452C19BB83003C869E /* NEPacketTunnelProvider+Extensions.swift in Sources */, A95EEE382B722DFC00A8A39B /* PingStats.swift in Sources */, 583832272AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift in Sources */, 58FE25DC2AA72A8F003D1918 /* AnyTask.swift in Sources */, @@ -5953,7 +6039,6 @@ 58F3F36A2AA08E3C00D3B0A4 /* PacketTunnelProvider.swift in Sources */, 58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */, 58C7A45B2A8640030060C66F /* PacketTunnelPathObserver.swift in Sources */, - A948809B2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift in Sources */, 580D6B8E2AB33BBF00B2D6E0 /* BlockedStateErrorMapper.swift in Sources */, 06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */, 583FE02429C1ACB3006E85F9 /* RESTCreateApplePaymentResponse+Localization.swift in Sources */, @@ -6145,6 +6230,16 @@ files = ( A9630E492B921E6D00A65999 /* PacketTunnelProvider+TCPConnection.swift in Sources */, A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiator.swift in Sources */, + A98F1B5A2C19C4C4003C869E /* PostQuantumKeyExchangeActor.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A98F1B4A2C19C48D003C869E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A98F1B512C19C48D003C869E /* PostQuantumKeyExchangeActorTests.swift in Sources */, + A9C3083A2C19DDA7008715F1 /* MullvadPostQuantum+Stubs.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6368,6 +6463,17 @@ target = 58D223D4294C8E5E0029F5F8 /* MullvadTypes */; targetProxy = A9630E412B8E10F700A65999 /* PBXContainerItemProxy */; }; + A98F1B472C19BCFF003C869E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 58C7A4352A863F440060C66F /* PacketTunnelCore */; + targetProxy = A98F1B462C19BCFF003C869E /* PBXContainerItemProxy */; + }; + A98F1B542C19C48D003C869E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + platformFilter = ios; + target = A944F25B2B8DEFDB00473F4C /* MullvadPostQuantum */; + targetProxy = A98F1B532C19C48D003C869E /* PBXContainerItemProxy */; + }; A9EC20F22A5D79ED0040D56E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5840231E2A406BF5007B27AC /* TunnelObfuscation */; @@ -8351,6 +8457,107 @@ }; name = MockRelease; }; + A98F1B562C19C48D003C869E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadPostQuantumTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A98F1B572C19C48D003C869E /* Staging */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadPostQuantumTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Staging; + }; + A98F1B582C19C48D003C869E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadPostQuantumTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + A98F1B592C19C48D003C869E /* MockRelease */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadPostQuantumTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = MockRelease; + }; A9E99CE12B5195E600869AF2 /* MockRelease */ = { isa = XCBuildConfiguration; buildSettings = { @@ -9313,6 +9520,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + A98F1B552C19C48D003C869E /* Build configuration list for PBXNativeTarget "MullvadPostQuantumTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A98F1B562C19C48D003C869E /* Debug */, + A98F1B572C19C48D003C869E /* Staging */, + A98F1B582C19C48D003C869E /* Release */, + A98F1B592C19C48D003C869E /* MockRelease */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F0ACE3162BE4E479006D5333 /* Build configuration list for PBXNativeTarget "MullvadMockData" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -9401,6 +9619,11 @@ package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; productName = WireGuardKitTypes; }; + A959FC4D2C19CF70009733CD /* WireGuardKitTypes */ = { + isa = XCSwiftPackageProductDependency; + package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; + productName = WireGuardKitTypes; + }; F0ACE3272BE4E712006D5333 /* WireGuardKitTypes */ = { isa = XCSwiftPackageProductDependency; package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantumTests.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantumTests.xcscheme new file mode 100644 index 000000000000..8f78b7481d29 --- /dev/null +++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantumTests.xcscheme @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme index e96e9b561d23..8e3306fc2b00 100644 --- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme +++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme @@ -146,6 +146,20 @@ ReferencedContainer = "container:MullvadVPN.xcodeproj"> + + + + + private let negotiationProvider: PostQuantumKeyNegotiation.Type // Callback in the event of the negotiation failing on startup var onFailure: () -> Void - init( - packetTunnel: PacketTunnelProvider, - onFailure: @escaping (() -> Void) + public init( + packetTunnel: any TunnelProvider, + onFailure: @escaping (() -> Void), + negotiationProvider: PostQuantumKeyNegotiation.Type = PostQuantumKeyNegotiator.self, + keyExchangeRetriesIterator: AnyIterator = REST.RetryStrategy.postQuantumKeyExchange + .makeDelayIterator() ) { self.packetTunnel = packetTunnel self.onFailure = onFailure + self.negotiationProvider = negotiationProvider + self.keyExchangeRetriesIterator = keyExchangeRetriesIterator } private func createTCPConnection(_ gatewayEndpoint: NWHostEndpoint) -> NWTCPConnection { @@ -59,9 +65,9 @@ class PostQuantumKeyExchangeActor { /// It is reset after every successful key exchange. /// /// - Parameter privateKey: The device's current private key - func startNegotiation(with privateKey: PrivateKey) { + public func startNegotiation(with privateKey: PrivateKey) { endCurrentNegotiation() - let negotiator = PostQuantumKeyNegotiator() + let negotiator = negotiationProvider.init() let gatewayAddress = "10.64.0.1" let IPv4Gateway = IPv4Address(gatewayAddress)! @@ -103,13 +109,13 @@ class PostQuantumKeyExchangeActor { } /// Cancels the ongoing key exchange. - func endCurrentNegotiation() { + public func endCurrentNegotiation() { negotiation?.cancel() negotiation = nil } /// Resets the exponential timeout for successful key exchanges, and ends the current key exchange. - func reset() { + public func reset() { keyExchangeRetriesIterator = REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() endCurrentNegotiation() } diff --git a/ios/PacketTunnelCore/NEPacketTunnelProvider+Extensions.swift b/ios/PacketTunnelCore/NEPacketTunnelProvider+Extensions.swift new file mode 100644 index 000000000000..9961da4bebb8 --- /dev/null +++ b/ios/PacketTunnelCore/NEPacketTunnelProvider+Extensions.swift @@ -0,0 +1,21 @@ +// +// NEPacketTunnelProvider+Extensions.swift +// PacketTunnelCore +// +// Created by Marco Nikic on 2024-06-12. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import NetworkExtension + +public protocol TunnelProvider: AnyObject { + func createTCPConnectionThroughTunnel( + to remoteEndpoint: NWEndpoint, + enableTLS: Bool, + tlsParameters TLSParameters: NWTLSParameters?, + delegate: Any? + ) -> NWTCPConnection +} + +extension NEPacketTunnelProvider: TunnelProvider {} diff --git a/ios/TestPlans/MullvadVPNApp.xctestplan b/ios/TestPlans/MullvadVPNApp.xctestplan index ba94d95b9852..e86ac7a2d2d2 100644 --- a/ios/TestPlans/MullvadVPNApp.xctestplan +++ b/ios/TestPlans/MullvadVPNApp.xctestplan @@ -64,6 +64,13 @@ "identifier" : "7A88DCD62A8FABBE00D2FF0E", "name" : "RoutingTests" } + }, + { + "target" : { + "containerPath" : "container:MullvadVPN.xcodeproj", + "identifier" : "A98F1B4D2C19C48D003C869E", + "name" : "MullvadPostQuantumTests" + } } ], "version" : 1 diff --git a/ios/TestPlans/MullvadVPNCI.xctestplan b/ios/TestPlans/MullvadVPNCI.xctestplan index f71d07ccfd89..4cbc9ab73186 100644 --- a/ios/TestPlans/MullvadVPNCI.xctestplan +++ b/ios/TestPlans/MullvadVPNCI.xctestplan @@ -64,6 +64,13 @@ "identifier" : "7A88DCD62A8FABBE00D2FF0E", "name" : "RoutingTests" } + }, + { + "target" : { + "containerPath" : "container:MullvadVPN.xcodeproj", + "identifier" : "A98F1B4D2C19C48D003C869E", + "name" : "MullvadPostQuantumTests" + } } ], "version" : 1 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 ba93ae2ecec6..509b38c461c1 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs @@ -9,9 +9,7 @@ use std::{ sync::{Arc, Mutex}, }; use talpid_types::net::wireguard::{PrivateKey, PublicKey}; -use tokio::{ - runtime::Builder, -}; +use tokio::runtime::Builder; use tonic::transport::channel::Endpoint; use tower::util::service_fn; @@ -156,12 +154,14 @@ impl IOSRuntime { swift_post_quantum_key_ready(packet_tunnel_ptr, preshared_key_bytes.as_ptr(), self.ephemeral_key.as_ptr()); }, None => unsafe { + log::error!("No suitable peer was found"); swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); } } }, - Err(_) => unsafe { + Err(error) => unsafe { + log::error!("Key exchange failed {}", error); swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); } } From 9030a3b015eb488f6babf7b5be2af1e7ce4b9e23 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 13 Jun 2024 13:20:21 +0200 Subject: [PATCH 4/5] Use a more appropriate protocol name --- ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift | 4 ++-- ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift | 4 ++-- ios/PacketTunnel/PostQuantumKeyExchangeActor.swift | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift index 4765ba76802c..6876686fd0be 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift @@ -14,7 +14,7 @@ import TalpidTunnelConfigClientProxy import WireGuardKitTypes // swiftlint:disable function_parameter_count -public protocol PostQuantumKeyNegotiation { +public protocol PostQuantumKeyNegotiating { func startNegotiation( gatewayIP: IPv4Address, devicePublicKey: PublicKey, @@ -32,7 +32,7 @@ public protocol PostQuantumKeyNegotiation { /** Attempt to start the asynchronous process of key negotiation. Returns true if successfully started, false if failed. */ -public class PostQuantumKeyNegotiator: PostQuantumKeyNegotiation { +public class PostQuantumKeyNegotiator: PostQuantumKeyNegotiating { required public init() {} var cancelToken: PostQuantumCancelToken? diff --git a/ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift b/ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift index 8d4e2f30e31b..1df6584444b4 100644 --- a/ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift +++ b/ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift @@ -43,7 +43,7 @@ class TunnelProviderStub: TunnelProvider { } } -class FailedNegotiatorStub: PostQuantumKeyNegotiation { +class FailedNegotiatorStub: PostQuantumKeyNegotiating { var onCancelKeyNegotiation: (() -> Void)? required init() { @@ -68,7 +68,7 @@ class FailedNegotiatorStub: PostQuantumKeyNegotiation { } } -class SuccessfulNegotiatorStub: PostQuantumKeyNegotiation { +class SuccessfulNegotiatorStub: PostQuantumKeyNegotiating { var onCancelKeyNegotiation: (() -> Void)? required init() { onCancelKeyNegotiation = nil diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index fef159af2c09..a9a13e263575 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -15,7 +15,7 @@ import WireGuardKitTypes public class PostQuantumKeyExchangeActor { struct Negotiation { - var negotiator: PostQuantumKeyNegotiation + var negotiator: PostQuantumKeyNegotiating var inTunnelTCPConnection: NWTCPConnection var tcpConnectionObserver: NSKeyValueObservation @@ -30,7 +30,7 @@ public class PostQuantumKeyExchangeActor { internal var negotiation: Negotiation? private var timer: DispatchSourceTimer? private var keyExchangeRetriesIterator: AnyIterator - private let negotiationProvider: PostQuantumKeyNegotiation.Type + private let negotiationProvider: PostQuantumKeyNegotiating.Type // Callback in the event of the negotiation failing on startup var onFailure: () -> Void @@ -38,7 +38,7 @@ public class PostQuantumKeyExchangeActor { public init( packetTunnel: any TunnelProvider, onFailure: @escaping (() -> Void), - negotiationProvider: PostQuantumKeyNegotiation.Type = PostQuantumKeyNegotiator.self, + negotiationProvider: PostQuantumKeyNegotiating.Type = PostQuantumKeyNegotiator.self, keyExchangeRetriesIterator: AnyIterator = REST.RetryStrategy.postQuantumKeyExchange .makeDelayIterator() ) { From 0af08f5096693881a03c5914906e783b7dd99288 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Fri, 14 Jun 2024 10:19:31 +0200 Subject: [PATCH 5/5] Clean up the code --- .../src/ios_ffi/ios_runtime.rs | 16 +++++++++----- .../src/ios_ffi/ios_tcp_connection.rs | 22 ++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) 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 509b38c461c1..1ab08a19bc21 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs @@ -96,8 +96,8 @@ impl IOSRuntime { self.run_service_inner(); }); } - /// Creates a `RelayConfigService` using the in-tunnel TCP Connection provided by the Packet - /// Tunnel Provider # Safety + /// 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, @@ -153,16 +153,20 @@ impl IOSRuntime { let preshared_key_bytes = preshared_key.as_bytes(); swift_post_quantum_key_ready(packet_tunnel_ptr, preshared_key_bytes.as_ptr(), self.ephemeral_key.as_ptr()); }, - None => unsafe { + None => { log::error!("No suitable peer was found"); - swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); + unsafe { + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); + } } } }, - Err(error) => unsafe { + Err(error) => { log::error!("Key exchange failed {}", error); - swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); + unsafe { + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); + } } } } 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 656bb142c85e..b550cc69b3e0 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 @@ -64,7 +64,7 @@ unsafe impl Send for ConnectionContext {} impl IosTcpProvider { /// # Safety - /// `tcp_connection` must be pointing to a valid instance of a `NWTCPConnection`, created by the + /// `connection` must be pointing to a valid instance of a `NWTCPConnection`, created by the /// `PacketTunnelProvider` pub unsafe fn new(connection: Arc>) -> (Self, IosTcpShutdownHandle) { let (tx, rx) = mpsc::unbounded_channel(); @@ -93,17 +93,15 @@ impl IosTcpProvider { impl IosTcpShutdownHandle { pub fn shutdown(self) { - { - let Ok(mut context) = self.context.lock() else { - return; - }; - - context.tcp_connection = None; - if let Some(waker) = context.waker.take() { - waker.wake(); - } - std::mem::drop(context); + let Ok(mut context) = self.context.lock() else { + return; + }; + + context.tcp_connection = None; + if let Some(waker) = context.waker.take() { + waker.wake(); } + std::mem::drop(context); } } @@ -136,7 +134,6 @@ impl AsyncWrite for IosTcpProvider { let raw_sender = Weak::into_raw(Arc::downgrade(&self.write_tx)); unsafe { swift_nw_tcp_connection_send( - // self.tcp_connection, tcp_ptr, buf.as_ptr() as _, buf.len(), @@ -193,7 +190,6 @@ impl AsyncRead for IosTcpProvider { if !self.read_in_progress { let raw_sender = Weak::into_raw(Arc::downgrade(&self.read_tx)); unsafe { - // TODO swift_nw_tcp_connection_read(tcp_ptr, raw_sender as _); } self.read_in_progress = true;