From 4f33c90f0b98c91ae7d9ed4cc3257e3c24bc9d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Em=C4=ABls?= Date: Tue, 6 Feb 2024 23:07:37 +0100 Subject: [PATCH 01/44] Add FFI to talpid-tunnel-config-client --- Cargo.lock | 23 + Cargo.toml | 3 +- ios/MullvadPostQuantum/MullvadPostQuantum.h | 19 + .../PacketTunnelProvider+TCPConnection.swift | 62 +++ .../PostQuantumKeyNegotiatior.swift | 35 ++ .../module.private.modulemap | 5 + .../talpid-tunnel-config-client/Cargo.toml | 46 ++ .../talpid-tunnel-config-client/build.rs | 18 + .../include/talpid_tunnel_config_client.h | 45 ++ .../src/ios_ffi.rs | 52 +++ .../src/ios_tcp_connection.rs | 123 ++++++ .../talpid-tunnel-config-client/src/lib.rs | 131 ++++++ ios/MullvadVPN.xcodeproj/project.pbxproj | 397 ++++++++++++++++++ .../xcschemes/MullvadPostQuantum.xcscheme | 66 +++ .../NWTCPConnection+Async.swift | 42 ++ .../PacketTunnelProvider.swift | 80 +++- .../Actor/ConfigurationBuilder.swift | 18 +- .../Actor/ObservedState.swift | 2 + .../Actor/PacketTunnelActor.swift | 2 +- ios/build-rust-library.sh | 20 +- talpid-routing/src/unix/mod.rs | 2 +- talpid-tunnel-config-client/Cargo.toml | 15 +- talpid-tunnel-config-client/src/lib.rs | 1 - 23 files changed, 1186 insertions(+), 21 deletions(-) create mode 100644 ios/MullvadPostQuantum/MullvadPostQuantum.h create mode 100644 ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift create mode 100644 ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift create mode 100644 ios/MullvadPostQuantum/module.private.modulemap create mode 100644 ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml create mode 100644 ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs create mode 100644 ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h create mode 100644 ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs create mode 100644 ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs create mode 100644 ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs create mode 100644 ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantum.xcscheme create mode 100644 ios/PacketTunnel/PacketTunnelProvider/NWTCPConnection+Async.swift diff --git a/Cargo.lock b/Cargo.lock index ad18fc949820..007b0d82000b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4070,6 +4070,7 @@ dependencies = [ name = "talpid-tunnel-config-client" version = "0.0.0" dependencies = [ + "cbindgen", "classic-mceliece-rust", "libc", "log", @@ -4085,6 +4086,28 @@ dependencies = [ "zeroize", ] +[[package]] +name = "talpid-tunnel-config-client-proxy" +version = "0.0.0" +dependencies = [ + "cbindgen", + "classic-mceliece-rust", + "futures", + "libc", + "log", + "oslog", + "pqc_kyber", + "prost", + "rand 0.8.5", + "talpid-tunnel-config-client", + "talpid-types", + "tokio", + "tonic", + "tonic-build", + "tower", + "zeroize", +] + [[package]] name = "talpid-types" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index aa3b9145c884..173f8d8f6ebe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "android/translations-converter", "ios/MullvadREST/Transport/Shadowsocks/shadowsocks-proxy", "ios/TunnelObfuscation/tunnel-obfuscator-proxy", + "ios/MullvadPostQuantum/talpid-tunnel-config-client", "mullvad-api", "mullvad-cli", "mullvad-daemon", @@ -80,7 +81,7 @@ shadowsocks-service = { version = "1.16" } windows-sys = "0.52.0" -chrono = { version = "0.4.26", default-features = false} +chrono = { version = "0.4.26", default-features = false } clap = { version = "4.4.18", features = ["cargo", "derive"] } once_cell = "1.13" diff --git a/ios/MullvadPostQuantum/MullvadPostQuantum.h b/ios/MullvadPostQuantum/MullvadPostQuantum.h new file mode 100644 index 000000000000..f48ca77519ac --- /dev/null +++ b/ios/MullvadPostQuantum/MullvadPostQuantum.h @@ -0,0 +1,19 @@ +// +// MullvadPostQuantum.h +// MullvadPostQuantum +// +// Created by Marco Nikic on 2024-02-27. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +#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 + + diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift new file mode 100644 index 000000000000..8796498cddbb --- /dev/null +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -0,0 +1,62 @@ +// +// PacketTunnelProvider+TCPConnection.swift +// PacketTunnel +// +// Created by Marco Nikic on 2024-02-15. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadTypes +import NetworkExtension +import TalpidTunnelConfigClientProxy +import WireGuardKitTypes + +@_cdecl("swift_nw_tcp_connection_send") +func tcpConnectionSend( + connection: UnsafeMutableRawPointer, + data: UnsafeMutableRawPointer, + dataLength: UInt, + sender: UnsafeMutableRawPointer +) { + let tcpConnection = Unmanaged.fromOpaque(connection).takeUnretainedValue() + let rawData = Data(bytes: data, count: Int(dataLength)) + + // The guarantee that no more than 2 writes happen in parallel is done by virtue of not returning the execution context + // to Rust before this closure is done executing. + tcpConnection.write(rawData, completionHandler: { maybeError in + if maybeError != nil { + handle_sent(0, sender) + } else { + handle_sent(dataLength, sender) + } + }) +} + +@_cdecl("swift_nw_tcp_connection_read") +func tcpConnectionReceive( + connection: UnsafeMutableRawPointer, + sender: UnsafeMutableRawPointer +) { + let tcpConnection = Unmanaged.fromOpaque(connection).takeUnretainedValue() + tcpConnection.readMinimumLength(0, maximumLength: Int(UInt16.max)) { data, maybeError in + if let data { + if maybeError != nil { + handle_recv(Data().map { $0 }, 0, sender) + } else { + handle_recv(data.map { $0 }, UInt(data.count), sender) + } + } + } +} + +@_cdecl("swift_post_quantum_key_ready") +func receivePostQuantumKey(rawPacketTunnel: UnsafeMutableRawPointer, rawPresharedKey: UnsafeMutableRawPointer) { + let packetTunnel = Unmanaged.fromOpaque(rawPacketTunnel).takeUnretainedValue() + // TODO: The `rawPresharedKey` pointer might be null, this means the key exchanged failed, and we should try from the start again + let presharedKey = Data(bytes: rawPresharedKey, count: 32) + if let postQuantumKeyReceiver = packetTunnel as? PostQuantumKeyReceiving, + let key = PreSharedKey(rawValue: presharedKey) { + postQuantumKeyReceiver.receivePostQuantumKey(key) + } +} diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift new file mode 100644 index 000000000000..5908312506ba --- /dev/null +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift @@ -0,0 +1,35 @@ +// +// PostQuantumKeyNegotiatior.swift +// PacketTunnel +// +// Created by Marco Nikic on 2024-02-16. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import NetworkExtension +import TalpidTunnelConfigClientProxy +import WireGuardKitTypes + +public struct PostQuantumKeyNegotiatior { + public init() {} + + public func negotiateKey( + gatewayIP: IPv4Address, + devicePublicKey: PublicKey, + presharedKey: PublicKey, + packetTunnel: NEPacketTunnelProvider, + tcpConnection: NWTCPConnection + ) { + let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque() + let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque() + + // TODO: Any non 0 return is considered a failure, and should be handled gracefully + negotiate_post_quantum_key( + devicePublicKey.rawValue.map { $0 }, + presharedKey.rawValue.map { $0 }, + packetTunnelPointer, + opaqueConnection + ) + } +} diff --git a/ios/MullvadPostQuantum/module.private.modulemap b/ios/MullvadPostQuantum/module.private.modulemap new file mode 100644 index 000000000000..ff5f63eee8d6 --- /dev/null +++ b/ios/MullvadPostQuantum/module.private.modulemap @@ -0,0 +1,5 @@ +framework module TalpidTunnelConfigClientProxy { + header "talpid_tunnel_config_client.h" + link "libtalpid_tunnel_config_client_proxy" + export * +} diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml b/ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml new file mode 100644 index 000000000000..902882201ab7 --- /dev/null +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "talpid-tunnel-config-client-proxy" +description = "Uses the relay RPC service to set up PQ-safe peers, etc." +authors.workspace = true +repository.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +talpid-tunnel-config-client = { path = "../../../talpid-tunnel-config-client" } +talpid-types = { path = "../../../talpid-types" } + +log = { workspace = true } +rand = "0.8" +tonic = { workspace = true } +futures = "0.3" +tower = { workspace = true } +prost = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +classic-mceliece-rust = { version = "2.0.0", features = [ + "mceliece460896f", + "zeroize", +] } +pqc_kyber = { version = "0.4.0", features = ["std", "kyber1024", "zeroize"] } +zeroize = "1.5.7" +libc = "0.2" + +[lib] +crate-type = ["rlib", "staticlib"] +bench = false + +[dev-dependencies] +tokio = { workspace = true, features = ["rt-multi-thread"] } +[build-dependencies] +tonic-build = { workspace = true, default-features = false, features = [ + "transport", + "prost", +] } +cbindgen = { version = "0.24.3", default-features = false } + +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +oslog = "0.2" diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs new file mode 100644 index 000000000000..3a4f92f421c5 --- /dev/null +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs @@ -0,0 +1,18 @@ +fn main() { + tonic_build::compile_protos("../../../talpid-tunnel-config-client/proto/tunnel_config.proto") + .unwrap(); + + match std::env::var("TARGET").unwrap().as_str() { + "aarch64-apple-ios" | "aarch64-apple-ios-sim" => { + println!("cargo:rerun-if-changed=include/talpid_tunnel_config_client.h"); + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(cbindgen::Language::C) + .generate() + .expect("failed to generate bindings") + .write_to_file("include/talpid_tunnel_config_client.h"); + } + &_ => (), + } +} 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 new file mode 100644 index 000000000000..19c9cd159f33 --- /dev/null +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +/** + * Callback to call when the TCP connection has written data. + */ +void handle_sent(uintptr_t bytes_sent, const void *sender); + +/** + * Callback to call when the TCP connection has received data. + */ +void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender); + +/** + * Entry point for exchanging post quantum keys on iOS. + * The TCP connection must be created to go through the tunnel. + */ +int32_t negotiate_post_quantum_key(const uint8_t *public_key, + const uint8_t *ephemeral_public_key, + const void *packet_tunnel, + const void *tcp_connection); + +/** + * Called when there is data to send on the TCP connection. + * The TCP connection must write data on the wire, then call the `handle_sent` function. + */ +extern void swift_nw_tcp_connection_send(const void *connection, + const void *data, + uintptr_t data_len, + const void *sender); + +/** + * Called when there is data to read on the TCP connection. + * The TCP connection must read data from the wire, then call the `handle_read` function. + */ +extern void swift_nw_tcp_connection_read(const void *connection, const void *sender); + +/** + * Called when the preshared post quantum key is ready. + * `raw_preshared_key` might be NULL if the key negotiation failed. + */ +extern void swift_post_quantum_key_ready(const void *raw_packet_tunnel, + const uint8_t *raw_preshared_key); diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs new file mode 100644 index 000000000000..32774c94d724 --- /dev/null +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs @@ -0,0 +1,52 @@ +use libc::c_void; +use tokio::sync::mpsc; + +use super::run_ios_runtime; + +use std::sync::Once; +static INIT_LOGGING: Once = Once::new(); + +/// 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); +} + +/// 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 _) }; + 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()); +} + +/// Entry point for exchanging post quantum keys on iOS. +/// The TCP connection must be created to go through the tunnel. +#[no_mangle] +pub unsafe extern "C" fn negotiate_post_quantum_key( + public_key: *const u8, + ephemeral_public_key: *const u8, + packet_tunnel: *const libc::c_void, + tcp_connection: *const libc::c_void, +) -> i32 { + INIT_LOGGING.call_once(|| { + let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.TTCC") + .level_filter(log::LevelFilter::Trace) + .init(); + }); + + let pub_key_copy: [u8; 32] = unsafe { std::ptr::read(public_key as *const [u8; 32]) }; + let eph_pub_key_copy: [u8; 32] = + unsafe { std::ptr::read(ephemeral_public_key as *const [u8; 32]) }; + + run_ios_runtime( + pub_key_copy, + eph_pub_key_copy, + packet_tunnel, + tcp_connection, + ) +} diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs new file mode 100644 index 000000000000..a93eec0fa8a1 --- /dev/null +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs @@ -0,0 +1,123 @@ +use libc::c_void; +use std::io; +use std::io::Result; +use std::task::Poll; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::sync::mpsc; + +extern "C" { + /// Called when there is data to send on the TCP connection. + /// The TCP connection must write data on the wire, then call the `handle_sent` function. + pub fn swift_nw_tcp_connection_send( + connection: *const libc::c_void, + data: *const libc::c_void, + data_len: usize, + sender: *const libc::c_void, + ); + + /// Called when there is data to read on the TCP connection. + /// The TCP connection must read data from the wire, then call the `handle_read` function. + pub fn swift_nw_tcp_connection_read( + connection: *const libc::c_void, + sender: *const libc::c_void, + ); + + /// Called when the preshared post quantum key is ready. + /// `raw_preshared_key` might be NULL if the key negotiation failed. + pub fn swift_post_quantum_key_ready( + raw_packet_tunnel: *const c_void, + raw_preshared_key: *const u8, + ); +} + +unsafe impl Send for IosTcpProvider {} + +pub struct IosTcpProvider { + write_tx: mpsc::UnboundedSender, + write_rx: mpsc::UnboundedReceiver, + read_tx: mpsc::UnboundedSender>, + read_rx: mpsc::UnboundedReceiver>, + tcp_connection: *const c_void, +} + +impl IosTcpProvider { + pub unsafe fn new(tcp_connection: *const c_void) -> Self { + let (tx, rx) = mpsc::unbounded_channel(); + let (recv_tx, recv_rx) = mpsc::unbounded_channel(); + Self { + write_tx: tx, + write_rx: rx, + read_tx: recv_tx, + read_rx: recv_rx, + tcp_connection, + } + } +} + +impl AsyncWrite for IosTcpProvider { + fn poll_write( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + let raw_sender = Box::into_raw(Box::new(self.write_tx.clone())); + + match self.write_rx.poll_recv(cx) { + std::task::Poll::Ready(Some(bytes_sent)) => Poll::Ready(Ok(bytes_sent)), + std::task::Poll::Ready(None) => { + Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "sender dropped"))) + } + std::task::Poll::Pending => { + unsafe { + swift_nw_tcp_connection_send( + self.tcp_connection, + buf.as_ptr() as _, + buf.len(), + raw_sender as _, + ); + } + std::task::Poll::Pending + } + } + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + _: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + _: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } +} +impl AsyncRead for IosTcpProvider { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + 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())); + + match self.read_rx.poll_recv(cx) { + std::task::Poll::Ready(Some(data)) => { + buf.put_slice(&data); + Poll::Ready(Ok(())) + } + std::task::Poll::Ready(None) => { + Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "sender dropped"))) + } + std::task::Poll::Pending => { + unsafe { + swift_nw_tcp_connection_read(self.tcp_connection, raw_sender as _); + } + + std::task::Poll::Pending + } + } + } +} diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs new file mode 100644 index 000000000000..5f972db13a51 --- /dev/null +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs @@ -0,0 +1,131 @@ +use std::ptr; + +use libc::c_void; + +use std::io; + +mod ios_ffi; +pub use ios_ffi::negotiate_post_quantum_key; + +use crate::ios_tcp_connection::swift_post_quantum_key_ready; +use crate::ios_tcp_connection::IosTcpProvider; +mod ios_tcp_connection; +use talpid_tunnel_config_client::Error; +use talpid_tunnel_config_client::RelayConfigService; +use talpid_types::net::wireguard::PublicKey; +use tonic::transport::Endpoint; +use tower::service_fn; + +pub unsafe fn run_ios_runtime( + pub_key: [u8; 32], + ephemeral_pub_key: [u8; 32], + packet_tunnel: *const c_void, + tcp_connection: *const c_void, +) -> i32 { + match IOSRuntime::new(pub_key, ephemeral_pub_key, packet_tunnel, tcp_connection) { + Ok(runtime) => { + runtime.run(); + 0 + } + Err(err) => { + log::error!("Failed to create runtime {}", err); + err.raw_os_error().unwrap_or(-1) + } + } +} + +#[derive(Clone)] +struct SwiftContext { + pub packet_tunnel: *const c_void, + pub tcp_connection: *const c_void, +} + +unsafe impl Send for SwiftContext {} +unsafe impl Sync for SwiftContext {} + +struct IOSRuntime { + runtime: tokio::runtime::Runtime, + pub_key: [u8; 32], + ephemeral_public_key: [u8; 32], + packet_tunnel: SwiftContext, +} + +impl IOSRuntime { + pub unsafe fn new( + pub_key: [u8; 32], + ephemeral_public_key: [u8; 32], + packet_tunnel: *const libc::c_void, + tcp_connection: *const c_void, + ) -> io::Result { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(2) + .build()?; + + let context = SwiftContext { + packet_tunnel, + tcp_connection, + }; + + Ok(Self { + runtime, + pub_key, + ephemeral_public_key, + packet_tunnel: context, + }) + } + + pub fn run(self) { + std::thread::spawn(move || { + self.run_service_inner(); + }); + } + + async fn ios_tcp_client(ctx: SwiftContext) -> Result { + let endpoint = Endpoint::from_static("tcp://0.0.0.0:0"); + let conn = endpoint + .connect_with_connector(service_fn(move |_| { + let ctx = ctx.clone(); + let tcp_provider = unsafe { IosTcpProvider::new(ctx.tcp_connection) }; + async move { Ok::<_, Error>(tcp_provider) } + })) + .await + .map_err(Error::GrpcConnectError)?; + + Ok(RelayConfigService::new(conn)) + } + + fn run_service_inner(self) { + let Self { runtime, .. } = self; + + let packet_tunnel_ptr = self.packet_tunnel.packet_tunnel; + runtime.block_on(async move { + let mut async_provider = match Self::ios_tcp_client(self.packet_tunnel).await { + Ok(async_provider) => async_provider, + Err(error) => { + log::error!("Failed to create iOS TCP client: {error}"); + unsafe { + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null_mut()); + } + return; + } + }; + let preshared_key = talpid_tunnel_config_client::push_pq_inner( + &mut async_provider, + PublicKey::from(self.pub_key), + PublicKey::from(self.ephemeral_public_key), + ) + .await; + + match preshared_key { + Ok(key) => unsafe { + let bytes = key.as_bytes(); + swift_post_quantum_key_ready(packet_tunnel_ptr, bytes.as_ptr()); + }, + Err(_) => unsafe { + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null_mut()); + }, + } + }); + } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index e2d33e5c275c..23a5b4b9c8ff 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -657,6 +657,8 @@ A900E9BC2ACC609200C95F67 /* DevicesProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */; }; A900E9BE2ACC654100C95F67 /* APIProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */; }; A900E9C02ACC661900C95F67 /* AccessTokenManager+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */; }; + A906F94A2BA1E09A002BF22E /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = A906F9492BA1E09A002BF22E /* WireGuardKitTypes */; }; + A906F94C2BA1E09A002BF22E /* WireGuardKitTypes in Embed Frameworks */ = {isa = PBXBuildFile; productRef = A906F9492BA1E09A002BF22E /* WireGuardKitTypes */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; A90763B02B2857D50045ADF0 /* Socks5ConnectCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A02B2857D50045ADF0 /* Socks5ConnectCommand.swift */; }; A90763B12B2857D50045ADF0 /* Socks5Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A12B2857D50045ADF0 /* Socks5Endpoint.swift */; }; A90763B22B2857D50045ADF0 /* Socks5EndpointReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A22B2857D50045ADF0 /* Socks5EndpointReader.swift */; }; @@ -683,15 +685,26 @@ A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; }; A91D78E32B03BDF200FCD5D3 /* TunnelObfuscation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5840231F2A406BF5007B27AC /* TunnelObfuscation.framework */; }; A91D78E42B03C01600FCD5D3 /* MullvadSettings.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */; }; + A9259FD22B8E06E90032C82B /* MullvadPostQuantum.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A93181A12B727ED700E341D2 /* TunnelSettingsV4.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93181A02B727ED700E341D2 /* TunnelSettingsV4.swift */; }; A932D9EF2B5ADD0700999395 /* ProxyConfigurationTransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A932D9EE2B5ADD0700999395 /* ProxyConfigurationTransportProvider.swift */; }; A932D9F32B5EB61100999395 /* HeadRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A932D9F22B5EB61100999395 /* HeadRequestTests.swift */; }; A932D9F52B5EBB9D00999395 /* RESTTransportStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A932D9F42B5EBB9D00999395 /* RESTTransportStub.swift */; }; A935594C2B4C2DA900D5D524 /* APIAvailabilityTestRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A935594B2B4C2DA900D5D524 /* APIAvailabilityTestRequest.swift */; }; + A944F25F2B8DEFDB00473F4C /* MullvadPostQuantum.h in Headers */ = {isa = PBXBuildFile; fileRef = A944F25E2B8DEFDB00473F4C /* MullvadPostQuantum.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A944F2622B8DEFDB00473F4C /* MullvadPostQuantum.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; }; + A944F2632B8DEFDB00473F4C /* MullvadPostQuantum.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiatior.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiatior.swift */; }; + A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client_proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.a */; }; A94D691A2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E22AA72AE9003D1918 /* WireGuardKitTypes */; }; A94D691B2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E72AA7399D003D1918 /* 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, ); }; }; + A9630E432B8E10FB00A65999 /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; }; + A9630E442B8E115A00A65999 /* MullvadPostQuantum.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; }; + A9630E472B8E1BF700A65999 /* NWTCPConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9630E462B8E1BF700A65999 /* NWTCPConnection+Async.swift */; }; + A9630E492B921E6D00A65999 /* PacketTunnelProvider+TCPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */; }; A970C89D2B29E38C000A7684 /* Socks5UsernamePasswordCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = A970C89C2B29E38C000A7684 /* Socks5UsernamePasswordCommand.swift */; }; A97D25AE2B0BB18100946B2D /* ProtocolObfuscator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97D25AD2B0BB18100946B2D /* ProtocolObfuscator.swift */; }; A97D25B02B0BB5C400946B2D /* ProtocolObfuscationStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97D25AF2B0BB5C400946B2D /* ProtocolObfuscationStub.swift */; }; @@ -1177,6 +1190,20 @@ remoteGlobalIDString = 5840231E2A406BF5007B27AC; remoteInfo = TunnelObfuscation; }; + A944F2602B8DEFDB00473F4C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 58CE5E58224146200008646E /* Project object */; + proxyType = 1; + remoteGlobalIDString = A944F25B2B8DEFDB00473F4C; + remoteInfo = MullvadPostQuantum; + }; + A9630E412B8E10F700A65999 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 58CE5E58224146200008646E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 58D223D4294C8E5E0029F5F8; + remoteInfo = MullvadTypes; + }; A9EC20F12A5D79ED0040D56E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 58CE5E58224146200008646E /* Project object */; @@ -1202,6 +1229,7 @@ files = ( 58D223E7294C8F120029F5F8 /* MullvadTypes.framework in Embed Frameworks */, 58D223FA294C8FF10029F5F8 /* MullvadLogging.framework in Embed Frameworks */, + A944F2632B8DEFDB00473F4C /* MullvadPostQuantum.framework in Embed Frameworks */, 58B2FDDA2AA71D2A003EB5C6 /* MullvadSettings.framework in Embed Frameworks */, A9EC20F02A5D79ED0040D56E /* TunnelObfuscation.framework in Embed Frameworks */, 7ABCA5B42A9349F20044A708 /* Routing.framework in Embed Frameworks */, @@ -1283,6 +1311,28 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + A906F94B2BA1E09A002BF22E /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + A906F94C2BA1E09A002BF22E /* WireGuardKitTypes in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + A9259FD52B8E06E90032C82B /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + A9259FD22B8E06E90032C82B /* MullvadPostQuantum.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -1957,9 +2007,14 @@ A932D9F42B5EBB9D00999395 /* RESTTransportStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTTransportStub.swift; sourceTree = ""; }; A935594B2B4C2DA900D5D524 /* APIAvailabilityTestRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIAvailabilityTestRequest.swift; sourceTree = ""; }; A935594D2B4E919F00D5D524 /* Api.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Api.xcconfig; sourceTree = ""; }; + A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadPostQuantum.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A944F25E2B8DEFDB00473F4C /* MullvadPostQuantum.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadPostQuantum.h; sourceTree = ""; }; + A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtalpid_tunnel_config_client_proxy.a; path = "../target/aarch64-apple-ios/debug/libtalpid_tunnel_config_client_proxy.a"; sourceTree = ""; }; A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTests.swift; sourceTree = ""; }; A95EEE352B722CD600A8A39B /* TunnelMonitorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorState.swift; sourceTree = ""; }; A95EEE372B722DFC00A8A39B /* PingStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PingStats.swift; sourceTree = ""; }; + A9630E3B2B8E0E7B00A65999 /* talpid_tunnel_config_client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = talpid_tunnel_config_client.h; path = "talpid-tunnel-config-client/include/talpid_tunnel_config_client.h"; sourceTree = ""; }; + A9630E462B8E1BF700A65999 /* NWTCPConnection+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWTCPConnection+Async.swift"; sourceTree = ""; }; A970C89C2B29E38C000A7684 /* Socks5UsernamePasswordCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Socks5UsernamePasswordCommand.swift; sourceTree = ""; }; A97D25AD2B0BB18100946B2D /* ProtocolObfuscator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolObfuscator.swift; sourceTree = ""; }; A97D25AF2B0BB5C400946B2D /* ProtocolObfuscationStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolObfuscationStub.swift; sourceTree = ""; }; @@ -1975,6 +2030,7 @@ A99E5EDF2B7628150033F241 /* ProblemReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportViewModel.swift; sourceTree = ""; }; A99E5EE12B762ED30033F241 /* ProblemReportViewController+ViewManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProblemReportViewController+ViewManagement.swift"; sourceTree = ""; }; A9A1DE782AD5708E0073F689 /* TransportStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportStrategy.swift; sourceTree = ""; }; + A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelProvider+TCPConnection.swift"; sourceTree = ""; }; A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManagerTests.swift; sourceTree = ""; }; A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = ""; }; A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = ""; }; @@ -1990,6 +2046,7 @@ A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelStore+Stubs.swift"; sourceTree = ""; }; A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusBlockObserver.swift; sourceTree = ""; }; A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = ""; }; + A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiatior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyNegotiatior.swift; sourceTree = ""; }; A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesTests.swift; sourceTree = ""; }; A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNConnectionProtocol.swift; sourceTree = ""; }; E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = ""; }; @@ -2151,6 +2208,7 @@ 58F0974E2A20C31100DA2DAD /* WireGuardKitTypes in Frameworks */, 58C7A4492A863F490060C66F /* PacketTunnelCore.framework in Frameworks */, 58D223F9294C8FF00029F5F8 /* MullvadLogging.framework in Frameworks */, + A944F2622B8DEFDB00473F4C /* MullvadPostQuantum.framework in Frameworks */, 58D223E6294C8F120029F5F8 /* MullvadTypes.framework in Frameworks */, 7ABCA5B32A9349F20044A708 /* Routing.framework in Frameworks */, 58D223CC294C8BCB0029F5F8 /* Operations.framework in Frameworks */, @@ -2163,6 +2221,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A9630E442B8E115A00A65999 /* MullvadPostQuantum.framework in Frameworks */, 589C6A7D2A45B06800DAD3EF /* TunnelObfuscation.framework in Frameworks */, 58FE25C62AA72779003D1918 /* PacketTunnelCore.framework in Frameworks */, 58FE25CE2AA72802003D1918 /* MullvadSettings.framework in Frameworks */, @@ -2238,6 +2297,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A944F2592B8DEFDB00473F4C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A9630E432B8E10FB00A65999 /* MullvadTypes.framework in Frameworks */, + A906F94A2BA1E09A002BF22E /* WireGuardKitTypes in Frameworks */, + A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client_proxy.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -2931,6 +3000,7 @@ 584F991F2902CBDD001F858D /* Frameworks */ = { isa = PBXGroup; children = ( + A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.a */, 01EF6F332B6A590700125696 /* libmullvad_api.a */, 01EF6F312B6A58F000125696 /* debug */, 01EF6F2F2B6A588300125696 /* aarch64-apple-ios */, @@ -3337,6 +3407,7 @@ 58C7A4432A863F490060C66F /* PacketTunnelCoreTests */, 7A88DCCF2A8FABBE00D2FF0E /* Routing */, 7A88DCDD2A8FABBE00D2FF0E /* RoutingTests */, + A944F25D2B8DEFDB00473F4C /* MullvadPostQuantum */, 58CE5E61224146200008646E /* Products */, 584F991F2902CBDD001F858D /* Frameworks */, ); @@ -3363,6 +3434,7 @@ 7A88DCD72A8FABBE00D2FF0E /* RoutingTests.xctest */, 58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */, 852969252B4D9C1F007EAD4C /* MullvadVPNUITests.xctest */, + A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */, ); name = Products; sourceTree = ""; @@ -3610,6 +3682,7 @@ 58FF23A22AB09BEE003A2AF2 /* DeviceChecker.swift */, 58225D272A84F23B0083D7F1 /* PacketTunnelPathObserver.swift */, 58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */, + A9630E462B8E1BF700A65999 /* NWTCPConnection+Async.swift */, ); path = PacketTunnelProvider; sourceTree = ""; @@ -3853,6 +3926,17 @@ path = Socks5; sourceTree = ""; }; + A944F25D2B8DEFDB00473F4C /* MullvadPostQuantum */ = { + isa = PBXGroup; + children = ( + A944F25E2B8DEFDB00473F4C /* MullvadPostQuantum.h */, + A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */, + A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiatior.swift */, + A9630E3B2B8E0E7B00A65999 /* talpid_tunnel_config_client.h */, + ); + path = MullvadPostQuantum; + sourceTree = ""; + }; F028A5472A336E1900C0CAA3 /* RedeemVoucher */ = { isa = PBXGroup; children = ( @@ -4097,6 +4181,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A944F2572B8DEFDB00473F4C /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A944F25F2B8DEFDB00473F4C /* MullvadPostQuantum.h in Headers */, + A9630E3C2B8E0E7B00A65999 /* talpid_tunnel_config_client.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXLegacyTarget section */ @@ -4315,6 +4408,7 @@ 58C7A4482A863F490060C66F /* PBXTargetDependency */, 7ABCA5B62A9349F20044A708 /* PBXTargetDependency */, 58B2FDD82AA71D2A003EB5C6 /* PBXTargetDependency */, + A944F2612B8DEFDB00473F4C /* PBXTargetDependency */, ); name = MullvadVPN; packageProductDependencies = ( @@ -4332,6 +4426,7 @@ 58CE5E75224146470008646E /* Sources */, 58CE5E76224146470008646E /* Frameworks */, 58CE5E77224146470008646E /* Resources */, + A9259FD52B8E06E90032C82B /* Embed Frameworks */, ); buildRules = ( ); @@ -4514,6 +4609,30 @@ productReference = 852969252B4D9C1F007EAD4C /* MullvadVPNUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + A944F25B2B8DEFDB00473F4C /* MullvadPostQuantum */ = { + isa = PBXNativeTarget; + buildConfigurationList = A944F2682B8DEFDB00473F4C /* Build configuration list for PBXNativeTarget "MullvadPostQuantum" */; + buildPhases = ( + A944F2692B8DF00C00473F4C /* Build Talpid Tunnel Config Client */, + A944F2572B8DEFDB00473F4C /* Headers */, + A944F2582B8DEFDB00473F4C /* Sources */, + A944F2592B8DEFDB00473F4C /* Frameworks */, + A944F25A2B8DEFDB00473F4C /* Resources */, + A906F94B2BA1E09A002BF22E /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + A9630E422B8E10F700A65999 /* PBXTargetDependency */, + ); + name = MullvadPostQuantum; + packageProductDependencies = ( + A906F9492BA1E09A002BF22E /* WireGuardKitTypes */, + ); + productName = MullvadPostQuantum; + productReference = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -4600,6 +4719,9 @@ CreatedOnToolsVersion = 15.1; TestTargetID = 58CE5E5F224146200008646E; }; + A944F25B2B8DEFDB00473F4C = { + CreatedOnToolsVersion = 15.2; + }; }; }; buildConfigurationList = 58CE5E5B224146200008646E /* Build configuration list for PBXProject "MullvadVPN" */; @@ -4638,6 +4760,7 @@ 58B2FDD22AA71D2A003EB5C6 /* MullvadSettings */, 7A88DCCD2A8FABBE00D2FF0E /* Routing */, 7A88DCD62A8FABBE00D2FF0E /* RoutingTests */, + A944F25B2B8DEFDB00473F4C /* MullvadPostQuantum */, ); }; /* End PBXProject section */ @@ -4769,6 +4892,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A944F25A2B8DEFDB00473F4C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -4846,6 +4976,25 @@ shellPath = /bin/sh; shellScript = "CARGO_TARGET_DIR=${PROJECT_DIR}/../target bash ${PROJECT_DIR}/build-rust-library.sh tunnel-obfuscator-proxy\n"; }; + A944F2692B8DF00C00473F4C /* Build Talpid Tunnel Config Client */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build Talpid Tunnel Config Client"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "CARGO_TARGET_DIR=${PROJECT_DIR}/../target bash ${PROJECT_DIR}/build-rust-library.sh talpid-tunnel-config-client-proxy\n"; + }; F05F39962B21C704006E60A7 /* Prebuild relays */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -5653,6 +5802,7 @@ 580D6B8E2AB33BBF00B2D6E0 /* BlockedStateErrorMapper.swift in Sources */, 06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */, 583FE02429C1ACB3006E85F9 /* RESTCreateApplePaymentResponse+Localization.swift in Sources */, + A9630E472B8E1BF700A65999 /* NWTCPConnection+Async.swift in Sources */, 58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */, 58E511E828DDDF2400B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */, 582403822A827E1500163DE8 /* RelaySelectorWrapper.swift in Sources */, @@ -5829,6 +5979,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A944F2582B8DEFDB00473F4C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A9630E492B921E6D00A65999 /* PacketTunnelProvider+TCPConnection.swift in Sources */, + A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiatior.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -6029,6 +6188,16 @@ target = 5840231E2A406BF5007B27AC /* TunnelObfuscation */; targetProxy = A91D78E12B03BDE500FCD5D3 /* PBXContainerItemProxy */; }; + A944F2612B8DEFDB00473F4C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A944F25B2B8DEFDB00473F4C /* MullvadPostQuantum */; + targetProxy = A944F2602B8DEFDB00473F4C /* PBXContainerItemProxy */; + }; + A9630E422B8E10F700A65999 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 58D223D4294C8E5E0029F5F8 /* MullvadTypes */; + targetProxy = A9630E412B8E10F700A65999 /* PBXContainerItemProxy */; + }; A9EC20F22A5D79ED0040D56E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5840231E2A406BF5007B27AC /* TunnelObfuscation */; @@ -6649,6 +6818,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; }; name = Debug; @@ -6669,6 +6839,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; }; name = Release; @@ -6689,6 +6860,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; }; name = Debug; @@ -6708,6 +6880,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; }; name = Release; @@ -7318,6 +7491,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Mullvad VPN Development"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; }; name = Staging; @@ -7356,6 +7530,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Packet Tunnel Development"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; }; name = Staging; @@ -7855,6 +8030,210 @@ }; name = MockRelease; }; + A944F2642B8DEFDB00473F4C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */; + buildSettings = { + APPLICATION_IDENTIFIER = net.mullvad.mullvadVPN; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 4; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Mullvad VPN AB. All rights reserved."; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/../target/x86_64-apple-ios/debug"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MODULEMAP_PRIVATE_FILE = $PROJECT_DIR/MullvadPostQuantum/module.private.modulemap; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).MullvadPostQuantum"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + A944F2652B8DEFDB00473F4C /* Staging */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */; + buildSettings = { + APPLICATION_IDENTIFIER = net.mullvad.mullvadVPN; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 4; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Mullvad VPN AB. All rights reserved."; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ""; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/../target/x86_64-apple-ios/debug"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MODULEMAP_PRIVATE_FILE = $PROJECT_DIR/MullvadPostQuantum/module.private.modulemap; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).MullvadPostQuantum"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Staging; + }; + A944F2662B8DEFDB00473F4C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */; + buildSettings = { + APPLICATION_IDENTIFIER = net.mullvad.mullvadVPN; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 4; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Mullvad VPN AB. All rights reserved."; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ""; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/../target/x86_64-apple-ios/release"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MODULEMAP_PRIVATE_FILE = $PROJECT_DIR/MullvadPostQuantum/module.private.modulemap; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).MullvadPostQuantum"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + A944F2672B8DEFDB00473F4C /* MockRelease */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */; + buildSettings = { + APPLICATION_IDENTIFIER = net.mullvad.mullvadVPN; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 4; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Mullvad VPN AB. All rights reserved."; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ""; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/../target/x86_64-apple-ios/release"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MODULEMAP_PRIVATE_FILE = $PROJECT_DIR/MullvadPostQuantum/module.private.modulemap; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).MullvadPostQuantum"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = MockRelease; + }; A9E99CE12B5195E600869AF2 /* MockRelease */ = { isa = XCBuildConfiguration; buildSettings = { @@ -7930,6 +8309,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Mullvad VPN Development"; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; }; name = MockRelease; @@ -7964,6 +8344,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel"; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Packet Tunnel Development"; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; }; name = MockRelease; @@ -8636,6 +9017,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + A944F2682B8DEFDB00473F4C /* Build configuration list for PBXNativeTarget "MullvadPostQuantum" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A944F2642B8DEFDB00473F4C /* Debug */, + A944F2652B8DEFDB00473F4C /* Staging */, + A944F2662B8DEFDB00473F4C /* Release */, + A944F2672B8DEFDB00473F4C /* MockRelease */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -8708,6 +9100,11 @@ package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; productName = WireGuardKitTypes; }; + A906F9492BA1E09A002BF22E /* WireGuardKitTypes */ = { + isa = XCSwiftPackageProductDependency; + package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */; + productName = WireGuardKitTypes; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 58CE5E58224146200008646E /* Project object */; diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantum.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantum.xcscheme new file mode 100644 index 000000000000..0d18c5e1d9f6 --- /dev/null +++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantum.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/PacketTunnel/PacketTunnelProvider/NWTCPConnection+Async.swift b/ios/PacketTunnel/PacketTunnelProvider/NWTCPConnection+Async.swift new file mode 100644 index 000000000000..2b1575a48d76 --- /dev/null +++ b/ios/PacketTunnel/PacketTunnelProvider/NWTCPConnection+Async.swift @@ -0,0 +1,42 @@ +// +// NWTCPConnection+Async.swift +// PacketTunnel +// +// Created by Marco Nikic on 2024-02-27. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import NetworkExtension + +class KVOWrapper { + let observation: NSKeyValueObservation + + init(observation: NSKeyValueObservation) { + self.observation = observation + } + + deinit { + NSLog("Bye cruel life") + } +} + +extension NWTCPConnection { + var viability: AsyncStream { + AsyncStream { continuation in + let keyPath: KeyPath = \.isViable + + let isViableObserver = observe(keyPath, options: [.new]) { connection, _ in + continuation.yield(connection.isViable) + } + + let wrapper = KVOWrapper(observation: isViableObserver) + + continuation.onTermination = { @Sendable _ in + wrapper.observation.invalidate() + } + + continuation.yield(false) + } + } +} diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 628828f5282c..93ead4a257a3 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -8,6 +8,7 @@ import Foundation import MullvadLogging +import MullvadPostQuantum import MullvadREST import MullvadSettings import MullvadTypes @@ -25,6 +26,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private var appMessageHandler: AppMessageHandler! private var stateObserverTask: AnyTask? private var deviceChecker: DeviceChecker! + private var adapter: WgAdapter! + private var relaySelector: RelaySelectorWrapper! override init() { Self.configureLogging() @@ -48,7 +51,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { addressCache: addressCache ) - let adapter = WgAdapter(packetTunnelProvider: self) + adapter = WgAdapter(packetTunnelProvider: self) let tunnelMonitor = TunnelMonitor( eventQueue: internalQueue, @@ -65,6 +68,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let devicesProxy = proxyFactory.createDevicesProxy() deviceChecker = DeviceChecker(accountsProxy: accountsProxy, devicesProxy: devicesProxy) + relaySelector = RelaySelectorWrapper(relayCache: ipOverrideWrapper) actor = PacketTunnelActor( timings: PacketTunnelActorTimings(), @@ -72,7 +76,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { tunnelMonitor: tunnelMonitor, defaultPathObserver: PacketTunnelPathObserver(packetTunnelProvider: self, eventQueue: internalQueue), blockedStateErrorMapper: BlockedStateErrorMapper(), - relaySelector: RelaySelectorWrapper(relayCache: ipOverrideWrapper), + relaySelector: relaySelector, settingsReader: SettingsReader(), protocolObfuscator: ProtocolObfuscator() ) @@ -110,6 +114,77 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } + // MARK: - Uncomment the next three functions to test Post Quantum Key exchange + +// override func startTunnel(options: [String: NSObject]? = nil) async throws { +// let startOptions = parseStartOptions(options ?? [:]) +// +// startObservingActorState() +// +// try await startPostQuantumKeyExchange() +// } +// +// func selectGothenburgRelay() throws -> MullvadEndpoint { +// let constraints = RelayConstraints( +// locations: .only(UserSelectedRelays(locations: [.city("se", "got")])) +// ) +// let relay = try relaySelector.selectRelay(with: constraints, connectionAttemptFailureCount: 0) +// return relay.endpoint +// } +// +// var pqTCPConnection: NWTCPConnection? +// +// func startPostQuantumKeyExchange() async throws { +// let settingsReader = SettingsReader() +// let settings: Settings = try settingsReader.read() +// let privateKey = settings.privateKey +// let postQuantumSharedKey = PrivateKey() // This will become the new private key of the device +// +// let IPv4Gateway = IPv4Address("10.64.0.1")! +// let gothenburgRelay = try selectGothenburgRelay() +// +// let configurationBuilder = ConfigurationBuilder( +// privateKey: settings.privateKey, +// interfaceAddresses: settings.interfaceAddresses, +// dns: settings.dnsServers, +// endpoint: gothenburgRelay, +// allowedIPs: [ +// IPAddressRange(from: "10.64.0.1/8")!, +// ] +// ) +// +// try await adapter.start(configuration: configurationBuilder.makeConfiguration()) +// +// let negotiator = PostQuantumKeyNegotiatior() +// let gatewayEndpoint = NWHostEndpoint(hostname: "10.64.0.1", port: "1337") +// +// pqTCPConnection = createTCPConnectionThroughTunnel( +// to: gatewayEndpoint, +// enableTLS: false, +// tlsParameters: nil, +// delegate: nil +// ) +// guard let pqTCPConnection else { return } +// +// // This will work as long as there is a detached, top-level task here. +// // It might be due to the async runtime environment for `override func startTunnel(options: [String: NSObject]? = nil) async throws` +// // There is a strong chance that the function's async availability was not properly declared by Apple. +// Task.detached { +// for await isViable in pqTCPConnection.viability where isViable == true { +// negotiator.negotiateKey( +// gatewayIP: IPv4Gateway, +// devicePublicKey: privateKey.publicKey, +// presharedKey: postQuantumSharedKey.publicKey, +// packetTunnel: self, +// tcpConnection: self.pqTCPConnection! +// ) +// break +// } +// } +// } + + // MARK: - End testing Post Quantum key exchange + override func stopTunnel(with reason: NEProviderStopReason) async { providerLogger.debug("stopTunnel: \(reason)") @@ -279,7 +354,6 @@ extension PacketTunnelProvider { extension PacketTunnelProvider: PostQuantumKeyReceiving { func receivePostQuantumKey(_ key: PreSharedKey) { - // TODO: send the key to the actor actor.replacePreSharedKey(key) } } diff --git a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift index 06b970a223b7..b389a168bcc9 100644 --- a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift +++ b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift @@ -21,14 +21,28 @@ public struct PublicKeyError: LocalizedError { } /// Struct building tunnel adapter configuration. -struct ConfigurationBuilder { +public struct ConfigurationBuilder { var privateKey: PrivateKey var interfaceAddresses: [IPAddressRange] var dns: SelectedDNSServers? var endpoint: MullvadEndpoint? var allowedIPs: [IPAddressRange] - func makeConfiguration() throws -> TunnelAdapterConfiguration { + public init( + privateKey: PrivateKey, + interfaceAddresses: [IPAddressRange], + dns: SelectedDNSServers? = nil, + endpoint: MullvadEndpoint? = nil, + allowedIPs: [IPAddressRange] + ) { + self.privateKey = privateKey + self.interfaceAddresses = interfaceAddresses + self.dns = dns + self.endpoint = endpoint + self.allowedIPs = allowedIPs + } + + public func makeConfiguration() throws -> TunnelAdapterConfiguration { return TunnelAdapterConfiguration( privateKey: privateKey, interfaceAddresses: interfaceAddresses, diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift index 3f92b300e50c..75b125d424e7 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState.swift @@ -14,6 +14,8 @@ import Network /// A serializable representation of internal state. public enum ObservedState: Equatable, Codable { case initial + // TODO: Handle this maybe ? +// case exchangingPostQuantumKey(ObservedConnectionState) case connecting(ObservedConnectionState) case reconnecting(ObservedConnectionState) #if DEBUG diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 81b3bfaff179..3cb8a6c4d014 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -43,7 +43,7 @@ public actor PacketTunnelActor { let tunnelMonitor: TunnelMonitorProtocol let defaultPathObserver: DefaultPathObserverProtocol let blockedStateErrorMapper: BlockedStateErrorMapperProtocol - let relaySelector: RelaySelectorProtocol + public let relaySelector: RelaySelectorProtocol let settingsReader: SettingsReaderProtocol let protocolObfuscator: ProtocolObfuscation diff --git a/ios/build-rust-library.sh b/ios/build-rust-library.sh index 5de58559b07c..94b8571a7859 100644 --- a/ios/build-rust-library.sh +++ b/ios/build-rust-library.sh @@ -9,6 +9,8 @@ then exit 1 fi + + # what to pass to cargo build -p, e.g. your_lib_ffi FFI_TARGET=$1 @@ -30,13 +32,17 @@ if [[ "$CONFIGURATION" == "MockRelease" ]]; then RELFLAG=--release fi -if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then - # Assume we're in Xcode, which means we're probably cross-compiling. - # In this case, we need to add an extra library search path for build scripts and proc-macros, - # which run on the host instead of the target. - # (macOS Big Sur does not have linkable libraries in /usr/lib/.) - export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}" -fi +# For whatever reason, Xcode includes its toolchain paths in the PATH variable such as +# +# /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin +# /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/appleinternal/bin +# /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/local/bin +# /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/libexec +# When this happens, cargo will be tricked into building for the wrong architecture, which will lead to linker issues down the line. +# cargo does not need to know about all this, therefore, set the path to the bare minimum +export PATH="${HOME}/.cargo/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:" +# Since some of the dependencies come from homebrew, add it manually as well +export PATH="${PATH}:/opt/homebrew/bin:" IS_SIMULATOR=0 if [ "${LLVM_TARGET_TRIPLE_SUFFIX-}" = "-simulator" ]; then diff --git a/talpid-routing/src/unix/mod.rs b/talpid-routing/src/unix/mod.rs index 5f14d88d67ac..ca3f44093540 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(target_os = "macos")] +#[cfg(any(target_os = "macos", target_os = "ios"))] #[path = "macos/mod.rs"] pub mod imp; diff --git a/talpid-tunnel-config-client/Cargo.toml b/talpid-tunnel-config-client/Cargo.toml index 650f2ba566ff..6aa8e5e3fc79 100644 --- a/talpid-tunnel-config-client/Cargo.toml +++ b/talpid-tunnel-config-client/Cargo.toml @@ -18,19 +18,24 @@ tonic = { workspace = true } tower = { workspace = true } prost = { workspace = true } tokio = { workspace = true, features = ["macros"] } -classic-mceliece-rust = { version = "2.0.0", features = ["mceliece460896f", "zeroize"] } +classic-mceliece-rust = { version = "2.0.0", features = [ + "mceliece460896f", + "zeroize", +] } pqc_kyber = { version = "0.4.0", features = ["std", "kyber1024", "zeroize"] } zeroize = "1.5.7" libc = "0.2" [target.'cfg(windows)'.dependencies.windows-sys] workspace = true -features = [ - "Win32_Networking_WinSock" -] +features = ["Win32_Networking_WinSock"] [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread"] } [build-dependencies] -tonic-build = { workspace = true, default-features = false, features = ["transport", "prost"] } +tonic-build = { workspace = true, default-features = false, features = [ + "transport", + "prost", +] } +cbindgen = { version = "0.24.3", default-features = false } diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs index e272e794c776..d287dc400431 100644 --- a/talpid-tunnel-config-client/src/lib.rs +++ b/talpid-tunnel-config-client/src/lib.rs @@ -134,7 +134,6 @@ pub async fn request_ephemeral_peer( activate_daita: enable_daita, }); - 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(), From e5f80857f333c6af1dea7731fef5078439f4aad0 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 20 Mar 2024 12:38:45 +0100 Subject: [PATCH 02/44] Remove debug statements --- .../xcschemes/MullvadVPN.xcscheme | 2 +- .../Coordinators/ApplicationCoordinator.swift | 2 -- .../MapConnectionStatusOperation.swift | 2 -- .../TunnelManager/StopTunnelOperation.swift | 2 -- .../TunnelManager/TunnelManager.swift | 5 ++-- .../TunnelManager/TunnelState.swift | 8 ------ .../Tunnel/TunnelControlView.swift | 14 ++-------- .../Tunnel/TunnelViewController.swift | 2 -- .../VPNSettings/VPNSettingsCellFactory.swift | 2 -- .../VPNSettings/VPNSettingsDataSource.swift | 26 ------------------- .../PacketTunnelProvider.swift | 3 +-- .../Actor/ObservedState+Extensions.swift | 4 --- .../Actor/ObservedState.swift | 2 -- 13 files changed, 6 insertions(+), 68 deletions(-) diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme index e96e9b561d23..540a9b776de4 100644 --- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme +++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme @@ -271,7 +271,7 @@ { case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error: doShutDownTunnel() - #if DEBUG case .negotiatingKey: doShutDownTunnel() - #endif case .disconnected, .disconnecting, .pendingReconnect, .waitingForConnectivity(.noNetwork): finish(result: .success(())) diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index fe989a790cd5..ac0164e93b33 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -675,10 +675,9 @@ final class TunnelManager: StorePaymentObserver { // while the tunnel process is trying to connect. startPollingTunnelStatus(interval: establishingTunnelStatusPollInterval) - #if DEBUG case .negotiatingKey: - startPollingTunnelStatus(interval: establishingTunnelStatusPollInterval) - #endif + // 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. diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift index 08b889899ef7..6a62bf1b47d2 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelState.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift @@ -50,10 +50,8 @@ enum TunnelState: Equatable, CustomStringConvertible { /// Connecting the tunnel. case connecting(SelectedRelay?) - #if DEBUG /// Negotiating a key for post-quantum resistance case negotiatingKey(SelectedRelay) - #endif /// Connected the tunnel case connected(SelectedRelay) @@ -99,10 +97,8 @@ enum TunnelState: Equatable, CustomStringConvertible { "waiting for connectivity" case let .error(blockedStateReason): "error state: \(blockedStateReason)" - #if DEBUG case let .negotiatingKey(tunnelRelay): "negotiating key with \(tunnelRelay.hostname)" - #endif } } @@ -113,10 +109,8 @@ enum TunnelState: Equatable, CustomStringConvertible { true case .pendingReconnect, .disconnecting, .disconnected, .waitingForConnectivity(.noNetwork), .error: false - #if DEBUG case .negotiatingKey: false - #endif } } @@ -126,10 +120,8 @@ enum TunnelState: Equatable, CustomStringConvertible { relay case let .connecting(relay): relay - #if DEBUG case let .negotiatingKey(relay): relay - #endif case .disconnecting, .disconnected, .waitingForConnectivity, .pendingReconnect, .error: nil } diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift index 8635bc1b9d79..230f470da58e 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift @@ -457,10 +457,9 @@ private extension TunnelState { case .connecting, .reconnecting, .waitingForConnectivity(.noConnection): .white - #if DEBUG + // TODO: Is this the correct color ? case .negotiatingKey: .white - #endif case .connected: .successColor @@ -488,7 +487,7 @@ private extension TunnelState { comment: "" ) - #if DEBUG + // TODO: Is this the correct message here ? case .negotiatingKey: NSLocalizedString( "TUNNEL_STATE_NEGOTIATING_KEY", @@ -496,7 +495,6 @@ private extension TunnelState { value: "Negotiating key", comment: "" ) - #endif case .connected: NSLocalizedString( @@ -573,7 +571,6 @@ private extension TunnelState { comment: "" ) - #if DEBUG case .negotiatingKey: NSLocalizedString( "SWITCH_LOCATION_BUTTON_TITLE", @@ -581,7 +578,6 @@ private extension TunnelState { value: "Switch location", comment: "" ) - #endif } } @@ -595,7 +591,6 @@ private extension TunnelState { comment: "" ) - #if DEBUG case .negotiatingKey: NSLocalizedString( "TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL", @@ -603,7 +598,6 @@ private extension TunnelState { value: "Creating secure connection", comment: "" ) - #endif case let .connected(tunnelInfo): String( @@ -682,10 +676,8 @@ private extension TunnelState { .waitingForConnectivity(.noConnection): [.selectLocation, .cancel] - #if DEBUG case .negotiatingKey: [.selectLocation, .cancel] - #endif case .connected, .reconnecting, .error: [.selectLocation, .disconnect] @@ -700,10 +692,8 @@ private extension TunnelState { .waitingForConnectivity(.noConnection): [.cancel] - #if DEBUG case .negotiatingKey: [.cancel] - #endif case .connected, .reconnecting, .error: [.disconnect] } diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index 7d68b3dd4136..db6773b24507 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -157,12 +157,10 @@ class TunnelViewController: UIViewController, RootContainment { contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelay.location.geoCoordinate, animated: animated) - #if DEBUG case let .negotiatingKey(tunnelRelay): mapViewController.removeLocationMarker() contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelay.location.geoCoordinate, animated: animated) - #endif case let .connected(tunnelRelay): let center = tunnelRelay.location.geoCoordinate diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift index bd3b42b76a85..2934e7d4571c 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift @@ -170,7 +170,6 @@ final class VPNSettingsCellFactory: CellFactoryProtocol { cell.accessibilityIdentifier = "\(item.accessibilityIdentifier.rawValue)\(portString)" cell.applySubCellStyling() - #if DEBUG case .quantumResistanceAutomatic: guard let cell = cell as? SelectableSettingsCell else { return } @@ -205,7 +204,6 @@ final class VPNSettingsCellFactory: CellFactoryProtocol { ) cell.accessibilityIdentifier = item.accessibilityIdentifier cell.applySubCellStyling() - #endif } } } diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift index ed4f2727b3d0..38ed7120ef88 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift @@ -57,9 +57,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case wireGuardPorts case wireGuardObfuscation case wireGuardObfuscationPort - #if DEBUG case quantumResistance - #endif } enum Item: Hashable { @@ -71,11 +69,9 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case wireGuardObfuscationOn case wireGuardObfuscationOff case wireGuardObfuscationPort(_ port: UInt16) - #if DEBUG case quantumResistanceAutomatic case quantumResistanceOn case quantumResistanceOff - #endif static var wireGuardPorts: [Item] { let defaultPorts = VPNSettingsViewModel.defaultWireGuardPorts.map { @@ -92,11 +88,9 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< [.wireGuardObfuscationPort(0), wireGuardObfuscationPort(80), wireGuardObfuscationPort(5001)] } - #if DEBUG static var quantumResistance: [Item] { [.quantumResistanceAutomatic, .quantumResistanceOn, .quantumResistanceOff] } - #endif var accessibilityIdentifier: AccessibilityIdentifier { switch self { @@ -116,14 +110,12 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< return .wireGuardObfuscationOff case .wireGuardObfuscationPort: return .wireGuardObfuscationPort - #if DEBUG case .quantumResistanceAutomatic: return .quantumResistanceAutomatic case .quantumResistanceOn: return .quantumResistanceOn case .quantumResistanceOff: return .quantumResistanceOff - #endif } } @@ -141,10 +133,8 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< return .wireGuardObfuscation case .wireGuardObfuscationPort: return .wireGuardObfuscationPort - #if DEBUG case .quantumResistanceAutomatic, .quantumResistanceOn, .quantumResistanceOff: return .quantumResistance - #endif } } } @@ -167,30 +157,20 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case .off: .wireGuardObfuscationOff case .on: .wireGuardObfuscationOn } - #if DEBUG let quantumResistanceItem: Item = switch viewModel.quantumResistance { case .automatic: .quantumResistanceAutomatic case .off: .quantumResistanceOff case .on: .quantumResistanceOn } - #endif let obfuscationPortItem: Item = .wireGuardObfuscationPort(viewModel.obfuscationPort.portValue) - #if DEBUG return [ wireGuardPortItem, obfuscationStateItem, obfuscationPortItem, quantumResistanceItem, ].compactMap { indexPath(for: $0) } - #else - return [ - wireGuardPortItem, - obfuscationStateItem, - obfuscationPortItem, - ].compactMap { indexPath(for: $0) } - #endif } init(tableView: UITableView) { @@ -299,7 +279,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< selectObfuscationPort(port) delegate?.didChangeViewModel(viewModel) - #if DEBUG case .quantumResistanceAutomatic: selectQuantumResistance(.automatic) delegate?.didChangeViewModel(viewModel) @@ -309,7 +288,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case .quantumResistanceOff: selectQuantumResistance(.off) delegate?.didChangeViewModel(viewModel) - #endif default: break } @@ -347,11 +325,9 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< case .wireGuardObfuscationPort: configureObfuscationPortHeader(view) return view - #if DEBUG case .quantumResistance: configureQuantumResistanceHeader(view) return view - #endif default: return nil @@ -539,7 +515,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< } } - #if DEBUG private func configureQuantumResistanceHeader(_ header: SettingsHeaderView) { let title = NSLocalizedString( "QUANTUM_RESISTANCE_HEADER_LABEL", @@ -568,7 +543,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource< self.map { $0.delegate?.showInfo(for: .quantumResistance) } } } - #endif private func selectRow(at indexPath: IndexPath?, animated: Bool = false) { tableView?.selectRow(at: indexPath, animated: animated, scrollPosition: .none) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 93ead4a257a3..3531a7833a96 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -305,10 +305,9 @@ extension PacketTunnelProvider { // Cache last connection attempt to filter out repeating calls. lastConnectionAttempt = connectionAttempt - #if DEBUG case .negotiatingKey: + // TODO: Call the key negotiatior here ? break - #endif case .initial, .connected, .disconnecting, .disconnected, .error: break diff --git a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift index 1504f3e47ddf..30208d97b1b8 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift @@ -15,10 +15,8 @@ extension ObservedState { case let .connecting(connState), let .connected(connState), let .reconnecting(connState): connState.relayConstraints - #if DEBUG case let .negotiatingKey(connState): connState.relayConstraints - #endif case let .error(blockedState): blockedState.relayConstraints @@ -33,10 +31,8 @@ extension ObservedState { "Connected" case .connecting: "Connecting" - #if DEBUG case .negotiatingKey: "Negotiating key" - #endif case .reconnecting: "Reconnecting" case .disconnecting: diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift index 75b125d424e7..a913894f0e0d 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState.swift @@ -18,9 +18,7 @@ public enum ObservedState: Equatable, Codable { // case exchangingPostQuantumKey(ObservedConnectionState) case connecting(ObservedConnectionState) case reconnecting(ObservedConnectionState) - #if DEBUG case negotiatingKey(ObservedConnectionState) - #endif case connected(ObservedConnectionState) case disconnecting(ObservedConnectionState) case disconnected From 074b9b3c8406c21cbfee5973375abce7ea5411d5 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 20 Mar 2024 13:00:26 +0100 Subject: [PATCH 03/44] Regroup switch cases together --- ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift | 5 +---- ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift | 5 +---- ios/MullvadVPN/TunnelManager/TunnelState.swift | 4 +--- .../View controllers/Tunnel/TunnelControlView.swift | 6 +----- .../View controllers/Tunnel/TunnelViewController.swift | 7 +------ ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift | 4 +--- 6 files changed, 6 insertions(+), 25 deletions(-) diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift index e7b8b7d67fca..673f0602049e 100644 --- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift @@ -980,10 +980,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo guard tunnelManager.deviceState.isLoggedIn else { return false } switch tunnelManager.tunnelStatus.state { - case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error: - tunnelManager.reconnectTunnel(selectNewRelay: true) - - case .negotiatingKey: + case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error, .negotiatingKey: tunnelManager.reconnectTunnel(selectNewRelay: true) case .disconnecting, .disconnected: diff --git a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift index 89a67eb17b2c..0448de22fd49 100644 --- a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift @@ -35,10 +35,7 @@ class StopTunnelOperation: ResultOperation { finish(result: .success(())) - case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error: - doShutDownTunnel() - - case .negotiatingKey: + case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error, .negotiatingKey: doShutDownTunnel() case .disconnected, .disconnecting, .pendingReconnect, .waitingForConnectivity(.noNetwork): diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift index 6a62bf1b47d2..7115c472d315 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelState.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift @@ -116,12 +116,10 @@ enum TunnelState: Equatable, CustomStringConvertible { var relay: SelectedRelay? { switch self { - case let .connected(relay), let .reconnecting(relay): + case let .connected(relay), let .reconnecting(relay), let .negotiatingKey(relay): relay case let .connecting(relay): relay - case let .negotiatingKey(relay): - relay case .disconnecting, .disconnected, .waitingForConnectivity, .pendingReconnect, .error: nil } diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift index 230f470da58e..84be14ae43d3 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift @@ -454,11 +454,7 @@ final class TunnelControlView: UIView { private extension TunnelState { var textColorForSecureLabel: UIColor { switch self { - case .connecting, .reconnecting, .waitingForConnectivity(.noConnection): - .white - - // TODO: Is this the correct color ? - case .negotiatingKey: + case .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .negotiatingKey: .white case .connected: diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index db6773b24507..f257928386b6 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -152,12 +152,7 @@ class TunnelViewController: UIViewController, RootContainment { contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelay?.location.geoCoordinate, animated: animated) - case let .reconnecting(tunnelRelay): - mapViewController.removeLocationMarker() - contentView.setAnimatingActivity(true) - mapViewController.setCenter(tunnelRelay.location.geoCoordinate, animated: animated) - - case let .negotiatingKey(tunnelRelay): + case let .reconnecting(tunnelRelay), let .negotiatingKey(tunnelRelay): mapViewController.removeLocationMarker() contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelay.location.geoCoordinate, animated: animated) diff --git a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift index 30208d97b1b8..7c26ed5d1440 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift @@ -12,11 +12,9 @@ import MullvadTypes extension ObservedState { public var relayConstraints: RelayConstraints? { switch self { - case let .connecting(connState), let .connected(connState), let .reconnecting(connState): + case let .connecting(connState), let .connected(connState), let .reconnecting(connState), let .negotiatingKey(connState): connState.relayConstraints - case let .negotiatingKey(connState): - connState.relayConstraints case let .error(blockedState): blockedState.relayConstraints From c66cfc220ceb9ff584eabcdc98c73a4b59037e6f Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 20 Mar 2024 15:16:24 +0100 Subject: [PATCH 04/44] Enable reading Quantum Resistance settings from SettingsReader --- .../PacketTunnelProvider/SettingsReader.swift | 3 ++- .../Actor/ObservedState+Extensions.swift | 3 ++- .../Protocols/SettingsReaderProtocol.swift | 6 +++++- .../Mocks/SettingsReaderStub.swift | 3 ++- .../PacketTunnelActorTests.swift | 3 ++- .../ProtocolObfuscatorTests.swift | 19 ++++++++++--------- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/SettingsReader.swift b/ios/PacketTunnel/PacketTunnelProvider/SettingsReader.swift index 10282b5ff32e..ade3b5a3afd2 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/SettingsReader.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/SettingsReader.swift @@ -21,7 +21,8 @@ struct SettingsReader: SettingsReaderProtocol { interfaceAddresses: [deviceData.ipv4Address, deviceData.ipv6Address], relayConstraints: settings.relayConstraints, dnsServers: settings.dnsSettings.selectedDNSServers, - obfuscation: settings.wireGuardObfuscation + obfuscation: settings.wireGuardObfuscation, + quantumResistance: settings.tunnelQuantumResistance ) } } diff --git a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift index 7c26ed5d1440..cc4a9c79a61a 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift @@ -12,7 +12,8 @@ import MullvadTypes extension ObservedState { public var relayConstraints: RelayConstraints? { switch self { - case let .connecting(connState), let .connected(connState), let .reconnecting(connState), let .negotiatingKey(connState): + case let .connecting(connState), let .connected(connState), let .reconnecting(connState), + let .negotiatingKey(connState): connState.relayConstraints case let .error(blockedState): diff --git a/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift index 9d75149b455b..ffe7cdcc2ffe 100644 --- a/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift +++ b/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift @@ -40,18 +40,22 @@ public struct Settings { /// Obfuscation settings public var obfuscation: WireGuardObfuscationSettings + public var quantumResistance: TunnelQuantumResistance + public init( privateKey: PrivateKey, interfaceAddresses: [IPAddressRange], relayConstraints: RelayConstraints, dnsServers: SelectedDNSServers, - obfuscation: WireGuardObfuscationSettings + obfuscation: WireGuardObfuscationSettings, + quantumResistance: TunnelQuantumResistance ) { self.privateKey = privateKey self.interfaceAddresses = interfaceAddresses self.relayConstraints = relayConstraints self.dnsServers = dnsServers self.obfuscation = obfuscation + self.quantumResistance = quantumResistance } } diff --git a/ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift b/ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift index c95133b09108..edb9e99e6d40 100644 --- a/ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift +++ b/ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift @@ -29,7 +29,8 @@ extension SettingsReaderStub { interfaceAddresses: [IPAddressRange(from: "127.0.0.1/32")!], relayConstraints: RelayConstraints(), dnsServers: .gateway, - obfuscation: WireGuardObfuscationSettings(state: .off, port: .automatic) + obfuscation: WireGuardObfuscationSettings(state: .off, port: .automatic), + quantumResistance: .automatic ) return SettingsReaderStub { diff --git a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift index fb37ef6b0da1..9fa8b90258fd 100644 --- a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift +++ b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift @@ -208,7 +208,8 @@ final class PacketTunnelActorTests: XCTestCase { interfaceAddresses: [IPAddressRange(from: "127.0.0.1/32")!], relayConstraints: RelayConstraints(), dnsServers: .gateway, - obfuscation: WireGuardObfuscationSettings(state: .off, port: .automatic) + obfuscation: WireGuardObfuscationSettings(state: .off, port: .automatic), + quantumResistance: .automatic ) } } diff --git a/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift b/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift index dd644a74e14b..21f30991bbf5 100644 --- a/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift +++ b/ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift @@ -34,14 +34,14 @@ final class ProtocolObfuscatorTests: XCTestCase { } func testObfuscateOffDoesNotChangeEndpoint() { - let settings = settings(.off, obfuscationPort: .automatic) + let settings = settings(.off, obfuscationPort: .automatic, quantumResistance: .automatic) let nonObfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings) XCTAssertEqual(endpoint, nonObfuscatedEndpoint) } func testObfuscateOnPort80() throws { - let settings = settings(.on, obfuscationPort: .port80) + let settings = settings(.on, obfuscationPort: .port80, quantumResistance: .automatic) let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings) let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub) @@ -49,7 +49,7 @@ final class ProtocolObfuscatorTests: XCTestCase { } func testObfuscateOnPort5001() throws { - let settings = settings(.on, obfuscationPort: .port5001) + let settings = settings(.on, obfuscationPort: .port5001, quantumResistance: .automatic) let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings) let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub) @@ -57,7 +57,7 @@ final class ProtocolObfuscatorTests: XCTestCase { } func testObfuscateOnPortAutomaticIsPort80OnEvenRetryAttempts() throws { - let settings = settings(.on, obfuscationPort: .automatic) + let settings = settings(.on, obfuscationPort: .automatic, quantumResistance: .automatic) let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings, retryAttempts: 2) let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub) @@ -65,7 +65,7 @@ final class ProtocolObfuscatorTests: XCTestCase { } func testObfuscateOnPortAutomaticIsPort5001OnOddRetryAttempts() throws { - let settings = settings(.on, obfuscationPort: .automatic) + let settings = settings(.on, obfuscationPort: .automatic, quantumResistance: .automatic) let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings, retryAttempts: 3) let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub) @@ -73,7 +73,7 @@ final class ProtocolObfuscatorTests: XCTestCase { } func testObfuscateAutomaticIsPort80EveryThirdAttempts() throws { - let settings = settings(.automatic, obfuscationPort: .automatic) + let settings = settings(.automatic, obfuscationPort: .automatic, quantumResistance: .automatic) let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings, retryAttempts: 6) let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub) @@ -81,7 +81,7 @@ final class ProtocolObfuscatorTests: XCTestCase { } func testObfuscateAutomaticIsPort5001EveryFourthAttempts() throws { - let settings = settings(.automatic, obfuscationPort: .automatic) + let settings = settings(.automatic, obfuscationPort: .automatic, quantumResistance: .automatic) let obfuscatedEndpoint = obfuscator.obfuscate(endpoint, settings: settings, retryAttempts: 7) let obfuscationProtocol = try XCTUnwrap(obfuscator.tunnelObfuscator as? TunnelObfuscationStub) @@ -100,7 +100,8 @@ final class ProtocolObfuscatorTests: XCTestCase { private func settings( _ obfuscationState: WireGuardObfuscationState, - obfuscationPort: WireGuardObfuscationPort + obfuscationPort: WireGuardObfuscationPort, + quantumResistance: TunnelQuantumResistance ) -> Settings { Settings( privateKey: PrivateKey(), @@ -110,7 +111,7 @@ final class ProtocolObfuscatorTests: XCTestCase { obfuscation: WireGuardObfuscationSettings( state: obfuscationState, port: obfuscationPort - ) + ), quantumResistance: quantumResistance ) } } From c47f7af940909d366d986ea9d35403c21f30d5c2 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Tue, 19 Mar 2024 17:27:44 +0100 Subject: [PATCH 05/44] Add utility methods for mutating `State`'s data, replacing switch stmts --- .../PacketTunnelProvider.swift | 121 +++++++++--------- .../Actor/PacketTunnelActor+KeyPolicy.swift | 35 +++++ .../Actor/State+Extensions.swift | 1 + 3 files changed, 98 insertions(+), 59 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 3531a7833a96..f87eb25ebfee 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -108,6 +108,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider { if connectionState.connectionAttemptCount > 1 { return } + case let .negotiatingKey(connectionState): + try await startPostQuantumKeyExchange() + return default: break } @@ -123,65 +126,65 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // // try await startPostQuantumKeyExchange() // } -// -// func selectGothenburgRelay() throws -> MullvadEndpoint { -// let constraints = RelayConstraints( -// locations: .only(UserSelectedRelays(locations: [.city("se", "got")])) -// ) -// let relay = try relaySelector.selectRelay(with: constraints, connectionAttemptFailureCount: 0) -// return relay.endpoint -// } -// -// var pqTCPConnection: NWTCPConnection? -// -// func startPostQuantumKeyExchange() async throws { -// let settingsReader = SettingsReader() -// let settings: Settings = try settingsReader.read() -// let privateKey = settings.privateKey -// let postQuantumSharedKey = PrivateKey() // This will become the new private key of the device -// -// let IPv4Gateway = IPv4Address("10.64.0.1")! -// let gothenburgRelay = try selectGothenburgRelay() -// -// let configurationBuilder = ConfigurationBuilder( -// privateKey: settings.privateKey, -// interfaceAddresses: settings.interfaceAddresses, -// dns: settings.dnsServers, -// endpoint: gothenburgRelay, -// allowedIPs: [ -// IPAddressRange(from: "10.64.0.1/8")!, -// ] -// ) -// -// try await adapter.start(configuration: configurationBuilder.makeConfiguration()) -// -// let negotiator = PostQuantumKeyNegotiatior() -// let gatewayEndpoint = NWHostEndpoint(hostname: "10.64.0.1", port: "1337") -// -// pqTCPConnection = createTCPConnectionThroughTunnel( -// to: gatewayEndpoint, -// enableTLS: false, -// tlsParameters: nil, -// delegate: nil -// ) -// guard let pqTCPConnection else { return } -// -// // This will work as long as there is a detached, top-level task here. -// // It might be due to the async runtime environment for `override func startTunnel(options: [String: NSObject]? = nil) async throws` -// // There is a strong chance that the function's async availability was not properly declared by Apple. -// Task.detached { -// for await isViable in pqTCPConnection.viability where isViable == true { -// negotiator.negotiateKey( -// gatewayIP: IPv4Gateway, -// devicePublicKey: privateKey.publicKey, -// presharedKey: postQuantumSharedKey.publicKey, -// packetTunnel: self, -// tcpConnection: self.pqTCPConnection! -// ) -// break -// } -// } -// } + + func selectGothenburgRelay() throws -> MullvadEndpoint { + let constraints = RelayConstraints( + locations: .only(UserSelectedRelays(locations: [.city("se", "got")])) + ) + let relay = try relaySelector.selectRelay(with: constraints, connectionAttemptFailureCount: 0) + return relay.endpoint + } + + var pqTCPConnection: NWTCPConnection? + + func startPostQuantumKeyExchange() async throws { + let settingsReader = SettingsReader() + let settings: Settings = try settingsReader.read() + let privateKey = settings.privateKey + let postQuantumSharedKey = PrivateKey() // This will become the new private key of the device + + let IPv4Gateway = IPv4Address("10.64.0.1")! + let gothenburgRelay = try selectGothenburgRelay() + + let configurationBuilder = ConfigurationBuilder( + privateKey: settings.privateKey, + interfaceAddresses: settings.interfaceAddresses, + dns: settings.dnsServers, + endpoint: gothenburgRelay, + allowedIPs: [ + IPAddressRange(from: "10.64.0.1/8")!, + ] + ) + + try await adapter.start(configuration: configurationBuilder.makeConfiguration()) + + let negotiator = PostQuantumKeyNegotiatior() + let gatewayEndpoint = NWHostEndpoint(hostname: "10.64.0.1", port: "1337") + + pqTCPConnection = createTCPConnectionThroughTunnel( + to: gatewayEndpoint, + enableTLS: false, + tlsParameters: nil, + delegate: nil + ) + guard let pqTCPConnection else { return } + + // This will work as long as there is a detached, top-level task here. + // It might be due to the async runtime environment for `override func startTunnel(options: [String: NSObject]? = nil) async throws` + // There is a strong chance that the function's async availability was not properly declared by Apple. + Task.detached { + for await isViable in pqTCPConnection.viability where isViable == true { + negotiator.negotiateKey( + gatewayIP: IPv4Gateway, + devicePublicKey: privateKey.publicKey, + presharedKey: postQuantumSharedKey.publicKey, + packetTunnel: self, + tcpConnection: self.pqTCPConnection! + ) + break + } + } + } // MARK: - End testing Post Quantum key exchange diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift index 9181a73edfd5..ba2327ffe837 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift @@ -36,7 +36,11 @@ extension PacketTunnelActor { // Move currentKey into keyPolicy. stateData.keyPolicy = .usePrior(currentKey, startKeySwitchTask()) stateData.currentKey = nil + } + + _ = state.mutateConnectionState(connectionStateMutator) || + state.mutateBlockedState(blockedStateMutator) } /** @@ -82,6 +86,37 @@ extension PacketTunnelActor { // Prevent tunnel from reconnecting when in blocked state. guard case .error = state else { return state.keyPolicy != oldKeyPolicy } return false +// switch state { +// case var .connecting(connState): +// if setCurrentKeyPolicy(&connState.keyPolicy) { +// state = .connecting(connState) +// return true +// } +// +// case var .connected(connState): +// if setCurrentKeyPolicy(&connState.keyPolicy) { +// state = .connected(connState) +// return true +// } +// +// case var .reconnecting(connState): +// if setCurrentKeyPolicy(&connState.keyPolicy) { +// state = .reconnecting(connState) +// return true +// } +// +// case var .error(blockedState): +// if setCurrentKeyPolicy(&blockedState.keyPolicy) { +// state = .error(blockedState) +// +// // Prevent tunnel from reconnecting when in blocked state. +// return false +// } +// +// case .disconnected, .disconnecting, .initial: +// break +// } +// return false } /** diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift index 45b28a0c9c32..3ba1aea5f2e8 100644 --- a/ios/PacketTunnelCore/Actor/State+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift @@ -144,6 +144,7 @@ extension State { default: break + } } From 9f8a766f7810ce9e0fd1e15d59484aecef077093 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Tue, 19 Mar 2024 17:36:11 +0100 Subject: [PATCH 06/44] Remove premature changes not related to this refactoring --- .../PacketTunnelProvider.swift | 121 +++++++++--------- 1 file changed, 59 insertions(+), 62 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index f87eb25ebfee..3531a7833a96 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -108,9 +108,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { if connectionState.connectionAttemptCount > 1 { return } - case let .negotiatingKey(connectionState): - try await startPostQuantumKeyExchange() - return default: break } @@ -126,65 +123,65 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // // try await startPostQuantumKeyExchange() // } - - func selectGothenburgRelay() throws -> MullvadEndpoint { - let constraints = RelayConstraints( - locations: .only(UserSelectedRelays(locations: [.city("se", "got")])) - ) - let relay = try relaySelector.selectRelay(with: constraints, connectionAttemptFailureCount: 0) - return relay.endpoint - } - - var pqTCPConnection: NWTCPConnection? - - func startPostQuantumKeyExchange() async throws { - let settingsReader = SettingsReader() - let settings: Settings = try settingsReader.read() - let privateKey = settings.privateKey - let postQuantumSharedKey = PrivateKey() // This will become the new private key of the device - - let IPv4Gateway = IPv4Address("10.64.0.1")! - let gothenburgRelay = try selectGothenburgRelay() - - let configurationBuilder = ConfigurationBuilder( - privateKey: settings.privateKey, - interfaceAddresses: settings.interfaceAddresses, - dns: settings.dnsServers, - endpoint: gothenburgRelay, - allowedIPs: [ - IPAddressRange(from: "10.64.0.1/8")!, - ] - ) - - try await adapter.start(configuration: configurationBuilder.makeConfiguration()) - - let negotiator = PostQuantumKeyNegotiatior() - let gatewayEndpoint = NWHostEndpoint(hostname: "10.64.0.1", port: "1337") - - pqTCPConnection = createTCPConnectionThroughTunnel( - to: gatewayEndpoint, - enableTLS: false, - tlsParameters: nil, - delegate: nil - ) - guard let pqTCPConnection else { return } - - // This will work as long as there is a detached, top-level task here. - // It might be due to the async runtime environment for `override func startTunnel(options: [String: NSObject]? = nil) async throws` - // There is a strong chance that the function's async availability was not properly declared by Apple. - Task.detached { - for await isViable in pqTCPConnection.viability where isViable == true { - negotiator.negotiateKey( - gatewayIP: IPv4Gateway, - devicePublicKey: privateKey.publicKey, - presharedKey: postQuantumSharedKey.publicKey, - packetTunnel: self, - tcpConnection: self.pqTCPConnection! - ) - break - } - } - } +// +// func selectGothenburgRelay() throws -> MullvadEndpoint { +// let constraints = RelayConstraints( +// locations: .only(UserSelectedRelays(locations: [.city("se", "got")])) +// ) +// let relay = try relaySelector.selectRelay(with: constraints, connectionAttemptFailureCount: 0) +// return relay.endpoint +// } +// +// var pqTCPConnection: NWTCPConnection? +// +// func startPostQuantumKeyExchange() async throws { +// let settingsReader = SettingsReader() +// let settings: Settings = try settingsReader.read() +// let privateKey = settings.privateKey +// let postQuantumSharedKey = PrivateKey() // This will become the new private key of the device +// +// let IPv4Gateway = IPv4Address("10.64.0.1")! +// let gothenburgRelay = try selectGothenburgRelay() +// +// let configurationBuilder = ConfigurationBuilder( +// privateKey: settings.privateKey, +// interfaceAddresses: settings.interfaceAddresses, +// dns: settings.dnsServers, +// endpoint: gothenburgRelay, +// allowedIPs: [ +// IPAddressRange(from: "10.64.0.1/8")!, +// ] +// ) +// +// try await adapter.start(configuration: configurationBuilder.makeConfiguration()) +// +// let negotiator = PostQuantumKeyNegotiatior() +// let gatewayEndpoint = NWHostEndpoint(hostname: "10.64.0.1", port: "1337") +// +// pqTCPConnection = createTCPConnectionThroughTunnel( +// to: gatewayEndpoint, +// enableTLS: false, +// tlsParameters: nil, +// delegate: nil +// ) +// guard let pqTCPConnection else { return } +// +// // This will work as long as there is a detached, top-level task here. +// // It might be due to the async runtime environment for `override func startTunnel(options: [String: NSObject]? = nil) async throws` +// // There is a strong chance that the function's async availability was not properly declared by Apple. +// Task.detached { +// for await isViable in pqTCPConnection.viability where isViable == true { +// negotiator.negotiateKey( +// gatewayIP: IPv4Gateway, +// devicePublicKey: privateKey.publicKey, +// presharedKey: postQuantumSharedKey.publicKey, +// packetTunnel: self, +// tcpConnection: self.pqTCPConnection! +// ) +// break +// } +// } +// } // MARK: - End testing Post Quantum key exchange From af2723488373baac1cfed18f748bdc9902d18b66 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Tue, 19 Mar 2024 17:37:28 +0100 Subject: [PATCH 07/44] Remove commented dead code --- .../Actor/PacketTunnelActor+KeyPolicy.swift | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift index ba2327ffe837..e0b49ada551b 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift @@ -86,37 +86,6 @@ extension PacketTunnelActor { // Prevent tunnel from reconnecting when in blocked state. guard case .error = state else { return state.keyPolicy != oldKeyPolicy } return false -// switch state { -// case var .connecting(connState): -// if setCurrentKeyPolicy(&connState.keyPolicy) { -// state = .connecting(connState) -// return true -// } -// -// case var .connected(connState): -// if setCurrentKeyPolicy(&connState.keyPolicy) { -// state = .connected(connState) -// return true -// } -// -// case var .reconnecting(connState): -// if setCurrentKeyPolicy(&connState.keyPolicy) { -// state = .reconnecting(connState) -// return true -// } -// -// case var .error(blockedState): -// if setCurrentKeyPolicy(&blockedState.keyPolicy) { -// state = .error(blockedState) -// -// // Prevent tunnel from reconnecting when in blocked state. -// return false -// } -// -// case .disconnected, .disconnecting, .initial: -// break -// } -// return false } /** From 39f9bdb17e0b927081f6b6d051670e42ef280880 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 20 Mar 2024 15:48:39 +0100 Subject: [PATCH 08/44] Make State Equatable, move change checks to didSet &c --- ios/PacketTunnelCore/Actor/State+Extensions.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift index 3ba1aea5f2e8..5451dc528c3d 100644 --- a/ios/PacketTunnelCore/Actor/State+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift @@ -92,6 +92,7 @@ extension State { let .disconnecting(connState): connState default: nil } + return modified } var blockedData: State.BlockingData? { From 4a4be65cc989cd58e3b488ffb3a29dd50e34ecb7 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 20 Mar 2024 16:20:59 +0100 Subject: [PATCH 09/44] Add a new actor state for key negotiation --- .../TunnelManager/MapConnectionStatusOperation.swift | 6 +++--- .../PacketTunnelProvider/PacketTunnelProvider.swift | 2 +- .../Actor/ObservedState+Extensions.swift | 4 ++-- ios/PacketTunnelCore/Actor/ObservedState.swift | 6 +++--- .../Actor/PacketTunnelActor+ConnectionMonitoring.swift | 4 ++-- .../Actor/PacketTunnelActor+ErrorState.swift | 3 ++- ios/PacketTunnelCore/Actor/PacketTunnelActor.swift | 9 +++++++-- ios/PacketTunnelCore/Actor/State+Extensions.swift | 6 ++++-- ios/PacketTunnelCore/Actor/State.swift | 3 +++ 9 files changed, 27 insertions(+), 16 deletions(-) diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift index bfa8b5d45e86..a6a604569e0a 100644 --- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift @@ -57,9 +57,9 @@ class MapConnectionStatusOperation: AsyncOperation { return connectionState.isNetworkReachable ? .connecting(connectionState.selectedRelay) : .waitingForConnectivity(.noConnection) - case let .negotiatingKey(connectionState): - return connectionState.isNetworkReachable - ? .negotiatingKey(connectionState.selectedRelay) + case let .negotiatingPostQuantumKey(connectionState): + connectionState.isNetworkReachable + ? .negotiatingPostQuantumKey(connectionState.selectedRelay) : .waitingForConnectivity(.noConnection) case let .reconnecting(connectionState): return connectionState.isNetworkReachable diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 3531a7833a96..b7962f8d9615 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -305,7 +305,7 @@ extension PacketTunnelProvider { // Cache last connection attempt to filter out repeating calls. lastConnectionAttempt = connectionAttempt - case .negotiatingKey: + case .negotiatingPostQuantumKey: // TODO: Call the key negotiatior here ? break diff --git a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift index cc4a9c79a61a..c69103c9b9a0 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift @@ -13,7 +13,7 @@ extension ObservedState { public var relayConstraints: RelayConstraints? { switch self { case let .connecting(connState), let .connected(connState), let .reconnecting(connState), - let .negotiatingKey(connState): + let .negotiatingPostQuantumKey(connState): connState.relayConstraints case let .error(blockedState): @@ -30,7 +30,7 @@ extension ObservedState { "Connected" case .connecting: "Connecting" - case .negotiatingKey: + case .negotiatingPostQuantumKey: "Negotiating key" case .reconnecting: "Reconnecting" diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift index a913894f0e0d..1643e765b9ae 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState.swift @@ -14,11 +14,9 @@ import Network /// A serializable representation of internal state. public enum ObservedState: Equatable, Codable { case initial - // TODO: Handle this maybe ? -// case exchangingPostQuantumKey(ObservedConnectionState) case connecting(ObservedConnectionState) case reconnecting(ObservedConnectionState) - case negotiatingKey(ObservedConnectionState) + case negotiatingPostQuantumKey(ObservedConnectionState) case connected(ObservedConnectionState) case disconnecting(ObservedConnectionState) case disconnected @@ -78,6 +76,8 @@ extension State { return .reconnecting(connState.observedConnectionState) case let .disconnecting(connState): return .disconnecting(connState.observedConnectionState) + case let .negotiatingPostQuantumKey(connState): + return .negotiatingPostQuantumKey(connState.observedConnectionState) case .disconnected: return .disconnected case let .error(blockedState): diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift index c2bc921b055e..84d34d2650cd 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift @@ -42,7 +42,7 @@ extension PacketTunnelActor { connState.connectionAttemptCount = 0 state = .connected(connState) - case .initial, .connected, .disconnecting, .disconnected, .error: + case .initial, .connected, .disconnecting, .disconnected, .error, .negotiatingPostQuantumKey: break } } @@ -53,7 +53,7 @@ extension PacketTunnelActor { case .connecting, .reconnecting, .connected: commandChannel.send(.reconnect(.random, reason: .connectionLoss)) - case .initial, .disconnected, .disconnecting, .error: + case .initial, .disconnected, .disconnecting, .error, .negotiatingPostQuantumKey: break } } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift index b450f44819c3..fd88ea94e4ad 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift @@ -84,7 +84,8 @@ extension PacketTunnelActor { return nil } - case .disconnecting, .disconnected: + // Post quantum key exchange cannot enter the blocked state + case .disconnecting, .disconnected, .negotiatingPostQuantumKey: return nil } } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 3cb8a6c4d014..196d91af4882 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -164,7 +164,8 @@ extension PacketTunnelActor { /// Stop the tunnel. private func stop() async { switch state { - case let .connected(connState), let .connecting(connState), let .reconnecting(connState): + case let .connected(connState), let .connecting(connState), let .reconnecting(connState), + let .negotiatingPostQuantumKey(connState): state = .disconnecting(connState) tunnelMonitor.stop() @@ -199,6 +200,10 @@ extension PacketTunnelActor { private func reconnect(to nextRelay: NextRelay, reason: ReconnectReason) async { do { switch state { + case .negotiatingPostQuantumKey: + // There is no connection monitoring going on when exchanging keys. + // The procedure starts from scratch for each reconnection attempts. + try await tryStart(nextRelay: nextRelay, reason: reason) case .connecting, .connected, .reconnecting, .error: switch reason { case .connectionLoss: @@ -326,7 +331,7 @@ extension PacketTunnelActor { connectionState.incrementAttemptCount() } fallthrough - case var .connected(connectionState): + case var .connected(connectionState), var .negotiatingPostQuantumKey(connectionState): let selectedRelay = try callRelaySelector( connectionState.selectedRelay, connectionState.connectionAttemptCount diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift index 5451dc528c3d..5737aa413818 100644 --- a/ios/PacketTunnelCore/Actor/State+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift @@ -23,7 +23,7 @@ extension State { case .initial: return .connecting - case .connecting: + case .connecting, .negotiatingPostQuantumKey: return .connecting case .connected, .reconnecting: @@ -59,13 +59,15 @@ extension State { case let .error(blockedState): return "\(name): \(blockedState.reason)" - case .initial, .disconnecting, .disconnected: + case .initial, .disconnecting, .disconnected, .negotiatingPostQuantumKey: return name } } var name: String { switch self { + case .negotiatingPostQuantumKey: + "Negotiating Post Quantum Key" case .connected: "Connected" case .connecting: diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift index ab3fe767c937..5ac9c1d8f7dd 100644 --- a/ios/PacketTunnelCore/Actor/State.swift +++ b/ios/PacketTunnelCore/Actor/State.swift @@ -58,6 +58,9 @@ enum State: Equatable { /// Initial state at the time when actor is initialized but before the first connection attempt. case initial + /// Establish a connection to the gateway, and exchange a post quantum key with the GRPC service that resides there. + case negotiatingPostQuantumKey(ConnectionData) + /// Tunnel is attempting to connect. /// The actor should remain in this state until the very first connection is established, i.e determined by tunnel monitor. case connecting(ConnectionData) From 6110b4a1c76a19b11e639668deda24a5abae9c73 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 21 Mar 2024 17:27:13 +0100 Subject: [PATCH 10/44] Read settings to determine whether to exchange a PQ key --- .../PacketTunnelProvider+TCPConnection.swift | 9 +++ .../PostQuantumKeyNegotiatior.swift | 18 +++++- .../include/talpid_tunnel_config_client.h | 10 ++-- .../src/ios_ffi.rs | 17 ++++-- .../talpid-tunnel-config-client/src/lib.rs | 58 +++++++++++++------ .../xcschemes/MullvadVPN.xcscheme | 2 +- .../PacketTunnelProvider.swift | 56 +++++++++++++++++- .../Actor/ObservedState+Extensions.swift | 2 +- .../Actor/ObservedState.swift | 7 ++- .../Actor/PacketTunnelActor.swift | 41 ++++++++++--- ios/PacketTunnelCore/Actor/State.swift | 2 +- 11 files changed, 175 insertions(+), 47 deletions(-) diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift index 8796498cddbb..d9af21d38b10 100644 --- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -19,6 +19,7 @@ func tcpConnectionSend( dataLength: UInt, sender: UnsafeMutableRawPointer ) { + NSLog("\(#function) receiving raw pointer \(connection)") let tcpConnection = Unmanaged.fromOpaque(connection).takeUnretainedValue() let rawData = Data(bytes: data, count: Int(dataLength)) @@ -38,6 +39,7 @@ func tcpConnectionReceive( connection: UnsafeMutableRawPointer, sender: UnsafeMutableRawPointer ) { + NSLog("\(#function) receiving raw pointer \(connection)") let tcpConnection = Unmanaged.fromOpaque(connection).takeUnretainedValue() tcpConnection.readMinimumLength(0, maximumLength: Int(UInt16.max)) { data, maybeError in if let data { @@ -54,6 +56,13 @@ func tcpConnectionReceive( func receivePostQuantumKey(rawPacketTunnel: UnsafeMutableRawPointer, rawPresharedKey: UnsafeMutableRawPointer) { let packetTunnel = Unmanaged.fromOpaque(rawPacketTunnel).takeUnretainedValue() // TODO: The `rawPresharedKey` pointer might be null, this means the key exchanged failed, and we should try from the start again + + guard rawPresharedKey.hashValue != 0.hashValue else { + // Fail here + print("no key for you") + return + } + let presharedKey = Data(bytes: rawPresharedKey, count: 32) if let postQuantumKeyReceiver = packetTunnel as? PostQuantumKeyReceiving, let key = PreSharedKey(rawValue: presharedKey) { diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift index 5908312506ba..679441f163c3 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift @@ -11,7 +11,8 @@ import NetworkExtension import TalpidTunnelConfigClientProxy import WireGuardKitTypes -public struct PostQuantumKeyNegotiatior { +public class PostQuantumKeyNegotiatior { + private var cancellationToken: UnsafeRawPointer! public init() {} public func negotiateKey( @@ -23,13 +24,26 @@ public struct PostQuantumKeyNegotiatior { ) { let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque() let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque() + NSLog("\(#function) passing raw pointer \(opaqueConnection)") // TODO: Any non 0 return is considered a failure, and should be handled gracefully - negotiate_post_quantum_key( + let token = negotiate_post_quantum_key( devicePublicKey.rawValue.map { $0 }, presharedKey.rawValue.map { $0 }, packetTunnelPointer, opaqueConnection ) + guard token?.hashValue != 0.hashValue else { + // Handle failure here + return + } + + cancellationToken = token + } + + public func cancelKeyNegotiation() { + if let cancellationToken, cancellationToken.hashValue != 0.hashValue { + cancel_post_quantum_key_exchange(cancellationToken) + } } } 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 19c9cd159f33..80508a0f0a70 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 @@ -3,6 +3,8 @@ #include #include +void cancel_post_quantum_key_exchange(const void *sender); + /** * Callback to call when the TCP connection has written data. */ @@ -17,10 +19,10 @@ void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender); * Entry point for exchanging post quantum keys on iOS. * The TCP connection must be created to go through the tunnel. */ -int32_t negotiate_post_quantum_key(const uint8_t *public_key, - const uint8_t *ephemeral_public_key, - const void *packet_tunnel, - const void *tcp_connection); +const void *negotiate_post_quantum_key(const uint8_t *public_key, + const uint8_t *ephemeral_public_key, + const void *packet_tunnel, + const void *tcp_connection); /** * Called when there is data to send on the TCP connection. diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs index 32774c94d724..95c3b5dc754e 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs @@ -3,9 +3,18 @@ use tokio::sync::mpsc; use super::run_ios_runtime; -use std::sync::Once; +use std::{rc::Weak, sync::Once}; static INIT_LOGGING: Once = Once::new(); +#[no_mangle] +pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const c_void) { + // 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: Weak> = unsafe { Weak::from_raw(sender as _) }; + match send_tx.upgrade() { + Some(tx) => _ = tx.send(()), + None => (), + } +} /// 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) { @@ -30,9 +39,9 @@ pub unsafe extern "C" fn handle_recv(data: *const u8, data_len: usize, sender: * pub unsafe extern "C" fn negotiate_post_quantum_key( public_key: *const u8, ephemeral_public_key: *const u8, - packet_tunnel: *const libc::c_void, - tcp_connection: *const libc::c_void, -) -> i32 { + packet_tunnel: *const c_void, + tcp_connection: *const c_void, +) -> *const c_void { INIT_LOGGING.call_once(|| { let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.TTCC") .level_filter(log::LevelFilter::Trace) diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs index 5f972db13a51..d5730714c118 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs @@ -1,8 +1,11 @@ use std::ptr; +use std::rc::Weak; use libc::c_void; +use tokio::sync::mpsc; use std::io; +use std::sync::Arc; mod ios_ffi; pub use ios_ffi::negotiate_post_quantum_key; @@ -21,15 +24,17 @@ pub unsafe fn run_ios_runtime( ephemeral_pub_key: [u8; 32], packet_tunnel: *const c_void, tcp_connection: *const c_void, -) -> i32 { +) -> *const c_void { match IOSRuntime::new(pub_key, ephemeral_pub_key, packet_tunnel, tcp_connection) { Ok(runtime) => { + let weak_cancel_token = Arc::downgrade(&runtime.cancel_token_tx); + let token = weak_cancel_token.into_raw() as _; runtime.run(); - 0 + token } Err(err) => { log::error!("Failed to create runtime {}", err); - err.raw_os_error().unwrap_or(-1) + std::ptr::null() } } } @@ -48,6 +53,8 @@ struct IOSRuntime { pub_key: [u8; 32], ephemeral_public_key: [u8; 32], packet_tunnel: SwiftContext, + cancel_token_tx: Arc>, + cancel_token_rx: mpsc::UnboundedReceiver<()>, } impl IOSRuntime { @@ -67,11 +74,15 @@ impl IOSRuntime { tcp_connection, }; + let (tx, rx) = mpsc::unbounded_channel(); + Ok(Self { runtime, pub_key, ephemeral_public_key, packet_tunnel: context, + cancel_token_tx: Arc::new(tx), + cancel_token_rx: rx, }) } @@ -96,7 +107,11 @@ impl IOSRuntime { } fn run_service_inner(self) { - let Self { runtime, .. } = self; + let Self { + runtime, + mut cancel_token_rx, + .. + } = self; let packet_tunnel_ptr = self.packet_tunnel.packet_tunnel; runtime.block_on(async move { @@ -110,21 +125,26 @@ impl IOSRuntime { return; } }; - let preshared_key = talpid_tunnel_config_client::push_pq_inner( - &mut async_provider, - PublicKey::from(self.pub_key), - PublicKey::from(self.ephemeral_public_key), - ) - .await; - - match preshared_key { - Ok(key) => unsafe { - let bytes = key.as_bytes(); - swift_post_quantum_key_ready(packet_tunnel_ptr, bytes.as_ptr()); - }, - Err(_) => unsafe { - swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null_mut()); - }, + tokio::select! { + preshared_key = talpid_tunnel_config_client::push_pq_inner( + &mut async_provider, + PublicKey::from(self.pub_key), + PublicKey::from(self.ephemeral_public_key), + ) => { + match preshared_key { + Ok(key) => unsafe { + let bytes = key.as_bytes(); + swift_post_quantum_key_ready(packet_tunnel_ptr, bytes.as_ptr()); + }, + Err(_) => unsafe { + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null_mut()); + }, + } + } + + _ = cancel_token_rx.recv() => { + // The swift runtime pre emptively cancelled the key exchange, nothing to do here. + } } }); } diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme index 540a9b776de4..e96e9b561d23 100644 --- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme +++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme @@ -271,7 +271,7 @@ PrivateKey { + switch state.keyPolicy { + case .useCurrent: + settings.privateKey + case let .usePrior(priorKey, _): + priorKey + } + } + private func obfuscateConnection( nextRelay: NextRelay, settings: Settings, diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift index 5ac9c1d8f7dd..259993a7f8e0 100644 --- a/ios/PacketTunnelCore/Actor/State.swift +++ b/ios/PacketTunnelCore/Actor/State.swift @@ -59,7 +59,7 @@ enum State: Equatable { case initial /// Establish a connection to the gateway, and exchange a post quantum key with the GRPC service that resides there. - case negotiatingPostQuantumKey(ConnectionData) + case negotiatingPostQuantumKey(ConnectionData, PrivateKey) /// Tunnel is attempting to connect. /// The actor should remain in this state until the very first connection is established, i.e determined by tunnel monitor. From b876e587f622e5746038edb42a88a9c8b56373cf Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Tue, 26 Mar 2024 11:13:20 +0100 Subject: [PATCH 11/44] Try to reconnect when PQ key exchange fails --- .../PacketTunnelProvider+TCPConnection.swift | 8 +++----- ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift | 1 - .../talpid-tunnel-config-client/src/ios_ffi.rs | 10 +++++++--- .../talpid-tunnel-config-client/src/lib.rs | 3 ++- .../Protocols/PostQuantumKeyReceiving.swift | 2 +- .../PacketTunnelProvider/PacketTunnelProvider.swift | 10 ++++++++-- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift index d9af21d38b10..226e50a07960 100644 --- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -55,17 +55,15 @@ func tcpConnectionReceive( @_cdecl("swift_post_quantum_key_ready") func receivePostQuantumKey(rawPacketTunnel: UnsafeMutableRawPointer, rawPresharedKey: UnsafeMutableRawPointer) { let packetTunnel = Unmanaged.fromOpaque(rawPacketTunnel).takeUnretainedValue() - // TODO: The `rawPresharedKey` pointer might be null, this means the key exchanged failed, and we should try from the start again + guard let postQuantumKeyReceiver = packetTunnel as? PostQuantumKeyReceiving else { return } guard rawPresharedKey.hashValue != 0.hashValue else { - // Fail here - print("no key for you") + postQuantumKeyReceiver.receivePostQuantumKey(.none) return } let presharedKey = Data(bytes: rawPresharedKey, count: 32) - if let postQuantumKeyReceiver = packetTunnel as? PostQuantumKeyReceiving, - let key = PreSharedKey(rawValue: presharedKey) { + if let key = PreSharedKey(rawValue: presharedKey) { postQuantumKeyReceiver.receivePostQuantumKey(key) } } diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift index 679441f163c3..e9d1b7e413e5 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift @@ -24,7 +24,6 @@ public class PostQuantumKeyNegotiatior { ) { let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque() let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque() - NSLog("\(#function) passing raw pointer \(opaqueConnection)") // TODO: Any non 0 return is considered a failure, and should be handled gracefully let token = negotiate_post_quantum_key( diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs index 95c3b5dc754e..78db55283f06 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs @@ -6,13 +6,15 @@ use super::run_ios_runtime; use std::{rc::Weak, sync::Once}; static INIT_LOGGING: Once = Once::new(); +#[allow(clippy::let_underscore_future)] #[no_mangle] pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const c_void) { // 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: Weak> = unsafe { Weak::from_raw(sender as _) }; - match send_tx.upgrade() { - Some(tx) => _ = tx.send(()), - None => (), + if let Some(tx) = send_tx.upgrade() { + // # Safety + // Clippy warns of a non-binding let on a future, this future is being awaited on. + _ = tx.send(()); } } /// Callback to call when the TCP connection has written data. @@ -35,6 +37,8 @@ pub unsafe extern "C" fn handle_recv(data: *const u8, data_len: usize, sender: * /// 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 #[no_mangle] pub unsafe extern "C" fn negotiate_post_quantum_key( public_key: *const u8, diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs index d5730714c118..81c5d272dcfa 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs @@ -1,5 +1,4 @@ use std::ptr; -use std::rc::Weak; use libc::c_void; use tokio::sync::mpsc; @@ -19,6 +18,8 @@ use talpid_types::net::wireguard::PublicKey; use tonic::transport::Endpoint; use tower::service_fn; +/// # Safety +/// This function is safe to call pub unsafe fn run_ios_runtime( pub_key: [u8; 32], ephemeral_pub_key: [u8; 32], diff --git a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift index eb696fcca300..3abbfd6738e4 100644 --- a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift +++ b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift @@ -10,7 +10,7 @@ import Foundation import WireGuardKitTypes public protocol PostQuantumKeyReceiving { - func receivePostQuantumKey(_ key: PreSharedKey) + func receivePostQuantumKey(_ key: PreSharedKey?) } public enum PostQuantumKeyReceivingError: Error { diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 8d153d2eed73..5dbf0e915a2d 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -397,12 +397,18 @@ extension PacketTunnelProvider { } extension PacketTunnelProvider: PostQuantumKeyReceiving { - func receivePostQuantumKey(_ key: PreSharedKey) { + func receivePostQuantumKey(_ key: PreSharedKey?) { + quantumKeyNegotiatior?.cancelKeyNegotiation() tcpConnectionObserver?.invalidate() inTunnelTCPConnection.cancel() tcpConnectionObserver = nil inTunnelTCPConnection = nil + quantumKeyNegotiatior = nil - actor.replacePreSharedKey(key) + if let key { + actor.replacePreSharedKey(key) + } else { + actor.reconnect(to: .current) + } } } From d20ed4055f6be3aac671eddb250bd320b39911ef Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Tue, 26 Mar 2024 14:43:46 +0100 Subject: [PATCH 12/44] Remove dead code --- .../PacketTunnelProvider+TCPConnection.swift | 2 +- .../include/talpid_tunnel_config_client.h | 2 + .../Protocols/PostQuantumKeyReceiving.swift | 9 --- .../PacketTunnelProvider.swift | 71 ------------------- 4 files changed, 3 insertions(+), 81 deletions(-) diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift index 226e50a07960..30a52e310358 100644 --- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -63,7 +63,7 @@ func receivePostQuantumKey(rawPacketTunnel: UnsafeMutableRawPointer, rawPreshare } let presharedKey = Data(bytes: rawPresharedKey, count: 32) - if let key = PreSharedKey(rawValue: presharedKey) { + if let key = PreSharedKey(rawValue: presharedKey) { postQuantumKeyReceiver.receivePostQuantumKey(key) } } 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 80508a0f0a70..5d4604a59e39 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 @@ -18,6 +18,8 @@ void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender); /** * 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 */ const void *negotiate_post_quantum_key(const uint8_t *public_key, const uint8_t *ephemeral_public_key, diff --git a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift index 3abbfd6738e4..714f4cd33e5b 100644 --- a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift +++ b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift @@ -16,12 +16,3 @@ public protocol PostQuantumKeyReceiving { public enum PostQuantumKeyReceivingError: Error { case invalidKey } - -public extension PostQuantumKeyReceiving { - func receivePostQuantumKey(_ keyData: Data) throws { - guard let key = PreSharedKey(rawValue: keyData) else { - throw PostQuantumKeyReceivingError.invalidKey - } - receivePostQuantumKey(key) - } -} diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 5dbf0e915a2d..009b26cda6e6 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -119,77 +119,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } - // MARK: - Uncomment the next three functions to test Post Quantum Key exchange - -// override func startTunnel(options: [String: NSObject]? = nil) async throws { -// let startOptions = parseStartOptions(options ?? [:]) -// -// startObservingActorState() -// -// try await startPostQuantumKeyExchange() -// } -// -// func selectGothenburgRelay() throws -> MullvadEndpoint { -// let constraints = RelayConstraints( -// locations: .only(UserSelectedRelays(locations: [.city("se", "got")])) -// ) -// let relay = try relaySelector.selectRelay(with: constraints, connectionAttemptFailureCount: 0) -// return relay.endpoint -// } -// -// var pqTCPConnection: NWTCPConnection? -// -// func startPostQuantumKeyExchange() async throws { -// let settingsReader = SettingsReader() -// let settings: Settings = try settingsReader.read() -// let privateKey = settings.privateKey -// let postQuantumSharedKey = PrivateKey() // This will become the new private key of the device -// -// let IPv4Gateway = IPv4Address("10.64.0.1")! -// let gothenburgRelay = try selectGothenburgRelay() -// -// let configurationBuilder = ConfigurationBuilder( -// privateKey: settings.privateKey, -// interfaceAddresses: settings.interfaceAddresses, -// dns: settings.dnsServers, -// endpoint: gothenburgRelay, -// allowedIPs: [ -// IPAddressRange(from: "10.64.0.1/8")!, -// ] -// ) -// -// try await adapter.start(configuration: configurationBuilder.makeConfiguration()) -// -// let negotiator = PostQuantumKeyNegotiatior() -// let gatewayEndpoint = NWHostEndpoint(hostname: "10.64.0.1", port: "1337") -// -// pqTCPConnection = createTCPConnectionThroughTunnel( -// to: gatewayEndpoint, -// enableTLS: false, -// tlsParameters: nil, -// delegate: nil -// ) -// guard let pqTCPConnection else { return } -// -// // This will work as long as there is a detached, top-level task here. -// // It might be due to the async runtime environment for `override func startTunnel(options: [String: NSObject]? = nil) async throws` -// // There is a strong chance that the function's async availability was not properly declared by Apple. -// Task.detached { -// for await isViable in pqTCPConnection.viability where isViable == true { -// negotiator.negotiateKey( -// gatewayIP: IPv4Gateway, -// devicePublicKey: privateKey.publicKey, -// presharedKey: postQuantumSharedKey.publicKey, -// packetTunnel: self, -// tcpConnection: self.pqTCPConnection! -// ) -// break -// } -// } -// } - - // MARK: - End testing Post Quantum key exchange - override func stopTunnel(with reason: NEProviderStopReason) async { providerLogger.debug("stopTunnel: \(reason)") From 5560936aa7c1a40d4ff3b678790179f048863173 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Tue, 26 Mar 2024 16:26:12 +0100 Subject: [PATCH 13/44] Enable exchanging PQ keys from an unconnected state --- .../PacketTunnelProvider/PacketTunnelProvider.swift | 6 ++++++ ios/PacketTunnelCore/Actor/PacketTunnelActor.swift | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 009b26cda6e6..e0f93c3eb2ce 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -113,6 +113,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider { if connectionState.connectionAttemptCount > 1 { return } + case .negotiatingPostQuantumKey: + // When negotiating post quantun keys, allow the connection to go through immediately. + // Otherwise, the in-tunnel TCP connection will never become ready as the OS doesn't let + // any traffic through until this function returns, which would prevent negotiating keys + // from an unconnected state. + return default: break } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 9c584ca0af08..887cff157de8 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -257,7 +257,7 @@ extension PacketTunnelActor { dns: settings.dnsServers, endpoint: connectionState.connectedEndpoint, allowedIPs: [ - IPAddressRange(from: "10.64.0.1/8")!, + IPAddressRange(from: "10.64.0.1/32")!, ] ) From 7d4683270b5d180f2cf059bc7e241fb46e748d5d Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Tue, 26 Mar 2024 17:16:13 +0100 Subject: [PATCH 14/44] Add safety checks at the FFI boundary --- .../PacketTunnelProvider+TCPConnection.swift | 18 +++++++++--------- .../PostQuantumKeyNegotiatior.swift | 2 +- .../talpid-tunnel-config-client/src/lib.rs | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift index 30a52e310358..48e350cec110 100644 --- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -14,12 +14,12 @@ import WireGuardKitTypes @_cdecl("swift_nw_tcp_connection_send") func tcpConnectionSend( - connection: UnsafeMutableRawPointer, + connection: UnsafeMutableRawPointer?, data: UnsafeMutableRawPointer, dataLength: UInt, - sender: UnsafeMutableRawPointer + sender: UnsafeMutableRawPointer? ) { - NSLog("\(#function) receiving raw pointer \(connection)") + guard let connection, let sender else { return } let tcpConnection = Unmanaged.fromOpaque(connection).takeUnretainedValue() let rawData = Data(bytes: data, count: Int(dataLength)) @@ -36,10 +36,10 @@ func tcpConnectionSend( @_cdecl("swift_nw_tcp_connection_read") func tcpConnectionReceive( - connection: UnsafeMutableRawPointer, - sender: UnsafeMutableRawPointer + connection: UnsafeMutableRawPointer?, + sender: UnsafeMutableRawPointer? ) { - NSLog("\(#function) receiving raw pointer \(connection)") + guard let connection, let sender else { return } let tcpConnection = Unmanaged.fromOpaque(connection).takeUnretainedValue() tcpConnection.readMinimumLength(0, maximumLength: Int(UInt16.max)) { data, maybeError in if let data { @@ -53,11 +53,11 @@ func tcpConnectionReceive( } @_cdecl("swift_post_quantum_key_ready") -func receivePostQuantumKey(rawPacketTunnel: UnsafeMutableRawPointer, rawPresharedKey: UnsafeMutableRawPointer) { +func receivePostQuantumKey(rawPacketTunnel: UnsafeMutableRawPointer?, rawPresharedKey: UnsafeMutableRawPointer?) { + guard let rawPacketTunnel else { return } let packetTunnel = Unmanaged.fromOpaque(rawPacketTunnel).takeUnretainedValue() guard let postQuantumKeyReceiver = packetTunnel as? PostQuantumKeyReceiving else { return } - - guard rawPresharedKey.hashValue != 0.hashValue else { + guard let rawPresharedKey else { postQuantumKeyReceiver.receivePostQuantumKey(.none) return } diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift index e9d1b7e413e5..fd9fda167282 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift @@ -32,7 +32,7 @@ public class PostQuantumKeyNegotiatior { packetTunnelPointer, opaqueConnection ) - guard token?.hashValue != 0.hashValue else { + guard let token else { // Handle failure here return } diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs index 81c5d272dcfa..17212a0a269f 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs @@ -121,7 +121,7 @@ impl IOSRuntime { Err(error) => { log::error!("Failed to create iOS TCP client: {error}"); unsafe { - swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null_mut()); + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null()); } return; } @@ -138,7 +138,7 @@ impl IOSRuntime { swift_post_quantum_key_ready(packet_tunnel_ptr, bytes.as_ptr()); }, Err(_) => unsafe { - swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null_mut()); + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null()); }, } } From f869246d9b27005894c1170ff35692f001e930d6 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 27 Mar 2024 08:20:42 +0100 Subject: [PATCH 15/44] Remove unneeded dependencies --- Cargo.lock | 2 -- ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml | 2 -- 2 files changed, 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 007b0d82000b..315419a73a10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4092,13 +4092,11 @@ version = "0.0.0" dependencies = [ "cbindgen", "classic-mceliece-rust", - "futures", "libc", "log", "oslog", "pqc_kyber", "prost", - "rand 0.8.5", "talpid-tunnel-config-client", "talpid-types", "tokio", diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml b/ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml index 902882201ab7..93ea14f99f15 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml @@ -15,9 +15,7 @@ talpid-tunnel-config-client = { path = "../../../talpid-tunnel-config-client" } talpid-types = { path = "../../../talpid-types" } log = { workspace = true } -rand = "0.8" tonic = { workspace = true } -futures = "0.3" tower = { workspace = true } prost = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } From d433f9a973ac2488e47243f0dc09c339cfd33763 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 27 Mar 2024 11:08:41 +0100 Subject: [PATCH 16/44] Tidy code around key negotiation and cancellation token flow --- .../PostQuantumKeyNegotiatior.swift | 7 +++---- .../PacketTunnelProvider.swift | 20 +++++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift index fd9fda167282..861ecee3033b 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift @@ -12,7 +12,7 @@ import TalpidTunnelConfigClientProxy import WireGuardKitTypes public class PostQuantumKeyNegotiatior { - private var cancellationToken: UnsafeRawPointer! + private var cancellationToken: UnsafeRawPointer? public init() {} public func negotiateKey( @@ -41,8 +41,7 @@ public class PostQuantumKeyNegotiatior { } public func cancelKeyNegotiation() { - if let cancellationToken, cancellationToken.hashValue != 0.hashValue { - cancel_post_quantum_key_exchange(cancellationToken) - } + guard let cancellationToken else { return } + cancel_post_quantum_key_exchange(cancellationToken) } } diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index e0f93c3eb2ce..95396be3640a 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -276,17 +276,15 @@ extension PacketTunnelProvider { .initial, .new, ]) { [weak self] observedConnection, _ in - guard let self else { return } - if observedConnection.isViable == true { - keyNegotiatior.negotiateKey( - gatewayIP: IPv4Gateway, - devicePublicKey: privateKey.publicKey, - presharedKey: postQuantumSharedKey.publicKey, - packetTunnel: self, - tcpConnection: tcpConnection - ) - self.tcpConnectionObserver.invalidate() - } + guard let self, observedConnection.isViable else { return } + keyNegotiatior.negotiateKey( + gatewayIP: IPv4Gateway, + devicePublicKey: privateKey.publicKey, + presharedKey: postQuantumSharedKey.publicKey, + packetTunnel: self, + tcpConnection: tcpConnection + ) + self.tcpConnectionObserver.invalidate() } inTunnelTCPConnection = tcpConnection From 912dfedb3f344a0dfcf6e648dfab7f3263671a71 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 27 Mar 2024 14:43:29 +0100 Subject: [PATCH 17/44] Add "tidy" as an imperative verb --- .github/workflows/git-commit-message-style.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/git-commit-message-style.yml b/.github/workflows/git-commit-message-style.yml index 3f57a51a160b..f18951f26d04 100644 --- a/.github/workflows/git-commit-message-style.yml +++ b/.github/workflows/git-commit-message-style.yml @@ -32,4 +32,8 @@ jobs: # This action defaults to 50 char subjects, but 72 is fine. max-subject-line-length: '72' # The action's wordlist is a bit short. Add more accepted verbs +<<<<<<< HEAD additional-verbs: 'tidy, wrap' +======= + additional-verbs: 'restart, coalesce, tidy' +>>>>>>> 52690ba8c (Add "tidy" as an imperative verb) From 835b4c203f6d00f7f006dad580405fb415692399 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 27 Mar 2024 19:33:46 +0100 Subject: [PATCH 18/44] Update git-commit-message-style to match main --- .github/workflows/git-commit-message-style.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/git-commit-message-style.yml b/.github/workflows/git-commit-message-style.yml index f18951f26d04..3f57a51a160b 100644 --- a/.github/workflows/git-commit-message-style.yml +++ b/.github/workflows/git-commit-message-style.yml @@ -32,8 +32,4 @@ jobs: # This action defaults to 50 char subjects, but 72 is fine. max-subject-line-length: '72' # The action's wordlist is a bit short. Add more accepted verbs -<<<<<<< HEAD additional-verbs: 'tidy, wrap' -======= - additional-verbs: 'restart, coalesce, tidy' ->>>>>>> 52690ba8c (Add "tidy" as an imperative verb) From 529f055e2ab59790bc01e011aa3620eb906f84d4 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 28 Mar 2024 09:29:40 +0100 Subject: [PATCH 19/44] Fix a typo --- .../PacketTunnelProvider/PacketTunnelProvider.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 95396be3640a..1eeec56b72bc 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -114,7 +114,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return } case .negotiatingPostQuantumKey: - // When negotiating post quantun keys, allow the connection to go through immediately. + // When negotiating post quantum keys, allow the connection to go through immediately. // Otherwise, the in-tunnel TCP connection will never become ready as the OS doesn't let // any traffic through until this function returns, which would prevent negotiating keys // from an unconnected state. From b1c5edbe8b611702b87ed2bd5fcf29002a2061ba Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 28 Mar 2024 14:47:10 +0100 Subject: [PATCH 20/44] Remove unneeded framework reference --- ios/MullvadVPN.xcodeproj/project.pbxproj | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 23a5b4b9c8ff..a9d85366bfe7 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -1211,13 +1211,6 @@ remoteGlobalIDString = 5840231E2A406BF5007B27AC; remoteInfo = TunnelObfuscation; }; - F04F959E2B21D02700431E08 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 58CE5E58224146200008646E /* Project object */; - proxyType = 1; - remoteGlobalIDString = 06799ABB28F98E1D00ACD94E; - remoteInfo = MullvadREST; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -4251,7 +4244,6 @@ buildRules = ( ); dependencies = ( - F04F959F2B21D02700431E08 /* PBXTargetDependency */, A91614D32B108F4D00F416EB /* PBXTargetDependency */, ); name = TunnelObfuscation; @@ -6203,11 +6195,6 @@ target = 5840231E2A406BF5007B27AC /* TunnelObfuscation */; targetProxy = A9EC20F12A5D79ED0040D56E /* PBXContainerItemProxy */; }; - F04F959F2B21D02700431E08 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 06799ABB28F98E1D00ACD94E /* MullvadREST */; - targetProxy = F04F959E2B21D02700431E08 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ From 98f4526d4b01b76dfb22b50ddeda39a358cb09f1 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 28 Mar 2024 16:22:42 +0100 Subject: [PATCH 21/44] Rewrite the PQ cancellation token in a safer way --- .../PostQuantumKeyNegotiatior.swift | 23 +++++---- .../include/talpid_tunnel_config_client.h | 17 +++++-- .../src/ios_ffi.rs | 47 ++++++++++++------- .../talpid-tunnel-config-client/src/lib.rs | 41 +++++++++++++--- .../Actor/State+Extensions.swift | 2 + 5 files changed, 93 insertions(+), 37 deletions(-) diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift index 861ecee3033b..9bdd8b41e838 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift @@ -12,9 +12,10 @@ import TalpidTunnelConfigClientProxy import WireGuardKitTypes public class PostQuantumKeyNegotiatior { - private var cancellationToken: UnsafeRawPointer? public init() {} + var cancelToken: PostQuantumCancelToken? + public func negotiateKey( gatewayIP: IPv4Address, devicePublicKey: PublicKey, @@ -24,24 +25,30 @@ public class PostQuantumKeyNegotiatior { ) { 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 token = negotiate_post_quantum_key( + let result = negotiate_post_quantum_key( devicePublicKey.rawValue.map { $0 }, presharedKey.rawValue.map { $0 }, packetTunnelPointer, - opaqueConnection + opaqueConnection, + &cancelToken ) - guard let token else { + guard result == 0 else { // Handle failure here return } - - cancellationToken = token + self.cancelToken = cancelToken } public func cancelKeyNegotiation() { - guard let cancellationToken else { return } - cancel_post_quantum_key_exchange(cancellationToken) + guard var cancelToken else { return } + cancel_post_quantum_key_exchange(&cancelToken) + } + + deinit { + guard var cancelToken else { return } + drop_post_quantum_key_exchange_token(&cancelToken) } } 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 5d4604a59e39..c14dda4e12ba 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 @@ -3,7 +3,13 @@ #include #include -void cancel_post_quantum_key_exchange(const void *sender); +typedef struct PostQuantumCancelToken { + void *context; +} PostQuantumCancelToken; + +void cancel_post_quantum_key_exchange(const struct PostQuantumCancelToken *sender); + +void drop_post_quantum_key_exchange_token(const struct PostQuantumCancelToken *sender); /** * Callback to call when the TCP connection has written data. @@ -21,10 +27,11 @@ void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender); * # Safety * This function is safe to call */ -const void *negotiate_post_quantum_key(const uint8_t *public_key, - const uint8_t *ephemeral_public_key, - const void *packet_tunnel, - const void *tcp_connection); +int32_t negotiate_post_quantum_key(const uint8_t *public_key, + const uint8_t *ephemeral_public_key, + const void *packet_tunnel, + const void *tcp_connection, + struct PostQuantumCancelToken *cancel_token); /** * Called when there is data to send on the TCP connection. diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs index 78db55283f06..b69a1abdf4ec 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs @@ -1,22 +1,26 @@ use libc::c_void; use tokio::sync::mpsc; +use crate::PostQuantumCancelToken; + use super::run_ios_runtime; -use std::{rc::Weak, sync::Once}; +use std::sync::Once; static INIT_LOGGING: Once = Once::new(); -#[allow(clippy::let_underscore_future)] #[no_mangle] -pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const c_void) { - // 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: Weak> = unsafe { Weak::from_raw(sender as _) }; - if let Some(tx) = send_tx.upgrade() { - // # Safety - // Clippy warns of a non-binding let on a future, this future is being awaited on. - _ = tx.send(()); - } +pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const PostQuantumCancelToken) { + let sender = unsafe { &*sender }; + sender.cancel(); } + +#[no_mangle] +pub unsafe extern "C" fn drop_post_quantum_key_exchange_token( + sender: *const PostQuantumCancelToken, +) { + let _sender = unsafe { std::ptr::read(sender) }; +} + /// 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) { @@ -45,7 +49,8 @@ pub unsafe extern "C" fn negotiate_post_quantum_key( ephemeral_public_key: *const u8, packet_tunnel: *const c_void, tcp_connection: *const c_void, -) -> *const c_void { + cancel_token: *mut PostQuantumCancelToken, +) -> i32 { INIT_LOGGING.call_once(|| { let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.TTCC") .level_filter(log::LevelFilter::Trace) @@ -56,10 +61,18 @@ pub unsafe extern "C" fn negotiate_post_quantum_key( let eph_pub_key_copy: [u8; 32] = unsafe { std::ptr::read(ephemeral_public_key as *const [u8; 32]) }; - run_ios_runtime( - pub_key_copy, - eph_pub_key_copy, - packet_tunnel, - tcp_connection, - ) + match unsafe { + run_ios_runtime( + pub_key_copy, + eph_pub_key_copy, + packet_tunnel, + tcp_connection, + ) + } { + Ok(token) => { + unsafe { std::ptr::write(cancel_token, token) }; + 0 + } + Err(err) => err, + } } diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs index 17212a0a269f..079baf102708 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs @@ -18,24 +18,51 @@ use talpid_types::net::wireguard::PublicKey; use tonic::transport::Endpoint; use tower::service_fn; +#[repr(C)] +pub struct PostQuantumCancelToken { + // Must keep a pointer to a valid std::sync::Arc + pub context: *mut c_void, +} + +impl PostQuantumCancelToken { + /// #Safety + /// This function can only be called when the context pointer is valid. + unsafe fn cancel(&self) { + // 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(()); + std::mem::forget(send_tx); + } +} + +impl Drop for PostQuantumCancelToken { + fn drop(&mut self) { + let _: Arc> = unsafe { Arc::from_raw(self.context as _) }; + } +} +unsafe impl Send for PostQuantumCancelToken {} + /// # Safety -/// This function is safe to call +/// 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_key: [u8; 32], ephemeral_pub_key: [u8; 32], packet_tunnel: *const c_void, tcp_connection: *const c_void, -) -> *const c_void { - match IOSRuntime::new(pub_key, ephemeral_pub_key, packet_tunnel, tcp_connection) { +) -> Result { + match unsafe { IOSRuntime::new(pub_key, ephemeral_pub_key, packet_tunnel, tcp_connection) } { Ok(runtime) => { - let weak_cancel_token = Arc::downgrade(&runtime.cancel_token_tx); - let token = weak_cancel_token.into_raw() as _; + let token = runtime.cancel_token_tx.clone(); + runtime.run(); - token + Ok(PostQuantumCancelToken { + context: Arc::into_raw(token) as *mut _, + }) } Err(err) => { log::error!("Failed to create runtime {}", err); - std::ptr::null() + Err(-1) } } } diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift index 5737aa413818..93fe29f3b731 100644 --- a/ios/PacketTunnelCore/Actor/State+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift @@ -91,6 +91,7 @@ extension State { let .connecting(connState), let .connected(connState), let .reconnecting(connState), + let .negotiatingPostQuantumKey(connState, _), let .disconnecting(connState): connState default: nil } @@ -121,6 +122,7 @@ extension State { case .connected: .connected(newValue) case .reconnecting: .reconnecting(newValue) case .disconnecting: .disconnecting(newValue) + case let .negotiatingPostQuantumKey(_, privateKey): .negotiatingPostQuantumKey(newValue, privateKey) default: self } } From 1473189da94aa7da915d1b539123e5c324a857d2 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Mon, 8 Apr 2024 17:17:37 +0200 Subject: [PATCH 22/44] Fix rebase issues --- .../Coordinators/ApplicationCoordinator.swift | 3 ++- .../MapConnectionStatusOperation.swift | 6 +++--- .../TunnelManager/StopTunnelOperation.swift | 3 ++- ios/MullvadVPN/TunnelManager/TunnelManager.swift | 2 +- ios/MullvadVPN/TunnelManager/TunnelState.swift | 9 +++++---- .../Tunnel/TunnelControlView.swift | 14 ++++++++------ .../Tunnel/TunnelViewController.swift | 2 +- .../Actor/PacketTunnelActor+KeyPolicy.swift | 4 ---- ios/PacketTunnelCore/Actor/State+Extensions.swift | 2 -- 9 files changed, 22 insertions(+), 23 deletions(-) diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift index 673f0602049e..80352cdda2c8 100644 --- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift @@ -980,7 +980,8 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo guard tunnelManager.deviceState.isLoggedIn else { return false } switch tunnelManager.tunnelStatus.state { - case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error, .negotiatingKey: + case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error, + .negotiatingPostQuantumKey: tunnelManager.reconnectTunnel(selectNewRelay: true) case .disconnecting, .disconnected: diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift index a6a604569e0a..f92fd37e077b 100644 --- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift @@ -57,9 +57,9 @@ class MapConnectionStatusOperation: AsyncOperation { return connectionState.isNetworkReachable ? .connecting(connectionState.selectedRelay) : .waitingForConnectivity(.noConnection) - case let .negotiatingPostQuantumKey(connectionState): - connectionState.isNetworkReachable - ? .negotiatingPostQuantumKey(connectionState.selectedRelay) + case let .negotiatingPostQuantumKey(connectionState, privateKey): + return connectionState.isNetworkReachable + ? .negotiatingPostQuantumKey(connectionState.selectedRelay, privateKey) : .waitingForConnectivity(.noConnection) case let .reconnecting(connectionState): return connectionState.isNetworkReachable diff --git a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift index 0448de22fd49..1bd7a4f9c783 100644 --- a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift @@ -35,7 +35,8 @@ class StopTunnelOperation: ResultOperation { finish(result: .success(())) - case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error, .negotiatingKey: + case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error, + .negotiatingPostQuantumKey: doShutDownTunnel() case .disconnected, .disconnecting, .pendingReconnect, .waitingForConnectivity(.noNetwork): diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index ac0164e93b33..8f5ffdf2a3ab 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -675,7 +675,7 @@ final class TunnelManager: StorePaymentObserver { // while the tunnel process is trying to connect. startPollingTunnelStatus(interval: establishingTunnelStatusPollInterval) - case .negotiatingKey: + case .negotiatingPostQuantumKey: // No need to poll the tunnel while negotiating post quantum keys, assume the connection will work break diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift index 7115c472d315..1d480fe3a53f 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelState.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift @@ -9,6 +9,7 @@ import Foundation import MullvadTypes import PacketTunnelCore +import WireGuardKitTypes /// A struct describing the tunnel status. struct TunnelStatus: Equatable, CustomStringConvertible { @@ -51,7 +52,7 @@ enum TunnelState: Equatable, CustomStringConvertible { case connecting(SelectedRelay?) /// Negotiating a key for post-quantum resistance - case negotiatingKey(SelectedRelay) + case negotiatingPostQuantumKey(SelectedRelay, PrivateKey) /// Connected the tunnel case connected(SelectedRelay) @@ -97,7 +98,7 @@ enum TunnelState: Equatable, CustomStringConvertible { "waiting for connectivity" case let .error(blockedStateReason): "error state: \(blockedStateReason)" - case let .negotiatingKey(tunnelRelay): + case let .negotiatingPostQuantumKey(tunnelRelay, _): "negotiating key with \(tunnelRelay.hostname)" } } @@ -109,14 +110,14 @@ enum TunnelState: Equatable, CustomStringConvertible { true case .pendingReconnect, .disconnecting, .disconnected, .waitingForConnectivity(.noNetwork), .error: false - case .negotiatingKey: + case .negotiatingPostQuantumKey: false } } var relay: SelectedRelay? { switch self { - case let .connected(relay), let .reconnecting(relay), let .negotiatingKey(relay): + case let .connected(relay), let .reconnecting(relay), let .negotiatingPostQuantumKey(relay, _): relay case let .connecting(relay): relay diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift index 84be14ae43d3..75f26547476a 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift @@ -454,7 +454,7 @@ final class TunnelControlView: UIView { private extension TunnelState { var textColorForSecureLabel: UIColor { switch self { - case .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .negotiatingKey: + case .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .negotiatingPostQuantumKey: .white case .connected: @@ -484,7 +484,7 @@ private extension TunnelState { ) // TODO: Is this the correct message here ? - case .negotiatingKey: + case .negotiatingPostQuantumKey: NSLocalizedString( "TUNNEL_STATE_NEGOTIATING_KEY", tableName: "Main", @@ -567,7 +567,8 @@ private extension TunnelState { comment: "" ) - case .negotiatingKey: + // TODO: Is this correct ? + case .negotiatingPostQuantumKey: NSLocalizedString( "SWITCH_LOCATION_BUTTON_TITLE", tableName: "Main", @@ -587,7 +588,8 @@ private extension TunnelState { comment: "" ) - case .negotiatingKey: + // TODO: Is this correct ? + case .negotiatingPostQuantumKey: NSLocalizedString( "TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL", tableName: "Main", @@ -672,7 +674,7 @@ private extension TunnelState { .waitingForConnectivity(.noConnection): [.selectLocation, .cancel] - case .negotiatingKey: + case .negotiatingPostQuantumKey: [.selectLocation, .cancel] case .connected, .reconnecting, .error: @@ -688,7 +690,7 @@ private extension TunnelState { .waitingForConnectivity(.noConnection): [.cancel] - case .negotiatingKey: + case .negotiatingPostQuantumKey: [.cancel] case .connected, .reconnecting, .error: [.disconnect] diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index f257928386b6..ce7b72a9f357 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -152,7 +152,7 @@ class TunnelViewController: UIViewController, RootContainment { contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelay?.location.geoCoordinate, animated: animated) - case let .reconnecting(tunnelRelay), let .negotiatingKey(tunnelRelay): + case let .reconnecting(tunnelRelay), let .negotiatingPostQuantumKey(tunnelRelay, _): mapViewController.removeLocationMarker() contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelay.location.geoCoordinate, animated: animated) diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift index e0b49ada551b..9181a73edfd5 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift @@ -36,11 +36,7 @@ extension PacketTunnelActor { // Move currentKey into keyPolicy. stateData.keyPolicy = .usePrior(currentKey, startKeySwitchTask()) stateData.currentKey = nil - } - - _ = state.mutateConnectionState(connectionStateMutator) || - state.mutateBlockedState(blockedStateMutator) } /** diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift index 93fe29f3b731..01913b4e24ce 100644 --- a/ios/PacketTunnelCore/Actor/State+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift @@ -95,7 +95,6 @@ extension State { let .disconnecting(connState): connState default: nil } - return modified } var blockedData: State.BlockingData? { @@ -149,7 +148,6 @@ extension State { default: break - } } From 4db3f95e37f922b1c27c5f1a0e7c2300de39d1e5 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Wed, 10 Apr 2024 11:55:20 +0200 Subject: [PATCH 23/44] Add a method to receive the preshared key in the actor --- ios/PacketTunnelCore/Actor/PacketTunnelActor.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 887cff157de8..ee1394de8f7a 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -113,8 +113,8 @@ public actor PacketTunnelActor { case let .networkReachability(defaultPath): await handleDefaultPathChange(defaultPath) - case .replaceDevicePrivateKey: - self.logger.warning("Not yet implemented") + case let .replaceDevicePrivateKey(preSharedKey): + await postQuantumConnect(with: preSharedKey) } } } @@ -226,6 +226,8 @@ extension PacketTunnelActor { } } + private func postQuantumConnect(with key: PreSharedKey) async {} + /** Attempt to start the tunnel by performing the following steps: From 5b83fdefc046466ed0edb92bc5a09f889ba48bbd Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 10 Apr 2024 14:19:17 +0200 Subject: [PATCH 24/44] Add preSharedKey to configurationBuilder --- ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift index b389a168bcc9..79833d16c2aa 100644 --- a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift +++ b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift @@ -27,19 +27,23 @@ public struct ConfigurationBuilder { var dns: SelectedDNSServers? var endpoint: MullvadEndpoint? var allowedIPs: [IPAddressRange] + // or should this go in MullvadEndpoint? + var preSharedKey: PreSharedKey? public init( privateKey: PrivateKey, interfaceAddresses: [IPAddressRange], dns: SelectedDNSServers? = nil, endpoint: MullvadEndpoint? = nil, - allowedIPs: [IPAddressRange] + allowedIPs: [IPAddressRange], + preSharedKey: PreSharedKey? = nil ) { self.privateKey = privateKey self.interfaceAddresses = interfaceAddresses self.dns = dns self.endpoint = endpoint self.allowedIPs = allowedIPs + self.preSharedKey = preSharedKey } public func makeConfiguration() throws -> TunnelAdapterConfiguration { @@ -62,7 +66,8 @@ public struct ConfigurationBuilder { return TunnelPeer( endpoint: .ipv4(endpoint.ipv4Relay), - publicKey: publicKey + publicKey: publicKey, + preSharedKey: preSharedKey ) } } From 7bf1753ebddda5d119f86e8200e593d329c2ec8f Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 10 Apr 2024 15:34:10 +0200 Subject: [PATCH 25/44] Attempt at starting a tunnel with the PQ shared key --- .../Actor/PacketTunnelActor.swift | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index ee1394de8f7a..efccb030974a 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -226,7 +226,40 @@ extension PacketTunnelActor { } } - private func postQuantumConnect(with key: PreSharedKey) async {} + private func postQuantumConnect(with key: PreSharedKey) async { + guard + let settings: Settings = try? settingsReader.read(), + let connectionState = try? obfuscateConnection(nextRelay: .random, settings: settings, reason: .userInitiated), + let targetState = state.targetStateForReconnect + else { return } + let activeKey = activeKey(from: connectionState, in: settings) + let configurationBuilder = ConfigurationBuilder( + privateKey: activeKey, + interfaceAddresses: settings.interfaceAddresses, + dns: settings.dnsServers, + endpoint: connectionState.connectedEndpoint, + allowedIPs: [ + IPAddressRange(from: "0.0.0.0/0")!, + IPAddressRange(from: "::/0")!, + ], + preSharedKey: key + ) + stopDefaultPathObserver() + + defer { + // Restart default path observer and notify the observer with the current path that might have changed while + // path observer was paused. + startDefaultPathObserver(notifyObserverWithCurrentPath: true) + } + + guard let _ = try? await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration()) else { + return + } + + // 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: From ea93d6d006cad542afce7d9a6b3d441a7d36a530 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 11 Apr 2024 10:06:37 +0200 Subject: [PATCH 26/44] Hack the new private key as a global to prove PQ PSK works --- .../Tunnel/TunnelControlView.swift | 2 +- .../PacketTunnelProvider.swift | 1 + .../Actor/ObservedState+Extensions.swift | 2 +- .../Actor/PacketTunnelActor.swift | 30 +++++++++++++------ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift index 75f26547476a..2d348b252835 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift @@ -488,7 +488,7 @@ private extension TunnelState { NSLocalizedString( "TUNNEL_STATE_NEGOTIATING_KEY", tableName: "Main", - value: "Negotiating key", + value: "Creating quantum secure connection", comment: "" ) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 1eeec56b72bc..b1b72b92c2fb 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -272,6 +272,7 @@ extension PacketTunnelProvider { ) let postQuantumSharedKey = PrivateKey() // This will become the new private key of the device + PacketTunnelActor.newPQPrivateKey = postQuantumSharedKey let observer = tcpConnection.observe(\.isViable, options: [ .initial, .new, diff --git a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift index 2452ee0d1001..cf6d786bebe0 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift @@ -31,7 +31,7 @@ extension ObservedState { case .connecting: "Connecting" case .negotiatingPostQuantumKey: - "Negotiating key" + "Negotiating Post Quantum Secure Key" case .reconnecting: "Reconnecting" case .disconnecting: diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index efccb030974a..c0c4d1276006 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -36,6 +36,8 @@ public actor PacketTunnelActor { @Published internal(set) public var observedState: ObservedState = .initial + public static var newPQPrivateKey: PrivateKey! + let logger = Logger(label: "PacketTunnelActor") let timings: PacketTunnelActorTimings @@ -203,7 +205,8 @@ extension PacketTunnelActor { case .negotiatingPostQuantumKey: // There is no connection monitoring going on when exchanging keys. // The procedure starts from scratch for each reconnection attempts. - try await tryStart(nextRelay: nextRelay, reason: reason) +// try await tryStart(nextRelay: nextRelay, reason: reason) + break // DO nothing at all for the moment case .connecting, .connected, .reconnecting, .error: switch reason { case .connectionLoss: @@ -229,12 +232,18 @@ extension PacketTunnelActor { private func postQuantumConnect(with key: PreSharedKey) async { guard let settings: Settings = try? settingsReader.read(), - let connectionState = try? obfuscateConnection(nextRelay: .random, settings: settings, reason: .userInitiated), + let connectionState = try? obfuscateConnection( + nextRelay: .random, + settings: settings, + reason: .userInitiated + ), let targetState = state.targetStateForReconnect else { return } + let activeKey = activeKey(from: connectionState, in: settings) let configurationBuilder = ConfigurationBuilder( - privateKey: activeKey, + // privateKey: activeKey, + privateKey: Self.newPQPrivateKey, interfaceAddresses: settings.interfaceAddresses, dns: settings.dnsServers, endpoint: connectionState.connectedEndpoint, @@ -246,19 +255,22 @@ extension PacketTunnelActor { ) stopDefaultPathObserver() + switch targetState { + case .connecting: + state = .connecting(connectionState) + case .reconnecting: + state = .reconnecting(connectionState) + } + defer { // Restart default path observer and notify the observer with the current path that might have changed while // path observer was paused. - startDefaultPathObserver(notifyObserverWithCurrentPath: true) - } - - guard let _ = try? await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration()) else { - return + startDefaultPathObserver(notifyObserverWithCurrentPath: false) } + 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) - } /** From 984fe97566c796e7cd704aff035c3655c0ec63ce Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Fri, 12 Apr 2024 11:31:59 +0200 Subject: [PATCH 27/44] Prototype a new actor to handle PQ PSK --- ios/MullvadVPN.xcodeproj/project.pbxproj | 4 +++ .../PacketTunnelProvider.swift | 27 ++++++++++++------- .../PostQuantumKeyExchangeActor.swift | 26 ++++++++++++++++++ .../Actor/PacketTunnelActorCommand.swift | 1 + 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 ios/PacketTunnel/PostQuantumKeyExchangeActor.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index a9d85366bfe7..bc6499b0bfe0 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -696,6 +696,7 @@ A944F2632B8DEFDB00473F4C /* MullvadPostQuantum.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiatior.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiatior.swift */; }; A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client_proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.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 */; }; A95EEE362B722CD600A8A39B /* TunnelMonitorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95EEE352B722CD600A8A39B /* TunnelMonitorState.swift */; }; @@ -2004,6 +2005,7 @@ A944F25E2B8DEFDB00473F4C /* MullvadPostQuantum.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadPostQuantum.h; sourceTree = ""; }; A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtalpid_tunnel_config_client_proxy.a; path = "../target/aarch64-apple-ios/debug/libtalpid_tunnel_config_client_proxy.a"; sourceTree = ""; }; A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTests.swift; sourceTree = ""; }; + A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangeActor.swift; sourceTree = ""; }; A95EEE352B722CD600A8A39B /* TunnelMonitorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorState.swift; sourceTree = ""; }; A95EEE372B722DFC00A8A39B /* PingStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PingStats.swift; sourceTree = ""; }; A9630E3B2B8E0E7B00A65999 /* talpid_tunnel_config_client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = talpid_tunnel_config_client.h; path = "talpid-tunnel-config-client/include/talpid_tunnel_config_client.h"; sourceTree = ""; }; @@ -3470,6 +3472,7 @@ 58F3F3682AA08E2200D3B0A4 /* PacketTunnelProvider */, 58915D662A25F9F20066445B /* DeviceCheck */, 588395612A9DF497008B63F6 /* WireGuardAdapter */, + A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */, ); path = PacketTunnel; sourceTree = ""; @@ -5791,6 +5794,7 @@ 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 */, diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index b1b72b92c2fb..77533c22b566 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -257,22 +257,29 @@ extension PacketTunnelProvider { } } - private func startPostQuantumKeyNegotation(with privateKey: PrivateKey) { - quantumKeyNegotiatior?.cancelKeyNegotiation() - - let keyNegotiatior = PostQuantumKeyNegotiatior() - let gatewayAddress = "10.64.0.1" - let IPv4Gateway = IPv4Address(gatewayAddress)! + func createTCPConnectionForPQPSK(_ gatewayAddress: String) -> NWTCPConnection { let gatewayEndpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337") - let tcpConnection = createTCPConnectionThroughTunnel( + return createTCPConnectionThroughTunnel( to: gatewayEndpoint, enableTLS: false, tlsParameters: nil, delegate: nil ) + } + + private func startPostQuantumKeyNegotation(with privateKey: PrivateKey) { + quantumKeyNegotiatior?.cancelKeyNegotiation() + + let keyNegotiatior = PostQuantumKeyNegotiatior() + + let gatewayAddress = "10.64.0.1" + let IPv4Gateway = IPv4Address(gatewayAddress)! + let tcpConnection = createTCPConnectionForPQPSK(gatewayAddress) - let postQuantumSharedKey = PrivateKey() // This will become the new private key of the device - PacketTunnelActor.newPQPrivateKey = postQuantumSharedKey + // TODO: Pass the private key to rust directly + // It will derive the public key, and give us back both the preshared key, and the ephemeral private key + let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device + PacketTunnelActor.newPQPrivateKey = ephemeralSharedKey let observer = tcpConnection.observe(\.isViable, options: [ .initial, .new, @@ -281,7 +288,7 @@ extension PacketTunnelProvider { keyNegotiatior.negotiateKey( gatewayIP: IPv4Gateway, devicePublicKey: privateKey.publicKey, - presharedKey: postQuantumSharedKey.publicKey, + presharedKey: ephemeralSharedKey.publicKey, packetTunnel: self, tcpConnection: tcpConnection ) diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift new file mode 100644 index 000000000000..47a93c6663e4 --- /dev/null +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -0,0 +1,26 @@ +// +// PostQuantumKeyExchangeActor.swift +// PacketTunnel +// +// Created by Marco Nikic on 2024-04-12. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadPostQuantum +import NetworkExtension + +typealias InTunnelTCPConnectionCreator = (NWHostEndpoint) -> NWTCPConnection + +actor PostQuantumKeyExchangeActor { + let createNetworkConnection: InTunnelTCPConnectionCreator + unowned let packetTunnel: PacketTunnelProvider + private var quantumKeyNegotiatior: PostQuantumKeyNegotiatior! + private var inTunnelTCPConnection: NWTCPConnection! + private var tcpConnectionObserver: NSKeyValueObservation! + + init(packetTunnel: PacketTunnelProvider, createNetworkConnection: @escaping InTunnelTCPConnectionCreator) { + self.packetTunnel = packetTunnel + self.createNetworkConnection = createNetworkConnection + } +} diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift index be325fc7b65a..58600c633170 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift @@ -36,6 +36,7 @@ extension PacketTunnelActor { /// Network reachability events. case networkReachability(NetworkPath) + // TODO: Add the device's ephemeral new private key here /// Update the device private key, as per post-quantum protocols case replaceDevicePrivateKey(PreSharedKey) From 1629a5bd91b94bd79c1abc29ce82f27d6c12708d Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Fri, 12 Apr 2024 14:46:02 +0200 Subject: [PATCH 28/44] Add the ephemeral private key to the negotiation data flow (Swift-side only; Rust needs work) --- .../PacketTunnelProvider+TCPConnection.swift | 17 +++++++++++++---- .../PostQuantumKeyNegotiatior.swift | 2 +- .../Protocols/PostQuantumKeyReceiving.swift | 2 +- .../PacketTunnelProvider.swift | 6 +++--- .../Actor/PacketTunnelActor+Public.swift | 4 ++-- .../Actor/PacketTunnelActor.swift | 2 +- .../Actor/PacketTunnelActorCommand.swift | 2 +- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift index 48e350cec110..875f6ca5cd8d 100644 --- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -53,17 +53,26 @@ func tcpConnectionReceive( } @_cdecl("swift_post_quantum_key_ready") -func receivePostQuantumKey(rawPacketTunnel: UnsafeMutableRawPointer?, rawPresharedKey: UnsafeMutableRawPointer?) { - guard let rawPacketTunnel else { return } +func receivePostQuantumKey( + rawPacketTunnel: UnsafeMutableRawPointer?, + rawPresharedKey: UnsafeMutableRawPointer?, + rawEphemeralKey: UnsafeMutableRawPointer? +) { + guard + let rawPacketTunnel, + let rawEphemeralKey, + let ephemeralKey = PrivateKey(rawValue: Data(bytes: rawEphemeralKey, count: 32)) + else { return } let packetTunnel = Unmanaged.fromOpaque(rawPacketTunnel).takeUnretainedValue() + guard let postQuantumKeyReceiver = packetTunnel as? PostQuantumKeyReceiving else { return } guard let rawPresharedKey else { - postQuantumKeyReceiver.receivePostQuantumKey(.none) + postQuantumKeyReceiver.receivePostQuantumKey(.none, ephemeralKey: ephemeralKey) return } let presharedKey = Data(bytes: rawPresharedKey, count: 32) if let key = PreSharedKey(rawValue: presharedKey) { - postQuantumKeyReceiver.receivePostQuantumKey(key) + postQuantumKeyReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey) } } diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift index 9bdd8b41e838..377837bf178f 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift @@ -19,7 +19,7 @@ public class PostQuantumKeyNegotiatior { public func negotiateKey( gatewayIP: IPv4Address, devicePublicKey: PublicKey, - presharedKey: PublicKey, + presharedKey: PrivateKey, packetTunnel: NEPacketTunnelProvider, tcpConnection: NWTCPConnection ) { diff --git a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift index 714f4cd33e5b..a91ab0872da6 100644 --- a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift +++ b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift @@ -10,7 +10,7 @@ import Foundation import WireGuardKitTypes public protocol PostQuantumKeyReceiving { - func receivePostQuantumKey(_ key: PreSharedKey?) + func receivePostQuantumKey(_ key: PreSharedKey?, ephemeralKey: PrivateKey) } public enum PostQuantumKeyReceivingError: Error { diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 77533c22b566..b077909fc194 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -288,7 +288,7 @@ extension PacketTunnelProvider { keyNegotiatior.negotiateKey( gatewayIP: IPv4Gateway, devicePublicKey: privateKey.publicKey, - presharedKey: ephemeralSharedKey.publicKey, + presharedKey: ephemeralSharedKey, packetTunnel: self, tcpConnection: tcpConnection ) @@ -338,7 +338,7 @@ extension PacketTunnelProvider { } extension PacketTunnelProvider: PostQuantumKeyReceiving { - func receivePostQuantumKey(_ key: PreSharedKey?) { + func receivePostQuantumKey(_ key: PreSharedKey?, ephemeralKey: PrivateKey) { quantumKeyNegotiatior?.cancelKeyNegotiation() tcpConnectionObserver?.invalidate() inTunnelTCPConnection.cancel() @@ -347,7 +347,7 @@ extension PacketTunnelProvider: PostQuantumKeyReceiving { quantumKeyNegotiatior = nil if let key { - actor.replacePreSharedKey(key) + actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) } else { actor.reconnect(to: .current) } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift index 0d80fb6d580a..209a6041d54c 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift @@ -56,8 +56,8 @@ extension PacketTunnelActor { - Parameter key: the new key */ - nonisolated public func replacePreSharedKey(_ key: PreSharedKey) { - commandChannel.send(.replaceDevicePrivateKey(key)) + nonisolated public func replacePreSharedKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) { + commandChannel.send(.replaceDevicePrivateKey(key, ephemeralKey: ephemeralKey)) } /** diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index c0c4d1276006..ccc17bbc988f 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -115,7 +115,7 @@ public actor PacketTunnelActor { case let .networkReachability(defaultPath): await handleDefaultPathChange(defaultPath) - case let .replaceDevicePrivateKey(preSharedKey): + case let .replaceDevicePrivateKey(preSharedKey, ephemeralKey): await postQuantumConnect(with: preSharedKey) } } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift index 58600c633170..dc3735a9e77f 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift @@ -38,7 +38,7 @@ extension PacketTunnelActor { // TODO: Add the device's ephemeral new private key here /// Update the device private key, as per post-quantum protocols - case replaceDevicePrivateKey(PreSharedKey) + case replaceDevicePrivateKey(PreSharedKey, ephemeralKey: PrivateKey) /// Format command for log output. func logFormat() -> String { From 94ccbf89c21d50fb38c10539ce6e9d3932ad9143 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Fri, 12 Apr 2024 15:10:17 +0200 Subject: [PATCH 29/44] Pass the ephemeral key to postQuantumConnect --- .../PacketTunnelProvider/PacketTunnelProvider.swift | 1 - ios/PacketTunnelCore/Actor/PacketTunnelActor.swift | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index b077909fc194..08cadd6fed2c 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -279,7 +279,6 @@ extension PacketTunnelProvider { // TODO: Pass the private key to rust directly // It will derive the public key, and give us back both the preshared key, and the ephemeral private key let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device - PacketTunnelActor.newPQPrivateKey = ephemeralSharedKey let observer = tcpConnection.observe(\.isViable, options: [ .initial, .new, diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index ccc17bbc988f..8076f3e01237 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -36,8 +36,6 @@ public actor PacketTunnelActor { @Published internal(set) public var observedState: ObservedState = .initial - public static var newPQPrivateKey: PrivateKey! - let logger = Logger(label: "PacketTunnelActor") let timings: PacketTunnelActorTimings @@ -116,7 +114,7 @@ public actor PacketTunnelActor { await handleDefaultPathChange(defaultPath) case let .replaceDevicePrivateKey(preSharedKey, ephemeralKey): - await postQuantumConnect(with: preSharedKey) + await postQuantumConnect(with: preSharedKey, privateKey: ephemeralKey) } } } @@ -229,7 +227,7 @@ extension PacketTunnelActor { } } - private func postQuantumConnect(with key: PreSharedKey) async { + private func postQuantumConnect(with key: PreSharedKey, privateKey: PrivateKey) async { guard let settings: Settings = try? settingsReader.read(), let connectionState = try? obfuscateConnection( @@ -243,7 +241,7 @@ extension PacketTunnelActor { let activeKey = activeKey(from: connectionState, in: settings) let configurationBuilder = ConfigurationBuilder( // privateKey: activeKey, - privateKey: Self.newPQPrivateKey, + privateKey: privateKey, interfaceAddresses: settings.interfaceAddresses, dns: settings.dnsServers, endpoint: connectionState.connectedEndpoint, From 4a6f6264689311031abd53a0e6068c1cc8221175 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Mon, 15 Apr 2024 14:43:11 +0200 Subject: [PATCH 30/44] Modify (partially) Rust code to handle the ephemeral key as passed through --- .../src/ios_ffi.rs | 9 +++++---- .../talpid-tunnel-config-client/src/lib.rs | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs index b69a1abdf4ec..56906d1296a9 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs @@ -46,7 +46,8 @@ pub unsafe extern "C" fn handle_recv(data: *const u8, data_len: usize, sender: * #[no_mangle] pub unsafe extern "C" fn negotiate_post_quantum_key( public_key: *const u8, - ephemeral_public_key: *const u8, + /// ephemeral_key is now the private key that's being passed back + ephemeral_key: *const u8, packet_tunnel: *const c_void, tcp_connection: *const c_void, cancel_token: *mut PostQuantumCancelToken, @@ -58,13 +59,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_pub_key_copy: [u8; 32] = - unsafe { std::ptr::read(ephemeral_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_pub_key_copy, + eph_key_copy, packet_tunnel, tcp_connection, ) diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs index 079baf102708..a940769c2ee1 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs @@ -47,11 +47,11 @@ unsafe impl Send for PostQuantumCancelToken {} /// pub unsafe fn run_ios_runtime( pub_key: [u8; 32], - ephemeral_pub_key: [u8; 32], + ephemeral_key: [u8; 32], packet_tunnel: *const c_void, tcp_connection: *const c_void, ) -> Result { - match unsafe { IOSRuntime::new(pub_key, ephemeral_pub_key, packet_tunnel, tcp_connection) } { + match unsafe { IOSRuntime::new(pub_key, ephemeral_key, packet_tunnel, tcp_connection) } { Ok(runtime) => { let token = runtime.cancel_token_tx.clone(); @@ -79,7 +79,7 @@ unsafe impl Sync for SwiftContext {} struct IOSRuntime { runtime: tokio::runtime::Runtime, pub_key: [u8; 32], - ephemeral_public_key: [u8; 32], + ephemeral_key: [u8; 32], packet_tunnel: SwiftContext, cancel_token_tx: Arc>, cancel_token_rx: mpsc::UnboundedReceiver<()>, @@ -88,7 +88,7 @@ struct IOSRuntime { impl IOSRuntime { pub unsafe fn new( pub_key: [u8; 32], - ephemeral_public_key: [u8; 32], + ephemeral_key: [u8; 32], packet_tunnel: *const libc::c_void, tcp_connection: *const c_void, ) -> io::Result { @@ -107,7 +107,7 @@ impl IOSRuntime { Ok(Self { runtime, pub_key, - ephemeral_public_key, + ephemeral_key, packet_tunnel: context, cancel_token_tx: Arc::new(tx), cancel_token_rx: rx, @@ -153,19 +153,22 @@ impl IOSRuntime { return; } }; + // TODO: derive the public key from the (private) ephemeral key for use here + // let ephemeral_key = ..... tokio::select! { preshared_key = talpid_tunnel_config_client::push_pq_inner( &mut async_provider, PublicKey::from(self.pub_key), - PublicKey::from(self.ephemeral_public_key), + PublicKey::from(ephemeral_key), ) => { match preshared_key { Ok(key) => unsafe { let bytes = key.as_bytes(); - swift_post_quantum_key_ready(packet_tunnel_ptr, bytes.as_ptr()); + let eph_bytes = self.ephemeral_public_key.as_bytes(); + swift_post_quantum_key_ready(packet_tunnel_ptr, bytes.as_ptr(), eph_bytes.as_ptr()); }, Err(_) => unsafe { - swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null()); + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); }, } } From 49c051b7a8461df0bb22cd12b81eddd181847547 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Mon, 15 Apr 2024 16:51:41 +0200 Subject: [PATCH 31/44] Derive the ephemeral public key from the private key in the FFI --- .../include/talpid_tunnel_config_client.h | 5 +++-- .../src/ios_ffi.rs | 13 ++----------- .../src/ios_tcp_connection.rs | 1 + .../talpid-tunnel-config-client/src/lib.rs | 19 +++++++++---------- 4 files changed, 15 insertions(+), 23 deletions(-) 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 c14dda4e12ba..12603e7b402f 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 @@ -28,7 +28,7 @@ void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender); * This function is safe to call */ int32_t negotiate_post_quantum_key(const uint8_t *public_key, - const uint8_t *ephemeral_public_key, + const uint8_t *ephemeral_key, const void *packet_tunnel, const void *tcp_connection, struct PostQuantumCancelToken *cancel_token); @@ -53,4 +53,5 @@ extern void swift_nw_tcp_connection_read(const void *connection, const void *sen * `raw_preshared_key` might be NULL if the key negotiation failed. */ extern void swift_post_quantum_key_ready(const void *raw_packet_tunnel, - const uint8_t *raw_preshared_key); + const uint8_t *raw_preshared_key, + const uint8_t *raw_ephemeral_private_key); diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs index 56906d1296a9..a37106db8912 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs @@ -46,7 +46,6 @@ pub unsafe extern "C" fn handle_recv(data: *const u8, data_len: usize, sender: * #[no_mangle] pub unsafe extern "C" fn negotiate_post_quantum_key( public_key: *const u8, - /// ephemeral_key is now the private key that's being passed back ephemeral_key: *const u8, packet_tunnel: *const c_void, tcp_connection: *const c_void, @@ -59,17 +58,9 @@ 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]) }; + 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_ios_runtime(pub_key_copy, eph_key_copy, packet_tunnel, tcp_connection) } { Ok(token) => { unsafe { std::ptr::write(cancel_token, token) }; 0 diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs index a93eec0fa8a1..ac3b7e3cb326 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs @@ -27,6 +27,7 @@ extern "C" { pub fn swift_post_quantum_key_ready( raw_packet_tunnel: *const c_void, raw_preshared_key: *const u8, + raw_ephemeral_private_key: *const u8, ); } diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs index a940769c2ee1..77920e4ea580 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs @@ -1,6 +1,7 @@ use std::ptr; use libc::c_void; +use talpid_types::net::wireguard::PrivateKey; use tokio::sync::mpsc; use std::io; @@ -148,24 +149,22 @@ impl IOSRuntime { Err(error) => { log::error!("Failed to create iOS TCP client: {error}"); unsafe { - swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null()); + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); } return; } }; - // TODO: derive the public key from the (private) ephemeral key for use here - // let ephemeral_key = ..... + let ephemeral_pub_key = PrivateKey::from(self.ephemeral_key).public_key(); tokio::select! { - preshared_key = talpid_tunnel_config_client::push_pq_inner( + key_result = talpid_tunnel_config_client::push_pq_inner( &mut async_provider, PublicKey::from(self.pub_key), - PublicKey::from(ephemeral_key), + ephemeral_pub_key, ) => { - match preshared_key { - Ok(key) => unsafe { - let bytes = key.as_bytes(); - let eph_bytes = self.ephemeral_public_key.as_bytes(); - swift_post_quantum_key_ready(packet_tunnel_ptr, bytes.as_ptr(), eph_bytes.as_ptr()); + match key_result { + Ok(preshared_key) => unsafe { + 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()); }, Err(_) => unsafe { swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); From ca4481edd50f4eb7de5671bdca042477461a9b00 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Tue, 23 Apr 2024 18:36:25 +0200 Subject: [PATCH 32/44] Correct spelling of PostQuantumKeyNegotiator --- ...eyNegotiatior.swift => PostQuantumKeyNegotiator.swift} | 4 ++-- ios/MullvadVPN.xcodeproj/project.pbxproj | 8 ++++---- .../PacketTunnelProvider/PacketTunnelProvider.swift | 4 ++-- ios/PacketTunnel/PostQuantumKeyExchangeActor.swift | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename ios/MullvadPostQuantum/{PostQuantumKeyNegotiatior.swift => PostQuantumKeyNegotiator.swift} (95%) diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift similarity index 95% rename from ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift rename to ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift index 377837bf178f..8e813f47df90 100644 --- a/ios/MullvadPostQuantum/PostQuantumKeyNegotiatior.swift +++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift @@ -1,5 +1,5 @@ // -// PostQuantumKeyNegotiatior.swift +// PostQuantumKeyNegotiator.swift // PacketTunnel // // Created by Marco Nikic on 2024-02-16. @@ -11,7 +11,7 @@ import NetworkExtension import TalpidTunnelConfigClientProxy import WireGuardKitTypes -public class PostQuantumKeyNegotiatior { +public class PostQuantumKeyNegotiator { public init() {} var cancelToken: PostQuantumCancelToken? diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index bc6499b0bfe0..62194c3a0e99 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -694,7 +694,7 @@ A944F25F2B8DEFDB00473F4C /* MullvadPostQuantum.h in Headers */ = {isa = PBXBuildFile; fileRef = A944F25E2B8DEFDB00473F4C /* MullvadPostQuantum.h */; settings = {ATTRIBUTES = (Public, ); }; }; A944F2622B8DEFDB00473F4C /* MullvadPostQuantum.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; }; A944F2632B8DEFDB00473F4C /* MullvadPostQuantum.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiatior.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiatior.swift */; }; + A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiator.swift */; }; A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client_proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.a */; }; A948809B2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */; }; A94D691A2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E22AA72AE9003D1918 /* WireGuardKitTypes */; }; @@ -2041,7 +2041,7 @@ A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelStore+Stubs.swift"; sourceTree = ""; }; A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusBlockObserver.swift; sourceTree = ""; }; A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = ""; }; - A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiatior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyNegotiatior.swift; sourceTree = ""; }; + A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyNegotiator.swift; sourceTree = ""; }; A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesTests.swift; sourceTree = ""; }; A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNConnectionProtocol.swift; sourceTree = ""; }; E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = ""; }; @@ -3927,7 +3927,7 @@ children = ( A944F25E2B8DEFDB00473F4C /* MullvadPostQuantum.h */, A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */, - A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiatior.swift */, + A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiator.swift */, A9630E3B2B8E0E7B00A65999 /* talpid_tunnel_config_client.h */, ); path = MullvadPostQuantum; @@ -5980,7 +5980,7 @@ buildActionMask = 2147483647; files = ( A9630E492B921E6D00A65999 /* PacketTunnelProvider+TCPConnection.swift in Sources */, - A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiatior.swift in Sources */, + A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 08cadd6fed2c..13181740c43a 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -32,7 +32,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // Post Quantum Key required variables private var inTunnelTCPConnection: NWTCPConnection! private var tcpConnectionObserver: NSKeyValueObservation! - private var quantumKeyNegotiatior: PostQuantumKeyNegotiatior! + private var quantumKeyNegotiatior: PostQuantumKeyNegotiator! override init() { Self.configureLogging() @@ -270,7 +270,7 @@ extension PacketTunnelProvider { private func startPostQuantumKeyNegotation(with privateKey: PrivateKey) { quantumKeyNegotiatior?.cancelKeyNegotiation() - let keyNegotiatior = PostQuantumKeyNegotiatior() + let keyNegotiatior = PostQuantumKeyNegotiator() let gatewayAddress = "10.64.0.1" let IPv4Gateway = IPv4Address(gatewayAddress)! diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index 47a93c6663e4..783ca123ca03 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -15,7 +15,7 @@ typealias InTunnelTCPConnectionCreator = (NWHostEndpoint) -> NWTCPConnection actor PostQuantumKeyExchangeActor { let createNetworkConnection: InTunnelTCPConnectionCreator unowned let packetTunnel: PacketTunnelProvider - private var quantumKeyNegotiatior: PostQuantumKeyNegotiatior! + private var quantumKeyNegotiatior: PostQuantumKeyNegotiator! private var inTunnelTCPConnection: NWTCPConnection! private var tcpConnectionObserver: NSKeyValueObservation! From bb9f7802384ef819381e44af0b8f45fcf70e1e48 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 24 Apr 2024 10:39:52 +0200 Subject: [PATCH 33/44] Add functionality to the actor --- .../PacketTunnelProvider.swift | 3 ++ .../PostQuantumKeyExchangeActor.swift | 50 ++++++++++++++++--- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 13181740c43a..79f6fd4e17ef 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -23,6 +23,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private let constraintsUpdater = RelayConstraintsUpdater() private var actor: PacketTunnelActor! + private var postQuantumActor: PostQuantumKeyExchangeActor! private var appMessageHandler: AppMessageHandler! private var stateObserverTask: AnyTask? private var deviceChecker: DeviceChecker! @@ -86,6 +87,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { protocolObfuscator: ProtocolObfuscator() ) + postQuantumActor = PostQuantumKeyExchangeActor(packetTunnel: self) + let urlRequestProxy = URLRequestProxy(dispatchQueue: internalQueue, transportProvider: transportProvider) appMessageHandler = AppMessageHandler(packetTunnelActor: actor, urlRequestProxy: urlRequestProxy) diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index 783ca123ca03..7f7fd8e06c74 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -9,18 +9,54 @@ import Foundation import MullvadPostQuantum import NetworkExtension +import WireGuardKitTypes -typealias InTunnelTCPConnectionCreator = (NWHostEndpoint) -> NWTCPConnection +// not needed? as we have the PacketTunnelProvider +// typealias InTunnelTCPConnectionCreator = (NWHostEndpoint) -> NWTCPConnection actor PostQuantumKeyExchangeActor { - let createNetworkConnection: InTunnelTCPConnectionCreator +// let createNetworkConnection: InTunnelTCPConnectionCreator unowned let packetTunnel: PacketTunnelProvider - private var quantumKeyNegotiatior: PostQuantumKeyNegotiator! - private var inTunnelTCPConnection: NWTCPConnection! - private var tcpConnectionObserver: NSKeyValueObservation! + private var negotiator: PostQuantumKeyNegotiator? + private var inTunnelTCPConnection: NWTCPConnection? + private var tcpConnectionObserver: NSKeyValueObservation? - init(packetTunnel: PacketTunnelProvider, createNetworkConnection: @escaping InTunnelTCPConnectionCreator) { + init(packetTunnel: PacketTunnelProvider /* , createNetworkConnection: @escaping InTunnelTCPConnectionCreator */ ) { self.packetTunnel = packetTunnel - self.createNetworkConnection = createNetworkConnection + } + + private func createTCPConnection(_ gatewayEndpoint: NWHostEndpoint) -> NWTCPConnection { + self.packetTunnel.createTCPConnectionThroughTunnel( + to: gatewayEndpoint, + enableTLS: false, + tlsParameters: nil, + delegate: nil + ) + } + + func startNegotiation(with privateKey: PrivateKey) { + negotiator = PostQuantumKeyNegotiator() + + let gatewayAddress = "10.64.0.1" + let IPv4Gateway = IPv4Address(gatewayAddress)! + let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337") + inTunnelTCPConnection = createTCPConnection(endpoint) + + let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device + + tcpConnectionObserver = inTunnelTCPConnection!.observe(\.isViable, options: [ + .initial, + .new, + ]) { [weak self] observedConnection, _ in + guard let self, observedConnection.isViable else { return } + negotiator!.negotiateKey( + gatewayIP: IPv4Gateway, + devicePublicKey: privateKey.publicKey, + presharedKey: ephemeralSharedKey, + packetTunnel: packetTunnel, + tcpConnection: inTunnelTCPConnection! + ) + self.tcpConnectionObserver!.invalidate() + } } } From 0c57834ebea8f9e172429be7fe85e173ec37edd7 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 24 Apr 2024 12:04:22 +0200 Subject: [PATCH 34/44] Move PQ negotiation to Actor which is now not an Actor --- .../PacketTunnelProvider.swift | 72 ++++++++++--------- .../PostQuantumKeyExchangeActor.swift | 11 ++- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 79f6fd4e17ef..21bfec36899e 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -271,35 +271,36 @@ extension PacketTunnelProvider { } private func startPostQuantumKeyNegotation(with privateKey: PrivateKey) { - quantumKeyNegotiatior?.cancelKeyNegotiation() - - let keyNegotiatior = PostQuantumKeyNegotiator() - - let gatewayAddress = "10.64.0.1" - let IPv4Gateway = IPv4Address(gatewayAddress)! - let tcpConnection = createTCPConnectionForPQPSK(gatewayAddress) - - // TODO: Pass the private key to rust directly - // It will derive the public key, and give us back both the preshared key, and the ephemeral private key - let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device - let observer = tcpConnection.observe(\.isViable, options: [ - .initial, - .new, - ]) { [weak self] observedConnection, _ in - guard let self, observedConnection.isViable else { return } - keyNegotiatior.negotiateKey( - gatewayIP: IPv4Gateway, - devicePublicKey: privateKey.publicKey, - presharedKey: ephemeralSharedKey, - packetTunnel: self, - tcpConnection: tcpConnection - ) - self.tcpConnectionObserver.invalidate() - } - - inTunnelTCPConnection = tcpConnection - tcpConnectionObserver = observer - quantumKeyNegotiatior = keyNegotiatior + postQuantumActor.startNegotiation(with: privateKey) +// quantumKeyNegotiatior?.cancelKeyNegotiation() +// +// let keyNegotiatior = PostQuantumKeyNegotiator() +// +// let gatewayAddress = "10.64.0.1" +// let IPv4Gateway = IPv4Address(gatewayAddress)! +// let tcpConnection = createTCPConnectionForPQPSK(gatewayAddress) +// +// // TODO: Pass the private key to rust directly +// // It will derive the public key, and give us back both the preshared key, and the ephemeral private key +// let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device +// let observer = tcpConnection.observe(\.isViable, options: [ +// .initial, +// .new, +// ]) { [weak self] observedConnection, _ in +// guard let self, observedConnection.isViable else { return } +// keyNegotiatior.negotiateKey( +// gatewayIP: IPv4Gateway, +// devicePublicKey: privateKey.publicKey, +// presharedKey: ephemeralSharedKey, +// packetTunnel: self, +// tcpConnection: tcpConnection +// ) +// self.tcpConnectionObserver.invalidate() +// } +// +// inTunnelTCPConnection = tcpConnection +// tcpConnectionObserver = observer +// quantumKeyNegotiatior = keyNegotiatior // Re-establish the tunnel connection to let the TCP connection flow through // reasserting = false } @@ -341,12 +342,13 @@ extension PacketTunnelProvider { extension PacketTunnelProvider: PostQuantumKeyReceiving { func receivePostQuantumKey(_ key: PreSharedKey?, ephemeralKey: PrivateKey) { - quantumKeyNegotiatior?.cancelKeyNegotiation() - tcpConnectionObserver?.invalidate() - inTunnelTCPConnection.cancel() - tcpConnectionObserver = nil - inTunnelTCPConnection = nil - quantumKeyNegotiatior = nil + postQuantumActor.acknowledgePostQuantumKey() +// quantumKeyNegotiatior?.cancelKeyNegotiation() +// tcpConnectionObserver?.invalidate() +// inTunnelTCPConnection.cancel() +// tcpConnectionObserver = nil +// inTunnelTCPConnection = nil +// quantumKeyNegotiatior = nil if let key { actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index 7f7fd8e06c74..69ba1edadaa1 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -14,7 +14,7 @@ import WireGuardKitTypes // not needed? as we have the PacketTunnelProvider // typealias InTunnelTCPConnectionCreator = (NWHostEndpoint) -> NWTCPConnection -actor PostQuantumKeyExchangeActor { +class PostQuantumKeyExchangeActor { // let createNetworkConnection: InTunnelTCPConnectionCreator unowned let packetTunnel: PacketTunnelProvider private var negotiator: PostQuantumKeyNegotiator? @@ -59,4 +59,13 @@ actor PostQuantumKeyExchangeActor { self.tcpConnectionObserver!.invalidate() } } + + func acknowledgePostQuantumKey() { + negotiator?.cancelKeyNegotiation() + tcpConnectionObserver?.invalidate() + inTunnelTCPConnection?.cancel() + tcpConnectionObserver = nil + inTunnelTCPConnection = nil + negotiator = nil + } } From 24b58c403850bb8d240609493024309aff09dbf3 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 24 Apr 2024 14:37:32 +0200 Subject: [PATCH 35/44] Refactor PQ key receiving protocol --- .../PacketTunnelProvider+TCPConnection.swift | 24 +++++++++---------- .../Protocols/PostQuantumKeyReceiving.swift | 3 ++- .../PacketTunnelProvider.swift | 15 ++++++------ .../PostQuantumKeyExchangeActor.swift | 2 +- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift index 875f6ca5cd8d..5da6d0c9331a 100644 --- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -58,21 +58,21 @@ func receivePostQuantumKey( rawPresharedKey: UnsafeMutableRawPointer?, rawEphemeralKey: UnsafeMutableRawPointer? ) { + guard let rawPacketTunnel, - let rawEphemeralKey, - let ephemeralKey = PrivateKey(rawValue: Data(bytes: rawEphemeralKey, count: 32)) + let postQuantumKeyReceiver = Unmanaged.fromOpaque(rawPacketTunnel).takeUnretainedValue() as? PostQuantumKeyReceiving else { return } - let packetTunnel = Unmanaged.fromOpaque(rawPacketTunnel).takeUnretainedValue() - - guard let postQuantumKeyReceiver = packetTunnel as? PostQuantumKeyReceiving else { return } - guard let rawPresharedKey else { - postQuantumKeyReceiver.receivePostQuantumKey(.none, ephemeralKey: ephemeralKey) + + guard + let rawPresharedKey, + let rawEphemeralKey, + let ephemeralKey = PrivateKey(rawValue: Data(bytes: rawEphemeralKey, count: 32)), + let key = PreSharedKey(rawValue: Data(bytes: rawPresharedKey, count: 32)) + else { + postQuantumKeyReceiver.keyExchangeFailed() return } - - let presharedKey = Data(bytes: rawPresharedKey, count: 32) - if let key = PreSharedKey(rawValue: presharedKey) { - postQuantumKeyReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey) - } + + postQuantumKeyReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey) } diff --git a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift index a91ab0872da6..50809c50b168 100644 --- a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift +++ b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift @@ -10,7 +10,8 @@ import Foundation import WireGuardKitTypes public protocol PostQuantumKeyReceiving { - func receivePostQuantumKey(_ key: PreSharedKey?, ephemeralKey: PrivateKey) + func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) + func keyExchangeFailed() } public enum PostQuantumKeyReceivingError: Error { diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 21bfec36899e..eb20e4a039df 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -341,8 +341,8 @@ extension PacketTunnelProvider { } extension PacketTunnelProvider: PostQuantumKeyReceiving { - func receivePostQuantumKey(_ key: PreSharedKey?, ephemeralKey: PrivateKey) { - postQuantumActor.acknowledgePostQuantumKey() + func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) { + postQuantumActor.acknowledgeNegotiationConcluded() // quantumKeyNegotiatior?.cancelKeyNegotiation() // tcpConnectionObserver?.invalidate() // inTunnelTCPConnection.cancel() @@ -350,10 +350,11 @@ extension PacketTunnelProvider: PostQuantumKeyReceiving { // inTunnelTCPConnection = nil // quantumKeyNegotiatior = nil - if let key { - actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) - } else { - actor.reconnect(to: .current) - } + actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) + } + + func keyExchangeFailed() { + postQuantumActor.acknowledgeNegotiationConcluded() + actor.reconnect(to: .current) } } diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index 69ba1edadaa1..fe991040f6d5 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -60,7 +60,7 @@ class PostQuantumKeyExchangeActor { } } - func acknowledgePostQuantumKey() { + func acknowledgeNegotiationConcluded() { negotiator?.cancelKeyNegotiation() tcpConnectionObserver?.invalidate() inTunnelTCPConnection?.cancel() From 7c7b5a9277d98bdffdd2fd3ee2175dbd170d2c6f Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 24 Apr 2024 14:37:48 +0200 Subject: [PATCH 36/44] Fix whitespace --- .../PacketTunnelProvider+TCPConnection.swift | 8 ++++---- .../PacketTunnelProvider/PacketTunnelProvider.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift index 5da6d0c9331a..8b06c7b178c1 100644 --- a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift +++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift @@ -58,12 +58,12 @@ func receivePostQuantumKey( rawPresharedKey: UnsafeMutableRawPointer?, rawEphemeralKey: UnsafeMutableRawPointer? ) { - guard let rawPacketTunnel, - let postQuantumKeyReceiver = Unmanaged.fromOpaque(rawPacketTunnel).takeUnretainedValue() as? PostQuantumKeyReceiving + let postQuantumKeyReceiver = Unmanaged.fromOpaque(rawPacketTunnel) + .takeUnretainedValue() as? PostQuantumKeyReceiving else { return } - + guard let rawPresharedKey, let rawEphemeralKey, @@ -73,6 +73,6 @@ func receivePostQuantumKey( postQuantumKeyReceiver.keyExchangeFailed() return } - + postQuantumKeyReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey) } diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index eb20e4a039df..d66f0e5a4ef2 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -352,7 +352,7 @@ extension PacketTunnelProvider: PostQuantumKeyReceiving { actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) } - + func keyExchangeFailed() { postQuantumActor.acknowledgeNegotiationConcluded() actor.reconnect(to: .current) From 3c01a1b787bfb916ba52abdd2f91ea835b54d557 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 24 Apr 2024 15:16:08 +0200 Subject: [PATCH 37/44] Combine all negotiation-lifecycle data in one optional struct --- .../PostQuantumKeyExchangeActor.swift | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index fe991040f6d5..f46045169d0c 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -11,15 +11,21 @@ import MullvadPostQuantum import NetworkExtension import WireGuardKitTypes -// not needed? as we have the PacketTunnelProvider -// typealias InTunnelTCPConnectionCreator = (NWHostEndpoint) -> NWTCPConnection - class PostQuantumKeyExchangeActor { -// let createNetworkConnection: InTunnelTCPConnectionCreator + struct Negotiation { + var negotiator: PostQuantumKeyNegotiator + var inTunnelTCPConnection: NWTCPConnection + var tcpConnectionObserver: NSKeyValueObservation + + func cancel() { + negotiator.cancelKeyNegotiation() + tcpConnectionObserver.invalidate() + inTunnelTCPConnection.cancel() + } + } + unowned let packetTunnel: PacketTunnelProvider - private var negotiator: PostQuantumKeyNegotiator? - private var inTunnelTCPConnection: NWTCPConnection? - private var tcpConnectionObserver: NSKeyValueObservation? + private var negotiation: Negotiation? init(packetTunnel: PacketTunnelProvider /* , createNetworkConnection: @escaping InTunnelTCPConnectionCreator */ ) { self.packetTunnel = packetTunnel @@ -35,37 +41,38 @@ class PostQuantumKeyExchangeActor { } func startNegotiation(with privateKey: PrivateKey) { - negotiator = PostQuantumKeyNegotiator() + let negotiator = PostQuantumKeyNegotiator() let gatewayAddress = "10.64.0.1" let IPv4Gateway = IPv4Address(gatewayAddress)! let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337") - inTunnelTCPConnection = createTCPConnection(endpoint) + let inTunnelTCPConnection = createTCPConnection(endpoint) let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device - tcpConnectionObserver = inTunnelTCPConnection!.observe(\.isViable, options: [ + let tcpConnectionObserver = inTunnelTCPConnection.observe(\.isViable, options: [ .initial, .new, ]) { [weak self] observedConnection, _ in guard let self, observedConnection.isViable else { return } - negotiator!.negotiateKey( + negotiator.negotiateKey( gatewayIP: IPv4Gateway, devicePublicKey: privateKey.publicKey, presharedKey: ephemeralSharedKey, packetTunnel: packetTunnel, - tcpConnection: inTunnelTCPConnection! + tcpConnection: inTunnelTCPConnection ) - self.tcpConnectionObserver!.invalidate() + self.negotiation?.tcpConnectionObserver.invalidate() } + negotiation = Negotiation( + negotiator: negotiator, + inTunnelTCPConnection: inTunnelTCPConnection, + tcpConnectionObserver: tcpConnectionObserver + ) } func acknowledgeNegotiationConcluded() { - negotiator?.cancelKeyNegotiation() - tcpConnectionObserver?.invalidate() - inTunnelTCPConnection?.cancel() - tcpConnectionObserver = nil - inTunnelTCPConnection = nil - negotiator = nil + negotiation?.cancel() + negotiation = nil } } From a917197174507289941b7cfd6b9a8b4374659660 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Wed, 24 Apr 2024 16:23:38 +0200 Subject: [PATCH 38/44] Remove obsolete code from PacketTunnelProvider --- .../PacketTunnelProvider.swift | 41 ------------------- .../PostQuantumKeyExchangeActor.swift | 2 +- 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index d66f0e5a4ef2..9325fa6cf8b6 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -30,11 +30,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private var adapter: WgAdapter! private var relaySelector: RelaySelectorWrapper! - // Post Quantum Key required variables - private var inTunnelTCPConnection: NWTCPConnection! - private var tcpConnectionObserver: NSKeyValueObservation! - private var quantumKeyNegotiatior: PostQuantumKeyNegotiator! - override init() { Self.configureLogging() @@ -272,35 +267,6 @@ extension PacketTunnelProvider { private func startPostQuantumKeyNegotation(with privateKey: PrivateKey) { postQuantumActor.startNegotiation(with: privateKey) -// quantumKeyNegotiatior?.cancelKeyNegotiation() -// -// let keyNegotiatior = PostQuantumKeyNegotiator() -// -// let gatewayAddress = "10.64.0.1" -// let IPv4Gateway = IPv4Address(gatewayAddress)! -// let tcpConnection = createTCPConnectionForPQPSK(gatewayAddress) -// -// // TODO: Pass the private key to rust directly -// // It will derive the public key, and give us back both the preshared key, and the ephemeral private key -// let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device -// let observer = tcpConnection.observe(\.isViable, options: [ -// .initial, -// .new, -// ]) { [weak self] observedConnection, _ in -// guard let self, observedConnection.isViable else { return } -// keyNegotiatior.negotiateKey( -// gatewayIP: IPv4Gateway, -// devicePublicKey: privateKey.publicKey, -// presharedKey: ephemeralSharedKey, -// packetTunnel: self, -// tcpConnection: tcpConnection -// ) -// self.tcpConnectionObserver.invalidate() -// } -// -// inTunnelTCPConnection = tcpConnection -// tcpConnectionObserver = observer -// quantumKeyNegotiatior = keyNegotiatior // Re-establish the tunnel connection to let the TCP connection flow through // reasserting = false } @@ -343,13 +309,6 @@ extension PacketTunnelProvider { extension PacketTunnelProvider: PostQuantumKeyReceiving { func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) { postQuantumActor.acknowledgeNegotiationConcluded() -// quantumKeyNegotiatior?.cancelKeyNegotiation() -// tcpConnectionObserver?.invalidate() -// inTunnelTCPConnection.cancel() -// tcpConnectionObserver = nil -// inTunnelTCPConnection = nil -// quantumKeyNegotiatior = nil - actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) } diff --git a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift index f46045169d0c..e9189d947c65 100644 --- a/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift +++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift @@ -27,7 +27,7 @@ class PostQuantumKeyExchangeActor { unowned let packetTunnel: PacketTunnelProvider private var negotiation: Negotiation? - init(packetTunnel: PacketTunnelProvider /* , createNetworkConnection: @escaping InTunnelTCPConnectionCreator */ ) { + init(packetTunnel: PacketTunnelProvider) { self.packetTunnel = packetTunnel } From fbfc80b6f018dccb428d60bb6f70bed6a05323b9 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Fri, 26 Apr 2024 15:54:37 +0200 Subject: [PATCH 39/44] Do not schedule more than one read or writes at a time --- .../src/ios_tcp_connection.rs | 20 ++++++++++++++++++- .../PacketTunnelProvider.swift | 2 +- .../Actor/PacketTunnelActor.swift | 2 -- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs index ac3b7e3cb326..d11202b83164 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_tcp_connection.rs @@ -39,6 +39,8 @@ pub struct IosTcpProvider { read_tx: mpsc::UnboundedSender>, read_rx: mpsc::UnboundedReceiver>, tcp_connection: *const c_void, + read_in_progress: bool, + write_in_progress: bool, } impl IosTcpProvider { @@ -51,6 +53,8 @@ impl IosTcpProvider { read_tx: recv_tx, read_rx: recv_rx, tcp_connection, + read_in_progress: false, + write_in_progress: false, } } } @@ -64,11 +68,19 @@ impl AsyncWrite for IosTcpProvider { let raw_sender = Box::into_raw(Box::new(self.write_tx.clone())); match self.write_rx.poll_recv(cx) { - std::task::Poll::Ready(Some(bytes_sent)) => Poll::Ready(Ok(bytes_sent)), + std::task::Poll::Ready(Some(bytes_sent)) => { + self.write_in_progress = false; + Poll::Ready(Ok(bytes_sent)) + } std::task::Poll::Ready(None) => { + self.write_in_progress = false; Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "sender dropped"))) } std::task::Poll::Pending => { + if self.write_in_progress { + return std::task::Poll::Pending; + } + self.write_in_progress = true; unsafe { swift_nw_tcp_connection_send( self.tcp_connection, @@ -107,12 +119,18 @@ impl AsyncRead for IosTcpProvider { match self.read_rx.poll_recv(cx) { std::task::Poll::Ready(Some(data)) => { buf.put_slice(&data); + self.read_in_progress = false; Poll::Ready(Ok(())) } std::task::Poll::Ready(None) => { + self.read_in_progress = false; Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "sender dropped"))) } 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 _); } diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 9325fa6cf8b6..0d6b7a1c38dd 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -308,8 +308,8 @@ extension PacketTunnelProvider { extension PacketTunnelProvider: PostQuantumKeyReceiving { func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) { - postQuantumActor.acknowledgeNegotiationConcluded() actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey) + postQuantumActor.acknowledgeNegotiationConcluded() } func keyExchangeFailed() { diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 8076f3e01237..ae50069ee8e8 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -238,9 +238,7 @@ extension PacketTunnelActor { let targetState = state.targetStateForReconnect else { return } - let activeKey = activeKey(from: connectionState, in: settings) let configurationBuilder = ConfigurationBuilder( - // privateKey: activeKey, privateKey: privateKey, interfaceAddresses: settings.interfaceAddresses, dns: settings.dnsServers, From 909e53c3033e0b9a24b2ba3d85636058616b7d6b Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Tue, 30 Apr 2024 19:36:53 +0200 Subject: [PATCH 40/44] Set relay to current when opening PQ connection --- ios/PacketTunnelCore/Actor/PacketTunnelActor.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index ae50069ee8e8..72a0de3deb1f 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -231,7 +231,7 @@ extension PacketTunnelActor { guard let settings: Settings = try? settingsReader.read(), let connectionState = try? obfuscateConnection( - nextRelay: .random, + nextRelay: .current, settings: settings, reason: .userInitiated ), From a78120995b97ce0ea9a262dd10c871dc540267fc Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Mon, 29 Apr 2024 11:29:39 +0200 Subject: [PATCH 41/44] Remove the iOS custom TTCC crate in favour of the original one --- Cargo.lock | 21 +-- Cargo.toml | 1 - .../module.private.modulemap | 2 +- .../talpid-tunnel-config-client/build.rs | 2 +- .../include/talpid_tunnel_config_client.h | 5 + .../talpid-tunnel-config-client/src/lib.rs | 2 +- ios/MullvadVPN.xcodeproj/project.pbxproj | 10 +- talpid-tunnel-config-client/Cargo.toml | 11 +- talpid-tunnel-config-client/build.rs | 12 ++ .../src/ios_ffi/ios_runtime.rs | 160 ++++++++++++++++++ .../src/ios_ffi/ios_tcp_connection.rs | 142 ++++++++++++++++ .../src/ios_ffi/mod.rs | 95 +++++++++++ talpid-tunnel-config-client/src/lib.rs | 95 +++++++---- 13 files changed, 493 insertions(+), 65 deletions(-) create mode 100644 talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs create mode 100644 talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs create mode 100644 talpid-tunnel-config-client/src/ios_ffi/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 315419a73a10..8c44bc3d02d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4074,6 +4074,7 @@ dependencies = [ "classic-mceliece-rust", "libc", "log", + "oslog", "pqc_kyber", "prost", "rand 0.8.5", @@ -4086,26 +4087,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "talpid-tunnel-config-client-proxy" -version = "0.0.0" -dependencies = [ - "cbindgen", - "classic-mceliece-rust", - "libc", - "log", - "oslog", - "pqc_kyber", - "prost", - "talpid-tunnel-config-client", - "talpid-types", - "tokio", - "tonic", - "tonic-build", - "tower", - "zeroize", -] - [[package]] name = "talpid-types" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 173f8d8f6ebe..f7fcd70642b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ members = [ "android/translations-converter", "ios/MullvadREST/Transport/Shadowsocks/shadowsocks-proxy", "ios/TunnelObfuscation/tunnel-obfuscator-proxy", - "ios/MullvadPostQuantum/talpid-tunnel-config-client", "mullvad-api", "mullvad-cli", "mullvad-daemon", diff --git a/ios/MullvadPostQuantum/module.private.modulemap b/ios/MullvadPostQuantum/module.private.modulemap index ff5f63eee8d6..f831c7ca2e37 100644 --- a/ios/MullvadPostQuantum/module.private.modulemap +++ b/ios/MullvadPostQuantum/module.private.modulemap @@ -1,5 +1,5 @@ framework module TalpidTunnelConfigClientProxy { header "talpid_tunnel_config_client.h" - link "libtalpid_tunnel_config_client_proxy" + link "libtalpid_tunnel_config_client" export * } diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs index 3a4f92f421c5..54a77b1e1019 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs @@ -1,5 +1,5 @@ fn main() { - tonic_build::compile_protos("../../../talpid-tunnel-config-client/proto/tunnel_config.proto") + tonic_build::compile_protos("../../../talpid-tunnel-config-client/proto/ephemeralpeer.proto") .unwrap(); match std::env::var("TARGET").unwrap().as_str() { 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 12603e7b402f..e289ca4af895 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 @@ -3,6 +3,11 @@ #include #include +/** + * Port used by the tunnel config service. + */ +#define CONFIG_SERVICE_PORT 1337 + typedef struct PostQuantumCancelToken { void *context; } PostQuantumCancelToken; diff --git a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs index 77920e4ea580..5577aaf5863c 100644 --- a/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs +++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/src/lib.rs @@ -121,7 +121,7 @@ impl IOSRuntime { }); } - async fn ios_tcp_client(ctx: SwiftContext) -> Result { + pub async fn ios_tcp_client(ctx: SwiftContext) -> Result { let endpoint = Endpoint::from_static("tcp://0.0.0.0:0"); let conn = endpoint .connect_with_connector(service_fn(move |_| { diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 62194c3a0e99..c8fb16cb0135 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -695,7 +695,7 @@ A944F2622B8DEFDB00473F4C /* MullvadPostQuantum.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */; }; 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_proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.a */; }; + 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 */; }; @@ -2003,7 +2003,7 @@ A935594D2B4E919F00D5D524 /* Api.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Api.xcconfig; sourceTree = ""; }; A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadPostQuantum.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A944F25E2B8DEFDB00473F4C /* MullvadPostQuantum.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadPostQuantum.h; sourceTree = ""; }; - A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtalpid_tunnel_config_client_proxy.a; path = "../target/aarch64-apple-ios/debug/libtalpid_tunnel_config_client_proxy.a"; sourceTree = ""; }; + A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtalpid_tunnel_config_client.a; path = "../target/aarch64-apple-ios/debug/libtalpid_tunnel_config_client.a"; sourceTree = ""; }; A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTests.swift; sourceTree = ""; }; A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangeActor.swift; sourceTree = ""; }; A95EEE352B722CD600A8A39B /* TunnelMonitorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorState.swift; sourceTree = ""; }; @@ -2298,7 +2298,7 @@ files = ( A9630E432B8E10FB00A65999 /* MullvadTypes.framework in Frameworks */, A906F94A2BA1E09A002BF22E /* WireGuardKitTypes in Frameworks */, - A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client_proxy.a in Frameworks */, + A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2995,7 +2995,7 @@ 584F991F2902CBDD001F858D /* Frameworks */ = { isa = PBXGroup; children = ( - A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client_proxy.a */, + A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client.a */, 01EF6F332B6A590700125696 /* libmullvad_api.a */, 01EF6F312B6A58F000125696 /* debug */, 01EF6F2F2B6A588300125696 /* aarch64-apple-ios */, @@ -4988,7 +4988,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARGO_TARGET_DIR=${PROJECT_DIR}/../target bash ${PROJECT_DIR}/build-rust-library.sh talpid-tunnel-config-client-proxy\n"; + shellScript = "CARGO_TARGET_DIR=${PROJECT_DIR}/../target bash ${PROJECT_DIR}/build-rust-library.sh talpid-tunnel-config-client\n"; }; F05F39962B21C704006E60A7 /* Prebuild relays */ = { isa = PBXShellScriptBuildPhase; diff --git a/talpid-tunnel-config-client/Cargo.toml b/talpid-tunnel-config-client/Cargo.toml index 6aa8e5e3fc79..18eb9342d014 100644 --- a/talpid-tunnel-config-client/Cargo.toml +++ b/talpid-tunnel-config-client/Cargo.toml @@ -17,7 +17,7 @@ talpid-types = { path = "../talpid-types" } tonic = { workspace = true } tower = { workspace = true } prost = { workspace = true } -tokio = { workspace = true, features = ["macros"] } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } classic-mceliece-rust = { version = "2.0.0", features = [ "mceliece460896f", "zeroize", @@ -30,12 +30,15 @@ libc = "0.2" workspace = true features = ["Win32_Networking_WinSock"] -[dev-dependencies] -tokio = { workspace = true, features = ["rt-multi-thread"] } - [build-dependencies] tonic-build = { workspace = true, default-features = false, features = [ "transport", "prost", ] } cbindgen = { version = "0.24.3", default-features = false } + +[target.'cfg(target_os = "ios")'.dependencies] +oslog = "0.2" + +[lib] +crate-type = ["staticlib"] diff --git a/talpid-tunnel-config-client/build.rs b/talpid-tunnel-config-client/build.rs index aeb21fe009f2..50c9bfd1df6d 100644 --- a/talpid-tunnel-config-client/build.rs +++ b/talpid-tunnel-config-client/build.rs @@ -1,3 +1,15 @@ fn main() { tonic_build::compile_protos("proto/ephemeralpeer.proto").unwrap(); + match std::env::var("TARGET").unwrap().as_str() { + "aarch64-apple-ios" | "aarch64-apple-ios-sim" => { + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(cbindgen::Language::C) + .generate() + .expect("failed to generate bindings") + .write_to_file("../ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h"); + } + &_ => (), + } } diff --git a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs new file mode 100644 index 000000000000..52a185fee5c5 --- /dev/null +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs @@ -0,0 +1,160 @@ +use super::ios_tcp_connection::*; +use super::PostQuantumCancelToken; +use crate::request_ephemeral_peer; +use crate::Error; +use crate::RelayConfigService; +use libc::c_void; +use std::sync::Arc; +use std::{io, ptr}; +use talpid_types::net::wireguard::{PrivateKey, PublicKey}; +use tokio::runtime::Builder; +use tokio::sync::mpsc; +use tonic::transport::channel::Endpoint; +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_key: [u8; 32], + ephemeral_key: [u8; 32], + packet_tunnel: *const c_void, + tcp_connection: *const c_void, +) -> Result { + match unsafe { IOSRuntime::new(pub_key, ephemeral_key, packet_tunnel, tcp_connection) } { + Ok(runtime) => { + let token = runtime.cancel_token_tx.clone(); + + runtime.run(); + Ok(PostQuantumCancelToken { + context: Arc::into_raw(token) as *mut _, + }) + } + Err(err) => { + log::error!("Failed to create runtime {}", err); + Err(-1) + } + } +} + +#[derive(Clone)] +pub struct SwiftContext { + pub packet_tunnel: *const c_void, + pub tcp_connection: *const c_void, +} + +unsafe impl Send for SwiftContext {} +unsafe impl Sync for SwiftContext {} + +struct IOSRuntime { + runtime: tokio::runtime::Runtime, + pub_key: [u8; 32], + ephemeral_key: [u8; 32], + packet_tunnel: SwiftContext, + cancel_token_tx: Arc>, + cancel_token_rx: mpsc::UnboundedReceiver<()>, +} + +impl IOSRuntime { + pub unsafe fn new( + pub_key: [u8; 32], + ephemeral_key: [u8; 32], + packet_tunnel: *const libc::c_void, + tcp_connection: *const c_void, + ) -> io::Result { + let runtime = Builder::new_multi_thread() + .enable_all() + .worker_threads(2) + .build()?; + + let context = SwiftContext { + packet_tunnel, + 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, + }) + } + + pub fn run(self) { + std::thread::spawn(move || { + self.run_service_inner(); + }); + } + + pub async fn ios_tcp_client(ctx: SwiftContext) -> Result { + let endpoint = Endpoint::from_static("tcp://0.0.0.0:0"); + let conn = endpoint + .connect_with_connector(service_fn(move |_| { + let ctx = ctx.clone(); + let tcp_provider = unsafe { IosTcpProvider::new(ctx.tcp_connection) }; + async move { Ok::<_, Error>(tcp_provider) } + })) + .await + .map_err(Error::GrpcConnectError)?; + + Ok(RelayConfigService::new(conn)) + } + + fn run_service_inner(self) { + let Self { + runtime, + mut cancel_token_rx, + .. + } = self; + + let packet_tunnel_ptr = self.packet_tunnel.packet_tunnel; + runtime.block_on(async move { + let async_provider = match Self::ios_tcp_client(self.packet_tunnel).await { + Ok(async_provider) => async_provider, + 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( + PublicKey::from(self.pub_key), + ephemeral_pub_key, + true, + false, + async_provider, + ) => { + match ephemeral_peer { + Ok(peer) => { + match peer.psk { + Some(preshared_key) => unsafe { + 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 { + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); + } + } + }, + Err(_) => unsafe { + swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); + } + } + } + + _ = cancel_token_rx.recv() => { + // 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 new file mode 100644 index 000000000000..d11202b83164 --- /dev/null +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs @@ -0,0 +1,142 @@ +use libc::c_void; +use std::io; +use std::io::Result; +use std::task::Poll; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::sync::mpsc; + +extern "C" { + /// Called when there is data to send on the TCP connection. + /// The TCP connection must write data on the wire, then call the `handle_sent` function. + pub fn swift_nw_tcp_connection_send( + connection: *const libc::c_void, + data: *const libc::c_void, + data_len: usize, + sender: *const libc::c_void, + ); + + /// Called when there is data to read on the TCP connection. + /// The TCP connection must read data from the wire, then call the `handle_read` function. + pub fn swift_nw_tcp_connection_read( + connection: *const libc::c_void, + sender: *const libc::c_void, + ); + + /// Called when the preshared post quantum key is ready. + /// `raw_preshared_key` might be NULL if the key negotiation failed. + pub fn swift_post_quantum_key_ready( + raw_packet_tunnel: *const c_void, + raw_preshared_key: *const u8, + raw_ephemeral_private_key: *const u8, + ); +} + +unsafe impl Send for IosTcpProvider {} + +pub struct IosTcpProvider { + write_tx: mpsc::UnboundedSender, + write_rx: mpsc::UnboundedReceiver, + read_tx: mpsc::UnboundedSender>, + read_rx: mpsc::UnboundedReceiver>, + tcp_connection: *const c_void, + read_in_progress: bool, + write_in_progress: bool, +} + +impl IosTcpProvider { + pub unsafe fn new(tcp_connection: *const c_void) -> Self { + let (tx, rx) = mpsc::unbounded_channel(); + let (recv_tx, recv_rx) = mpsc::unbounded_channel(); + Self { + write_tx: tx, + write_rx: rx, + read_tx: recv_tx, + read_rx: recv_rx, + tcp_connection, + read_in_progress: false, + write_in_progress: false, + } + } +} + +impl AsyncWrite for IosTcpProvider { + fn poll_write( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + let raw_sender = Box::into_raw(Box::new(self.write_tx.clone())); + + match self.write_rx.poll_recv(cx) { + std::task::Poll::Ready(Some(bytes_sent)) => { + self.write_in_progress = false; + Poll::Ready(Ok(bytes_sent)) + } + std::task::Poll::Ready(None) => { + self.write_in_progress = false; + Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "sender dropped"))) + } + std::task::Poll::Pending => { + 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 _, + ); + } + std::task::Poll::Pending + } + } + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + _: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + _: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } +} +impl AsyncRead for IosTcpProvider { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + 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())); + + match self.read_rx.poll_recv(cx) { + std::task::Poll::Ready(Some(data)) => { + buf.put_slice(&data); + self.read_in_progress = false; + Poll::Ready(Ok(())) + } + std::task::Poll::Ready(None) => { + self.read_in_progress = false; + Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "sender dropped"))) + } + 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 _); + } + + std::task::Poll::Pending + } + } + } +} diff --git a/talpid-tunnel-config-client/src/ios_ffi/mod.rs b/talpid-tunnel-config-client/src/ios_ffi/mod.rs new file mode 100644 index 000000000000..e6d65c959d96 --- /dev/null +++ b/talpid-tunnel-config-client/src/ios_ffi/mod.rs @@ -0,0 +1,95 @@ +pub mod ios_runtime; +pub mod ios_tcp_connection; + +use crate::ios_ffi::ios_runtime::run_ios_runtime; +use libc::c_void; +use std::sync::Arc; +use tokio::sync::mpsc; + +use std::sync::Once; +static INIT_LOGGING: Once = Once::new(); + +#[repr(C)] +pub struct PostQuantumCancelToken { + // Must keep a pointer to a valid std::sync::Arc + pub context: *mut c_void, +} + +impl PostQuantumCancelToken { + /// #Safety + /// This function can only be called when the context pointer is valid. + unsafe fn cancel(&self) { + // 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(()); + std::mem::forget(send_tx); + } +} + +impl Drop for PostQuantumCancelToken { + fn drop(&mut self) { + let _: Arc> = unsafe { Arc::from_raw(self.context as _) }; + } +} +unsafe impl Send for PostQuantumCancelToken {} + +#[no_mangle] +pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const PostQuantumCancelToken) { + let sender = unsafe { &*sender }; + sender.cancel(); +} + +#[no_mangle] +pub unsafe extern "C" fn drop_post_quantum_key_exchange_token( + sender: *const PostQuantumCancelToken, +) { + let _sender = unsafe { std::ptr::read(sender) }; +} + +/// 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); +} + +/// 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 _) }; + 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()); +} + +/// 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 +#[no_mangle] +pub unsafe extern "C" fn negotiate_post_quantum_key( + public_key: *const u8, + ephemeral_key: *const u8, + packet_tunnel: *const c_void, + tcp_connection: *const c_void, + cancel_token: *mut PostQuantumCancelToken, +) -> i32 { + INIT_LOGGING.call_once(|| { + let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.TTCC") + .level_filter(log::LevelFilter::Trace) + .init(); + }); + + 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) } { + Ok(token) => { + unsafe { std::ptr::write(cancel_token, token) }; + 0 + } + Err(err) => err, + } +} diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs index d287dc400431..458d9aa071c4 100644 --- a/talpid-tunnel-config-client/src/lib.rs +++ b/talpid-tunnel-config-client/src/lib.rs @@ -1,10 +1,16 @@ -use std::{ - fmt, - net::{IpAddr, SocketAddr}, -}; +use proto::PostQuantumRequestV1; +use std::fmt; +#[cfg(not(target_os = "ios"))] +use std::net::IpAddr; +#[cfg(not(target_os = "ios"))] +use std::net::SocketAddr; use talpid_types::net::wireguard::{PresharedKey, PublicKey}; +#[cfg(not(target_os = "ios"))] use tokio::net::TcpSocket; -use tonic::transport::{Channel, Endpoint}; +use tonic::transport::Channel; +#[cfg(not(target_os = "ios"))] +use tonic::transport::Endpoint; +#[cfg(not(target_os = "ios"))] use tower::service_fn; use zeroize::Zeroize; @@ -16,18 +22,27 @@ mod proto { tonic::include_proto!("ephemeralpeer"); } +#[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; -#[cfg(not(target_os = "windows"))] +#[cfg(not(any(target_os = "windows", target_os = "ios")))] mod sys { pub use libc::{socklen_t, IPPROTO_TCP, TCP_MAXSEG}; - pub use std::os::fd::{AsRawFd, RawFd}; + pub use std::os::fd::RawFd; } +#[cfg(not(target_os = "ios"))] +pub use std::os::fd::AsRawFd; #[cfg(target_os = "windows")] mod sys { pub use std::os::windows::io::{AsRawSocket, RawSocket}; pub use windows_sys::Win32::Networking::WinSock::{IPPROTO_IP, IP_USER_MTU}; } +#[cfg(not(target_os = "ios"))] use sys::*; #[derive(Debug)] @@ -79,7 +94,7 @@ impl std::error::Error for Error { } } -type RelayConfigService = proto::ephemeral_peer_client::EphemeralPeerClient; +pub type RelayConfigService = proto::ephemeral_peer_client::EphemeralPeerClient; /// Port used by the tunnel config service. pub const CONFIG_SERVICE_PORT: u16 = 1337; @@ -93,6 +108,7 @@ pub const CONFIG_SERVICE_PORT: u16 = 1337; /// 2. MH + PQ on macOS has connection issues during the handshake due to PF blocking packet /// fragments for not having a port. In the longer term this might be fixed by allowing the /// handshake to work even if there is fragmentation. +#[cfg(not(target_os = "ios"))] const CONFIG_CLIENT_MTU: u16 = 576; pub struct EphemeralPeer { @@ -101,39 +117,22 @@ pub struct EphemeralPeer { /// Negotiate a short-lived peer with a PQ-safe PSK or with DAITA enabled. pub async fn request_ephemeral_peer( - service_address: IpAddr, + #[cfg(not(target_os = "ios"))] service_address: IpAddr, 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) = 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()); - - ( - Some(proto::PostQuantumRequestV1 { - kem_pubkeys: vec![ - proto::KemPubkeyV1 { - algorithm_name: classic_mceliece::ALGORITHM_NAME.to_owned(), - key_data: cme_kem_pubkey.as_array().to_vec(), - }, - proto::KemPubkeyV1 { - algorithm_name: kyber::ALGORITHM_NAME.to_owned(), - key_data: kyber_keypair.public.to_vec(), - }, - ], - }), - Some((cme_kem_secret, kyber_keypair.secret)), - ) - } else { - (None, None) - }; + 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(), @@ -191,6 +190,37 @@ pub async fn request_ephemeral_peer( Ok(EphemeralPeer { psk }) } +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 { + let (cme_kem_pubkey, cme_kem_secret) = classic_mceliece::generate_keys().await; + let kyber_keypair = kyber::keypair(&mut rand::thread_rng()); + + ( + Some(proto::PostQuantumRequestV1 { + kem_pubkeys: vec![ + proto::KemPubkeyV1 { + algorithm_name: classic_mceliece::ALGORITHM_NAME.to_owned(), + key_data: cme_kem_pubkey.as_array().to_vec(), + }, + proto::KemPubkeyV1 { + algorithm_name: kyber::ALGORITHM_NAME.to_owned(), + key_data: kyber_keypair.public.to_vec(), + }, + ], + }), + Some((cme_kem_secret, kyber_keypair.secret)), + ) + } else { + (None, None) + }; + (pq_request, kem_secrets) +} + /// Performs `dst = dst ^ src`. fn xor_assign(dst: &mut [u8; 32], src: &[u8; 32]) { for (dst_byte, src_byte) in dst.iter_mut().zip(src.iter()) { @@ -198,6 +228,7 @@ fn xor_assign(dst: &mut [u8; 32], src: &[u8; 32]) { } } +#[cfg(not(target_os = "ios"))] async fn new_client(addr: IpAddr) -> Result { let endpoint = Endpoint::from_static("tcp://0.0.0.0:0"); @@ -244,7 +275,7 @@ fn try_set_tcp_sock_mtu(sock: RawSocket, mtu: u16) { } } -#[cfg(not(windows))] +#[cfg(not(any(target_os = "windows", target_os = "ios")))] fn try_set_tcp_sock_mtu(dest: &IpAddr, sock: RawFd, mut mtu: u16) { const IPV4_HEADER_SIZE: u16 = 20; const IPV6_HEADER_SIZE: u16 = 40; From d2ceeb14e4cd95756cb4954bd7ad5d907e61767a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Em=C4=ABls?= Date: Fri, 3 May 2024 12:01:58 +0200 Subject: [PATCH 42/44] Modify the TCP connection to be shutdown safe --- .../src/ios_ffi/ios_runtime.rs | 47 ++++++++----- .../src/ios_ffi/ios_tcp_connection.rs | 69 ++++++++++++++----- talpid-tunnel-config-client/src/lib.rs | 4 ++ 3 files changed, 84 insertions(+), 36 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 52a185fee5c5..d0ab33efda23 100644 --- a/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs +++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs @@ -1,20 +1,15 @@ -use super::ios_tcp_connection::*; -use super::PostQuantumCancelToken; -use crate::request_ephemeral_peer; -use crate::Error; -use crate::RelayConfigService; +use super::{ios_tcp_connection::*, PostQuantumCancelToken}; +use crate::{request_ephemeral_peer, Error, RelayConfigService}; use libc::c_void; -use std::sync::Arc; -use std::{io, ptr}; +use std::{io, ptr, sync::Arc, future::Future, pin::Pin}; use talpid_types::net::wireguard::{PrivateKey, PublicKey}; -use tokio::runtime::Builder; -use tokio::sync::mpsc; +use tokio::{runtime::Builder, sync::mpsc}; use tonic::transport::channel::Endpoint; use tower::util::service_fn; /// # Safety -/// 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. pub unsafe fn run_ios_runtime( pub_key: [u8; 32], ephemeral_key: [u8; 32], @@ -90,18 +85,29 @@ impl IOSRuntime { }); } - pub async fn ios_tcp_client(ctx: SwiftContext) -> Result { + pub async fn ios_tcp_client( + ctx: SwiftContext, + ) -> Result<(RelayConfigService, IosTcpShutdownHandle), Error> { let endpoint = Endpoint::from_static("tcp://0.0.0.0:0"); + + let (tcp_provider, conn_handle) = unsafe { IosTcpProvider::new(ctx.tcp_connection) }; + // One (1) TCP connection + let mut one_tcp_connection = Some(tcp_provider); let conn = endpoint - .connect_with_connector(service_fn(move |_| { - let ctx = ctx.clone(); - let tcp_provider = unsafe { IosTcpProvider::new(ctx.tcp_connection) }; - async move { Ok::<_, Error>(tcp_provider) } + .connect_with_connector(service_fn(move |_| -> Pin + Send>> { + if let Some(connection) = one_tcp_connection.take() { + return Box::pin(async move { + return Ok::<_, Error>(connection); + }); + } + return Box::pin(async { + Err(Error::TcpConnectionExpired) + }); })) .await .map_err(Error::GrpcConnectError)?; - Ok(RelayConfigService::new(conn)) + Ok((RelayConfigService::new(conn), conn_handle)) } fn run_service_inner(self) { @@ -113,8 +119,8 @@ impl IOSRuntime { let packet_tunnel_ptr = self.packet_tunnel.packet_tunnel; runtime.block_on(async move { - let async_provider = match Self::ios_tcp_client(self.packet_tunnel).await { - Ok(async_provider) => async_provider, + let (async_provider, shutdown_handle) = match Self::ios_tcp_client(self.packet_tunnel).await { + Ok(result) => result, Err(error) => { log::error!("Failed to create iOS TCP client: {error}"); unsafe { @@ -133,6 +139,7 @@ impl IOSRuntime { false, async_provider, ) => { + shutdown_handle.shutdown(); match ephemeral_peer { Ok(peer) => { match peer.psk { @@ -143,6 +150,7 @@ impl IOSRuntime { None => unsafe { swift_post_quantum_key_ready(packet_tunnel_ptr, ptr::null(), ptr::null()); } + } }, Err(_) => unsafe { @@ -152,6 +160,7 @@ impl IOSRuntime { } _ = 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 d11202b83164..b1c2f4ad9f77 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,9 +1,17 @@ use libc::c_void; -use std::io; -use std::io::Result; -use std::task::Poll; -use tokio::io::{AsyncRead, AsyncWrite}; -use tokio::sync::mpsc; +use std::{ + io::{self, Result}, + sync::{Arc, atomic::{AtomicBool, self}}, + task::Poll, +}; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + sync::mpsc, +}; + +fn connection_closed_err() -> io::Error { + io::Error::new(io::ErrorKind::BrokenPipe, "TCP connection closed") +} extern "C" { /// Called when there is data to send on the TCP connection. @@ -41,21 +49,42 @@ pub struct IosTcpProvider { tcp_connection: *const c_void, read_in_progress: bool, write_in_progress: bool, + shutdown: Arc, +} + +pub struct IosTcpShutdownHandle { + shutdown: Arc, } impl IosTcpProvider { - pub unsafe fn new(tcp_connection: *const c_void) -> Self { + pub unsafe fn new(tcp_connection: *const c_void) -> (Self, IosTcpShutdownHandle) { let (tx, rx) = mpsc::unbounded_channel(); let (recv_tx, recv_rx) = mpsc::unbounded_channel(); - Self { - write_tx: tx, - write_rx: rx, - read_tx: recv_tx, - read_rx: recv_rx, - tcp_connection, - read_in_progress: false, - write_in_progress: false, - } + let shutdown = Arc::new(AtomicBool::new(false)); + + ( + Self { + write_tx: tx, + write_rx: rx, + read_tx: recv_tx, + read_rx: recv_rx, + tcp_connection, + read_in_progress: false, + write_in_progress: false, + shutdown: shutdown.clone(), + }, + IosTcpShutdownHandle { shutdown }, + ) + } + + fn is_shutdown(&self) -> bool { + self.shutdown.load(atomic::Ordering::SeqCst) + } +} + +impl IosTcpShutdownHandle { + pub fn shutdown(&self) { + self.shutdown.store(true, atomic::Ordering::SeqCst); } } @@ -74,9 +103,12 @@ impl AsyncWrite for IosTcpProvider { } std::task::Poll::Ready(None) => { self.write_in_progress = false; - Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "sender dropped"))) + 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 { return std::task::Poll::Pending; } @@ -115,6 +147,9 @@ impl AsyncRead for IosTcpProvider { 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())); + } match self.read_rx.poll_recv(cx) { std::task::Poll::Ready(Some(data)) => { @@ -124,7 +159,7 @@ impl AsyncRead for IosTcpProvider { } std::task::Poll::Ready(None) => { self.read_in_progress = false; - Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "sender dropped"))) + Poll::Ready(Err(connection_closed_err())) } std::task::Poll::Pending => { if self.read_in_progress { diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs index 458d9aa071c4..04a81d924bd2 100644 --- a/talpid-tunnel-config-client/src/lib.rs +++ b/talpid-tunnel-config-client/src/lib.rs @@ -59,6 +59,8 @@ pub enum Error { actual: usize, }, FailedDecapsulateKyber(kyber::KyberError), + #[cfg(target_os = "ios")] + TcpConnectionExpired, } impl std::fmt::Display for Error { @@ -80,6 +82,8 @@ impl std::fmt::Display for Error { write!(f, "Expected 2 ciphertext in the response, got {actual}") } FailedDecapsulateKyber(_) => "Failed to decapsulate Kyber1024 ciphertext".fmt(f), + #[cfg(target_os = "ios")] + TcpConnectionExpired => "TCP connection is already shut down".fmt(f), } } } From 77a28c0357fc402d35162aa76544206bb1a51e12 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Fri, 3 May 2024 16:19:33 +0200 Subject: [PATCH 43/44] Guarantee connecting to the same relay the PQ has been negotiated with --- .../WireGuardAdapter/WgAdapter.swift | 4 +-- .../Actor/PacketTunnelActor.swift | 32 ++++++++----------- .../Actor/PacketTunnelActorCommand.swift | 1 - .../Protocols/RelaySelectorProtocol.swift | 6 ++++ 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift index 3392e585ee1c..712761b88f79 100644 --- a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift +++ b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift @@ -32,8 +32,8 @@ struct WgAdapter: TunnelAdapterProtocol { func start(configuration: TunnelAdapterConfiguration) async throws { let wgConfig = configuration.asWgConfig do { - try await adapter.stop() - try await adapter.start(tunnelConfiguration: wgConfig) + try await adapter.update(tunnelConfiguration: wgConfig) +// try await adapter.start(tunnelConfiguration: wgConfig) } catch WireGuardAdapterError.invalidState { try await adapter.start(tunnelConfiguration: wgConfig) } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 72a0de3deb1f..768024ab23cd 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -200,12 +200,9 @@ extension PacketTunnelActor { private func reconnect(to nextRelay: NextRelay, reason: ReconnectReason) async { do { switch state { - case .negotiatingPostQuantumKey: - // There is no connection monitoring going on when exchanging keys. - // The procedure starts from scratch for each reconnection attempts. -// try await tryStart(nextRelay: nextRelay, reason: reason) - break // DO nothing at all for the moment - case .connecting, .connected, .reconnecting, .error: + // There is no connection monitoring going on when exchanging keys. + // The procedure starts from scratch for each reconnection attempts. + case .connecting, .connected, .reconnecting, .error, .negotiatingPostQuantumKey: switch reason { case .connectionLoss: // Tunnel monitor is already paused at this point. Avoid calling stop() to prevent the reset of @@ -229,13 +226,14 @@ 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: .current, + nextRelay: .preSelected(selectedRelay), settings: settings, reason: .userInitiated - ), - let targetState = state.targetStateForReconnect + ) else { return } let configurationBuilder = ConfigurationBuilder( @@ -251,12 +249,7 @@ extension PacketTunnelActor { ) stopDefaultPathObserver() - switch targetState { - case .connecting: - state = .connecting(connectionState) - case .reconnecting: - state = .reconnecting(connectionState) - } + state = .connecting(connectionState) defer { // Restart default path observer and notify the observer with the current path that might have changed while @@ -285,20 +278,21 @@ extension PacketTunnelActor { - reason: reason for reconnect */ private func tryStart( - nextRelay: NextRelay = .random, + nextRelay: NextRelay, reason: ReconnectReason = .userInitiated ) async throws { let settings: Settings = try settingsReader.read() guard settings.quantumResistance == .off || settings.quantumResistance == .automatic else { 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: connectionState.connectedEndpoint, + endpoint: selectedEndpoint, allowedIPs: [ IPAddressRange(from: "10.64.0.1/32")!, ] @@ -388,7 +382,9 @@ extension PacketTunnelActor { connectionState.incrementAttemptCount() } fallthrough - case var .connected(connectionState), var .negotiatingPostQuantumKey(connectionState, _): + case let .negotiatingPostQuantumKey(connectionState, _): + return connectionState + case var .connected(connectionState): let selectedRelay = try callRelaySelector( connectionState.selectedRelay, connectionState.connectionAttemptCount diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift index dc3735a9e77f..c4bd2c314bbd 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift @@ -36,7 +36,6 @@ extension PacketTunnelActor { /// Network reachability events. case networkReachability(NetworkPath) - // TODO: Add the device's ephemeral new private key here /// Update the device private key, as per post-quantum protocols case replaceDevicePrivateKey(PreSharedKey, ephemeralKey: PrivateKey) diff --git a/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift index 4224233f7026..a4408392e363 100644 --- a/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift +++ b/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift @@ -36,3 +36,9 @@ public struct SelectedRelay: Equatable, Codable { self.retryAttempts = retryAttempts } } + +extension SelectedRelay: CustomDebugStringConvertible { + public var debugDescription: String { + "\(hostname) -> \(endpoint.ipv4Relay.description)" + } +} From c90da3760810da79258819b7bb225456e6084003 Mon Sep 17 00:00:00 2001 From: Andrew Bulhak Date: Thu, 2 May 2024 18:29:12 +0200 Subject: [PATCH 44/44] Update the UI to indicate PQ state --- .../QuantumResistanceSettings.swift | 7 + ios/MullvadVPN.xcodeproj/project.pbxproj | 10 + .../SimulatorTunnelProviderHost.swift | 6 +- .../MapConnectionStatusOperation.swift | 6 +- .../TunnelManager/StartTunnelOperation.swift | 5 +- .../TunnelManager/TunnelState+UI.swift | 260 ++++++++++++++++++ .../TunnelManager/TunnelState.swift | 28 +- .../Tunnel/TunnelControlView.swift | 216 +-------------- .../Tunnel/TunnelViewController.swift | 6 +- .../TunnelManager/TunnelStateTests.swift | 139 ++++++++++ .../Actor/ObservedState.swift | 8 +- .../Actor/PacketTunnelActor.swift | 8 +- ios/PacketTunnelCore/Actor/State.swift | 3 + 13 files changed, 460 insertions(+), 242 deletions(-) create mode 100644 ios/MullvadVPN/TunnelManager/TunnelState+UI.swift create mode 100644 ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStateTests.swift diff --git a/ios/MullvadSettings/QuantumResistanceSettings.swift b/ios/MullvadSettings/QuantumResistanceSettings.swift index b5c12ae703b5..956b2fd0ded2 100644 --- a/ios/MullvadSettings/QuantumResistanceSettings.swift +++ b/ios/MullvadSettings/QuantumResistanceSettings.swift @@ -13,3 +13,10 @@ public enum TunnelQuantumResistance: Codable { case on case off } + +public extension TunnelQuantumResistance { + /// A single source of truth for whether the current state counts as on + var isEnabled: Bool { + self == .on + } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index c8fb16cb0135..01a4df4ae586 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -47,6 +47,9 @@ 449EBA262B975B9700DFA4EB /* PostQuantumKeyReceiving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449EBA252B975B9700DFA4EB /* PostQuantumKeyReceiving.swift */; }; 44B02E3B2BC5732D008EDF34 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B02E3A2BC5732D008EDF34 /* LoggingTests.swift */; }; 44B02E3C2BC5B8A5008EDF34 /* Bundle+ProductVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */; }; + 44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; }; + 44BB5F982BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; }; + 44BB5F9A2BE529FF002520EB /* TunnelStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F992BE529FE002520EB /* TunnelStateTests.swift */; }; 44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */; }; 44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */; }; 44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; }; @@ -1379,6 +1382,8 @@ 449EB9FE2B95FF2500DFA4EB /* AccountMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMock.swift; sourceTree = ""; }; 449EBA252B975B9700DFA4EB /* PostQuantumKeyReceiving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyReceiving.swift; sourceTree = ""; }; 44B02E3A2BC5732D008EDF34 /* LoggingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = ""; }; + 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelState+UI.swift"; sourceTree = ""; }; + 44BB5F992BE529FE002520EB /* TunnelStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelStateTests.swift; sourceTree = ""; }; 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = ""; }; 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = ""; }; 44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = ""; }; @@ -2483,6 +2488,7 @@ 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */, 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */, A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */, + 44BB5F992BE529FE002520EB /* TunnelStateTests.swift */, A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */, A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */, 58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */, @@ -2596,6 +2602,7 @@ 5820676326E771DB00655B05 /* TunnelManagerErrors.swift */, 5823FA5326CE49F600283BF8 /* TunnelObserver.swift */, 58B93A1226C3F13600A55733 /* TunnelState.swift */, + 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */, 5803B4B12940A48700C23744 /* TunnelStore.swift */, 5842102F282D8A3C00F24E46 /* UpdateAccountDataOperation.swift */, 58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */, @@ -5199,6 +5206,7 @@ A9A5F9FE2ACB05160083449F /* NotificationManager.swift in Sources */, A9A5F9FF2ACB05160083449F /* NotificationManagerDelegate.swift in Sources */, 7A9BE5AD2B90DF2D00E2A7D0 /* AllLocationsDataSourceTests.swift in Sources */, + 44BB5F9A2BE529FF002520EB /* TunnelStateTests.swift in Sources */, A900E9BE2ACC654100C95F67 /* APIProxy+Stubs.swift in Sources */, A900E9BA2ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift in Sources */, A9A5FA002ACB05160083449F /* ProductsRequestOperation.swift in Sources */, @@ -5267,6 +5275,7 @@ 7A9BE5AB2B909A1700E2A7D0 /* LocationDataSourceProtocol.swift in Sources */, A9A5FA2A2ACB05160083449F /* CoordinatesTests.swift in Sources */, 44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */, + 44BB5F982BE527F4002520EB /* TunnelState+UI.swift in Sources */, A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */, A9A5FA2C2ACB05160083449F /* DeviceCheckOperationTests.swift in Sources */, A9A5FA2D2ACB05160083449F /* DurationTests.swift in Sources */, @@ -5444,6 +5453,7 @@ 7A9CCCB72A96302800DD6A34 /* RevokedCoordinator.swift in Sources */, 7A6389F82B864CDF008E77E1 /* LocationNode.swift in Sources */, 587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */, + 44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */, 7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */, 5827B0902B0CAA0500CCBBA1 /* EditAccessMethodCoordinator.swift in Sources */, 5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */, diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift index e7bf690f872d..ea5260b8af2d 100644 --- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift +++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift @@ -176,14 +176,16 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { guard let selectedRelay = selectedRelay else { return } do { + let settings = try SettingsManager.readSettings() observedState = .connected( ObservedConnectionState( selectedRelay: selectedRelay, - relayConstraints: try SettingsManager.readSettings().relayConstraints, + relayConstraints: settings.relayConstraints, networkReachability: .reachable, connectionAttemptCount: 0, transportLayer: .udp, - remotePort: selectedRelay.endpoint.ipv4Relay.port + remotePort: selectedRelay.endpoint.ipv4Relay.port, + isPostQuantum: settings.tunnelQuantumResistance.isEnabled ) ) } catch { diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift index f92fd37e077b..4957af7c52ec 100644 --- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift @@ -51,11 +51,11 @@ class MapConnectionStatusOperation: AsyncOperation { switch observedState { case let .connected(connectionState): return connectionState.isNetworkReachable - ? .connected(connectionState.selectedRelay) + ? .connected(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum) : .waitingForConnectivity(.noConnection) case let .connecting(connectionState): return connectionState.isNetworkReachable - ? .connecting(connectionState.selectedRelay) + ? .connecting(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum) : .waitingForConnectivity(.noConnection) case let .negotiatingPostQuantumKey(connectionState, privateKey): return connectionState.isNetworkReachable @@ -63,7 +63,7 @@ class MapConnectionStatusOperation: AsyncOperation { : .waitingForConnectivity(.noConnection) case let .reconnecting(connectionState): return connectionState.isNetworkReachable - ? .reconnecting(connectionState.selectedRelay) + ? .reconnecting(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum) : .waitingForConnectivity(.noConnection) case let .error(blockedState): return .error(blockedState.reason) diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift index 9474a0a4813f..cd9e8b7a88c5 100644 --- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift @@ -90,7 +90,10 @@ class StartTunnelOperation: ResultOperation { interactor.updateTunnelStatus { tunnelStatus in tunnelStatus = TunnelStatus() - tunnelStatus.state = .connecting(selectedRelay) + tunnelStatus.state = .connecting( + selectedRelay, + isPostQuantum: interactor.settings.tunnelQuantumResistance.isEnabled + ) } try tunnel.start(options: tunnelOptions.rawOptions()) diff --git a/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift b/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift new file mode 100644 index 000000000000..090fdbfe2256 --- /dev/null +++ b/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift @@ -0,0 +1,260 @@ +// +// TunnelState+UI.swift +// MullvadVPN +// +// Created by Andrew Bulhak on 2024-05-03. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import UIKit + +extension TunnelState { + var textColorForSecureLabel: UIColor { + switch self { + case .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .negotiatingPostQuantumKey: + .white + + case .connected: + .successColor + + case .disconnecting, .disconnected, .pendingReconnect, .waitingForConnectivity(.noNetwork), .error: + .dangerColor + } + } + + var shouldEnableButtons: Bool { + if case .waitingForConnectivity(.noNetwork) = self { + return false + } + + return true + } + + var localizedTitleForSecureLabel: String { + switch self { + case let .connecting(_, isPostQuantum), let .reconnecting(_, isPostQuantum): + if isPostQuantum { + NSLocalizedString( + "TUNNEL_STATE_PQ_CONNECTING", + tableName: "Main", + value: "Creating quantum secure connection", + comment: "" + ) + } else { + NSLocalizedString( + "TUNNEL_STATE_CONNECTING", + tableName: "Main", + value: "Creating secure connection", + comment: "" + ) + } + + case .negotiatingPostQuantumKey: + NSLocalizedString( + "TUNNEL_STATE_NEGOTIATING_KEY", + tableName: "Main", + value: "Creating quantum secure connection", + comment: "" + ) + + case let .connected(_, isPostQuantum): + if isPostQuantum { + NSLocalizedString( + "TUNNEL_STATE_PQ_CONNECTED", + tableName: "Main", + value: "Quantum secure connection", + comment: "" + ) + } else { + NSLocalizedString( + "TUNNEL_STATE_CONNECTED", + tableName: "Main", + value: "Secure connection", + comment: "" + ) + } + + case .disconnecting(.nothing): + NSLocalizedString( + "TUNNEL_STATE_DISCONNECTING", + tableName: "Main", + value: "Disconnecting", + comment: "" + ) + case .disconnecting(.reconnect), .pendingReconnect: + NSLocalizedString( + "TUNNEL_STATE_PENDING_RECONNECT", + tableName: "Main", + value: "Reconnecting", + comment: "" + ) + + case .disconnected: + NSLocalizedString( + "TUNNEL_STATE_DISCONNECTED", + tableName: "Main", + value: "Unsecured connection", + comment: "" + ) + + case .waitingForConnectivity(.noConnection), .error: + NSLocalizedString( + "TUNNEL_STATE_WAITING_FOR_CONNECTIVITY", + tableName: "Main", + value: "Blocked connection", + comment: "" + ) + + case .waitingForConnectivity(.noNetwork): + NSLocalizedString( + "TUNNEL_STATE_NO_NETWORK", + tableName: "Main", + value: "No network", + comment: "" + ) + } + } + + var localizedTitleForSelectLocationButton: String? { + switch self { + case .disconnecting(.reconnect), .pendingReconnect: + NSLocalizedString( + "SWITCH_LOCATION_BUTTON_TITLE", + tableName: "Main", + value: "Select location", + comment: "" + ) + + case .disconnected, .disconnecting(.nothing): + NSLocalizedString( + "SELECT_LOCATION_BUTTON_TITLE", + tableName: "Main", + value: "Select location", + comment: "" + ) + + case .connecting, .connected, .reconnecting, .waitingForConnectivity, .error: + NSLocalizedString( + "SWITCH_LOCATION_BUTTON_TITLE", + tableName: "Main", + value: "Switch location", + comment: "" + ) + + case .negotiatingPostQuantumKey: + NSLocalizedString( + "SWITCH_LOCATION_BUTTON_TITLE", + tableName: "Main", + value: "Switch location", + comment: "" + ) + } + } + + var localizedAccessibilityLabel: String { + switch self { + case let .connecting(_, isPostQuantum): + if isPostQuantum { + NSLocalizedString( + "TUNNEL_STATE_PQ_CONNECTING_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Creating quantum secure connection", + comment: "" + ) + } else { + NSLocalizedString( + "TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Creating secure connection", + comment: "" + ) + } + + // TODO: Is this correct ? + case .negotiatingPostQuantumKey: + NSLocalizedString( + "TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Creating quantum secure connection", + comment: "" + ) + + case let .connected(tunnelInfo, isPostQuantum): + if isPostQuantum { + String( + format: NSLocalizedString( + "TUNNEL_STATE_PQ_CONNECTED_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Quantum secure connection. Connected to %@, %@", + comment: "" + ), + tunnelInfo.location.city, + tunnelInfo.location.country + ) + } else { + String( + format: NSLocalizedString( + "TUNNEL_STATE_CONNECTED_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Secure connection. Connected to %@, %@", + comment: "" + ), + tunnelInfo.location.city, + tunnelInfo.location.country + ) + } + + case .disconnected: + NSLocalizedString( + "TUNNEL_STATE_DISCONNECTED_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Unsecured connection", + comment: "" + ) + + case let .reconnecting(tunnelInfo, _): + String( + format: NSLocalizedString( + "TUNNEL_STATE_RECONNECTING_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Reconnecting to %@, %@", + comment: "" + ), + tunnelInfo.location.city, + tunnelInfo.location.country + ) + + case .waitingForConnectivity(.noConnection), .error: + NSLocalizedString( + "TUNNEL_STATE_WAITING_FOR_CONNECTIVITY_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Blocked connection", + comment: "" + ) + + case .waitingForConnectivity(.noNetwork): + NSLocalizedString( + "TUNNEL_STATE_NO_NETWORK_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "No network", + comment: "" + ) + + case .disconnecting(.nothing): + NSLocalizedString( + "TUNNEL_STATE_DISCONNECTING_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Disconnecting", + comment: "" + ) + + case .disconnecting(.reconnect), .pendingReconnect: + NSLocalizedString( + "TUNNEL_STATE_PENDING_RECONNECT_ACCESSIBILITY_LABEL", + tableName: "Main", + value: "Reconnecting", + comment: "" + ) + } + } +} diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift index 1d480fe3a53f..76148bdbb889 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelState.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift @@ -49,13 +49,13 @@ enum TunnelState: Equatable, CustomStringConvertible { case pendingReconnect /// Connecting the tunnel. - case connecting(SelectedRelay?) + case connecting(SelectedRelay?, isPostQuantum: Bool) /// Negotiating a key for post-quantum resistance case negotiatingPostQuantumKey(SelectedRelay, PrivateKey) /// Connected the tunnel - case connected(SelectedRelay) + case connected(SelectedRelay, isPostQuantum: Bool) /// Disconnecting the tunnel case disconnecting(ActionAfterDisconnect) @@ -68,7 +68,7 @@ enum TunnelState: Equatable, CustomStringConvertible { /// 1. Asking the running tunnel to reconnect to new relay via IPC. /// 2. Tunnel attempts to reconnect to new relay as the current relay appears to be /// dysfunctional. - case reconnecting(SelectedRelay) + case reconnecting(SelectedRelay, isPostQuantum: Bool) /// Waiting for connectivity to come back up. case waitingForConnectivity(WaitingForConnectionReason) @@ -80,20 +80,20 @@ enum TunnelState: Equatable, CustomStringConvertible { switch self { case .pendingReconnect: "pending reconnect after disconnect" - case let .connecting(tunnelRelay): + case let .connecting(tunnelRelay, isPostQuantum): if let tunnelRelay { - "connecting to \(tunnelRelay.hostname)" + "connecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelay.hostname)" } else { - "connecting, fetching relay" + "connecting\(isPostQuantum ? " (PQ)" : ""), fetching relay" } - case let .connected(tunnelRelay): - "connected to \(tunnelRelay.hostname)" + case let .connected(tunnelRelay, isPostQuantum): + "connected \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelay.hostname)" case let .disconnecting(actionAfterDisconnect): "disconnecting and then \(actionAfterDisconnect)" case .disconnected: "disconnected" - case let .reconnecting(tunnelRelay): - "reconnecting to \(tunnelRelay.hostname)" + case let .reconnecting(tunnelRelay, isPostQuantum): + "reconnecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelay.hostname)" case .waitingForConnectivity: "waiting for connectivity" case let .error(blockedStateReason): @@ -106,20 +106,18 @@ enum TunnelState: Equatable, CustomStringConvertible { var isSecured: Bool { switch self { case .reconnecting, .connecting, .connected, .waitingForConnectivity(.noConnection), .error(.accountExpired), - .error(.deviceRevoked): + .error(.deviceRevoked), .negotiatingPostQuantumKey: true case .pendingReconnect, .disconnecting, .disconnected, .waitingForConnectivity(.noNetwork), .error: false - case .negotiatingPostQuantumKey: - false } } var relay: SelectedRelay? { switch self { - case let .connected(relay), let .reconnecting(relay), let .negotiatingPostQuantumKey(relay, _): + case let .connected(relay, _), let .reconnecting(relay, _), let .negotiatingPostQuantumKey(relay, _): relay - case let .connecting(relay): + case let .connecting(relay, _): relay case .disconnecting, .disconnected, .waitingForConnectivity, .pendingReconnect, .error: nil diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift index 2d348b252835..92acdb7a5542 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift @@ -27,7 +27,7 @@ private enum TunnelControlActionButton { } final class TunnelControlView: UIView { - private let secureLabel = makeBoldTextLabel(ofSize: 20) + private let secureLabel = makeBoldTextLabel(ofSize: 20, numberOfLines: 0) private let cityLabel = makeBoldTextLabel(ofSize: 34) private let countryLabel = makeBoldTextLabel(ofSize: 34) @@ -420,11 +420,12 @@ final class TunnelControlView: UIView { ) } - private class func makeBoldTextLabel(ofSize fontSize: CGFloat) -> UILabel { + private class func makeBoldTextLabel(ofSize fontSize: CGFloat, numberOfLines: Int = 1) -> UILabel { let textLabel = UILabel() textLabel.translatesAutoresizingMaskIntoConstraints = false textLabel.font = UIFont.boldSystemFont(ofSize: fontSize) textLabel.textColor = .white + textLabel.numberOfLines = numberOfLines return textLabel } @@ -452,217 +453,6 @@ final class TunnelControlView: UIView { } private extension TunnelState { - var textColorForSecureLabel: UIColor { - switch self { - case .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .negotiatingPostQuantumKey: - .white - - case .connected: - .successColor - - case .disconnecting, .disconnected, .pendingReconnect, .waitingForConnectivity(.noNetwork), .error: - .dangerColor - } - } - - var shouldEnableButtons: Bool { - if case .waitingForConnectivity(.noNetwork) = self { - return false - } - - return true - } - - var localizedTitleForSecureLabel: String { - switch self { - case .connecting, .reconnecting: - NSLocalizedString( - "TUNNEL_STATE_CONNECTING", - tableName: "Main", - value: "Creating secure connection", - comment: "" - ) - - // TODO: Is this the correct message here ? - case .negotiatingPostQuantumKey: - NSLocalizedString( - "TUNNEL_STATE_NEGOTIATING_KEY", - tableName: "Main", - value: "Creating quantum secure connection", - comment: "" - ) - - case .connected: - NSLocalizedString( - "TUNNEL_STATE_CONNECTED", - tableName: "Main", - value: "Secure connection", - comment: "" - ) - - case .disconnecting(.nothing): - NSLocalizedString( - "TUNNEL_STATE_DISCONNECTING", - tableName: "Main", - value: "Disconnecting", - comment: "" - ) - case .disconnecting(.reconnect), .pendingReconnect: - NSLocalizedString( - "TUNNEL_STATE_PENDING_RECONNECT", - tableName: "Main", - value: "Reconnecting", - comment: "" - ) - - case .disconnected: - NSLocalizedString( - "TUNNEL_STATE_DISCONNECTED", - tableName: "Main", - value: "Unsecured connection", - comment: "" - ) - - case .waitingForConnectivity(.noConnection), .error: - NSLocalizedString( - "TUNNEL_STATE_WAITING_FOR_CONNECTIVITY", - tableName: "Main", - value: "Blocked connection", - comment: "" - ) - - case .waitingForConnectivity(.noNetwork): - NSLocalizedString( - "TUNNEL_STATE_NO_NETWORK", - tableName: "Main", - value: "No network", - comment: "" - ) - } - } - - var localizedTitleForSelectLocationButton: String? { - switch self { - case .disconnecting(.reconnect), .pendingReconnect: - NSLocalizedString( - "SWITCH_LOCATION_BUTTON_TITLE", - tableName: "Main", - value: "Select location", - comment: "" - ) - - case .disconnected, .disconnecting(.nothing): - NSLocalizedString( - "SELECT_LOCATION_BUTTON_TITLE", - tableName: "Main", - value: "Select location", - comment: "" - ) - - case .connecting, .connected, .reconnecting, .waitingForConnectivity, .error: - NSLocalizedString( - "SWITCH_LOCATION_BUTTON_TITLE", - tableName: "Main", - value: "Switch location", - comment: "" - ) - - // TODO: Is this correct ? - case .negotiatingPostQuantumKey: - NSLocalizedString( - "SWITCH_LOCATION_BUTTON_TITLE", - tableName: "Main", - value: "Switch location", - comment: "" - ) - } - } - - var localizedAccessibilityLabel: String { - switch self { - case .connecting: - NSLocalizedString( - "TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Creating secure connection", - comment: "" - ) - - // TODO: Is this correct ? - case .negotiatingPostQuantumKey: - NSLocalizedString( - "TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Creating secure connection", - comment: "" - ) - - case let .connected(tunnelInfo): - String( - format: NSLocalizedString( - "TUNNEL_STATE_CONNECTED_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Secure connection. Connected to %@, %@", - comment: "" - ), - tunnelInfo.location.city, - tunnelInfo.location.country - ) - - case .disconnected: - NSLocalizedString( - "TUNNEL_STATE_DISCONNECTED_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Unsecured connection", - comment: "" - ) - - case let .reconnecting(tunnelInfo): - String( - format: NSLocalizedString( - "TUNNEL_STATE_RECONNECTING_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Reconnecting to %@, %@", - comment: "" - ), - tunnelInfo.location.city, - tunnelInfo.location.country - ) - - case .waitingForConnectivity(.noConnection), .error: - NSLocalizedString( - "TUNNEL_STATE_WAITING_FOR_CONNECTIVITY_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Blocked connection", - comment: "" - ) - - case .waitingForConnectivity(.noNetwork): - NSLocalizedString( - "TUNNEL_STATE_NO_NETWORK_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "No network", - comment: "" - ) - - case .disconnecting(.nothing): - NSLocalizedString( - "TUNNEL_STATE_DISCONNECTING_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Disconnecting", - comment: "" - ) - - case .disconnecting(.reconnect), .pendingReconnect: - NSLocalizedString( - "TUNNEL_STATE_PENDING_RECONNECT_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Reconnecting", - comment: "" - ) - } - } - func actionButtons(traitCollection: UITraitCollection) -> [TunnelControlActionButton] { switch (traitCollection.userInterfaceIdiom, traitCollection.horizontalSizeClass) { case (.phone, _), (.pad, .compact): diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index ce7b72a9f357..36f853504768 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -147,17 +147,17 @@ class TunnelViewController: UIViewController, RootContainment { private func updateMap(animated: Bool) { switch tunnelState { - case let .connecting(tunnelRelay): + case let .connecting(tunnelRelay, _): mapViewController.removeLocationMarker() contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelay?.location.geoCoordinate, animated: animated) - case let .reconnecting(tunnelRelay), let .negotiatingPostQuantumKey(tunnelRelay, _): + case let .reconnecting(tunnelRelay, _), let .negotiatingPostQuantumKey(tunnelRelay, _): mapViewController.removeLocationMarker() contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelay.location.geoCoordinate, animated: animated) - case let .connected(tunnelRelay): + case let .connected(tunnelRelay, _): let center = tunnelRelay.location.geoCoordinate mapViewController.setCenter(center, animated: animated) { self.contentView.setAnimatingActivity(false) diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStateTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStateTests.swift new file mode 100644 index 000000000000..9a707fb30d3f --- /dev/null +++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStateTests.swift @@ -0,0 +1,139 @@ +// +// TunnelStateTests.swift +// MullvadVPNTests +// +// Created by Andrew Bulhak on 2024-05-03. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import MullvadTypes +import PacketTunnelCore +import XCTest + +final class TunnelStateTests: XCTestCase { + let arbitrarySelectedRelay = SelectedRelay( + endpoint: MullvadEndpoint( + ipv4Relay: IPv4Endpoint(ip: .any, port: 0), + ipv4Gateway: .any, + ipv6Gateway: .any, + publicKey: Data() + ), + hostname: "hostname-goes-here", + location: Location(country: "country", countryCode: "", city: "city", cityCode: "", latitude: 0, longitude: 0), + retryAttempts: 0 + ) + + // MARK: description + + func testDescription_Connecting_NoRelay() { + XCTAssertEqual( + TunnelState.connecting(nil, isPostQuantum: false).description, + "connecting, fetching relay" + ) + + XCTAssertEqual( + TunnelState.connecting(nil, isPostQuantum: true).description, + "connecting (PQ), fetching relay" + ) + } + + func testDescription_Connecting_WithRelay() { + XCTAssertEqual( + TunnelState.connecting(arbitrarySelectedRelay, isPostQuantum: false).description, + "connecting to hostname-goes-here" + ) + + XCTAssertEqual( + TunnelState.connecting(arbitrarySelectedRelay, isPostQuantum: true).description, + "connecting (PQ) to hostname-goes-here" + ) + } + + func testDescription_Connected() { + XCTAssertEqual( + TunnelState.connected(arbitrarySelectedRelay, isPostQuantum: false).description, + "connected to hostname-goes-here" + ) + + XCTAssertEqual( + TunnelState.connected(arbitrarySelectedRelay, isPostQuantum: true).description, + "connected (PQ) to hostname-goes-here" + ) + } + + // MARK: localizedTitleForSecureLabel + + func testLocalizedTitleForSecureLabel_Connecting() { + XCTAssertEqual( + TunnelState.connecting(nil, isPostQuantum: false).localizedTitleForSecureLabel, + "Creating secure connection" + ) + + XCTAssertEqual( + TunnelState.connecting(nil, isPostQuantum: true).localizedTitleForSecureLabel, + "Creating quantum secure connection" + ) + } + + func testLocalizedTitleForSecureLabel_Reconnecting() { + XCTAssertEqual( + TunnelState.reconnecting(arbitrarySelectedRelay, isPostQuantum: false).localizedTitleForSecureLabel, + "Creating secure connection" + ) + + XCTAssertEqual( + TunnelState.reconnecting(arbitrarySelectedRelay, isPostQuantum: true).localizedTitleForSecureLabel, + "Creating quantum secure connection" + ) + } + + func testLocalizedTitleForSecureLabel_Connected() { + XCTAssertEqual( + TunnelState.connected(arbitrarySelectedRelay, isPostQuantum: false).localizedTitleForSecureLabel, + "Secure connection" + ) + + XCTAssertEqual( + TunnelState.connected(arbitrarySelectedRelay, isPostQuantum: true).localizedTitleForSecureLabel, + "Quantum secure connection" + ) + } + + // MARK: localizedAccessibilityLabel + + func testLocalizedAccessibilityLabel_Connecting() { + XCTAssertEqual( + TunnelState.connecting(nil, isPostQuantum: false).localizedAccessibilityLabel, + "Creating secure connection" + ) + + XCTAssertEqual( + TunnelState.connecting(nil, isPostQuantum: true).localizedAccessibilityLabel, + "Creating quantum secure connection" + ) + } + + func testLocalizedAccessibilityLabel_Reconnecting() { + XCTAssertEqual( + TunnelState.reconnecting(arbitrarySelectedRelay, isPostQuantum: false).localizedAccessibilityLabel, + "Reconnecting to city, country" + ) + + XCTAssertEqual( + TunnelState.reconnecting(arbitrarySelectedRelay, isPostQuantum: true).localizedAccessibilityLabel, + "Reconnecting to city, country" + ) + } + + func testLocalizedAccessibilityLabel_Connected() { + XCTAssertEqual( + TunnelState.connected(arbitrarySelectedRelay, isPostQuantum: false).localizedAccessibilityLabel, + "Secure connection. Connected to city, country" + ) + + XCTAssertEqual( + TunnelState.connected(arbitrarySelectedRelay, isPostQuantum: true).localizedAccessibilityLabel, + "Quantum secure connection. Connected to city, country" + ) + } +} diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift index f05d68e5252c..bdb85a8e51b4 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState.swift @@ -33,6 +33,7 @@ public struct ObservedConnectionState: Equatable, Codable { public var transportLayer: TransportLayer public var remotePort: UInt16 public var lastKeyRotation: Date? + public let isPostQuantum: Bool public var isNetworkReachable: Bool { networkReachability != .unreachable @@ -45,7 +46,8 @@ public struct ObservedConnectionState: Equatable, Codable { connectionAttemptCount: UInt, transportLayer: TransportLayer, remotePort: UInt16, - lastKeyRotation: Date? = nil + lastKeyRotation: Date? = nil, + isPostQuantum: Bool ) { self.selectedRelay = selectedRelay self.relayConstraints = relayConstraints @@ -54,6 +56,7 @@ public struct ObservedConnectionState: Equatable, Codable { self.transportLayer = transportLayer self.remotePort = remotePort self.lastKeyRotation = lastKeyRotation + self.isPostQuantum = isPostQuantum } } @@ -97,7 +100,8 @@ extension State.ConnectionData { connectionAttemptCount: connectionAttemptCount, transportLayer: transportLayer, remotePort: remotePort, - lastKeyRotation: lastKeyRotation + lastKeyRotation: lastKeyRotation, + isPostQuantum: isPostQuantum ) } } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift index 768024ab23cd..552624d504c7 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift @@ -283,7 +283,7 @@ extension PacketTunnelActor { ) async throws { let settings: Settings = try settingsReader.read() - guard settings.quantumResistance == .off || settings.quantumResistance == .automatic else { + 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) @@ -411,7 +411,8 @@ extension PacketTunnelActor { lastKeyRotation: lastKeyRotation, connectedEndpoint: selectedRelay.endpoint, transportLayer: .udp, - remotePort: selectedRelay.endpoint.ipv4Relay.port + remotePort: selectedRelay.endpoint.ipv4Relay.port, + isPostQuantum: settings.quantumResistance.isEnabled ) } @@ -449,7 +450,8 @@ extension PacketTunnelActor { lastKeyRotation: connectionState.lastKeyRotation, connectedEndpoint: obfuscatedEndpoint, transportLayer: transportLayer, - remotePort: protocolObfuscator.remotePort + remotePort: protocolObfuscator.remotePort, + isPostQuantum: connectionState.isPostQuantum ) } diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift index 259993a7f8e0..f99799201cea 100644 --- a/ios/PacketTunnelCore/Actor/State.swift +++ b/ios/PacketTunnelCore/Actor/State.swift @@ -146,6 +146,9 @@ extension State { /// The remote port that was chosen to connect to `connectedEndpoint` public let remotePort: UInt16 + + /// True if post-quantum key exchange is enabled + public let isPostQuantum: Bool } /// Data associated with error state.