matrix_sdk/authentication/oauth/qrcode/secure_channel/
mod.rs1use crypto_channel::*;
16use matrix_sdk_base::crypto::types::qr_login::{
17 Msc4108IntentData, QrCodeData, QrCodeIntent, QrCodeIntentData,
18};
19use serde::{Serialize, de::DeserializeOwned};
20use tracing::{instrument, trace};
21use url::Url;
22use vodozemac::ecies::{
23 CheckCode, Ecies, EstablishedEcies, InboundCreationResult, OutboundCreationResult,
24};
25
26use super::{
27 SecureChannelError as Error,
28 rendezvous_channel::{InboundChannelCreationResult, RendezvousChannel, RendezvousInfo},
29};
30use crate::{config::RequestConfig, http_client::HttpClient};
31mod crypto_channel;
32
33const LOGIN_INITIATE_MESSAGE: &str = "MATRIX_QR_CODE_LOGIN_INITIATE";
34const LOGIN_OK_MESSAGE: &str = "MATRIX_QR_CODE_LOGIN_OK";
35
36pub(super) struct SecureChannel {
37 channel: RendezvousChannel,
38 qr_code_data: QrCodeData,
39 crypto_channel: CryptoChannel,
40}
41
42impl SecureChannel {
43 pub(super) async fn login(
45 http_client: HttpClient,
46 homeserver_url: &Url,
47 ) -> Result<Self, Error> {
48 let channel = RendezvousChannel::create_outbound(http_client, homeserver_url).await?;
49
50 let (crypto_channel, qr_code_data) = match channel.rendezvous_info() {
51 RendezvousInfo::Msc4108 { rendezvous_url } => {
52 let intent_data = Msc4108IntentData::Login;
53 let crypto_channel = CryptoChannel::new_ecies();
54
55 let qr_code_data = QrCodeData::new_msc4108(
56 crypto_channel.public_key(),
57 rendezvous_url.clone(),
58 intent_data,
59 );
60
61 (crypto_channel, qr_code_data)
62 }
63 };
64
65 Ok(Self { channel, qr_code_data, crypto_channel })
66 }
67
68 pub(super) async fn reciprocate(
70 http_client: HttpClient,
71 homeserver_url: &Url,
72 ) -> Result<Self, Error> {
73 let mut channel = SecureChannel::login(http_client, homeserver_url).await?;
74
75 match channel.channel.rendezvous_info() {
76 RendezvousInfo::Msc4108 { rendezvous_url } => {
77 let mode_data =
78 Msc4108IntentData::Reciprocate { server_name: homeserver_url.to_string() };
79
80 channel.qr_code_data = QrCodeData::new_msc4108(
81 channel.crypto_channel.public_key(),
82 rendezvous_url.clone(),
83 mode_data,
84 );
85 }
86 }
87
88 Ok(channel)
89 }
90
91 pub(super) fn qr_code_data(&self) -> &QrCodeData {
92 &self.qr_code_data
93 }
94
95 #[instrument(skip(self))]
96 pub(super) async fn connect(mut self) -> Result<AlmostEstablishedSecureChannel, Error> {
97 trace!("Trying to connect the secure channel.");
98
99 let message = self.channel.receive().await?;
100 let result = self.crypto_channel.establish_inbound_channel(&message)?;
101
102 let message = std::str::from_utf8(result.plaintext())?;
103
104 trace!("Received the initial secure channel message");
105
106 if message == LOGIN_INITIATE_MESSAGE {
107 let secure_channel = match result {
108 CryptoChannelCreationResult::Ecies(InboundCreationResult { ecies, .. }) => {
109 let crypto_channel = EstablishedCryptoChannel::Ecies(ecies);
110
111 let mut secure_channel =
112 EstablishedSecureChannel { channel: self.channel, crypto_channel };
113
114 trace!("Sending the LOGIN OK message");
115
116 secure_channel.send(LOGIN_OK_MESSAGE).await?;
117 secure_channel
118 }
119 };
120
121 Ok(AlmostEstablishedSecureChannel { secure_channel })
122 } else {
123 Err(Error::SecureChannelMessage {
124 expected: LOGIN_INITIATE_MESSAGE,
125 received: message.to_owned(),
126 })
127 }
128 }
129}
130
131pub(super) struct AlmostEstablishedSecureChannel {
134 secure_channel: EstablishedSecureChannel,
135}
136
137impl AlmostEstablishedSecureChannel {
138 pub(super) fn confirm(self, check_code: u8) -> Result<EstablishedSecureChannel, Error> {
143 if check_code == self.secure_channel.check_code().to_digit() {
144 Ok(self.secure_channel)
145 } else {
146 Err(Error::InvalidCheckCode)
147 }
148 }
149}
150
151pub(super) struct EstablishedSecureChannel {
152 channel: RendezvousChannel,
153 crypto_channel: EstablishedCryptoChannel,
154}
155
156impl EstablishedSecureChannel {
157 #[instrument(skip(client))]
159 pub(super) async fn from_qr_code(
160 client: reqwest::Client,
161 qr_code_data: &QrCodeData,
162 expected_mode: QrCodeIntent,
163 ) -> Result<Self, Error> {
164 enum ChannelType {
165 Ecies(EstablishedEcies),
166 }
167
168 if qr_code_data.intent() == expected_mode {
169 Err(Error::InvalidIntent)
170 } else {
171 trace!("Attempting to create a new inbound secure channel from a QR code.");
172
173 let client = HttpClient::new(client, RequestConfig::short_retry());
174
175 let (crypto_channel, encoded_message) = {
180 let ecies = Ecies::new();
181
182 let OutboundCreationResult { ecies, message } = ecies.establish_outbound_channel(
183 qr_code_data.public_key(),
184 LOGIN_INITIATE_MESSAGE.as_bytes(),
185 )?;
186 (ChannelType::Ecies(ecies), message.encode())
187 };
188
189 let mut channel = match qr_code_data.intent_data() {
194 QrCodeIntentData::Msc4108 { rendezvous_url, .. } => {
195 let InboundChannelCreationResult { channel, .. } =
196 RendezvousChannel::create_inbound(client, rendezvous_url).await?;
197 channel
198 }
199 QrCodeIntentData::Msc4388 { .. } => return Err(Error::UnsupportedQrCodeType),
202 };
203
204 trace!(
205 "Received the initial message from the rendezvous channel, sending the LOGIN \
206 INITIATE message"
207 );
208
209 channel.send(encoded_message).await?;
212
213 trace!("Waiting for the LOGIN OK message");
214
215 let (response, channel) = match crypto_channel {
216 ChannelType::Ecies(ecies) => {
217 let crypto_channel = EstablishedCryptoChannel::Ecies(ecies);
220 let mut channel = Self { channel, crypto_channel };
221
222 let response = channel.receive().await?;
223 (response, channel)
224 }
225 };
226
227 trace!("Received the LOGIN OK message, maybe.");
228
229 if response == LOGIN_OK_MESSAGE {
230 Ok(channel)
231 } else {
232 Err(Error::SecureChannelMessage { expected: LOGIN_OK_MESSAGE, received: response })
233 }
234 }
235 }
236
237 pub(super) fn check_code(&self) -> &CheckCode {
241 self.crypto_channel.check_code()
242 }
243
244 pub(super) async fn send_json(&mut self, message: impl Serialize) -> Result<(), Error> {
249 let message = serde_json::to_string(&message)?;
250 self.send(&message).await
251 }
252
253 pub(super) async fn receive_json<D: DeserializeOwned>(&mut self) -> Result<D, Error> {
258 let message = self.receive().await?;
259 Ok(serde_json::from_str(&message)?)
260 }
261
262 async fn send(&mut self, message: &str) -> Result<(), Error> {
263 let message = self.crypto_channel.seal(message);
264
265 Ok(self.channel.send(message).await?)
266 }
267
268 async fn receive(&mut self) -> Result<String, Error> {
269 let message = self.channel.receive().await?;
270 self.crypto_channel.open(&message)
271 }
272}
273
274#[cfg(all(test, not(target_family = "wasm")))]
275pub(super) mod test {
276 use std::{
277 sync::{
278 Arc, Mutex,
279 atomic::{AtomicU8, Ordering},
280 },
281 time::Duration,
282 };
283
284 use matrix_sdk_base::crypto::types::qr_login::QrCodeIntent;
285 use matrix_sdk_common::executor::spawn;
286 use matrix_sdk_test::async_test;
287 use ruma::time::Instant;
288 use serde_json::json;
289 use similar_asserts::assert_eq;
290 use url::Url;
291 use wiremock::{
292 Mock, MockGuard, MockServer, ResponseTemplate,
293 matchers::{method, path},
294 };
295
296 use super::{EstablishedSecureChannel, SecureChannel};
297 use crate::http_client::HttpClient;
298
299 #[allow(dead_code)]
300 pub struct MockedRendezvousServer {
301 pub homeserver_url: Url,
302 pub rendezvous_url: Url,
303 expiration: Duration,
304 content: Arc<Mutex<Option<String>>>,
305 created: Arc<Mutex<Option<Instant>>>,
306 etag: Arc<AtomicU8>,
307 post_guard: MockGuard,
308 put_guard: MockGuard,
309 get_guard: MockGuard,
310 }
311
312 impl MockedRendezvousServer {
313 pub async fn new(server: &MockServer, location: &str, expiration: Duration) -> Self {
314 let content: Arc<Mutex<Option<String>>> = Mutex::default().into();
315 let created: Arc<Mutex<Option<Instant>>> = Mutex::default().into();
316 let etag = Arc::new(AtomicU8::new(0));
317
318 let homeserver_url = Url::parse(&server.uri())
319 .expect("We should be able to parse the example homeserver");
320
321 let rendezvous_url = homeserver_url
322 .join(location)
323 .expect("We should be able to create a rendezvous URL");
324
325 let post_guard = server
326 .register_as_scoped(
327 Mock::given(method("POST"))
328 .and(path("/_matrix/client/unstable/org.matrix.msc4108/rendezvous"))
329 .respond_with({
330 *created.lock().unwrap() = Some(Instant::now());
331
332 ResponseTemplate::new(200)
333 .append_header("X-Max-Bytes", "10240")
334 .append_header("ETag", "1")
335 .append_header("Expires", "Wed, 07 Sep 2022 14:28:51 GMT")
336 .append_header("Last-Modified", "Wed, 07 Sep 2022 14:27:51 GMT")
337 .set_body_json(json!({
338 "url": rendezvous_url,
339 }))
340 }),
341 )
342 .await;
343
344 let put_guard = server
345 .register_as_scoped(
346 Mock::given(method("PUT")).and(path("/abcdEFG12345")).respond_with({
347 let content = content.clone();
348 let created = created.clone();
349 let etag = etag.clone();
350
351 move |request: &wiremock::Request| {
352 if created.lock().unwrap().unwrap().elapsed() > expiration {
354 return ResponseTemplate::new(404).set_body_json(json!({
355 "errcode": "M_NOT_FOUND",
356 "error": "This rendezvous session does not exist.",
357 }));
358 }
359
360 *content.lock().unwrap() =
361 Some(String::from_utf8(request.body.clone()).unwrap());
362 let current_etag = etag.fetch_add(1, Ordering::SeqCst);
363
364 ResponseTemplate::new(200)
365 .append_header("ETag", (current_etag + 2).to_string())
366 .append_header("Expires", "Wed, 07 Sep 2022 14:28:51 GMT")
367 .append_header("Last-Modified", "Wed, 07 Sep 2022 14:27:51 GMT")
368 }
369 }),
370 )
371 .await;
372
373 let get_guard = server
374 .register_as_scoped(
375 Mock::given(method("GET")).and(path("/abcdEFG12345")).respond_with({
376 let content = content.clone();
377 let created = created.clone();
378 let etag = etag.clone();
379
380 move |request: &wiremock::Request| {
381 if created.lock().unwrap().unwrap().elapsed() > expiration {
383 return ResponseTemplate::new(404).set_body_json(json!({
384 "errcode": "M_NOT_FOUND",
385 "error": "This rendezvous session does not exist.",
386 }));
387 }
388
389 let requested_etag = request.headers.get("if-none-match").map(|etag| {
390 str::parse::<u8>(std::str::from_utf8(etag.as_bytes()).unwrap())
391 .unwrap()
392 });
393
394 let mut content = content.lock().unwrap();
395 let current_etag = etag.load(Ordering::SeqCst);
396
397 if requested_etag == Some(current_etag) || requested_etag.is_none() {
398 let content = content.take();
399
400 ResponseTemplate::new(200)
401 .append_header("ETag", (current_etag).to_string())
402 .append_header("Expires", "Wed, 07 Sep 2022 14:28:51 GMT")
403 .append_header("Last-Modified", "Wed, 07 Sep 2022 14:27:51 GMT")
404 .set_body_string(content.unwrap_or_default())
405 } else {
406 let etag = requested_etag.unwrap_or_default();
407
408 ResponseTemplate::new(304)
409 .append_header("ETag", etag.to_string())
410 .append_header("Expires", "Wed, 07 Sep 2022 14:28:51 GMT")
411 .append_header("Last-Modified", "Wed, 07 Sep 2022 14:27:51 GMT")
412 }
413 }
414 }),
415 )
416 .await;
417
418 Self {
419 expiration,
420 content,
421 created,
422 etag,
423 post_guard,
424 put_guard,
425 get_guard,
426 homeserver_url,
427 rendezvous_url,
428 }
429 }
430 }
431
432 #[async_test]
433 async fn test_creation() {
434 let server = MockServer::start().await;
435 let rendezvous_server =
436 MockedRendezvousServer::new(&server, "abcdEFG12345", Duration::MAX).await;
437
438 let client = HttpClient::new(reqwest::Client::new(), Default::default());
439 let alice = SecureChannel::reciprocate(client, &rendezvous_server.homeserver_url)
440 .await
441 .expect("Alice should be able to create a secure channel.");
442
443 let qr_code_data = alice.qr_code_data().clone();
444
445 let bob_task = spawn(async move {
446 EstablishedSecureChannel::from_qr_code(
447 reqwest::Client::new(),
448 &qr_code_data,
449 QrCodeIntent::Login,
450 )
451 .await
452 .expect("Bob should be able to fully establish the secure channel.")
453 });
454
455 let alice_task = spawn(async move {
456 alice
457 .connect()
458 .await
459 .expect("Alice should be able to connect the established secure channel")
460 });
461
462 let bob = bob_task.await.unwrap();
463 let alice = alice_task.await.unwrap();
464
465 assert_eq!(alice.secure_channel.check_code(), bob.check_code());
466
467 let alice = alice
468 .confirm(bob.check_code().to_digit())
469 .expect("Alice should be able to confirm the established secure channel.");
470
471 assert_eq!(bob.channel.rendezvous_info(), alice.channel.rendezvous_info());
472 }
473}