summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <faern@faern.net>2022-09-20 16:10:36 +0200
committerLinus Färnstrand <linus@mullvad.net>2022-10-03 09:00:30 +0200
commit697dde0b2e3dc1aa1025284f783a4038f1d87f7c (patch)
treef73afbf4adc67dc5e9b3750b16e927343d5c23c1
parent2c7ff8afd020dd62d80082158687a10bc8142f75 (diff)
downloadmullvadvpn-697dde0b2e3dc1aa1025284f783a4038f1d87f7c.tar.xz
mullvadvpn-697dde0b2e3dc1aa1025284f783a4038f1d87f7c.zip
Implement PskExchangeExperimentalV1 client side
-rw-r--r--talpid-tunnel-config-client/Cargo.toml2
-rw-r--r--talpid-tunnel-config-client/src/classic_mceliece.rs (renamed from talpid-tunnel-config-client/src/kem.rs)8
-rw-r--r--talpid-tunnel-config-client/src/lib.rs66
3 files changed, 53 insertions, 23 deletions
diff --git a/talpid-tunnel-config-client/Cargo.toml b/talpid-tunnel-config-client/Cargo.toml
index 62d6ab376b..d99e5eb13e 100644
--- a/talpid-tunnel-config-client/Cargo.toml
+++ b/talpid-tunnel-config-client/Cargo.toml
@@ -15,7 +15,7 @@ tonic = "0.8"
prost = "0.11"
tower = "0.4"
tokio = "1"
-classic-mceliece-rust = { version = "2.0.0", features = ["mceliece8192128f"] }
+classic-mceliece-rust = { version = "2.0.0", features = ["mceliece460896f"] }
[dev-dependencies]
tokio = { version = "1", features = ["rt-multi-thread"] }
diff --git a/talpid-tunnel-config-client/src/kem.rs b/talpid-tunnel-config-client/src/classic_mceliece.rs
index 778c66a0aa..ae373b1eee 100644
--- a/talpid-tunnel-config-client/src/kem.rs
+++ b/talpid-tunnel-config-client/src/classic_mceliece.rs
@@ -1,5 +1,4 @@
-use classic_mceliece_rust::keypair_boxed;
-use talpid_types::net::wireguard::PresharedKey;
+use classic_mceliece_rust::{keypair_boxed, SharedSecret};
const STACK_SIZE: usize = 2 * 1024 * 1024;
@@ -19,7 +18,6 @@ pub async fn generate_keys() -> (PublicKey<'static>, SecretKey<'static>) {
rx.await.unwrap()
}
-pub fn decapsulate(secret: &SecretKey, ciphertext: &Ciphertext) -> PresharedKey {
- let shared_secret = classic_mceliece_rust::decapsulate_boxed(ciphertext, secret);
- PresharedKey::from(*shared_secret.as_array())
+pub fn decapsulate(secret: &SecretKey, ciphertext: &Ciphertext) -> SharedSecret<'static> {
+ classic_mceliece_rust::decapsulate_boxed(ciphertext, secret)
}
diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs
index 4dce3edf67..e3080741f9 100644
--- a/talpid-tunnel-config-client/src/lib.rs
+++ b/talpid-tunnel-config-client/src/lib.rs
@@ -2,7 +2,7 @@ use std::{fmt, net::IpAddr};
use talpid_types::net::wireguard::{PresharedKey, PrivateKey, PublicKey};
use tonic::transport::Channel;
-mod kem;
+mod classic_mceliece;
#[allow(clippy::derive_partial_eq_without_eq)]
mod proto {
@@ -13,7 +13,8 @@ mod proto {
pub enum Error {
GrpcConnectError(tonic::transport::Error),
GrpcError(tonic::Status),
- InvalidCiphertext,
+ InvalidCiphertextLength { actual: usize, expected: usize },
+ InvalidCiphertextCount { actual: usize },
}
impl std::fmt::Display for Error {
@@ -22,7 +23,13 @@ impl std::fmt::Display for Error {
match self {
GrpcConnectError(_) => "Failed to connect to config service".fmt(f),
GrpcError(status) => write!(f, "RPC failed: {}", status),
- InvalidCiphertext => "The service returned an invalid ciphertext".fmt(f),
+ InvalidCiphertextLength { actual, expected } => write!(
+ f,
+ "Expected a ciphertext of length {expected}, got {actual} bytes"
+ ),
+ InvalidCiphertextCount { actual } => {
+ write!(f, "Expected 1 ciphertext in the response, got {actual}")
+ }
}
}
}
@@ -41,7 +48,9 @@ type RelayConfigService = proto::post_quantum_secure_client::PostQuantumSecureCl
/// Port used by the tunnel config service.
pub const CONFIG_SERVICE_PORT: u16 = 1337;
-const ALGORITHM_NAME: &str = "Classic-McEliece-8192128f";
+/// Use the smallest CME variant with NIST security level 3. This variant has significantly smaller
+/// keys than the larger variants, and is considered safe.
+const CLASSIC_MCELIECE_VARIANT: &str = "Classic-McEliece-460896f";
/// Generates a new WireGuard key pair and negotiates a PSK with the relay in a PQ-safe
/// manner. This creates a peer on the relay with the new WireGuard pubkey and PSK,
@@ -52,28 +61,51 @@ pub async fn push_pq_key(
wg_pubkey: PublicKey,
) -> Result<(PrivateKey, PresharedKey), Error> {
let wg_psk_privkey = PrivateKey::new_from_random();
- let (kem_pubkey, kem_secret) = kem::generate_keys().await;
+ let (cme_kem_pubkey, cme_kem_secret) = classic_mceliece::generate_keys().await;
let mut client = new_client(service_address).await?;
let response = client
- .psk_exchange_experimental_v0(proto::PskRequestExperimentalV0 {
+ .psk_exchange_experimental_v1(proto::PskRequestExperimentalV1 {
wg_pubkey: wg_pubkey.as_bytes().to_vec(),
wg_psk_pubkey: wg_psk_privkey.public_key().as_bytes().to_vec(),
- kem_pubkey: Some(proto::KemPubkeyExperimentalV0 {
- algorithm_name: ALGORITHM_NAME.to_string(),
- key_data: kem_pubkey.as_array().to_vec(),
- }),
+ kem_pubkeys: vec![proto::KemPubkeyExperimentalV1 {
+ algorithm_name: CLASSIC_MCELIECE_VARIANT.to_owned(),
+ key_data: cme_kem_pubkey.as_array().to_vec(),
+ }],
})
.await
.map_err(Error::GrpcError)?;
- let ciphertext_array: [u8; kem::CRYPTO_CIPHERTEXTBYTES] = response
- .into_inner()
- .ciphertext
- .try_into()
- .map_err(|_| Error::InvalidCiphertext)?;
- let ciphertext = kem::Ciphertext::from(ciphertext_array);
- Ok((wg_psk_privkey, kem::decapsulate(&kem_secret, &ciphertext)))
+ let ciphertexts = response.into_inner().ciphertexts;
+ // Unpack the ciphertexts into one per KEM without needing to access them by index.
+ let [cme_ciphertext] = <&[Vec<u8>; 1]>::try_from(ciphertexts.as_slice()).map_err(|_| {
+ Error::InvalidCiphertextCount {
+ actual: ciphertexts.len(),
+ }
+ })?;
+
+ let mut psk_data = [0u8; 32];
+ // Decapsulate Classic McEliece and mix into PSK
+ {
+ let ciphertext_array =
+ <[u8; classic_mceliece::CRYPTO_CIPHERTEXTBYTES]>::try_from(cme_ciphertext.as_slice())
+ .map_err(|_| Error::InvalidCiphertextLength {
+ actual: cme_ciphertext.len(),
+ expected: classic_mceliece::CRYPTO_CIPHERTEXTBYTES,
+ })?;
+ let ciphertext = classic_mceliece::Ciphertext::from(ciphertext_array);
+ let shared_secret = classic_mceliece::decapsulate(&cme_kem_secret, &ciphertext);
+ xor_assign(&mut psk_data, shared_secret.as_array());
+ }
+
+ Ok((wg_psk_privkey, PresharedKey::from(psk_data)))
+}
+
+/// Performs `dst = dst ^ src`.
+fn xor_assign(dst: &mut [u8; 32], src: &[u8; 32]) {
+ for (dst_byte, src_byte) in dst.iter_mut().zip(src.iter()) {
+ *dst_byte ^= src_byte;
+ }
}
async fn new_client(addr: IpAddr) -> Result<RelayConfigService, Error> {