Skip to content

Commit

Permalink
Enable Daita in the PacketTunnel actor
Browse files Browse the repository at this point in the history
  • Loading branch information
buggmagnet authored and pinkisemils committed Sep 6, 2024
1 parent 50c9d86 commit 678f653
Show file tree
Hide file tree
Showing 57 changed files with 872 additions and 539 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// PostQuantumKeyExchangeActor.swift
// EphemeralPeerExchangeActor.swift
// PacketTunnel
//
// Created by Marco Nikic on 2024-04-12.
Expand All @@ -12,15 +12,15 @@ import MullvadTypes
import NetworkExtension
import WireGuardKitTypes

public protocol PostQuantumKeyExchangeActorProtocol {
func startNegotiation(with privateKey: PrivateKey)
public protocol EphemeralPeerExchangeActorProtocol {
func startNegotiation(with privateKey: PrivateKey, enablePostQuantum: Bool, enableDaita: Bool)
func endCurrentNegotiation()
func reset()
}

public class PostQuantumKeyExchangeActor: PostQuantumKeyExchangeActorProtocol {
public class EphemeralPeerExchangeActor: EphemeralPeerExchangeActorProtocol {
struct Negotiation {
var negotiator: PostQuantumKeyNegotiating
var negotiator: EphemeralPeerNegotiating
var inTunnelTCPConnection: NWTCPConnection
var tcpConnectionObserver: NSKeyValueObservation

Expand All @@ -36,15 +36,15 @@ public class PostQuantumKeyExchangeActor: PostQuantumKeyExchangeActorProtocol {
private var timer: DispatchSourceTimer?
private var keyExchangeRetriesIterator: AnyIterator<Duration>!
private let iteratorProvider: () -> AnyIterator<Duration>
private let negotiationProvider: PostQuantumKeyNegotiating.Type
private let negotiationProvider: EphemeralPeerNegotiating.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,
negotiationProvider: EphemeralPeerNegotiating.Type = EphemeralPeerNegotiator.self,
iteratorProvider: @escaping () -> AnyIterator<Duration>
) {
self.packetTunnel = packetTunnel
Expand All @@ -71,7 +71,7 @@ public class PostQuantumKeyExchangeActor: PostQuantumKeyExchangeActorProtocol {
/// It is reset after every successful key exchange.
///
/// - Parameter privateKey: The device's current private key
public func startNegotiation(with privateKey: PrivateKey) {
public func startNegotiation(with privateKey: PrivateKey, enablePostQuantum: Bool, enableDaita: Bool) {
endCurrentNegotiation()
let negotiator = negotiationProvider.init()

Expand Down Expand Up @@ -99,9 +99,11 @@ public class PostQuantumKeyExchangeActor: PostQuantumKeyExchangeActorProtocol {
gatewayIP: IPv4Gateway,
devicePublicKey: privateKey.publicKey,
presharedKey: ephemeralSharedKey,
postQuantumKeyReceiver: packetTunnel,
peerReceiver: packetTunnel,
tcpConnection: inTunnelTCPConnection,
postQuantumKeyExchangeTimeout: tcpConnectionTimeout
peerExchangeTimeout: tcpConnectionTimeout,
enablePostQuantum: enablePostQuantum,
enableDaita: enableDaita
) {
// Cancel the negotiation to shut down any remaining use of the TCP connection on the Rust side
self.negotiation?.cancel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,54 @@ import NetworkExtension
import WireGuardKitTypes

// swiftlint:disable function_parameter_count
public protocol PostQuantumKeyNegotiating {
public protocol EphemeralPeerNegotiating {
func startNegotiation(
gatewayIP: IPv4Address,
devicePublicKey: PublicKey,
presharedKey: PrivateKey,
postQuantumKeyReceiver: any TunnelProvider,
peerReceiver: any TunnelProvider,
tcpConnection: NWTCPConnection,
postQuantumKeyExchangeTimeout: Duration
peerExchangeTimeout: Duration,
enablePostQuantum: Bool,
enableDaita: Bool
) -> Bool

func cancelKeyNegotiation()

init()
}

/**
Attempt to start the asynchronous process of key negotiation. Returns true if successfully started, false if failed.
*/
public class PostQuantumKeyNegotiator: PostQuantumKeyNegotiating {
/// Requests an ephemeral peer asynchronously.
public class EphemeralPeerNegotiator: EphemeralPeerNegotiating {
required public init() {}

var cancelToken: PostQuantumCancelToken?
var cancelToken: EphemeralPeerCancelToken?

public func startNegotiation(
gatewayIP: IPv4Address,
devicePublicKey: PublicKey,
presharedKey: PrivateKey,
postQuantumKeyReceiver: any TunnelProvider,
peerReceiver: any TunnelProvider,
tcpConnection: NWTCPConnection,
postQuantumKeyExchangeTimeout: Duration
peerExchangeTimeout: Duration,
enablePostQuantum: Bool,
enableDaita: Bool
) -> Bool {
// swiftlint:disable:next force_cast
let postQuantumKeyReceiver = Unmanaged.passUnretained(postQuantumKeyReceiver as! PostQuantumKeyReceiver)
let ephemeralPeerReceiver = Unmanaged.passUnretained(peerReceiver as! EphemeralPeerReceiver)
.toOpaque()
let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque()
var cancelToken = PostQuantumCancelToken()
var cancelToken = EphemeralPeerCancelToken()

let result = negotiate_post_quantum_key(
let result = request_ephemeral_peer(
devicePublicKey.rawValue.map { $0 },
presharedKey.rawValue.map { $0 },
postQuantumKeyReceiver,
ephemeralPeerReceiver,
opaqueConnection,
&cancelToken,
UInt64(postQuantumKeyExchangeTimeout.timeInterval)
UInt64(peerExchangeTimeout.timeInterval),
enablePostQuantum,
enableDaita
)
guard result == 0 else {
return false
Expand All @@ -66,12 +70,12 @@ public class PostQuantumKeyNegotiator: PostQuantumKeyNegotiating {

public func cancelKeyNegotiation() {
guard var cancelToken else { return }
cancel_post_quantum_key_exchange(&cancelToken)
cancel_ephemeral_peer_exchange(&cancelToken)
}

deinit {
guard var cancelToken else { return }
drop_post_quantum_key_exchange_token(&cancelToken)
drop_ephemeral_peer_exchange_token(&cancelToken)
}
}

Expand Down
38 changes: 22 additions & 16 deletions ios/MullvadRustRuntime/PacketTunnelProvider+TCPConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,35 +78,41 @@ func tcpConnectionReceive(
}
}

/// End sequence of a quantum-secure pre shared key exchange.
/// End sequence of an ephemeral peer exchange.
///
/// This FFI function is called by Rust when the quantum-secure pre shared key exchange has either failed, or succeeded.
/// This FFI function is called by Rust when an ephemeral peer negotiation succeeded or failed.
/// When both the `rawPresharedKey` and the `rawEphemeralKey` are raw pointers to 32 bytes data arrays,
/// the quantum-secure key exchange is considered successful. In any other case, the exchange is considered failed.
/// the quantum-secure key exchange is considered successful.
/// If the `rawPresharedKey` is nil, but there is a valid `rawEphemeralKey`, it means a Daita peer has been negotiated with.
/// If `rawEphemeralKey` is nil, the negotiation is considered failed.
///
/// - Parameters:
/// - rawPacketTunnel: A raw pointer to the running instance of `NEPacketTunnelProvider`
/// - rawEphemeralPeerReceiver: A raw pointer to the running instance of `NEPacketTunnelProvider`
/// - rawPresharedKey: A raw pointer to the quantum-secure pre shared key
/// - rawEphemeralKey: A raw pointer to the ephemeral private key of the device
@_cdecl("swift_post_quantum_key_ready")
@_cdecl("swift_ephemeral_peer_ready")
func receivePostQuantumKey(
rawPostQuantumKeyReceiver: UnsafeMutableRawPointer?,
rawEphemeralPeerReceiver: UnsafeMutableRawPointer?,
rawPresharedKey: UnsafeMutableRawPointer?,
rawEphemeralKey: UnsafeMutableRawPointer?
) {
guard let rawPostQuantumKeyReceiver else { return }
let postQuantumKeyReceiver = Unmanaged<PostQuantumKeyReceiver>.fromOpaque(rawPostQuantumKeyReceiver)
guard let rawEphemeralPeerReceiver else { return }
let ephemeralPeerReceiver = Unmanaged<EphemeralPeerReceiver>.fromOpaque(rawEphemeralPeerReceiver)
.takeUnretainedValue()

guard
let rawPresharedKey,
let rawEphemeralKey,
let ephemeralKey = PrivateKey(rawValue: Data(bytes: rawEphemeralKey, count: 32)),
let key = PreSharedKey(rawValue: Data(bytes: rawPresharedKey, count: 32))
else {
postQuantumKeyReceiver.keyExchangeFailed()
// If there are no private keys for the ephemeral peer, then the negotiation either failed, or timed out.
guard let rawEphemeralKey,
let ephemeralKey = PrivateKey(rawValue: Data(bytes: rawEphemeralKey, count: 32)) else {
ephemeralPeerReceiver.ephemeralPeerExchangeFailed()
return
}

postQuantumKeyReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
// If there is a pre-shared key, an ephemeral peer was negotiated with Post Quantum options
// Otherwise, a Daita enabled ephemeral peer was requested
if let rawPresharedKey, let key = PreSharedKey(rawValue: Data(bytes: rawPresharedKey, count: 32)) {
ephemeralPeerReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
} else {
ephemeralPeerReceiver.receiveEphemeralPeerPrivateKey(ephemeralKey)
}
return
}
49 changes: 27 additions & 22 deletions ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
#include <stdint.h>
#include <stdlib.h>

typedef struct PostQuantumCancelToken {
typedef struct EphemeralPeerCancelToken {
void *context;
} PostQuantumCancelToken;
} EphemeralPeerCancelToken;

typedef struct ProxyHandle {
void *context;
Expand All @@ -17,28 +17,28 @@ typedef struct ProxyHandle {
extern const uint16_t CONFIG_SERVICE_PORT;

/**
* Called by the Swift side to signal that the quantum-secure key exchange should be cancelled.
* Called by the Swift side to signal that the ephemeral peer exchange should be cancelled.
* After this call, the cancel token is no longer valid.
*
* # Safety
* `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the
* `sender` must be pointing to a valid instance of a `EphemeralPeerCancelToken` created by the
* `PacketTunnelProvider`.
*/
void cancel_post_quantum_key_exchange(const struct PostQuantumCancelToken *sender);
void cancel_ephemeral_peer_exchange(const struct EphemeralPeerCancelToken *sender);

/**
* Called by the Swift side to signal that the Rust `PostQuantumCancelToken` can be safely dropped
* Called by the Swift side to signal that the Rust `EphemeralPeerCancelToken` can be safely dropped
* from memory.
*
* # Safety
* `sender` must be pointing to a valid instance of a `PostQuantumCancelToken` created by the
* `sender` must be pointing to a valid instance of a `EphemeralPeerCancelToken` created by the
* `PacketTunnelProvider`.
*/
void drop_post_quantum_key_exchange_token(const struct PostQuantumCancelToken *sender);
void drop_ephemeral_peer_exchange_token(const struct EphemeralPeerCancelToken *sender);

/**
* Called by Swift whenever data has been written to the in-tunnel TCP connection when exchanging
* quantum-resistant pre shared keys.
* quantum-resistant pre shared keys, or ephemeral peers.
*
* If `bytes_sent` is 0, this indicates that the connection was closed or that an error occurred.
*
Expand All @@ -50,7 +50,7 @@ void handle_sent(uintptr_t bytes_sent, const void *sender);

/**
* Called by Swift whenever data has been read from the in-tunnel TCP connection when exchanging
* quantum-resistant pre shared keys.
* quantum-resistant pre shared keys, or ephemeral peers.
*
* If `data` is null or empty, this indicates that the connection was closed or that an error
* occurred. An empty buffer is sent to the underlying reader to signal EOF.
Expand All @@ -63,7 +63,7 @@ void handle_sent(uintptr_t bytes_sent, const void *sender);
void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender);

/**
* Entry point for exchanging post quantum keys on iOS.
* Entry point for requesting ephemeral peers on iOS.
* The TCP connection must be created to go through the tunnel.
* # Safety
* `public_key` and `ephemeral_key` must be valid respective `PublicKey` and `PrivateKey` types.
Expand All @@ -72,12 +72,14 @@ void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender);
* connection instances.
* `cancel_token` should be owned by the caller of this function.
*/
int32_t negotiate_post_quantum_key(const uint8_t *public_key,
const uint8_t *ephemeral_key,
const void *packet_tunnel,
const void *tcp_connection,
struct PostQuantumCancelToken *cancel_token,
uint64_t post_quantum_key_exchange_timeout);
int32_t request_ephemeral_peer(const uint8_t *public_key,
const uint8_t *ephemeral_key,
const void *packet_tunnel,
const void *tcp_connection,
struct EphemeralPeerCancelToken *cancel_token,
uint64_t peer_exchange_timeout,
bool enable_post_quantum,
bool enable_daita);

/**
* Called when there is data to send on the TCP connection.
Expand All @@ -95,12 +97,15 @@ extern void swift_nw_tcp_connection_send(const void *connection,
extern void swift_nw_tcp_connection_read(const void *connection, const void *sender);

/**
* Called when the preshared post quantum key is ready.
* `raw_preshared_key` might be NULL if the key negotiation failed.
* Called when the preshared post quantum key is ready,
* or when a Daita peer has been successfully requested.
* `raw_preshared_key` will be NULL if:
* - The post quantum key negotiation failed
* - A Daita peer has been requested without enabling post quantum keys.
*/
extern void swift_post_quantum_key_ready(const void *raw_packet_tunnel,
const uint8_t *raw_preshared_key,
const uint8_t *raw_ephemeral_private_key);
extern void swift_ephemeral_peer_ready(const void *raw_packet_tunnel,
const uint8_t *raw_preshared_key,
const uint8_t *raw_ephemeral_private_key);

/**
* # Safety
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// MullvadPostQuantumTests.swift
// MullvadPostQuantumTests
// EphemeralPeerExchangeActorTests.swift
// MullvadRustRuntimeTests
//
// Created by Marco Nikic on 2024-06-12.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
Expand All @@ -14,7 +14,7 @@ import NetworkExtension
@testable import WireGuardKitTypes
import XCTest

class MullvadPostQuantumTests: XCTestCase {
class EphemeralPeerExchangeActorTests: XCTestCase {
var tcpConnection: NWTCPConnectionStub!
var tunnelProvider: TunnelProviderStub!

Expand All @@ -26,7 +26,7 @@ class MullvadPostQuantumTests: XCTestCase {
func testKeyExchangeFailsWhenNegotiationCannotStart() {
let negotiationFailure = expectation(description: "Negotiation failed")

let keyExchangeActor = PostQuantumKeyExchangeActor(
let keyExchangeActor = EphemeralPeerExchangeActor(
packetTunnel: tunnelProvider,
onFailure: {
negotiationFailure.fulfill()
Expand All @@ -36,7 +36,7 @@ class MullvadPostQuantumTests: XCTestCase {
)

let privateKey = PrivateKey()
keyExchangeActor.startNegotiation(with: privateKey)
keyExchangeActor.startNegotiation(with: privateKey, enablePostQuantum: true, enableDaita: false)
tcpConnection.becomeViable()

wait(for: [negotiationFailure])
Expand All @@ -46,7 +46,7 @@ class MullvadPostQuantumTests: XCTestCase {
let negotiationFailure = expectation(description: "Negotiation failed")

// Setup the actor to wait only 10 milliseconds before declaring the TCP connection is not ready in time.
let keyExchangeActor = PostQuantumKeyExchangeActor(
let keyExchangeActor = EphemeralPeerExchangeActor(
packetTunnel: tunnelProvider,
onFailure: {
negotiationFailure.fulfill()
Expand All @@ -56,7 +56,7 @@ class MullvadPostQuantumTests: XCTestCase {
)

let privateKey = PrivateKey()
keyExchangeActor.startNegotiation(with: privateKey)
keyExchangeActor.startNegotiation(with: privateKey, enablePostQuantum: true, enableDaita: false)

wait(for: [negotiationFailure])
}
Expand All @@ -65,7 +65,7 @@ class MullvadPostQuantumTests: XCTestCase {
let unexpectedNegotiationFailure = expectation(description: "Unexpected negotiation failure")
unexpectedNegotiationFailure.isInverted = true

let keyExchangeActor = PostQuantumKeyExchangeActor(
let keyExchangeActor = EphemeralPeerExchangeActor(
packetTunnel: tunnelProvider,
onFailure: {
unexpectedNegotiationFailure.fulfill()
Expand All @@ -75,7 +75,7 @@ class MullvadPostQuantumTests: XCTestCase {
)

let privateKey = PrivateKey()
keyExchangeActor.startNegotiation(with: privateKey)
keyExchangeActor.startNegotiation(with: privateKey, enablePostQuantum: true, enableDaita: false)

let negotiationProvider = try XCTUnwrap(
keyExchangeActor.negotiation?
Expand Down
Loading

0 comments on commit 678f653

Please sign in to comment.