WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 2522a36

Browse files
committed
refactor(qr-login): Prepare the secure channel to be usable with HPKE
This patch abstracts away the cryptographic channel which is used in the SecureChannel implementation for the QR code login support. This will allow us to use HPKE alongside of ECIES since MSC4108 recently proposed the switch to HPKE.
1 parent f2ba338 commit 2522a36

File tree

2 files changed

+177
-35
lines changed

2 files changed

+177
-35
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2025 The Matrix.org Foundation C.I.C.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! Module implementing the cryptographic part of a [`SecureChannel`].
16+
//!
17+
//! This implements an abstraction over the secure channel provided by
18+
//! vodozemac. As [MSC4108] evolved, the underlying cryptographic primitives
19+
//! have changed from ECIES to [HPKE]. Since QR code login has shipped in some
20+
//! clients before the MSC got approved and merged into the spec, we're in the
21+
//! unlucky position of having to support both cryptographic channels for a
22+
//! while.
23+
//!
24+
//! This module allows this backwards compatibility and adds a bit of
25+
//! cryptographic agility for the time when we will have to support post-quanum
26+
//! safe HPKE variants.
27+
//!
28+
//! [HPKE]: https://www.rfc-editor.org/rfc/rfc9180.html
29+
//! [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
30+
31+
use vodozemac::{
32+
Curve25519PublicKey,
33+
ecies::{CheckCode, Ecies, EstablishedEcies, InboundCreationResult, InitialMessage, Message},
34+
};
35+
36+
use crate::authentication::oauth::qrcode::SecureChannelError as Error;
37+
38+
/// A cryptograhpic communication channel.
39+
pub(super) enum CryptoChannel {
40+
Ecies(Ecies),
41+
}
42+
43+
impl CryptoChannel {
44+
/// Create a new ECIES-based [`CryptoChannel`].
45+
pub(super) fn new_ecies() -> Self {
46+
CryptoChannel::Ecies(Ecies::new())
47+
}
48+
49+
/// Get the [`Curve25519PublicKey`] of this cryptographic channel.
50+
pub(super) fn public_key(&self) -> Curve25519PublicKey {
51+
match self {
52+
CryptoChannel::Ecies(ecies) => ecies.public_key(),
53+
}
54+
}
55+
56+
/// Establish a cryptographic channel by unsealing an initial message.
57+
pub(super) fn establish_inbound_channel(
58+
self,
59+
message: &[u8],
60+
) -> Result<CryptoChannelCreationResult, Error> {
61+
let message = std::str::from_utf8(message)?;
62+
63+
match self {
64+
CryptoChannel::Ecies(ecies) => {
65+
let message = InitialMessage::decode(message)?;
66+
Ok(CryptoChannelCreationResult::Ecies(ecies.establish_inbound_channel(&message)?))
67+
}
68+
}
69+
}
70+
}
71+
72+
pub(super) enum CryptoChannelCreationResult {
73+
Ecies(InboundCreationResult),
74+
}
75+
76+
impl CryptoChannelCreationResult {
77+
/// Get the unsealed plaintext of the initial message.
78+
pub(super) fn plaintext(&self) -> &[u8] {
79+
match self {
80+
CryptoChannelCreationResult::Ecies(inbound_creation_result) => {
81+
&inbound_creation_result.message
82+
}
83+
}
84+
}
85+
}
86+
87+
/// A fully established cryptograhpic communication channel.
88+
///
89+
/// This channel allows you to seal/encrypt as well as open/decrypt
90+
/// cryptographic messages.
91+
pub(super) enum EstablishedCryptoChannel {
92+
Ecies(EstablishedEcies),
93+
}
94+
95+
impl EstablishedCryptoChannel {
96+
/// Get the [`CheckCode`] of this [`EstablishedCryptoChannel`].
97+
pub(super) fn check_code(&self) -> &CheckCode {
98+
match self {
99+
EstablishedCryptoChannel::Ecies(established_ecies) => established_ecies.check_code(),
100+
}
101+
}
102+
103+
/// Seal the given plaintext using this [`EstablishedCryptoChannel`].
104+
pub(super) fn seal(&mut self, plaintext: &[u8]) -> Vec<u8> {
105+
let message = match self {
106+
EstablishedCryptoChannel::Ecies(channel) => {
107+
let message = channel.encrypt(plaintext);
108+
message.encode()
109+
}
110+
};
111+
112+
message.as_bytes().to_vec()
113+
}
114+
115+
/// Open the given sealed message using this [`EstablishedCryptoChannel`].
116+
pub(super) fn open(&mut self, message: &[u8]) -> Result<Vec<u8>, Error> {
117+
let message = str::from_utf8(message)?;
118+
119+
match self {
120+
EstablishedCryptoChannel::Ecies(channel) => {
121+
let message = Message::decode(message)?;
122+
Ok(channel.decrypt(&message)?)
123+
}
124+
}
125+
}
126+
}

crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel.rs renamed to crates/matrix-sdk/src/authentication/oauth/qrcode/secure_channel/mod.rs

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,29 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use crypto_channel::*;
1516
use matrix_sdk_base::crypto::types::qr_login::{QrCodeData, QrCodeMode, QrCodeModeData};
1617
use serde::{Serialize, de::DeserializeOwned};
1718
use tracing::{instrument, trace};
1819
use url::Url;
1920
use vodozemac::ecies::{
20-
CheckCode, Ecies, EstablishedEcies, InboundCreationResult, InitialMessage, Message,
21-
OutboundCreationResult,
21+
CheckCode, Ecies, EstablishedEcies, InboundCreationResult, OutboundCreationResult,
2222
};
2323

2424
use super::{
2525
SecureChannelError as Error,
2626
rendezvous_channel::{InboundChannelCreationResult, RendezvousChannel},
2727
};
2828
use crate::{config::RequestConfig, http_client::HttpClient};
29+
mod crypto_channel;
2930

3031
const LOGIN_INITIATE_MESSAGE: &str = "MATRIX_QR_CODE_LOGIN_INITIATE";
3132
const LOGIN_OK_MESSAGE: &str = "MATRIX_QR_CODE_LOGIN_OK";
3233

3334
pub(super) struct SecureChannel {
3435
channel: RendezvousChannel,
3536
qr_code_data: QrCodeData,
36-
ecies: Ecies,
37+
crypto_channel: CryptoChannel,
3738
}
3839

3940
impl SecureChannel {
@@ -46,12 +47,12 @@ impl SecureChannel {
4647
let rendezvous_url = channel.rendezvous_url().to_owned();
4748
let mode_data = QrCodeModeData::Login;
4849

49-
let ecies = Ecies::new();
50-
let public_key = ecies.public_key();
50+
let crypto_channel = CryptoChannel::new_ecies();
51+
let public_key = crypto_channel.public_key();
5152

5253
let qr_code_data = QrCodeData { public_key, rendezvous_url, mode_data };
5354

54-
Ok(Self { channel, qr_code_data, ecies })
55+
Ok(Self { channel, qr_code_data, crypto_channel })
5556
}
5657

5758
/// Create a new secure channel to reciprocate an existing login with.
@@ -74,21 +75,26 @@ impl SecureChannel {
7475
trace!("Trying to connect the secure channel.");
7576

7677
let message = self.channel.receive().await?;
77-
let message = std::str::from_utf8(&message)?;
78-
let message = InitialMessage::decode(message)?;
78+
let result = self.crypto_channel.establish_inbound_channel(&message)?;
7979

80-
let InboundCreationResult { ecies, message } =
81-
self.ecies.establish_inbound_channel(&message)?;
82-
let message = std::str::from_utf8(&message)?;
80+
let message = std::str::from_utf8(result.plaintext())?;
8381

8482
trace!("Received the initial secure channel message");
8583

8684
if message == LOGIN_INITIATE_MESSAGE {
87-
let mut secure_channel = EstablishedSecureChannel { channel: self.channel, ecies };
85+
let secure_channel = match result {
86+
CryptoChannelCreationResult::Ecies(InboundCreationResult { ecies, .. }) => {
87+
let crypto_channel = EstablishedCryptoChannel::Ecies(ecies);
8888

89-
trace!("Sending the LOGIN OK message");
89+
let mut secure_channel =
90+
EstablishedSecureChannel { channel: self.channel, crypto_channel };
9091

91-
secure_channel.send(LOGIN_OK_MESSAGE).await?;
92+
trace!("Sending the LOGIN OK message");
93+
94+
secure_channel.send(LOGIN_OK_MESSAGE).await?;
95+
secure_channel
96+
}
97+
};
9298

9399
Ok(AlmostEstablishedSecureChannel { secure_channel })
94100
} else {
@@ -122,7 +128,7 @@ impl AlmostEstablishedSecureChannel {
122128

123129
pub(super) struct EstablishedSecureChannel {
124130
channel: RendezvousChannel,
125-
ecies: EstablishedEcies,
131+
crypto_channel: EstablishedCryptoChannel,
126132
}
127133

128134
impl EstablishedSecureChannel {
@@ -133,22 +139,30 @@ impl EstablishedSecureChannel {
133139
qr_code_data: &QrCodeData,
134140
expected_mode: QrCodeMode,
135141
) -> Result<Self, Error> {
142+
enum ChannelType {
143+
Ecies(EstablishedEcies),
144+
}
145+
136146
if qr_code_data.mode() == expected_mode {
137147
Err(Error::InvalidIntent)
138148
} else {
139149
trace!("Attempting to create a new inbound secure channel from a QR code.");
140150

141151
let client = HttpClient::new(client, RequestConfig::short_retry());
142-
let ecies = Ecies::new();
143152

144153
// Let's establish an outbound ECIES channel, the other side won't know that
145154
// it's talking to us, the device that scanned the QR code, until it
146155
// receives and successfully decrypts the initial message. We're here encrypting
147156
// the `LOGIN_INITIATE_MESSAGE`.
148-
let OutboundCreationResult { ecies, message } = ecies.establish_outbound_channel(
149-
qr_code_data.public_key,
150-
LOGIN_INITIATE_MESSAGE.as_bytes(),
151-
)?;
157+
let (crypto_channel, encoded_message) = {
158+
let ecies = Ecies::new();
159+
160+
let OutboundCreationResult { ecies, message } = ecies.establish_outbound_channel(
161+
qr_code_data.public_key,
162+
LOGIN_INITIATE_MESSAGE.as_bytes(),
163+
)?;
164+
(ChannelType::Ecies(ecies), message.encode().as_bytes().to_vec())
165+
};
152166

153167
// The other side has crated a rendezvous channel, we're going to connect to it
154168
// and send this initial encrypted message through it. The initial message on
@@ -159,25 +173,31 @@ impl EstablishedSecureChannel {
159173

160174
trace!(
161175
"Received the initial message from the rendezvous channel, sending the LOGIN \
162-
INITIATE message"
176+
INITIATE message"
163177
);
164178

165179
// Now we're sending the encrypted message through the rendezvous channel to the
166180
// other side.
167-
let encoded_message = message.encode().as_bytes().to_vec();
168181
channel.send(encoded_message).await?;
169182

170183
trace!("Waiting for the LOGIN OK message");
171184

172-
// We can create our EstablishedSecureChannel struct now and use the
173-
// convenient helpers which transparently decrypt on receival.
174-
let mut ret = Self { channel, ecies };
175-
let response = ret.receive().await?;
185+
let (response, channel) = match crypto_channel {
186+
ChannelType::Ecies(ecies) => {
187+
// We can create our EstablishedSecureChannel struct now and use the
188+
// convenient helpers which transparently decrypt on receival.
189+
let crypto_channel = EstablishedCryptoChannel::Ecies(ecies);
190+
let mut channel = Self { channel, crypto_channel };
191+
192+
let response = channel.receive().await?;
193+
(response, channel)
194+
}
195+
};
176196

177197
trace!("Received the LOGIN OK message, maybe.");
178198

179199
if response == LOGIN_OK_MESSAGE {
180-
Ok(ret)
200+
Ok(channel)
181201
} else {
182202
Err(Error::SecureChannelMessage { expected: LOGIN_OK_MESSAGE, received: response })
183203
}
@@ -188,7 +208,7 @@ impl EstablishedSecureChannel {
188208
/// both sides of the channel are indeed communicating with each other and
189209
/// not with a 3rd party.
190210
pub(super) fn check_code(&self) -> &CheckCode {
191-
self.ecies.check_code()
211+
self.crypto_channel.check_code()
192212
}
193213

194214
/// Send the given message over to the other side.
@@ -210,18 +230,14 @@ impl EstablishedSecureChannel {
210230
}
211231

212232
async fn send(&mut self, message: &str) -> Result<(), Error> {
213-
let message = self.ecies.encrypt(message.as_bytes());
214-
let message = message.encode();
233+
let message = self.crypto_channel.seal(message.as_bytes());
215234

216-
Ok(self.channel.send(message.as_bytes().to_vec()).await?)
235+
Ok(self.channel.send(message).await?)
217236
}
218237

219238
async fn receive(&mut self) -> Result<String, Error> {
220239
let message = self.channel.receive().await?;
221-
let ciphertext = std::str::from_utf8(&message)?;
222-
let message = Message::decode(ciphertext)?;
223-
224-
let decrypted = self.ecies.decrypt(&message)?;
240+
let decrypted = self.crypto_channel.open(&message)?;
225241

226242
Ok(String::from_utf8(decrypted).map_err(|e| e.utf8_error())?)
227243
}

0 commit comments

Comments
 (0)