matrix_sdk/authentication/oauth/qrcode/
secure_channel.rs1#[cfg(test)]
16use matrix_sdk_base::crypto::types::qr_login::QrCodeModeData;
17use matrix_sdk_base::crypto::types::qr_login::{QrCodeData, QrCodeMode};
18use serde::{de::DeserializeOwned, Serialize};
19use tracing::{instrument, trace};
20#[cfg(test)]
21use url::Url;
22use vodozemac::ecies::{CheckCode, Ecies, EstablishedEcies, Message, OutboundCreationResult};
23#[cfg(test)]
24use vodozemac::ecies::{InboundCreationResult, InitialMessage};
25
26use super::{
27 rendezvous_channel::{InboundChannelCreationResult, RendezvousChannel},
28 SecureChannelError as Error,
29};
30use crate::{config::RequestConfig, http_client::HttpClient};
31
32const LOGIN_INITIATE_MESSAGE: &str = "MATRIX_QR_CODE_LOGIN_INITIATE";
33const LOGIN_OK_MESSAGE: &str = "MATRIX_QR_CODE_LOGIN_OK";
34
35#[cfg(test)]
36pub(super) struct SecureChannel {
37 channel: RendezvousChannel,
38 qr_code_data: QrCodeData,
39 ecies: Ecies,
40}
41
42#[cfg(test)]
49impl SecureChannel {
50 pub(super) async fn new(http_client: HttpClient, homeserver_url: &Url) -> Result<Self, Error> {
51 let channel = RendezvousChannel::create_outbound(http_client, homeserver_url).await?;
52 let rendezvous_url = channel.rendezvous_url().to_owned();
53 let mode_data = QrCodeModeData::Reciprocate { server_name: homeserver_url.to_string() };
56
57 let ecies = Ecies::new();
58 let public_key = ecies.public_key();
59
60 let qr_code_data = QrCodeData { public_key, rendezvous_url, mode_data };
61
62 Ok(Self { channel, qr_code_data, ecies })
63 }
64
65 pub(super) fn qr_code_data(&self) -> &QrCodeData {
66 &self.qr_code_data
67 }
68
69 #[instrument(skip(self))]
70 pub(super) async fn connect(mut self) -> Result<AlmostEstablishedSecureChannel, Error> {
71 trace!("Trying to connect the secure channel.");
72
73 let message = self.channel.receive().await?;
74 let message = std::str::from_utf8(&message)?;
75 let message = InitialMessage::decode(message)?;
76
77 let InboundCreationResult { ecies, message } =
78 self.ecies.establish_inbound_channel(&message)?;
79 let message = std::str::from_utf8(&message)?;
80
81 trace!("Received the initial secure channel message");
82
83 if message == LOGIN_INITIATE_MESSAGE {
84 let mut secure_channel = EstablishedSecureChannel { channel: self.channel, ecies };
85
86 trace!("Sending the LOGIN OK message");
87
88 secure_channel.send(LOGIN_OK_MESSAGE).await?;
89
90 Ok(AlmostEstablishedSecureChannel { secure_channel })
91 } else {
92 Err(Error::SecureChannelMessage {
93 expected: LOGIN_INITIATE_MESSAGE,
94 received: message.to_owned(),
95 })
96 }
97 }
98}
99
100#[cfg(test)]
103pub(super) struct AlmostEstablishedSecureChannel {
104 secure_channel: EstablishedSecureChannel,
105}
106
107#[cfg(test)]
108impl AlmostEstablishedSecureChannel {
109 pub(super) fn confirm(self, check_code: u8) -> Result<EstablishedSecureChannel, Error> {
114 if check_code == self.secure_channel.check_code().to_digit() {
115 Ok(self.secure_channel)
116 } else {
117 Err(Error::InvalidCheckCode)
118 }
119 }
120}
121
122pub(super) struct EstablishedSecureChannel {
123 channel: RendezvousChannel,
124 ecies: EstablishedEcies,
125}
126
127impl EstablishedSecureChannel {
128 #[instrument(skip(client))]
130 pub(super) async fn from_qr_code(
131 client: reqwest::Client,
132 qr_code_data: &QrCodeData,
133 expected_mode: QrCodeMode,
134 ) -> Result<Self, Error> {
135 if qr_code_data.mode() == expected_mode {
136 Err(Error::InvalidIntent)
137 } else {
138 trace!("Attempting to create a new inbound secure channel from a QR code.");
139
140 let client = HttpClient::new(client, RequestConfig::short_retry());
141 let ecies = Ecies::new();
142
143 let OutboundCreationResult { ecies, message } = ecies.establish_outbound_channel(
148 qr_code_data.public_key,
149 LOGIN_INITIATE_MESSAGE.as_bytes(),
150 )?;
151
152 let InboundChannelCreationResult { mut channel, .. } =
157 RendezvousChannel::create_inbound(client, &qr_code_data.rendezvous_url).await?;
158
159 trace!(
160 "Received the initial message from the rendezvous channel, sending the LOGIN \
161 INITIATE message"
162 );
163
164 let encoded_message = message.encode().as_bytes().to_vec();
167 channel.send(encoded_message).await?;
168
169 trace!("Waiting for the LOGIN OK message");
170
171 let mut ret = Self { channel, ecies };
174 let response = ret.receive().await?;
175
176 trace!("Received the LOGIN OK message, maybe.");
177
178 if response == LOGIN_OK_MESSAGE {
179 Ok(ret)
180 } else {
181 Err(Error::SecureChannelMessage { expected: LOGIN_OK_MESSAGE, received: response })
182 }
183 }
184 }
185
186 pub(super) fn check_code(&self) -> &CheckCode {
190 self.ecies.check_code()
191 }
192
193 pub(super) async fn send_json(&mut self, message: impl Serialize) -> Result<(), Error> {
198 let message = serde_json::to_string(&message)?;
199 self.send(&message).await
200 }
201
202 pub(super) async fn receive_json<D: DeserializeOwned>(&mut self) -> Result<D, Error> {
207 let message = self.receive().await?;
208 Ok(serde_json::from_str(&message)?)
209 }
210
211 async fn send(&mut self, message: &str) -> Result<(), Error> {
212 let message = self.ecies.encrypt(message.as_bytes());
213 let message = message.encode();
214
215 Ok(self.channel.send(message.as_bytes().to_vec()).await?)
216 }
217
218 async fn receive(&mut self) -> Result<String, Error> {
219 let message = self.channel.receive().await?;
220 let ciphertext = std::str::from_utf8(&message)?;
221 let message = Message::decode(ciphertext)?;
222
223 let decrypted = self.ecies.decrypt(&message)?;
224
225 Ok(String::from_utf8(decrypted).map_err(|e| e.utf8_error())?)
226 }
227}
228
229#[cfg(all(test, not(target_family = "wasm")))]
230pub(super) mod test {
231 use std::sync::{
232 atomic::{AtomicU8, Ordering},
233 Arc, Mutex,
234 };
235
236 use matrix_sdk_base::crypto::types::qr_login::QrCodeMode;
237 use matrix_sdk_common::executor::spawn;
238 use matrix_sdk_test::async_test;
239 use serde_json::json;
240 use similar_asserts::assert_eq;
241 use url::Url;
242 use wiremock::{
243 matchers::{method, path},
244 Mock, MockGuard, MockServer, ResponseTemplate,
245 };
246
247 use super::{EstablishedSecureChannel, SecureChannel};
248 use crate::http_client::HttpClient;
249
250 #[allow(dead_code)]
251 pub struct MockedRendezvousServer {
252 pub homeserver_url: Url,
253 pub rendezvous_url: Url,
254 content: Arc<Mutex<Option<String>>>,
255 etag: Arc<AtomicU8>,
256 post_guard: MockGuard,
257 put_guard: MockGuard,
258 get_guard: MockGuard,
259 }
260
261 impl MockedRendezvousServer {
262 pub async fn new(server: &MockServer, location: &str) -> Self {
263 let content: Arc<Mutex<Option<String>>> = Mutex::default().into();
264 let etag = Arc::new(AtomicU8::new(0));
265
266 let homeserver_url = Url::parse(&server.uri())
267 .expect("We should be able to parse the example homeserver");
268
269 let rendezvous_url = homeserver_url
270 .join(location)
271 .expect("We should be able to create a rendezvous URL");
272
273 let post_guard = server
274 .register_as_scoped(
275 Mock::given(method("POST"))
276 .and(path("/_matrix/client/unstable/org.matrix.msc4108/rendezvous"))
277 .respond_with(
278 ResponseTemplate::new(200)
279 .append_header("X-Max-Bytes", "10240")
280 .append_header("ETag", "1")
281 .append_header("Expires", "Wed, 07 Sep 2022 14:28:51 GMT")
282 .append_header("Last-Modified", "Wed, 07 Sep 2022 14:27:51 GMT")
283 .set_body_json(json!({
284 "url": rendezvous_url,
285 })),
286 ),
287 )
288 .await;
289
290 let put_guard = server
291 .register_as_scoped(
292 Mock::given(method("PUT")).and(path("/abcdEFG12345")).respond_with({
293 let content = content.clone();
294 let etag = etag.clone();
295
296 move |request: &wiremock::Request| {
297 *content.lock().unwrap() =
298 Some(String::from_utf8(request.body.clone()).unwrap());
299 let current_etag = etag.fetch_add(1, Ordering::SeqCst);
300
301 ResponseTemplate::new(200)
302 .append_header("ETag", (current_etag + 2).to_string())
303 .append_header("Expires", "Wed, 07 Sep 2022 14:28:51 GMT")
304 .append_header("Last-Modified", "Wed, 07 Sep 2022 14:27:51 GMT")
305 }
306 }),
307 )
308 .await;
309
310 let get_guard = server
311 .register_as_scoped(
312 Mock::given(method("GET")).and(path("/abcdEFG12345")).respond_with({
313 let content = content.clone();
314 let etag = etag.clone();
315
316 move |request: &wiremock::Request| {
317 let requested_etag = request.headers.get("if-none-match").map(|etag| {
318 str::parse::<u8>(std::str::from_utf8(etag.as_bytes()).unwrap())
319 .unwrap()
320 });
321
322 let mut content = content.lock().unwrap();
323 let current_etag = etag.load(Ordering::SeqCst);
324
325 if requested_etag == Some(current_etag) || requested_etag.is_none() {
326 let content = content.take();
327
328 ResponseTemplate::new(200)
329 .append_header("ETag", (current_etag).to_string())
330 .append_header("Expires", "Wed, 07 Sep 2022 14:28:51 GMT")
331 .append_header("Last-Modified", "Wed, 07 Sep 2022 14:27:51 GMT")
332 .set_body_string(content.unwrap_or_default())
333 } else {
334 let etag = requested_etag.unwrap_or_default();
335
336 ResponseTemplate::new(304)
337 .append_header("ETag", etag.to_string())
338 .append_header("Expires", "Wed, 07 Sep 2022 14:28:51 GMT")
339 .append_header("Last-Modified", "Wed, 07 Sep 2022 14:27:51 GMT")
340 }
341 }
342 }),
343 )
344 .await;
345
346 Self { content, etag, post_guard, put_guard, get_guard, homeserver_url, rendezvous_url }
347 }
348 }
349
350 #[async_test]
351 async fn test_creation() {
352 let server = MockServer::start().await;
353 let rendezvous_server = MockedRendezvousServer::new(&server, "abcdEFG12345").await;
354
355 let client = HttpClient::new(reqwest::Client::new(), Default::default());
356 let alice = SecureChannel::new(client, &rendezvous_server.homeserver_url)
357 .await
358 .expect("Alice should be able to create a secure channel.");
359
360 let qr_code_data = alice.qr_code_data().clone();
361
362 let bob_task = spawn(async move {
363 EstablishedSecureChannel::from_qr_code(
364 reqwest::Client::new(),
365 &qr_code_data,
366 QrCodeMode::Login,
367 )
368 .await
369 .expect("Bob should be able to fully establish the secure channel.")
370 });
371
372 let alice_task = spawn(async move {
373 alice
374 .connect()
375 .await
376 .expect("Alice should be able to connect the established secure channel")
377 });
378
379 let bob = bob_task.await.unwrap();
380 let alice = alice_task.await.unwrap();
381
382 assert_eq!(alice.secure_channel.check_code(), bob.check_code());
383
384 let alice = alice
385 .confirm(bob.check_code().to_digit())
386 .expect("Alice should be able to confirm the established secure channel.");
387
388 assert_eq!(bob.channel.rendezvous_url(), alice.channel.rendezvous_url());
389 }
390}