1#![doc = include_str!("../docs/encryption.md")]
17#![cfg_attr(target_arch = "wasm32", allow(unused_imports))]
18
19use std::{
20 collections::{BTreeMap, HashSet},
21 io::{Cursor, Read, Write},
22 iter,
23 path::PathBuf,
24 sync::Arc,
25};
26
27use eyeball::{SharedObservable, Subscriber};
28use futures_core::Stream;
29use futures_util::{
30 future::try_join,
31 stream::{self, StreamExt},
32};
33use matrix_sdk_base::crypto::{
34 store::RoomKeyInfo,
35 types::requests::{
36 OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
37 },
38 CrossSigningBootstrapRequests, OlmMachine,
39};
40use matrix_sdk_common::{executor::spawn, locks::Mutex as StdMutex};
41use ruma::{
42 api::client::{
43 keys::{
44 get_keys, upload_keys, upload_signatures::v3::Request as UploadSignaturesRequest,
45 upload_signing_keys::v3::Request as UploadSigningKeysRequest,
46 },
47 message::send_message_event,
48 to_device::send_event_to_device::v3::{
49 Request as RumaToDeviceRequest, Response as ToDeviceResponse,
50 },
51 uiaa::{AuthData, UiaaInfo},
52 },
53 assign,
54 events::{
55 direct::DirectUserIdentifier,
56 room::{MediaSource, ThumbnailInfo},
57 },
58 DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, TransactionId, UserId,
59};
60use serde::Deserialize;
61use tokio::sync::{Mutex, RwLockReadGuard};
62use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
63use tracing::{debug, error, instrument, trace, warn};
64use url::Url;
65use vodozemac::Curve25519PublicKey;
66
67use self::{
68 backups::{types::BackupClientState, Backups},
69 futures::UploadEncryptedFile,
70 identities::{Device, DeviceUpdates, IdentityUpdates, UserDevices, UserIdentity},
71 recovery::{Recovery, RecoveryState},
72 secret_storage::SecretStorage,
73 tasks::{BackupDownloadTask, BackupUploadingTask, ClientTasks},
74 verification::{SasVerification, Verification, VerificationRequest},
75};
76use crate::{
77 attachment::Thumbnail,
78 client::{ClientInner, WeakClient},
79 error::HttpResult,
80 store_locks::CrossProcessStoreLockGuard,
81 Client, Error, HttpError, Result, Room, TransmissionProgress,
82};
83
84pub mod backups;
85pub mod futures;
86pub mod identities;
87pub mod recovery;
88pub mod secret_storage;
89pub(crate) mod tasks;
90pub mod verification;
91
92pub use matrix_sdk_base::crypto::{
93 olm::{
94 SessionCreationError as MegolmSessionCreationError,
95 SessionExportError as OlmSessionExportError,
96 },
97 vodozemac, CrossSigningStatus, CryptoStoreError, DecryptorError, EventError, KeyExportError,
98 LocalTrust, MediaEncryptionInfo, MegolmError, OlmError, RoomKeyImportResult, SecretImportError,
99 SessionCreationError, SignatureError, VERSION,
100};
101
102pub use crate::error::RoomKeyImportError;
103
104pub(crate) struct EncryptionData {
106 pub tasks: StdMutex<ClientTasks>,
109
110 pub encryption_settings: EncryptionSettings,
112
113 pub backup_state: BackupClientState,
115
116 pub recovery_state: SharedObservable<RecoveryState>,
118}
119
120impl EncryptionData {
121 pub fn new(encryption_settings: EncryptionSettings) -> Self {
122 Self {
123 encryption_settings,
124
125 tasks: StdMutex::new(Default::default()),
126 backup_state: Default::default(),
127 recovery_state: Default::default(),
128 }
129 }
130
131 pub fn initialize_room_key_tasks(&self, client: &Arc<ClientInner>) {
132 let weak_client = WeakClient::from_inner(client);
133
134 let mut tasks = self.tasks.lock();
135 tasks.upload_room_keys = Some(BackupUploadingTask::new(weak_client.clone()));
136
137 if self.encryption_settings.backup_download_strategy
138 == BackupDownloadStrategy::AfterDecryptionFailure
139 {
140 tasks.download_room_keys = Some(BackupDownloadTask::new(weak_client));
141 }
142 }
143
144 pub fn initialize_recovery_state_update_task(&self, client: &Client) {
150 let mut guard = self.tasks.lock();
151
152 let future = Recovery::update_state_after_backup_state_change(client);
153 let join_handle = spawn(future);
154
155 guard.update_recovery_state_after_backup = Some(join_handle);
156 }
157}
158
159#[derive(Clone, Copy, Debug, Default)]
161pub struct EncryptionSettings {
162 pub auto_enable_cross_signing: bool,
168
169 pub backup_download_strategy: BackupDownloadStrategy,
174
175 pub auto_enable_backups: bool,
177}
178
179#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
181#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
182pub enum BackupDownloadStrategy {
183 OneShot,
194
195 AfterDecryptionFailure,
197
198 #[default]
203 Manual,
204}
205
206#[derive(Clone, Copy, Debug, Eq, PartialEq)]
211pub enum VerificationState {
212 Unknown,
214 Verified,
217 Unverified,
219}
220
221#[derive(Debug)]
223pub struct CrossProcessLockStoreGuardWithGeneration {
224 _guard: CrossProcessStoreLockGuard,
225 generation: u64,
226}
227
228impl CrossProcessLockStoreGuardWithGeneration {
229 pub fn generation(&self) -> u64 {
231 self.generation
232 }
233}
234
235#[derive(Debug)]
246pub struct CrossSigningResetHandle {
247 client: Client,
248 upload_request: UploadSigningKeysRequest,
249 signatures_request: UploadSignaturesRequest,
250 auth_type: CrossSigningResetAuthType,
251 is_cancelled: Mutex<bool>,
252}
253
254impl CrossSigningResetHandle {
255 pub fn new(
257 client: Client,
258 upload_request: UploadSigningKeysRequest,
259 signatures_request: UploadSignaturesRequest,
260 auth_type: CrossSigningResetAuthType,
261 ) -> Self {
262 Self {
263 client,
264 upload_request,
265 signatures_request,
266 auth_type,
267 is_cancelled: Mutex::new(false),
268 }
269 }
270
271 pub fn auth_type(&self) -> &CrossSigningResetAuthType {
274 &self.auth_type
275 }
276
277 pub async fn auth(&self, auth: Option<AuthData>) -> Result<()> {
281 let mut upload_request = self.upload_request.clone();
282 upload_request.auth = auth;
283
284 while let Err(e) = self.client.send(upload_request.clone()).await {
285 if *self.is_cancelled.lock().await {
286 return Ok(());
287 }
288
289 match e.as_uiaa_response() {
290 Some(uiaa_info) => {
291 if uiaa_info.auth_error.is_some() {
292 return Err(e.into());
293 }
294 }
295 None => return Err(e.into()),
296 }
297 }
298
299 self.client.send(self.signatures_request.clone()).await?;
300
301 Ok(())
302 }
303
304 pub async fn cancel(&self) {
306 *self.is_cancelled.lock().await = true;
307 }
308}
309
310#[derive(Debug, Clone)]
313pub enum CrossSigningResetAuthType {
314 Uiaa(UiaaInfo),
316 OAuth(OAuthCrossSigningResetInfo),
319}
320
321impl CrossSigningResetAuthType {
322 fn new(error: &HttpError) -> Result<Option<Self>> {
323 if let Some(auth_info) = error.as_uiaa_response() {
324 if let Ok(auth_info) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
325 Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
326 } else {
327 Ok(Some(CrossSigningResetAuthType::Uiaa(auth_info.clone())))
328 }
329 } else {
330 Ok(None)
331 }
332 }
333}
334
335#[derive(Debug, Clone, Deserialize)]
338pub struct OAuthCrossSigningResetInfo {
339 pub approval_url: Url,
341}
342
343impl OAuthCrossSigningResetInfo {
344 fn from_auth_info(auth_info: &UiaaInfo) -> Result<Self> {
345 let parameters =
346 serde_json::from_str::<OAuthCrossSigningResetUiaaParameters>(auth_info.params.get())?;
347
348 Ok(OAuthCrossSigningResetInfo { approval_url: parameters.reset.url })
349 }
350}
351
352#[derive(Debug, Deserialize)]
355struct OAuthCrossSigningResetUiaaParameters {
356 #[serde(rename = "org.matrix.cross_signing_reset")]
358 reset: OAuthCrossSigningResetUiaaResetParameter,
359}
360
361#[derive(Debug, Deserialize)]
364struct OAuthCrossSigningResetUiaaResetParameter {
365 url: Url,
367}
368
369impl Client {
370 pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
371 self.base_client().olm_machine().await
372 }
373
374 pub(crate) async fn mark_request_as_sent(
375 &self,
376 request_id: &TransactionId,
377 response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
378 ) -> Result<(), matrix_sdk_base::Error> {
379 Ok(self
380 .olm_machine()
381 .await
382 .as_ref()
383 .expect(
384 "We should have an olm machine once we try to mark E2EE related requests as sent",
385 )
386 .mark_request_as_sent(request_id, response)
387 .await?)
388 }
389
390 #[instrument(skip(self, device_keys))]
396 pub(crate) async fn keys_query(
397 &self,
398 request_id: &TransactionId,
399 device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
400 ) -> Result<get_keys::v3::Response> {
401 let request = assign!(get_keys::v3::Request::new(), { device_keys });
402
403 let response = self.send(request).await?;
404 self.mark_request_as_sent(request_id, &response).await?;
405 self.encryption().update_state_after_keys_query(&response).await;
406
407 Ok(response)
408 }
409
410 pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
444 &'a self,
445 content_type: &'a mime::Mime,
446 reader: &'a mut R,
447 ) -> UploadEncryptedFile<'a, R> {
448 UploadEncryptedFile::new(self, content_type, reader)
449 }
450
451 pub(crate) async fn upload_encrypted_media_and_thumbnail(
454 &self,
455 content_type: &mime::Mime,
456 data: &[u8],
457 thumbnail: Option<Thumbnail>,
458 send_progress: SharedObservable<TransmissionProgress>,
459 ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
460 let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
461
462 let upload_attachment = async {
463 let mut cursor = Cursor::new(data);
464 self.upload_encrypted_file(content_type, &mut cursor)
465 .with_send_progress_observable(send_progress)
466 .await
467 };
468
469 let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
470
471 Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
472 }
473
474 async fn upload_encrypted_thumbnail(
477 &self,
478 thumbnail: Option<Thumbnail>,
479 send_progress: SharedObservable<TransmissionProgress>,
480 ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
481 let Some(thumbnail) = thumbnail else {
482 return Ok(None);
483 };
484
485 let (data, content_type, thumbnail_info) = thumbnail.into_parts();
486 let mut cursor = Cursor::new(data);
487
488 let file = self
489 .upload_encrypted_file(&content_type, &mut cursor)
490 .with_send_progress_observable(send_progress)
491 .await?;
492
493 Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
494 }
495
496 pub(crate) async fn claim_one_time_keys(
502 &self,
503 users: impl Iterator<Item = &UserId>,
504 ) -> Result<()> {
505 let _lock = self.locks().key_claim_lock.lock().await;
506
507 if let Some((request_id, request)) = self
508 .olm_machine()
509 .await
510 .as_ref()
511 .ok_or(Error::NoOlmMachine)?
512 .get_missing_sessions(users)
513 .await?
514 {
515 let response = self.send(request).await?;
516 self.mark_request_as_sent(&request_id, &response).await?;
517 }
518
519 Ok(())
520 }
521
522 #[instrument(skip(self, request))]
532 pub(crate) async fn keys_upload(
533 &self,
534 request_id: &TransactionId,
535 request: &upload_keys::v3::Request,
536 ) -> Result<upload_keys::v3::Response> {
537 debug!(
538 device_keys = request.device_keys.is_some(),
539 one_time_key_count = request.one_time_keys.len(),
540 "Uploading public encryption keys",
541 );
542
543 let response = self.send(request.clone()).await?;
544 self.mark_request_as_sent(request_id, &response).await?;
545
546 Ok(response)
547 }
548
549 pub(crate) async fn room_send_helper(
550 &self,
551 request: &RoomMessageRequest,
552 ) -> Result<send_message_event::v3::Response> {
553 let content = request.content.clone();
554 let txn_id = request.txn_id.clone();
555 let room_id = &request.room_id;
556
557 self.get_room(room_id)
558 .expect("Can't send a message to a room that isn't known to the store")
559 .send(content)
560 .with_transaction_id(txn_id)
561 .await
562 }
563
564 pub(crate) async fn send_to_device(
565 &self,
566 request: &ToDeviceRequest,
567 ) -> HttpResult<ToDeviceResponse> {
568 let request = RumaToDeviceRequest::new_raw(
569 request.event_type.clone(),
570 request.txn_id.clone(),
571 request.messages.clone(),
572 );
573
574 self.send(request).await
575 }
576
577 pub(crate) async fn send_verification_request(
578 &self,
579 request: OutgoingVerificationRequest,
580 ) -> Result<()> {
581 use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
582
583 match request {
584 ToDevice(t) => {
585 self.send_to_device(&t).await?;
586 }
587 InRoom(r) => {
588 self.room_send_helper(&r).await?;
589 }
590 }
591
592 Ok(())
593 }
594
595 pub fn get_dm_room(&self, user_id: &UserId) -> Option<Room> {
597 let rooms = self.joined_rooms();
598
599 let room = rooms.into_iter().find(|r| {
601 let targets = r.direct_targets();
602 targets.len() == 1 && targets.contains(<&DirectUserIdentifier>::from(user_id))
603 });
604
605 trace!(?room, "Found room");
606 room
607 }
608
609 async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
610 use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
611
612 match r.request() {
613 AnyOutgoingRequest::KeysQuery(request) => {
614 self.keys_query(r.request_id(), request.device_keys.clone()).await?;
615 }
616 AnyOutgoingRequest::KeysUpload(request) => {
617 self.keys_upload(r.request_id(), request).await?;
618 }
619 AnyOutgoingRequest::ToDeviceRequest(request) => {
620 let response = self.send_to_device(request).await?;
621 self.mark_request_as_sent(r.request_id(), &response).await?;
622 }
623 AnyOutgoingRequest::SignatureUpload(request) => {
624 let response = self.send(request.clone()).await?;
625 self.mark_request_as_sent(r.request_id(), &response).await?;
626 }
627 AnyOutgoingRequest::RoomMessage(request) => {
628 let response = self.room_send_helper(request).await?;
629 self.mark_request_as_sent(r.request_id(), &response).await?;
630 }
631 AnyOutgoingRequest::KeysClaim(request) => {
632 let response = self.send(request.clone()).await?;
633 self.mark_request_as_sent(r.request_id(), &response).await?;
634 }
635 }
636
637 Ok(())
638 }
639
640 pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
641 const MAX_CONCURRENT_REQUESTS: usize = 20;
642
643 if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
646 warn!("Error while claiming one-time keys {:?}", e);
647 }
648
649 let outgoing_requests = stream::iter(
650 self.olm_machine()
651 .await
652 .as_ref()
653 .ok_or(Error::NoOlmMachine)?
654 .outgoing_requests()
655 .await?,
656 )
657 .map(|r| self.send_outgoing_request(r));
658
659 let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
660
661 requests
662 .for_each(|r| async move {
663 match r {
664 Ok(_) => (),
665 Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
666 }
667 })
668 .await;
669
670 Ok(())
671 }
672}
673
674#[cfg(any(feature = "testing", test))]
675impl Client {
676 pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
678 self.olm_machine().await
679 }
680}
681
682#[derive(Debug, Clone)]
686pub struct Encryption {
687 client: Client,
689}
690
691impl Encryption {
692 pub(crate) fn new(client: Client) -> Self {
693 Self { client }
694 }
695
696 pub(crate) fn settings(&self) -> EncryptionSettings {
698 self.client.inner.e2ee.encryption_settings
699 }
700
701 pub async fn ed25519_key(&self) -> Option<String> {
704 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
705 }
706
707 pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
709 self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
710 }
711
712 pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
714 match self.get_own_device().await {
715 Ok(Some(device)) => device.first_time_seen_ts(),
716 _ => MilliSecondsSinceUnixEpoch::now(),
718 }
719 }
720
721 #[cfg(not(target_arch = "wasm32"))]
722 pub(crate) async fn import_secrets_bundle(
723 &self,
724 bundle: &matrix_sdk_base::crypto::types::SecretsBundle,
725 ) -> Result<(), SecretImportError> {
726 let olm_machine = self.client.olm_machine().await;
727 let olm_machine =
728 olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
729
730 olm_machine.store().import_secrets_bundle(bundle).await
731 }
732
733 pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
738 let olm = self.client.olm_machine().await;
739 let machine = olm.as_ref()?;
740 Some(machine.cross_signing_status().await)
741 }
742
743 pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
748 if let Some(machine) = self.client.olm_machine().await.as_ref() {
749 machine.tracked_users().await
750 } else {
751 Ok(HashSet::new())
752 }
753 }
754
755 pub fn verification_state(&self) -> Subscriber<VerificationState> {
778 self.client.inner.verification_state.subscribe_reset()
779 }
780
781 pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
783 let olm = self.client.olm_machine().await;
784 let olm = olm.as_ref()?;
785 #[allow(clippy::bind_instead_of_map)]
786 olm.get_verification(user_id, flow_id).and_then(|v| match v {
787 matrix_sdk_base::crypto::Verification::SasV1(s) => {
788 Some(SasVerification { inner: s, client: self.client.clone() }.into())
789 }
790 #[cfg(feature = "qrcode")]
791 matrix_sdk_base::crypto::Verification::QrV1(qr) => {
792 Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
793 }
794 _ => None,
795 })
796 }
797
798 pub async fn get_verification_request(
801 &self,
802 user_id: &UserId,
803 flow_id: impl AsRef<str>,
804 ) -> Option<VerificationRequest> {
805 let olm = self.client.olm_machine().await;
806 let olm = olm.as_ref()?;
807
808 olm.get_verification_request(user_id, flow_id)
809 .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
810 }
811
812 pub async fn get_device(
846 &self,
847 user_id: &UserId,
848 device_id: &DeviceId,
849 ) -> Result<Option<Device>, CryptoStoreError> {
850 let olm = self.client.olm_machine().await;
851 let Some(machine) = olm.as_ref() else { return Ok(None) };
852 let device = machine.get_device(user_id, device_id, None).await?;
853 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
854 }
855
856 pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
863 let olm = self.client.olm_machine().await;
864 let Some(machine) = olm.as_ref() else { return Ok(None) };
865 let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
866 Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
867 }
868
869 pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
895 let devices = self
896 .client
897 .olm_machine()
898 .await
899 .as_ref()
900 .ok_or(Error::NoOlmMachine)?
901 .get_user_devices(user_id, None)
902 .await?;
903
904 Ok(UserDevices { inner: devices, client: self.client.clone() })
905 }
906
907 pub async fn get_user_identity(
943 &self,
944 user_id: &UserId,
945 ) -> Result<Option<UserIdentity>, CryptoStoreError> {
946 let olm = self.client.olm_machine().await;
947 let Some(olm) = olm.as_ref() else { return Ok(None) };
948 let identity = olm.get_identity(user_id, None).await?;
949
950 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
951 }
952
953 pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
991 let olm = self.client.olm_machine().await;
992 let Some(olm) = olm.as_ref() else { return Ok(None) };
993
994 let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
995 self.client.keys_query(&request_id, request.device_keys).await?;
996
997 let identity = olm.get_identity(user_id, None).await?;
998 Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
999 }
1000
1001 pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates>> {
1032 let olm = self.client.olm_machine().await;
1033 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1034 let client = self.client.to_owned();
1035
1036 Ok(olm
1037 .store()
1038 .devices_stream()
1039 .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1040 }
1041
1042 pub async fn user_identities_stream(&self) -> Result<impl Stream<Item = IdentityUpdates>> {
1070 let olm = self.client.olm_machine().await;
1071 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1072 let client = self.client.to_owned();
1073
1074 Ok(olm
1075 .store()
1076 .user_identities_stream()
1077 .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1078 }
1079
1080 pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1119 let olm = self.client.olm_machine().await;
1120 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1121
1122 let CrossSigningBootstrapRequests {
1123 upload_signing_keys_req,
1124 upload_keys_req,
1125 upload_signatures_req,
1126 } = olm.bootstrap_cross_signing(false).await?;
1127
1128 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1129 auth: auth_data,
1130 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1131 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1132 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1133 });
1134
1135 if let Some(req) = upload_keys_req {
1136 self.client.send_outgoing_request(req).await?;
1137 }
1138 self.client.send(upload_signing_keys_req).await?;
1139 self.client.send(upload_signatures_req).await?;
1140
1141 Ok(())
1142 }
1143
1144 pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1181 let olm = self.client.olm_machine().await;
1182 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1183
1184 let CrossSigningBootstrapRequests {
1185 upload_keys_req,
1186 upload_signing_keys_req,
1187 upload_signatures_req,
1188 } = olm.bootstrap_cross_signing(true).await?;
1189
1190 let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1191 auth: None,
1192 master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1193 self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1194 user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1195 });
1196
1197 if let Some(req) = upload_keys_req {
1198 self.client.send_outgoing_request(req).await?;
1199 }
1200
1201 if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1202 if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1203 let client = self.client.clone();
1204
1205 Ok(Some(CrossSigningResetHandle::new(
1206 client,
1207 upload_signing_keys_req,
1208 upload_signatures_req,
1209 auth_type,
1210 )))
1211 } else {
1212 Err(error.into())
1213 }
1214 } else {
1215 self.client.send(upload_signatures_req).await?;
1216
1217 Ok(None)
1218 }
1219 }
1220
1221 async fn ensure_initial_key_query(&self) -> Result<()> {
1224 let olm_machine = self.client.olm_machine().await;
1225 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1226
1227 let user_id = olm_machine.user_id();
1228
1229 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1230 let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1231 self.client.keys_query(&request_id, request.device_keys).await?;
1232 }
1233
1234 Ok(())
1235 }
1236
1237 pub async fn bootstrap_cross_signing_if_needed(
1284 &self,
1285 auth_data: Option<AuthData>,
1286 ) -> Result<()> {
1287 let olm_machine = self.client.olm_machine().await;
1288 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1289 let user_id = olm_machine.user_id();
1290
1291 self.ensure_initial_key_query().await?;
1292
1293 if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1294 self.bootstrap_cross_signing(auth_data).await?;
1295 }
1296
1297 Ok(())
1298 }
1299
1300 #[cfg(not(target_arch = "wasm32"))]
1352 pub async fn export_room_keys(
1353 &self,
1354 path: PathBuf,
1355 passphrase: &str,
1356 predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1357 ) -> Result<()> {
1358 let olm = self.client.olm_machine().await;
1359 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1360
1361 let keys = olm.store().export_room_keys(predicate).await?;
1362 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1363
1364 let encrypt = move || -> Result<()> {
1365 let export: String =
1366 matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1367 let mut file = std::fs::File::create(path)?;
1368 file.write_all(&export.into_bytes())?;
1369 Ok(())
1370 };
1371
1372 let task = tokio::task::spawn_blocking(encrypt);
1373 task.await.expect("Task join error")
1374 }
1375
1376 #[cfg(not(target_arch = "wasm32"))]
1414 pub async fn import_room_keys(
1415 &self,
1416 path: PathBuf,
1417 passphrase: &str,
1418 ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1419 let olm = self.client.olm_machine().await;
1420 let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1421 let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1422
1423 let decrypt = move || {
1424 let file = std::fs::File::open(path)?;
1425 matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1426 };
1427
1428 let task = tokio::task::spawn_blocking(decrypt);
1429 let import = task.await.expect("Task join error")?;
1430
1431 let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1432
1433 self.backups().maybe_trigger_backup();
1434
1435 Ok(ret)
1436 }
1437
1438 pub async fn room_keys_received_stream(
1469 &self,
1470 ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>>> {
1471 let olm = self.client.olm_machine().await;
1472 let olm = olm.as_ref()?;
1473
1474 Some(olm.store().room_keys_received_stream())
1475 }
1476
1477 pub fn secret_storage(&self) -> SecretStorage {
1479 SecretStorage { client: self.client.to_owned() }
1480 }
1481
1482 pub fn backups(&self) -> Backups {
1484 Backups { client: self.client.to_owned() }
1485 }
1486
1487 pub fn recovery(&self) -> Recovery {
1489 Recovery { client: self.client.to_owned() }
1490 }
1491
1492 pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1504 if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1506 let prev_holder = prev_lock.lock_holder();
1507 if prev_holder == lock_value {
1508 return Ok(());
1509 }
1510 warn!("Recreating cross-process store lock with a different holder value: prev was {prev_holder}, new is {lock_value}");
1511 }
1512
1513 let olm_machine = self.client.base_client().olm_machine().await;
1514 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1515
1516 let lock =
1517 olm_machine.store().create_store_lock("cross_process_lock".to_owned(), lock_value);
1518
1519 {
1524 let guard = lock.try_lock_once().await?;
1525 if guard.is_some() {
1526 olm_machine
1527 .initialize_crypto_store_generation(
1528 &self.client.locks().crypto_store_generation,
1529 )
1530 .await?;
1531 }
1532 }
1533
1534 self.client
1535 .locks()
1536 .cross_process_crypto_store_lock
1537 .set(lock)
1538 .map_err(|_| Error::BadCryptoStoreState)?;
1539
1540 Ok(())
1541 }
1542
1543 async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
1548 let olm_machine_guard = self.client.olm_machine().await;
1549 if let Some(olm_machine) = olm_machine_guard.as_ref() {
1550 let (new_gen, generation_number) = olm_machine
1551 .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
1552 .await?;
1553 if new_gen {
1555 drop(olm_machine_guard);
1557 self.client.base_client().regenerate_olm(None).await?;
1559 }
1560 Ok(generation_number)
1561 } else {
1562 warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
1567 Ok(0)
1568 }
1569 }
1570
1571 pub async fn spin_lock_store(
1577 &self,
1578 max_backoff: Option<u32>,
1579 ) -> Result<Option<CrossProcessLockStoreGuardWithGeneration>, Error> {
1580 if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1581 let guard = lock.spin_lock(max_backoff).await?;
1582
1583 let generation = self.on_lock_newly_acquired().await?;
1584
1585 Ok(Some(CrossProcessLockStoreGuardWithGeneration { _guard: guard, generation }))
1586 } else {
1587 Ok(None)
1588 }
1589 }
1590
1591 pub async fn try_lock_store_once(
1596 &self,
1597 ) -> Result<Option<CrossProcessLockStoreGuardWithGeneration>, Error> {
1598 if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1599 let maybe_guard = lock.try_lock_once().await?;
1600
1601 let Some(guard) = maybe_guard else {
1602 return Ok(None);
1603 };
1604
1605 let generation = self.on_lock_newly_acquired().await?;
1606
1607 Ok(Some(CrossProcessLockStoreGuardWithGeneration { _guard: guard, generation }))
1608 } else {
1609 Ok(None)
1610 }
1611 }
1612
1613 #[cfg(any(test, feature = "testing"))]
1615 pub async fn uploaded_key_count(&self) -> Result<u64> {
1616 let olm_machine = self.client.olm_machine().await;
1617 let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
1618 Ok(olm_machine.uploaded_key_count().await?)
1619 }
1620
1621 pub(crate) fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
1645 let mut tasks = self.client.inner.e2ee.tasks.lock();
1646
1647 let this = self.clone();
1648 tasks.setup_e2ee = Some(spawn(async move {
1649 this.update_verification_state().await;
1652
1653 if this.settings().auto_enable_cross_signing {
1654 if let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await {
1655 error!("Couldn't bootstrap cross signing {e:?}");
1656 }
1657 }
1658
1659 if let Err(e) = this.backups().setup_and_resume().await {
1660 error!("Couldn't setup and resume backups {e:?}");
1661 }
1662 if let Err(e) = this.recovery().setup().await {
1663 error!("Couldn't setup and resume recovery {e:?}");
1664 }
1665 }));
1666 }
1667
1668 pub async fn wait_for_e2ee_initialization_tasks(&self) {
1671 let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
1672
1673 if let Some(task) = task {
1674 if let Err(err) = task.await {
1675 warn!("Error when initializing backups: {err}");
1676 }
1677 }
1678 }
1679
1680 #[cfg(not(target_arch = "wasm32"))]
1690 pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
1691 let olm = self.client.olm_machine().await;
1692 let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1693
1694 if let Some((request_id, request)) = olm.upload_device_keys().await? {
1695 self.client.keys_upload(&request_id, &request).await?;
1696
1697 let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
1698 self.client.keys_query(&request_id, request.device_keys).await?;
1699 }
1700
1701 Ok(())
1702 }
1703
1704 pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
1705 self.recovery().update_state_after_keys_query(response).await;
1706
1707 if let Some(user_id) = self.client.user_id() {
1709 let contains_own_device = response.device_keys.contains_key(user_id);
1710
1711 if contains_own_device {
1712 self.update_verification_state().await;
1713 }
1714 }
1715 }
1716
1717 async fn update_verification_state(&self) {
1718 match self.get_own_device().await {
1719 Ok(device) => {
1720 if let Some(device) = device {
1721 let is_verified = device.is_cross_signed_by_owner();
1722
1723 if is_verified {
1724 self.client.inner.verification_state.set(VerificationState::Verified);
1725 } else {
1726 self.client.inner.verification_state.set(VerificationState::Unverified);
1727 }
1728 } else {
1729 warn!("Couldn't find out own device in the store.");
1730 self.client.inner.verification_state.set(VerificationState::Unknown);
1731 }
1732 }
1733 Err(error) => {
1734 warn!("Failed retrieving own device: {error}");
1735 self.client.inner.verification_state.set(VerificationState::Unknown);
1736 }
1737 }
1738 }
1739}
1740
1741#[cfg(all(test, not(target_arch = "wasm32")))]
1742mod tests {
1743 use std::{
1744 ops::Not,
1745 sync::{
1746 atomic::{AtomicBool, Ordering},
1747 Arc,
1748 },
1749 time::Duration,
1750 };
1751
1752 use matrix_sdk_test::{
1753 async_test, test_json, GlobalAccountDataTestEvent, JoinedRoomBuilder, StateTestEvent,
1754 SyncResponseBuilder, DEFAULT_TEST_ROOM_ID,
1755 };
1756 use ruma::{
1757 event_id,
1758 events::{reaction::ReactionEventContent, relation::Annotation},
1759 user_id,
1760 };
1761 use serde_json::json;
1762 use wiremock::{
1763 matchers::{header, method, path_regex},
1764 Mock, MockServer, Request, ResponseTemplate,
1765 };
1766
1767 use crate::{
1768 assert_next_matches_with_timeout,
1769 config::RequestConfig,
1770 encryption::{OAuthCrossSigningResetInfo, VerificationState},
1771 test_utils::{
1772 client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
1773 },
1774 Client,
1775 };
1776
1777 #[async_test]
1778 async fn test_reaction_sending() {
1779 let server = MockServer::start().await;
1780 let client = logged_in_client(Some(server.uri())).await;
1781
1782 let event_id = event_id!("$2:example.org");
1783
1784 Mock::given(method("GET"))
1785 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
1786 .and(header("authorization", "Bearer 1234"))
1787 .respond_with(
1788 ResponseTemplate::new(200)
1789 .set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT),
1790 )
1791 .mount(&server)
1792 .await;
1793
1794 Mock::given(method("PUT"))
1795 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
1796 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1797 "event_id": event_id,
1798 })))
1799 .mount(&server)
1800 .await;
1801
1802 let response = SyncResponseBuilder::default()
1803 .add_joined_room(
1804 JoinedRoomBuilder::default()
1805 .add_state_event(StateTestEvent::Member)
1806 .add_state_event(StateTestEvent::PowerLevels)
1807 .add_state_event(StateTestEvent::Encryption),
1808 )
1809 .build_sync_response();
1810
1811 client.base_client().receive_sync_response(response).await.unwrap();
1812
1813 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
1814 assert!(room
1815 .latest_encryption_state()
1816 .await
1817 .expect("Getting encryption state")
1818 .is_encrypted());
1819
1820 let event_id = event_id!("$1:example.org");
1821 let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
1822 room.send(reaction).await.expect("Sending the reaction should not fail");
1823
1824 room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
1825 }
1826
1827 #[async_test]
1828 async fn test_get_dm_room_returns_the_room_we_have_with_this_user() {
1829 let server = MockServer::start().await;
1830 let client = logged_in_client(Some(server.uri())).await;
1831 let user_id = user_id!("@invited:localhost");
1835
1836 let response = SyncResponseBuilder::default()
1838 .add_joined_room(
1839 JoinedRoomBuilder::default().add_state_event(StateTestEvent::MemberAdditional),
1840 )
1841 .add_global_account_data_event(GlobalAccountDataTestEvent::Direct)
1842 .build_sync_response();
1843 client.base_client().receive_sync_response(response).await.unwrap();
1844
1845 let found_room = client.get_dm_room(user_id).expect("DM not found!");
1847 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
1848 }
1849
1850 #[async_test]
1851 async fn test_get_dm_room_still_finds_room_where_participant_is_only_invited() {
1852 let server = MockServer::start().await;
1853 let client = logged_in_client(Some(server.uri())).await;
1854 let user_id = user_id!("@invited:localhost");
1856
1857 let response = SyncResponseBuilder::default()
1859 .add_joined_room(
1860 JoinedRoomBuilder::default().add_state_event(StateTestEvent::MemberInvite),
1861 )
1862 .add_global_account_data_event(GlobalAccountDataTestEvent::Direct)
1863 .build_sync_response();
1864 client.base_client().receive_sync_response(response).await.unwrap();
1865
1866 let found_room = client.get_dm_room(user_id).expect("DM not found!");
1868 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
1869 }
1870
1871 #[async_test]
1872 async fn test_get_dm_room_still_finds_left_room() {
1873 let server = MockServer::start().await;
1877 let client = logged_in_client(Some(server.uri())).await;
1878 let user_id = user_id!("@invited:localhost");
1882
1883 let response = SyncResponseBuilder::default()
1885 .add_joined_room(
1886 JoinedRoomBuilder::default().add_state_event(StateTestEvent::MemberLeave),
1887 )
1888 .add_global_account_data_event(GlobalAccountDataTestEvent::Direct)
1889 .build_sync_response();
1890 client.base_client().receive_sync_response(response).await.unwrap();
1891
1892 let found_room = client.get_dm_room(user_id).expect("DM not found!");
1894 assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
1895 }
1896
1897 #[cfg(feature = "sqlite")]
1898 #[async_test]
1899 async fn test_generation_counter_invalidates_olm_machine() {
1900 use matrix_sdk_base::store::RoomLoadSettings;
1903 let sqlite_path = std::env::temp_dir().join("generation_counter_sqlite.db");
1904 let session = mock_matrix_session();
1905
1906 let client1 = Client::builder()
1907 .homeserver_url("http://localhost:1234")
1908 .request_config(RequestConfig::new().disable_retry())
1909 .sqlite_store(&sqlite_path, None)
1910 .build()
1911 .await
1912 .unwrap();
1913 client1
1914 .matrix_auth()
1915 .restore_session(session.clone(), RoomLoadSettings::default())
1916 .await
1917 .unwrap();
1918
1919 let client2 = Client::builder()
1920 .homeserver_url("http://localhost:1234")
1921 .request_config(RequestConfig::new().disable_retry())
1922 .sqlite_store(sqlite_path, None)
1923 .build()
1924 .await
1925 .unwrap();
1926 client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
1927
1928 let guard = client1.encryption().try_lock_store_once().await.unwrap();
1930 assert!(guard.is_none());
1931
1932 client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
1933 client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
1934
1935 let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
1937 assert!(acquired1.is_some());
1938
1939 let initial_olm_machine =
1941 client1.olm_machine().await.clone().expect("must have an olm machine");
1942
1943 let decryption_key = matrix_sdk_base::crypto::store::BackupDecryptionKey::new()
1945 .expect("Can't create new recovery key");
1946 let backup_key = decryption_key.megolm_v1_public_key();
1947 backup_key.set_version("1".to_owned());
1948 initial_olm_machine
1949 .backup_machine()
1950 .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
1951 .await
1952 .expect("Should save");
1953
1954 initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
1955
1956 assert!(client1.encryption().backups().are_enabled().await);
1957
1958 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
1960 assert!(acquired2.is_none());
1961
1962 drop(acquired1);
1964 tokio::time::sleep(Duration::from_millis(100)).await;
1965
1966 let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
1968 assert!(acquired1.is_some());
1969
1970 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
1972 assert!(initial_olm_machine.same_as(&olm_machine));
1973
1974 drop(acquired1);
1976 tokio::time::sleep(Duration::from_millis(100)).await;
1977
1978 let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
1980 assert!(acquired2.is_some());
1981
1982 drop(acquired2);
1984 tokio::time::sleep(Duration::from_millis(100)).await;
1985
1986 let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
1988 assert!(acquired1.is_some());
1989
1990 let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
1992
1993 assert!(!initial_olm_machine.same_as(&olm_machine));
1994
1995 let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
1996 assert!(backup_key_new.decryption_key.is_some());
1997 assert_eq!(
1998 backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
1999 backup_key.to_base64()
2000 );
2001 assert!(client1.encryption().backups().are_enabled().await);
2002 }
2003
2004 #[cfg(feature = "sqlite")]
2005 #[async_test]
2006 async fn test_generation_counter_no_spurious_invalidation() {
2007 use matrix_sdk_base::store::RoomLoadSettings;
2010 let sqlite_path =
2011 std::env::temp_dir().join("generation_counter_no_spurious_invalidations.db");
2012 let session = mock_matrix_session();
2013
2014 let client = Client::builder()
2015 .homeserver_url("http://localhost:1234")
2016 .request_config(RequestConfig::new().disable_retry())
2017 .sqlite_store(&sqlite_path, None)
2018 .build()
2019 .await
2020 .unwrap();
2021 client
2022 .matrix_auth()
2023 .restore_session(session.clone(), RoomLoadSettings::default())
2024 .await
2025 .unwrap();
2026
2027 let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2028
2029 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2030
2031 let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2033 assert!(initial_olm_machine.same_as(&after_enabling_lock));
2034
2035 {
2036 let client2 = Client::builder()
2038 .homeserver_url("http://localhost:1234")
2039 .request_config(RequestConfig::new().disable_retry())
2040 .sqlite_store(sqlite_path, None)
2041 .build()
2042 .await
2043 .unwrap();
2044 client2
2045 .matrix_auth()
2046 .restore_session(session, RoomLoadSettings::default())
2047 .await
2048 .unwrap();
2049
2050 client2
2051 .encryption()
2052 .enable_cross_process_store_lock("client2".to_owned())
2053 .await
2054 .unwrap();
2055
2056 let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2057 assert!(guard.is_some());
2058
2059 drop(guard);
2060 tokio::time::sleep(Duration::from_millis(100)).await;
2061 }
2062
2063 {
2064 let acquired = client.encryption().try_lock_store_once().await.unwrap();
2065 assert!(acquired.is_some());
2066 }
2067
2068 let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2070 assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2071
2072 {
2073 let acquired = client.encryption().try_lock_store_once().await.unwrap();
2074 assert!(acquired.is_some());
2075 }
2076
2077 let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2079 assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2080 }
2081
2082 #[async_test]
2083 async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2084 let client = no_retry_test_client(None).await;
2086 let server = MockServer::start().await;
2087
2088 let mut verification_state = client.encryption().verification_state();
2090
2091 assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2093
2094 let keys_requested = Arc::new(AtomicBool::new(false));
2097 let inner_bool = keys_requested.clone();
2098
2099 Mock::given(method("GET"))
2100 .and(path_regex(
2101 r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2102 ))
2103 .respond_with(move |_req: &Request| {
2104 inner_bool.fetch_or(true, Ordering::SeqCst);
2105 ResponseTemplate::new(200).set_body_json(json!({}))
2106 })
2107 .mount(&server)
2108 .await;
2109
2110 set_client_session(&client).await;
2112
2113 assert!(keys_requested.load(Ordering::SeqCst).not());
2115 assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2116 }
2117
2118 #[test]
2119 fn test_oauth_reset_info_from_uiaa_info() {
2120 let auth_info = json!({
2121 "session": "dummy",
2122 "flows": [
2123 {
2124 "stages": [
2125 "org.matrix.cross_signing_reset"
2126 ]
2127 }
2128 ],
2129 "params": {
2130 "org.matrix.cross_signing_reset": {
2131 "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2132 }
2133 },
2134 "msg": "To reset..."
2135 });
2136
2137 let auth_info = serde_json::from_value(auth_info)
2138 .expect("We should be able to deserialize the UiaaInfo");
2139 OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2140 .expect("We should be able to fetch the cross-signing reset info from the auth info");
2141 }
2142}