matrix_sdk/encryption/
mod.rs

1// Copyright 2021 The Matrix.org Foundation C.I.C.
2// Copyright 2021 Damir Jelić
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![doc = include_str!("../docs/encryption.md")]
17#![cfg_attr(target_family = "wasm", allow(unused_imports))]
18
19#[cfg(feature = "experimental-send-custom-to-device")]
20use std::ops::Deref;
21use std::{
22    collections::{BTreeMap, HashSet},
23    io::{Cursor, Read, Write},
24    iter,
25    path::PathBuf,
26    sync::Arc,
27};
28
29use eyeball::{SharedObservable, Subscriber};
30use futures_core::Stream;
31use futures_util::{
32    future::try_join,
33    stream::{self, StreamExt},
34};
35#[cfg(feature = "experimental-send-custom-to-device")]
36use matrix_sdk_base::crypto::CollectStrategy;
37use matrix_sdk_base::crypto::{
38    store::types::{RoomKeyBundleInfo, RoomKeyInfo},
39    types::requests::{
40        OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
41    },
42    CrossSigningBootstrapRequests, OlmMachine,
43};
44use matrix_sdk_common::{executor::spawn, locks::Mutex as StdMutex};
45use ruma::{
46    api::client::{
47        error::ErrorBody,
48        keys::{
49            get_keys, upload_keys, upload_signatures::v3::Request as UploadSignaturesRequest,
50            upload_signing_keys::v3::Request as UploadSigningKeysRequest,
51        },
52        message::send_message_event,
53        to_device::send_event_to_device::v3::{
54            Request as RumaToDeviceRequest, Response as ToDeviceResponse,
55        },
56        uiaa::{AuthData, UiaaInfo},
57    },
58    assign,
59    events::{
60        direct::DirectUserIdentifier,
61        room::{MediaSource, ThumbnailInfo},
62    },
63    DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, TransactionId, UserId,
64};
65#[cfg(feature = "experimental-send-custom-to-device")]
66use ruma::{events::AnyToDeviceEventContent, serde::Raw, to_device::DeviceIdOrAllDevices};
67use serde::Deserialize;
68use tasks::BundleReceiverTask;
69use tokio::sync::{Mutex, RwLockReadGuard};
70use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
71use tracing::{debug, error, instrument, trace, warn};
72use url::Url;
73use vodozemac::Curve25519PublicKey;
74
75use self::{
76    backups::{types::BackupClientState, Backups},
77    futures::UploadEncryptedFile,
78    identities::{Device, DeviceUpdates, IdentityUpdates, UserDevices, UserIdentity},
79    recovery::{Recovery, RecoveryState},
80    secret_storage::SecretStorage,
81    tasks::{BackupDownloadTask, BackupUploadingTask, ClientTasks},
82    verification::{SasVerification, Verification, VerificationRequest},
83};
84use crate::{
85    attachment::Thumbnail,
86    client::{ClientInner, WeakClient},
87    error::HttpResult,
88    store_locks::CrossProcessStoreLockGuard,
89    Client, Error, HttpError, Result, Room, RumaApiError, TransmissionProgress,
90};
91
92pub mod backups;
93pub mod futures;
94pub mod identities;
95pub mod recovery;
96pub mod secret_storage;
97pub(crate) mod tasks;
98pub mod verification;
99
100pub use matrix_sdk_base::crypto::{
101    olm::{
102        SessionCreationError as MegolmSessionCreationError,
103        SessionExportError as OlmSessionExportError,
104    },
105    vodozemac, CrossSigningStatus, CryptoStoreError, DecryptorError, EventError, KeyExportError,
106    LocalTrust, MediaEncryptionInfo, MegolmError, OlmError, RoomKeyImportResult, SecretImportError,
107    SessionCreationError, SignatureError, VERSION,
108};
109
110#[cfg(feature = "experimental-send-custom-to-device")]
111use crate::config::RequestConfig;
112pub use crate::error::RoomKeyImportError;
113
114/// All the data related to the encryption state.
115pub(crate) struct EncryptionData {
116    /// Background tasks related to encryption (key backup, initialization
117    /// tasks, etc.).
118    pub tasks: StdMutex<ClientTasks>,
119
120    /// End-to-end encryption settings.
121    pub encryption_settings: EncryptionSettings,
122
123    /// All state related to key backup.
124    pub backup_state: BackupClientState,
125
126    /// All state related to secret storage recovery.
127    pub recovery_state: SharedObservable<RecoveryState>,
128}
129
130impl EncryptionData {
131    pub fn new(encryption_settings: EncryptionSettings) -> Self {
132        Self {
133            encryption_settings,
134
135            tasks: StdMutex::new(Default::default()),
136            backup_state: Default::default(),
137            recovery_state: Default::default(),
138        }
139    }
140
141    pub fn initialize_tasks(&self, client: &Arc<ClientInner>) {
142        let weak_client = WeakClient::from_inner(client);
143
144        let mut tasks = self.tasks.lock();
145        tasks.upload_room_keys = Some(BackupUploadingTask::new(weak_client.clone()));
146
147        if self.encryption_settings.backup_download_strategy
148            == BackupDownloadStrategy::AfterDecryptionFailure
149        {
150            tasks.download_room_keys = Some(BackupDownloadTask::new(weak_client));
151        }
152    }
153
154    /// Initialize the background task which listens for changes in the
155    /// [`backups::BackupState`] and updataes the [`recovery::RecoveryState`].
156    ///
157    /// This should happen after the usual tasks have been set up and after the
158    /// E2EE initialization tasks have been set up.
159    pub fn initialize_recovery_state_update_task(&self, client: &Client) {
160        let mut guard = self.tasks.lock();
161
162        let future = Recovery::update_state_after_backup_state_change(client);
163        let join_handle = spawn(future);
164
165        guard.update_recovery_state_after_backup = Some(join_handle);
166    }
167}
168
169/// Settings for end-to-end encryption features.
170#[derive(Clone, Copy, Debug, Default)]
171pub struct EncryptionSettings {
172    /// Automatically bootstrap cross-signing for a user once they're logged, in
173    /// case it's not already done yet.
174    ///
175    /// This requires to login with a username and password, or that MSC3967 is
176    /// enabled on the server, as of 2023-10-20.
177    pub auto_enable_cross_signing: bool,
178
179    /// Select a strategy to download room keys from the backup, by default room
180    /// keys won't be downloaded from the backup automatically.
181    ///
182    /// Take a look at the [`BackupDownloadStrategy`] enum for more options.
183    pub backup_download_strategy: BackupDownloadStrategy,
184
185    /// Automatically create a backup version if no backup exists.
186    pub auto_enable_backups: bool,
187}
188
189/// Settings for end-to-end encryption features.
190#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
191#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
192pub enum BackupDownloadStrategy {
193    /// Automatically download all room keys from the backup when the backup
194    /// recovery key has been received. The backup recovery key can be received
195    /// in two ways:
196    ///
197    /// 1. Received as a `m.secret.send` to-device event, after a successful
198    ///    interactive verification.
199    /// 2. Imported from secret storage (4S) using the
200    ///    [`SecretStore::import_secrets()`] method.
201    ///
202    /// [`SecretStore::import_secrets()`]: crate::encryption::secret_storage::SecretStore::import_secrets
203    OneShot,
204
205    /// Attempt to download a single room key if an event fails to be decrypted.
206    AfterDecryptionFailure,
207
208    /// Don't download any room keys automatically. The user can manually
209    /// download room keys using the [`Backups::download_room_key()`] methods.
210    ///
211    /// This is the default option.
212    #[default]
213    Manual,
214}
215
216/// The verification state of our own device
217///
218/// This enum tells us if our own user identity trusts these devices, in other
219/// words it tells us if the user identity has signed the device.
220#[derive(Clone, Copy, Debug, Eq, PartialEq)]
221pub enum VerificationState {
222    /// The verification state is unknown for now.
223    Unknown,
224    /// The device is considered to be verified, it has been signed by its user
225    /// identity.
226    Verified,
227    /// The device is unverified.
228    Unverified,
229}
230
231/// Wraps together a `CrossProcessLockStoreGuard` and a generation number.
232#[derive(Debug)]
233pub struct CrossProcessLockStoreGuardWithGeneration {
234    _guard: CrossProcessStoreLockGuard,
235    generation: u64,
236}
237
238impl CrossProcessLockStoreGuardWithGeneration {
239    /// Return the Crypto Store generation associated with this store lock.
240    pub fn generation(&self) -> u64 {
241        self.generation
242    }
243}
244
245/// A stateful struct remembering the cross-signing keys we need to upload.
246///
247/// Since the `/_matrix/client/v3/keys/device_signing/upload` might require
248/// additional authentication, this struct will contain information on the type
249/// of authentication the user needs to complete before the upload might be
250/// continued.
251///
252/// More info can be found in the [spec].
253///
254/// [spec]: https://spec.matrix.org/v1.11/client-server-api/#post_matrixclientv3keysdevice_signingupload
255#[derive(Debug)]
256pub struct CrossSigningResetHandle {
257    client: Client,
258    upload_request: UploadSigningKeysRequest,
259    signatures_request: UploadSignaturesRequest,
260    auth_type: CrossSigningResetAuthType,
261    is_cancelled: Mutex<bool>,
262}
263
264impl CrossSigningResetHandle {
265    /// Set up a new `CrossSigningResetHandle`.
266    pub fn new(
267        client: Client,
268        upload_request: UploadSigningKeysRequest,
269        signatures_request: UploadSignaturesRequest,
270        auth_type: CrossSigningResetAuthType,
271    ) -> Self {
272        Self {
273            client,
274            upload_request,
275            signatures_request,
276            auth_type,
277            is_cancelled: Mutex::new(false),
278        }
279    }
280
281    /// Get the [`CrossSigningResetAuthType`] this cross-signing reset process
282    /// is using.
283    pub fn auth_type(&self) -> &CrossSigningResetAuthType {
284        &self.auth_type
285    }
286
287    /// Continue the cross-signing reset by either waiting for the
288    /// authentication to be done on the side of the OAuth 2.0 server or by
289    /// providing additional [`AuthData`] the homeserver requires.
290    pub async fn auth(&self, auth: Option<AuthData>) -> Result<()> {
291        let mut upload_request = self.upload_request.clone();
292        upload_request.auth = auth;
293
294        while let Err(e) = self.client.send(upload_request.clone()).await {
295            if *self.is_cancelled.lock().await {
296                return Ok(());
297            }
298
299            match e.as_uiaa_response() {
300                Some(uiaa_info) => {
301                    if uiaa_info.auth_error.is_some() {
302                        return Err(e.into());
303                    }
304                }
305                None => return Err(e.into()),
306            }
307        }
308
309        self.client.send(self.signatures_request.clone()).await?;
310
311        Ok(())
312    }
313
314    /// Cancel the ongoing identity reset process
315    pub async fn cancel(&self) {
316        *self.is_cancelled.lock().await = true;
317    }
318}
319
320/// information about the additional authentication that is required before the
321/// cross-signing keys can be uploaded.
322#[derive(Debug, Clone)]
323pub enum CrossSigningResetAuthType {
324    /// The homeserver requires user-interactive authentication.
325    Uiaa(UiaaInfo),
326    /// OAuth 2.0 is used for authentication and the user needs to open a URL to
327    /// approve the upload of cross-signing keys.
328    OAuth(OAuthCrossSigningResetInfo),
329}
330
331impl CrossSigningResetAuthType {
332    fn new(error: &HttpError) -> Result<Option<Self>> {
333        if let Some(auth_info) = error.as_uiaa_response() {
334            if let Ok(auth_info) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
335                Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
336            } else {
337                Ok(Some(CrossSigningResetAuthType::Uiaa(auth_info.clone())))
338            }
339        } else {
340            Ok(None)
341        }
342    }
343}
344
345/// OAuth 2.0 specific information about the required authentication for the
346/// upload of cross-signing keys.
347#[derive(Debug, Clone, Deserialize)]
348pub struct OAuthCrossSigningResetInfo {
349    /// The URL where the user can approve the reset of the cross-signing keys.
350    pub approval_url: Url,
351}
352
353impl OAuthCrossSigningResetInfo {
354    fn from_auth_info(auth_info: &UiaaInfo) -> Result<Self> {
355        let parameters = serde_json::from_str::<OAuthCrossSigningResetUiaaParameters>(
356            auth_info.params.as_ref().map(|value| value.get()).unwrap_or_default(),
357        )?;
358
359        Ok(OAuthCrossSigningResetInfo { approval_url: parameters.reset.url })
360    }
361}
362
363/// The parsed `parameters` part of a [`ruma::api::client::uiaa::UiaaInfo`]
364/// response
365#[derive(Debug, Deserialize)]
366struct OAuthCrossSigningResetUiaaParameters {
367    /// The URL where the user can approve the reset of the cross-signing keys.
368    #[serde(rename = "org.matrix.cross_signing_reset")]
369    reset: OAuthCrossSigningResetUiaaResetParameter,
370}
371
372/// The `org.matrix.cross_signing_reset` part of the Uiaa response `parameters``
373/// dictionary.
374#[derive(Debug, Deserialize)]
375struct OAuthCrossSigningResetUiaaResetParameter {
376    /// The URL where the user can approve the reset of the cross-signing keys.
377    url: Url,
378}
379
380impl Client {
381    pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
382        self.base_client().olm_machine().await
383    }
384
385    pub(crate) async fn mark_request_as_sent(
386        &self,
387        request_id: &TransactionId,
388        response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
389    ) -> Result<(), matrix_sdk_base::Error> {
390        Ok(self
391            .olm_machine()
392            .await
393            .as_ref()
394            .expect(
395                "We should have an olm machine once we try to mark E2EE related requests as sent",
396            )
397            .mark_request_as_sent(request_id, response)
398            .await?)
399    }
400
401    /// Query the server for users device keys.
402    ///
403    /// # Panics
404    ///
405    /// Panics if no key query needs to be done.
406    #[instrument(skip(self, device_keys))]
407    pub(crate) async fn keys_query(
408        &self,
409        request_id: &TransactionId,
410        device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
411    ) -> Result<get_keys::v3::Response> {
412        let request = assign!(get_keys::v3::Request::new(), { device_keys });
413
414        let response = self.send(request).await?;
415        self.mark_request_as_sent(request_id, &response).await?;
416        self.encryption().update_state_after_keys_query(&response).await;
417
418        Ok(response)
419    }
420
421    /// Construct a [`EncryptedFile`][ruma::events::room::EncryptedFile] by
422    /// encrypting and uploading a provided reader.
423    ///
424    /// # Arguments
425    ///
426    /// * `content_type` - The content type of the file.
427    /// * `reader` - The reader that should be encrypted and uploaded.
428    ///
429    /// # Examples
430    ///
431    /// ```no_run
432    /// # use matrix_sdk::Client;
433    /// # use url::Url;
434    /// # use matrix_sdk::ruma::{room_id, OwnedRoomId};
435    /// use serde::{Deserialize, Serialize};
436    /// use matrix_sdk::ruma::events::{macros::EventContent, room::EncryptedFile};
437    ///
438    /// #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
439    /// #[ruma_event(type = "com.example.custom", kind = MessageLike)]
440    /// struct CustomEventContent {
441    ///     encrypted_file: EncryptedFile,
442    /// }
443    ///
444    /// # async {
445    /// # let homeserver = Url::parse("http://example.com")?;
446    /// # let client = Client::new(homeserver).await?;
447    /// # let room = client.get_room(&room_id!("!test:example.com")).unwrap();
448    /// let mut reader = std::io::Cursor::new(b"Hello, world!");
449    /// let encrypted_file = client.upload_encrypted_file(&mut reader).await?;
450    ///
451    /// room.send(CustomEventContent { encrypted_file }).await?;
452    /// # anyhow::Ok(()) };
453    /// ```
454    pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
455        &'a self,
456        reader: &'a mut R,
457    ) -> UploadEncryptedFile<'a, R> {
458        UploadEncryptedFile::new(self, reader)
459    }
460
461    /// Encrypt and upload the file and thumbnails, and return the source
462    /// information.
463    pub(crate) async fn upload_encrypted_media_and_thumbnail(
464        &self,
465        data: &[u8],
466        thumbnail: Option<Thumbnail>,
467        send_progress: SharedObservable<TransmissionProgress>,
468    ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
469        let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
470
471        let upload_attachment = async {
472            let mut cursor = Cursor::new(data);
473            self.upload_encrypted_file(&mut cursor)
474                .with_send_progress_observable(send_progress)
475                .await
476        };
477
478        let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
479
480        Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
481    }
482
483    /// Uploads an encrypted thumbnail to the media repository, and returns
484    /// its source and extra information.
485    async fn upload_encrypted_thumbnail(
486        &self,
487        thumbnail: Option<Thumbnail>,
488        send_progress: SharedObservable<TransmissionProgress>,
489    ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
490        let Some(thumbnail) = thumbnail else {
491            return Ok(None);
492        };
493
494        let (data, _, thumbnail_info) = thumbnail.into_parts();
495        let mut cursor = Cursor::new(data);
496
497        let file = self
498            .upload_encrypted_file(&mut cursor)
499            .with_send_progress_observable(send_progress)
500            .await?;
501
502        Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
503    }
504
505    /// Claim one-time keys creating new Olm sessions.
506    ///
507    /// # Arguments
508    ///
509    /// * `users` - The list of user/device pairs that we should claim keys for.
510    pub(crate) async fn claim_one_time_keys(
511        &self,
512        users: impl Iterator<Item = &UserId>,
513    ) -> Result<()> {
514        let _lock = self.locks().key_claim_lock.lock().await;
515
516        if let Some((request_id, request)) = self
517            .olm_machine()
518            .await
519            .as_ref()
520            .ok_or(Error::NoOlmMachine)?
521            .get_missing_sessions(users)
522            .await?
523        {
524            let response = self.send(request).await?;
525            self.mark_request_as_sent(&request_id, &response).await?;
526        }
527
528        Ok(())
529    }
530
531    /// Upload the E2E encryption keys.
532    ///
533    /// This uploads the long lived device keys as well as the required amount
534    /// of one-time keys.
535    ///
536    /// # Panics
537    ///
538    /// Panics if the client isn't logged in, or if no encryption keys need to
539    /// be uploaded.
540    #[instrument(skip(self, request))]
541    pub(crate) async fn keys_upload(
542        &self,
543        request_id: &TransactionId,
544        request: &upload_keys::v3::Request,
545    ) -> Result<upload_keys::v3::Response> {
546        debug!(
547            device_keys = request.device_keys.is_some(),
548            one_time_key_count = request.one_time_keys.len(),
549            "Uploading public encryption keys",
550        );
551
552        let response = self.send(request.clone()).await?;
553        self.mark_request_as_sent(request_id, &response).await?;
554
555        Ok(response)
556    }
557
558    pub(crate) async fn room_send_helper(
559        &self,
560        request: &RoomMessageRequest,
561    ) -> Result<send_message_event::v3::Response> {
562        let content = request.content.clone();
563        let txn_id = request.txn_id.clone();
564        let room_id = &request.room_id;
565
566        self.get_room(room_id)
567            .expect("Can't send a message to a room that isn't known to the store")
568            .send(*content)
569            .with_transaction_id(txn_id)
570            .await
571    }
572
573    pub(crate) async fn send_to_device(
574        &self,
575        request: &ToDeviceRequest,
576    ) -> HttpResult<ToDeviceResponse> {
577        let request = RumaToDeviceRequest::new_raw(
578            request.event_type.clone(),
579            request.txn_id.clone(),
580            request.messages.clone(),
581        );
582
583        self.send(request).await
584    }
585
586    pub(crate) async fn send_verification_request(
587        &self,
588        request: OutgoingVerificationRequest,
589    ) -> Result<()> {
590        use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
591
592        match request {
593            ToDevice(t) => {
594                self.send_to_device(&t).await?;
595            }
596            InRoom(r) => {
597                self.room_send_helper(&r).await?;
598            }
599        }
600
601        Ok(())
602    }
603
604    /// Get the existing DM room with the given user, if any.
605    pub fn get_dm_room(&self, user_id: &UserId) -> Option<Room> {
606        let rooms = self.joined_rooms();
607
608        // Find the room we share with the `user_id` and only with `user_id`
609        let room = rooms.into_iter().find(|r| {
610            let targets = r.direct_targets();
611            targets.len() == 1 && targets.contains(<&DirectUserIdentifier>::from(user_id))
612        });
613
614        trace!(?room, "Found room");
615        room
616    }
617
618    async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
619        use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
620
621        match r.request() {
622            AnyOutgoingRequest::KeysQuery(request) => {
623                self.keys_query(r.request_id(), request.device_keys.clone()).await?;
624            }
625            AnyOutgoingRequest::KeysUpload(request) => {
626                self.keys_upload(r.request_id(), request).await.inspect_err(|e| {
627                    match e.as_ruma_api_error() {
628                        Some(RumaApiError::ClientApi(e)) if e.status_code == 400 => {
629                            if let ErrorBody::Standard { message, .. } = &e.body {
630                                // This is one of the nastiest errors we can have. The server
631                                // telling us that we already have a one-time key uploaded means
632                                // that we forgot about some of our one-time keys. This will lead to
633                                // UTDs.
634                                if message.starts_with("One time key") {
635                                    tracing::error!(
636                                        sentry = true,
637                                        error_message = message,
638                                        "Duplicate one-time keys have been uploaded"
639                                    );
640                                }
641                            }
642                        }
643                        _ => {}
644                    }
645                })?;
646            }
647            AnyOutgoingRequest::ToDeviceRequest(request) => {
648                let response = self.send_to_device(request).await?;
649                self.mark_request_as_sent(r.request_id(), &response).await?;
650            }
651            AnyOutgoingRequest::SignatureUpload(request) => {
652                let response = self.send(request.clone()).await?;
653                self.mark_request_as_sent(r.request_id(), &response).await?;
654            }
655            AnyOutgoingRequest::RoomMessage(request) => {
656                let response = self.room_send_helper(request).await?;
657                self.mark_request_as_sent(r.request_id(), &response).await?;
658            }
659            AnyOutgoingRequest::KeysClaim(request) => {
660                let response = self.send(request.clone()).await?;
661                self.mark_request_as_sent(r.request_id(), &response).await?;
662            }
663        }
664
665        Ok(())
666    }
667
668    #[instrument(skip_all)]
669    pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
670        const MAX_CONCURRENT_REQUESTS: usize = 20;
671
672        // This is needed because sometimes we need to automatically
673        // claim some one-time keys to unwedge an existing Olm session.
674        if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
675            warn!("Error while claiming one-time keys {:?}", e);
676        }
677
678        let outgoing_requests = stream::iter(
679            self.olm_machine()
680                .await
681                .as_ref()
682                .ok_or(Error::NoOlmMachine)?
683                .outgoing_requests()
684                .await?,
685        )
686        .map(|r| self.send_outgoing_request(r));
687
688        let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
689
690        requests
691            .for_each(|r| async move {
692                match r {
693                    Ok(_) => (),
694                    Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
695                }
696            })
697            .await;
698
699        Ok(())
700    }
701}
702
703#[cfg(any(feature = "testing", test))]
704impl Client {
705    /// Get the olm machine, for testing purposes only.
706    pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
707        self.olm_machine().await
708    }
709}
710
711/// A high-level API to manage the client's encryption.
712///
713/// To get this, use [`Client::encryption()`].
714#[derive(Debug, Clone)]
715pub struct Encryption {
716    /// The underlying client.
717    client: Client,
718}
719
720impl Encryption {
721    pub(crate) fn new(client: Client) -> Self {
722        Self { client }
723    }
724
725    /// Returns the current encryption settings for this client.
726    pub(crate) fn settings(&self) -> EncryptionSettings {
727        self.client.inner.e2ee.encryption_settings
728    }
729
730    /// Get the public ed25519 key of our own device. This is usually what is
731    /// called the fingerprint of the device.
732    pub async fn ed25519_key(&self) -> Option<String> {
733        self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
734    }
735
736    /// Get the public Curve25519 key of our own device.
737    pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
738        self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
739    }
740
741    /// Get the current device creation timestamp.
742    pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
743        match self.get_own_device().await {
744            Ok(Some(device)) => device.first_time_seen_ts(),
745            // Should not happen, there should always be an own device
746            _ => MilliSecondsSinceUnixEpoch::now(),
747        }
748    }
749
750    pub(crate) async fn import_secrets_bundle(
751        &self,
752        bundle: &matrix_sdk_base::crypto::types::SecretsBundle,
753    ) -> Result<(), SecretImportError> {
754        let olm_machine = self.client.olm_machine().await;
755        let olm_machine =
756            olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
757
758        olm_machine.store().import_secrets_bundle(bundle).await
759    }
760
761    /// Get the status of the private cross signing keys.
762    ///
763    /// This can be used to check which private cross signing keys we have
764    /// stored locally.
765    pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
766        let olm = self.client.olm_machine().await;
767        let machine = olm.as_ref()?;
768        Some(machine.cross_signing_status().await)
769    }
770
771    /// Get all the tracked users we know about
772    ///
773    /// Tracked users are users for which we keep the device list of E2EE
774    /// capable devices up to date.
775    pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
776        if let Some(machine) = self.client.olm_machine().await.as_ref() {
777            machine.tracked_users().await
778        } else {
779            Ok(HashSet::new())
780        }
781    }
782
783    /// Get a [`Subscriber`] for the [`VerificationState`].
784    ///
785    /// # Examples
786    ///
787    /// ```no_run
788    /// use matrix_sdk::{encryption, Client};
789    /// use url::Url;
790    ///
791    /// # async {
792    /// let homeserver = Url::parse("http://example.com")?;
793    /// let client = Client::new(homeserver).await?;
794    /// let mut subscriber = client.encryption().verification_state();
795    ///
796    /// let current_value = subscriber.get();
797    ///
798    /// println!("The current verification state is: {current_value:?}");
799    ///
800    /// if let Some(verification_state) = subscriber.next().await {
801    ///     println!("Received verification state update {:?}", verification_state)
802    /// }
803    /// # anyhow::Ok(()) };
804    /// ```
805    pub fn verification_state(&self) -> Subscriber<VerificationState> {
806        self.client.inner.verification_state.subscribe_reset()
807    }
808
809    /// Get a verification object with the given flow id.
810    pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
811        let olm = self.client.olm_machine().await;
812        let olm = olm.as_ref()?;
813        #[allow(clippy::bind_instead_of_map)]
814        olm.get_verification(user_id, flow_id).and_then(|v| match v {
815            matrix_sdk_base::crypto::Verification::SasV1(sas) => {
816                Some(SasVerification { inner: sas, client: self.client.clone() }.into())
817            }
818            #[cfg(feature = "qrcode")]
819            matrix_sdk_base::crypto::Verification::QrV1(qr) => {
820                Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
821            }
822            _ => None,
823        })
824    }
825
826    /// Get a `VerificationRequest` object for the given user with the given
827    /// flow id.
828    pub async fn get_verification_request(
829        &self,
830        user_id: &UserId,
831        flow_id: impl AsRef<str>,
832    ) -> Option<VerificationRequest> {
833        let olm = self.client.olm_machine().await;
834        let olm = olm.as_ref()?;
835
836        olm.get_verification_request(user_id, flow_id)
837            .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
838    }
839
840    /// Get a specific device of a user.
841    ///
842    /// # Arguments
843    ///
844    /// * `user_id` - The unique id of the user that the device belongs to.
845    ///
846    /// * `device_id` - The unique id of the device.
847    ///
848    /// Returns a `Device` if one is found and the crypto store didn't throw an
849    /// error.
850    ///
851    /// This will always return None if the client hasn't been logged in.
852    ///
853    /// # Examples
854    ///
855    /// ```no_run
856    /// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
857    /// # use url::Url;
858    /// # async {
859    /// # let alice = user_id!("@alice:example.org");
860    /// # let homeserver = Url::parse("http://example.com")?;
861    /// # let client = Client::new(homeserver).await?;
862    /// if let Some(device) =
863    ///     client.encryption().get_device(alice, device_id!("DEVICEID")).await?
864    /// {
865    ///     println!("{:?}", device.is_verified());
866    ///
867    ///     if !device.is_verified() {
868    ///         let verification = device.request_verification().await?;
869    ///     }
870    /// }
871    /// # anyhow::Ok(()) };
872    /// ```
873    pub async fn get_device(
874        &self,
875        user_id: &UserId,
876        device_id: &DeviceId,
877    ) -> Result<Option<Device>, CryptoStoreError> {
878        let olm = self.client.olm_machine().await;
879        let Some(machine) = olm.as_ref() else { return Ok(None) };
880        let device = machine.get_device(user_id, device_id, None).await?;
881        Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
882    }
883
884    /// A convenience method to retrieve your own device from the store.
885    ///
886    /// This is the same as calling [`Encryption::get_device()`] with your own
887    /// user and device ID.
888    ///
889    /// This will always return a device, unless you are not logged in.
890    pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
891        let olm = self.client.olm_machine().await;
892        let Some(machine) = olm.as_ref() else { return Ok(None) };
893        let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
894        Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
895    }
896
897    /// Get a map holding all the devices of an user.
898    ///
899    /// This will always return an empty map if the client hasn't been logged
900    /// in.
901    ///
902    /// # Arguments
903    ///
904    /// * `user_id` - The unique id of the user that the devices belong to.
905    ///
906    /// # Examples
907    ///
908    /// ```no_run
909    /// # use matrix_sdk::{Client, ruma::user_id};
910    /// # use url::Url;
911    /// # async {
912    /// # let alice = user_id!("@alice:example.org");
913    /// # let homeserver = Url::parse("http://example.com")?;
914    /// # let client = Client::new(homeserver).await?;
915    /// let devices = client.encryption().get_user_devices(alice).await?;
916    ///
917    /// for device in devices.devices() {
918    ///     println!("{device:?}");
919    /// }
920    /// # anyhow::Ok(()) };
921    /// ```
922    pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
923        let devices = self
924            .client
925            .olm_machine()
926            .await
927            .as_ref()
928            .ok_or(Error::NoOlmMachine)?
929            .get_user_devices(user_id, None)
930            .await?;
931
932        Ok(UserDevices { inner: devices, client: self.client.clone() })
933    }
934
935    /// Get the E2EE identity of a user from the crypto store.
936    ///
937    /// Usually, we only have the E2EE identity of a user locally if the user
938    /// is tracked, meaning that we are both members of the same encrypted room.
939    ///
940    /// To get the E2EE identity of a user even if it is not available locally
941    /// use [`Encryption::request_user_identity()`].
942    ///
943    /// # Arguments
944    ///
945    /// * `user_id` - The unique id of the user that the identity belongs to.
946    ///
947    /// Returns a `UserIdentity` if one is found and the crypto store
948    /// didn't throw an error.
949    ///
950    /// This will always return None if the client hasn't been logged in.
951    ///
952    /// # Examples
953    ///
954    /// ```no_run
955    /// # use matrix_sdk::{Client, ruma::user_id};
956    /// # use url::Url;
957    /// # async {
958    /// # let alice = user_id!("@alice:example.org");
959    /// # let homeserver = Url::parse("http://example.com")?;
960    /// # let client = Client::new(homeserver).await?;
961    /// let user = client.encryption().get_user_identity(alice).await?;
962    ///
963    /// if let Some(user) = user {
964    ///     println!("{:?}", user.is_verified());
965    ///
966    ///     let verification = user.request_verification().await?;
967    /// }
968    /// # anyhow::Ok(()) };
969    /// ```
970    pub async fn get_user_identity(
971        &self,
972        user_id: &UserId,
973    ) -> Result<Option<UserIdentity>, CryptoStoreError> {
974        let olm = self.client.olm_machine().await;
975        let Some(olm) = olm.as_ref() else { return Ok(None) };
976        let identity = olm.get_identity(user_id, None).await?;
977
978        Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
979    }
980
981    /// Get the E2EE identity of a user from the homeserver.
982    ///
983    /// The E2EE identity returned is always guaranteed to be up-to-date. If the
984    /// E2EE identity is not found, it should mean that the user did not set
985    /// up cross-signing.
986    ///
987    /// If you want the E2EE identity of a user without making a request to the
988    /// homeserver, use [`Encryption::get_user_identity()`] instead.
989    ///
990    /// # Arguments
991    ///
992    /// * `user_id` - The ID of the user that the identity belongs to.
993    ///
994    /// Returns a [`UserIdentity`] if one is found. Returns an error if there
995    /// was an issue with the crypto store or with the request to the
996    /// homeserver.
997    ///
998    /// This will always return `None` if the client hasn't been logged in.
999    ///
1000    /// # Examples
1001    ///
1002    /// ```no_run
1003    /// # use matrix_sdk::{Client, ruma::user_id};
1004    /// # use url::Url;
1005    /// # async {
1006    /// # let alice = user_id!("@alice:example.org");
1007    /// # let homeserver = Url::parse("http://example.com")?;
1008    /// # let client = Client::new(homeserver).await?;
1009    /// let user = client.encryption().request_user_identity(alice).await?;
1010    ///
1011    /// if let Some(user) = user {
1012    ///     println!("User is verified: {:?}", user.is_verified());
1013    ///
1014    ///     let verification = user.request_verification().await?;
1015    /// }
1016    /// # anyhow::Ok(()) };
1017    /// ```
1018    pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1019        let olm = self.client.olm_machine().await;
1020        let Some(olm) = olm.as_ref() else { return Ok(None) };
1021
1022        let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1023        self.client.keys_query(&request_id, request.device_keys).await?;
1024
1025        let identity = olm.get_identity(user_id, None).await?;
1026        Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1027    }
1028
1029    /// Returns a stream of device updates, allowing users to listen for
1030    /// notifications about new or changed devices.
1031    ///
1032    /// The stream produced by this method emits updates whenever a new device
1033    /// is discovered or when an existing device's information is changed. Users
1034    /// can subscribe to this stream and receive updates in real-time.
1035    ///
1036    /// # Examples
1037    ///
1038    /// ```no_run
1039    /// # use matrix_sdk::Client;
1040    /// # use ruma::{device_id, user_id};
1041    /// # use futures_util::{pin_mut, StreamExt};
1042    /// # let client: Client = unimplemented!();
1043    /// # async {
1044    /// let devices_stream = client.encryption().devices_stream().await?;
1045    /// let user_id = client
1046    ///     .user_id()
1047    ///     .expect("We should know our user id after we have logged in");
1048    /// pin_mut!(devices_stream);
1049    ///
1050    /// for device_updates in devices_stream.next().await {
1051    ///     if let Some(user_devices) = device_updates.new.get(user_id) {
1052    ///         for device in user_devices.values() {
1053    ///             println!("A new device has been added {}", device.device_id());
1054    ///         }
1055    ///     }
1056    /// }
1057    /// # anyhow::Ok(()) };
1058    /// ```
1059    pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates>> {
1060        let olm = self.client.olm_machine().await;
1061        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1062        let client = self.client.to_owned();
1063
1064        Ok(olm
1065            .store()
1066            .devices_stream()
1067            .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1068    }
1069
1070    /// Returns a stream of user identity updates, allowing users to listen for
1071    /// notifications about new or changed user identities.
1072    ///
1073    /// The stream produced by this method emits updates whenever a new user
1074    /// identity is discovered or when an existing identities information is
1075    /// changed. Users can subscribe to this stream and receive updates in
1076    /// real-time.
1077    ///
1078    /// # Examples
1079    ///
1080    /// ```no_run
1081    /// # use matrix_sdk::Client;
1082    /// # use ruma::{device_id, user_id};
1083    /// # use futures_util::{pin_mut, StreamExt};
1084    /// # let client: Client = unimplemented!();
1085    /// # async {
1086    /// let identities_stream =
1087    ///     client.encryption().user_identities_stream().await?;
1088    /// pin_mut!(identities_stream);
1089    ///
1090    /// for identity_updates in identities_stream.next().await {
1091    ///     for (_, identity) in identity_updates.new {
1092    ///         println!("A new identity has been added {}", identity.user_id());
1093    ///     }
1094    /// }
1095    /// # anyhow::Ok(()) };
1096    /// ```
1097    pub async fn user_identities_stream(&self) -> Result<impl Stream<Item = IdentityUpdates>> {
1098        let olm = self.client.olm_machine().await;
1099        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1100        let client = self.client.to_owned();
1101
1102        Ok(olm
1103            .store()
1104            .user_identities_stream()
1105            .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1106    }
1107
1108    /// Create and upload a new cross signing identity.
1109    ///
1110    /// # Arguments
1111    ///
1112    /// * `auth_data` - This request requires user interactive auth, the first
1113    ///   request needs to set this to `None` and will always fail with an
1114    ///   `UiaaResponse`. The response will contain information for the
1115    ///   interactive auth and the same request needs to be made but this time
1116    ///   with some `auth_data` provided.
1117    ///
1118    /// # Examples
1119    ///
1120    /// ```no_run
1121    /// # use std::collections::BTreeMap;
1122    /// # use matrix_sdk::{ruma::api::client::uiaa, Client};
1123    /// # use url::Url;
1124    /// # use serde_json::json;
1125    /// # async {
1126    /// # let homeserver = Url::parse("http://example.com")?;
1127    /// # let client = Client::new(homeserver).await?;
1128    /// if let Err(e) = client.encryption().bootstrap_cross_signing(None).await {
1129    ///     if let Some(response) = e.as_uiaa_response() {
1130    ///         let mut password = uiaa::Password::new(
1131    ///             uiaa::UserIdentifier::UserIdOrLocalpart("example".to_owned()),
1132    ///             "wordpass".to_owned(),
1133    ///         );
1134    ///         password.session = response.session.clone();
1135    ///
1136    ///         client
1137    ///             .encryption()
1138    ///             .bootstrap_cross_signing(Some(uiaa::AuthData::Password(password)))
1139    ///             .await
1140    ///             .expect("Couldn't bootstrap cross signing")
1141    ///     } else {
1142    ///         panic!("Error during cross signing bootstrap {:#?}", e);
1143    ///     }
1144    /// }
1145    /// # anyhow::Ok(()) };
1146    pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1147        let olm = self.client.olm_machine().await;
1148        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1149
1150        let CrossSigningBootstrapRequests {
1151            upload_signing_keys_req,
1152            upload_keys_req,
1153            upload_signatures_req,
1154        } = olm.bootstrap_cross_signing(false).await?;
1155
1156        let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1157            auth: auth_data,
1158            master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1159            self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1160            user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1161        });
1162
1163        if let Some(req) = upload_keys_req {
1164            self.client.send_outgoing_request(req).await?;
1165        }
1166        self.client.send(upload_signing_keys_req).await?;
1167        self.client.send(upload_signatures_req).await?;
1168
1169        Ok(())
1170    }
1171
1172    /// Reset the cross-signing keys.
1173    ///
1174    /// # Example
1175    ///
1176    /// ```no_run
1177    /// # use matrix_sdk::{ruma::api::client::uiaa, Client, encryption::CrossSigningResetAuthType};
1178    /// # use url::Url;
1179    /// # async {
1180    /// # let homeserver = Url::parse("http://example.com")?;
1181    /// # let client = Client::new(homeserver).await?;
1182    /// # let user_id = unimplemented!();
1183    /// let encryption = client.encryption();
1184    ///
1185    /// if let Some(handle) = encryption.reset_cross_signing().await? {
1186    ///     match handle.auth_type() {
1187    ///         CrossSigningResetAuthType::Uiaa(uiaa) => {
1188    ///             use matrix_sdk::ruma::api::client::uiaa;
1189    ///
1190    ///             let password = "1234".to_owned();
1191    ///             let mut password = uiaa::Password::new(user_id, password);
1192    ///             password.session = uiaa.session;
1193    ///
1194    ///             handle.auth(Some(uiaa::AuthData::Password(password))).await?;
1195    ///         }
1196    ///         CrossSigningResetAuthType::OAuth(o) => {
1197    ///             println!(
1198    ///                 "To reset your end-to-end encryption cross-signing identity, \
1199    ///                 you first need to approve it at {}",
1200    ///                 o.approval_url
1201    ///             );
1202    ///             handle.auth(None).await?;
1203    ///         }
1204    ///     }
1205    /// }
1206    /// # anyhow::Ok(()) };
1207    /// ```
1208    pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1209        let olm = self.client.olm_machine().await;
1210        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1211
1212        let CrossSigningBootstrapRequests {
1213            upload_keys_req,
1214            upload_signing_keys_req,
1215            upload_signatures_req,
1216        } = olm.bootstrap_cross_signing(true).await?;
1217
1218        let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1219            auth: None,
1220            master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1221            self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1222            user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1223        });
1224
1225        if let Some(req) = upload_keys_req {
1226            self.client.send_outgoing_request(req).await?;
1227        }
1228
1229        if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1230            if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1231                let client = self.client.clone();
1232
1233                Ok(Some(CrossSigningResetHandle::new(
1234                    client,
1235                    upload_signing_keys_req,
1236                    upload_signatures_req,
1237                    auth_type,
1238                )))
1239            } else {
1240                Err(error.into())
1241            }
1242        } else {
1243            self.client.send(upload_signatures_req).await?;
1244
1245            Ok(None)
1246        }
1247    }
1248
1249    /// Query the user's own device keys, if, and only if, we didn't have their
1250    /// identity in the first place.
1251    async fn ensure_initial_key_query(&self) -> Result<()> {
1252        let olm_machine = self.client.olm_machine().await;
1253        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1254
1255        let user_id = olm_machine.user_id();
1256
1257        if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1258            let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1259            self.client.keys_query(&request_id, request.device_keys).await?;
1260        }
1261
1262        Ok(())
1263    }
1264
1265    /// Create and upload a new cross signing identity, if that has not been
1266    /// done yet.
1267    ///
1268    /// This will only create a new cross-signing identity if the user had never
1269    /// done it before. If the user did it before, then this is a no-op.
1270    ///
1271    /// See also the documentation of [`Self::bootstrap_cross_signing`] for the
1272    /// behavior of this function.
1273    ///
1274    /// # Arguments
1275    ///
1276    /// * `auth_data` - This request requires user interactive auth, the first
1277    ///   request needs to set this to `None` and will always fail with an
1278    ///   `UiaaResponse`. The response will contain information for the
1279    ///   interactive auth and the same request needs to be made but this time
1280    ///   with some `auth_data` provided.
1281    ///
1282    /// # Examples
1283    /// ```no_run
1284    /// # use std::collections::BTreeMap;
1285    /// # use matrix_sdk::{ruma::api::client::uiaa, Client};
1286    /// # use url::Url;
1287    /// # use serde_json::json;
1288    /// # async {
1289    /// # let homeserver = Url::parse("http://example.com")?;
1290    /// # let client = Client::new(homeserver).await?;
1291    /// if let Err(e) = client.encryption().bootstrap_cross_signing_if_needed(None).await {
1292    ///     if let Some(response) = e.as_uiaa_response() {
1293    ///         let mut password = uiaa::Password::new(
1294    ///             uiaa::UserIdentifier::UserIdOrLocalpart("example".to_owned()),
1295    ///             "wordpass".to_owned(),
1296    ///         );
1297    ///         password.session = response.session.clone();
1298    ///
1299    ///         // Note, on the failed attempt we can use `bootstrap_cross_signing` immediately, to
1300    ///         // avoid checks.
1301    ///         client
1302    ///             .encryption()
1303    ///             .bootstrap_cross_signing(Some(uiaa::AuthData::Password(password)))
1304    ///             .await
1305    ///             .expect("Couldn't bootstrap cross signing")
1306    ///     } else {
1307    ///         panic!("Error during cross signing bootstrap {:#?}", e);
1308    ///     }
1309    /// }
1310    /// # anyhow::Ok(()) };
1311    pub async fn bootstrap_cross_signing_if_needed(
1312        &self,
1313        auth_data: Option<AuthData>,
1314    ) -> Result<()> {
1315        let olm_machine = self.client.olm_machine().await;
1316        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1317        let user_id = olm_machine.user_id();
1318
1319        self.ensure_initial_key_query().await?;
1320
1321        if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1322            self.bootstrap_cross_signing(auth_data).await?;
1323        }
1324
1325        Ok(())
1326    }
1327
1328    /// Export E2EE keys that match the given predicate encrypting them with the
1329    /// given passphrase.
1330    ///
1331    /// # Arguments
1332    ///
1333    /// * `path` - The file path where the exported key file will be saved.
1334    ///
1335    /// * `passphrase` - The passphrase that will be used to encrypt the
1336    ///   exported room keys.
1337    ///
1338    /// * `predicate` - A closure that will be called for every known
1339    ///   `InboundGroupSession`, which represents a room key. If the closure
1340    ///   returns `true` the `InboundGroupSessoin` will be included in the
1341    ///   export, if the closure returns `false` it will not be included.
1342    ///
1343    /// # Panics
1344    ///
1345    /// This method will panic if it isn't run on a Tokio runtime.
1346    ///
1347    /// This method will panic if it can't get enough randomness from the OS to
1348    /// encrypt the exported keys securely.
1349    ///
1350    /// # Examples
1351    ///
1352    /// ```no_run
1353    /// # use std::{path::PathBuf, time::Duration};
1354    /// # use matrix_sdk::{
1355    /// #     Client, config::SyncSettings,
1356    /// #     ruma::room_id,
1357    /// # };
1358    /// # use url::Url;
1359    /// # async {
1360    /// # let homeserver = Url::parse("http://localhost:8080")?;
1361    /// # let mut client = Client::new(homeserver).await?;
1362    /// let path = PathBuf::from("/home/example/e2e-keys.txt");
1363    /// // Export all room keys.
1364    /// client
1365    ///     .encryption()
1366    ///     .export_room_keys(path, "secret-passphrase", |_| true)
1367    ///     .await?;
1368    ///
1369    /// // Export only the room keys for a certain room.
1370    /// let path = PathBuf::from("/home/example/e2e-room-keys.txt");
1371    /// let room_id = room_id!("!test:localhost");
1372    ///
1373    /// client
1374    ///     .encryption()
1375    ///     .export_room_keys(path, "secret-passphrase", |s| s.room_id() == room_id)
1376    ///     .await?;
1377    /// # anyhow::Ok(()) };
1378    /// ```
1379    #[cfg(not(target_family = "wasm"))]
1380    pub async fn export_room_keys(
1381        &self,
1382        path: PathBuf,
1383        passphrase: &str,
1384        predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1385    ) -> Result<()> {
1386        let olm = self.client.olm_machine().await;
1387        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1388
1389        let keys = olm.store().export_room_keys(predicate).await?;
1390        let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1391
1392        let encrypt = move || -> Result<()> {
1393            let export: String =
1394                matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1395            let mut file = std::fs::File::create(path)?;
1396            file.write_all(&export.into_bytes())?;
1397            Ok(())
1398        };
1399
1400        let task = tokio::task::spawn_blocking(encrypt);
1401        task.await.expect("Task join error")
1402    }
1403
1404    /// Import E2EE keys from the given file path.
1405    ///
1406    /// # Arguments
1407    ///
1408    /// * `path` - The file path where the exported key file will can be found.
1409    ///
1410    /// * `passphrase` - The passphrase that should be used to decrypt the
1411    ///   exported room keys.
1412    ///
1413    /// Returns a tuple of numbers that represent the number of sessions that
1414    /// were imported and the total number of sessions that were found in the
1415    /// key export.
1416    ///
1417    /// # Panics
1418    ///
1419    /// This method will panic if it isn't run on a Tokio runtime.
1420    ///
1421    /// ```no_run
1422    /// # use std::{path::PathBuf, time::Duration};
1423    /// # use matrix_sdk::{
1424    /// #     Client, config::SyncSettings,
1425    /// #     ruma::room_id,
1426    /// # };
1427    /// # use url::Url;
1428    /// # async {
1429    /// # let homeserver = Url::parse("http://localhost:8080")?;
1430    /// # let mut client = Client::new(homeserver).await?;
1431    /// let path = PathBuf::from("/home/example/e2e-keys.txt");
1432    /// let result =
1433    ///     client.encryption().import_room_keys(path, "secret-passphrase").await?;
1434    ///
1435    /// println!(
1436    ///     "Imported {} room keys out of {}",
1437    ///     result.imported_count, result.total_count
1438    /// );
1439    /// # anyhow::Ok(()) };
1440    /// ```
1441    #[cfg(not(target_family = "wasm"))]
1442    pub async fn import_room_keys(
1443        &self,
1444        path: PathBuf,
1445        passphrase: &str,
1446    ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1447        let olm = self.client.olm_machine().await;
1448        let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1449        let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1450
1451        let decrypt = move || {
1452            let file = std::fs::File::open(path)?;
1453            matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1454        };
1455
1456        let task = tokio::task::spawn_blocking(decrypt);
1457        let import = task.await.expect("Task join error")?;
1458
1459        let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1460
1461        self.backups().maybe_trigger_backup();
1462
1463        Ok(ret)
1464    }
1465
1466    /// Receive notifications of room keys being received as a [`Stream`].
1467    ///
1468    /// Each time a room key is updated in any way, an update will be sent to
1469    /// the stream. Updates that happen at the same time are batched into a
1470    /// [`Vec`].
1471    ///
1472    /// If the reader of the stream lags too far behind, an error is broadcast
1473    /// containing the number of skipped items.
1474    ///
1475    /// # Examples
1476    ///
1477    /// ```no_run
1478    /// # use matrix_sdk::Client;
1479    /// # use url::Url;
1480    /// # async {
1481    /// # let homeserver = Url::parse("http://example.com")?;
1482    /// # let client = Client::new(homeserver).await?;
1483    /// use futures_util::StreamExt;
1484    ///
1485    /// let Some(mut room_keys_stream) =
1486    ///     client.encryption().room_keys_received_stream().await
1487    /// else {
1488    ///     return Ok(());
1489    /// };
1490    ///
1491    /// while let Some(update) = room_keys_stream.next().await {
1492    ///     println!("Received room keys {update:?}");
1493    /// }
1494    /// # anyhow::Ok(()) };
1495    /// ```
1496    pub async fn room_keys_received_stream(
1497        &self,
1498    ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>>> {
1499        let olm = self.client.olm_machine().await;
1500        let olm = olm.as_ref()?;
1501
1502        Some(olm.store().room_keys_received_stream())
1503    }
1504
1505    /// Receive notifications of historic room key bundles as a [`Stream`].
1506    ///
1507    /// Historic room key bundles are defined in [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
1508    ///
1509    /// Each time a historic room key bundle was received, an update will be
1510    /// sent to the stream. This stream is useful for informative purposes
1511    /// exclusively, historic room key bundles are handled by the SDK
1512    /// automatically.
1513    ///
1514    /// # Examples
1515    ///
1516    /// ```no_run
1517    /// # use matrix_sdk::Client;
1518    /// # use url::Url;
1519    /// # async {
1520    /// # let homeserver = Url::parse("http://example.com")?;
1521    /// # let client = Client::new(homeserver).await?;
1522    /// use futures_util::StreamExt;
1523    ///
1524    /// let Some(mut bundle_stream) =
1525    ///     client.encryption().historic_room_key_stream().await
1526    /// else {
1527    ///     return Ok(());
1528    /// };
1529    ///
1530    /// while let Some(bundle_info) = bundle_stream.next().await {
1531    ///     println!("Received a historic room key bundle {bundle_info:?}");
1532    /// }
1533    /// # anyhow::Ok(()) };
1534    /// ```
1535    pub async fn historic_room_key_stream(&self) -> Option<impl Stream<Item = RoomKeyBundleInfo>> {
1536        let olm = self.client.olm_machine().await;
1537        let olm = olm.as_ref()?;
1538
1539        Some(olm.store().historic_room_key_stream())
1540    }
1541
1542    /// Get the secret storage manager of the client.
1543    pub fn secret_storage(&self) -> SecretStorage {
1544        SecretStorage { client: self.client.to_owned() }
1545    }
1546
1547    /// Get the backups manager of the client.
1548    pub fn backups(&self) -> Backups {
1549        Backups { client: self.client.to_owned() }
1550    }
1551
1552    /// Get the recovery manager of the client.
1553    pub fn recovery(&self) -> Recovery {
1554        Recovery { client: self.client.to_owned() }
1555    }
1556
1557    /// Enables the crypto-store cross-process lock.
1558    ///
1559    /// This may be required if there are multiple processes that may do writes
1560    /// to the same crypto store. In that case, it's necessary to create a
1561    /// lock, so that only one process writes to it, otherwise this may
1562    /// cause confusing issues because of stale data contained in in-memory
1563    /// caches.
1564    ///
1565    /// The provided `lock_value` must be a unique identifier for this process.
1566    /// Check [`Client::cross_process_store_locks_holder_name`] to
1567    /// get the global value.
1568    pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1569        // If the lock has already been created, don't recreate it from scratch.
1570        if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1571            let prev_holder = prev_lock.lock_holder();
1572            if prev_holder == lock_value {
1573                return Ok(());
1574            }
1575            warn!(
1576                "Recreating cross-process store lock with a different holder value: \
1577                 prev was {prev_holder}, new is {lock_value}"
1578            );
1579        }
1580
1581        let olm_machine = self.client.base_client().olm_machine().await;
1582        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1583
1584        let lock =
1585            olm_machine.store().create_store_lock("cross_process_lock".to_owned(), lock_value);
1586
1587        // Gently try to initialize the crypto store generation counter.
1588        //
1589        // If we don't get the lock immediately, then it is already acquired by another
1590        // process, and we'll get to reload next time we acquire the lock.
1591        {
1592            let guard = lock.try_lock_once().await?;
1593            if guard.is_some() {
1594                olm_machine
1595                    .initialize_crypto_store_generation(
1596                        &self.client.locks().crypto_store_generation,
1597                    )
1598                    .await?;
1599            }
1600        }
1601
1602        self.client
1603            .locks()
1604            .cross_process_crypto_store_lock
1605            .set(lock)
1606            .map_err(|_| Error::BadCryptoStoreState)?;
1607
1608        Ok(())
1609    }
1610
1611    /// Maybe reload the `OlmMachine` after acquiring the lock for the first
1612    /// time.
1613    ///
1614    /// Returns the current generation number.
1615    async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
1616        let olm_machine_guard = self.client.olm_machine().await;
1617        if let Some(olm_machine) = olm_machine_guard.as_ref() {
1618            let (new_gen, generation_number) = olm_machine
1619                .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
1620                .await?;
1621            // If the crypto store generation has changed,
1622            if new_gen {
1623                // (get rid of the reference to the current crypto store first)
1624                drop(olm_machine_guard);
1625                // Recreate the OlmMachine.
1626                self.client.base_client().regenerate_olm(None).await?;
1627            }
1628            Ok(generation_number)
1629        } else {
1630            // XXX: not sure this is reachable. Seems like the OlmMachine should always have
1631            // been initialised by the time we get here. Ideally we'd panic, or return an
1632            // error, but for now I'm just adding some logging to check if it
1633            // happens, and returning the magic number 0.
1634            warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
1635            Ok(0)
1636        }
1637    }
1638
1639    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
1640    /// spin-waits until the lock is available.
1641    ///
1642    /// May reload the `OlmMachine`, after obtaining the lock but not on the
1643    /// first time.
1644    pub async fn spin_lock_store(
1645        &self,
1646        max_backoff: Option<u32>,
1647    ) -> Result<Option<CrossProcessLockStoreGuardWithGeneration>, Error> {
1648        if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1649            let guard = lock.spin_lock(max_backoff).await?;
1650
1651            let generation = self.on_lock_newly_acquired().await?;
1652
1653            Ok(Some(CrossProcessLockStoreGuardWithGeneration { _guard: guard, generation }))
1654        } else {
1655            Ok(None)
1656        }
1657    }
1658
1659    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
1660    /// attempts to lock it once.
1661    ///
1662    /// Returns a guard to the lock, if it was obtained.
1663    pub async fn try_lock_store_once(
1664        &self,
1665    ) -> Result<Option<CrossProcessLockStoreGuardWithGeneration>, Error> {
1666        if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1667            let maybe_guard = lock.try_lock_once().await?;
1668
1669            let Some(guard) = maybe_guard else {
1670                return Ok(None);
1671            };
1672
1673            let generation = self.on_lock_newly_acquired().await?;
1674
1675            Ok(Some(CrossProcessLockStoreGuardWithGeneration { _guard: guard, generation }))
1676        } else {
1677            Ok(None)
1678        }
1679    }
1680
1681    /// Testing purposes only.
1682    #[cfg(any(test, feature = "testing"))]
1683    pub async fn uploaded_key_count(&self) -> Result<u64> {
1684        let olm_machine = self.client.olm_machine().await;
1685        let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
1686        Ok(olm_machine.uploaded_key_count().await?)
1687    }
1688
1689    /// Bootstrap encryption and enables event listeners for the E2EE support.
1690    ///
1691    /// Based on the `EncryptionSettings`, this call might:
1692    /// - Bootstrap cross-signing if needed (POST `/device_signing/upload`)
1693    /// - Create a key backup if needed (POST `/room_keys/version`)
1694    /// - Create a secret storage if needed (PUT `/account_data/{type}`)
1695    ///
1696    /// As part of this process, and if needed, the current device keys would be
1697    /// uploaded to the server, new account data would be added, and cross
1698    /// signing keys and signatures might be uploaded.
1699    ///
1700    /// Should be called once we
1701    /// created a [`OlmMachine`], i.e. after logging in.
1702    ///
1703    /// # Arguments
1704    ///
1705    /// * `auth_data` - Some requests may require re-authentication. To prevent
1706    ///   the user from having to re-enter their password (or use other
1707    ///   methods), we can provide the authentication data here. This is
1708    ///   necessary for uploading cross-signing keys. However, please note that
1709    ///   there is a proposal (MSC3967) to remove this requirement, which would
1710    ///   allow for the initial upload of cross-signing keys without
1711    ///   authentication, rendering this parameter obsolete.
1712    pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
1713        // It's fine to be async here as we're only getting the lock protecting the
1714        // `OlmMachine`. Since the lock shouldn't be that contested right after logging
1715        // in we won't delay the login or restoration of the Client.
1716        let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
1717            Some(BundleReceiverTask::new(&self.client).await)
1718        } else {
1719            None
1720        };
1721
1722        let mut tasks = self.client.inner.e2ee.tasks.lock();
1723
1724        let this = self.clone();
1725
1726        tasks.setup_e2ee = Some(spawn(async move {
1727            // Update the current state first, so we don't have to wait for the result of
1728            // network requests
1729            this.update_verification_state().await;
1730
1731            if this.settings().auto_enable_cross_signing {
1732                if let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await {
1733                    error!("Couldn't bootstrap cross signing {e:?}");
1734                }
1735            }
1736
1737            if let Err(e) = this.backups().setup_and_resume().await {
1738                error!("Couldn't setup and resume backups {e:?}");
1739            }
1740            if let Err(e) = this.recovery().setup().await {
1741                error!("Couldn't setup and resume recovery {e:?}");
1742            }
1743        }));
1744
1745        tasks.receive_historic_room_key_bundles = bundle_receiver_task;
1746    }
1747
1748    /// Waits for end-to-end encryption initialization tasks to finish, if any
1749    /// was running in the background.
1750    pub async fn wait_for_e2ee_initialization_tasks(&self) {
1751        let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
1752
1753        if let Some(task) = task {
1754            if let Err(err) = task.await {
1755                warn!("Error when initializing backups: {err}");
1756            }
1757        }
1758    }
1759
1760    /// Upload the device keys and initial set of one-time keys to the server.
1761    ///
1762    /// This should only be called when the user logs in for the first time,
1763    /// the method will ensure that other devices see our own device as an
1764    /// end-to-end encryption enabled one.
1765    ///
1766    /// **Warning**: Do not use this method if we're already calling
1767    /// [`Client::send_outgoing_request()`]. This method is intended for
1768    /// explicitly uploading the device keys before starting a sync.
1769    pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
1770        let olm = self.client.olm_machine().await;
1771        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1772
1773        if let Some((request_id, request)) = olm.upload_device_keys().await? {
1774            self.client.keys_upload(&request_id, &request).await?;
1775
1776            let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
1777            self.client.keys_query(&request_id, request.device_keys).await?;
1778        }
1779
1780        Ok(())
1781    }
1782
1783    pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
1784        self.recovery().update_state_after_keys_query(response).await;
1785
1786        // Only update the verification_state if our own devices changed
1787        if let Some(user_id) = self.client.user_id() {
1788            let contains_own_device = response.device_keys.contains_key(user_id);
1789
1790            if contains_own_device {
1791                self.update_verification_state().await;
1792            }
1793        }
1794    }
1795
1796    async fn update_verification_state(&self) {
1797        match self.get_own_device().await {
1798            Ok(device) => {
1799                if let Some(device) = device {
1800                    let is_verified = device.is_cross_signed_by_owner();
1801
1802                    if is_verified {
1803                        self.client.inner.verification_state.set(VerificationState::Verified);
1804                    } else {
1805                        self.client.inner.verification_state.set(VerificationState::Unverified);
1806                    }
1807                } else {
1808                    warn!("Couldn't find out own device in the store.");
1809                    self.client.inner.verification_state.set(VerificationState::Unknown);
1810                }
1811            }
1812            Err(error) => {
1813                warn!("Failed retrieving own device: {error}");
1814                self.client.inner.verification_state.set(VerificationState::Unknown);
1815            }
1816        }
1817    }
1818
1819    /// Encrypts then send the given content via the `/sendToDevice` end-point
1820    /// using Olm encryption.
1821    ///
1822    /// If there are a lot of recipient devices multiple `/sendToDevice`
1823    /// requests might be sent out.
1824    ///
1825    /// # Returns
1826    /// A list of failures. The list of devices that couldn't get the messages.
1827    #[cfg(feature = "experimental-send-custom-to-device")]
1828    pub async fn encrypt_and_send_raw_to_device(
1829        &self,
1830        recipient_devices: Vec<&Device>,
1831        event_type: &str,
1832        content: Raw<AnyToDeviceEventContent>,
1833        share_strategy: CollectStrategy,
1834    ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
1835        let users = recipient_devices.iter().map(|device| device.user_id());
1836
1837        // Will claim one-time-key for users that needs it
1838        // TODO: For later optimisation: This will establish missing olm sessions with
1839        // all this users devices, but we just want for some devices.
1840        self.client.claim_one_time_keys(users).await?;
1841
1842        let olm = self.client.olm_machine().await;
1843        let olm = olm.as_ref().expect("Olm machine wasn't started");
1844
1845        let (requests, withhelds) = olm
1846            .encrypt_content_for_devices(
1847                recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
1848                event_type,
1849                &content
1850                    .deserialize_as::<serde_json::Value>()
1851                    .expect("Deserialize as Value will always work"),
1852                share_strategy,
1853            )
1854            .await?;
1855
1856        let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
1857
1858        // Push the withhelds in the failures
1859        withhelds.iter().for_each(|(d, _)| {
1860            failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
1861        });
1862
1863        // TODO: parallelize that? it's already grouping 250 devices per chunk.
1864        for request in requests {
1865            let ruma_request = RumaToDeviceRequest::new_raw(
1866                request.event_type.clone(),
1867                request.txn_id.clone(),
1868                request.messages.clone(),
1869            );
1870
1871            let send_result = self
1872                .client
1873                .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
1874                .await;
1875
1876            // If the sending failed we need to collect the failures to report them
1877            if send_result.is_err() {
1878                // Mark the sending as failed
1879                for (user_id, device_map) in request.messages {
1880                    for device_id in device_map.keys() {
1881                        match device_id {
1882                            DeviceIdOrAllDevices::DeviceId(device_id) => {
1883                                failures.push((user_id.clone(), device_id.to_owned()));
1884                            }
1885                            DeviceIdOrAllDevices::AllDevices => {
1886                                // Cannot happen in this case
1887                            }
1888                        }
1889                    }
1890                }
1891            }
1892        }
1893
1894        Ok(failures)
1895    }
1896}
1897
1898#[cfg(all(test, not(target_family = "wasm")))]
1899mod tests {
1900    use std::{
1901        ops::Not,
1902        sync::{
1903            atomic::{AtomicBool, Ordering},
1904            Arc,
1905        },
1906        time::Duration,
1907    };
1908
1909    use matrix_sdk_test::{
1910        async_test, test_json, GlobalAccountDataTestEvent, JoinedRoomBuilder, StateTestEvent,
1911        SyncResponseBuilder, DEFAULT_TEST_ROOM_ID,
1912    };
1913    use ruma::{
1914        event_id,
1915        events::{reaction::ReactionEventContent, relation::Annotation},
1916        user_id,
1917    };
1918    use serde_json::json;
1919    use wiremock::{
1920        matchers::{header, method, path_regex},
1921        Mock, MockServer, Request, ResponseTemplate,
1922    };
1923
1924    use crate::{
1925        assert_next_matches_with_timeout,
1926        config::RequestConfig,
1927        encryption::{OAuthCrossSigningResetInfo, VerificationState},
1928        test_utils::{
1929            client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
1930        },
1931        Client,
1932    };
1933
1934    #[async_test]
1935    async fn test_reaction_sending() {
1936        let server = MockServer::start().await;
1937        let client = logged_in_client(Some(server.uri())).await;
1938
1939        let event_id = event_id!("$2:example.org");
1940
1941        Mock::given(method("GET"))
1942            .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
1943            .and(header("authorization", "Bearer 1234"))
1944            .respond_with(
1945                ResponseTemplate::new(200)
1946                    .set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT),
1947            )
1948            .mount(&server)
1949            .await;
1950
1951        Mock::given(method("PUT"))
1952            .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
1953            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1954                "event_id": event_id,
1955            })))
1956            .mount(&server)
1957            .await;
1958
1959        let response = SyncResponseBuilder::default()
1960            .add_joined_room(
1961                JoinedRoomBuilder::default()
1962                    .add_state_event(StateTestEvent::Member)
1963                    .add_state_event(StateTestEvent::PowerLevels)
1964                    .add_state_event(StateTestEvent::Encryption),
1965            )
1966            .build_sync_response();
1967
1968        client.base_client().receive_sync_response(response).await.unwrap();
1969
1970        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
1971        assert!(room
1972            .latest_encryption_state()
1973            .await
1974            .expect("Getting encryption state")
1975            .is_encrypted());
1976
1977        let event_id = event_id!("$1:example.org");
1978        let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
1979        room.send(reaction).await.expect("Sending the reaction should not fail");
1980
1981        room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
1982    }
1983
1984    #[async_test]
1985    async fn test_get_dm_room_returns_the_room_we_have_with_this_user() {
1986        let server = MockServer::start().await;
1987        let client = logged_in_client(Some(server.uri())).await;
1988        // This is the user ID that is inside MemberAdditional.
1989        // Note the confusing username, so we can share
1990        // GlobalAccountDataTestEvent::Direct with the invited test.
1991        let user_id = user_id!("@invited:localhost");
1992
1993        // When we receive a sync response saying "invited" is invited to a DM
1994        let response = SyncResponseBuilder::default()
1995            .add_joined_room(
1996                JoinedRoomBuilder::default().add_state_event(StateTestEvent::MemberAdditional),
1997            )
1998            .add_global_account_data_event(GlobalAccountDataTestEvent::Direct)
1999            .build_sync_response();
2000        client.base_client().receive_sync_response(response).await.unwrap();
2001
2002        // Then get_dm_room finds this room
2003        let found_room = client.get_dm_room(user_id).expect("DM not found!");
2004        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
2005    }
2006
2007    #[async_test]
2008    async fn test_get_dm_room_still_finds_room_where_participant_is_only_invited() {
2009        let server = MockServer::start().await;
2010        let client = logged_in_client(Some(server.uri())).await;
2011        // This is the user ID that is inside MemberInvite
2012        let user_id = user_id!("@invited:localhost");
2013
2014        // When we receive a sync response saying "invited" is invited to a DM
2015        let response = SyncResponseBuilder::default()
2016            .add_joined_room(
2017                JoinedRoomBuilder::default().add_state_event(StateTestEvent::MemberInvite),
2018            )
2019            .add_global_account_data_event(GlobalAccountDataTestEvent::Direct)
2020            .build_sync_response();
2021        client.base_client().receive_sync_response(response).await.unwrap();
2022
2023        // Then get_dm_room finds this room
2024        let found_room = client.get_dm_room(user_id).expect("DM not found!");
2025        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
2026    }
2027
2028    #[async_test]
2029    async fn test_get_dm_room_still_finds_left_room() {
2030        // See the discussion in https://github.com/matrix-org/matrix-rust-sdk/issues/2017
2031        // and the high-level issue at https://github.com/vector-im/element-x-ios/issues/1077
2032
2033        let server = MockServer::start().await;
2034        let client = logged_in_client(Some(server.uri())).await;
2035        // This is the user ID that is inside MemberAdditional.
2036        // Note the confusing username, so we can share
2037        // GlobalAccountDataTestEvent::Direct with the invited test.
2038        let user_id = user_id!("@invited:localhost");
2039
2040        // When we receive a sync response saying "invited" is invited to a DM
2041        let response = SyncResponseBuilder::default()
2042            .add_joined_room(
2043                JoinedRoomBuilder::default().add_state_event(StateTestEvent::MemberLeave),
2044            )
2045            .add_global_account_data_event(GlobalAccountDataTestEvent::Direct)
2046            .build_sync_response();
2047        client.base_client().receive_sync_response(response).await.unwrap();
2048
2049        // Then get_dm_room finds this room
2050        let found_room = client.get_dm_room(user_id).expect("DM not found!");
2051        assert!(found_room.get_member_no_sync(user_id).await.unwrap().is_some());
2052    }
2053
2054    #[cfg(feature = "sqlite")]
2055    #[async_test]
2056    async fn test_generation_counter_invalidates_olm_machine() {
2057        // Create two clients using the same sqlite database.
2058
2059        use matrix_sdk_base::store::RoomLoadSettings;
2060        let sqlite_path = std::env::temp_dir().join("generation_counter_sqlite.db");
2061        let session = mock_matrix_session();
2062
2063        let client1 = Client::builder()
2064            .homeserver_url("http://localhost:1234")
2065            .request_config(RequestConfig::new().disable_retry())
2066            .sqlite_store(&sqlite_path, None)
2067            .build()
2068            .await
2069            .unwrap();
2070        client1
2071            .matrix_auth()
2072            .restore_session(session.clone(), RoomLoadSettings::default())
2073            .await
2074            .unwrap();
2075
2076        let client2 = Client::builder()
2077            .homeserver_url("http://localhost:1234")
2078            .request_config(RequestConfig::new().disable_retry())
2079            .sqlite_store(sqlite_path, None)
2080            .build()
2081            .await
2082            .unwrap();
2083        client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2084
2085        // When the lock isn't enabled, any attempt at locking won't return a guard.
2086        let guard = client1.encryption().try_lock_store_once().await.unwrap();
2087        assert!(guard.is_none());
2088
2089        client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2090        client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2091
2092        // One client can take the lock.
2093        let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2094        assert!(acquired1.is_some());
2095
2096        // Keep the olm machine, so we can see if it's changed later, by comparing Arcs.
2097        let initial_olm_machine =
2098            client1.olm_machine().await.clone().expect("must have an olm machine");
2099
2100        // Also enable backup to check that new machine has the same backup keys.
2101        let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new()
2102            .expect("Can't create new recovery key");
2103        let backup_key = decryption_key.megolm_v1_public_key();
2104        backup_key.set_version("1".to_owned());
2105        initial_olm_machine
2106            .backup_machine()
2107            .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2108            .await
2109            .expect("Should save");
2110
2111        initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2112
2113        assert!(client1.encryption().backups().are_enabled().await);
2114
2115        // The other client can't take the lock too.
2116        let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2117        assert!(acquired2.is_none());
2118
2119        // Now have the first client release the lock,
2120        drop(acquired1);
2121        tokio::time::sleep(Duration::from_millis(100)).await;
2122
2123        // And re-take it.
2124        let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2125        assert!(acquired1.is_some());
2126
2127        // In that case, the Olm Machine shouldn't change.
2128        let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2129        assert!(initial_olm_machine.same_as(&olm_machine));
2130
2131        // Ok, release again.
2132        drop(acquired1);
2133        tokio::time::sleep(Duration::from_millis(100)).await;
2134
2135        // Client2 can acquire the lock.
2136        let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2137        assert!(acquired2.is_some());
2138
2139        // And then release it.
2140        drop(acquired2);
2141        tokio::time::sleep(Duration::from_millis(100)).await;
2142
2143        // Client1 can acquire it again,
2144        let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2145        assert!(acquired1.is_some());
2146
2147        // But now its olm machine has been invalidated and thus regenerated!
2148        let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2149
2150        assert!(!initial_olm_machine.same_as(&olm_machine));
2151
2152        let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2153        assert!(backup_key_new.decryption_key.is_some());
2154        assert_eq!(
2155            backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2156            backup_key.to_base64()
2157        );
2158        assert!(client1.encryption().backups().are_enabled().await);
2159    }
2160
2161    #[cfg(feature = "sqlite")]
2162    #[async_test]
2163    async fn test_generation_counter_no_spurious_invalidation() {
2164        // Create two clients using the same sqlite database.
2165
2166        use matrix_sdk_base::store::RoomLoadSettings;
2167        let sqlite_path =
2168            std::env::temp_dir().join("generation_counter_no_spurious_invalidations.db");
2169        let session = mock_matrix_session();
2170
2171        let client = Client::builder()
2172            .homeserver_url("http://localhost:1234")
2173            .request_config(RequestConfig::new().disable_retry())
2174            .sqlite_store(&sqlite_path, None)
2175            .build()
2176            .await
2177            .unwrap();
2178        client
2179            .matrix_auth()
2180            .restore_session(session.clone(), RoomLoadSettings::default())
2181            .await
2182            .unwrap();
2183
2184        let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2185
2186        client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2187
2188        // Enabling the lock doesn't update the olm machine.
2189        let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2190        assert!(initial_olm_machine.same_as(&after_enabling_lock));
2191
2192        {
2193            // Simulate that another client hold the lock before.
2194            let client2 = Client::builder()
2195                .homeserver_url("http://localhost:1234")
2196                .request_config(RequestConfig::new().disable_retry())
2197                .sqlite_store(sqlite_path, None)
2198                .build()
2199                .await
2200                .unwrap();
2201            client2
2202                .matrix_auth()
2203                .restore_session(session, RoomLoadSettings::default())
2204                .await
2205                .unwrap();
2206
2207            client2
2208                .encryption()
2209                .enable_cross_process_store_lock("client2".to_owned())
2210                .await
2211                .unwrap();
2212
2213            let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2214            assert!(guard.is_some());
2215
2216            drop(guard);
2217            tokio::time::sleep(Duration::from_millis(100)).await;
2218        }
2219
2220        {
2221            let acquired = client.encryption().try_lock_store_once().await.unwrap();
2222            assert!(acquired.is_some());
2223        }
2224
2225        // Taking the lock the first time will update the olm machine.
2226        let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2227        assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2228
2229        {
2230            let acquired = client.encryption().try_lock_store_once().await.unwrap();
2231            assert!(acquired.is_some());
2232        }
2233
2234        // Re-taking the lock doesn't update the olm machine.
2235        let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2236        assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2237    }
2238
2239    #[async_test]
2240    async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2241        // Given a client and a server
2242        let client = no_retry_test_client(None).await;
2243        let server = MockServer::start().await;
2244
2245        // When we subscribe to its verification state
2246        let mut verification_state = client.encryption().verification_state();
2247
2248        // We can get its initial value, and it's Unknown
2249        assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2250
2251        // We set up a mocked request to check this endpoint is not called before
2252        // reading the new state
2253        let keys_requested = Arc::new(AtomicBool::new(false));
2254        let inner_bool = keys_requested.clone();
2255
2256        Mock::given(method("GET"))
2257            .and(path_regex(
2258                r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2259            ))
2260            .respond_with(move |_req: &Request| {
2261                inner_bool.fetch_or(true, Ordering::SeqCst);
2262                ResponseTemplate::new(200).set_body_json(json!({}))
2263            })
2264            .mount(&server)
2265            .await;
2266
2267        // When the session is initialised and the encryption tasks spawn
2268        set_client_session(&client).await;
2269
2270        // Then we can get an updated value without waiting for any network requests
2271        assert!(keys_requested.load(Ordering::SeqCst).not());
2272        assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2273    }
2274
2275    #[test]
2276    fn test_oauth_reset_info_from_uiaa_info() {
2277        let auth_info = json!({
2278            "session": "dummy",
2279            "flows": [
2280                {
2281                    "stages": [
2282                        "org.matrix.cross_signing_reset"
2283                    ]
2284                }
2285            ],
2286            "params": {
2287                "org.matrix.cross_signing_reset": {
2288                    "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2289                }
2290            },
2291            "msg": "To reset..."
2292        });
2293
2294        let auth_info = serde_json::from_value(auth_info)
2295            .expect("We should be able to deserialize the UiaaInfo");
2296        OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2297            .expect("We should be able to fetch the cross-signing reset info from the auth info");
2298    }
2299}