Skip to content

Commit 8061e0c

Browse files
mojganiibuggmagnet
mojganii
authored andcommitted
Apply PQ key exchanging for multihop
1 parent 0c728b7 commit 8061e0c

27 files changed

+1228
-102
lines changed

ios/MullvadRustRuntime/PostQuantumKeyExchangeActor.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ import MullvadTypes
1212
import NetworkExtension
1313
import WireGuardKitTypes
1414

15-
public class PostQuantumKeyExchangeActor {
15+
public protocol PostQuantumKeyExchangeActorProtocol {
16+
func startNegotiation(with privateKey: PrivateKey)
17+
func endCurrentNegotiation()
18+
func reset()
19+
}
20+
21+
public class PostQuantumKeyExchangeActor: PostQuantumKeyExchangeActorProtocol {
1622
struct Negotiation {
1723
var negotiator: PostQuantumKeyNegotiating
1824
var inTunnelTCPConnection: NWTCPConnection
@@ -69,7 +75,7 @@ public class PostQuantumKeyExchangeActor {
6975
endCurrentNegotiation()
7076
let negotiator = negotiationProvider.init()
7177

72-
let gatewayAddress = "10.64.0.1"
78+
let gatewayAddress = LocalNetworkIPs.gatewayAddress.rawValue
7379
let IPv4Gateway = IPv4Address(gatewayAddress)!
7480
let endpoint = NWHostEndpoint(hostname: gatewayAddress, port: "\(CONFIG_SERVICE_PORT)")
7581
let inTunnelTCPConnection = createTCPConnection(endpoint)
@@ -97,6 +103,8 @@ public class PostQuantumKeyExchangeActor {
97103
tcpConnection: inTunnelTCPConnection,
98104
postQuantumKeyExchangeTimeout: tcpConnectionTimeout
99105
) {
106+
// Cancel the negotiation to shut down any remaining use of the TCP connection on the Rust side
107+
self.negotiation?.cancel()
100108
self.negotiation = nil
101109
self.onFailure()
102110
}

ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class FailedNegotiatorStub: PostQuantumKeyNegotiating {
5858
gatewayIP: IPv4Address,
5959
devicePublicKey: WireGuardKitTypes.PublicKey,
6060
presharedKey: WireGuardKitTypes.PrivateKey,
61-
packetTunnel: PacketTunnelCore.TunnelProvider,
61+
postQuantumKeyReceiver packetTunnel: any MullvadTypes.TunnelProvider,
6262
tcpConnection: NWTCPConnection,
6363
postQuantumKeyExchangeTimeout: MullvadTypes.Duration
6464
) -> Bool { false }
@@ -82,7 +82,7 @@ class SuccessfulNegotiatorStub: PostQuantumKeyNegotiating {
8282
gatewayIP: IPv4Address,
8383
devicePublicKey: WireGuardKitTypes.PublicKey,
8484
presharedKey: WireGuardKitTypes.PrivateKey,
85-
packetTunnel: PacketTunnelCore.TunnelProvider,
85+
postQuantumKeyReceiver packetTunnel: any MullvadTypes.TunnelProvider,
8686
tcpConnection: NWTCPConnection,
8787
postQuantumKeyExchangeTimeout: MullvadTypes.Duration
8888
) -> Bool { true }

ios/MullvadRustRuntimeTests/PostQuantumKeyExchangeActorTests.swift ios/MullvadRustRuntimeTests/MullvadPostQuantumTests.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
2-
// PostQuantumKeyExchangeActorTests.swift
3-
// PostQuantumKeyExchangeActorTests
2+
// MullvadPostQuantumTests.swift
3+
// MullvadPostQuantumTests
44
//
55
// Created by Marco Nikic on 2024-06-12.
66
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
@@ -71,7 +71,7 @@ class MullvadPostQuantumTests: XCTestCase {
7171
unexpectedNegotiationFailure.fulfill()
7272
},
7373
negotiationProvider: SuccessfulNegotiatorStub.self,
74-
iteratorProvider: { AnyIterator { .milliseconds(10) } }
74+
iteratorProvider: { AnyIterator { .seconds(1) } }
7575
)
7676

7777
let privateKey = PrivateKey()
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// LocalNetworkIPs.swift
3+
// MullvadTypes
4+
//
5+
// Created by Mojgan on 2024-07-26.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public enum LocalNetworkIPs: String {
12+
case gatewayAddress = "10.64.0.1"
13+
case defaultRouteIpV4 = "0.0.0.0"
14+
case defaultRouteIpV6 = "::"
15+
}

ios/MullvadTypes/Protocols/PostQuantumKeyReceiving.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import Foundation
1010
import WireGuardKitTypes
1111

12-
public protocol PostQuantumKeyReceiving: AnyObject {
12+
public protocol PostQuantumKeyReceiving {
1313
func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey)
1414
func keyExchangeFailed()
1515
}

ios/MullvadVPN.xcodeproj/project.pbxproj

+127-16
Large diffs are not rendered by default.

ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift

+19-14
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
2121
private let providerLogger: Logger
2222

2323
private var actor: PacketTunnelActor!
24-
private var postQuantumActor: PostQuantumKeyExchangeActor!
2524
private var appMessageHandler: AppMessageHandler!
2625
private var stateObserverTask: AnyTask?
2726
private var deviceChecker: DeviceChecker!
2827
private var adapter: WgAdapter!
2928
private var relaySelector: RelaySelectorWrapper!
29+
private var postQuantumKeyExchangingPipeline: PostQuantumKeyExchangingPipeline!
3030

3131
private let multihopStateListener = MultihopStateListener()
3232
private let multihopUpdater: MultihopUpdater
@@ -94,14 +94,21 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
9494
protocolObfuscator: ProtocolObfuscator<UDPOverTCPObfuscator>()
9595
)
9696

97-
postQuantumActor = PostQuantumKeyExchangeActor(
98-
packetTunnel: postQuantumReceiver,
99-
onFailure: self.keyExchangeFailed,
100-
iteratorProvider: { REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() }
101-
)
102-
10397
let urlRequestProxy = URLRequestProxy(dispatchQueue: internalQueue, transportProvider: transportProvider)
10498
appMessageHandler = AppMessageHandler(packetTunnelActor: actor, urlRequestProxy: urlRequestProxy)
99+
100+
postQuantumKeyExchangingPipeline = PostQuantumKeyExchangingPipeline(
101+
PostQuantumKeyExchangeActor(
102+
packetTunnel: postQuantumReceiver,
103+
onFailure: self.keyExchangeFailed,
104+
iteratorProvider: { REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() }
105+
),
106+
onUpdateConfiguration: { [unowned self] configuration in
107+
actor.replaceKeyWithPQ(configuration: configuration)
108+
}, onFinish: { [unowned self] in
109+
actor.notifyPostQuantumKeyExchanged()
110+
}
111+
)
105112
}
106113

107114
override func startTunnel(options: [String: NSObject]? = nil) async throws {
@@ -251,8 +258,8 @@ extension PacketTunnelProvider {
251258
}
252259

253260
switch newState {
254-
case let .reconnecting(connState), let .connecting(connState):
255-
let connectionAttempt = connState.connectionAttemptCount
261+
case let .reconnecting(observedConnectionState), let .connecting(observedConnectionState):
262+
let connectionAttempt = observedConnectionState.connectionAttemptCount
256263

257264
// Start device check every second failure attempt to connect.
258265
if lastConnectionAttempt != connectionAttempt, connectionAttempt > 0,
@@ -263,9 +270,8 @@ extension PacketTunnelProvider {
263270
// Cache last connection attempt to filter out repeating calls.
264271
lastConnectionAttempt = connectionAttempt
265272

266-
case let .negotiatingPostQuantumKey(_, privateKey):
267-
postQuantumActor.startNegotiation(with: privateKey)
268-
273+
case let .negotiatingPostQuantumKey(observedConnectionState, privateKey):
274+
postQuantumKeyExchangingPipeline.startNegotiation(observedConnectionState, privateKey: privateKey)
269275
case .initial, .connected, .disconnecting, .disconnected, .error:
270276
break
271277
}
@@ -310,8 +316,7 @@ extension PacketTunnelProvider {
310316

311317
extension PacketTunnelProvider: PostQuantumKeyReceiving {
312318
func receivePostQuantumKey(_ key: PreSharedKey, ephemeralKey: PrivateKey) {
313-
postQuantumActor.reset()
314-
actor.replacePreSharedKey(key, ephemeralKey: ephemeralKey)
319+
postQuantumKeyExchangingPipeline.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
315320
}
316321

317322
func keyExchangeFailed() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//
2+
// MultiHopPostQuantumKeyExchanging.swift
3+
// PacketTunnel
4+
//
5+
// Created by Mojgan on 2024-07-15.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import MullvadREST
10+
import MullvadRustRuntime
11+
import MullvadSettings
12+
import MullvadTypes
13+
import PacketTunnelCore
14+
import WireGuardKitTypes
15+
16+
final class MultiHopPostQuantumKeyExchanging: PostQuantumKeyExchangingProtocol {
17+
let entry: SelectedRelay
18+
let exit: SelectedRelay
19+
let keyExchanger: PostQuantumKeyExchangeActorProtocol
20+
let devicePrivateKey: PrivateKey
21+
let onFinish: () -> Void
22+
let onUpdateConfiguration: (PostQuantumNegotiationState) -> Void
23+
24+
private var entryPostQuantumKey: PostQuantumKey!
25+
private var exitPostQuantumKey: PostQuantumKey!
26+
27+
private let defaultGatewayAddressRange = [IPAddressRange(from: "\(LocalNetworkIPs.gatewayAddress.rawValue)/32")!]
28+
private let allTrafficRange = [
29+
IPAddressRange(from: "\(LocalNetworkIPs.defaultRouteIpV4.rawValue)/0")!,
30+
IPAddressRange(from: "\(LocalNetworkIPs.defaultRouteIpV6.rawValue)/0")!,
31+
]
32+
33+
private var state: StateMachine = .initial
34+
35+
enum StateMachine {
36+
case initial
37+
case negotiatingWithEntry
38+
case negotiatingBetweenEntryAndExit
39+
case makeConnection
40+
}
41+
42+
init(
43+
entry: SelectedRelay,
44+
exit: SelectedRelay,
45+
devicePrivateKey: PrivateKey,
46+
keyExchanger: PostQuantumKeyExchangeActorProtocol,
47+
onUpdateConfiguration: @escaping (PostQuantumNegotiationState) -> Void,
48+
onFinish: @escaping () -> Void
49+
) {
50+
self.entry = entry
51+
self.exit = exit
52+
self.devicePrivateKey = devicePrivateKey
53+
self.keyExchanger = keyExchanger
54+
self.onUpdateConfiguration = onUpdateConfiguration
55+
self.onFinish = onFinish
56+
}
57+
58+
func start() {
59+
guard state == .initial else { return }
60+
negotiateWithEntry()
61+
}
62+
63+
func receivePostQuantumKey(
64+
_ preSharedKey: PreSharedKey,
65+
ephemeralKey: PrivateKey
66+
) {
67+
if state == .negotiatingWithEntry {
68+
entryPostQuantumKey = PostQuantumKey(preSharedKey: preSharedKey, ephemeralKey: ephemeralKey)
69+
negotiateBetweenEntryAndExit()
70+
} else if state == .negotiatingBetweenEntryAndExit {
71+
exitPostQuantumKey = PostQuantumKey(preSharedKey: preSharedKey, ephemeralKey: ephemeralKey)
72+
makeConnection()
73+
}
74+
}
75+
76+
private func negotiateWithEntry() {
77+
state = .negotiatingWithEntry
78+
onUpdateConfiguration(.single(PostQuantumConfigurationRelay(
79+
relay: entry,
80+
configuration: PostQuantumConfiguration(
81+
privateKey: devicePrivateKey,
82+
allowedIPs: defaultGatewayAddressRange
83+
)
84+
)))
85+
keyExchanger.startNegotiation(with: devicePrivateKey)
86+
}
87+
88+
private func negotiateBetweenEntryAndExit() {
89+
state = .negotiatingBetweenEntryAndExit
90+
onUpdateConfiguration(.multi(
91+
entry: PostQuantumConfigurationRelay(
92+
relay: entry,
93+
configuration: PostQuantumConfiguration(
94+
privateKey: entryPostQuantumKey.ephemeralKey,
95+
preSharedKey: entryPostQuantumKey.preSharedKey,
96+
allowedIPs: [IPAddressRange(from: "\(exit.endpoint.ipv4Relay.ip)/32")!]
97+
)
98+
),
99+
exit: PostQuantumConfigurationRelay(
100+
relay: exit,
101+
configuration: PostQuantumConfiguration(
102+
privateKey: devicePrivateKey,
103+
allowedIPs: defaultGatewayAddressRange
104+
)
105+
)
106+
))
107+
keyExchanger.startNegotiation(with: devicePrivateKey)
108+
}
109+
110+
private func makeConnection() {
111+
state = .makeConnection
112+
onUpdateConfiguration(.multi(
113+
entry: PostQuantumConfigurationRelay(
114+
relay: entry,
115+
configuration: PostQuantumConfiguration(
116+
privateKey: entryPostQuantumKey.ephemeralKey,
117+
preSharedKey: entryPostQuantumKey.preSharedKey,
118+
allowedIPs: [IPAddressRange(from: "\(exit.endpoint.ipv4Relay.ip)/32")!]
119+
)
120+
),
121+
exit: PostQuantumConfigurationRelay(
122+
relay: exit,
123+
configuration: PostQuantumConfiguration(
124+
privateKey: exitPostQuantumKey.ephemeralKey,
125+
preSharedKey: exitPostQuantumKey.preSharedKey,
126+
allowedIPs: allTrafficRange
127+
)
128+
)
129+
))
130+
self.onFinish()
131+
}
132+
}

0 commit comments

Comments
 (0)