feat(qr-login): Include some additional authenticated data in the HPKE channel

This commit is contained in:
Damir Jelić
2026-02-23 15:56:28 +01:00
parent 0ef9dce247
commit b3d3fc46fd
4 changed files with 70 additions and 32 deletions

View File

@@ -140,4 +140,28 @@ impl RendezvousChannel {
RendezvousChannel::Msc4388(channel) => Ok(channel.receive().await?),
}
}
/// Get additional authenticated data which should be used by the crypto
/// channel to bind individual messages to this specific rendezvous
/// channel run.
///
/// This is only used in MSC4388, as such only the HPKE crypto channel will
/// use this.
pub(super) fn additional_authenticated_data(&self) -> Option<Vec<u8>> {
match self {
RendezvousChannel::Msc4108(_) => None,
RendezvousChannel::Msc4388(channel) => {
let msc_4388::Channel { base_url, rendezvous_id, sequence_token, .. } = channel;
Some(
[
base_url.as_str().as_bytes(),
rendezvous_id.as_bytes(),
sequence_token.as_bytes(),
]
.concat(),
)
}
}
}
}

View File

@@ -52,9 +52,11 @@ struct RendezvousMessage {
pub(crate) struct Channel {
client: HttpClient,
base_url: Url,
rendezvous_id: String,
sequence_token: String,
pub(super) base_url: Url,
/// The ID of the rendezvous session we're using to exchange messages
/// through the channel.
pub(super) rendezvous_id: String,
pub(super) sequence_token: String,
}
fn response_to_error(status: StatusCode, data: String) -> HttpError {

View File

@@ -67,6 +67,7 @@ impl CryptoChannel {
pub(super) fn establish_inbound_channel(
self,
message: &str,
aad: &[u8],
) -> Result<CryptoChannelCreationResult, Error> {
match self {
CryptoChannel::Ecies(ecies) => {
@@ -79,7 +80,7 @@ impl CryptoChannel {
let message =
hpke::InitialMessage::decode(message).map_err(MessageDecodeError::from)?;
Ok(CryptoChannelCreationResult::Hpke(
hpke.establish_channel(&message, &[]).map_err(DecryptionError::from)?,
hpke.establish_channel(&message, aad).map_err(DecryptionError::from)?,
))
}
}

View File

@@ -70,7 +70,7 @@ impl SecureChannel {
(crypto_channel, qr_code_data)
}
RendezvousInfo::Msc4388 { rendezvous_id } => {
RendezvousInfo::Msc4388 { rendezvous_id, .. } => {
let crypto_channel = CryptoChannel::new_hpke();
let qr_code_data = QrCodeData::new_msc4388(
@@ -106,7 +106,7 @@ impl SecureChannel {
mode_data,
);
}
RendezvousInfo::Msc4388 { rendezvous_id } => {
RendezvousInfo::Msc4388 { rendezvous_id, .. } => {
channel.qr_code_data = QrCodeData::new_msc4388(
channel.crypto_channel.public_key(),
rendezvous_id.to_owned(),
@@ -127,8 +127,9 @@ impl SecureChannel {
pub(super) async fn connect(mut self) -> Result<AlmostEstablishedSecureChannel, Error> {
trace!("Trying to connect the secure channel.");
let aad = self.channel.additional_authenticated_data().unwrap_or_default();
let message = self.channel.receive().await?;
let result = self.crypto_channel.establish_inbound_channel(&message)?;
let result = self.crypto_channel.establish_inbound_channel(&message, &aad)?;
let message = std::str::from_utf8(result.plaintext()).map_err(MessageDecodeError::from)?;
@@ -148,8 +149,10 @@ impl SecureChannel {
secure_channel
}
CryptoChannelCreationResult::Hpke(RecipientCreationResult { channel, .. }) => {
let aad = self.channel.additional_authenticated_data().unwrap_or_default();
let BidirectionalCreationResult { channel, message } =
channel.establish_bidirectional_channel(LOGIN_OK_MESSAGE.as_bytes(), &[]);
channel.establish_bidirectional_channel(LOGIN_OK_MESSAGE.as_bytes(), &aad);
self.channel.send(message.encode()).await?;
let crypto_channel = EstablishedCryptoChannel::Hpke(channel);
@@ -213,7 +216,25 @@ impl EstablishedSecureChannel {
let client = HttpClient::new(client, RequestConfig::short_retry());
// Let's establish an outbound ECIES channel, the other side won't know that
// The other side has crated a rendezvous channel, we're going to connect to it
// and send this initial encrypted message through it. The initial message on
// the rendezvous channel will have an empty body, so we can just
// drop it.
let mut channel = match qr_code_data.intent_data() {
QrCodeIntentData::Msc4108 { rendezvous_url, .. } => {
let InboundChannelCreationResult { channel, .. } =
RendezvousChannel::create_inbound(client, rendezvous_url).await?;
channel
}
QrCodeIntentData::Msc4388 { rendezvous_id, base_url } => {
let InboundChannelCreationResult { channel, .. } =
RendezvousChannel::create_inbound_msc4388(client, base_url, rendezvous_id)
.await?;
channel
}
};
// Let's establish an outbound crypto channel, the other side won't know that
// 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`.
@@ -230,35 +251,18 @@ impl EstablishedSecureChannel {
(ChannelType::Ecies(ecies), message.encode())
}
QrCodeIntentData::Msc4388 { .. } => {
let aad = channel.additional_authenticated_data().unwrap_or_default();
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?
&[],
&aad,
);
(ChannelType::Hpke(channel), message.encode())
}
};
// The other side has crated a rendezvous channel, we're going to connect to it
// and send this initial encrypted message through it. The initial message on
// the rendezvous channel will have an empty body, so we can just
// drop it.
let mut channel = match qr_code_data.intent_data() {
QrCodeIntentData::Msc4108 { rendezvous_url, .. } => {
let InboundChannelCreationResult { channel, .. } =
RendezvousChannel::create_inbound(client, rendezvous_url).await?;
channel
}
QrCodeIntentData::Msc4388 { rendezvous_id, base_url } => {
let InboundChannelCreationResult { channel, .. } =
RendezvousChannel::create_inbound_msc4388(client, base_url, rendezvous_id)
.await?;
channel
}
};
trace!(
"Received the initial message from the rendezvous channel, sending the LOGIN \
INITIATE message"
@@ -281,13 +285,14 @@ impl EstablishedSecureChannel {
(response, channel)
}
ChannelType::Hpke(crypto_channel) => {
let aad = channel.additional_authenticated_data().unwrap_or_default();
let response = channel.receive().await?;
let response =
InitialResponse::decode(&response).map_err(MessageDecodeError::from)?;
let BidirectionalCreationResult { channel: crypto_channel, message } =
crypto_channel
.establish_bidirectional_channel(&response, &[])
.establish_bidirectional_channel(&response, &aad)
.map_err(DecryptionError::from)?;
let response = String::from_utf8(message)
.map_err(|e| MessageDecodeError::from(e.utf8_error()))?;
@@ -339,13 +344,19 @@ impl EstablishedSecureChannel {
}
async fn send(&mut self, message: &str) -> Result<(), Error> {
let message = self.crypto_channel.seal(message, &[]);
let aad = self.channel.additional_authenticated_data().unwrap_or_default();
let message = self.crypto_channel.seal(message, &aad);
Ok(self.channel.send(message).await?)
}
async fn receive(&mut self) -> Result<String, Error> {
let aad = self.channel.additional_authenticated_data().unwrap_or_default();
let message = self.channel.receive().await?;
self.crypto_channel.open(&message, &[])
let decrypted = self.crypto_channel.open(&message, &aad)?;
Ok(decrypted)
}
}