Skip to content

Commit

Permalink
Merge branch 'daita-v2'
Browse files Browse the repository at this point in the history
  • Loading branch information
dlon committed Dec 12, 2024
2 parents b1a7b1e + 39a61f7 commit 0fff38c
Show file tree
Hide file tree
Showing 18 changed files with 170 additions and 111 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/android-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,6 @@ jobs:
name: relay-list
path: android/app/build/extraAssets

- name: Copy maybenot machines to asset directory
run: cp dist-assets/maybenot_machines android/app/build/extraAssets/

- name: Build app
uses: burrunan/gradle-cache-action@v1
with:
Expand Down
10 changes: 0 additions & 10 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ plugins {
val repoRootPath = rootProject.projectDir.absoluteFile.parentFile.absolutePath
val extraAssetsDirectory = layout.buildDirectory.dir("extraAssets").get()
val relayListPath = extraAssetsDirectory.file("relays.json").asFile
val maybenotMachinesFile = extraAssetsDirectory.file("maybenot_machines").asFile
val defaultChangelogAssetsDirectory = "$repoRootPath/android/src/main/play/release-notes/"
val extraJniDirectory = layout.buildDirectory.dir("extraJni").get()

Expand Down Expand Up @@ -245,7 +244,6 @@ android {
// Ensure all relevant assemble tasks depend on our ensure tasks.
tasks["assemble$capitalizedVariantName"].apply {
dependsOn(tasks["ensureRelayListExist"])
dependsOn(tasks["ensureMaybenotMachinesExist"])
dependsOn(tasks["ensureJniDirectoryExist"])
dependsOn(tasks["ensureValidVersionCode"])
}
Expand Down Expand Up @@ -288,14 +286,6 @@ tasks.register("ensureRelayListExist") {
}
}

tasks.register("ensureMaybenotMachinesExist") {
doLast {
if (!maybenotMachinesFile.exists()) {
throw GradleException("Missing maybenot machines: $maybenotMachinesFile")
}
}
}

tasks.register("ensureJniDirectoryExist") {
doLast {
if (!extraJniDirectory.asFile.exists()) {
Expand Down
1 change: 0 additions & 1 deletion android/scripts/update-lockfile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ GRADLE_TASKS=(
EXCLUDED_GRADLE_TASKS=(
"-xensureRelayListExist"
"-xensureJniDirectoryExist"
"-xensureMaybenotMachinesExist"
)

export GRADLE_OPTS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import org.koin.android.ext.android.getKoin
import org.koin.core.context.loadKoinModules

private const val RELAY_LIST_ASSET_NAME = "relays.json"
private const val MAYBENOT_MACHINES_ASSET_NAME = "maybenot_machines"

class MullvadVpnService : TalpidVpnService() {

Expand Down Expand Up @@ -224,7 +223,6 @@ class MullvadVpnService : TalpidVpnService() {

private fun Context.prepareFiles() {
extractAndOverwriteIfAssetMoreRecent(RELAY_LIST_ASSET_NAME)
extractAndOverwriteIfAssetMoreRecent(MAYBENOT_MACHINES_ASSET_NAME)
}

companion object {
Expand Down
3 changes: 0 additions & 3 deletions build-apk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,6 @@ done
echo "Updating relays.json..."
cargo run --bin relay_list "${CARGO_ARGS[@]}" > android/app/build/extraAssets/relays.json

echo "Copying maybenot machines..."
cp dist-assets/maybenot_machines android/app/build/extraAssets/

cd "$SCRIPT_DIR/android"
$GRADLE_CMD --console plain "${GRADLE_TASKS[@]}"

Expand Down
2 changes: 0 additions & 2 deletions desktop/packages/mullvad-vpn/tasks/distribution.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ function newConfig() {
{ from: distAssets('uninstall_macos.sh'), to: './uninstall.sh' },
{ from: buildAssets('shell-completions/_mullvad'), to: '.' },
{ from: buildAssets('shell-completions/mullvad.fish'), to: '.' },
{ from: distAssets('maybenot_machines'), to: '.' },
],
},

Expand Down Expand Up @@ -208,7 +207,6 @@ function newConfig() {
{ from: distAssets(path.join('linux', 'apparmor_mullvad')), to: '.' },
{ from: distAssets(path.join('binaries', '${env.TARGET_TRIPLE}', 'openvpn')), to: '.' },
{ from: distAssets(path.join('binaries', '${env.TARGET_TRIPLE}', 'apisocks5')), to: '.' },
{ from: distAssets('maybenot_machines'), to: '.' },
],
},

Expand Down
5 changes: 4 additions & 1 deletion talpid-tunnel-config-client/examples/tuncfg-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ impl EphemeralPeer for EphemeralPeerImpl {
None
};

Ok(Response::new(EphemeralPeerResponseV1 { post_quantum }))
Ok(Response::new(EphemeralPeerResponseV1 {
post_quantum,
daita: None,
}))
}
}

Expand Down
38 changes: 38 additions & 0 deletions talpid-tunnel-config-client/proto/ephemeralpeer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ message EphemeralPeerRequestV1 {
bytes wg_ephemeral_peer_pubkey = 2;
PostQuantumRequestV1 post_quantum = 3;
DaitaRequestV1 daita = 4;
DaitaRequestV2 daita_v2 = 5;
}

// The v1 request supports these three algorithms.
Expand All @@ -58,6 +59,41 @@ message KemPubkeyV1 {

message DaitaRequestV1 { bool activate_daita = 1; }

enum DaitaPlatform {
undefined = 0;
windows_native = 1;
linux_wg_go = 2;
macos_wg_go = 3;
ios_wg_go = 4;
android_wg_go = 5;
}

enum DaitaLevel {
level_default = 0;
level_1 = 1;
level_2 = 2;
level_3 = 3;
level_4 = 4;
level_5 = 5;
level_6 = 6;
level_7 = 7;
level_8 = 8;
level_9 = 9;
level_10 = 10;
}

message DaitaRequestV2 {
uint32 version = 1;
DaitaPlatform platform = 2;
DaitaLevel level = 3;
}

message DaitaResponseV2 {
repeated string client_machines = 1;
double max_padding_frac = 2;
double max_blocking_frac = 3;
}

message EphemeralPeerResponseV1 {
// The response from the VPN server contains:
// * `ciphertexts` - A list of the ciphertexts (the encapsulated shared secrets) for all
Expand All @@ -80,6 +116,8 @@ message EphemeralPeerResponseV1 {
// is known. Both B *and* C must be known to compute any bit in A. This means all involved
// KEM algorithms must be broken before the PSK can be computed by an attacker.
PostQuantumResponseV1 post_quantum = 1;

DaitaResponseV2 daita = 2;
}

message PostQuantumResponseV1 { repeated bytes ciphertexts = 1; }
64 changes: 62 additions & 2 deletions talpid-tunnel-config-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ mod proto {
tonic::include_proto!("ephemeralpeer");
}

#[cfg(all(unix, not(target_os = "ios")))]
const DAITA_VERSION: u32 = 2;

#[derive(Debug)]
pub enum Error {
GrpcConnectError(tonic::transport::Error),
Expand All @@ -35,6 +38,7 @@ pub enum Error {
InvalidCiphertextCount {
actual: usize,
},
MissingDaitaResponse,
#[cfg(target_os = "ios")]
TcpConnectionExpired,
#[cfg(target_os = "ios")]
Expand All @@ -59,6 +63,7 @@ impl std::fmt::Display for Error {
InvalidCiphertextCount { actual } => {
write!(f, "Expected 2 ciphertext in the response, got {actual}")
}
MissingDaitaResponse => "Expected DAITA configuration in response".fmt(f),
#[cfg(target_os = "ios")]
TcpConnectionExpired => "TCP connection is already shut down".fmt(f),
#[cfg(target_os = "ios")]
Expand All @@ -83,6 +88,14 @@ pub const CONFIG_SERVICE_PORT: u16 = 1337;

pub struct EphemeralPeer {
pub psk: Option<PresharedKey>,
#[cfg(all(unix, not(target_os = "ios")))]
pub daita: Option<DaitaSettings>,
}

pub struct DaitaSettings {
pub client_machines: Vec<String>,
pub max_padding_frac: f64,
pub max_blocking_frac: f64,
}

/// Negotiate a short-lived peer with a PQ-safe PSK or with DAITA enabled.
Expand Down Expand Up @@ -125,16 +138,28 @@ pub async fn request_ephemeral_peer_with(
wg_parent_pubkey: parent_pubkey.as_bytes().to_vec(),
wg_ephemeral_peer_pubkey: ephemeral_pubkey.as_bytes().to_vec(),
post_quantum: pq_request,
#[cfg(any(windows, target_os = "ios"))]
daita: Some(proto::DaitaRequestV1 {
activate_daita: enable_daita,
}),
#[cfg(any(windows, target_os = "ios"))]
daita_v2: None,
#[cfg(all(unix, not(target_os = "ios")))]
daita: None,
#[cfg(all(unix, not(target_os = "ios")))]
daita_v2: enable_daita.then(|| proto::DaitaRequestV2 {
level: i32::from(proto::DaitaLevel::LevelDefault),
platform: i32::from(get_platform()),
version: DAITA_VERSION,
}),
})
.await
.map_err(Error::GrpcError)?;

let response = response.into_inner();

let psk = if let Some((cme_kem_secret, ml_kem_secret)) = kem_secrets {
let ciphertexts = response
.into_inner()
.post_quantum
.ok_or(Error::MissingCiphertexts)?
.ciphertexts;
Expand Down Expand Up @@ -176,7 +201,42 @@ pub async fn request_ephemeral_peer_with(
None
};

Ok(EphemeralPeer { psk })
#[cfg(all(unix, not(target_os = "ios")))]
{
let daita = response.daita.map(|daita| DaitaSettings {
client_machines: daita.client_machines,
max_padding_frac: daita.max_padding_frac,
max_blocking_frac: daita.max_blocking_frac,
});
if daita.is_none() && enable_daita {
return Err(Error::MissingDaitaResponse);
}
Ok(EphemeralPeer { psk, daita })
}

#[cfg(any(windows, target_os = "ios"))]
{
Ok(EphemeralPeer { psk })
}
}

#[cfg(all(unix, not(target_os = "ios")))]
const fn get_platform() -> proto::DaitaPlatform {
use proto::DaitaPlatform;
const PLATFORM: DaitaPlatform = if cfg!(target_os = "windows") {
DaitaPlatform::WindowsNative
} else if cfg!(target_os = "linux") {
DaitaPlatform::LinuxWgGo
} else if cfg!(target_os = "macos") {
DaitaPlatform::MacosWgGo
} else if cfg!(target_os = "android") {
DaitaPlatform::AndroidWgGo
} else if cfg!(target_os = "ios") {
DaitaPlatform::IosWgGo
} else {
panic!("This platform does not support DAITA V2")
};
PLATFORM
}

async fn post_quantum_secrets() -> (
Expand Down
5 changes: 4 additions & 1 deletion talpid-wireguard/src/connectivity/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ impl Tunnel for MockTunnel {
}

#[cfg(daita)]
fn start_daita(&mut self) -> std::result::Result<(), TunnelError> {
fn start_daita(
&mut self,
#[cfg(not(target_os = "windows"))] _: talpid_tunnel_config_client::DaitaSettings,
) -> std::result::Result<(), TunnelError> {
Ok(())
}
}
Expand Down
36 changes: 28 additions & 8 deletions talpid-wireguard/src/ephemeral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use std::{
use talpid_tunnel::tun_provider::TunProvider;

use ipnetwork::IpNetwork;
use talpid_types::net::wireguard::{PresharedKey, PrivateKey, PublicKey};
use talpid_tunnel_config_client::EphemeralPeer;
use talpid_types::net::wireguard::{PrivateKey, PublicKey};
use tokio::sync::Mutex as AsyncMutex;

const INITIAL_PSK_EXCHANGE_TIMEOUT: Duration = Duration::from_secs(8);
Expand Down Expand Up @@ -100,7 +101,7 @@ async fn config_ephemeral_peers_inner(
let close_obfs_sender = close_obfs_sender.clone();

let exit_should_have_daita = config.daita && !config.is_multihop();
let exit_psk = request_ephemeral_peer(
let exit_ephemeral_peer = request_ephemeral_peer(
retry_attempt,
config,
ephemeral_private_key.public_key(),
Expand All @@ -109,6 +110,9 @@ async fn config_ephemeral_peers_inner(
)
.await?;

#[cfg(not(target_os = "windows"))]
let mut daita = exit_ephemeral_peer.daita;

log::debug!("Retrieved ephemeral peer");

if config.is_multihop() {
Expand All @@ -130,8 +134,7 @@ async fn config_ephemeral_peers_inner(
&tun_provider,
)
.await?;

let entry_psk = request_ephemeral_peer(
let entry_ephemeral_peer = request_ephemeral_peer(
retry_attempt,
&entry_config,
ephemeral_private_key.public_key(),
Expand All @@ -141,10 +144,14 @@ async fn config_ephemeral_peers_inner(
.await?;
log::debug!("Successfully exchanged PSK with entry peer");

config.entry_peer.psk = entry_psk;
config.entry_peer.psk = entry_ephemeral_peer.psk;
#[cfg(not(target_os = "windows"))]
{
daita = entry_ephemeral_peer.daita;
}
}

config.exit_peer_mut().psk = exit_psk;
config.exit_peer_mut().psk = exit_ephemeral_peer.psk;
#[cfg(daita)]
if config.daita {
log::trace!("Enabling constant packet size for entry peer");
Expand All @@ -165,9 +172,22 @@ async fn config_ephemeral_peers_inner(

#[cfg(daita)]
if config.daita {
#[cfg(not(target_os = "windows"))]
let Some(daita) = daita
else {
unreachable!("missing DAITA settings");
};

// Start local DAITA machines
let mut tunnel = tunnel.lock().await;
if let Some(tunnel) = tunnel.as_mut() {
#[cfg(not(target_os = "windows"))]
tunnel
.start_daita(daita)
.map_err(Error::TunnelError)
.map_err(CloseMsg::SetupError)?;

#[cfg(target_os = "windows")]
tunnel
.start_daita()
.map_err(Error::TunnelError)
Expand Down Expand Up @@ -254,7 +274,7 @@ async fn request_ephemeral_peer(
wg_psk_pubkey: PublicKey,
enable_pq: bool,
enable_daita: bool,
) -> std::result::Result<Option<PresharedKey>, CloseMsg> {
) -> std::result::Result<EphemeralPeer, CloseMsg> {
log::debug!("Requesting ephemeral peer");

let timeout = std::cmp::min(
Expand All @@ -281,5 +301,5 @@ async fn request_ephemeral_peer(
.map_err(Error::EphemeralPeerNegotiationError)
.map_err(CloseMsg::SetupError)?;

Ok(ephemeral.psk)
Ok(ephemeral)
}
Loading

0 comments on commit 0fff38c

Please sign in to comment.