diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2024-07-26 14:34:17 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2024-07-26 14:34:17 +0200 |
| commit | 55cfd69fc4bc3292a97e440531fa20e2e09b9e99 (patch) | |
| tree | e3427ca899157c434e5716b052ce8967f39e3070 /ios | |
| parent | f02965c4903f70f2e1e9d0dfc56203ee7b0accc2 (diff) | |
| parent | 8061e0c0ab5aecba2e80d9b05eed21227982a66d (diff) | |
| download | mullvadvpn-55cfd69fc4bc3292a97e440531fa20e2e09b9e99.tar.xz mullvadvpn-55cfd69fc4bc3292a97e440531fa20e2e09b9e99.zip | |
Merge branch 'negotiate-with-both-peers-when-using-multihop-and-pq-ios-746'
Diffstat (limited to 'ios')
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" } } ], |
