summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2024-07-26 14:34:17 +0200
committerBug Magnet <marco.nikic@mullvad.net>2024-07-26 14:34:17 +0200
commit55cfd69fc4bc3292a97e440531fa20e2e09b9e99 (patch)
treee3427ca899157c434e5716b052ce8967f39e3070 /ios
parentf02965c4903f70f2e1e9d0dfc56203ee7b0accc2 (diff)
parent8061e0c0ab5aecba2e80d9b05eed21227982a66d (diff)
downloadmullvadvpn-55cfd69fc4bc3292a97e440531fa20e2e09b9e99.tar.xz
mullvadvpn-55cfd69fc4bc3292a97e440531fa20e2e09b9e99.zip
Merge branch 'negotiate-with-both-peers-when-using-multihop-and-pq-ios-746'
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift12
-rw-r--r--ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift4
-rw-r--r--ios/MullvadRustRuntimeTests/MullvadPostQuantumTests.swift (renamed from ios/MullvadRustRuntimeTests/PostQuantumKeyExchangeActorTests.swift)6
-rw-r--r--ios/MullvadTypes/LocalNetworkIPs.swift15
-rw-r--r--ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift2
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj143
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift33
-rw-r--r--ios/PacketTunnel/PostQuantum/MultiHopPostQuantumKeyExchanging.swift132
-rw-r--r--ios/PacketTunnel/PostQuantum/PostQuantumKeyExchangeActor.swift145
-rw-r--r--ios/PacketTunnel/PostQuantum/PostQuantumKeyExchangingPipeline.swift59
-rw-r--r--ios/PacketTunnel/PostQuantum/SingleHopPostQuantumKeyExchanging.swift65
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift103
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift12
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift11
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift11
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift16
-rw-r--r--ios/PacketTunnelCore/Actor/PostQuantumKey.swift19
-rw-r--r--ios/PacketTunnelCore/Actor/PostQuantumNegotiationState.swift61
-rw-r--r--ios/PacketTunnelCore/Actor/Protocols/PostQuantumKeyExchangingProtocol.swift14
-rw-r--r--ios/PacketTunnelCoreTests/KeyExchangingResultStub.swift24
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift15
-rw-r--r--ios/PacketTunnelCoreTests/MultiHopPostQuantumKeyExchangingTests.swift130
-rw-r--r--ios/PacketTunnelCoreTests/PostQuantumKeyExchangeActorStub.swift38
-rw-r--r--ios/PacketTunnelCoreTests/PostQuantumKeyExchangingPipelineTests.swift138
-rw-r--r--ios/PacketTunnelCoreTests/SingleHopPostQuantumKeyExchangingTests.swift108
-rw-r--r--ios/TestPlans/MullvadVPNApp.xctestplan12
27 files changed, 1228 insertions, 102 deletions
diff --git a/ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift b/ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift
index 8028c1ba3e..a854d83cd8 100644
--- a/ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift
+++ b/ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift
@@ -12,7 +12,13 @@ import MullvadTypes
import NetworkExtension
import WireGuardKitTypes
-public class PostQuantumKeyExchangeActor {
+public protocol PostQuantumKeyExchangeActorProtocol {
+ func startNegotiation(with privateKey: PrivateKey)
+ func endCurrentNegotiation()
+ func reset()
+}
+
+public class PostQuantumKeyExchangeActor: PostQuantumKeyExchangeActorProtocol {
struct Negotiation {
var negotiator: PostQuantumKeyNegotiating
var inTunnelTCPConnection: NWTCPConnection
@@ -69,7 +75,7 @@ public class PostQuantumKeyExchangeActor {
endCurrentNegotiation()
let negotiator = negotiationProvider.init()
- let gatewayAddress = "10.64.0.1"
+ let gatewayAddress = LocalNetworkIPs.gatewayAddress.rawValue
let IPv4Gateway = IPv4Address(gatewayAddress)!
let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "\(CONFIG_SERVICE_PORT)")
let inTunnelTCPConnection = createTCPConnection(endpoint)
@@ -97,6 +103,8 @@ public class PostQuantumKeyExchangeActor {
tcpConnection: inTunnelTCPConnection,
postQuantumKeyExchangeTimeout: tcpConnectionTimeout
) {
+ // Cancel the negotiation to shut down any remaining use of the TCP connection on the Rust side
+ self.negotiation?.cancel()
self.negotiation = nil
self.onFailure()
}
diff --git a/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift b/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift
index 31c7edf6e9..04ada1293a 100644
--- a/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift
+++ b/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift
@@ -58,7 +58,7 @@ class FailedNegotiatorStub: PostQuantumKeyNegotiating {
gatewayIP: IPv4Address,
devicePublicKey: WireGuardKitTypes.PublicKey,
presharedKey: WireGuardKitTypes.PrivateKey,
- packetTunnel: PacketTunnelCore.TunnelProvider,
+ postQuantumKeyReceiver packetTunnel: any MullvadTypes.TunnelProvider,
tcpConnection: NWTCPConnection,
postQuantumKeyExchangeTimeout: MullvadTypes.Duration
) -> Bool { false }
@@ -82,7 +82,7 @@ class SuccessfulNegotiatorStub: PostQuantumKeyNegotiating {
gatewayIP: IPv4Address,
devicePublicKey: WireGuardKitTypes.PublicKey,
presharedKey: WireGuardKitTypes.PrivateKey,
- packetTunnel: PacketTunnelCore.TunnelProvider,
+ postQuantumKeyReceiver packetTunnel: any MullvadTypes.TunnelProvider,
tcpConnection: NWTCPConnection,
postQuantumKeyExchangeTimeout: MullvadTypes.Duration
) -> Bool { true }
diff --git a/ios/MullvadRustRuntimeTests/PostQuantumKeyExchangeActorTests.swift b/ios/MullvadRustRuntimeTests/MullvadPostQuantumTests.swift
index 201ff51633..1d9fe2f2f6 100644
--- a/ios/MullvadRustRuntimeTests/PostQuantumKeyExchangeActorTests.swift
+++ b/ios/MullvadRustRuntimeTests/MullvadPostQuantumTests.swift
@@ -1,6 +1,6 @@
//
-// PostQuantumKeyExchangeActorTests.swift
-// PostQuantumKeyExchangeActorTests
+// MullvadPostQuantumTests.swift
+// MullvadPostQuantumTests
//
// Created by Marco Nikic on 2024-06-12.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
@@ -71,7 +71,7 @@ class MullvadPostQuantumTests: XCTestCase {
unexpectedNegotiationFailure.fulfill()
},
negotiationProvider: SuccessfulNegotiatorStub.self,
- iteratorProvider: { AnyIterator { .milliseconds(10) } }
+ iteratorProvider: { AnyIterator { .seconds(1) } }
)
let privateKey = PrivateKey()
diff --git a/ios/MullvadTypes/LocalNetworkIPs.swift b/ios/MullvadTypes/LocalNetworkIPs.swift
new file mode 100644
index 0000000000..d831623d0a
--- /dev/null
+++ b/ios/MullvadTypes/LocalNetworkIPs.swift
@@ -0,0 +1,15 @@
+//
+// LocalNetworkIPs.swift
+// MullvadTypes
+//
+// Created by Mojgan on 2024-07-26.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+public enum LocalNetworkIPs: String {
+ case gatewayAddress = "10.64.0.1"
+ case defaultRouteIpV4 = "0.0.0.0"
+ case defaultRouteIpV6 = "::"
+}
diff --git a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift
index a7246004e4..50809c50b1 100644
--- a/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift
+++ b/ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift
@@ -9,7 +9,7 @@
import Foundation
import WireGuardKitTypes
-public protocol PostQuantumKeyReceiving: AnyObject {
+public protocol PostQuantumKeyReceiving {
func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey)
func keyExchangeFailed()
}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index fd66cab9a7..a37cd1ae97 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -831,7 +831,6 @@
A9D9A4CE2C36D54E004088DD /* TunnelObfuscationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58695A9F2A4ADA9200328DB3 /* TunnelObfuscationTests.swift */; };
A9D9A4CF2C36D54E004088DD /* TCPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585A02EC2A4B28F300C6CAFF /* TCPConnection.swift */; };
A9D9A4D02C36DAFD004088DD /* PostQuantumKeyExchangeActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */; };
- A9D9A4D12C36DB98004088DD /* PostQuantumKeyExchangeActorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98F1B502C19C48D003C869E /* PostQuantumKeyExchangeActorTests.swift */; };
A9D9A4D22C36DBAF004088DD /* MullvadPostQuantum+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C308392C19DDA7008715F1 /* MullvadPostQuantum+Stubs.swift */; };
A9D9A4D42C36E1EA004088DD /* mullvad_rust_runtime.h in Headers */ = {isa = PBXBuildFile; fileRef = A9D9A4D32C36E1EA004088DD /* mullvad_rust_runtime.h */; settings = {ATTRIBUTES = (Private, ); }; };
A9DF789B2B7D1DF10094E4AD /* mullvad-api.h in Headers */ = {isa = PBXBuildFile; fileRef = 01EF6F2D2B6A51B100125696 /* mullvad-api.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -846,6 +845,7 @@
E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; };
E1FD0DF528AA7CE400299DB4 /* StatusActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */; };
F006CCFC2B99CC8400C6C2AC /* EditLocationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F006CCFB2B99CC8400C6C2AC /* EditLocationsCoordinator.swift */; };
+ F0077EEE2C52844800DAB2AA /* KeyExchangingResultStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0FBD98E2C4A60CC00EE5323 /* KeyExchangingResultStub.swift */; };
F01528BB2BFF3FEE00B01D00 /* ShadowsocksRelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01528BA2BFF3FEE00B01D00 /* ShadowsocksRelaySelector.swift */; };
F0164EBA2B4456D30020268D /* AccessMethodRepositoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EB92B4456D30020268D /* AccessMethodRepositoryStub.swift */; };
F0164EBC2B482E430020268D /* AppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EBB2B482E430020268D /* AppStorage.swift */; };
@@ -863,6 +863,7 @@
F03A69F92C2AD414000E2E7E /* FormsheetPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03A69F82C2AD413000E2E7E /* FormsheetPresentationController.swift */; };
F04413612BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */; };
F04413622BA45CE30018A6EE /* CustomListLocationNodeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */; };
+ F04AF92D2C466013004A8314 /* PostQuantumNegotiationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04AF92C2C466013004A8314 /* PostQuantumNegotiationState.swift */; };
F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04FBE602A8379EE009278D7 /* AppPreferences.swift */; };
F050AE4E2B70D7F8003F4EDB /* LocationCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE4D2B70D7F8003F4EDB /* LocationCellViewModel.swift */; };
F050AE522B70DFC0003F4EDB /* LocationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE512B70DFC0003F4EDB /* LocationSection.swift */; };
@@ -873,6 +874,11 @@
F050AE5E2B739A73003F4EDB /* LocationDataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE5D2B739A73003F4EDB /* LocationDataSourceProtocol.swift */; };
F050AE602B73A41E003F4EDB /* AllLocationDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE5F2B73A41E003F4EDB /* AllLocationDataSource.swift */; };
F050AE622B74DBAC003F4EDB /* CustomListsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F050AE612B74DBAC003F4EDB /* CustomListsDataSource.swift */; };
+ F0570CD12C4FB8E1007BDF2D /* PostQuantumKeyExchangingPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05919762C453FAF00C301F3 /* PostQuantumKeyExchangingPipeline.swift */; };
+ F0570CD22C4FB8E1007BDF2D /* SingleHopPostQuantumKeyExchanging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05919782C45402E00C301F3 /* SingleHopPostQuantumKeyExchanging.swift */; };
+ F0570CD42C4FB8E1007BDF2D /* MultiHopPostQuantumKeyExchanging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F059197C2C454C9200C301F3 /* MultiHopPostQuantumKeyExchanging.swift */; };
+ F05919752C45194B00C301F3 /* PostQuantumKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05919742C45194B00C301F3 /* PostQuantumKey.swift */; };
+ F05919802C45515200C301F3 /* PostQuantumKeyExchangeActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A948809A2BC9308D0090A44C /* PostQuantumKeyExchangeActor.swift */; };
F05F39942B21C6C6006E60A7 /* relays.json in Resources */ = {isa = PBXBuildFile; fileRef = 58F3C0A524A50155003E76BE /* relays.json */; };
F05F39972B21C735006E60A7 /* RelayCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820675A26E6576800655B05 /* RelayCache.swift */; };
F05F39982B21C73C006E60A7 /* CachedRelays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA87626B024A600B8C587 /* CachedRelays.swift */; };
@@ -882,12 +888,23 @@
F062B94D2C16E09700B6D47A /* TunnelSettingsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */; };
F072D3CF2C07122400906F64 /* MultihopUpdaterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3CE2C07122400906F64 /* MultihopUpdaterTests.swift */; };
F072D3D22C071AD100906F64 /* ShadowsocksLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */; };
+ F07751552C50F149006E6A12 /* PostQuantumKeyExchangeActorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C4C9BF2C495E7500A79006 /* PostQuantumKeyExchangeActorStub.swift */; };
+ F07751572C50F149006E6A12 /* PostQuantumKeyExchangingPipelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F053F4B92C4A94D300FBD937 /* PostQuantumKeyExchangingPipelineTests.swift */; };
+ F07751582C50F149006E6A12 /* MultiHopPostQuantumKeyExchangingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C4C9BD2C49477B00A79006 /* MultiHopPostQuantumKeyExchangingTests.swift */; };
+ F07751592C50F149006E6A12 /* SingleHopPostQuantumKeyExchangingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A163882C47B46300592300 /* SingleHopPostQuantumKeyExchangingTests.swift */; };
+ F07B53572C53B5270024F547 /* LocalNetworkIPs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07B53562C53B5270024F547 /* LocalNetworkIPs.swift */; };
F07BF2622A26279100042943 /* RedeemVoucherOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */; };
F07C9D952B220C77006F1C5E /* libmullvad_ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 01F1FF1D29F0627D007083C3 /* libmullvad_ios.a */; };
F07CFF2029F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */; };
F08827872B318C840020A383 /* ShadowsocksCipherOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DFF7D92B02862E00F864E0 /* ShadowsocksCipherOptions.swift */; };
F08827882B318F960020A383 /* PersistentAccessMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C0D962B04E0AC00E7CDD7 /* PersistentAccessMethod.swift */; };
F08827892B3192110020A383 /* AccessMethodRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EF875A2B16385400C098B2 /* AccessMethodRepositoryProtocol.swift */; };
+ F08B6B772C52878400D0A121 /* MullvadPostQuantumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98F1B502C19C48D003C869E /* MullvadPostQuantumTests.swift */; };
+ F08B6B782C528B8A00D0A121 /* PostQuantumKeyExchangingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F059197E2C454CE000C301F3 /* PostQuantumKeyExchangingProtocol.swift */; };
+ F08B6B7C2C528C6300D0A121 /* SingleHopPostQuantumKeyExchanging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05919782C45402E00C301F3 /* SingleHopPostQuantumKeyExchanging.swift */; };
+ F08B6B7D2C528C6300D0A121 /* PostQuantumKeyExchangingPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05919762C453FAF00C301F3 /* PostQuantumKeyExchangingPipeline.swift */; };
+ F08B6B7E2C528C6300D0A121 /* MultiHopPostQuantumKeyExchanging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F059197C2C454C9200C301F3 /* MultiHopPostQuantumKeyExchanging.swift */; };
+ F08B6B822C52931600D0A121 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = F08B6B812C52931600D0A121 /* WireGuardKitTypes */; };
F09A297B2A9F8A9B00EA3B6F /* LogoutDialogueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A29782A9F8A9B00EA3B6F /* LogoutDialogueView.swift */; };
F09A297C2A9F8A9B00EA3B6F /* VoucherTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A29792A9F8A9B00EA3B6F /* VoucherTextField.swift */; };
F09A297D2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09A297A2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift */; };
@@ -903,6 +920,7 @@
F0A0868E2C22D60100BF83E7 /* Tunnel+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A0868D2C22D60100BF83E7 /* Tunnel+Settings.swift */; };
F0A086902C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */; };
F0A086922C22E0F100BF83E7 /* Tunnel+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A0868D2C22D60100BF83E7 /* Tunnel+Settings.swift */; };
+ F0A1638A2C47B77300592300 /* ServerRelaysResponse+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ACE3342BE51745006D5333 /* ServerRelaysResponse+Stubs.swift */; };
F0ACE30D2BE4E478006D5333 /* MullvadMockData.h in Headers */ = {isa = PBXBuildFile; fileRef = F0ACE30A2BE4E478006D5333 /* MullvadMockData.h */; settings = {ATTRIBUTES = (Public, ); }; };
F0ACE3102BE4E478006D5333 /* MullvadMockData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */; };
F0ACE3112BE4E478006D5333 /* MullvadMockData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -1252,6 +1270,13 @@
remoteGlobalIDString = A992DA1C2C24709F00DE7CE5;
remoteInfo = MullvadRustRuntime;
};
+ F08B6B7F2C52927600D0A121 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 58C7A4352A863F440060C66F;
+ remoteInfo = PacketTunnelCore;
+ };
F0ACE30E2BE4E478006D5333 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 58CE5E58224146200008646E /* Project object */;
@@ -2065,7 +2090,7 @@
A98502022B627B120061901E /* LocalNetworkProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkProbe.swift; sourceTree = "<group>"; };
A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardObfuscationSettings.swift; sourceTree = "<group>"; };
A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV3.swift; sourceTree = "<group>"; };
- A98F1B502C19C48D003C869E /* PostQuantumKeyExchangeActorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangeActorTests.swift; sourceTree = "<group>"; };
+ A98F1B502C19C48D003C869E /* MullvadPostQuantumTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadPostQuantumTests.swift; sourceTree = "<group>"; };
A992DA1D2C24709F00DE7CE5 /* MullvadRustRuntime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadRustRuntime.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A992DA1F2C24709F00DE7CE5 /* MullvadRustRuntime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadRustRuntime.h; sourceTree = "<group>"; };
A998DA802BD147AD001D61A2 /* ListCustomListsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCustomListsPage.swift; sourceTree = "<group>"; };
@@ -2116,6 +2141,7 @@
F03A69F62C2AD2D5000E2E7E /* TimeInterval+Timeout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Timeout.swift"; sourceTree = "<group>"; };
F03A69F82C2AD413000E2E7E /* FormsheetPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormsheetPresentationController.swift; sourceTree = "<group>"; };
F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomListLocationNodeBuilder.swift; sourceTree = "<group>"; };
+ F04AF92C2C466013004A8314 /* PostQuantumNegotiationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumNegotiationState.swift; sourceTree = "<group>"; };
F04DD3D72C130DF600E03E28 /* TunnelSettingsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManager.swift; sourceTree = "<group>"; };
F04FBE602A8379EE009278D7 /* AppPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreferences.swift; sourceTree = "<group>"; };
F050AE4D2B70D7F8003F4EDB /* LocationCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCellViewModel.swift; sourceTree = "<group>"; };
@@ -2127,12 +2153,19 @@
F050AE5D2B739A73003F4EDB /* LocationDataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataSourceProtocol.swift; sourceTree = "<group>"; };
F050AE5F2B73A41E003F4EDB /* AllLocationDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllLocationDataSource.swift; sourceTree = "<group>"; };
F050AE612B74DBAC003F4EDB /* CustomListsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsDataSource.swift; sourceTree = "<group>"; };
+ F053F4B92C4A94D300FBD937 /* PostQuantumKeyExchangingPipelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangingPipelineTests.swift; sourceTree = "<group>"; };
+ F05919742C45194B00C301F3 /* PostQuantumKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKey.swift; sourceTree = "<group>"; };
+ F05919762C453FAF00C301F3 /* PostQuantumKeyExchangingPipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangingPipeline.swift; sourceTree = "<group>"; };
+ F05919782C45402E00C301F3 /* SingleHopPostQuantumKeyExchanging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleHopPostQuantumKeyExchanging.swift; sourceTree = "<group>"; };
+ F059197C2C454C9200C301F3 /* MultiHopPostQuantumKeyExchanging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiHopPostQuantumKeyExchanging.swift; sourceTree = "<group>"; };
+ F059197E2C454CE000C301F3 /* PostQuantumKeyExchangingProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangingProtocol.swift; sourceTree = "<group>"; };
F06045E52B231EB700B2D37A /* URLSessionTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTransport.swift; sourceTree = "<group>"; };
F06045E92B23217E00B2D37A /* ShadowsocksTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksTransport.swift; sourceTree = "<group>"; };
F06045EB2B2322A500B2D37A /* Jittered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Jittered.swift; sourceTree = "<group>"; };
F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManagerTests.swift; sourceTree = "<group>"; };
F072D3CE2C07122400906F64 /* MultihopUpdaterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopUpdaterTests.swift; sourceTree = "<group>"; };
F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoaderTests.swift; sourceTree = "<group>"; };
+ F07B53562C53B5270024F547 /* LocalNetworkIPs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkIPs.swift; sourceTree = "<group>"; };
F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextFormatterTests.swift; sourceTree = "<group>"; };
F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherOperation.swift; sourceTree = "<group>"; };
F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisteredDeviceInAppNotificationProvider.swift; sourceTree = "<group>"; };
@@ -2148,6 +2181,7 @@
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionServiceTests.swift; sourceTree = "<group>"; };
F0A0868D2C22D60100BF83E7 /* Tunnel+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tunnel+Settings.swift"; sourceTree = "<group>"; };
F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsStrategyTests.swift; sourceTree = "<group>"; };
+ F0A163882C47B46300592300 /* SingleHopPostQuantumKeyExchangingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleHopPostQuantumKeyExchangingTests.swift; sourceTree = "<group>"; };
F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadMockData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F0ACE30A2BE4E478006D5333 /* MullvadMockData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadMockData.h; sourceTree = "<group>"; };
F0ACE32E2BE4EA8B006D5333 /* MockProxyFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProxyFactory.swift; sourceTree = "<group>"; };
@@ -2159,6 +2193,8 @@
F0B894F42BF7528700817A42 /* RelaySelector+Shadowsocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelaySelector+Shadowsocks.swift"; sourceTree = "<group>"; };
F0BE65362B9F136A005CC385 /* LocationSectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationSectionHeaderView.swift; sourceTree = "<group>"; };
F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationProviderIdentifier.swift; sourceTree = "<group>"; };
+ F0C4C9BD2C49477B00A79006 /* MultiHopPostQuantumKeyExchangingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiHopPostQuantumKeyExchangingTests.swift; sourceTree = "<group>"; };
+ F0C4C9BF2C495E7500A79006 /* PostQuantumKeyExchangeActorStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyExchangeActorStub.swift; sourceTree = "<group>"; };
F0C6A8422AB08E54000777A8 /* RedeemVoucherViewConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedeemVoucherViewConfiguration.swift; sourceTree = "<group>"; };
F0C6FA842A6A733700F521F0 /* InAppPurchaseInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseInteractor.swift; sourceTree = "<group>"; };
F0D8825A2B04F53600D3EF9A /* OutgoingConnectionData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionData.swift; sourceTree = "<group>"; };
@@ -2189,6 +2225,7 @@
F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchArguments.swift; sourceTree = "<group>"; };
F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorResult.swift; sourceTree = "<group>"; };
F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoRelaysSatisfyingConstraintsError.swift; sourceTree = "<group>"; };
+ F0FBD98E2C4A60CC00EE5323 /* KeyExchangingResultStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyExchangingResultStub.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -2354,6 +2391,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ F08B6B822C52931600D0A121 /* WireGuardKitTypes in Frameworks */,
A9D9A4C42C36D53C004088DD /* MullvadRustRuntime.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2635,6 +2673,7 @@
58561C98239A5D1500BD6B5E /* IPv4Endpoint.swift */,
586A95112901321B007BAF2B /* IPv6Endpoint.swift */,
58AEEF642344A36000C9BBD5 /* KeychainError.swift */,
+ F07B53562C53B5270024F547 /* LocalNetworkIPs.swift */,
58A1AA8623F43901009F7EA6 /* Location.swift */,
5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */,
58D223D7294C8E5E0029F5F8 /* MullvadTypes.h */,
@@ -3119,6 +3158,8 @@
583832282AC3DF1300EA2071 /* PacketTunnelActorCommand.swift */,
7AD0AA192AD69B6E00119E10 /* PacketTunnelActorProtocol.swift */,
44B3C4392BFE2C800079782C /* PacketTunnelActorReducer.swift */,
+ F05919742C45194B00C301F3 /* PostQuantumKey.swift */,
+ F04AF92C2C466013004A8314 /* PostQuantumNegotiationState.swift */,
A97D25AD2B0BB18100946B2D /* ProtocolObfuscator.swift */,
58E7A0312AA0715100C57861 /* Protocols */,
58ED3A132A7C199C0085CE65 /* StartOptions.swift */,
@@ -3379,10 +3420,15 @@
children = (
7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */,
586C14572AC463BB00245C01 /* EventChannelTests.swift */,
+ F0FBD98E2C4A60CC00EE5323 /* KeyExchangingResultStub.swift */,
58EC067D2A8D2B0700BEB973 /* Mocks */,
+ F0C4C9BD2C49477B00A79006 /* MultiHopPostQuantumKeyExchangingTests.swift */,
58FE25D32AA729B5003D1918 /* PacketTunnelActorTests.swift */,
58C7A46F2A8649ED0060C66F /* PingerTests.swift */,
+ F0C4C9BF2C495E7500A79006 /* PostQuantumKeyExchangeActorStub.swift */,
+ F053F4B92C4A94D300FBD937 /* PostQuantumKeyExchangingPipelineTests.swift */,
A97D25B12B0CB02D00946B2D /* ProtocolObfuscatorTests.swift */,
+ F0A163882C47B46300592300 /* SingleHopPostQuantumKeyExchangingTests.swift */,
5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */,
58092E532A8B832E00C3CC72 /* TunnelMonitorTests.swift */,
F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */,
@@ -3532,10 +3578,11 @@
58CE5E7A224146470008646E /* PacketTunnel */ = {
isa = PBXGroup;
children = (
+ 58915D662A25F9F20066445B /* DeviceCheck */,
58CE5E7D224146470008646E /* Info.plist */,
58CE5E7E224146470008646E /* PacketTunnel.entitlements */,
58F3F3682AA08E2200D3B0A4 /* PacketTunnelProvider */,
- 58915D662A25F9F20066445B /* DeviceCheck */,
+ F059197A2C45404500C301F3 /* PostQuantum */,
588395612A9DF497008B63F6 /* WireGuardAdapter */,
);
path = PacketTunnel;
@@ -3657,6 +3704,7 @@
isa = PBXGroup;
children = (
580D6B8B2AB3369300B2D6E0 /* BlockedStateErrorMapperProtocol.swift */,
+ F059197E2C454CE000C301F3 /* PostQuantumKeyExchangingProtocol.swift */,
586E7A2C2A987689006DAB1B /* SettingsReaderProtocol.swift */,
5819ABC22A8CF02C007B59A6 /* TunnelAdapterProtocol.swift */,
);
@@ -3666,19 +3714,19 @@
58EC067D2A8D2B0700BEB973 /* Mocks */ = {
isa = PBXGroup;
children = (
+ 58F7753C2AB8473200425B47 /* BlockedStateErrorMapperStub.swift */,
581F23AC2A8CF92100788AB6 /* DefaultPathObserverFake.swift */,
- 581F23AE2A8CF94D00788AB6 /* PingerMock.swift */,
- 58EC06792A8D208D00BEB973 /* TunnelDeviceInfoStub.swift */,
58EC067B2A8D2A0B00BEB973 /* NetworkCounters.swift */,
- 58FE25EB2AA77638003D1918 /* TunnelMonitorStub.swift */,
- 58FE25ED2AA7764E003D1918 /* TunnelAdapterDummy.swift */,
- 58FE25F12AA77674003D1918 /* SettingsReaderStub.swift */,
- 58F7753C2AB8473200425B47 /* BlockedStateErrorMapperStub.swift */,
5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */,
7AD0AA1B2AD6A63F00119E10 /* PacketTunnelActorStub.swift */,
- 7AD0AA202AD6CB0000119E10 /* URLRequestProxyStub.swift */,
+ 581F23AE2A8CF94D00788AB6 /* PingerMock.swift */,
A97D25AF2B0BB5C400946B2D /* ProtocolObfuscationStub.swift */,
+ 58FE25F12AA77674003D1918 /* SettingsReaderStub.swift */,
+ 58FE25ED2AA7764E003D1918 /* TunnelAdapterDummy.swift */,
+ 58EC06792A8D208D00BEB973 /* TunnelDeviceInfoStub.swift */,
+ 58FE25EB2AA77638003D1918 /* TunnelMonitorStub.swift */,
A97D25B32B0CB59300946B2D /* TunnelObfuscationStub.swift */,
+ 7AD0AA202AD6CB0000119E10 /* URLRequestProxyStub.swift */,
);
path = Mocks;
sourceTree = "<group>";
@@ -4024,7 +4072,7 @@
isa = PBXGroup;
children = (
A9C308392C19DDA7008715F1 /* MullvadPostQuantum+Stubs.swift */,
- A98F1B502C19C48D003C869E /* PostQuantumKeyExchangeActorTests.swift */,
+ A98F1B502C19C48D003C869E /* MullvadPostQuantumTests.swift */,
585A02EC2A4B28F300C6CAFF /* TCPConnection.swift */,
585A02E82A4B283000C6CAFF /* TCPUnsafeListener.swift */,
58695A9F2A4ADA9200328DB3 /* TunnelObfuscationTests.swift */,
@@ -4047,6 +4095,16 @@
path = RedeemVoucher;
sourceTree = "<group>";
};
+ F059197A2C45404500C301F3 /* PostQuantum */ = {
+ isa = PBXGroup;
+ children = (
+ F059197C2C454C9200C301F3 /* MultiHopPostQuantumKeyExchanging.swift */,
+ F05919762C453FAF00C301F3 /* PostQuantumKeyExchangingPipeline.swift */,
+ F05919782C45402E00C301F3 /* SingleHopPostQuantumKeyExchanging.swift */,
+ );
+ path = PostQuantum;
+ sourceTree = "<group>";
+ };
F06045F02B2324DA00B2D37A /* ApiHandlers */ = {
isa = PBXGroup;
children = (
@@ -4733,9 +4791,13 @@
buildRules = (
);
dependencies = (
+ F08B6B802C52927600D0A121 /* PBXTargetDependency */,
A9D9A4C62C36D53C004088DD /* PBXTargetDependency */,
);
name = MullvadRustRuntimeTests;
+ packageProductDependencies = (
+ F08B6B812C52931600D0A121 /* WireGuardKitTypes */,
+ );
productName = MullvadRustRuntimeTests;
productReference = A9D9A4C02C36D53C004088DD /* MullvadRustRuntimeTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
@@ -5426,11 +5488,13 @@
58C7AF122ABD8480007EDD7A /* TunnelProviderReply.swift in Sources */,
58C7A4572A863FB90060C66F /* TunnelDeviceInfoProtocol.swift in Sources */,
58C7A4562A863FB90060C66F /* DefaultPathObserverProtocol.swift in Sources */,
+ F08B6B782C528B8A00D0A121 /* PostQuantumKeyExchangingProtocol.swift in Sources */,
58FE25DA2AA72A8F003D1918 /* PacketTunnelActor.swift in Sources */,
587A5E522ADD7569003A70F1 /* ObservedState+Extensions.swift in Sources */,
58FE25E62AA738E8003D1918 /* TunnelAdapterProtocol.swift in Sources */,
44DF8AC42BF20BD200869CA4 /* PacketTunnelActor+PostQuantum.swift in Sources */,
583832252AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift in Sources */,
+ F05919752C45194B00C301F3 /* PostQuantumKey.swift in Sources */,
58C7A4552A863FB90060C66F /* TunnelMonitor.swift in Sources */,
58C7AF182ABD84AB007EDD7A /* ProxyURLResponse.swift in Sources */,
58C7A4512A863FB50060C66F /* PingerProtocol.swift in Sources */,
@@ -5462,6 +5526,7 @@
A95EEE382B722DFC00A8A39B /* PingStats.swift in Sources */,
583832272AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift in Sources */,
58FE25DC2AA72A8F003D1918 /* AnyTask.swift in Sources */,
+ F04AF92D2C466013004A8314 /* PostQuantumNegotiationState.swift in Sources */,
58FE25D92AA72A8F003D1918 /* AutoCancellingTask.swift in Sources */,
58FE25E12AA72A9B003D1918 /* SettingsReaderProtocol.swift in Sources */,
58C7A4582A863FB90060C66F /* TunnelMonitorProtocol.swift in Sources */,
@@ -5476,22 +5541,30 @@
58EC067A2A8D208D00BEB973 /* TunnelDeviceInfoStub.swift in Sources */,
586C14582AC463BB00245C01 /* EventChannelTests.swift in Sources */,
58EC067C2A8D2A0B00BEB973 /* NetworkCounters.swift in Sources */,
+ F07751552C50F149006E6A12 /* PostQuantumKeyExchangeActorStub.swift in Sources */,
58FE25EC2AA77639003D1918 /* TunnelMonitorStub.swift in Sources */,
7A3FD1B82AD54AE60042BEA6 /* TimeServerProxy.swift in Sources */,
58FE25EE2AA7764E003D1918 /* TunnelAdapterDummy.swift in Sources */,
581F23AD2A8CF92100788AB6 /* DefaultPathObserverFake.swift in Sources */,
+ F07751582C50F149006E6A12 /* MultiHopPostQuantumKeyExchangingTests.swift in Sources */,
5838321B2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift in Sources */,
5838321D2AC1C54600EA2071 /* TaskSleepTests.swift in Sources */,
58092E542A8B832E00C3CC72 /* TunnelMonitorTests.swift in Sources */,
7AD0AA212AD6CB0000119E10 /* URLRequestProxyStub.swift in Sources */,
581F23AF2A8CF94D00788AB6 /* PingerMock.swift in Sources */,
+ F07751592C50F149006E6A12 /* SingleHopPostQuantumKeyExchangingTests.swift in Sources */,
A97D25B42B0CB59300946B2D /* TunnelObfuscationStub.swift in Sources */,
+ F0077EEE2C52844800DAB2AA /* KeyExchangingResultStub.swift in Sources */,
A97D25B02B0BB5C400946B2D /* ProtocolObfuscationStub.swift in Sources */,
7A3FD1B72AD54ABD0042BEA6 /* AnyTransport.swift in Sources */,
58FE25F22AA77674003D1918 /* SettingsReaderStub.swift in Sources */,
+ F08B6B7C2C528C6300D0A121 /* SingleHopPostQuantumKeyExchanging.swift in Sources */,
+ F08B6B7D2C528C6300D0A121 /* PostQuantumKeyExchangingPipeline.swift in Sources */,
+ F08B6B7E2C528C6300D0A121 /* MultiHopPostQuantumKeyExchanging.swift in Sources */,
F0ACE3372BE517F1006D5333 /* ServerRelaysResponse+Stubs.swift in Sources */,
58F7753D2AB8473200425B47 /* BlockedStateErrorMapperStub.swift in Sources */,
58FE25D42AA729B5003D1918 /* PacketTunnelActorTests.swift in Sources */,
+ F07751572C50F149006E6A12 /* PostQuantumKeyExchangingPipelineTests.swift in Sources */,
7A3FD1B52AD4465A0042BEA6 /* AppMessageHandlerTests.swift in Sources */,
58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */,
A97D25B22B0CB02D00946B2D /* ProtocolObfuscatorTests.swift in Sources */,
@@ -5883,6 +5956,9 @@
583D86482A2678DC0060D63B /* DeviceStateAccessor.swift in Sources */,
58F3F36A2AA08E3C00D3B0A4 /* PacketTunnelProvider.swift in Sources */,
58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */,
+ F0570CD12C4FB8E1007BDF2D /* PostQuantumKeyExchangingPipeline.swift in Sources */,
+ F0570CD22C4FB8E1007BDF2D /* SingleHopPostQuantumKeyExchanging.swift in Sources */,
+ F0570CD42C4FB8E1007BDF2D /* MultiHopPostQuantumKeyExchanging.swift in Sources */,
58C7A45B2A8640030060C66F /* PacketTunnelPathObserver.swift in Sources */,
580D6B8E2AB33BBF00B2D6E0 /* BlockedStateErrorMapper.swift in Sources */,
06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */,
@@ -5924,6 +6000,7 @@
58D22406294C90210029F5F8 /* IPv4Endpoint.swift in Sources */,
7A307ADB2A8F56DF0017618B /* Duration+Extensions.swift in Sources */,
58D22407294C90210029F5F8 /* IPv6Endpoint.swift in Sources */,
+ F07B53572C53B5270024F547 /* LocalNetworkIPs.swift in Sources */,
58CAFA032985367600BE19F7 /* Promise.swift in Sources */,
A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */,
58D22408294C90210029F5F8 /* AnyIPEndpoint.swift in Sources */,
@@ -6079,6 +6156,7 @@
A9D9A4D02C36DAFD004088DD /* PostQuantumKeyExchangeActor.swift in Sources */,
A9D9A4B22C36D12D004088DD /* UDPOverTCPObfuscator.swift in Sources */,
A9173C322C36CCDD00F6A08C /* PacketTunnelProvider+TCPConnection.swift in Sources */,
+ F05919802C45515200C301F3 /* PostQuantumKeyExchangeActor.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -6087,11 +6165,12 @@
buildActionMask = 2147483647;
files = (
A9D9A4D22C36DBAF004088DD /* MullvadPostQuantum+Stubs.swift in Sources */,
+ F08B6B772C52878400D0A121 /* MullvadPostQuantumTests.swift in Sources */,
A9D9A4CF2C36D54E004088DD /* TCPConnection.swift in Sources */,
A9D9A4CE2C36D54E004088DD /* TunnelObfuscationTests.swift in Sources */,
A9D9A4CC2C36D54E004088DD /* TCPUnsafeListener.swift in Sources */,
- A9D9A4D12C36DB98004088DD /* PostQuantumKeyExchangeActorTests.swift in Sources */,
A9D9A4CD2C36D54E004088DD /* UDPConnection.swift in Sources */,
+ F0A1638A2C47B77300592300 /* ServerRelaysResponse+Stubs.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -6327,6 +6406,11 @@
target = A992DA1C2C24709F00DE7CE5 /* MullvadRustRuntime */;
targetProxy = A9D9A4C52C36D53C004088DD /* PBXContainerItemProxy */;
};
+ F08B6B802C52927600D0A121 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 58C7A4352A863F440060C66F /* PacketTunnelCore */;
+ targetProxy = F08B6B7F2C52927600D0A121 /* PBXContainerItemProxy */;
+ };
F0ACE30F2BE4E478006D5333 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F0ACE3072BE4E478006D5333 /* MullvadMockData */;
@@ -6808,6 +6892,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "MullvadVPN/Supporting Files/MullvadVPN.entitlements";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CODE_SIGN_STYLE = Manual;
+ DEVELOPMENT_TEAM = "";
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
@@ -6816,6 +6905,8 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Mullvad VPN Development";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = minimal;
SWIFT_VERSION = 5.0;
@@ -6830,6 +6921,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "MullvadVPN/Supporting Files/MullvadVPN.entitlements";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CODE_SIGN_STYLE = Manual;
+ DEVELOPMENT_TEAM = "";
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
@@ -6838,6 +6934,8 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Mullvad VPN Development";
SWIFT_STRICT_CONCURRENCY = minimal;
SWIFT_VERSION = 5.0;
};
@@ -7442,7 +7540,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "MullvadVPN/Supporting Files/MullvadVPN.entitlements";
+ CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CODE_SIGN_STYLE = Manual;
+ DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist";
@@ -7452,6 +7553,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Mullvad VPN Development";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = minimal;
@@ -8124,7 +8226,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 17.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadRustRuntimeTests;
@@ -8148,7 +8250,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 17.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadRustRuntimeTests;
@@ -8171,7 +8273,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 17.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadRustRuntimeTests;
@@ -8194,7 +8296,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 17.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadRustRuntimeTests;
@@ -8269,7 +8371,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "MullvadVPN/Supporting Files/MullvadVPN.entitlements";
+ CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CODE_SIGN_STYLE = Manual;
+ DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist";
@@ -8279,6 +8384,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Mullvad VPN Development";
SWIFT_STRICT_CONCURRENCY = minimal;
SWIFT_VERSION = 5.0;
@@ -9182,6 +9288,11 @@
package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */;
productName = WireGuardKitTypes;
};
+ F08B6B812C52931600D0A121 /* WireGuardKitTypes */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */;
+ productName = WireGuardKitTypes;
+ };
F0ACE3272BE4E712006D5333 /* WireGuardKitTypes */ = {
isa = XCSwiftPackageProductDependency;
package = 58F097482A20C30000DA2DAD /* XCRemoteSwiftPackageReference "wireguard-apple" */;
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index 735cc75a19..2cb726c962 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -21,12 +21,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private let providerLogger: Logger
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!
+ private var postQuantumKeyExchangingPipeline: PostQuantumKeyExchangingPipeline!
private let multihopStateListener = MultihopStateListener()
private let multihopUpdater: MultihopUpdater
@@ -94,14 +94,21 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
protocolObfuscator: ProtocolObfuscator<UDPOverTCPObfuscator>()
)
- postQuantumActor = PostQuantumKeyExchangeActor(
- packetTunnel: postQuantumReceiver,
- onFailure: self.keyExchangeFailed,
- iteratorProvider: { REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() }
- )
-
let urlRequestProxy = URLRequestProxy(dispatchQueue: internalQueue, transportProvider: transportProvider)
appMessageHandler = AppMessageHandler(packetTunnelActor: actor, urlRequestProxy: urlRequestProxy)
+
+ postQuantumKeyExchangingPipeline = PostQuantumKeyExchangingPipeline(
+ PostQuantumKeyExchangeActor(
+ packetTunnel: postQuantumReceiver,
+ onFailure: self.keyExchangeFailed,
+ iteratorProvider: { REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() }
+ ),
+ onUpdateConfiguration: { [unowned self] configuration in
+ actor.replaceKeyWithPQ(configuration: configuration)
+ }, onFinish: { [unowned self] in
+ actor.notifyPostQuantumKeyExchanged()
+ }
+ )
}
override func startTunnel(options: [String: NSObject]? = nil) async throws {
@@ -251,8 +258,8 @@ extension PacketTunnelProvider {
}
switch newState {
- case let .reconnecting(connState), let .connecting(connState):
- let connectionAttempt = connState.connectionAttemptCount
+ case let .reconnecting(observedConnectionState), let .connecting(observedConnectionState):
+ let connectionAttempt = observedConnectionState.connectionAttemptCount
// Start device check every second failure attempt to connect.
if lastConnectionAttempt != connectionAttempt, connectionAttempt > 0,
@@ -263,9 +270,8 @@ extension PacketTunnelProvider {
// Cache last connection attempt to filter out repeating calls.
lastConnectionAttempt = connectionAttempt
- case let .negotiatingPostQuantumKey(_, privateKey):
- postQuantumActor.startNegotiation(with: privateKey)
-
+ case let .negotiatingPostQuantumKey(observedConnectionState, privateKey):
+ postQuantumKeyExchangingPipeline.startNegotiation(observedConnectionState, privateKey: privateKey)
case .initial, .connected, .disconnecting, .disconnected, .error:
break
}
@@ -310,8 +316,7 @@ extension PacketTunnelProvider {
extension PacketTunnelProvider: PostQuantumKeyReceiving {
func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
- postQuantumActor.reset()
- actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey)
+ postQuantumKeyExchangingPipeline.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
}
func keyExchangeFailed() {
diff --git a/ios/PacketTunnel/PostQuantum/MultiHopPostQuantumKeyExchanging.swift b/ios/PacketTunnel/PostQuantum/MultiHopPostQuantumKeyExchanging.swift
new file mode 100644
index 0000000000..ed602de976
--- /dev/null
+++ b/ios/PacketTunnel/PostQuantum/MultiHopPostQuantumKeyExchanging.swift
@@ -0,0 +1,132 @@
+//
+// MultiHopPostQuantumKeyExchanging.swift
+// PacketTunnel
+//
+// Created by Mojgan on 2024-07-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadREST
+import MullvadRustRuntime
+import MullvadSettings
+import MullvadTypes
+import PacketTunnelCore
+import WireGuardKitTypes
+
+final class MultiHopPostQuantumKeyExchanging: PostQuantumKeyExchangingProtocol {
+ let entry: SelectedRelay
+ let exit: SelectedRelay
+ let keyExchanger: PostQuantumKeyExchangeActorProtocol
+ let devicePrivateKey: PrivateKey
+ let onFinish: () -> Void
+ let onUpdateConfiguration: (PostQuantumNegotiationState) -> Void
+
+ private var entryPostQuantumKey: PostQuantumKey!
+ private var exitPostQuantumKey: PostQuantumKey!
+
+ private let defaultGatewayAddressRange = [IPAddressRange(from: "\(LocalNetworkIPs.gatewayAddress.rawValue)/32")!]
+ private let allTrafficRange = [
+ IPAddressRange(from: "\(LocalNetworkIPs.defaultRouteIpV4.rawValue)/0")!,
+ IPAddressRange(from: "\(LocalNetworkIPs.defaultRouteIpV6.rawValue)/0")!,
+ ]
+
+ private var state: StateMachine = .initial
+
+ enum StateMachine {
+ case initial
+ case negotiatingWithEntry
+ case negotiatingBetweenEntryAndExit
+ case makeConnection
+ }
+
+ init(
+ entry: SelectedRelay,
+ exit: SelectedRelay,
+ devicePrivateKey: PrivateKey,
+ keyExchanger: PostQuantumKeyExchangeActorProtocol,
+ onUpdateConfiguration: @escaping (PostQuantumNegotiationState) -> Void,
+ onFinish: @escaping () -> Void
+ ) {
+ self.entry = entry
+ self.exit = exit
+ self.devicePrivateKey = devicePrivateKey
+ self.keyExchanger = keyExchanger
+ self.onUpdateConfiguration = onUpdateConfiguration
+ self.onFinish = onFinish
+ }
+
+ func start() {
+ guard state == .initial else { return }
+ negotiateWithEntry()
+ }
+
+ func receivePostQuantumKey(
+ _ preSharedKey: PreSharedKey,
+ ephemeralKey: PrivateKey
+ ) {
+ if state == .negotiatingWithEntry {
+ entryPostQuantumKey = PostQuantumKey(preSharedKey: preSharedKey, ephemeralKey: ephemeralKey)
+ negotiateBetweenEntryAndExit()
+ } else if state == .negotiatingBetweenEntryAndExit {
+ exitPostQuantumKey = PostQuantumKey(preSharedKey: preSharedKey, ephemeralKey: ephemeralKey)
+ makeConnection()
+ }
+ }
+
+ private func negotiateWithEntry() {
+ state = .negotiatingWithEntry
+ onUpdateConfiguration(.single(PostQuantumConfigurationRelay(
+ relay: entry,
+ configuration: PostQuantumConfiguration(
+ privateKey: devicePrivateKey,
+ allowedIPs: defaultGatewayAddressRange
+ )
+ )))
+ keyExchanger.startNegotiation(with: devicePrivateKey)
+ }
+
+ private func negotiateBetweenEntryAndExit() {
+ state = .negotiatingBetweenEntryAndExit
+ onUpdateConfiguration(.multi(
+ entry: PostQuantumConfigurationRelay(
+ relay: entry,
+ configuration: PostQuantumConfiguration(
+ privateKey: entryPostQuantumKey.ephemeralKey,
+ preSharedKey: entryPostQuantumKey.preSharedKey,
+ allowedIPs: [IPAddressRange(from: "\(exit.endpoint.ipv4Relay.ip)/32")!]
+ )
+ ),
+ exit: PostQuantumConfigurationRelay(
+ relay: exit,
+ configuration: PostQuantumConfiguration(
+ privateKey: devicePrivateKey,
+ allowedIPs: defaultGatewayAddressRange
+ )
+ )
+ ))
+ keyExchanger.startNegotiation(with: devicePrivateKey)
+ }
+
+ private func makeConnection() {
+ state = .makeConnection
+ onUpdateConfiguration(.multi(
+ entry: PostQuantumConfigurationRelay(
+ relay: entry,
+ configuration: PostQuantumConfiguration(
+ privateKey: entryPostQuantumKey.ephemeralKey,
+ preSharedKey: entryPostQuantumKey.preSharedKey,
+ allowedIPs: [IPAddressRange(from: "\(exit.endpoint.ipv4Relay.ip)/32")!]
+ )
+ ),
+ exit: PostQuantumConfigurationRelay(
+ relay: exit,
+ configuration: PostQuantumConfiguration(
+ privateKey: exitPostQuantumKey.ephemeralKey,
+ preSharedKey: exitPostQuantumKey.preSharedKey,
+ allowedIPs: allTrafficRange
+ )
+ )
+ ))
+ self.onFinish()
+ }
+}
diff --git a/ios/PacketTunnel/PostQuantum/PostQuantumKeyExchangeActor.swift b/ios/PacketTunnel/PostQuantum/PostQuantumKeyExchangeActor.swift
new file mode 100644
index 0000000000..81365c6e81
--- /dev/null
+++ b/ios/PacketTunnel/PostQuantum/PostQuantumKeyExchangeActor.swift
@@ -0,0 +1,145 @@
+//
+// PostQuantumKeyExchangeActor.swift
+// PacketTunnel
+//
+// Created by Marco Nikic on 2024-04-12.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadRustRuntimeProxy
+import MullvadTypes
+import NetworkExtension
+import WireGuardKitTypes
+
+public protocol PostQuantumKeyExchangeActorProtocol {
+ func startNegotiation(with privateKey: PrivateKey)
+ func endCurrentNegotiation()
+ func reset()
+}
+
+public class PostQuantumKeyExchangeActor: PostQuantumKeyExchangeActorProtocol {
+ struct Negotiation {
+ var negotiator: PostQuantumKeyNegotiating
+ var inTunnelTCPConnection: NWTCPConnection
+ var tcpConnectionObserver: NSKeyValueObservation
+
+ func cancel() {
+ negotiator.cancelKeyNegotiation()
+ tcpConnectionObserver.invalidate()
+ inTunnelTCPConnection.cancel()
+ }
+ }
+
+ unowned let packetTunnel: any TunnelProvider
+ internal var negotiation: Negotiation?
+ private var timer: DispatchSourceTimer?
+ private var keyExchangeRetriesIterator: AnyIterator<Duration>!
+ private let iteratorProvider: () -> AnyIterator<Duration>
+ private let negotiationProvider: PostQuantumKeyNegotiating.Type
+
+ // Callback in the event of the negotiation failing on startup
+ var onFailure: () -> Void
+
+ public init(
+ packetTunnel: any TunnelProvider,
+ onFailure: @escaping (() -> Void),
+ negotiationProvider: PostQuantumKeyNegotiating.Type = PostQuantumKeyNegotiator.self,
+ iteratorProvider: @escaping () -> AnyIterator<Duration>
+ ) {
+ self.packetTunnel = packetTunnel
+ self.onFailure = onFailure
+ self.negotiationProvider = negotiationProvider
+ self.iteratorProvider = iteratorProvider
+ self.keyExchangeRetriesIterator = iteratorProvider()
+ }
+
+ private func createTCPConnection(_ gatewayEndpoint: NWHostEndpoint) -> NWTCPConnection {
+ self.packetTunnel.createTCPConnectionThroughTunnel(
+ to: gatewayEndpoint,
+ enableTLS: false,
+ tlsParameters: nil,
+ delegate: nil
+ )
+ }
+
+ /// Starts a new key exchange.
+ ///
+ /// Any ongoing key negotiation is stopped before starting a new one.
+ /// An exponential backoff timer is used to stop the exchange if it takes too long,
+ /// or if the TCP connection takes too long to become ready.
+ /// It is reset after every successful key exchange.
+ ///
+ /// - Parameter privateKey: The device's current private key
+ public func startNegotiation(with privateKey: PrivateKey) {
+ endCurrentNegotiation()
+ let negotiator = negotiationProvider.init()
+
+ let gatewayAddress = "10.64.0.1"
+ let IPv4Gateway = IPv4Address(gatewayAddress)!
+ let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "\(CONFIG_SERVICE_PORT)")
+ let inTunnelTCPConnection = createTCPConnection(endpoint)
+
+ // This will become the new private key of the device
+ let ephemeralSharedKey = PrivateKey()
+
+ let tcpConnectionTimeout = keyExchangeRetriesIterator.next() ?? .seconds(10)
+ // If the connection never becomes viable, force a reconnection after 10 seconds
+ scheduleInTunnelConnectionTimeout(startTime: .now() + tcpConnectionTimeout)
+
+ let tcpConnectionObserver = inTunnelTCPConnection.observe(\.isViable, options: [
+ .initial,
+ .new,
+ ]) { [weak self] observedConnection, _ in
+ guard let self, observedConnection.isViable else { return }
+ self.negotiation?.tcpConnectionObserver.invalidate()
+ self.timer?.cancel()
+
+ if !negotiator.startNegotiation(
+ gatewayIP: IPv4Gateway,
+ devicePublicKey: privateKey.publicKey,
+ presharedKey: ephemeralSharedKey,
+ postQuantumKeyReceiver: packetTunnel,
+ tcpConnection: inTunnelTCPConnection,
+ postQuantumKeyExchangeTimeout: tcpConnectionTimeout
+ ) {
+ // Cancel the negotiation to shut down any remaining use of the TCP connection on the Rust side
+ self.negotiation?.cancel()
+ self.negotiation = nil
+ self.onFailure()
+ }
+ }
+ negotiation = Negotiation(
+ negotiator: negotiator,
+ inTunnelTCPConnection: inTunnelTCPConnection,
+ tcpConnectionObserver: tcpConnectionObserver
+ )
+ }
+
+ /// Cancels the ongoing key exchange.
+ public func endCurrentNegotiation() {
+ negotiation?.cancel()
+ negotiation = nil
+ }
+
+ /// Resets the exponential timeout for successful key exchanges, and ends the current key exchange.
+ public func reset() {
+ keyExchangeRetriesIterator = iteratorProvider()
+ endCurrentNegotiation()
+ }
+
+ private func scheduleInTunnelConnectionTimeout(startTime: DispatchWallTime) {
+ let newTimer = DispatchSource.makeTimerSource()
+
+ newTimer.setEventHandler { [weak self] in
+ self?.onFailure()
+ self?.timer?.cancel()
+ }
+
+ newTimer.schedule(wallDeadline: startTime)
+ newTimer.activate()
+
+ timer?.cancel()
+ timer = newTimer
+ }
+}
diff --git a/ios/PacketTunnel/PostQuantum/PostQuantumKeyExchangingPipeline.swift b/ios/PacketTunnel/PostQuantum/PostQuantumKeyExchangingPipeline.swift
new file mode 100644
index 0000000000..1e9dc1d3c5
--- /dev/null
+++ b/ios/PacketTunnel/PostQuantum/PostQuantumKeyExchangingPipeline.swift
@@ -0,0 +1,59 @@
+//
+// PostQuantumKeyExchangingPipeline.swift
+// PacketTunnel
+//
+// Created by Mojgan on 2024-07-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadRustRuntime
+import MullvadSettings
+import PacketTunnelCore
+import WireGuardKitTypes
+
+final public class PostQuantumKeyExchangingPipeline {
+ let keyExchanger: PostQuantumKeyExchangeActorProtocol
+ let onUpdateConfiguration: (PostQuantumNegotiationState) -> Void
+ let onFinish: () -> Void
+
+ private var postQuantumKeyExchanging: PostQuantumKeyExchangingProtocol!
+
+ public init(
+ _ keyExchanger: PostQuantumKeyExchangeActorProtocol,
+ onUpdateConfiguration: @escaping (PostQuantumNegotiationState) -> Void,
+ onFinish: @escaping () -> Void
+ ) {
+ self.keyExchanger = keyExchanger
+ self.onUpdateConfiguration = onUpdateConfiguration
+ self.onFinish = onFinish
+ }
+
+ public func startNegotiation(_ connectionState: ObservedConnectionState, privateKey: PrivateKey) {
+ keyExchanger.reset()
+ let entryPeer = connectionState.selectedRelays.entry
+ let exitPeer = connectionState.selectedRelays.exit
+ if let entryPeer {
+ postQuantumKeyExchanging = MultiHopPostQuantumKeyExchanging(
+ entry: entryPeer,
+ exit: exitPeer,
+ devicePrivateKey: privateKey,
+ keyExchanger: keyExchanger,
+ onUpdateConfiguration: self.onUpdateConfiguration,
+ onFinish: onFinish
+ )
+ } else {
+ postQuantumKeyExchanging = SingleHopPostQuantumKeyExchanging(
+ exit: exitPeer,
+ devicePrivateKey: privateKey,
+ keyExchanger: keyExchanger,
+ onUpdateConfiguration: self.onUpdateConfiguration,
+ onFinish: onFinish
+ )
+ }
+ postQuantumKeyExchanging.start()
+ }
+
+ public func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
+ postQuantumKeyExchanging.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
+ }
+}
diff --git a/ios/PacketTunnel/PostQuantum/SingleHopPostQuantumKeyExchanging.swift b/ios/PacketTunnel/PostQuantum/SingleHopPostQuantumKeyExchanging.swift
new file mode 100644
index 0000000000..b05a819fb9
--- /dev/null
+++ b/ios/PacketTunnel/PostQuantum/SingleHopPostQuantumKeyExchanging.swift
@@ -0,0 +1,65 @@
+//
+// SingleHopPostQuantumKeyExchanging.swift
+// PacketTunnel
+//
+// Created by Mojgan on 2024-07-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadREST
+import MullvadRustRuntime
+import MullvadSettings
+import MullvadTypes
+import PacketTunnelCore
+import WireGuardKitTypes
+
+struct SingleHopPostQuantumKeyExchanging: PostQuantumKeyExchangingProtocol {
+ let exit: SelectedRelay
+ let keyExchanger: PostQuantumKeyExchangeActorProtocol
+ let devicePrivateKey: PrivateKey
+ let onFinish: () -> Void
+ let onUpdateConfiguration: (PostQuantumNegotiationState) -> Void
+
+ init(
+ exit: SelectedRelay,
+ devicePrivateKey: PrivateKey,
+ keyExchanger: PostQuantumKeyExchangeActorProtocol,
+ onUpdateConfiguration: @escaping (PostQuantumNegotiationState) -> Void,
+ onFinish: @escaping () -> Void
+ ) {
+ self.devicePrivateKey = devicePrivateKey
+ self.exit = exit
+ self.keyExchanger = keyExchanger
+ self.onUpdateConfiguration = onUpdateConfiguration
+ self.onFinish = onFinish
+ }
+
+ func start() {
+ onUpdateConfiguration(.single(PostQuantumConfigurationRelay(
+ relay: exit,
+ configuration: PostQuantumConfiguration(
+ privateKey: devicePrivateKey,
+ allowedIPs: [IPAddressRange(from: "\(LocalNetworkIPs.gatewayAddress.rawValue)/32")!]
+ )
+ )))
+ keyExchanger.startNegotiation(with: devicePrivateKey)
+ }
+
+ func receivePostQuantumKey(
+ _ preSharedKey: WireGuardKitTypes.PreSharedKey,
+ ephemeralKey: WireGuardKitTypes.PrivateKey
+ ) {
+ onUpdateConfiguration(.single(PostQuantumConfigurationRelay(
+ relay: exit,
+ configuration: PostQuantumConfiguration(
+ privateKey: ephemeralKey,
+ preSharedKey: preSharedKey,
+ allowedIPs: [
+ IPAddressRange(from: "\(LocalNetworkIPs.defaultRouteIpV4.rawValue)/0")!,
+ IPAddressRange(from: "\(LocalNetworkIPs.defaultRouteIpV6.rawValue)/0")!,
+ ]
+ )
+ )))
+ self.onFinish()
+ }
+}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift
index 8f94dd7086..de3fe8cb7a 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift
@@ -120,7 +120,7 @@ extension PacketTunnelActor {
)
var config = try configurationBuilder.makeConfiguration()
config.dns = [IPv4Address.loopback]
- config.interfaceAddresses = [IPAddressRange(from: "10.64.0.1/8")!]
+ config.interfaceAddresses = [IPAddressRange(from: "\(LocalNetworkIPs.gatewayAddress.rawValue)/8")!]
config.peer = TunnelPeer(
endpoint: .ipv4(IPv4Endpoint(string: "127.0.0.1:9090")!),
publicKey: PrivateKey().publicKey
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
index a03f0ae4f9..4793193d3c 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
@@ -20,20 +20,7 @@ extension PacketTunnelActor {
reason: ActorReconnectReason
) async throws {
if let connectionState = try obfuscateConnection(nextRelays: nextRelays, settings: settings, reason: reason) {
- let selectedEndpoint = connectionState.connectedEndpoint
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)
}
}
@@ -41,44 +28,78 @@ extension PacketTunnelActor {
/**
Called on receipt of the new PQ-negotiated key, to reconnect to the relay, in PQ-secure mode.
*/
- internal func postQuantumConnect(with key: PreSharedKey, privateKey: PrivateKey) async {
- guard
- // It is important to select the same relay that was saved in the connection state as the key negotiation happened with this specific relay.
- let selectedRelays = state.connectionData?.selectedRelays,
- let settings: Settings = try? settingsReader.read(),
- let connectionState = try? obfuscateConnection(
- nextRelays: .preSelected(selectedRelays),
- settings: settings,
- reason: .userInitiated
- )
+ internal func postQuantumConnect() async {
+ guard let connectionData = state.connectionData
else {
logger.error("Could not create connection state in PostQuantumConnect")
-
- let nextRelays: NextRelays = (state.connectionData?.selectedRelays).map { .preSelected($0) } ?? .current
- eventChannel.send(.reconnect(nextRelays))
+ eventChannel.send(.reconnect(.current))
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)
+ state = .connecting(connectionData)
- try? await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration())
- // Resume tunnel monitoring and use exit IPv4 gateway as a probe address.
- tunnelMonitor.start(probeAddress: connectionState.selectedRelays.exit.endpoint.ipv4Gateway)
+ // Resume tunnel monitoring and use IPv4 gateway as a probe address.
+ tunnelMonitor.start(probeAddress: connectionData.selectedRelays.exit.endpoint.ipv4Gateway)
// Restart default path observer and notify the observer with the current path that might have changed while
// path observer was paused.
startDefaultPathObserver(notifyObserverWithCurrentPath: false)
}
+
+ /**
+ Called to reconfigure the tunnel after each key negotiation.
+ */
+ internal func updatePostQuantumNegotiationState(configuration: PostQuantumNegotiationState) async throws {
+ /**
+ The obfuscater needs to be restarted every time a new tunnel configuration is being used,
+ because the obfuscation may be tied to a specific UDP session, as is the case for udp2tcp.
+ */
+ let settings: Settings = try settingsReader.read()
+ guard let connectionData = try obfuscateConnection(
+ nextRelays: .current,
+ settings: settings,
+ reason: .userInitiated
+ ) else {
+ logger.error("Tried to replace post quantum configuration in invalid state: \(state.name)")
+ return
+ }
+
+ switch configuration {
+ case let .single(hop):
+ let exitConfiguration = try ConfigurationBuilder(
+ privateKey: hop.configuration.privateKey,
+ interfaceAddresses: settings.interfaceAddresses,
+ dns: settings.dnsServers,
+ endpoint: connectionData.connectedEndpoint,
+ allowedIPs: hop.configuration.allowedIPs,
+ preSharedKey: hop.configuration.preSharedKey
+ ).makeConfiguration()
+
+ try await tunnelAdapter.start(configuration: exitConfiguration)
+
+ case let .multi(firstHop, secondHop):
+ let entryConfiguration = try ConfigurationBuilder(
+ privateKey: firstHop.configuration.privateKey,
+ interfaceAddresses: settings.interfaceAddresses,
+ dns: settings.dnsServers,
+ endpoint: connectionData.connectedEndpoint,
+ allowedIPs: firstHop.configuration.allowedIPs,
+ preSharedKey: firstHop.configuration.preSharedKey
+ ).makeConfiguration()
+
+ let exitConfiguration = try ConfigurationBuilder(
+ privateKey: secondHop.configuration.privateKey,
+ interfaceAddresses: settings.interfaceAddresses,
+ dns: settings.dnsServers,
+ endpoint: secondHop.relay.endpoint,
+ allowedIPs: secondHop.configuration.allowedIPs,
+ preSharedKey: secondHop.configuration.preSharedKey
+ ).makeConfiguration()
+
+ try await tunnelAdapter.startMultihop(
+ entryConfiguration: entryConfiguration, exitConfiguration: exitConfiguration
+ )
+ }
+ }
}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift
index 160fd9bbe6..625b69319e 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Public.swift
@@ -52,12 +52,20 @@ extension PacketTunnelActor {
}
/**
+ Tell actor that post quantum key exchanging took place.
+ */
+
+ nonisolated public func notifyPostQuantumKeyExchanged() {
+ eventChannel.send(.notifyPostQuantumKeyExchanged)
+ }
+
+ /**
Issue a new preshared key to the Actor.
- Parameter key: the new key
*/
- nonisolated public func replacePreSharedKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
- eventChannel.send(.replaceDevicePrivateKey(key, ephemeralKey: ephemeralKey))
+ nonisolated public func replaceKeyWithPQ(configuration: PostQuantumNegotiationState) {
+ eventChannel.send(.postQuantumNegotiationStateChanged(configuration))
}
/**
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index 1b17312739..f8c0f5997e 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -138,8 +138,15 @@ public actor PacketTunnelActor {
case let .cacheActiveKey(lastKeyRotation):
cacheActiveKey(lastKeyRotation: lastKeyRotation)
- case let .postQuantumConnect(key, privateKey: privateKey):
- await postQuantumConnect(with: key, privateKey: privateKey)
+ case let .reconfigureForPostQuantum(configuration):
+ do {
+ try await updatePostQuantumNegotiationState(configuration: configuration)
+ } catch {
+ logger.error(error: error, message: "Failed to reconfigure tunnel after each hop negotiation.")
+ await setErrorStateInternal(with: error)
+ }
+ case .postQuantumConnect:
+ await postQuantumConnect()
case .setDisconnectedState:
self.state = .disconnected
}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
index 2579a7de39..7ea1c785b5 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
@@ -37,7 +37,10 @@ extension PacketTunnelActor {
case networkReachability(NetworkPath)
/// Update the device private key, as per post-quantum protocols
- case replaceDevicePrivateKey(PreSharedKey, ephemeralKey: PrivateKey)
+ case postQuantumNegotiationStateChanged(PostQuantumNegotiationState)
+
+ /// Notify that post quantum key exchanging took place
+ case notifyPostQuantumKeyExchanged
/// Format command for log output.
func logFormat() -> String {
@@ -70,8 +73,10 @@ extension PacketTunnelActor {
return "networkReachability"
case .switchKey:
return "switchKey"
- case .replaceDevicePrivateKey:
- return "replaceDevicePrivateKey"
+ case .postQuantumNegotiationStateChanged:
+ return "postQuantumNegotiationStateChanged"
+ case .notifyPostQuantumKeyExchanged:
+ return "notifyPostQuantumKeyExchanged"
}
}
}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift
index 6d4056b683..7d18463f17 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorReducer.swift
@@ -19,12 +19,15 @@ extension PacketTunnelActor {
case updateTunnelMonitorPath(NetworkPath)
case startConnection(NextRelays)
case restartConnection(NextRelays, ActorReconnectReason)
+
// trigger a reconnect, which becomes several effects depending on the state
case reconnect(NextRelays)
case stopTunnelAdapter
case configureForErrorState(BlockedStateReason)
case cacheActiveKey(Date?)
- case postQuantumConnect(PreSharedKey, privateKey: PrivateKey)
+ case reconfigureForPostQuantum(PostQuantumNegotiationState)
+ case postQuantumConnect
+
// acknowledge that the disconnection process has concluded, go to .disconnected.
case setDisconnectedState
@@ -42,8 +45,8 @@ extension PacketTunnelActor {
case (.stopTunnelAdapter, .stopTunnelAdapter): true
case let (.configureForErrorState(r0), .configureForErrorState(r1)): r0 == r1
case let (.cacheActiveKey(d0), .cacheActiveKey(d1)): d0 == d1
- case let (.postQuantumConnect(psk0, privateKey: pk0), .postQuantumConnect(psk1, privateKey: pk1)): psk0 ==
- psk1 && pk0 == pk1
+ case let (.reconfigureForPostQuantum(pqns0), .reconfigureForPostQuantum(pqns1)): pqns0 == pqns1
+ case (.postQuantumConnect, .postQuantumConnect): true
case (.setDisconnectedState, .setDisconnectedState): true
default: false
}
@@ -86,8 +89,11 @@ extension PacketTunnelActor {
state.mutateAssociatedData { $0.networkReachability = newReachability }
return [.updateTunnelMonitorPath(defaultPath)]
- case let .replaceDevicePrivateKey(key, ephemeralKey: ephemeralKey):
- return [.postQuantumConnect(key, privateKey: ephemeralKey)]
+ case let .postQuantumNegotiationStateChanged(configuration):
+ return [.reconfigureForPostQuantum(configuration)]
+
+ case .notifyPostQuantumKeyExchanged:
+ return [.postQuantumConnect]
}
}
diff --git a/ios/PacketTunnelCore/Actor/PostQuantumKey.swift b/ios/PacketTunnelCore/Actor/PostQuantumKey.swift
new file mode 100644
index 0000000000..bb5cf7f438
--- /dev/null
+++ b/ios/PacketTunnelCore/Actor/PostQuantumKey.swift
@@ -0,0 +1,19 @@
+//
+// PostQuantumKey.swift
+// PacketTunnelCore
+//
+// Created by Mojgan on 2024-07-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import WireGuardKitTypes
+
+public struct PostQuantumKey: Equatable {
+ public let preSharedKey: PreSharedKey
+ public let ephemeralKey: PrivateKey
+
+ public init(preSharedKey: PreSharedKey, ephemeralKey: PrivateKey) {
+ self.preSharedKey = preSharedKey
+ self.ephemeralKey = ephemeralKey
+ }
+}
diff --git a/ios/PacketTunnelCore/Actor/PostQuantumNegotiationState.swift b/ios/PacketTunnelCore/Actor/PostQuantumNegotiationState.swift
new file mode 100644
index 0000000000..bb3b34c450
--- /dev/null
+++ b/ios/PacketTunnelCore/Actor/PostQuantumNegotiationState.swift
@@ -0,0 +1,61 @@
+//
+// PostQuantumConfiguration.swift
+// PacketTunnelCore
+//
+// Created by Mojgan on 2024-07-16.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadREST
+import WireGuardKitTypes
+
+public enum PostQuantumNegotiationState: Equatable {
+ case single(PostQuantumConfigurationRelay)
+ case multi(entry: PostQuantumConfigurationRelay, exit: PostQuantumConfigurationRelay)
+
+ public static func == (lhs: PostQuantumNegotiationState, rhs: PostQuantumNegotiationState) -> Bool {
+ return switch (lhs, rhs) {
+ case let (.single(hop1), .single(hop2)):
+ hop1 == hop2
+ case let (.multi(entry: entry1, exit: exit1), .multi(entry: entry2, exit: exit2)):
+ entry1 == entry2 && exit1 == exit2
+ default:
+ false
+ }
+ }
+}
+
+public struct PostQuantumConfigurationRelay: Equatable, CustomDebugStringConvertible {
+ public let relay: SelectedRelay
+ public let configuration: PostQuantumConfiguration
+
+ public init(relay: SelectedRelay, configuration: PostQuantumConfiguration) {
+ self.relay = relay
+ self.configuration = configuration
+ }
+
+ public var debugDescription: String {
+ "{ relay : \(relay.debugDescription), post quantum: \(configuration.debugDescription) }"
+ }
+}
+
+public struct PostQuantumConfiguration: Equatable, CustomDebugStringConvertible {
+ public let privateKey: PrivateKey
+ public let preSharedKey: PreSharedKey?
+ public let allowedIPs: [IPAddressRange]
+
+ public init(privateKey: PrivateKey, preSharedKey: PreSharedKey? = nil, allowedIPs: [IPAddressRange]) {
+ self.privateKey = privateKey
+ self.preSharedKey = preSharedKey
+ self.allowedIPs = allowedIPs
+ }
+
+ public var debugDescription: String {
+ var string = "{ private key : \(privateKey),"
+ string += preSharedKey.flatMap {
+ "preShared key: \($0), "
+ } ?? ""
+ string += ", allowedIPs: \(allowedIPs) }"
+ return string
+ }
+}
diff --git a/ios/PacketTunnelCore/Actor/Protocols/PostQuantumKeyExchangingProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/PostQuantumKeyExchangingProtocol.swift
new file mode 100644
index 0000000000..1df27c7176
--- /dev/null
+++ b/ios/PacketTunnelCore/Actor/Protocols/PostQuantumKeyExchangingProtocol.swift
@@ -0,0 +1,14 @@
+//
+// PostQuantumKeyExchangingProtocol.swift
+// PacketTunnel
+//
+// Created by Mojgan on 2024-07-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import WireGuardKitTypes
+
+public protocol PostQuantumKeyExchangingProtocol {
+ func start()
+ func receivePostQuantumKey(_ preSharedKey: PreSharedKey, ephemeralKey: PrivateKey)
+}
diff --git a/ios/PacketTunnelCoreTests/KeyExchangingResultStub.swift b/ios/PacketTunnelCoreTests/KeyExchangingResultStub.swift
new file mode 100644
index 0000000000..3d6b747a97
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/KeyExchangingResultStub.swift
@@ -0,0 +1,24 @@
+//
+// KeyExchangingResultStub.swift
+// MullvadPostQuantumTests
+//
+// Created by Mojgan on 2024-07-19.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+@testable import MullvadRustRuntime
+@testable import MullvadTypes
+@testable import WireGuardKitTypes
+
+struct KeyExchangingResultStub: PostQuantumKeyReceiving {
+ var onFailure: (() -> Void)?
+ var onReceivePostQuantumKey: ((PreSharedKey, PrivateKey) -> Void)?
+
+ func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
+ onReceivePostQuantumKey?(key, ephemeralKey)
+ }
+
+ func keyExchangeFailed() {
+ onFailure?()
+ }
+}
diff --git a/ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift b/ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift
new file mode 100644
index 0000000000..a49b9d0c44
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/Mocks/PostQuantumKeyExchangingUpdaterStub.swift
@@ -0,0 +1,15 @@
+//
+// PostQuantumKeyExchangingUpdaterStub.swift
+// PacketTunnelCoreTests
+//
+// Created by Mojgan on 2024-07-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+@testable import MullvadTypes
+@testable import PacketTunnelCore
+
+final class PostQuantumKeyExchangingUpdaterStub: PostQuantumKeyExchangingUpdaterProtocol {
+ var reconfigurationHandler: ConfigUpdater? = nil
+}
diff --git a/ios/PacketTunnelCoreTests/MultiHopPostQuantumKeyExchangingTests.swift b/ios/PacketTunnelCoreTests/MultiHopPostQuantumKeyExchangingTests.swift
new file mode 100644
index 0000000000..fea8988aa8
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/MultiHopPostQuantumKeyExchangingTests.swift
@@ -0,0 +1,130 @@
+//
+// MultiHopPostQuantumKeyExchangingTests.swift
+// MullvadPostQuantumTests
+//
+// Created by Mojgan on 2024-07-18.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+@testable import MullvadMockData
+@testable import MullvadREST
+@testable import MullvadRustRuntime
+@testable import MullvadTypes
+@testable import WireGuardKitTypes
+import XCTest
+
+final class MultiHopPostQuantumKeyExchangingTests: XCTestCase {
+ var exitRelay: SelectedRelay!
+ var entryRelay: SelectedRelay!
+
+ override func setUpWithError() throws {
+ let relayConstraints = RelayConstraints(
+ entryLocations: .only(UserSelectedRelays(locations: [.country("se")])),
+ exitLocations: .only(UserSelectedRelays(locations: [.country("us")]))
+ )
+
+ let exitMatch = try RelaySelector.WireGuard.pickCandidate(
+ from: try RelaySelector.WireGuard.findCandidates(
+ by: relayConstraints.exitLocations,
+ in: ServerRelaysResponseStubs.sampleRelays,
+ filterConstraint: relayConstraints.filter
+ ),
+ relays: ServerRelaysResponseStubs.sampleRelays,
+ portConstraint: relayConstraints.port,
+ numberOfFailedAttempts: 0
+ )
+
+ let entryMatch = try RelaySelector.WireGuard.pickCandidate(
+ from: try RelaySelector.WireGuard.findCandidates(
+ by: relayConstraints.entryLocations,
+ in: ServerRelaysResponseStubs.sampleRelays,
+ filterConstraint: relayConstraints.filter
+ ),
+ relays: ServerRelaysResponseStubs.sampleRelays,
+ portConstraint: relayConstraints.port,
+ numberOfFailedAttempts: 0
+ )
+
+ entryRelay = SelectedRelay(
+ endpoint: entryMatch.endpoint,
+ hostname: entryMatch.relay.hostname,
+ location: entryMatch.location
+ )
+ exitRelay = SelectedRelay(
+ endpoint: exitMatch.endpoint,
+ hostname: exitMatch.relay.hostname,
+ location: exitMatch.location
+ )
+ }
+
+ func testKeyExchangeFailsWhenNegotiationCannotStart() {
+ let expectedNegotiationFailure = expectation(description: "Negotiation failed.")
+
+ let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place")
+ reconfigurationExpectation.expectedFulfillmentCount = 1
+
+ let negotiationSuccessful = expectation(description: "Negotiation succeeded.")
+ negotiationSuccessful.isInverted = true
+
+ let keyExchangeActor = PostQuantumKeyExchangeActorStub()
+ keyExchangeActor.result = .failure(PostQuantumKeyExchangeErrorStub.canceled)
+
+ let multiHopPostQuantumKeyExchanging = MultiHopPostQuantumKeyExchanging(
+ entry: entryRelay,
+ exit: exitRelay,
+ devicePrivateKey: PrivateKey(),
+ keyExchanger: keyExchangeActor
+ ) { _ in
+ reconfigurationExpectation.fulfill()
+ } onFinish: {
+ negotiationSuccessful.fulfill()
+ }
+
+ keyExchangeActor.delegate = KeyExchangingResultStub {
+ expectedNegotiationFailure.fulfill()
+ }
+
+ multiHopPostQuantumKeyExchanging.start()
+
+ wait(
+ for: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ timeout: .UnitTest.invertedTimeout
+ )
+ }
+
+ func testKeyExchangeSuccessWhenNegotiationStart() throws {
+ let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.")
+ unexpectedNegotiationFailure.isInverted = true
+
+ let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place")
+ reconfigurationExpectation.expectedFulfillmentCount = 3
+
+ let negotiationSuccessful = expectation(description: "Negotiation succeeded.")
+ negotiationSuccessful.expectedFulfillmentCount = 1
+
+ let keyExchangeActor = PostQuantumKeyExchangeActorStub()
+ let preSharedKey = try XCTUnwrap(PreSharedKey(hexKey: PrivateKey().hexKey))
+ keyExchangeActor.result = .success((preSharedKey, PrivateKey()))
+
+ let multiHopPostQuantumKeyExchanging = MultiHopPostQuantumKeyExchanging(
+ entry: entryRelay,
+ exit: exitRelay,
+ devicePrivateKey: PrivateKey(),
+ keyExchanger: keyExchangeActor
+ ) { _ in
+ reconfigurationExpectation.fulfill()
+ } onFinish: {
+ negotiationSuccessful.fulfill()
+ }
+
+ keyExchangeActor.delegate = KeyExchangingResultStub(onReceivePostQuantumKey: { preSharedKey, ephemeralKey in
+ multiHopPostQuantumKeyExchanging.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey)
+ })
+ multiHopPostQuantumKeyExchanging.start()
+
+ wait(
+ for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ timeout: .UnitTest.invertedTimeout
+ )
+ }
+}
diff --git a/ios/PacketTunnelCoreTests/PostQuantumKeyExchangeActorStub.swift b/ios/PacketTunnelCoreTests/PostQuantumKeyExchangeActorStub.swift
new file mode 100644
index 0000000000..9ea468f547
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/PostQuantumKeyExchangeActorStub.swift
@@ -0,0 +1,38 @@
+//
+// PostQuantumKeyExchangeActorStub.swift
+// MullvadPostQuantumTests
+//
+// Created by Mojgan on 2024-07-18.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+@testable import MullvadRustRuntime
+@testable import MullvadTypes
+import NetworkExtension
+@testable import PacketTunnelCore
+@testable import WireGuardKitTypes
+
+final class PostQuantumKeyExchangeActorStub: PostQuantumKeyExchangeActorProtocol {
+ typealias KeyNegotiationResult = Result<(PreSharedKey, PrivateKey), PostQuantumKeyExchangeErrorStub>
+ var result: KeyNegotiationResult = .failure(.unknown)
+
+ var delegate: PostQuantumKeyReceiving?
+
+ func startNegotiation(with privateKey: PrivateKey) {
+ switch result {
+ case let .success((preSharedKey, ephemeralKey)):
+ delegate?.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey)
+ case .failure:
+ delegate?.keyExchangeFailed()
+ }
+ }
+
+ func endCurrentNegotiation() {}
+
+ func reset() {}
+}
+
+enum PostQuantumKeyExchangeErrorStub: Error {
+ case unknown
+ case canceled
+}
diff --git a/ios/PacketTunnelCoreTests/PostQuantumKeyExchangingPipelineTests.swift b/ios/PacketTunnelCoreTests/PostQuantumKeyExchangingPipelineTests.swift
new file mode 100644
index 0000000000..ba45034935
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/PostQuantumKeyExchangingPipelineTests.swift
@@ -0,0 +1,138 @@
+//
+// PostQuantumKeyExchangingPipelineTests.swift
+// MullvadPostQuantumTests
+//
+// Created by Mojgan on 2024-07-19.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+@testable import MullvadMockData
+@testable import MullvadREST
+@testable import MullvadRustRuntime
+@testable import MullvadTypes
+@testable import PacketTunnelCore
+@testable import WireGuardKitTypes
+import XCTest
+
+final class PostQuantumKeyExchangingPipelineTests: XCTestCase {
+ var entryRelay: SelectedRelay!
+ var exitRelay: SelectedRelay!
+ var relayConstraints: RelayConstraints!
+
+ override func setUpWithError() throws {
+ relayConstraints = RelayConstraints(
+ entryLocations: .only(UserSelectedRelays(locations: [.country("se")])),
+ exitLocations: .only(UserSelectedRelays(locations: [.country("us")]))
+ )
+
+ let exitMatch = try RelaySelector.WireGuard.pickCandidate(
+ from: try RelaySelector.WireGuard.findCandidates(
+ by: relayConstraints.exitLocations,
+ in: ServerRelaysResponseStubs.sampleRelays,
+ filterConstraint: relayConstraints.filter
+ ),
+ relays: ServerRelaysResponseStubs.sampleRelays,
+ portConstraint: relayConstraints.port,
+ numberOfFailedAttempts: 0
+ )
+
+ let entryMatch = try RelaySelector.WireGuard.pickCandidate(
+ from: try RelaySelector.WireGuard.findCandidates(
+ by: relayConstraints.entryLocations,
+ in: ServerRelaysResponseStubs.sampleRelays,
+ filterConstraint: relayConstraints.filter
+ ),
+ relays: ServerRelaysResponseStubs.sampleRelays,
+ portConstraint: relayConstraints.port,
+ numberOfFailedAttempts: 0
+ )
+
+ entryRelay = SelectedRelay(
+ endpoint: entryMatch.endpoint,
+ hostname: entryMatch.relay.hostname,
+ location: entryMatch.location
+ )
+ exitRelay = SelectedRelay(
+ endpoint: exitMatch.endpoint,
+ hostname: exitMatch.relay.hostname,
+ location: exitMatch.location
+ )
+ }
+
+ func testSingleHopKeyExchange() throws {
+ let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place")
+ reconfigurationExpectation.expectedFulfillmentCount = 2
+
+ let negotiationSuccessful = expectation(description: "Negotiation succeeded.")
+ negotiationSuccessful.expectedFulfillmentCount = 1
+
+ let keyExchangeActor = PostQuantumKeyExchangeActorStub()
+ let preSharedKey = try XCTUnwrap(PreSharedKey(hexKey: PrivateKey().hexKey))
+ keyExchangeActor.result = .success((preSharedKey, PrivateKey()))
+
+ let postQuantumKeyExchangingPipeline = PostQuantumKeyExchangingPipeline(keyExchangeActor) { _ in
+ reconfigurationExpectation.fulfill()
+ } onFinish: {
+ negotiationSuccessful.fulfill()
+ }
+
+ keyExchangeActor.delegate = KeyExchangingResultStub(onReceivePostQuantumKey: { preSharedKey, privateKey in
+ postQuantumKeyExchangingPipeline.receivePostQuantumKey(preSharedKey, ephemeralKey: privateKey)
+ })
+
+ let connectionState = ObservedConnectionState(
+ selectedRelays: SelectedRelays(entry: nil, exit: exitRelay, retryAttempt: 0),
+ relayConstraints: relayConstraints,
+ networkReachability: NetworkReachability.reachable,
+ connectionAttemptCount: 0,
+ transportLayer: .udp,
+ remotePort: 1234,
+ isPostQuantum: true
+ )
+
+ postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey())
+
+ wait(
+ for: [reconfigurationExpectation, negotiationSuccessful],
+ timeout: .UnitTest.invertedTimeout
+ )
+ }
+
+ func testMultiHopKeyExchange() throws {
+ let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place")
+ reconfigurationExpectation.expectedFulfillmentCount = 3
+
+ let negotiationSuccessful = expectation(description: "Negotiation succeeded.")
+ negotiationSuccessful.expectedFulfillmentCount = 1
+
+ let keyExchangeActor = PostQuantumKeyExchangeActorStub()
+ let preSharedKey = try XCTUnwrap(PreSharedKey(hexKey: PrivateKey().hexKey))
+ keyExchangeActor.result = .success((preSharedKey, PrivateKey()))
+
+ let postQuantumKeyExchangingPipeline = PostQuantumKeyExchangingPipeline(keyExchangeActor) { _ in
+ reconfigurationExpectation.fulfill()
+ } onFinish: {
+ negotiationSuccessful.fulfill()
+ }
+
+ keyExchangeActor.delegate = KeyExchangingResultStub(onReceivePostQuantumKey: { preSharedKey, privateKey in
+ postQuantumKeyExchangingPipeline.receivePostQuantumKey(preSharedKey, ephemeralKey: privateKey)
+ })
+
+ let connectionState = ObservedConnectionState(
+ selectedRelays: SelectedRelays(entry: entryRelay, exit: exitRelay, retryAttempt: 0),
+ relayConstraints: relayConstraints,
+ networkReachability: NetworkReachability.reachable,
+ connectionAttemptCount: 0,
+ transportLayer: .udp,
+ remotePort: 1234,
+ isPostQuantum: true
+ )
+
+ postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey())
+
+ wait(
+ for: [reconfigurationExpectation, negotiationSuccessful],
+ timeout: .UnitTest.invertedTimeout
+ )
+ }
+}
diff --git a/ios/PacketTunnelCoreTests/SingleHopPostQuantumKeyExchangingTests.swift b/ios/PacketTunnelCoreTests/SingleHopPostQuantumKeyExchangingTests.swift
new file mode 100644
index 0000000000..fbadc64a66
--- /dev/null
+++ b/ios/PacketTunnelCoreTests/SingleHopPostQuantumKeyExchangingTests.swift
@@ -0,0 +1,108 @@
+//
+// SingleHopPostQuantumKeyExchangingTests.swift
+// MullvadPostQuantumTests
+//
+// Created by Mojgan on 2024-07-17.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+@testable import MullvadMockData
+@testable import MullvadREST
+@testable import MullvadRustRuntime
+@testable import MullvadTypes
+@testable import WireGuardKitTypes
+import XCTest
+
+final class SingleHopPostQuantumKeyExchangingTests: XCTestCase {
+ var exitRelay: SelectedRelay!
+
+ override func setUpWithError() throws {
+ let relayConstraints = RelayConstraints(
+ exitLocations: .only(UserSelectedRelays(locations: [.hostname("se", "sto", "se6-wireguard")]))
+ )
+
+ let candidates = try RelaySelector.WireGuard.findCandidates(
+ by: relayConstraints.exitLocations,
+ in: ServerRelaysResponseStubs.sampleRelays,
+ filterConstraint: relayConstraints.filter
+ )
+
+ let match = try RelaySelector.WireGuard.pickCandidate(
+ from: candidates,
+ relays: ServerRelaysResponseStubs.sampleRelays,
+ portConstraint: relayConstraints.port,
+ numberOfFailedAttempts: 0
+ )
+
+ exitRelay = SelectedRelay(endpoint: match.endpoint, hostname: match.relay.hostname, location: match.location)
+ }
+
+ func testKeyExchangeFailsWhenNegotiationCannotStart() {
+ let expectedNegotiationFailure = expectation(description: "Negotiation failed.")
+
+ let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place")
+ reconfigurationExpectation.expectedFulfillmentCount = 1
+
+ let negotiationSuccessful = expectation(description: "Negotiation succeeded.")
+ negotiationSuccessful.isInverted = true
+
+ let keyExchangeActor = PostQuantumKeyExchangeActorStub()
+ keyExchangeActor.result = .failure(PostQuantumKeyExchangeErrorStub.canceled)
+
+ let singleHopPostQuantumKeyExchanging = SingleHopPostQuantumKeyExchanging(
+ exit: exitRelay,
+ devicePrivateKey: PrivateKey(),
+ keyExchanger: keyExchangeActor
+ ) { _ in
+ reconfigurationExpectation.fulfill()
+ } onFinish: {
+ negotiationSuccessful.fulfill()
+ }
+
+ keyExchangeActor.delegate = KeyExchangingResultStub {
+ expectedNegotiationFailure.fulfill()
+ }
+
+ singleHopPostQuantumKeyExchanging.start()
+
+ wait(
+ for: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ timeout: .UnitTest.invertedTimeout
+ )
+ }
+
+ func testKeyExchangeSuccessWhenNegotiationStart() throws {
+ let unexpectedNegotiationFailure = expectation(description: "Negotiation failed.")
+ unexpectedNegotiationFailure.isInverted = true
+
+ let reconfigurationExpectation = expectation(description: "Tunnel reconfiguration took place")
+ reconfigurationExpectation.expectedFulfillmentCount = 2
+
+ let negotiationSuccessful = expectation(description: "Negotiation succeeded.")
+ negotiationSuccessful.expectedFulfillmentCount = 1
+
+ let keyExchangeActor = PostQuantumKeyExchangeActorStub()
+ let preSharedKey = try XCTUnwrap(PreSharedKey(hexKey: PrivateKey().hexKey))
+ keyExchangeActor.result = .success((preSharedKey, PrivateKey()))
+
+ let singleHopPostQuantumKeyExchanging = SingleHopPostQuantumKeyExchanging(
+ exit: exitRelay,
+ devicePrivateKey: PrivateKey(),
+ keyExchanger: keyExchangeActor
+ ) { _ in
+ reconfigurationExpectation.fulfill()
+ } onFinish: {
+ negotiationSuccessful.fulfill()
+ }
+
+ keyExchangeActor.delegate = KeyExchangingResultStub(onReceivePostQuantumKey: { preSharedKey, ephemeralKey in
+ singleHopPostQuantumKeyExchanging.receivePostQuantumKey(preSharedKey, ephemeralKey: ephemeralKey)
+ })
+ singleHopPostQuantumKeyExchanging.start()
+
+ wait(
+ for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ timeout: .UnitTest.invertedTimeout
+ )
+ }
+}
diff --git a/ios/TestPlans/MullvadVPNApp.xctestplan b/ios/TestPlans/MullvadVPNApp.xctestplan
index e86ac7a2d2..5a849a2e94 100644
--- a/ios/TestPlans/MullvadVPNApp.xctestplan
+++ b/ios/TestPlans/MullvadVPNApp.xctestplan
@@ -37,14 +37,6 @@
"parallelizable" : true,
"target" : {
"containerPath" : "container:MullvadVPN.xcodeproj",
- "identifier" : "58695A9C2A4ADA9100328DB3",
- "name" : "TunnelObfuscationTests"
- }
- },
- {
- "parallelizable" : true,
- "target" : {
- "containerPath" : "container:MullvadVPN.xcodeproj",
"identifier" : "589A455128E094B300565204",
"name" : "OperationsTests"
}
@@ -68,8 +60,8 @@
{
"target" : {
"containerPath" : "container:MullvadVPN.xcodeproj",
- "identifier" : "A98F1B4D2C19C48D003C869E",
- "name" : "MullvadPostQuantumTests"
+ "identifier" : "A9D9A4BF2C36D53C004088DD",
+ "name" : "MullvadRustRuntimeTests"
}
}
],