summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2024-02-06 23:07:37 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-05-21 13:32:17 +0200
commite5ee91b2e65041bd2b347b8c5651cebf09861c64 (patch)
tree42a3c1181627b961d92318a8ccc2cda9efebbe49
parentcba171ca2232838b3e7628221ed1b5a56f93230f (diff)
downloadmullvadvpn-e5ee91b2e65041bd2b347b8c5651cebf09861c64.tar.xz
mullvadvpn-e5ee91b2e65041bd2b347b8c5651cebf09861c64.zip
Implement PQ PSK
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--ios/MullvadPostQuantum/MullvadPostQuantum.h19
-rw-r--r--ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift78
-rw-r--r--ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift54
-rw-r--r--ios/MullvadPostQuantum/module.private.modulemap5
-rw-r--r--ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h71
-rw-r--r--ios/MullvadSettings/QuantumResistanceSettings.swift7
-rw-r--r--ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift12
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj410
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantum.xcscheme66
-rw-r--r--ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift8
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift6
-rw-r--r--ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift12
-rw-r--r--ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift5
-rw-r--r--ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift7
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelState+UI.swift259
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelState.swift43
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift232
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift13
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift26
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStateTests.swift139
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift44
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/SettingsReader.swift3
-rw-r--r--ios/PacketTunnel/PostQuantumKeyExchangeActor.swift78
-rw-r--r--ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift25
-rw-r--r--ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift14
-rw-r--r--ios/PacketTunnelCore/Actor/ObservedState.swift15
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift4
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift3
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift4
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift99
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift6
-rw-r--r--ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift6
-rw-r--r--ios/PacketTunnelCore/Actor/State+Extensions.swift9
-rw-r--r--ios/PacketTunnelCore/Actor/State.swift6
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift3
-rw-r--r--ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift3
-rw-r--r--ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift19
-rw-r--r--ios/build-rust-library.sh20
-rw-r--r--talpid-routing/src/unix/mod.rs2
-rw-r--r--talpid-tunnel-config-client/Cargo.toml26
-rw-r--r--talpid-tunnel-config-client/build.rs12
-rw-r--r--talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs167
-rw-r--r--talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs184
-rw-r--r--talpid-tunnel-config-client/src/ios_ffi/mod.rs113
-rw-r--r--talpid-tunnel-config-client/src/lib.rs98
50 files changed, 2009 insertions, 442 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 34c23cef16..07fb64dcd9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4070,9 +4070,11 @@ dependencies = [
name = "talpid-tunnel-config-client"
version = "0.0.0"
dependencies = [
+ "cbindgen",
"classic-mceliece-rust",
"libc",
"log",
+ "oslog",
"pqc_kyber",
"prost",
"rand 0.8.5",
diff --git a/Cargo.toml b/Cargo.toml
index aa3b9145c8..f7fcd70642 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -80,7 +80,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 0000000000..f48ca77519
--- /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 <Foundation/Foundation.h>
+
+//! 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 <MullvadPostQuantum/PublicHeader.h>
+
+
diff --git a/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift
new file mode 100644
index 0000000000..8b06c7b178
--- /dev/null
+++ b/ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift
@@ -0,0 +1,78 @@
+//
+// 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?
+) {
+ guard let connection, let sender else { return }
+ let tcpConnection = Unmanaged<NWTCPConnection>.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?
+) {
+ guard let connection, let sender else { return }
+ let tcpConnection = Unmanaged<NWTCPConnection>.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?,
+ rawEphemeralKey: UnsafeMutableRawPointer?
+) {
+ guard
+ let rawPacketTunnel,
+ let postQuantumKeyReceiver = Unmanaged<NEPacketTunnelProvider>.fromOpaque(rawPacketTunnel)
+ .takeUnretainedValue() as? PostQuantumKeyReceiving
+ else { return }
+
+ 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
+ }
+
+ postQuantumKeyReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
+}
diff --git a/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift
new file mode 100644
index 0000000000..8e813f47df
--- /dev/null
+++ b/ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift
@@ -0,0 +1,54 @@
+//
+// PostQuantumKeyNegotiator.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 class PostQuantumKeyNegotiator {
+ public init() {}
+
+ var cancelToken: PostQuantumCancelToken?
+
+ public func negotiateKey(
+ gatewayIP: IPv4Address,
+ devicePublicKey: PublicKey,
+ presharedKey: PrivateKey,
+ packetTunnel: NEPacketTunnelProvider,
+ tcpConnection: NWTCPConnection
+ ) {
+ let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque()
+ let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque()
+ var cancelToken = PostQuantumCancelToken()
+
+ // TODO: Any non 0 return is considered a failure, and should be handled gracefully
+ let result = negotiate_post_quantum_key(
+ devicePublicKey.rawValue.map { $0 },
+ presharedKey.rawValue.map { $0 },
+ packetTunnelPointer,
+ opaqueConnection,
+ &cancelToken
+ )
+ guard result == 0 else {
+ // Handle failure here
+ return
+ }
+ self.cancelToken = cancelToken
+ }
+
+ public func cancelKeyNegotiation() {
+ 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/module.private.modulemap b/ios/MullvadPostQuantum/module.private.modulemap
new file mode 100644
index 0000000000..f831c7ca2e
--- /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"
+ export *
+}
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 0000000000..7b9315c12f
--- /dev/null
+++ b/ios/MullvadPostQuantum/talpid-tunnel-config-client/include/talpid_tunnel_config_client.h
@@ -0,0 +1,71 @@
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * Port used by the tunnel config service.
+ */
+#define CONFIG_SERVICE_PORT 1337
+
+typedef struct PostQuantumCancelToken {
+ void *context;
+} PostQuantumCancelToken;
+
+/**
+ * * # Safety * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`
+ */
+void cancel_post_quantum_key_exchange(const struct PostQuantumCancelToken *sender);
+
+/**
+ * * # Safety * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`.
+ */
+void drop_post_quantum_key_exchange_token(const struct PostQuantumCancelToken *sender);
+
+/**
+ * * # Safety * `sender` must be pointing to a valid instance of a `write_tx` created by the `IosTcpProvider` * * Callback to call when the TCP connection has written data.
+ */
+void handle_sent(uintptr_t bytes_sent,
+ const void *sender);
+
+/**
+ * * # Safety * `sender` must be pointing to a valid instance of a `read_tx` created by the `IosTcpProvider` * * Callback to call when the TCP connection has received data.
+ */
+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
+ */
+int32_t negotiate_post_quantum_key(const uint8_t *public_key,
+ const uint8_t *ephemeral_key,
+ const void *packet_tunnel,
+ const void *tcp_connection,
+ struct PostQuantumCancelToken *cancel_token);
+
+/**
+ * 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,
+ const uint8_t *raw_ephemeral_private_key);
diff --git a/ios/MullvadSettings/QuantumResistanceSettings.swift b/ios/MullvadSettings/QuantumResistanceSettings.swift
index b5c12ae703..956b2fd0de 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/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift
index eb696fcca3..50809c50b1 100644
--- a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift
+++ b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift
@@ -10,18 +10,10 @@ import Foundation
import WireGuardKitTypes
public protocol PostQuantumKeyReceiving {
- func receivePostQuantumKey(_ key: PreSharedKey)
+ func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey)
+ func keyExchangeFailed()
}
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/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 776943004b..c5b6fb9042 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -44,6 +44,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 */; };
@@ -683,15 +686,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 /* PostQuantumKeyNegotiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiator.swift */; };
+ A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client.a */; };
+ A948809B2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */; };
A94D691A2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E22AA72AE9003D1918 /* WireGuardKitTypes */; };
A94D691B2ABAD66700413DD4 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58FE25E72AA7399D003D1918 /* WireGuardKitTypes */; };
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 */; };
+ 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 */; };
@@ -1195,6 +1209,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 */;
@@ -1228,6 +1256,7 @@
F0ACE3112BE4E478006D5333 /* MullvadMockData.framework in Embed Frameworks */,
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 */,
@@ -1315,6 +1344,29 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
+ A906F94C2BA1E09A002BF22E /* WireGuardKitTypes in Embed Frameworks */,
+ );
+ 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;
@@ -1371,6 +1423,8 @@
449EB9FE2B95FF2500DFA4EB /* AccountMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMock.swift; sourceTree = "<group>"; };
449EBA252B975B9700DFA4EB /* PostQuantumKeyReceiving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyReceiving.swift; sourceTree = "<group>"; };
44B02E3A2BC5732D008EDF34 /* LoggingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; };
+ 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelState+UI.swift"; sourceTree = "<group>"; };
+ 44BB5F992BE529FE002520EB /* TunnelStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelStateTests.swift; sourceTree = "<group>"; };
44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = "<group>"; };
44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = "<group>"; };
44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = "<group>"; };
@@ -1998,9 +2052,14 @@
A932D9F42B5EBB9D00999395 /* RESTTransportStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTTransportStub.swift; sourceTree = "<group>"; };
A935594B2B4C2DA900D5D524 /* APIAvailabilityTestRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIAvailabilityTestRequest.swift; sourceTree = "<group>"; };
A935594D2B4E919F00D5D524 /* Api.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Api.xcconfig; sourceTree = "<group>"; };
+ 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 = "<group>"; };
+ 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 = "<group>"; };
A9467E7E2A29DEFE000DC21F /* RelayCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTests.swift; sourceTree = "<group>"; };
+ A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangeActor.swift; sourceTree = "<group>"; };
A95EEE352B722CD600A8A39B /* TunnelMonitorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorState.swift; sourceTree = "<group>"; };
A95EEE372B722DFC00A8A39B /* PingStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PingStats.swift; sourceTree = "<group>"; };
+ 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 = "<group>"; };
A970C89C2B29E38C000A7684 /* Socks5UsernamePasswordCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Socks5UsernamePasswordCommand.swift; sourceTree = "<group>"; };
A97D25AD2B0BB18100946B2D /* ProtocolObfuscator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolObfuscator.swift; sourceTree = "<group>"; };
A97D25AF2B0BB5C400946B2D /* ProtocolObfuscationStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolObfuscationStub.swift; sourceTree = "<group>"; };
@@ -2016,6 +2075,7 @@
A99E5EDF2B7628150033F241 /* ProblemReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportViewModel.swift; sourceTree = "<group>"; };
A99E5EE12B762ED30033F241 /* ProblemReportViewController+ViewManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProblemReportViewController+ViewManagement.swift"; sourceTree = "<group>"; };
A9A1DE782AD5708E0073F689 /* TransportStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportStrategy.swift; sourceTree = "<group>"; };
+ A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelProvider+TCPConnection.swift"; sourceTree = "<group>"; };
A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManagerTests.swift; sourceTree = "<group>"; };
A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = "<group>"; };
A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = "<group>"; };
@@ -2030,6 +2090,7 @@
A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelStore+Stubs.swift"; sourceTree = "<group>"; };
A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusBlockObserver.swift; sourceTree = "<group>"; };
A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = "<group>"; };
+ A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyNegotiator.swift; sourceTree = "<group>"; };
A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesTests.swift; sourceTree = "<group>"; };
A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNConnectionProtocol.swift; sourceTree = "<group>"; };
E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = "<group>"; };
@@ -2196,6 +2257,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 */,
@@ -2209,6 +2271,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 */,
@@ -2294,6 +2357,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ A944F2592B8DEFDB00473F4C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A9630E432B8E10FB00A65999 /* MullvadTypes.framework in Frameworks */,
+ A906F94A2BA1E09A002BF22E /* WireGuardKitTypes in Frameworks */,
+ A944F2722B8E02F600473F4C /* libtalpid_tunnel_config_client.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -2468,6 +2541,7 @@
44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */,
44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */,
A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */,
+ 44BB5F992BE529FE002520EB /* TunnelStateTests.swift */,
A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */,
A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */,
58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */,
@@ -2582,6 +2656,7 @@
5820676326E771DB00655B05 /* TunnelManagerErrors.swift */,
5823FA5326CE49F600283BF8 /* TunnelObserver.swift */,
58B93A1226C3F13600A55733 /* TunnelState.swift */,
+ 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */,
5803B4B12940A48700C23744 /* TunnelStore.swift */,
5842102F282D8A3C00F24E46 /* UpdateAccountDataOperation.swift */,
58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */,
@@ -2982,6 +3057,7 @@
584F991F2902CBDD001F858D /* Frameworks */ = {
isa = PBXGroup;
children = (
+ A944F2712B8E02E800473F4C /* libtalpid_tunnel_config_client.a */,
01EF6F332B6A590700125696 /* libmullvad_api.a */,
01EF6F312B6A58F000125696 /* debug */,
01EF6F2F2B6A588300125696 /* aarch64-apple-ios */,
@@ -3392,6 +3468,9 @@
7A83C3FC2A55B39500DFB83A /* TestPlans */,
584023202A406BF5007B27AC /* TunnelObfuscation */,
58695A9E2A4ADA9200328DB3 /* TunnelObfuscationTests */,
+ A944F25D2B8DEFDB00473F4C /* MullvadPostQuantum */,
+ 58CE5E61224146200008646E /* Products */,
+ 584F991F2902CBDD001F858D /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -3417,6 +3496,7 @@
58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */,
852969252B4D9C1F007EAD4C /* MullvadVPNUITests.xctest */,
F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */,
+ A944F25C2B8DEFDB00473F4C /* MullvadPostQuantum.framework */,
);
name = Products;
sourceTree = "<group>";
@@ -3459,6 +3539,7 @@
58F3F3682AA08E2200D3B0A4 /* PacketTunnelProvider */,
58915D662A25F9F20066445B /* DeviceCheck */,
588395612A9DF497008B63F6 /* WireGuardAdapter */,
+ A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */,
);
path = PacketTunnel;
sourceTree = "<group>";
@@ -3910,6 +3991,17 @@
path = Socks5;
sourceTree = "<group>";
};
+ A944F25D2B8DEFDB00473F4C /* MullvadPostQuantum */ = {
+ isa = PBXGroup;
+ children = (
+ A944F25E2B8DEFDB00473F4C /* MullvadPostQuantum.h */,
+ A9A557F42B7E3E5C0017ADA8 /* PacketTunnelProvider+TCPConnection.swift */,
+ A9EB4F9C2B7FAB21002A2D7A /* PostQuantumKeyNegotiator.swift */,
+ A9630E3B2B8E0E7B00A65999 /* talpid_tunnel_config_client.h */,
+ );
+ path = MullvadPostQuantum;
+ sourceTree = "<group>";
+ };
F028A5472A336E1900C0CAA3 /* RedeemVoucher */ = {
isa = PBXGroup;
children = (
@@ -4194,6 +4286,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 */
@@ -4255,7 +4356,6 @@
buildRules = (
);
dependencies = (
- F04F959F2B21D02700431E08 /* PBXTargetDependency */,
A91614D32B108F4D00F416EB /* PBXTargetDependency */,
);
name = TunnelObfuscation;
@@ -4413,6 +4513,7 @@
7ABCA5B62A9349F20044A708 /* PBXTargetDependency */,
58B2FDD82AA71D2A003EB5C6 /* PBXTargetDependency */,
F0ACE30F2BE4E478006D5333 /* PBXTargetDependency */,
+ A944F2612B8DEFDB00473F4C /* PBXTargetDependency */,
);
name = MullvadVPN;
packageProductDependencies = (
@@ -4430,6 +4531,7 @@
58CE5E75224146470008646E /* Sources */,
58CE5E76224146470008646E /* Frameworks */,
58CE5E77224146470008646E /* Resources */,
+ A9259FD52B8E06E90032C82B /* Embed Frameworks */,
);
buildRules = (
);
@@ -4634,6 +4736,30 @@
productReference = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */;
productType = "com.apple.product-type.framework";
};
+ 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 */
@@ -4721,7 +4847,10 @@
TestTargetID = 58CE5E5F224146200008646E;
};
F0ACE3072BE4E478006D5333 = {
- CreatedOnToolsVersion = 15.3;
+ CreatedOnToolsVersion = 15.2;
+ };
+ A944F25B2B8DEFDB00473F4C = {
+ CreatedOnToolsVersion = 15.2;
};
};
};
@@ -4762,6 +4891,7 @@
7A88DCCD2A8FABBE00D2FF0E /* Routing */,
7A88DCD62A8FABBE00D2FF0E /* RoutingTests */,
F0ACE3072BE4E478006D5333 /* MullvadMockData */,
+ A944F25B2B8DEFDB00473F4C /* MullvadPostQuantum */,
);
};
/* End PBXProject section */
@@ -4900,6 +5030,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ A944F25A2B8DEFDB00473F4C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -4977,6 +5114,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\n";
+ };
F05F39962B21C704006E60A7 /* Prebuild relays */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -5247,6 +5403,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 */,
@@ -5424,6 +5581,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 */,
@@ -5776,6 +5934,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 */,
@@ -5979,6 +6138,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ A944F2582B8DEFDB00473F4C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A9630E492B921E6D00A65999 /* PacketTunnelProvider+TCPConnection.swift in Sources */,
+ A944F26A2B8DF32900473F4C /* PostQuantumKeyNegotiator.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -6179,6 +6347,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 */;
@@ -6804,6 +6982,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_STRICT_CONCURRENCY = minimal;
SWIFT_VERSION = 5.0;
};
name = Debug;
@@ -6824,6 +7003,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_STRICT_CONCURRENCY = minimal;
SWIFT_VERSION = 5.0;
};
name = Release;
@@ -6844,6 +7024,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;
@@ -6863,6 +7044,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_STRICT_CONCURRENCY = minimal;
SWIFT_VERSION = 5.0;
};
name = Release;
@@ -7473,6 +7655,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;
@@ -7511,6 +7694,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;
@@ -8010,6 +8194,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 = {
@@ -8085,6 +8473,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;
@@ -8119,6 +8508,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;
@@ -8998,6 +9388,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 */
@@ -9075,6 +9476,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 0000000000..0d18c5e1d9
--- /dev/null
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadPostQuantum.xcscheme
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "1520"
+ version = "1.7">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "A944F25B2B8DEFDB00473F4C"
+ BuildableName = "MullvadPostQuantum.framework"
+ BlueprintName = "MullvadPostQuantum"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ shouldAutocreateTestPlan = "YES">
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "A944F25B2B8DEFDB00473F4C"
+ BuildableName = "MullvadPostQuantum.framework"
+ BlueprintName = "MullvadPostQuantum"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
index 0c2b8678f8..80352cdda2 100644
--- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
@@ -980,14 +980,10 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
guard tunnelManager.deviceState.isLoggedIn else { return false }
switch tunnelManager.tunnelStatus.state {
- case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error:
+ case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error,
+ .negotiatingPostQuantumKey:
tunnelManager.reconnectTunnel(selectNewRelay: true)
- #if DEBUG
- case .negotiatingKey:
- tunnelManager.reconnectTunnel(selectNewRelay: true)
- #endif
-
case .disconnecting, .disconnected:
tunnelManager.startTunnel()
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
index e7bf690f87..ea5260b8af 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 e2a87e882a..4957af7c52 100644
--- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
@@ -51,21 +51,19 @@ 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)
- #if DEBUG
- case let .negotiatingKey(connectionState):
+ case let .negotiatingPostQuantumKey(connectionState, privateKey):
return connectionState.isNetworkReachable
- ? .negotiatingKey(connectionState.selectedRelay)
+ ? .negotiatingPostQuantumKey(connectionState.selectedRelay, privateKey)
: .waitingForConnectivity(.noConnection)
- #endif
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 9474a0a481..cd9e8b7a88 100644
--- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
@@ -90,7 +90,10 @@ class StartTunnelOperation: ResultOperation<Void> {
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/StopTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
index 4701a4238c..1bd7a4f9c7 100644
--- a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
@@ -35,14 +35,10 @@ class StopTunnelOperation: ResultOperation<Void> {
finish(result: .success(()))
- case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error:
+ case .connected, .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .error,
+ .negotiatingPostQuantumKey:
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 8f3b69112c..897f0074ac 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
+ case .negotiatingPostQuantumKey:
+ // No need to poll the tunnel while negotiating post quantum keys, assume the connection will work
+ break
case .connected, .waitingForConnectivity(.noConnection):
// Start polling tunnel status to keep connectivity status up to date.
diff --git a/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift b/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift
new file mode 100644
index 0000000000..eefb1db415
--- /dev/null
+++ b/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift
@@ -0,0 +1,259 @@
+//
+// 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: ""
+ )
+ }
+
+ 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 08b889899e..76148bdbb8 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 {
@@ -48,15 +49,13 @@ enum TunnelState: Equatable, CustomStringConvertible {
case pendingReconnect
/// Connecting the tunnel.
- case connecting(SelectedRelay?)
+ case connecting(SelectedRelay?, isPostQuantum: Bool)
- #if DEBUG
/// Negotiating a key for post-quantum resistance
- case negotiatingKey(SelectedRelay)
- #endif
+ case negotiatingPostQuantumKey(SelectedRelay, PrivateKey)
/// Connected the tunnel
- case connected(SelectedRelay)
+ case connected(SelectedRelay, isPostQuantum: Bool)
/// Disconnecting the tunnel
case disconnecting(ActionAfterDisconnect)
@@ -69,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)
@@ -81,55 +80,45 @@ 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):
"error state: \(blockedStateReason)"
- #if DEBUG
- case let .negotiatingKey(tunnelRelay):
+ case let .negotiatingPostQuantumKey(tunnelRelay, _):
"negotiating key with \(tunnelRelay.hostname)"
- #endif
}
}
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
- #if DEBUG
- case .negotiatingKey:
- false
- #endif
}
}
var relay: SelectedRelay? {
switch self {
- case let .connected(relay), let .reconnecting(relay):
- relay
- case let .connecting(relay):
+ case let .connected(relay, _), let .reconnecting(relay, _), let .negotiatingPostQuantumKey(relay, _):
relay
- #if DEBUG
- case let .negotiatingKey(relay):
+ case let .connecting(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 8635bc1b9d..92acdb7a55 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,225 +453,6 @@ final class TunnelControlView: UIView {
}
private extension TunnelState {
- var textColorForSecureLabel: UIColor {
- switch self {
- case .connecting, .reconnecting, .waitingForConnectivity(.noConnection):
- .white
-
- #if DEBUG
- case .negotiatingKey:
- .white
- #endif
-
- 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: ""
- )
-
- #if DEBUG
- case .negotiatingKey:
- NSLocalizedString(
- "TUNNEL_STATE_NEGOTIATING_KEY",
- tableName: "Main",
- value: "Negotiating key",
- comment: ""
- )
- #endif
-
- 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: ""
- )
-
- #if DEBUG
- case .negotiatingKey:
- NSLocalizedString(
- "SWITCH_LOCATION_BUTTON_TITLE",
- tableName: "Main",
- value: "Switch location",
- comment: ""
- )
- #endif
- }
- }
-
- var localizedAccessibilityLabel: String {
- switch self {
- case .connecting:
- NSLocalizedString(
- "TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL",
- tableName: "Main",
- value: "Creating secure connection",
- comment: ""
- )
-
- #if DEBUG
- case .negotiatingKey:
- NSLocalizedString(
- "TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL",
- tableName: "Main",
- value: "Creating secure connection",
- comment: ""
- )
- #endif
-
- 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):
@@ -682,10 +464,8 @@ private extension TunnelState {
.waitingForConnectivity(.noConnection):
[.selectLocation, .cancel]
- #if DEBUG
- case .negotiatingKey:
+ case .negotiatingPostQuantumKey:
[.selectLocation, .cancel]
- #endif
case .connected, .reconnecting, .error:
[.selectLocation, .disconnect]
@@ -700,10 +480,8 @@ private extension TunnelState {
.waitingForConnectivity(.noConnection):
[.cancel]
- #if DEBUG
- case .negotiatingKey:
+ case .negotiatingPostQuantumKey:
[.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 7d68b3dd41..36f8535047 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
@@ -147,24 +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):
+ case let .reconnecting(tunnelRelay, _), let .negotiatingPostQuantumKey(tunnelRelay, _):
mapViewController.removeLocationMarker()
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):
+ case let .connected(tunnelRelay, _):
let center = tunnelRelay.location.geoCoordinate
mapViewController.setCenter(center, animated: animated) {
self.contentView.setAnimatingActivity(false)
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
index bd3b42b76a..2934e7d457 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 4525b7d886..2891fc4d5f 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
@@ -548,7 +524,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
}
}
- #if DEBUG
private func configureQuantumResistanceHeader(_ header: SettingsHeaderView) {
let title = NSLocalizedString(
"QUANTUM_RESISTANCE_HEADER_LABEL",
@@ -577,7 +552,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/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStateTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelStateTests.swift
new file mode 100644
index 0000000000..9a707fb30d
--- /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/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index a635348773..ab64040f0a 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
@@ -22,9 +23,12 @@ 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!
+ private var adapter: WgAdapter!
+ private var relaySelector: RelaySelectorWrapper!
override init() {
Self.configureLogging()
@@ -48,7 +52,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
addressCache: addressCache
)
- let adapter = WgAdapter(packetTunnelProvider: self)
+ adapter = WgAdapter(packetTunnelProvider: self)
let tunnelMonitor = TunnelMonitor(
eventQueue: internalQueue,
@@ -65,6 +69,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
let devicesProxy = proxyFactory.createDevicesProxy()
deviceChecker = DeviceChecker(accountsProxy: accountsProxy, devicesProxy: devicesProxy)
+ relaySelector = RelaySelectorWrapper(relayCache: ipOverrideWrapper)
actor = PacketTunnelActor(
timings: PacketTunnelActorTimings(),
@@ -72,11 +77,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
tunnelMonitor: tunnelMonitor,
defaultPathObserver: PacketTunnelPathObserver(packetTunnelProvider: self, eventQueue: internalQueue),
blockedStateErrorMapper: BlockedStateErrorMapper(),
- relaySelector: RelaySelectorWrapper(relayCache: ipOverrideWrapper),
+ relaySelector: relaySelector,
settingsReader: SettingsReader(),
protocolObfuscator: ProtocolObfuscator<UDPOverTCPObfuscator>()
)
+ postQuantumActor = PostQuantumKeyExchangeActor(packetTunnel: self)
+
let urlRequestProxy = URLRequestProxy(dispatchQueue: internalQueue, transportProvider: transportProvider)
appMessageHandler = AppMessageHandler(packetTunnelActor: actor, urlRequestProxy: urlRequestProxy)
@@ -104,6 +111,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
if connectionState.connectionAttemptCount > 1 {
return
}
+ case .negotiatingPostQuantumKey:
+ // 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.
+ return
default:
break
}
@@ -235,10 +248,8 @@ extension PacketTunnelProvider {
// Cache last connection attempt to filter out repeating calls.
lastConnectionAttempt = connectionAttempt
- #if DEBUG
- case .negotiatingKey:
- break
- #endif
+ case let .negotiatingPostQuantumKey(_, privateKey):
+ postQuantumActor.startNegotiation(with: privateKey)
case .initial, .connected, .disconnecting, .disconnected, .error:
break
@@ -247,6 +258,16 @@ extension PacketTunnelProvider {
}
}
+ func createTCPConnectionForPQPSK(_ gatewayAddress: String) -> NWTCPConnection {
+ let gatewayEndpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337")
+ return createTCPConnectionThroughTunnel(
+ to: gatewayEndpoint,
+ enableTLS: false,
+ tlsParameters: nil,
+ delegate: nil
+ )
+ }
+
private func stopObservingActorState() {
stateObserverTask?.cancel()
stateObserverTask = nil
@@ -283,8 +304,13 @@ extension PacketTunnelProvider {
}
extension PacketTunnelProvider: PostQuantumKeyReceiving {
- func receivePostQuantumKey(_ key: PreSharedKey) {
- // TODO: send the key to the actor
- actor.replacePreSharedKey(key)
+ func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
+ actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey)
+ postQuantumActor.acknowledgeNegotiationConcluded()
+ }
+
+ func keyExchangeFailed() {
+ postQuantumActor.acknowledgeNegotiationConcluded()
+ actor.reconnect(to: .current)
}
}
diff --git a/ios/PacketTunnel/PacketTunnelProvider/SettingsReader.swift b/ios/PacketTunnel/PacketTunnelProvider/SettingsReader.swift
index 10282b5ff3..ade3b5a3af 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/PacketTunnel/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift
new file mode 100644
index 0000000000..e9189d947c
--- /dev/null
+++ b/ios/PacketTunnel/PostQuantumKeyExchangeActor.swift
@@ -0,0 +1,78 @@
+//
+// 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
+import WireGuardKitTypes
+
+class PostQuantumKeyExchangeActor {
+ 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 negotiation: Negotiation?
+
+ init(packetTunnel: PacketTunnelProvider) {
+ self.packetTunnel = packetTunnel
+ }
+
+ private func createTCPConnection(_ gatewayEndpoint: NWHostEndpoint) -> NWTCPConnection {
+ self.packetTunnel.createTCPConnectionThroughTunnel(
+ to: gatewayEndpoint,
+ enableTLS: false,
+ tlsParameters: nil,
+ delegate: nil
+ )
+ }
+
+ func startNegotiation(with privateKey: PrivateKey) {
+ let negotiator = PostQuantumKeyNegotiator()
+
+ let gatewayAddress = "10.64.0.1"
+ let IPv4Gateway = IPv4Address(gatewayAddress)!
+ let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "1337")
+ let inTunnelTCPConnection = createTCPConnection(endpoint)
+
+ let ephemeralSharedKey = PrivateKey() // This will become the new private key of the device
+
+ let 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.negotiation?.tcpConnectionObserver.invalidate()
+ }
+ negotiation = Negotiation(
+ negotiator: negotiator,
+ inTunnelTCPConnection: inTunnelTCPConnection,
+ tcpConnectionObserver: tcpConnectionObserver
+ )
+ }
+
+ func acknowledgeNegotiationConcluded() {
+ negotiation?.cancel()
+ negotiation = nil
+ }
+}
diff --git a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
index 06b970a223..79833d16c2 100644
--- a/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
+++ b/ios/PacketTunnelCore/Actor/ConfigurationBuilder.swift
@@ -21,14 +21,32 @@ 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]
+ // or should this go in MullvadEndpoint?
+ var preSharedKey: PreSharedKey?
- func makeConfiguration() throws -> TunnelAdapterConfiguration {
+ public init(
+ privateKey: PrivateKey,
+ interfaceAddresses: [IPAddressRange],
+ dns: SelectedDNSServers? = nil,
+ endpoint: MullvadEndpoint? = nil,
+ 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 {
return TunnelAdapterConfiguration(
privateKey: privateKey,
interfaceAddresses: interfaceAddresses,
@@ -48,7 +66,8 @@ struct ConfigurationBuilder {
return TunnelPeer(
endpoint: .ipv4(endpoint.ipv4Relay),
- publicKey: publicKey
+ publicKey: publicKey,
+ preSharedKey: preSharedKey
)
}
}
diff --git a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift
index 1504f3e47d..08f023a2d8 100644
--- a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift
+++ b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift
@@ -12,13 +12,10 @@ 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 .negotiatingPostQuantumKey(connState, _):
connState.relayConstraints
- #if DEBUG
- case let .negotiatingKey(connState):
- connState.relayConstraints
- #endif
case let .error(blockedState):
blockedState.relayConstraints
@@ -33,10 +30,8 @@ extension ObservedState {
"Connected"
case .connecting:
"Connecting"
- #if DEBUG
- case .negotiatingKey:
- "Negotiating key"
- #endif
+ case .negotiatingPostQuantumKey:
+ "Negotiating Post Quantum Secure Key"
case .reconnecting:
"Reconnecting"
case .disconnecting:
@@ -56,6 +51,7 @@ extension ObservedState {
let .connecting(connectionState),
let .reconnecting(connectionState),
let .connected(connectionState),
+ let .negotiatingPostQuantumKey(connectionState, _),
let .disconnecting(connectionState):
connectionState
default:
diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift
index 3f92b300e5..bdb85a8e51 100644
--- a/ios/PacketTunnelCore/Actor/ObservedState.swift
+++ b/ios/PacketTunnelCore/Actor/ObservedState.swift
@@ -10,15 +10,14 @@ import Combine
import Foundation
import MullvadTypes
import Network
+import WireGuardKitTypes
/// A serializable representation of internal state.
public enum ObservedState: Equatable, Codable {
case initial
case connecting(ObservedConnectionState)
case reconnecting(ObservedConnectionState)
- #if DEBUG
- case negotiatingKey(ObservedConnectionState)
- #endif
+ case negotiatingPostQuantumKey(ObservedConnectionState, PrivateKey)
case connected(ObservedConnectionState)
case disconnecting(ObservedConnectionState)
case disconnected
@@ -34,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
@@ -46,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
@@ -55,6 +56,7 @@ public struct ObservedConnectionState: Equatable, Codable {
self.transportLayer = transportLayer
self.remotePort = remotePort
self.lastKeyRotation = lastKeyRotation
+ self.isPostQuantum = isPostQuantum
}
}
@@ -78,6 +80,8 @@ extension State {
return .reconnecting(connState.observedConnectionState)
case let .disconnecting(connState):
return .disconnecting(connState.observedConnectionState)
+ case let .negotiatingPostQuantumKey(connState, privateKey):
+ return .negotiatingPostQuantumKey(connState.observedConnectionState, privateKey)
case .disconnected:
return .disconnected
case let .error(blockedState):
@@ -96,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+ConnectionMonitoring.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ConnectionMonitoring.swift
index c2bc921b05..84d34d2650 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 b450f44819..fd88ea94e4 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+Public.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift
index 0d80fb6d58..209a6041d5 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 81b3bfaff1..552624d504 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
@@ -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, ephemeralKey):
+ await postQuantumConnect(with: preSharedKey, privateKey: ephemeralKey)
}
}
}
@@ -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,7 +200,9 @@ extension PacketTunnelActor {
private func reconnect(to nextRelay: NextRelay, reason: ReconnectReason) async {
do {
switch state {
- 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
@@ -221,6 +224,44 @@ extension PacketTunnelActor {
}
}
+ private func postQuantumConnect(with key: PreSharedKey, privateKey: PrivateKey) async {
+ guard
+ // It is important to select the same relay that was saved in the connection state as the key negotiation happened with this specific relay.
+ let selectedRelay = state.connectionData?.selectedRelay,
+ let settings: Settings = try? settingsReader.read(),
+ let connectionState = try? obfuscateConnection(
+ nextRelay: .preSelected(selectedRelay),
+ settings: settings,
+ reason: .userInitiated
+ )
+ else { return }
+
+ let configurationBuilder = ConfigurationBuilder(
+ privateKey: privateKey,
+ interfaceAddresses: settings.interfaceAddresses,
+ dns: settings.dnsServers,
+ endpoint: connectionState.connectedEndpoint,
+ allowedIPs: [
+ IPAddressRange(from: "0.0.0.0/0")!,
+ IPAddressRange(from: "::/0")!,
+ ],
+ preSharedKey: key
+ )
+ stopDefaultPathObserver()
+
+ state = .connecting(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: 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)
+ }
+
/**
Attempt to start the tunnel by performing the following steps:
@@ -237,21 +278,36 @@ 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()
+ if settings.quantumResistance.isEnabled {
+ if let connectionState = try makeConnectionState(nextRelay: nextRelay, settings: settings, reason: reason) {
+ let selectedEndpoint = connectionState.selectedRelay.endpoint
+ let activeKey = activeKey(from: connectionState, in: settings)
+
+ let configurationBuilder = ConfigurationBuilder(
+ privateKey: activeKey,
+ interfaceAddresses: settings.interfaceAddresses,
+ dns: settings.dnsServers,
+ endpoint: selectedEndpoint,
+ allowedIPs: [
+ IPAddressRange(from: "10.64.0.1/32")!,
+ ]
+ )
+
+ try await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration())
+ state = .negotiatingPostQuantumKey(connectionState, activeKey)
+ }
+ return
+ }
+
guard let connectionState = try obfuscateConnection(nextRelay: nextRelay, settings: settings, reason: reason),
let targetState = state.targetStateForReconnect else { return }
- let activeKey: PrivateKey
- switch connectionState.keyPolicy {
- case .useCurrent:
- activeKey = settings.privateKey
- case let .usePrior(priorKey, _):
- activeKey = priorKey
- }
+ let activeKey = activeKey(from: connectionState, in: settings)
switch targetState {
case .connecting:
@@ -326,6 +382,8 @@ extension PacketTunnelActor {
connectionState.incrementAttemptCount()
}
fallthrough
+ case let .negotiatingPostQuantumKey(connectionState, _):
+ return connectionState
case var .connected(connectionState):
let selectedRelay = try callRelaySelector(
connectionState.selectedRelay,
@@ -353,10 +411,20 @@ extension PacketTunnelActor {
lastKeyRotation: lastKeyRotation,
connectedEndpoint: selectedRelay.endpoint,
transportLayer: .udp,
- remotePort: selectedRelay.endpoint.ipv4Relay.port
+ remotePort: selectedRelay.endpoint.ipv4Relay.port,
+ isPostQuantum: settings.quantumResistance.isEnabled
)
}
+ private func activeKey(from state: State.ConnectionData, in settings: Settings) -> PrivateKey {
+ switch state.keyPolicy {
+ case .useCurrent:
+ settings.privateKey
+ case let .usePrior(priorKey, _):
+ priorKey
+ }
+ }
+
private func obfuscateConnection(
nextRelay: NextRelay,
settings: Settings,
@@ -382,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/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
index be325fc7b6..c4bd2c314b 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
@@ -37,7 +37,7 @@ extension PacketTunnelActor {
case networkReachability(NetworkPath)
/// 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 {
diff --git a/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift
index 4224233f70..a4408392e3 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)"
+ }
+}
diff --git a/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift
index 9d75149b45..ffe7cdcc2f 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/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift
index 45b28a0c9c..be1f05d52d 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:
@@ -89,6 +91,7 @@ extension State {
let .connecting(connState),
let .connected(connState),
let .reconnecting(connState),
+ let .negotiatingPostQuantumKey(connState, _),
let .disconnecting(connState): connState
default: nil
}
@@ -118,6 +121,7 @@ extension State {
case .connected: .connected(newValue)
case .reconnecting: .reconnecting(newValue)
case .disconnecting: .disconnecting(newValue)
+ case let .negotiatingPostQuantumKey(_, privateKey): .negotiatingPostQuantumKey(newValue, privateKey)
default: self
}
}
@@ -130,6 +134,7 @@ extension State {
case let .connecting(connState),
let .connected(connState),
let .reconnecting(connState),
+ let .negotiatingPostQuantumKey(connState, _),
let .disconnecting(connState):
var associatedData: StateAssociatedData = connState
modifier(&associatedData)
diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift
index ab3fe767c9..f99799201c 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, 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.
case connecting(ConnectionData)
@@ -143,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.
diff --git a/ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift b/ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift
index c95133b091..edb9e99e6d 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 fb37ef6b0d..9fa8b90258 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 dd644a74e1..21f30991bb 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
)
}
}
diff --git a/ios/build-rust-library.sh b/ios/build-rust-library.sh
index 5de58559b0..94b8571a78 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 5f14d88d67..ca3f440935 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 650f2ba566..18eb9342d0 100644
--- a/talpid-tunnel-config-client/Cargo.toml
+++ b/talpid-tunnel-config-client/Cargo.toml
@@ -17,20 +17,28 @@ talpid-types = { path = "../talpid-types" }
tonic = { workspace = true }
tower = { workspace = true }
prost = { workspace = true }
-tokio = { workspace = true, features = ["macros"] }
-classic-mceliece-rust = { version = "2.0.0", features = ["mceliece460896f", "zeroize"] }
+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"
[target.'cfg(windows)'.dependencies.windows-sys]
workspace = true
-features = [
- "Win32_Networking_WinSock"
-]
-
-[dev-dependencies]
-tokio = { workspace = true, features = ["rt-multi-thread"] }
+features = ["Win32_Networking_WinSock"]
[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 }
+
+[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 aeb21fe009..50c9bfd1df 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 0000000000..ce0faf4ddb
--- /dev/null
+++ b/talpid-tunnel-config-client/src/ios_ffi/ios_runtime.rs
@@ -0,0 +1,167 @@
+use super::{ios_tcp_connection::*, PostQuantumCancelToken};
+use crate::{request_ephemeral_peer, Error, RelayConfigService};
+use libc::c_void;
+use std::{future::Future, io, pin::Pin, ptr, sync::Arc};
+use talpid_types::net::wireguard::{PrivateKey, PublicKey};
+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.
+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<PostQuantumCancelToken, i32> {
+ 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<mpsc::UnboundedSender<()>>,
+ 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<Self> {
+ 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<(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 |_| -> Pin<Box<dyn Future<Output = _> + Send>> {
+ if let Some(connection) = one_tcp_connection.take() {
+ return Box::pin(async move { Ok::<_, Error>(connection) });
+ }
+ Box::pin(async { Err(Error::TcpConnectionExpired) })
+ },
+ ))
+ .await
+ .map_err(Error::GrpcConnectError)?;
+
+ Ok((RelayConfigService::new(conn), conn_handle))
+ }
+
+ 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, 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 {
+ 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,
+ ) => {
+ shutdown_handle.shutdown();
+ 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() => {
+ 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
new file mode 100644
index 0000000000..7609511b53
--- /dev/null
+++ b/talpid-tunnel-config-client/src/ios_ffi/ios_tcp_connection.rs
@@ -0,0 +1,184 @@
+use libc::c_void;
+use std::{
+ io::{self, Result},
+ sync::{
+ atomic::{self, AtomicBool},
+ Arc,
+ },
+ 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.
+ /// 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<usize>,
+ write_rx: mpsc::UnboundedReceiver<usize>,
+ read_tx: mpsc::UnboundedSender<Box<[u8]>>,
+ read_rx: mpsc::UnboundedReceiver<Box<[u8]>>,
+ tcp_connection: *const c_void,
+ read_in_progress: bool,
+ write_in_progress: bool,
+ shutdown: Arc<AtomicBool>,
+}
+
+pub struct IosTcpShutdownHandle {
+ shutdown: Arc<AtomicBool>,
+}
+
+impl IosTcpProvider {
+ /**
+ * # Safety
+ * `tcp_connection` must be pointing to a valid instance of a `NWTCPConnection`, created by the `PacketTunnelProvider`
+ */
+ pub unsafe fn new(tcp_connection: *const c_void) -> (Self, IosTcpShutdownHandle) {
+ let (tx, rx) = mpsc::unbounded_channel();
+ let (recv_tx, recv_rx) = mpsc::unbounded_channel();
+ 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);
+ }
+}
+
+impl AsyncWrite for IosTcpProvider {
+ fn poll_write(
+ mut self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ buf: &[u8],
+ ) -> std::task::Poll<Result<usize>> {
+ 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(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;
+ }
+ 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<Result<()>> {
+ std::task::Poll::Ready(Ok(()))
+ }
+
+ fn poll_shutdown(
+ self: std::pin::Pin<&mut Self>,
+ _: &mut std::task::Context<'_>,
+ ) -> std::task::Poll<Result<()>> {
+ 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<std::io::Result<()>> {
+ 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)) => {
+ 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(connection_closed_err()))
+ }
+ std::task::Poll::Pending => {
+ if self.read_in_progress {
+ return std::task::Poll::Pending;
+ }
+ self.read_in_progress = true;
+ unsafe {
+ swift_nw_tcp_connection_read(self.tcp_connection, raw_sender as _);
+ }
+
+ 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 0000000000..9941bf4340
--- /dev/null
+++ b/talpid-tunnel-config-client/src/ios_ffi/mod.rs
@@ -0,0 +1,113 @@
+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<tokio::mpsc::UnboundedSender>
+ 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<mpsc::UnboundedSender<()>> = 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<mpsc::UnboundedSender<()>> = unsafe { Arc::from_raw(self.context as _) };
+ }
+}
+unsafe impl Send for PostQuantumCancelToken {}
+
+#[no_mangle]
+/**
+ * # Safety
+ * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`
+ */
+pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const PostQuantumCancelToken) {
+ let sender = unsafe { &*sender };
+ sender.cancel();
+}
+
+/**
+ * # Safety
+ * `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the `PacketTunnelProvider`.
+ */
+#[no_mangle]
+pub unsafe extern "C" fn drop_post_quantum_key_exchange_token(
+ sender: *const PostQuantumCancelToken,
+) {
+ let _sender = unsafe { std::ptr::read(sender) };
+}
+
+/**
+ * # Safety
+ * `sender` must be pointing to a valid instance of a `write_tx` created by the `IosTcpProvider`
+ *
+ * Callback to call when the TCP connection has written data.
+ */
+#[no_mangle]
+pub unsafe extern "C" fn handle_sent(bytes_sent: usize, sender: *const c_void) {
+ let send_tx: Box<mpsc::UnboundedSender<usize>> = unsafe { Box::from_raw(sender as _) };
+ _ = send_tx.send(bytes_sent);
+}
+
+/**
+ * # Safety
+ * `sender` must be pointing to a valid instance of a `read_tx` created by the `IosTcpProvider`
+ *
+ * Callback to call when the TCP connection has received data.
+ */
+#[no_mangle]
+pub unsafe extern "C" fn handle_recv(data: *const u8, data_len: usize, sender: *const c_void) {
+ let read_tx: Box<mpsc::UnboundedSender<Box<[u8]>>> = 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 e272e794c7..04a81d924b 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)]
@@ -44,6 +59,8 @@ pub enum Error {
actual: usize,
},
FailedDecapsulateKyber(kyber::KyberError),
+ #[cfg(target_os = "ios")]
+ TcpConnectionExpired,
}
impl std::fmt::Display for Error {
@@ -65,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),
}
}
}
@@ -79,7 +98,7 @@ impl std::error::Error for Error {
}
}
-type RelayConfigService = proto::ephemeral_peer_client::EphemeralPeerClient<Channel>;
+pub type RelayConfigService = proto::ephemeral_peer_client::EphemeralPeerClient<Channel>;
/// Port used by the tunnel config service.
pub const CONFIG_SERVICE_PORT: u16 = 1337;
@@ -93,6 +112,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,40 +121,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<Channel>,
) -> Result<EphemeralPeer, Error> {
- 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(),
@@ -192,6 +194,37 @@ pub async fn request_ephemeral_peer(
Ok(EphemeralPeer { psk })
}
+async fn post_quantum_secrets(
+ enable_post_quantum: bool,
+) -> (
+ Option<PostQuantumRequestV1>,
+ 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()) {
@@ -199,6 +232,7 @@ fn xor_assign(dst: &mut [u8; 32], src: &[u8; 32]) {
}
}
+#[cfg(not(target_os = "ios"))]
async fn new_client(addr: IpAddr) -> Result<RelayConfigService, Error> {
let endpoint = Endpoint::from_static("tcp://0.0.0.0:0");
@@ -245,7 +279,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;