Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the UI to indicate that the tunnel is post-quantum #6214

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4f33c90
Add FFI to talpid-tunnel-config-client
pinkisemils Feb 6, 2024
e5f8085
Remove debug statements
buggmagnet Mar 20, 2024
074b9b3
Regroup switch cases together
buggmagnet Mar 20, 2024
c66cfc2
Enable reading Quantum Resistance settings from SettingsReader
buggmagnet Mar 20, 2024
c47f7af
Add utility methods for mutating `State`'s data, replacing switch stmts
acb-mv Mar 19, 2024
9f8a766
Remove premature changes not related to this refactoring
acb-mv Mar 19, 2024
af27234
Remove commented dead code
acb-mv Mar 19, 2024
39f9bdb
Make State Equatable, move change checks to didSet &c
acb-mv Mar 20, 2024
4a4be65
Add a new actor state for key negotiation
buggmagnet Mar 20, 2024
6110b4a
Read settings to determine whether to exchange a PQ key
buggmagnet Mar 21, 2024
b876e58
Try to reconnect when PQ key exchange fails
buggmagnet Mar 26, 2024
d20ed40
Remove dead code
buggmagnet Mar 26, 2024
5560936
Enable exchanging PQ keys from an unconnected state
buggmagnet Mar 26, 2024
7d46832
Add safety checks at the FFI boundary
buggmagnet Mar 26, 2024
f869246
Remove unneeded dependencies
buggmagnet Mar 27, 2024
d433f9a
Tidy code around key negotiation and cancellation token flow
acb-mv Mar 27, 2024
912dfed
Add "tidy" as an imperative verb
acb-mv Mar 27, 2024
835b4c2
Update git-commit-message-style to match main
acb-mv Mar 27, 2024
529f055
Fix a typo
buggmagnet Mar 28, 2024
b1c5edb
Remove unneeded framework reference
buggmagnet Mar 28, 2024
98f4526
Rewrite the PQ cancellation token in a safer way
buggmagnet Mar 28, 2024
1473189
Fix rebase issues
buggmagnet Apr 8, 2024
4db3f95
Add a method to receive the preshared key in the actor
buggmagnet Apr 10, 2024
5b83fde
Add preSharedKey to configurationBuilder
acb-mv Apr 10, 2024
7bf1753
Attempt at starting a tunnel with the PQ shared key
acb-mv Apr 10, 2024
ea93d6d
Hack the new private key as a global to prove PQ PSK works
buggmagnet Apr 11, 2024
984fe97
Prototype a new actor to handle PQ PSK
buggmagnet Apr 12, 2024
1629a5b
Add the ephemeral private key to the negotiation data flow (Swift-sid…
acb-mv Apr 12, 2024
94ccbf8
Pass the ephemeral key to postQuantumConnect
acb-mv Apr 12, 2024
4a6f626
Modify (partially) Rust code to handle the ephemeral key as passed th…
acb-mv Apr 15, 2024
49c051b
Derive the ephemeral public key from the private key in the FFI
buggmagnet Apr 15, 2024
ca4481e
Correct spelling of PostQuantumKeyNegotiator
acb-mv Apr 23, 2024
bb9f780
Add functionality to the actor
acb-mv Apr 24, 2024
0c57834
Move PQ negotiation to Actor which is now not an Actor
acb-mv Apr 24, 2024
24b58c4
Refactor PQ key receiving protocol
acb-mv Apr 24, 2024
7c7b5a9
Fix whitespace
acb-mv Apr 24, 2024
3c01a1b
Combine all negotiation-lifecycle data in one optional struct
acb-mv Apr 24, 2024
a917197
Remove obsolete code from PacketTunnelProvider
acb-mv Apr 24, 2024
fbfc80b
Do not schedule more than one read or writes at a time
buggmagnet Apr 26, 2024
909e53c
Set relay to current when opening PQ connection
acb-mv Apr 30, 2024
a781209
Remove the iOS custom TTCC crate in favour of the original one
buggmagnet Apr 29, 2024
d2ceeb1
Modify the TCP connection to be shutdown safe
pinkisemils May 3, 2024
77a28c0
Guarantee connecting to the same relay the PQ has been negotiated with
buggmagnet May 3, 2024
c90da37
Update the UI to indicate PQ state
acb-mv May 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ shadowsocks-service = { version = "1.16" }

windows-sys = "0.52.0"

chrono = { version = "0.4.26", default-features = false}
chrono = { version = "0.4.26", default-features = false }
clap = { version = "4.4.18", features = ["cargo", "derive"] }
once_cell = "1.13"

Expand Down
19 changes: 19 additions & 0 deletions ios/MullvadPostQuantum/MullvadPostQuantum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// MullvadPostQuantum.h
// MullvadPostQuantum
//
// Created by Marco Nikic on 2024-02-27.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

#import <Foundation/Foundation.h>

//! Project version number for MullvadPostQuantum.
FOUNDATION_EXPORT double MullvadPostQuantumVersionNumber;

//! Project version string for MullvadPostQuantum.
FOUNDATION_EXPORT const unsigned char MullvadPostQuantumVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <MullvadPostQuantum/PublicHeader.h>


78 changes: 78 additions & 0 deletions ios/MullvadPostQuantum/PacketTunnelProvider+TCPConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// PacketTunnelProvider+TCPConnection.swift
// PacketTunnel
//
// Created by Marco Nikic on 2024-02-15.
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadTypes
import NetworkExtension
import TalpidTunnelConfigClientProxy
import WireGuardKitTypes

@_cdecl("swift_nw_tcp_connection_send")
func tcpConnectionSend(
connection: UnsafeMutableRawPointer?,
data: UnsafeMutableRawPointer,
dataLength: UInt,
sender: UnsafeMutableRawPointer?
) {
guard let connection, let sender else { return }
let tcpConnection = Unmanaged<NWTCPConnection>.fromOpaque(connection).takeUnretainedValue()
let rawData = Data(bytes: data, count: Int(dataLength))

// The guarantee that no more than 2 writes happen in parallel is done by virtue of not returning the execution context
// to Rust before this closure is done executing.
tcpConnection.write(rawData, completionHandler: { maybeError in
if maybeError != nil {
handle_sent(0, sender)
} else {
handle_sent(dataLength, sender)
}
})
}

@_cdecl("swift_nw_tcp_connection_read")
func tcpConnectionReceive(
connection: UnsafeMutableRawPointer?,
sender: UnsafeMutableRawPointer?
) {
guard let connection, let sender else { return }
let tcpConnection = Unmanaged<NWTCPConnection>.fromOpaque(connection).takeUnretainedValue()
tcpConnection.readMinimumLength(0, maximumLength: Int(UInt16.max)) { data, maybeError in
if let data {
if maybeError != nil {
handle_recv(Data().map { $0 }, 0, sender)
} else {
handle_recv(data.map { $0 }, UInt(data.count), sender)
}
}
}
}

@_cdecl("swift_post_quantum_key_ready")
func receivePostQuantumKey(
rawPacketTunnel: UnsafeMutableRawPointer?,
rawPresharedKey: UnsafeMutableRawPointer?,
rawEphemeralKey: UnsafeMutableRawPointer?
) {
guard
let rawPacketTunnel,
let postQuantumKeyReceiver = Unmanaged<NEPacketTunnelProvider>.fromOpaque(rawPacketTunnel)
.takeUnretainedValue() as? PostQuantumKeyReceiving
else { return }

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()
return
}

postQuantumKeyReceiver.receivePostQuantumKey(key, ephemeralKey: ephemeralKey)
}
54 changes: 54 additions & 0 deletions ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// PostQuantumKeyNegotiator.swift
// PacketTunnel
//
// Created by Marco Nikic on 2024-02-16.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import NetworkExtension
import TalpidTunnelConfigClientProxy
import WireGuardKitTypes

public class PostQuantumKeyNegotiator {
public init() {}

var cancelToken: PostQuantumCancelToken?

public func negotiateKey(
gatewayIP: IPv4Address,
devicePublicKey: PublicKey,
presharedKey: PrivateKey,
packetTunnel: NEPacketTunnelProvider,
tcpConnection: NWTCPConnection
) {
let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque()
let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque()
var cancelToken = PostQuantumCancelToken()

// TODO: Any non 0 return is considered a failure, and should be handled gracefully
let result = negotiate_post_quantum_key(
devicePublicKey.rawValue.map { $0 },
presharedKey.rawValue.map { $0 },
packetTunnelPointer,
opaqueConnection,
&cancelToken
)
guard result == 0 else {
// Handle failure here
return
}
self.cancelToken = cancelToken
}

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

deinit {
guard var cancelToken else { return }
drop_post_quantum_key_exchange_token(&cancelToken)
}
}
5 changes: 5 additions & 0 deletions ios/MullvadPostQuantum/module.private.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
framework module TalpidTunnelConfigClientProxy {
header "talpid_tunnel_config_client.h"
link "libtalpid_tunnel_config_client"
export *
}
44 changes: 44 additions & 0 deletions ios/MullvadPostQuantum/talpid-tunnel-config-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[package]
name = "talpid-tunnel-config-client-proxy"
description = "Uses the relay RPC service to set up PQ-safe peers, etc."
authors.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true

[lints]
workspace = true

[dependencies]
talpid-tunnel-config-client = { path = "../../../talpid-tunnel-config-client" }
talpid-types = { path = "../../../talpid-types" }

log = { workspace = true }
tonic = { workspace = true }
tower = { workspace = true }
prost = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
classic-mceliece-rust = { version = "2.0.0", features = [
"mceliece460896f",
"zeroize",
] }
pqc_kyber = { version = "0.4.0", features = ["std", "kyber1024", "zeroize"] }
zeroize = "1.5.7"
libc = "0.2"

[lib]
crate-type = ["rlib", "staticlib"]
bench = false

[dev-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread"] }
[build-dependencies]
tonic-build = { workspace = true, default-features = false, features = [
"transport",
"prost",
] }
cbindgen = { version = "0.24.3", default-features = false }

[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
oslog = "0.2"
18 changes: 18 additions & 0 deletions ios/MullvadPostQuantum/talpid-tunnel-config-client/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
fn main() {
tonic_build::compile_protos("../../../talpid-tunnel-config-client/proto/ephemeralpeer.proto")
.unwrap();

match std::env::var("TARGET").unwrap().as_str() {
"aarch64-apple-ios" | "aarch64-apple-ios-sim" => {
println!("cargo:rerun-if-changed=include/talpid_tunnel_config_client.h");
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
cbindgen::Builder::new()
.with_crate(crate_dir)
.with_language(cbindgen::Language::C)
.generate()
.expect("failed to generate bindings")
.write_to_file("include/talpid_tunnel_config_client.h");
}
&_ => (),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

/**
* Port used by the tunnel config service.
*/
#define CONFIG_SERVICE_PORT 1337

typedef struct PostQuantumCancelToken {
void *context;
} PostQuantumCancelToken;

void cancel_post_quantum_key_exchange(const struct PostQuantumCancelToken *sender);

void drop_post_quantum_key_exchange_token(const struct PostQuantumCancelToken *sender);

/**
* Callback to call when the TCP connection has written data.
*/
void handle_sent(uintptr_t bytes_sent, const void *sender);

/**
* Callback to call when the TCP connection has received data.
*/
void handle_recv(const uint8_t *data, uintptr_t data_len, const void *sender);

/**
* Entry point for exchanging post quantum keys on iOS.
* The TCP connection must be created to go through the tunnel.
* # Safety
* This function is safe to call
*/
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);

/**
* Called when there is data to send on the TCP connection.
* The TCP connection must write data on the wire, then call the `handle_sent` function.
*/
extern void swift_nw_tcp_connection_send(const void *connection,
const void *data,
uintptr_t data_len,
const void *sender);

/**
* Called when there is data to read on the TCP connection.
* The TCP connection must read data from the wire, then call the `handle_read` function.
*/
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.
*/
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);
70 changes: 70 additions & 0 deletions ios/MullvadPostQuantum/talpid-tunnel-config-client/src/ios_ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use libc::c_void;
use tokio::sync::mpsc;

use crate::PostQuantumCancelToken;

use super::run_ios_runtime;

use std::sync::Once;
static INIT_LOGGING: Once = Once::new();

#[no_mangle]
pub unsafe extern "C" fn cancel_post_quantum_key_exchange(sender: *const PostQuantumCancelToken) {
let sender = unsafe { &*sender };
sender.cancel();
}

#[no_mangle]
pub unsafe extern "C" fn drop_post_quantum_key_exchange_token(
sender: *const PostQuantumCancelToken,
) {
let _sender = unsafe { std::ptr::read(sender) };
}

/// Callback to call when the TCP connection has written data.
#[no_mangle]
pub unsafe extern "C" fn handle_sent(bytes_sent: usize, sender: *const c_void) {
let send_tx: Box<mpsc::UnboundedSender<usize>> = unsafe { Box::from_raw(sender as _) };
_ = send_tx.send(bytes_sent);
}

/// Callback to call when the TCP connection has received data.
#[no_mangle]
pub unsafe extern "C" fn handle_recv(data: *const u8, data_len: usize, sender: *const c_void) {
let read_tx: Box<mpsc::UnboundedSender<Box<[u8]>>> = unsafe { Box::from_raw(sender as _) };
let mut bytes = vec![0u8; data_len];
if !data.is_null() {
std::ptr::copy_nonoverlapping(data, bytes.as_mut_ptr(), data_len);
}
_ = read_tx.send(bytes.into_boxed_slice());
}

/// Entry point for exchanging post quantum keys on iOS.
/// The TCP connection must be created to go through the tunnel.
/// # Safety
/// This function is safe to call
#[no_mangle]
pub unsafe extern "C" fn negotiate_post_quantum_key(
public_key: *const u8,
ephemeral_key: *const u8,
packet_tunnel: *const c_void,
tcp_connection: *const c_void,
cancel_token: *mut PostQuantumCancelToken,
) -> i32 {
INIT_LOGGING.call_once(|| {
let _ = oslog::OsLogger::new("net.mullvad.MullvadVPN.TTCC")
.level_filter(log::LevelFilter::Trace)
.init();
});

let pub_key_copy: [u8; 32] = unsafe { std::ptr::read(public_key as *const [u8; 32]) };
let eph_key_copy: [u8; 32] = unsafe { std::ptr::read(ephemeral_key as *const [u8; 32]) };

match unsafe { run_ios_runtime(pub_key_copy, eph_key_copy, packet_tunnel, tcp_connection) } {
Ok(token) => {
unsafe { std::ptr::write(cancel_token, token) };
0
}
Err(err) => err,
}
}
Loading
Loading