From 146fc6f40d0c1a18c80dea717b42e4eafe0208b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 5 Dec 2025 13:09:14 +0100 Subject: [PATCH] feat(qr-login): Support HPKE for the cryptographic channel --- .../qrcode/secure_channel/crypto_channel.rs | 32 ++++++++++- .../oauth/qrcode/secure_channel/mod.rs | 55 ++++++++++++++++--- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/crypto_channel.rs b/crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/crypto_channel.rs index ba8c32d2c..ebec5bed0 100644 --- a/crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/crypto_channel.rs +++ b/crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/crypto_channel.rs @@ -31,6 +31,7 @@ use vodozemac::{ Curve25519PublicKey, ecies::{CheckCode, Ecies, EstablishedEcies, InboundCreationResult, InitialMessage, Message}, + hpke::{self, EstablishedHpkeChannel, HpkeRecipientChannel, RecipientCreationResult}, }; use crate::authentication::oauth::qrcode::SecureChannelError as Error; @@ -38,6 +39,7 @@ use crate::authentication::oauth::qrcode::SecureChannelError as Error; /// A cryptographic communication channel. pub(super) enum CryptoChannel { Ecies(Ecies), + Hpke(HpkeRecipientChannel), } impl CryptoChannel { @@ -46,10 +48,16 @@ impl CryptoChannel { CryptoChannel::Ecies(Ecies::new()) } + /// Create a new HPKE-based [`CryptoChannel`]. + pub(super) fn new_hpke() -> Self { + CryptoChannel::Hpke(HpkeRecipientChannel::new()) + } + /// Get the [`Curve25519PublicKey`] of this cryptographic channel. pub(super) fn public_key(&self) -> Curve25519PublicKey { match self { CryptoChannel::Ecies(ecies) => ecies.public_key(), + CryptoChannel::Hpke(hpke) => hpke.public_key(), } } @@ -63,12 +71,19 @@ impl CryptoChannel { let message = InitialMessage::decode(message)?; Ok(CryptoChannelCreationResult::Ecies(ecies.establish_inbound_channel(&message)?)) } + CryptoChannel::Hpke(hpke) => { + let message = hpke::InitialMessage::decode(message).unwrap(); + Ok(CryptoChannelCreationResult::Hpke( + hpke.establish_channel(&message, &[]).unwrap(), + )) + } } } } pub(super) enum CryptoChannelCreationResult { Ecies(InboundCreationResult), + Hpke(RecipientCreationResult), } impl CryptoChannelCreationResult { @@ -78,6 +93,7 @@ impl CryptoChannelCreationResult { CryptoChannelCreationResult::Ecies(inbound_creation_result) => { &inbound_creation_result.message } + CryptoChannelCreationResult::Hpke(result) => &result.message, } } } @@ -88,6 +104,7 @@ impl CryptoChannelCreationResult { /// cryptographic messages. pub(super) enum EstablishedCryptoChannel { Ecies(EstablishedEcies), + Hpke(EstablishedHpkeChannel), } impl EstablishedCryptoChannel { @@ -95,26 +112,37 @@ impl EstablishedCryptoChannel { pub(super) fn check_code(&self) -> &CheckCode { match self { EstablishedCryptoChannel::Ecies(established_ecies) => established_ecies.check_code(), + EstablishedCryptoChannel::Hpke(established_hpke_channel) => { + established_hpke_channel.check_code() + } } } /// Seal the given plaintext using this [`EstablishedCryptoChannel`]. - pub(super) fn seal(&mut self, plaintext: &str) -> String { + pub(super) fn seal(&mut self, plaintext: &str, aad: &[u8]) -> String { match self { EstablishedCryptoChannel::Ecies(channel) => { let message = channel.encrypt(plaintext.as_bytes()); message.encode() } + EstablishedCryptoChannel::Hpke(channel) => { + let message = channel.seal(plaintext.as_bytes(), aad); + message.encode() + } } } /// Open the given sealed message using this [`EstablishedCryptoChannel`]. - pub(super) fn open(&mut self, message: &str) -> Result { + pub(super) fn open(&mut self, message: &str, aad: &[u8]) -> Result { let plaintext = match self { EstablishedCryptoChannel::Ecies(channel) => { let message = Message::decode(message)?; channel.decrypt(&message)? } + EstablishedCryptoChannel::Hpke(channel) => { + let message = hpke::Message::decode(message).unwrap(); + channel.open(&message, aad).unwrap() + } }; Ok(String::from_utf8(plaintext).map_err(|e| e.utf8_error())?) diff --git a/crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/mod.rs b/crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/mod.rs index eabef669d..a700dccb6 100644 --- a/crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/mod.rs +++ b/crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/mod.rs @@ -19,8 +19,12 @@ use matrix_sdk_base::crypto::types::qr_login::{ use serde::{Serialize, de::DeserializeOwned}; use tracing::{instrument, trace}; use url::Url; -use vodozemac::ecies::{ - CheckCode, DigitMode, Ecies, EstablishedEcies, InboundCreationResult, OutboundCreationResult, +use vodozemac::{ + ecies::{CheckCode, Ecies, EstablishedEcies, InboundCreationResult, OutboundCreationResult}, + hpke::{ + BidirectionalCreationResult, DigitMode, HpkeSenderChannel, InitialResponse, + RecipientCreationResult, SenderCreationResult, UnidirectionalSenderChannel, + }, }; use super::{ @@ -116,6 +120,15 @@ impl SecureChannel { secure_channel.send(LOGIN_OK_MESSAGE).await?; secure_channel } + CryptoChannelCreationResult::Hpke(RecipientCreationResult { channel, .. }) => { + let BidirectionalCreationResult { channel, message } = + channel.establish_bidirectional_channel(LOGIN_OK_MESSAGE.as_bytes(), &[]); + self.channel.send(message.encode()).await?; + + let crypto_channel = EstablishedCryptoChannel::Hpke(channel); + + EstablishedSecureChannel { channel: self.channel, crypto_channel } + } }; Ok(AlmostEstablishedSecureChannel { secure_channel }) @@ -163,6 +176,7 @@ impl EstablishedSecureChannel { ) -> Result { enum ChannelType { Ecies(EstablishedEcies), + Hpke(UnidirectionalSenderChannel), } if qr_code_data.intent() == expected_mode { @@ -176,7 +190,7 @@ impl EstablishedSecureChannel { // it's talking to us, the device that scanned the QR code, until it // receives and successfully decrypts the initial message. We're here encrypting // the `LOGIN_INITIATE_MESSAGE`. - let (crypto_channel, encoded_message) = { + let (crypto_channel, encoded_message) = if true { let ecies = Ecies::new(); let OutboundCreationResult { ecies, message } = ecies.establish_outbound_channel( @@ -184,6 +198,15 @@ impl EstablishedSecureChannel { LOGIN_INITIATE_MESSAGE.as_bytes(), )?; (ChannelType::Ecies(ecies), message.encode()) + } else { + let SenderCreationResult { channel, message } = HpkeSenderChannel::new() + .establish_channel( + qr_code_data.public_key(), + LOGIN_INITIATE_MESSAGE.as_bytes(), + // TODO: Do we want to include some additional authenticated data here? + &[], + ); + (ChannelType::Hpke(channel), message.encode()) }; // The other side has crated a rendezvous channel, we're going to connect to it @@ -213,15 +236,32 @@ impl EstablishedSecureChannel { trace!("Waiting for the LOGIN OK message"); let (response, channel) = match crypto_channel { - ChannelType::Ecies(ecies) => { + ChannelType::Ecies(crypto_channel) => { // We can create our EstablishedSecureChannel struct now and use the // convenient helpers which transparently decrypt on receival. - let crypto_channel = EstablishedCryptoChannel::Ecies(ecies); + let crypto_channel = EstablishedCryptoChannel::Ecies(crypto_channel); let mut channel = Self { channel, crypto_channel }; let response = channel.receive().await?; (response, channel) } + ChannelType::Hpke(crypto_channel) => { + let response = channel.receive().await?; + let response = InitialResponse::decode(&response).unwrap(); + + let BidirectionalCreationResult { channel: crypto_channel, message } = + crypto_channel.establish_bidirectional_channel(&response, &[]).unwrap(); + let response = String::from_utf8(message).unwrap(); + let crypto_channel = EstablishedCryptoChannel::Hpke(crypto_channel); + + // We can create our EstablishedSecureChannel struct now and use the + // convenient helpers which transparently decrypt on receival. + let channel = Self { channel, crypto_channel }; + + // We can create our EstablishedSecureChannel struct now and use the + // convenient helpers which transparently decrypt on receival. + (response, channel) + } }; trace!("Received the LOGIN OK message, maybe."); @@ -260,14 +300,13 @@ impl EstablishedSecureChannel { } async fn send(&mut self, message: &str) -> Result<(), Error> { - let message = self.crypto_channel.seal(message); - + let message = self.crypto_channel.seal(message, &[]); Ok(self.channel.send(message).await?) } async fn receive(&mut self) -> Result { let message = self.channel.receive().await?; - self.crypto_channel.open(&message) + self.crypto_channel.open(&message, &[]) } }