Skip to content

Commit 3f08309

Browse files
committed
Draft PR for Post Quantum tunneling
1 parent f565a3d commit 3f08309

15 files changed

+168
-9
lines changed

ios/MullvadSettings/TunnelSettings.swift

+7-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import Foundation
1010

1111
/// Alias to the latest version of the `TunnelSettings`.
12-
public typealias LatestTunnelSettings = TunnelSettingsV3
12+
public typealias LatestTunnelSettings = TunnelSettingsV4
1313

1414
/// Protocol all TunnelSettings must adhere to, for upgrade purposes.
1515
public protocol TunnelSettings: Codable {
@@ -27,22 +27,26 @@ public enum SchemaVersion: Int, Equatable {
2727
/// V2 format with WireGuard obfuscation options, stored as `TunnelSettingsV3`.
2828
case v3 = 3
2929

30+
case v4 = 4
31+
3032
var settingsType: any TunnelSettings.Type {
3133
switch self {
3234
case .v1: return TunnelSettingsV1.self
3335
case .v2: return TunnelSettingsV2.self
3436
case .v3: return TunnelSettingsV3.self
37+
case .v4: return TunnelSettingsV4.self
3538
}
3639
}
3740

3841
var nextVersion: Self {
3942
switch self {
4043
case .v1: return .v2
4144
case .v2: return .v3
42-
case .v3: return .v3
45+
case .v3: return .v4
46+
case .v4: return .v4
4347
}
4448
}
4549

4650
/// Current schema version.
47-
public static let current = SchemaVersion.v3
51+
public static let current = SchemaVersion.v4
4852
}

ios/MullvadSettings/TunnelSettingsV3.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public struct TunnelSettingsV3: Codable, Equatable, TunnelSettings {
3030
}
3131

3232
public func upgradeToNextVersion() -> any TunnelSettings {
33-
self
33+
TunnelSettingsV4(
34+
relayConstraints: relayConstraints,
35+
dnsSettings: dnsSettings,
36+
wireGuardObfuscation: wireGuardObfuscation,
37+
tunnelQuantumResistance: .automatic
38+
)
3439
}
3540
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// TunnelSettingsV4.swift
3+
// MullvadSettings
4+
//
5+
// Created by Marco Nikic on 2024-02-06.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import MullvadTypes
11+
12+
public struct TunnelSettingsV4: Codable, Equatable, TunnelSettings {
13+
/// Relay constraints.
14+
public var relayConstraints: RelayConstraints
15+
16+
/// DNS settings.
17+
public var dnsSettings: DNSSettings
18+
19+
/// WireGuard obfuscation settings
20+
public var wireGuardObfuscation: WireGuardObfuscationSettings
21+
22+
/// Whether Post Quantum exchanges are enabled.
23+
public var tunnelQuantumResistance: TunnelQuantumResistance
24+
25+
public init(
26+
relayConstraints: RelayConstraints = RelayConstraints(),
27+
dnsSettings: DNSSettings = DNSSettings(),
28+
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings(),
29+
tunnelQuantumResistance: TunnelQuantumResistance = .automatic
30+
) {
31+
self.relayConstraints = relayConstraints
32+
self.dnsSettings = dnsSettings
33+
self.wireGuardObfuscation = wireGuardObfuscation
34+
self.tunnelQuantumResistance = tunnelQuantumResistance
35+
}
36+
37+
public func upgradeToNextVersion() -> any TunnelSettings {
38+
self
39+
}
40+
}

ios/MullvadSettings/WireGuardObfuscationSettings.swift

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ public enum WireGuardObfuscationState: Codable {
1717
case off
1818
}
1919

20+
public enum TunnelQuantumResistance: Codable {
21+
case automatic
22+
case on
23+
case off
24+
}
25+
2026
/// The port to select when using UDP-over-TCP obfuscation
2127
///
2228
/// `.automatic` means an algorith will decide between using `port80` or `port5001`

ios/MullvadVPN.xcodeproj/project.pbxproj

+7
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@
610610
A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; };
611611
A91D78E32B03BDF200FCD5D3 /* TunnelObfuscation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5840231F2A406BF5007B27AC /* TunnelObfuscation.framework */; };
612612
A91D78E42B03C01600FCD5D3 /* MullvadSettings.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */; };
613+
A93181A12B727ED700E341D2 /* TunnelSettingsV4.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93181A02B727ED700E341D2 /* TunnelSettingsV4.swift */; };
613614
A932D9EF2B5ADD0700999395 /* ProxyConfigurationTransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A932D9EE2B5ADD0700999395 /* ProxyConfigurationTransportProvider.swift */; };
614615
A932D9F32B5EB61100999395 /* HeadRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A932D9F22B5EB61100999395 /* HeadRequestTests.swift */; };
615616
A932D9F52B5EBB9D00999395 /* RESTTransportStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A932D9F42B5EBB9D00999395 /* RESTTransportStub.swift */; };
@@ -1773,6 +1774,7 @@
17731774
A92ECC232A7802520052F1B1 /* StoredAccountData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredAccountData.swift; sourceTree = "<group>"; };
17741775
A92ECC272A7802AB0052F1B1 /* StoredDeviceData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredDeviceData.swift; sourceTree = "<group>"; };
17751776
A92ECC2B2A7803A50052F1B1 /* DeviceState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceState.swift; sourceTree = "<group>"; };
1777+
A93181A02B727ED700E341D2 /* TunnelSettingsV4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV4.swift; sourceTree = "<group>"; };
17761778
A932D9EE2B5ADD0700999395 /* ProxyConfigurationTransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyConfigurationTransportProvider.swift; sourceTree = "<group>"; };
17771779
A932D9F22B5EB61100999395 /* HeadRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadRequestTests.swift; sourceTree = "<group>"; };
17781780
A932D9F42B5EBB9D00999395 /* RESTTransportStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTTransportStub.swift; sourceTree = "<group>"; };
@@ -2794,6 +2796,7 @@
27942796
587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */,
27952797
580F8B8228197881002E0998 /* TunnelSettingsV2.swift */,
27962798
A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */,
2799+
A93181A02B727ED700E341D2 /* TunnelSettingsV4.swift */,
27972800
A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */,
27982801
);
27992802
path = MullvadSettings;
@@ -4612,6 +4615,7 @@
46124615
58B2FDE42AA71D5C003EB5C6 /* SettingsManager.swift in Sources */,
46134616
F0164EBC2B482E430020268D /* AppStorage.swift in Sources */,
46144617
58B2FDE62AA71D5C003EB5C6 /* DeviceState.swift in Sources */,
4618+
A93181A12B727ED700E341D2 /* TunnelSettingsV4.swift in Sources */,
46154619
58FE25BF2AA72311003D1918 /* MigrationManager.swift in Sources */,
46164620
58B2FDEF2AA720C4003EB5C6 /* ApplicationTarget.swift in Sources */,
46174621
A988DF272ADE86ED00D807EF /* WireGuardObfuscationSettings.swift in Sources */,
@@ -5634,6 +5638,7 @@
56345638
buildSettings = {
56355639
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
56365640
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
5641+
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
56375642
CODE_SIGN_STYLE = Manual;
56385643
DEVELOPMENT_TEAM = "";
56395644
GENERATE_INFOPLIST_FILE = YES;
@@ -6800,6 +6805,7 @@
68006805
buildSettings = {
68016806
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
68026807
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
6808+
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
68036809
CODE_SIGN_STYLE = Manual;
68046810
DEVELOPMENT_TEAM = "";
68056811
GENERATE_INFOPLIST_FILE = YES;
@@ -7376,6 +7382,7 @@
73767382
buildSettings = {
73777383
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
73787384
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
7385+
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
73797386
CODE_SIGN_STYLE = Manual;
73807387
DEVELOPMENT_TEAM = "";
73817388
GENERATE_INFOPLIST_FILE = YES;

ios/PacketTunnel/PacketTunnelProvider/SettingsReader.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ struct SettingsReader: SettingsReaderProtocol {
2121
interfaceAddresses: [deviceData.ipv4Address, deviceData.ipv6Address],
2222
relayConstraints: settings.relayConstraints,
2323
dnsServers: settings.dnsSettings.selectedDNSServers,
24-
obfuscation: settings.wireGuardObfuscation
24+
obfuscation: settings.wireGuardObfuscation,
25+
tunnelQuantumResistance: settings.tunnelQuantumResistance
2526
)
2627
}
2728
}

ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift

+43
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct WgAdapter: TunnelAdapterProtocol {
2929
)
3030
}
3131

32+
// TODO: Should we know here whether the configuration is for PQ key exchange or not ?
3233
func start(configuration: TunnelAdapterConfiguration) async throws {
3334
let wgConfig = configuration.asWgConfig
3435
do {
@@ -44,6 +45,21 @@ struct WgAdapter: TunnelAdapterProtocol {
4445
}
4546
}
4647

48+
func startPostQuantumKeyExchange(configuration: TunnelAdapterConfiguration) async throws {
49+
let wgConfig = configuration.asWgPQConfig
50+
do {
51+
try await adapter.update(tunnelConfiguration: wgConfig)
52+
} catch WireGuardAdapterError.invalidState {
53+
try await adapter.start(tunnelConfiguration: wgConfig)
54+
}
55+
56+
let tunAddresses = wgConfig.interface.addresses.map { $0.address }
57+
// TUN addresses can be empty when adapter is configured for blocked state.
58+
if !tunAddresses.isEmpty {
59+
logIfDeviceHasSameIP(than: tunAddresses)
60+
}
61+
}
62+
4763
func stop() async throws {
4864
try await adapter.stop()
4965
}
@@ -111,6 +127,8 @@ private extension TunnelAdapterConfiguration {
111127
if let peer {
112128
var peerConfig = PeerConfiguration(publicKey: peer.publicKey)
113129
peerConfig.endpoint = peer.endpoint.wgEndpoint
130+
// TODO: change this to 10.64.0.1/32
131+
// TODO: What about the IPv6 address here ?
114132
peerConfig.allowedIPs = [
115133
IPAddressRange(from: "0.0.0.0/0")!,
116134
IPAddressRange(from: "::/0")!,
@@ -124,6 +142,31 @@ private extension TunnelAdapterConfiguration {
124142
peers: peers
125143
)
126144
}
145+
146+
var asWgPQConfig: TunnelConfiguration {
147+
var interfaceConfig = InterfaceConfiguration(privateKey: privateKey)
148+
interfaceConfig.addresses = interfaceAddresses
149+
interfaceConfig.dns = dns.map { DNSServer(address: $0) }
150+
interfaceConfig.listenPort = 0
151+
152+
var peers: [PeerConfiguration] = []
153+
if let peer {
154+
var peerConfig = PeerConfiguration(publicKey: peer.publicKey)
155+
peerConfig.endpoint = peer.endpoint.wgEndpoint
156+
// TODO: change this to 10.64.0.1/32
157+
// TODO: What about the IPv6 address here ?
158+
peerConfig.allowedIPs = [
159+
IPAddressRange(from: "10.64.0.1/32")!,
160+
]
161+
peers.append(peerConfig)
162+
}
163+
164+
return TunnelConfiguration(
165+
name: nil,
166+
interface: interfaceConfig,
167+
peers: peers
168+
)
169+
}
127170
}
128171

129172
private extension AnyIPEndpoint {

ios/PacketTunnelCore/Actor/Command.swift

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Foundation
1010

1111
/// Describes action that actor can perform.
1212
enum Command {
13+
case negotiatePostQuantumKey(StartOptions)
14+
1315
/// Start tunnel.
1416
case start(StartOptions)
1517

@@ -37,6 +39,8 @@ enum Command {
3739
/// Format command for log output.
3840
func logFormat() -> String {
3941
switch self {
42+
case .negotiatePostQuantumKey:
43+
return "PQ key exchange"
4044
case .start:
4145
return "start"
4246
case .stop:

ios/PacketTunnelCore/Actor/PacketTunnelActor.swift

+40
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ public actor PacketTunnelActor {
111111

112112
case let .networkReachability(defaultPath):
113113
await handleDefaultPathChange(defaultPath)
114+
case let .negotiatePostQuantumKey(StartOptions):
115+
await negotiatePostQuantumKeyExchange(StartOptions)
114116
}
115117
}
116118
}
@@ -128,6 +130,7 @@ extension PacketTunnelActor {
128130
- Parameter options: start options produced by packet tunnel
129131
*/
130132
private func start(options: StartOptions) async {
133+
// TODO: Make sure that we are **Not** in the initial state anymore if PQ key exchange happened
131134
guard case .initial = state else { return }
132135

133136
logger.debug("\(options.logFormat())")
@@ -147,6 +150,43 @@ extension PacketTunnelActor {
147150
}
148151
}
149152

153+
private func negotiatePostQuantumKeyExchange(_ options: StartOptions, nextRelay: NextRelay = .current) async {
154+
// TODO: Should this be the same path as in a reconnection attempt ?
155+
guard case .initial = state else { return }
156+
do {
157+
let settings: Settings = try settingsReader.read()
158+
// `.automatic` is the same as `off` by default for now
159+
if settings.tunnelQuantumResistance == .on {
160+
// Generate the configuration for PQ key exchange
161+
let postQuantumConfiguration = ConfigurationBuilder(
162+
privateKey: settings.privateKey,
163+
interfaceAddresses: settings.interfaceAddresses
164+
)
165+
let tunnelAdapterConfiguration = try postQuantumConfiguration.makeConfiguration()
166+
167+
// Start the adapter with the configuration
168+
try await tunnelAdapter.startPostQuantumKeyExchange(configuration: tunnelAdapterConfiguration)
169+
170+
// Do the GRPC call from `talpid-tunnel-config-client`
171+
if await negotiatePostQuantumKey() {
172+
commandChannel.send(.start(options))
173+
} else {
174+
// TODO: Insert some kind of delay before retrying probably, or let the natural delay from the GRPC failure dictate how long to wait
175+
// TODO: Change the selected relay here, reuse the same logic as a normal connection
176+
await negotiatePostQuantumKeyExchange(options, nextRelay: .random)
177+
}
178+
}
179+
} catch {
180+
// TODO: probably enter error state here
181+
// TODO: OR just bounce between relays until we manage to connect to a relay ?
182+
}
183+
}
184+
185+
func negotiatePostQuantumKey() async -> Bool {
186+
// Do the GRPC call from `talpid-tunnel-config-client`
187+
true
188+
}
189+
150190
/// Stop the tunnel.
151191
private func stop() async {
152192
switch state {

ios/PacketTunnelCore/Actor/Protocols/SettingsReaderProtocol.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,22 @@ public struct Settings {
4040
/// Obfuscation settings
4141
public var obfuscation: WireGuardObfuscationSettings
4242

43+
public var tunnelQuantumResistance: TunnelQuantumResistance
44+
4345
public init(
4446
privateKey: PrivateKey,
4547
interfaceAddresses: [IPAddressRange],
4648
relayConstraints: RelayConstraints,
4749
dnsServers: SelectedDNSServers,
48-
obfuscation: WireGuardObfuscationSettings
50+
obfuscation: WireGuardObfuscationSettings,
51+
tunnelQuantumResistance: TunnelQuantumResistance
4952
) {
5053
self.privateKey = privateKey
5154
self.interfaceAddresses = interfaceAddresses
5255
self.relayConstraints = relayConstraints
5356
self.dnsServers = dnsServers
5457
self.obfuscation = obfuscation
58+
self.tunnelQuantumResistance = tunnelQuantumResistance
5559
}
5660
}
5761

ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import WireGuardKitTypes
1616
public protocol TunnelAdapterProtocol {
1717
/// Start tunnel adapter or update active configuration.
1818
func start(configuration: TunnelAdapterConfiguration) async throws
19+
func startPostQuantumKeyExchange(configuration: TunnelAdapterConfiguration) async throws
1920

2021
/// Stop tunnel adapter with the given configuration.
2122
func stop() async throws

ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ extension SettingsReaderStub {
2929
interfaceAddresses: [IPAddressRange(from: "127.0.0.1/32")!],
3030
relayConstraints: RelayConstraints(),
3131
dnsServers: .gateway,
32-
obfuscation: WireGuardObfuscationSettings(state: .off, port: .automatic)
32+
obfuscation: WireGuardObfuscationSettings(state: .off, port: .automatic),
33+
tunnelQuantumResistance: .automatic
3334
)
3435

3536
return SettingsReaderStub {

ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import PacketTunnelCore
1111

1212
/// Dummy tunnel adapter that does nothing and reports no errors.
1313
class TunnelAdapterDummy: TunnelAdapterProtocol {
14+
func startPostQuantumKeyExchange(configuration: PacketTunnelCore.TunnelAdapterConfiguration) async throws {}
15+
1416
func start(configuration: TunnelAdapterConfiguration) async throws {}
1517

1618
func stop() async throws {}

ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ final class PacketTunnelActorTests: XCTestCase {
207207
interfaceAddresses: [IPAddressRange(from: "127.0.0.1/32")!],
208208
relayConstraints: RelayConstraints(),
209209
dnsServers: .gateway,
210-
obfuscation: WireGuardObfuscationSettings(state: .off, port: .automatic)
210+
obfuscation: WireGuardObfuscationSettings(state: .off, port: .automatic),
211+
tunnelQuantumResistance: .automatic
211212
)
212213
}
213214
}

ios/PacketTunnelCoreTests/ProtocolObfuscatorTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ final class ProtocolObfuscatorTests: XCTestCase {
110110
obfuscation: WireGuardObfuscationSettings(
111111
state: obfuscationState,
112112
port: obfuscationPort
113-
)
113+
), tunnelQuantumResistance: .automatic
114114
)
115115
}
116116
}

0 commit comments

Comments
 (0)