Skip to main content

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::{Path, PathBuf},
26    str::FromStr,
27    sync::Arc,
28    time::Duration,
29};
30
31use eyeball::{SharedObservable, Subscriber};
32use futures_core::Stream;
33use futures_util::{
34    future::try_join,
35    stream::{self, StreamExt},
36};
37#[cfg(feature = "experimental-send-custom-to-device")]
38use matrix_sdk_base::crypto::CollectStrategy;
39use matrix_sdk_base::{
40    StateStoreDataKey, StateStoreDataValue,
41    cross_process_lock::{AcquireCrossProcessLockFn, CrossProcessLock, CrossProcessLockError},
42    crypto::{
43        CrossSigningBootstrapRequests, OlmMachine,
44        store::{
45            LockableCryptoStore, SecretImportError,
46            types::{RoomKeyBundleInfo, RoomKeyInfo},
47        },
48        types::{
49            SecretsBundle, SignedKey,
50            requests::{
51                OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
52            },
53        },
54    },
55    sleep::sleep,
56    timeout::timeout,
57};
58use matrix_sdk_common::{executor::spawn, locks::Mutex as StdMutex};
59use ruma::{
60    DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, TransactionId, UserId,
61    api::{
62        client::{
63            keys::{
64                get_keys, upload_keys, upload_signatures::v3::Request as UploadSignaturesRequest,
65                upload_signing_keys::v3::Request as UploadSigningKeysRequest,
66            },
67            message::send_message_event,
68            to_device::send_event_to_device::v3::{
69                Request as RumaToDeviceRequest, Response as ToDeviceResponse,
70            },
71            uiaa::{AuthData, AuthType, OAuthParams, UiaaInfo},
72        },
73        error::{ErrorBody, StandardErrorBody},
74    },
75    assign,
76    events::room::{
77        MediaSource, ThumbnailInfo,
78        member::{MembershipChange, OriginalSyncRoomMemberEvent},
79    },
80};
81#[cfg(feature = "experimental-send-custom-to-device")]
82use ruma::{events::AnyToDeviceEventContent, serde::Raw, to_device::DeviceIdOrAllDevices};
83use serde::{Deserialize, de::Error as _};
84use tasks::BundleReceiverTask;
85use tokio::sync::{Mutex, RwLockReadGuard};
86use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
87use tracing::{Instrument, Span, debug, error, instrument, warn};
88use url::Url;
89use vodozemac::Curve25519PublicKey;
90
91use self::{
92    backups::{Backups, types::BackupClientState},
93    futures::UploadEncryptedFile,
94    identities::{Device, DeviceUpdates, IdentityUpdates, UserDevices, UserIdentity},
95    recovery::{Recovery, RecoveryState},
96    secret_storage::SecretStorage,
97    tasks::{BackupDownloadTask, BackupUploadingTask, ClientTasks},
98    verification::{SasVerification, Verification, VerificationRequest},
99};
100use crate::{
101    Client, Error, HttpError, Result, Room, TransmissionProgress,
102    attachment::Thumbnail,
103    client::{ClientInner, WeakClient},
104    cross_process_lock::CrossProcessLockGuard,
105    error::HttpResult,
106};
107
108pub mod backups;
109pub mod dehydrated_devices;
110pub mod futures;
111pub mod identities;
112pub mod recovery;
113pub mod secret_storage;
114pub(crate) mod tasks;
115pub mod verification;
116
117use matrix_sdk_base::crypto::OlmMachineBuilder;
118pub use matrix_sdk_base::crypto::{
119    CrossSigningStatus, CryptoStoreError, DecryptorError, EventError, KeyExportError, LocalTrust,
120    MediaEncryptionInfo, MegolmError, OlmError, RoomKeyImportResult, SessionCreationError,
121    SignatureError, VERSION,
122    olm::{
123        SessionCreationError as MegolmSessionCreationError,
124        SessionExportError as OlmSessionExportError,
125    },
126    vodozemac,
127};
128use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
129
130#[cfg(feature = "experimental-send-custom-to-device")]
131use crate::config::RequestConfig;
132pub use crate::error::RoomKeyImportError;
133
134/// Error type describing failures that can happen while exporting a
135/// [`SecretsBundle`] from a SQLite store.
136#[cfg(feature = "sqlite")]
137#[derive(Debug, thiserror::Error)]
138pub enum BundleExportError {
139    /// The SQLite store couldn't be opened.
140    #[error(transparent)]
141    OpenStoreError(#[from] matrix_sdk_sqlite::OpenStoreError),
142    /// Data from the SQLite store couldn't be exported.
143    #[error(transparent)]
144    StoreError(#[from] CryptoStoreError),
145    /// The store doesn't contain a secrets bundle or it couldn't be read from
146    /// the store.
147    #[error(transparent)]
148    SecretExport(#[from] matrix_sdk_base::crypto::store::SecretsBundleExportError),
149}
150
151/// Error type describing failures that can happen while importing a
152/// [`SecretsBundle`].
153#[derive(Debug, thiserror::Error)]
154pub enum BundleImportError {
155    /// The bundle couldn't be imported.
156    #[error(transparent)]
157    SecretImport(#[from] SecretImportError),
158    /// The cross-signed device keys couldn't been uploaded.
159    #[error(transparent)]
160    DeviceKeys(#[from] Error),
161}
162
163/// Attempt to export a [`SecretsBundle`] from a crypto store.
164///
165/// This method can be used to retrieve a [`SecretsBundle`] from an existing
166/// `matrix-sdk`-based client in order to import the [`SecretsBundle`] in
167/// another [`Client`] instance.
168///
169/// This can be useful for migration purposes or to allow existing client
170/// instances create new ones that will be fully verified.
171#[cfg(feature = "sqlite")]
172pub async fn export_secrets_bundle_from_store(
173    database_path: impl AsRef<Path>,
174    passphrase: Option<&str>,
175) -> std::result::Result<Option<(OwnedUserId, SecretsBundle)>, BundleExportError> {
176    use matrix_sdk_base::crypto::store::CryptoStore;
177
178    let store = matrix_sdk_sqlite::SqliteCryptoStore::open(database_path, passphrase).await?;
179    let account =
180        store.load_account().await.map_err(|e| BundleExportError::StoreError(e.into()))?;
181
182    if let Some(account) = account {
183        let machine = OlmMachineBuilder::new(&account.user_id, &account.device_id)
184            .with_crypto_store(store)
185            .build()
186            .await
187            .map_err(BundleExportError::StoreError)?;
188
189        let bundle = machine.store().export_secrets_bundle().await?;
190
191        Ok(Some((account.user_id.to_owned(), bundle)))
192    } else {
193        Ok(None)
194    }
195}
196
197/// All the data related to the encryption state.
198pub(crate) struct EncryptionData {
199    /// Background tasks related to encryption (key backup, initialization
200    /// tasks, etc.).
201    pub tasks: StdMutex<ClientTasks>,
202
203    /// End-to-end encryption settings.
204    pub encryption_settings: EncryptionSettings,
205
206    /// All state related to key backup.
207    pub backup_state: BackupClientState,
208
209    /// All state related to secret storage recovery.
210    pub recovery_state: SharedObservable<RecoveryState>,
211
212    /// State for the dehydrated-devices manager (event channel, scheduled
213    /// rotation task).
214    pub dehydrated_devices_state: dehydrated_devices::DehydratedDevicesState,
215}
216
217impl EncryptionData {
218    pub fn new(encryption_settings: EncryptionSettings) -> Self {
219        Self {
220            encryption_settings,
221
222            tasks: StdMutex::new(Default::default()),
223            backup_state: Default::default(),
224            recovery_state: Default::default(),
225            dehydrated_devices_state: Default::default(),
226        }
227    }
228
229    pub fn initialize_tasks(&self, client: &Arc<ClientInner>) {
230        let weak_client = WeakClient::from_inner(client);
231
232        let mut tasks = self.tasks.lock();
233        tasks.upload_room_keys = Some(BackupUploadingTask::new(weak_client.clone()));
234
235        if self.encryption_settings.backup_download_strategy
236            == BackupDownloadStrategy::AfterDecryptionFailure
237        {
238            tasks.download_room_keys = Some(BackupDownloadTask::new(weak_client));
239        }
240    }
241
242    /// Initialize the background task which listens for changes in the
243    /// [`backups::BackupState`] and updataes the [`recovery::RecoveryState`].
244    ///
245    /// This should happen after the usual tasks have been set up and after the
246    /// E2EE initialization tasks have been set up.
247    pub fn initialize_recovery_state_update_task(&self, client: &Client) {
248        let mut guard = self.tasks.lock();
249
250        let future = Recovery::update_state_after_backup_state_change(client);
251        let join_handle = spawn(future);
252
253        guard.update_recovery_state_after_backup = Some(join_handle);
254    }
255}
256
257/// Settings for end-to-end encryption features.
258#[derive(Clone, Copy, Debug, Default)]
259pub struct EncryptionSettings {
260    /// Automatically bootstrap cross-signing for a user once they're logged, in
261    /// case it's not already done yet.
262    ///
263    /// This requires to login with a username and password, or that MSC3967 is
264    /// enabled on the server, as of 2023-10-20.
265    pub auto_enable_cross_signing: bool,
266
267    /// Select a strategy to download room keys from the backup, by default room
268    /// keys won't be downloaded from the backup automatically.
269    ///
270    /// Take a look at the [`BackupDownloadStrategy`] enum for more options.
271    pub backup_download_strategy: BackupDownloadStrategy,
272
273    /// Automatically create a backup version if no backup exists.
274    pub auto_enable_backups: bool,
275}
276
277/// Settings for end-to-end encryption features.
278#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
279#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
280pub enum BackupDownloadStrategy {
281    /// Automatically download all room keys from the backup when the backup
282    /// recovery key has been received. The backup recovery key can be received
283    /// in two ways:
284    ///
285    /// 1. Received as a `m.secret.send` to-device event, after a successful
286    ///    interactive verification.
287    /// 2. Imported from secret storage (4S) using the
288    ///    [`SecretStore::import_secrets()`] method.
289    ///
290    /// [`SecretStore::import_secrets()`]: crate::encryption::secret_storage::SecretStore::import_secrets
291    OneShot,
292
293    /// Attempt to download a single room key if an event fails to be decrypted.
294    AfterDecryptionFailure,
295
296    /// Don't download any room keys automatically. The user can manually
297    /// download room keys using the [`Backups::download_room_key()`] methods.
298    ///
299    /// This is the default option.
300    #[default]
301    Manual,
302}
303
304/// The verification state of our own device
305///
306/// This enum tells us if our own user identity trusts these devices, in other
307/// words it tells us if the user identity has signed the device.
308#[derive(Clone, Copy, Debug, Eq, PartialEq)]
309pub enum VerificationState {
310    /// The verification state is unknown for now.
311    Unknown,
312    /// The device is considered to be verified, it has been signed by its user
313    /// identity.
314    Verified,
315    /// The device is unverified.
316    Unverified,
317}
318
319/// A stateful struct remembering the cross-signing keys we need to upload.
320///
321/// Since the `/_matrix/client/v3/keys/device_signing/upload` might require
322/// additional authentication, this struct will contain information on the type
323/// of authentication the user needs to complete before the upload might be
324/// continued.
325///
326/// More info can be found in the [spec].
327///
328/// [spec]: https://spec.matrix.org/v1.11/client-server-api/#post_matrixclientv3keysdevice_signingupload
329#[derive(Debug)]
330pub struct CrossSigningResetHandle {
331    client: Client,
332    upload_request: UploadSigningKeysRequest,
333    signatures_request: UploadSignaturesRequest,
334    auth_type: CrossSigningResetAuthType,
335    is_cancelled: Mutex<bool>,
336}
337
338impl CrossSigningResetHandle {
339    /// Set up a new `CrossSigningResetHandle`.
340    pub fn new(
341        client: Client,
342        upload_request: UploadSigningKeysRequest,
343        signatures_request: UploadSignaturesRequest,
344        auth_type: CrossSigningResetAuthType,
345    ) -> Self {
346        Self {
347            client,
348            upload_request,
349            signatures_request,
350            auth_type,
351            is_cancelled: Mutex::new(false),
352        }
353    }
354
355    /// Get the [`CrossSigningResetAuthType`] this cross-signing reset process
356    /// is using.
357    pub fn auth_type(&self) -> &CrossSigningResetAuthType {
358        &self.auth_type
359    }
360
361    /// Continue the cross-signing reset by either waiting for the
362    /// authentication to be done on the side of the OAuth 2.0 server or by
363    /// providing additional [`AuthData`] the homeserver requires.
364    pub async fn auth(&self, auth: Option<AuthData>) -> Result<()> {
365        // Poll to see whether the reset has been authorized twice per second.
366        const RETRY_EVERY: Duration = Duration::from_millis(500);
367
368        // Give up after two minutes of polling.
369        const TIMEOUT: Duration = Duration::from_mins(2);
370
371        timeout(
372            async {
373                let mut upload_request = self.upload_request.clone();
374                upload_request.auth = auth;
375
376                debug!(
377                    "Repeatedly PUTting to keys/device_signing/upload until it works \
378                    or we hit a permanent failure."
379                );
380                while let Err(e) = self.client.send(upload_request.clone()).await {
381                    if *self.is_cancelled.lock().await {
382                        return Ok(());
383                    }
384
385                    match e.as_uiaa_response() {
386                        Some(uiaa_info) => {
387                            // Return the error except if we are at the `m.oauth` stage where we
388                            // want to keep polling.
389                            if !matches!(self.auth_type, CrossSigningResetAuthType::OAuth(_))
390                                && uiaa_info.auth_error.is_some()
391                            {
392                                return Err(e.into());
393                            }
394                        }
395                        None => return Err(e.into()),
396                    }
397
398                    debug!(
399                        "PUT to keys/device_signing/upload failed with 401. Retrying after \
400                        a short delay."
401                    );
402                    sleep(RETRY_EVERY).await;
403                }
404
405                self.client.send(self.signatures_request.clone()).await?;
406
407                Ok(())
408            },
409            TIMEOUT,
410        )
411        .await
412        .unwrap_or_else(|_| {
413            warn!("Timed out waiting for keys/device_signing/upload to succeed.");
414            Err(Error::Timeout)
415        })
416    }
417
418    /// Cancel the ongoing identity reset process
419    pub async fn cancel(&self) {
420        *self.is_cancelled.lock().await = true;
421    }
422}
423
424/// information about the additional authentication that is required before the
425/// cross-signing keys can be uploaded.
426#[derive(Debug, Clone)]
427pub enum CrossSigningResetAuthType {
428    /// The homeserver requires user-interactive authentication.
429    Uiaa(UiaaInfo),
430    /// OAuth 2.0 is used for authentication and the user needs to open a URL to
431    /// approve the upload of cross-signing keys.
432    OAuth(OAuthCrossSigningResetInfo),
433}
434
435impl CrossSigningResetAuthType {
436    fn new(error: &HttpError) -> Result<Option<Self>> {
437        if let Some(auth_info) = error.as_uiaa_response() {
438            if let Ok(Some(auth_info)) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
439                Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
440            } else {
441                Ok(Some(CrossSigningResetAuthType::Uiaa(auth_info.clone())))
442            }
443        } else {
444            Ok(None)
445        }
446    }
447}
448
449/// OAuth 2.0 specific information about the required authentication for the
450/// upload of cross-signing keys.
451#[derive(Debug, Clone, Deserialize)]
452pub struct OAuthCrossSigningResetInfo {
453    /// The URL where the user can approve the reset of the cross-signing keys.
454    pub approval_url: Url,
455
456    /// Session key to use to complete the authentication.
457    pub session: Option<String>,
458}
459
460impl OAuthCrossSigningResetInfo {
461    fn from_auth_info(auth_info: &UiaaInfo) -> Result<Option<Self>> {
462        let Some(parameters) = auth_info.params::<OAuthParams>(&AuthType::OAuth)? else {
463            return Ok(None);
464        };
465
466        Ok(Some(OAuthCrossSigningResetInfo {
467            approval_url: parameters.url.as_str().try_into()?,
468            session: auth_info.session.clone(),
469        }))
470    }
471}
472
473/// A struct that helps to parse the custom error message Synapse posts if a
474/// duplicate one-time key is uploaded.
475#[derive(Clone, Debug)]
476pub struct DuplicateOneTimeKeyErrorMessage {
477    /// The previously uploaded one-time key.
478    pub old_key: Curve25519PublicKey,
479    /// The one-time key we're attempting to upload right now.
480    pub new_key: Curve25519PublicKey,
481}
482
483impl FromStr for DuplicateOneTimeKeyErrorMessage {
484    type Err = serde_json::Error;
485
486    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
487        // First we split the string into two parts, the part containing the old key and
488        // the part containing the new key. The parts are conveniently separated
489        // by a `;` character.
490        let mut split = s.split_terminator(';');
491
492        let old_key = split
493            .next()
494            .ok_or(serde_json::Error::custom("Old key is missing in the error message"))?;
495        let new_key = split
496            .next()
497            .ok_or(serde_json::Error::custom("New key is missing in the error message"))?;
498
499        // Now we remove the lengthy prefix from the part containing the old key, we
500        // should be left with just the JSON of the signed key.
501        let old_key_index = old_key
502            .find("Old key:")
503            .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
504
505        let old_key = old_key[old_key_index..]
506            .trim()
507            .strip_prefix("Old key:")
508            .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
509
510        // The part containing the new key is much simpler, we just remove a static
511        // prefix.
512        let new_key = new_key
513            .trim()
514            .strip_prefix("new key:")
515            .ok_or(serde_json::Error::custom("New key is missing the prefix"))?;
516
517        // The JSON containing the new key is for some reason quoted using single
518        // quotes, so let's replace them with normal double quotes.
519        let new_key = new_key.replace("'", "\"");
520
521        // Let's deserialize now.
522        let old_key: SignedKey = serde_json::from_str(old_key)?;
523        let new_key: SignedKey = serde_json::from_str(&new_key)?;
524
525        // Pick out the Curve keys, we don't care about the rest that much.
526        let old_key = old_key.key();
527        let new_key = new_key.key();
528
529        Ok(Self { old_key, new_key })
530    }
531}
532
533impl Client {
534    pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
535        self.base_client().olm_machine().await
536    }
537
538    pub(crate) async fn mark_request_as_sent(
539        &self,
540        request_id: &TransactionId,
541        response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
542    ) -> Result<(), matrix_sdk_base::Error> {
543        Ok(self
544            .olm_machine()
545            .await
546            .as_ref()
547            .expect(
548                "We should have an olm machine once we try to mark E2EE related requests as sent",
549            )
550            .mark_request_as_sent(request_id, response)
551            .await?)
552    }
553
554    /// Query the server for users device keys.
555    ///
556    /// # Panics
557    ///
558    /// Panics if no key query needs to be done.
559    #[instrument(skip(self, device_keys))]
560    pub(crate) async fn keys_query(
561        &self,
562        request_id: &TransactionId,
563        device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
564    ) -> Result<get_keys::v3::Response> {
565        let request = assign!(get_keys::v3::Request::new(), { device_keys });
566
567        let response = self.send(request).await?;
568        self.mark_request_as_sent(request_id, &response).await?;
569        self.encryption().update_state_after_keys_query(&response).await;
570
571        Ok(response)
572    }
573
574    /// Construct a [`EncryptedFile`][ruma::events::room::EncryptedFile] by
575    /// encrypting and uploading a provided reader.
576    ///
577    /// # Arguments
578    ///
579    /// * `content_type` - The content type of the file.
580    /// * `reader` - The reader that should be encrypted and uploaded.
581    ///
582    /// # Examples
583    ///
584    /// ```no_run
585    /// # use matrix_sdk::Client;
586    /// # use url::Url;
587    /// # use matrix_sdk::ruma::{room_id, OwnedRoomId};
588    /// use serde::{Deserialize, Serialize};
589    /// use matrix_sdk::ruma::events::{macros::EventContent, room::EncryptedFile};
590    ///
591    /// #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
592    /// #[ruma_event(type = "com.example.custom", kind = MessageLike)]
593    /// struct CustomEventContent {
594    ///     encrypted_file: EncryptedFile,
595    /// }
596    ///
597    /// # async {
598    /// # let homeserver = Url::parse("http://example.com")?;
599    /// # let client = Client::new(homeserver).await?;
600    /// # let room = client.get_room(&room_id!("!test:example.com")).unwrap();
601    /// let mut reader = std::io::Cursor::new(b"Hello, world!");
602    /// let encrypted_file = client.upload_encrypted_file(&mut reader).await?;
603    ///
604    /// room.send(CustomEventContent { encrypted_file }).await?;
605    /// # anyhow::Ok(()) };
606    /// ```
607    pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
608        &'a self,
609        reader: &'a mut R,
610    ) -> UploadEncryptedFile<'a, R> {
611        UploadEncryptedFile::new(self, reader)
612    }
613
614    /// Encrypt and upload the file and thumbnails, and return the source
615    /// information.
616    pub(crate) async fn upload_encrypted_media_and_thumbnail(
617        &self,
618        data: &[u8],
619        thumbnail: Option<Thumbnail>,
620        send_progress: SharedObservable<TransmissionProgress>,
621    ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
622        let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
623
624        let upload_attachment = async {
625            let mut cursor = Cursor::new(data);
626            self.upload_encrypted_file(&mut cursor)
627                .with_send_progress_observable(send_progress)
628                .await
629        };
630
631        let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
632
633        Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
634    }
635
636    /// Uploads an encrypted thumbnail to the media repository, and returns
637    /// its source and extra information.
638    async fn upload_encrypted_thumbnail(
639        &self,
640        thumbnail: Option<Thumbnail>,
641        send_progress: SharedObservable<TransmissionProgress>,
642    ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
643        let Some(thumbnail) = thumbnail else {
644            return Ok(None);
645        };
646
647        let (data, _, thumbnail_info) = thumbnail.into_parts();
648        let mut cursor = Cursor::new(data);
649
650        let file = self
651            .upload_encrypted_file(&mut cursor)
652            .with_send_progress_observable(send_progress)
653            .await?;
654
655        Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
656    }
657
658    /// Claim one-time keys creating new Olm sessions.
659    ///
660    /// # Arguments
661    ///
662    /// * `users` - The list of user/device pairs that we should claim keys for.
663    pub(crate) async fn claim_one_time_keys(
664        &self,
665        users: impl Iterator<Item = &UserId>,
666    ) -> Result<()> {
667        let _lock = self.locks().key_claim_lock.lock().await;
668
669        if let Some((request_id, request)) = self
670            .olm_machine()
671            .await
672            .as_ref()
673            .ok_or(Error::NoOlmMachine)?
674            .get_missing_sessions(users)
675            .await?
676        {
677            let response = self.send(request).await?;
678            self.mark_request_as_sent(&request_id, &response).await?;
679        }
680
681        Ok(())
682    }
683
684    /// Upload the E2E encryption keys.
685    ///
686    /// This uploads the long lived device keys as well as the required amount
687    /// of one-time keys.
688    ///
689    /// # Panics
690    ///
691    /// Panics if the client isn't logged in, or if no encryption keys need to
692    /// be uploaded.
693    #[instrument(skip(self, request))]
694    pub(crate) async fn keys_upload(
695        &self,
696        request_id: &TransactionId,
697        request: &upload_keys::v3::Request,
698    ) -> Result<upload_keys::v3::Response> {
699        debug!(
700            device_keys = request.device_keys.is_some(),
701            one_time_key_count = request.one_time_keys.len(),
702            "Uploading public encryption keys",
703        );
704
705        let response = self.send(request.clone()).await?;
706        self.mark_request_as_sent(request_id, &response).await?;
707
708        Ok(response)
709    }
710
711    pub(crate) async fn room_send_helper(
712        &self,
713        request: &RoomMessageRequest,
714    ) -> Result<send_message_event::v3::Response> {
715        let content = request.content.clone();
716        let txn_id = request.txn_id.clone();
717        let room_id = &request.room_id;
718
719        self.get_room(room_id)
720            .expect("Can't send a message to a room that isn't known to the store")
721            .send(*content)
722            .with_transaction_id(txn_id)
723            .await
724            .map(|result| result.response)
725    }
726
727    pub(crate) async fn send_to_device(
728        &self,
729        request: &ToDeviceRequest,
730    ) -> HttpResult<ToDeviceResponse> {
731        let request = RumaToDeviceRequest::new_raw(
732            request.event_type.clone(),
733            request.txn_id.clone(),
734            request.messages.clone(),
735        );
736
737        self.send(request).await
738    }
739
740    pub(crate) async fn send_verification_request(
741        &self,
742        request: OutgoingVerificationRequest,
743    ) -> Result<()> {
744        use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
745
746        match request {
747            ToDevice(t) => {
748                self.send_to_device(&t).await?;
749            }
750            InRoom(r) => {
751                self.room_send_helper(&r).await?;
752            }
753        }
754
755        Ok(())
756    }
757
758    async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
759        use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
760
761        match r.request() {
762            AnyOutgoingRequest::KeysQuery(request) => {
763                self.keys_query(r.request_id(), request.device_keys.clone()).await?;
764            }
765            AnyOutgoingRequest::KeysUpload(request) => {
766                let response = self.keys_upload(r.request_id(), request).await;
767
768                if let Err(e) = &response {
769                    match e.as_client_api_error() {
770                        Some(e) if e.status_code == 400 => {
771                            if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &e.body
772                            {
773                                // This is one of the nastiest errors we can have. The server
774                                // telling us that we already have a one-time key uploaded means
775                                // that we forgot about some of our one-time keys. This will lead to
776                                // UTDs.
777                                {
778                                    let already_reported = self
779                                        .state_store()
780                                        .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
781                                        .await?
782                                        .is_some();
783
784                                    if message.starts_with("One time key") && !already_reported {
785                                        let error_message =
786                                            DuplicateOneTimeKeyErrorMessage::from_str(message);
787
788                                        if let Ok(message) = &error_message {
789                                            error!(
790                                                sentry = true,
791                                                old_key = %message.old_key,
792                                                new_key = %message.new_key,
793                                                "Duplicate one-time keys have been uploaded"
794                                            );
795                                        } else {
796                                            error!(
797                                                sentry = true,
798                                                "Duplicate one-time keys have been uploaded"
799                                            );
800                                        }
801
802                                        self.state_store()
803                                            .set_kv_data(
804                                                StateStoreDataKey::OneTimeKeyAlreadyUploaded,
805                                                StateStoreDataValue::OneTimeKeyAlreadyUploaded,
806                                            )
807                                            .await?;
808
809                                        if let Err(e) = self
810                                            .inner
811                                            .duplicate_key_upload_error_sender
812                                            .send(error_message.ok())
813                                        {
814                                            error!(
815                                                "Failed to dispatch duplicate key upload error notification: {}",
816                                                e
817                                            );
818                                        }
819                                    }
820                                }
821                            }
822                        }
823                        _ => {}
824                    }
825
826                    response?;
827                }
828            }
829            AnyOutgoingRequest::ToDeviceRequest(request) => {
830                let response = self.send_to_device(request).await?;
831                self.mark_request_as_sent(r.request_id(), &response).await?;
832            }
833            AnyOutgoingRequest::SignatureUpload(request) => {
834                let response = self.send(request.clone()).await?;
835                self.mark_request_as_sent(r.request_id(), &response).await?;
836            }
837            AnyOutgoingRequest::RoomMessage(request) => {
838                let response = self.room_send_helper(request).await?;
839                self.mark_request_as_sent(r.request_id(), &response).await?;
840            }
841            AnyOutgoingRequest::KeysClaim(request) => {
842                let response = self.send(request.clone()).await?;
843                self.mark_request_as_sent(r.request_id(), &response).await?;
844            }
845        }
846
847        Ok(())
848    }
849
850    #[instrument(skip_all)]
851    pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
852        const MAX_CONCURRENT_REQUESTS: usize = 20;
853
854        // This is needed because sometimes we need to automatically
855        // claim some one-time keys to unwedge an existing Olm session.
856        if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
857            warn!("Error while claiming one-time keys {:?}", e);
858        }
859
860        let outgoing_requests = stream::iter(
861            self.olm_machine()
862                .await
863                .as_ref()
864                .ok_or(Error::NoOlmMachine)?
865                .outgoing_requests()
866                .await?,
867        )
868        .map(|r| self.send_outgoing_request(r));
869
870        let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
871
872        requests
873            .for_each(|r| async move {
874                match r {
875                    Ok(_) => (),
876                    Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
877                }
878            })
879            .await;
880
881        Ok(())
882    }
883}
884
885#[cfg(any(feature = "testing", test))]
886impl Client {
887    /// Get the olm machine, for testing purposes only.
888    pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
889        self.olm_machine().await
890    }
891
892    /// Aborts the client's bundle receiver task, for testing purposes only.
893    pub fn abort_bundle_receiver_task(&self) {
894        let tasks = self.inner.e2ee.tasks.lock();
895        if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
896            task.abort()
897        }
898    }
899}
900
901/// A high-level API to manage the client's encryption.
902///
903/// To get this, use [`Client::encryption()`].
904#[derive(Debug, Clone)]
905pub struct Encryption {
906    /// The underlying client.
907    client: Client,
908}
909
910impl Encryption {
911    pub(crate) fn new(client: Client) -> Self {
912        Self { client }
913    }
914
915    /// Returns the current encryption settings for this client.
916    pub(crate) fn settings(&self) -> EncryptionSettings {
917        self.client.inner.e2ee.encryption_settings
918    }
919
920    /// Get the public ed25519 key of our own device. This is usually what is
921    /// called the fingerprint of the device.
922    pub async fn ed25519_key(&self) -> Option<String> {
923        self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
924    }
925
926    /// Get the public Curve25519 key of our own device.
927    pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
928        self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
929    }
930
931    /// Get the current device creation timestamp.
932    pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
933        match self.get_own_device().await {
934            Ok(Some(device)) => device.first_time_seen_ts(),
935            // Should not happen, there should always be an own device
936            _ => MilliSecondsSinceUnixEpoch::now(),
937        }
938    }
939
940    /// This method will import all the private cross-signing keys and, if
941    /// available, the private part of a backup key and its accompanying
942    /// version into the store.
943    ///
944    /// Importing all the secrets will mark the device as verified and enable
945    /// backups if a backup key was available in the bundle.
946    ///
947    /// **Warning**: Only import this from a trusted source, i.e. if an existing
948    /// device is sharing this with a new device.
949    ///
950    /// **Warning*: Only call this method right after logging in and before the
951    /// initial sync has been started.
952    pub async fn import_secrets_bundle(
953        &self,
954        bundle: &SecretsBundle,
955    ) -> Result<(), BundleImportError> {
956        self.import_secrets_bundle_impl(bundle).await?;
957
958        // Upload the device keys, this will ensure that other devices see us as a fully
959        // verified device as soon as this method returns.
960        self.ensure_device_keys_upload().await?;
961        self.wait_for_e2ee_initialization_tasks().await;
962
963        // If our initialization tasks completed before we imported the secrets bundle,
964        // backups might not have been enabled.
965        //
966        // In this case attempt to enable them again.
967        if !self.backups().are_enabled().await {
968            self.backups().maybe_resume_backups().await?;
969        }
970
971        Ok(())
972    }
973
974    pub(crate) async fn import_secrets_bundle_impl(
975        &self,
976        bundle: &SecretsBundle,
977    ) -> Result<(), SecretImportError> {
978        let olm_machine = self.client.olm_machine().await;
979        let olm_machine =
980            olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
981
982        olm_machine.store().import_secrets_bundle(bundle).await
983    }
984
985    /// Get the status of the private cross signing keys.
986    ///
987    /// This can be used to check which private cross signing keys we have
988    /// stored locally.
989    pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
990        let olm = self.client.olm_machine().await;
991        let machine = olm.as_ref()?;
992        Some(machine.cross_signing_status().await)
993    }
994
995    /// Does the user have other devices that the current device can verify
996    /// against?
997    ///
998    /// The device must be signed by the user's cross-signing key, must have an
999    /// identity, and must not be a dehydrated device.
1000    pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
1001        let olm_machine = self.client.olm_machine().await;
1002        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1003        let user_id = olm_machine.user_id();
1004
1005        self.ensure_initial_key_query().await?;
1006
1007        let devices = self.get_user_devices(user_id).await?;
1008
1009        let ret = devices.devices().any(|device| {
1010            device.is_cross_signed_by_owner()
1011                && device.curve25519_key().is_some()
1012                && !device.is_dehydrated()
1013        });
1014
1015        Ok(ret)
1016    }
1017
1018    /// Get all the tracked users we know about
1019    ///
1020    /// Tracked users are users for which we keep the device list of E2EE
1021    /// capable devices up to date.
1022    pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1023        if let Some(machine) = self.client.olm_machine().await.as_ref() {
1024            machine.tracked_users().await
1025        } else {
1026            Ok(HashSet::new())
1027        }
1028    }
1029
1030    /// Get a [`Subscriber`] for the [`VerificationState`].
1031    ///
1032    /// # Examples
1033    ///
1034    /// ```no_run
1035    /// use matrix_sdk::{Client, encryption};
1036    /// use url::Url;
1037    ///
1038    /// # async {
1039    /// let homeserver = Url::parse("http://example.com")?;
1040    /// let client = Client::new(homeserver).await?;
1041    /// let mut subscriber = client.encryption().verification_state();
1042    ///
1043    /// let current_value = subscriber.get();
1044    ///
1045    /// println!("The current verification state is: {current_value:?}");
1046    ///
1047    /// if let Some(verification_state) = subscriber.next().await {
1048    ///     println!("Received verification state update {:?}", verification_state)
1049    /// }
1050    /// # anyhow::Ok(()) };
1051    /// ```
1052    pub fn verification_state(&self) -> Subscriber<VerificationState> {
1053        self.client.inner.verification_state.subscribe_reset()
1054    }
1055
1056    /// Get a verification object with the given flow id.
1057    pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1058        let olm = self.client.olm_machine().await;
1059        let olm = olm.as_ref()?;
1060        #[allow(clippy::bind_instead_of_map)]
1061        olm.get_verification(user_id, flow_id).and_then(|v| match v {
1062            matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1063                Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1064            }
1065            #[cfg(feature = "qrcode")]
1066            matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1067                Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1068            }
1069            _ => None,
1070        })
1071    }
1072
1073    /// Get a `VerificationRequest` object for the given user with the given
1074    /// flow id.
1075    pub async fn get_verification_request(
1076        &self,
1077        user_id: &UserId,
1078        flow_id: impl AsRef<str>,
1079    ) -> Option<VerificationRequest> {
1080        let olm = self.client.olm_machine().await;
1081        let olm = olm.as_ref()?;
1082
1083        olm.get_verification_request(user_id, flow_id)
1084            .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1085    }
1086
1087    /// Get a specific device of a user.
1088    ///
1089    /// # Arguments
1090    ///
1091    /// * `user_id` - The unique id of the user that the device belongs to.
1092    ///
1093    /// * `device_id` - The unique id of the device.
1094    ///
1095    /// Returns a `Device` if one is found and the crypto store didn't throw an
1096    /// error.
1097    ///
1098    /// This will always return None if the client hasn't been logged in.
1099    ///
1100    /// # Examples
1101    ///
1102    /// ```no_run
1103    /// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
1104    /// # use url::Url;
1105    /// # async {
1106    /// # let alice = user_id!("@alice:example.org");
1107    /// # let homeserver = Url::parse("http://example.com")?;
1108    /// # let client = Client::new(homeserver).await?;
1109    /// if let Some(device) =
1110    ///     client.encryption().get_device(alice, device_id!("DEVICEID")).await?
1111    /// {
1112    ///     println!("{:?}", device.is_verified());
1113    ///
1114    ///     if !device.is_verified() {
1115    ///         let verification = device.request_verification().await?;
1116    ///     }
1117    /// }
1118    /// # anyhow::Ok(()) };
1119    /// ```
1120    pub async fn get_device(
1121        &self,
1122        user_id: &UserId,
1123        device_id: &DeviceId,
1124    ) -> Result<Option<Device>, CryptoStoreError> {
1125        let olm = self.client.olm_machine().await;
1126        let Some(machine) = olm.as_ref() else { return Ok(None) };
1127        let device = machine.get_device(user_id, device_id, None).await?;
1128        Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1129    }
1130
1131    /// A convenience method to retrieve your own device from the store.
1132    ///
1133    /// This is the same as calling [`Encryption::get_device()`] with your own
1134    /// user and device ID.
1135    ///
1136    /// This will always return a device, unless you are not logged in.
1137    pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1138        let olm = self.client.olm_machine().await;
1139        let Some(machine) = olm.as_ref() else { return Ok(None) };
1140        let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1141        Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1142    }
1143
1144    /// Get a map holding all the devices of an user.
1145    ///
1146    /// This will always return an empty map if the client hasn't been logged
1147    /// in.
1148    ///
1149    /// # Arguments
1150    ///
1151    /// * `user_id` - The unique id of the user that the devices belong to.
1152    ///
1153    /// # Examples
1154    ///
1155    /// ```no_run
1156    /// # use matrix_sdk::{Client, ruma::user_id};
1157    /// # use url::Url;
1158    /// # async {
1159    /// # let alice = user_id!("@alice:example.org");
1160    /// # let homeserver = Url::parse("http://example.com")?;
1161    /// # let client = Client::new(homeserver).await?;
1162    /// let devices = client.encryption().get_user_devices(alice).await?;
1163    ///
1164    /// for device in devices.devices() {
1165    ///     println!("{device:?}");
1166    /// }
1167    /// # anyhow::Ok(()) };
1168    /// ```
1169    pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1170        let devices = self
1171            .client
1172            .olm_machine()
1173            .await
1174            .as_ref()
1175            .ok_or(Error::NoOlmMachine)?
1176            .get_user_devices(user_id, None)
1177            .await?;
1178
1179        Ok(UserDevices { inner: devices, client: self.client.clone() })
1180    }
1181
1182    /// Get the E2EE identity of a user from the crypto store.
1183    ///
1184    /// Usually, we only have the E2EE identity of a user locally if the user
1185    /// is tracked, meaning that we are both members of the same encrypted room.
1186    ///
1187    /// To get the E2EE identity of a user even if it is not available locally
1188    /// use [`Encryption::request_user_identity()`].
1189    ///
1190    /// # Arguments
1191    ///
1192    /// * `user_id` - The unique id of the user that the identity belongs to.
1193    ///
1194    /// Returns a `UserIdentity` if one is found and the crypto store
1195    /// didn't throw an error.
1196    ///
1197    /// This will always return None if the client hasn't been logged in.
1198    ///
1199    /// # Examples
1200    ///
1201    /// ```no_run
1202    /// # use matrix_sdk::{Client, ruma::user_id};
1203    /// # use url::Url;
1204    /// # async {
1205    /// # let alice = user_id!("@alice:example.org");
1206    /// # let homeserver = Url::parse("http://example.com")?;
1207    /// # let client = Client::new(homeserver).await?;
1208    /// let user = client.encryption().get_user_identity(alice).await?;
1209    ///
1210    /// if let Some(user) = user {
1211    ///     println!("{:?}", user.is_verified());
1212    ///
1213    ///     let verification = user.request_verification().await?;
1214    /// }
1215    /// # anyhow::Ok(()) };
1216    /// ```
1217    pub async fn get_user_identity(
1218        &self,
1219        user_id: &UserId,
1220    ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1221        let olm = self.client.olm_machine().await;
1222        let Some(olm) = olm.as_ref() else { return Ok(None) };
1223        let identity = olm.get_identity(user_id, None).await?;
1224
1225        Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1226    }
1227
1228    /// Get the E2EE identity of a user from the homeserver.
1229    ///
1230    /// The E2EE identity returned is always guaranteed to be up-to-date. If the
1231    /// E2EE identity is not found, it should mean that the user did not set
1232    /// up cross-signing.
1233    ///
1234    /// If you want the E2EE identity of a user without making a request to the
1235    /// homeserver, use [`Encryption::get_user_identity()`] instead.
1236    ///
1237    /// # Arguments
1238    ///
1239    /// * `user_id` - The ID of the user that the identity belongs to.
1240    ///
1241    /// Returns a [`UserIdentity`] if one is found. Returns an error if there
1242    /// was an issue with the crypto store or with the request to the
1243    /// homeserver.
1244    ///
1245    /// This will always return `None` if the client hasn't been logged in.
1246    ///
1247    /// # Examples
1248    ///
1249    /// ```no_run
1250    /// # use matrix_sdk::{Client, ruma::user_id};
1251    /// # use url::Url;
1252    /// # async {
1253    /// # let alice = user_id!("@alice:example.org");
1254    /// # let homeserver = Url::parse("http://example.com")?;
1255    /// # let client = Client::new(homeserver).await?;
1256    /// let user = client.encryption().request_user_identity(alice).await?;
1257    ///
1258    /// if let Some(user) = user {
1259    ///     println!("User is verified: {:?}", user.is_verified());
1260    ///
1261    ///     let verification = user.request_verification().await?;
1262    /// }
1263    /// # anyhow::Ok(()) };
1264    /// ```
1265    pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1266        let olm = self.client.olm_machine().await;
1267        let Some(olm) = olm.as_ref() else { return Ok(None) };
1268
1269        let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1270        self.client.keys_query(&request_id, request.device_keys).await?;
1271
1272        let identity = olm.get_identity(user_id, None).await?;
1273        Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1274    }
1275
1276    /// Returns a stream of device updates, allowing users to listen for
1277    /// notifications about new or changed devices.
1278    ///
1279    /// The stream produced by this method emits updates whenever a new device
1280    /// is discovered or when an existing device's information is changed. Users
1281    /// can subscribe to this stream and receive updates in real-time.
1282    ///
1283    /// # Examples
1284    ///
1285    /// ```no_run
1286    /// # use matrix_sdk::Client;
1287    /// # use ruma::{device_id, user_id};
1288    /// # use futures_util::{pin_mut, StreamExt};
1289    /// # let client: Client = unimplemented!();
1290    /// # async {
1291    /// let devices_stream = client.encryption().devices_stream().await?;
1292    /// let user_id = client
1293    ///     .user_id()
1294    ///     .expect("We should know our user id after we have logged in");
1295    /// pin_mut!(devices_stream);
1296    ///
1297    /// for device_updates in devices_stream.next().await {
1298    ///     if let Some(user_devices) = device_updates.new.get(user_id) {
1299    ///         for device in user_devices.values() {
1300    ///             println!("A new device has been added {}", device.device_id());
1301    ///         }
1302    ///     }
1303    /// }
1304    /// # anyhow::Ok(()) };
1305    /// ```
1306    pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1307        let olm = self.client.olm_machine().await;
1308        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1309        let client = self.client.to_owned();
1310
1311        Ok(olm
1312            .store()
1313            .devices_stream()
1314            .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1315    }
1316
1317    /// Returns a stream of user identity updates, allowing users to listen for
1318    /// notifications about new or changed user identities.
1319    ///
1320    /// The stream produced by this method emits updates whenever a new user
1321    /// identity is discovered or when an existing identities information is
1322    /// changed. Users can subscribe to this stream and receive updates in
1323    /// real-time.
1324    ///
1325    /// # Examples
1326    ///
1327    /// ```no_run
1328    /// # use matrix_sdk::Client;
1329    /// # use ruma::{device_id, user_id};
1330    /// # use futures_util::{pin_mut, StreamExt};
1331    /// # let client: Client = unimplemented!();
1332    /// # async {
1333    /// let identities_stream =
1334    ///     client.encryption().user_identities_stream().await?;
1335    /// pin_mut!(identities_stream);
1336    ///
1337    /// for identity_updates in identities_stream.next().await {
1338    ///     for (_, identity) in identity_updates.new {
1339    ///         println!("A new identity has been added {}", identity.user_id());
1340    ///     }
1341    /// }
1342    /// # anyhow::Ok(()) };
1343    /// ```
1344    pub async fn user_identities_stream(
1345        &self,
1346    ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1347        let olm = self.client.olm_machine().await;
1348        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1349        let client = self.client.to_owned();
1350
1351        Ok(olm
1352            .store()
1353            .user_identities_stream()
1354            .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1355    }
1356
1357    /// Create and upload a new cross signing identity.
1358    ///
1359    /// # Arguments
1360    ///
1361    /// * `auth_data` - This request requires user interactive auth, the first
1362    ///   request needs to set this to `None` and will always fail with an
1363    ///   `UiaaResponse`. The response will contain information for the
1364    ///   interactive auth and the same request needs to be made but this time
1365    ///   with some `auth_data` provided.
1366    ///
1367    /// # Examples
1368    ///
1369    /// ```no_run
1370    /// # use std::collections::BTreeMap;
1371    /// # use matrix_sdk::{ruma::api::client::uiaa, Client};
1372    /// # use url::Url;
1373    /// # use serde_json::json;
1374    /// # async {
1375    /// # let homeserver = Url::parse("http://example.com")?;
1376    /// # let client = Client::new(homeserver).await?;
1377    /// if let Err(e) = client.encryption().bootstrap_cross_signing(None).await {
1378    ///     if let Some(response) = e.as_uiaa_response() {
1379    ///         let mut password = uiaa::Password::new(
1380    ///             uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier::new("example".to_owned())),
1381    ///             "wordpass".to_owned(),
1382    ///         );
1383    ///         password.session = response.session.clone();
1384    ///
1385    ///         client
1386    ///             .encryption()
1387    ///             .bootstrap_cross_signing(Some(uiaa::AuthData::Password(password)))
1388    ///             .await
1389    ///             .expect("Couldn't bootstrap cross signing")
1390    ///     } else {
1391    ///         panic!("Error during cross signing bootstrap {:#?}", e);
1392    ///     }
1393    /// }
1394    /// # anyhow::Ok(()) };
1395    pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1396        let olm = self.client.olm_machine().await;
1397        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1398
1399        let CrossSigningBootstrapRequests {
1400            upload_signing_keys_req,
1401            upload_keys_req,
1402            upload_signatures_req,
1403        } = olm.bootstrap_cross_signing(false).await?;
1404
1405        let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1406            auth: auth_data,
1407            master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1408            self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1409            user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1410        });
1411
1412        if let Some(req) = upload_keys_req {
1413            self.client.send_outgoing_request(req).await?;
1414        }
1415        self.client.send(upload_signing_keys_req).await?;
1416        self.client.send(upload_signatures_req).await?;
1417
1418        Ok(())
1419    }
1420
1421    /// Reset the cross-signing keys.
1422    ///
1423    /// # Example
1424    ///
1425    /// ```no_run
1426    /// use matrix_sdk::{ruma::api::client::uiaa, encryption::CrossSigningResetAuthType};
1427    ///
1428    /// # async {
1429    /// # let homeserver = url::Url::parse("http://example.com")?;
1430    /// # let client = matrix_sdk::Client::new(homeserver).await?;
1431    /// # let user_id = unimplemented!();
1432    /// let encryption = client.encryption();
1433    ///
1434    /// if let Some(handle) = encryption.reset_cross_signing().await? {
1435    ///     match handle.auth_type() {
1436    ///         CrossSigningResetAuthType::Uiaa(uiaa) => {
1437    ///             use matrix_sdk::ruma::api::client::uiaa;
1438    ///
1439    ///             let password = "1234".to_owned();
1440    ///             let mut password = uiaa::Password::new(user_id, password);
1441    ///             password.session = uiaa.session;
1442    ///
1443    ///             handle.auth(Some(uiaa::AuthData::Password(password))).await?;
1444    ///         }
1445    ///         CrossSigningResetAuthType::OAuth(o) => {
1446    ///             println!(
1447    ///                 "To reset your end-to-end encryption cross-signing identity, \
1448    ///                 you first need to approve it at {}",
1449    ///                 o.approval_url
1450    ///             );
1451    ///
1452    ///             let mut oauth = uiaa::OAuth::new();
1453    ///             oauth.session = o.session;
1454    ///
1455    ///             handle.auth(Some(uiaa::AuthData::OAuth(oauth))).await?;
1456    ///         }
1457    ///     }
1458    /// }
1459    /// # anyhow::Ok(()) };
1460    /// ```
1461    pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1462        let olm = self.client.olm_machine().await;
1463        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1464
1465        let CrossSigningBootstrapRequests {
1466            upload_keys_req,
1467            upload_signing_keys_req,
1468            upload_signatures_req,
1469        } = olm.bootstrap_cross_signing(true).await?;
1470
1471        let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1472            auth: None,
1473            master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1474            self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1475            user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1476        });
1477
1478        if let Some(req) = upload_keys_req {
1479            self.client.send_outgoing_request(req).await?;
1480        }
1481
1482        if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1483            if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1484                let client = self.client.clone();
1485
1486                Ok(Some(CrossSigningResetHandle::new(
1487                    client,
1488                    upload_signing_keys_req,
1489                    upload_signatures_req,
1490                    auth_type,
1491                )))
1492            } else {
1493                Err(error.into())
1494            }
1495        } else {
1496            self.client.send(upload_signatures_req).await?;
1497
1498            Ok(None)
1499        }
1500    }
1501
1502    /// Query the user's own device keys, if, and only if, we didn't have their
1503    /// identity in the first place.
1504    async fn ensure_initial_key_query(&self) -> Result<()> {
1505        let olm_machine = self.client.olm_machine().await;
1506        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1507
1508        let user_id = olm_machine.user_id();
1509
1510        if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1511            let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1512            self.client.keys_query(&request_id, request.device_keys).await?;
1513        }
1514
1515        Ok(())
1516    }
1517
1518    /// Create and upload a new cross signing identity, if that has not been
1519    /// done yet.
1520    ///
1521    /// This will only create a new cross-signing identity if the user had never
1522    /// done it before. If the user did it before, then this is a no-op.
1523    ///
1524    /// See also the documentation of [`Self::bootstrap_cross_signing`] for the
1525    /// behavior of this function.
1526    ///
1527    /// # Arguments
1528    ///
1529    /// * `auth_data` - This request requires user interactive auth, the first
1530    ///   request needs to set this to `None` and will always fail with an
1531    ///   `UiaaResponse`. The response will contain information for the
1532    ///   interactive auth and the same request needs to be made but this time
1533    ///   with some `auth_data` provided.
1534    ///
1535    /// # Examples
1536    /// ```no_run
1537    /// # use std::collections::BTreeMap;
1538    /// # use matrix_sdk::{ruma::api::client::uiaa, Client};
1539    /// # use url::Url;
1540    /// # use serde_json::json;
1541    /// # async {
1542    /// # let homeserver = Url::parse("http://example.com")?;
1543    /// # let client = Client::new(homeserver).await?;
1544    /// if let Err(e) = client.encryption().bootstrap_cross_signing_if_needed(None).await {
1545    ///     if let Some(response) = e.as_uiaa_response() {
1546    ///         let mut password = uiaa::Password::new(
1547    ///             uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier::new("example".to_owned())),
1548    ///             "wordpass".to_owned(),
1549    ///         );
1550    ///         password.session = response.session.clone();
1551    ///
1552    ///         // Note, on the failed attempt we can use `bootstrap_cross_signing` immediately, to
1553    ///         // avoid checks.
1554    ///         client
1555    ///             .encryption()
1556    ///             .bootstrap_cross_signing(Some(uiaa::AuthData::Password(password)))
1557    ///             .await
1558    ///             .expect("Couldn't bootstrap cross signing")
1559    ///     } else {
1560    ///         panic!("Error during cross signing bootstrap {:#?}", e);
1561    ///     }
1562    /// }
1563    /// # anyhow::Ok(()) };
1564    pub async fn bootstrap_cross_signing_if_needed(
1565        &self,
1566        auth_data: Option<AuthData>,
1567    ) -> Result<()> {
1568        let olm_machine = self.client.olm_machine().await;
1569        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1570        let user_id = olm_machine.user_id();
1571
1572        self.ensure_initial_key_query().await?;
1573
1574        if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1575            self.bootstrap_cross_signing(auth_data).await?;
1576        }
1577
1578        Ok(())
1579    }
1580
1581    /// Export E2EE keys that match the given predicate encrypting them with the
1582    /// given passphrase.
1583    ///
1584    /// # Arguments
1585    ///
1586    /// * `path` - The file path where the exported key file will be saved.
1587    ///
1588    /// * `passphrase` - The passphrase that will be used to encrypt the
1589    ///   exported room keys.
1590    ///
1591    /// * `predicate` - A closure that will be called for every known
1592    ///   `InboundGroupSession`, which represents a room key. If the closure
1593    ///   returns `true` the `InboundGroupSessoin` will be included in the
1594    ///   export, if the closure returns `false` it will not be included.
1595    ///
1596    /// # Panics
1597    ///
1598    /// This method will panic if it isn't run on a Tokio runtime.
1599    ///
1600    /// This method will panic if it can't get enough randomness from the OS to
1601    /// encrypt the exported keys securely.
1602    ///
1603    /// # Examples
1604    ///
1605    /// ```no_run
1606    /// # use std::{path::PathBuf, time::Duration};
1607    /// # use matrix_sdk::{
1608    /// #     Client, config::SyncSettings,
1609    /// #     ruma::room_id,
1610    /// # };
1611    /// # use url::Url;
1612    /// # async {
1613    /// # let homeserver = Url::parse("http://localhost:8080")?;
1614    /// # let mut client = Client::new(homeserver).await?;
1615    /// let path = PathBuf::from("/home/example/e2e-keys.txt");
1616    /// // Export all room keys.
1617    /// client
1618    ///     .encryption()
1619    ///     .export_room_keys(path, "secret-passphrase", |_| true)
1620    ///     .await?;
1621    ///
1622    /// // Export only the room keys for a certain room.
1623    /// let path = PathBuf::from("/home/example/e2e-room-keys.txt");
1624    /// let room_id = room_id!("!test:localhost");
1625    ///
1626    /// client
1627    ///     .encryption()
1628    ///     .export_room_keys(path, "secret-passphrase", |s| s.room_id() == room_id)
1629    ///     .await?;
1630    /// # anyhow::Ok(()) };
1631    /// ```
1632    #[cfg(not(target_family = "wasm"))]
1633    pub async fn export_room_keys(
1634        &self,
1635        path: PathBuf,
1636        passphrase: &str,
1637        predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1638    ) -> Result<()> {
1639        let olm = self.client.olm_machine().await;
1640        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1641
1642        let keys = olm.store().export_room_keys(predicate).await?;
1643        let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1644
1645        let encrypt = move || -> Result<()> {
1646            let export: String =
1647                matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1648            let mut file = std::fs::File::create(path)?;
1649            file.write_all(&export.into_bytes())?;
1650            Ok(())
1651        };
1652
1653        let task = tokio::task::spawn_blocking(encrypt);
1654        task.await.expect("Task join error")
1655    }
1656
1657    /// Import E2EE keys from the given file path.
1658    ///
1659    /// # Arguments
1660    ///
1661    /// * `path` - The file path where the exported key file will can be found.
1662    ///
1663    /// * `passphrase` - The passphrase that should be used to decrypt the
1664    ///   exported room keys.
1665    ///
1666    /// Returns a tuple of numbers that represent the number of sessions that
1667    /// were imported and the total number of sessions that were found in the
1668    /// key export.
1669    ///
1670    /// # Panics
1671    ///
1672    /// This method will panic if it isn't run on a Tokio runtime.
1673    ///
1674    /// ```no_run
1675    /// # use std::{path::PathBuf, time::Duration};
1676    /// # use matrix_sdk::{
1677    /// #     Client, config::SyncSettings,
1678    /// #     ruma::room_id,
1679    /// # };
1680    /// # use url::Url;
1681    /// # async {
1682    /// # let homeserver = Url::parse("http://localhost:8080")?;
1683    /// # let mut client = Client::new(homeserver).await?;
1684    /// let path = PathBuf::from("/home/example/e2e-keys.txt");
1685    /// let result =
1686    ///     client.encryption().import_room_keys(path, "secret-passphrase").await?;
1687    ///
1688    /// println!(
1689    ///     "Imported {} room keys out of {}",
1690    ///     result.imported_count, result.total_count
1691    /// );
1692    /// # anyhow::Ok(()) };
1693    /// ```
1694    #[cfg(not(target_family = "wasm"))]
1695    pub async fn import_room_keys(
1696        &self,
1697        path: PathBuf,
1698        passphrase: &str,
1699    ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1700        let olm = self.client.olm_machine().await;
1701        let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1702        let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1703
1704        let decrypt = move || {
1705            let file = std::fs::File::open(path)?;
1706            matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1707        };
1708
1709        let task = tokio::task::spawn_blocking(decrypt);
1710        let import = task.await.expect("Task join error")?;
1711
1712        let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1713
1714        self.backups().maybe_trigger_backup();
1715
1716        Ok(ret)
1717    }
1718
1719    /// Receive notifications of room keys being received as a [`Stream`].
1720    ///
1721    /// Each time a room key is updated in any way, an update will be sent to
1722    /// the stream. Updates that happen at the same time are batched into a
1723    /// [`Vec`].
1724    ///
1725    /// If the reader of the stream lags too far behind, an error is broadcast
1726    /// containing the number of skipped items.
1727    ///
1728    /// # Examples
1729    ///
1730    /// ```no_run
1731    /// # use matrix_sdk::Client;
1732    /// # use url::Url;
1733    /// # async {
1734    /// # let homeserver = Url::parse("http://example.com")?;
1735    /// # let client = Client::new(homeserver).await?;
1736    /// use futures_util::StreamExt;
1737    ///
1738    /// let Some(mut room_keys_stream) =
1739    ///     client.encryption().room_keys_received_stream().await
1740    /// else {
1741    ///     return Ok(());
1742    /// };
1743    ///
1744    /// while let Some(update) = room_keys_stream.next().await {
1745    ///     println!("Received room keys {update:?}");
1746    /// }
1747    /// # anyhow::Ok(()) };
1748    /// ```
1749    pub async fn room_keys_received_stream(
1750        &self,
1751    ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1752    {
1753        let olm = self.client.olm_machine().await;
1754        let olm = olm.as_ref()?;
1755
1756        Some(olm.store().room_keys_received_stream())
1757    }
1758
1759    /// Receive notifications of historic room key bundles as a [`Stream`].
1760    ///
1761    /// Historic room key bundles are defined in [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
1762    ///
1763    /// Each time a historic room key bundle was received, an update will be
1764    /// sent to the stream. This stream is useful for informative purposes
1765    /// exclusively, historic room key bundles are handled by the SDK
1766    /// automatically.
1767    ///
1768    /// # Examples
1769    ///
1770    /// ```no_run
1771    /// # use matrix_sdk::Client;
1772    /// # use url::Url;
1773    /// # async {
1774    /// # let homeserver = Url::parse("http://example.com")?;
1775    /// # let client = Client::new(homeserver).await?;
1776    /// use futures_util::StreamExt;
1777    ///
1778    /// let Some(mut bundle_stream) =
1779    ///     client.encryption().historic_room_key_stream().await
1780    /// else {
1781    ///     return Ok(());
1782    /// };
1783    ///
1784    /// while let Some(bundle_info) = bundle_stream.next().await {
1785    ///     println!("Received a historic room key bundle {bundle_info:?}");
1786    /// }
1787    /// # anyhow::Ok(()) };
1788    /// ```
1789    pub async fn historic_room_key_stream(
1790        &self,
1791    ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1792        let olm = self.client.olm_machine().await;
1793        let olm = olm.as_ref()?;
1794
1795        Some(olm.store().historic_room_key_stream())
1796    }
1797
1798    /// Get the secret storage manager of the client.
1799    pub fn secret_storage(&self) -> SecretStorage {
1800        SecretStorage { client: self.client.to_owned() }
1801    }
1802
1803    /// Get the backups manager of the client.
1804    pub fn backups(&self) -> Backups {
1805        Backups { client: self.client.to_owned() }
1806    }
1807
1808    /// Get the recovery manager of the client.
1809    pub fn recovery(&self) -> Recovery {
1810        Recovery { client: self.client.to_owned() }
1811    }
1812
1813    /// Get the dehydrated-devices manager of the client.
1814    ///
1815    /// A dehydrated device is a virtual device that the homeserver holds on
1816    /// the user's behalf and that can receive end-to-end encrypted to-device
1817    /// events while the user is offline. See the
1818    /// [`dehydrated_devices`] module
1819    /// for the full lifecycle and an example.
1820    pub fn dehydrated_devices(&self) -> dehydrated_devices::DehydratedDevices {
1821        dehydrated_devices::DehydratedDevices { client: self.client.to_owned() }
1822    }
1823
1824    /// Enables the crypto-store cross-process lock.
1825    ///
1826    /// This may be required if there are multiple processes that may do writes
1827    /// to the same crypto store. In that case, it's necessary to create a
1828    /// lock, so that only one process writes to it, otherwise this may
1829    /// cause confusing issues because of stale data contained in in-memory
1830    /// caches.
1831    ///
1832    /// The provided `lock_value` must be a unique identifier for this process.
1833    /// Use [`Client::cross_process_lock_config`] to get the global value, if
1834    /// multi-process is enabled.
1835    pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1836        // If the lock has already been created, don't recreate it from scratch.
1837        if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1838            let prev_holder = prev_lock.lock_holder();
1839            if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
1840                return Ok(());
1841            }
1842            warn!(
1843                "Recreating cross-process store lock with a different holder value: \
1844                 prev was {prev_holder:?}, new is {lock_value}"
1845            );
1846        }
1847
1848        let olm_machine = self.client.base_client().olm_machine().await;
1849        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1850
1851        let lock = olm_machine.store().create_store_lock(
1852            "cross_process_lock".to_owned(),
1853            CrossProcessLockConfig::multi_process(lock_value.to_owned()),
1854        );
1855
1856        // Gently try to initialize the crypto store generation counter.
1857        //
1858        // If we don't get the lock immediately, then it is already acquired by another
1859        // process, and we'll get to reload next time we acquire the lock.
1860        {
1861            let lock_result = lock.try_lock_once().await?;
1862
1863            if lock_result.is_ok() {
1864                olm_machine
1865                    .initialize_crypto_store_generation(
1866                        &self.client.locks().crypto_store_generation,
1867                    )
1868                    .await?;
1869            }
1870        }
1871
1872        self.client
1873            .locks()
1874            .cross_process_crypto_store_lock
1875            .set(lock)
1876            .map_err(|_| Error::BadCryptoStoreState)?;
1877
1878        Ok(())
1879    }
1880
1881    /// Maybe reload the `OlmMachine` after acquiring the lock for the first
1882    /// time.
1883    ///
1884    /// Returns the current generation number.
1885    #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
1886    async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
1887        let olm_machine_guard = self.client.olm_machine().await;
1888        if let Some(olm_machine) = olm_machine_guard.as_ref() {
1889            let (new_gen, generation_number) = olm_machine
1890                .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
1891                .await?;
1892
1893            Span::current()
1894                .record("olm_machine_new_generation", new_gen)
1895                .record("olm_machine_generation", generation_number);
1896            debug!("OlmMachine generation maintained in CryptoStore");
1897
1898            // If the crypto store generation has changed,
1899            if new_gen {
1900                // (get rid of the reference to the current crypto store first)
1901                drop(olm_machine_guard);
1902                // Recreate the OlmMachine.
1903                self.client.base_client().regenerate_olm(None).await?;
1904            }
1905            Ok(generation_number)
1906        } else {
1907            // XXX: not sure this is reachable. Seems like the OlmMachine should always have
1908            // been initialised by the time we get here. Ideally we'd panic, or return an
1909            // error, but for now I'm just adding some logging to check if it
1910            // happens, and returning the magic number 0.
1911            warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
1912            Ok(0)
1913        }
1914    }
1915
1916    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
1917    /// spin-waits until the lock is available.
1918    ///
1919    /// May reload the `OlmMachine`, after obtaining the lock but not on the
1920    /// first time.
1921    ///
1922    /// Returns a guard to the lock, if it was obtained.
1923    pub async fn spin_lock_store(
1924        &self,
1925        max_backoff: Option<u32>,
1926    ) -> Result<Option<CrossProcessLockGuard>, Error> {
1927        self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
1928    }
1929
1930    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
1931    /// attempts to lock it once.
1932    ///
1933    /// May reload the `OlmMachine`, after obtaining the lock but not on the
1934    /// first time.
1935    ///
1936    /// Returns a guard to the lock, if it was obtained.
1937    pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
1938        match self.lock_store(CrossProcessLock::try_lock_once).await {
1939            Err(Error::CrossProcessLockError(e))
1940                if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
1941            {
1942                Ok(None)
1943            }
1944            other => other,
1945        }
1946    }
1947
1948    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
1949    /// locks the store with the given function, `acquire`.
1950    ///
1951    /// Reloads the `OlmMachine` after obtaining the lock, if the lock is dirty.
1952    ///
1953    /// Returns a guard to the lock if it was obtained.
1954    pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
1955        &self,
1956        acquire: F,
1957    ) -> Result<Option<CrossProcessLockGuard>, Error> {
1958        let wrap_err = |e: CryptoStoreError| {
1959            Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
1960        };
1961        if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1962            let guard = acquire(lock).await.map_err(wrap_err)??;
1963            let _ = self.on_lock_newly_acquired().await?;
1964            Ok(Some(guard.into_guard()))
1965        } else {
1966            Ok(None)
1967        }
1968    }
1969
1970    /// Testing purposes only.
1971    #[cfg(any(test, feature = "testing"))]
1972    pub async fn uploaded_key_count(&self) -> Result<u64> {
1973        let olm_machine = self.client.olm_machine().await;
1974        let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
1975        Ok(olm_machine.uploaded_key_count().await?)
1976    }
1977
1978    /// Bootstrap encryption and enables event listeners for the E2EE support.
1979    ///
1980    /// Based on the `EncryptionSettings`, this call might:
1981    /// - Bootstrap cross-signing if needed (POST `/device_signing/upload`)
1982    /// - Create a key backup if needed (POST `/room_keys/version`)
1983    /// - Create a secret storage if needed (PUT `/account_data/{type}`)
1984    ///
1985    /// As part of this process, and if needed, the current device keys would be
1986    /// uploaded to the server, new account data would be added, and cross
1987    /// signing keys and signatures might be uploaded.
1988    ///
1989    /// Should be called once we
1990    /// created a [`OlmMachine`], i.e. after logging in.
1991    ///
1992    /// # Arguments
1993    ///
1994    /// * `auth_data` - Some requests may require re-authentication. To prevent
1995    ///   the user from having to re-enter their password (or use other
1996    ///   methods), we can provide the authentication data here. This is
1997    ///   necessary for uploading cross-signing keys. However, please note that
1998    ///   there is a proposal (MSC3967) to remove this requirement, which would
1999    ///   allow for the initial upload of cross-signing keys without
2000    ///   authentication, rendering this parameter obsolete.
2001    pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
2002        // It's fine to be async here as we're only getting the lock protecting the
2003        // `OlmMachine`. Since the lock shouldn't be that contested right after logging
2004        // in we won't delay the login or restoration of the Client.
2005        let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
2006            Some(BundleReceiverTask::new(&self.client).await)
2007        } else {
2008            None
2009        };
2010
2011        let mut tasks = self.client.inner.e2ee.tasks.lock();
2012
2013        let this = self.clone();
2014
2015        tasks.setup_e2ee = Some(spawn(
2016            async move {
2017                // Update the current state first, so we don't have to wait for the result of
2018                // network requests
2019                this.update_verification_state().await;
2020
2021                if this.settings().auto_enable_cross_signing
2022                    && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2023                {
2024                    error!("Couldn't bootstrap cross signing {e:?}");
2025                }
2026
2027                if let Err(e) = this.backups().setup_and_resume().await {
2028                    error!("Couldn't setup and resume backups {e:?}");
2029                }
2030                if let Err(e) = this.recovery().setup().await {
2031                    error!("Couldn't setup and resume recovery {e:?}");
2032                }
2033            }
2034            .instrument(Span::current()),
2035        ));
2036
2037        tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2038
2039        self.setup_room_membership_session_discard_handler();
2040    }
2041
2042    /// Waits for end-to-end encryption initialization tasks to finish, if any
2043    /// was running in the background.
2044    pub async fn wait_for_e2ee_initialization_tasks(&self) {
2045        let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2046
2047        if let Some(task) = task
2048            && let Err(err) = task.await
2049        {
2050            warn!("Error when initializing backups: {err}");
2051        }
2052    }
2053
2054    /// Upload the device keys and initial set of one-time keys to the server.
2055    ///
2056    /// This should only be called when the user logs in for the first time,
2057    /// the method will ensure that other devices see our own device as an
2058    /// end-to-end encryption enabled one.
2059    ///
2060    /// **Warning**: Do not use this method if we're already calling
2061    /// [`Client::send_outgoing_request()`]. This method is intended for
2062    /// explicitly uploading the device keys before starting a sync.
2063    pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2064        let olm = self.client.olm_machine().await;
2065        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2066
2067        if let Some((request_id, request)) = olm.upload_device_keys().await? {
2068            self.client.keys_upload(&request_id, &request).await?;
2069
2070            let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2071            self.client.keys_query(&request_id, request.device_keys).await?;
2072        }
2073
2074        Ok(())
2075    }
2076
2077    pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2078        self.recovery().update_state_after_keys_query(response).await;
2079
2080        // Only update the verification_state if our own devices changed
2081        if let Some(user_id) = self.client.user_id() {
2082            let contains_own_device = response.device_keys.contains_key(user_id);
2083
2084            if contains_own_device {
2085                self.update_verification_state().await;
2086            }
2087        }
2088    }
2089
2090    async fn update_verification_state(&self) {
2091        match self.get_own_device().await {
2092            Ok(device) => {
2093                if let Some(device) = device {
2094                    let is_verified = device.is_cross_signed_by_owner();
2095
2096                    if is_verified {
2097                        self.client.inner.verification_state.set(VerificationState::Verified);
2098                    } else {
2099                        self.client.inner.verification_state.set(VerificationState::Unverified);
2100                    }
2101                } else {
2102                    warn!("Couldn't find out own device in the store.");
2103                    self.client.inner.verification_state.set(VerificationState::Unknown);
2104                }
2105            }
2106            Err(error) => {
2107                warn!("Failed retrieving own device: {error}");
2108                self.client.inner.verification_state.set(VerificationState::Unknown);
2109            }
2110        }
2111    }
2112
2113    /// Sets up a handler to rotate room keys when a user leaves a room.
2114    ///
2115    /// Previously, it was sufficient to check if we need to rotate the room key
2116    /// prior to sending a message. However, the history sharing feature
2117    /// ([MSC4268]) breaks this logic:
2118    ///
2119    /// 1. Alice sends a message M1 in room X;
2120    /// 2. Bob invites Charlie, who joins and immediately leaves the room;
2121    /// 3. Alice sends another message M2 in room X.
2122    ///
2123    /// Under the old logic, Alice would not rotate her key after Charlie
2124    /// leaves, resulting in M2 being encrypted with the same session as M1.
2125    /// This would allow Charlie to decrypt M2 if he ever gains access to
2126    /// the event.
2127    ///
2128    /// This handler listens for changes to the room membership, and discards
2129    /// the current room key if the event is a `leave` event.
2130    ///
2131    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
2132    fn setup_room_membership_session_discard_handler(&self) {
2133        let client = WeakClient::from_client(&self.client);
2134        self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2135            let Some(client) = client.get() else {
2136                // The main client has been dropped.
2137                return;
2138            };
2139            let Some(user_id) = client.user_id() else {
2140                // We aren't logged in, so this shouldn't ever happen.
2141                return;
2142            };
2143            let olm = client.olm_machine().await;
2144            let Some(olm) = olm.as_ref() else {
2145                warn!("Cannot discard session - Olm machine is not available");
2146                return;
2147            };
2148
2149            if matches!(
2150                ev.membership_change(),
2151                MembershipChange::Joined |
2152                MembershipChange::Invited |
2153                MembershipChange::KnockAccepted |
2154                MembershipChange::InvitationAccepted |
2155                MembershipChange::ProfileChanged { .. }
2156            ) || ev.sender == user_id {
2157                // We can ignore events that did not remove us, and those that we sent.
2158                return;
2159            }
2160
2161            debug!(room_id = ?room.room_id(), member_id = ?ev.sender, "Discarding session as a user left the room");
2162
2163            // Attempt to discard the current room key. This won't do anything if we don't have one,
2164            // but that's fine since we will create a new room key whenever we try to send a message.
2165            if let Err(e) = olm.discard_room_key(room.room_id()).await {
2166                warn!(
2167                    room_id = ?room.room_id(),
2168                    "Error discarding room key after member leave: {e:?}"
2169                );
2170            }
2171        });
2172    }
2173
2174    /// Encrypts then send the given content via the `/sendToDevice` end-point
2175    /// using Olm encryption.
2176    ///
2177    /// If there are a lot of recipient devices multiple `/sendToDevice`
2178    /// requests might be sent out.
2179    ///
2180    /// # Returns
2181    /// A list of failures. The list of devices that couldn't get the messages.
2182    #[cfg(feature = "experimental-send-custom-to-device")]
2183    pub async fn encrypt_and_send_raw_to_device(
2184        &self,
2185        recipient_devices: Vec<&Device>,
2186        event_type: &str,
2187        content: Raw<AnyToDeviceEventContent>,
2188        share_strategy: CollectStrategy,
2189    ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2190        let users = recipient_devices.iter().map(|device| device.user_id());
2191
2192        // Will claim one-time-key for users that needs it
2193        // TODO: For later optimisation: This will establish missing olm sessions with
2194        // all this users devices, but we just want for some devices.
2195        self.client.claim_one_time_keys(users).await?;
2196
2197        let olm = self.client.olm_machine().await;
2198        let olm = olm.as_ref().expect("Olm machine wasn't started");
2199
2200        let (requests, withhelds) = olm
2201            .encrypt_content_for_devices(
2202                recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2203                event_type,
2204                &content
2205                    .deserialize_as::<serde_json::Value>()
2206                    .expect("Deserialize as Value will always work"),
2207                share_strategy,
2208            )
2209            .await?;
2210
2211        let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2212
2213        // Push the withhelds in the failures
2214        withhelds.iter().for_each(|(d, _)| {
2215            failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2216        });
2217
2218        // TODO: parallelize that? it's already grouping 250 devices per chunk.
2219        for request in requests {
2220            let ruma_request = RumaToDeviceRequest::new_raw(
2221                request.event_type.clone(),
2222                request.txn_id.clone(),
2223                request.messages.clone(),
2224            );
2225
2226            let send_result = self
2227                .client
2228                .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2229                .await;
2230
2231            // If the sending failed we need to collect the failures to report them
2232            if send_result.is_err() {
2233                // Mark the sending as failed
2234                for (user_id, device_map) in request.messages {
2235                    for device_id in device_map.keys() {
2236                        match device_id {
2237                            DeviceIdOrAllDevices::DeviceId(device_id) => {
2238                                failures.push((user_id.clone(), device_id.to_owned()));
2239                            }
2240                            DeviceIdOrAllDevices::AllDevices => {
2241                                // Cannot happen in this case
2242                            }
2243                        }
2244                    }
2245                }
2246            }
2247        }
2248
2249        Ok(failures)
2250    }
2251}
2252
2253#[cfg(all(test, not(target_family = "wasm")))]
2254mod tests {
2255    use std::{
2256        ops::Not,
2257        str::FromStr,
2258        sync::{
2259            Arc,
2260            atomic::{AtomicBool, Ordering},
2261        },
2262        time::Duration,
2263    };
2264
2265    use matrix_sdk_test::{
2266        DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2267        event_factory::EventFactory,
2268    };
2269    use ruma::{
2270        event_id,
2271        events::{reaction::ReactionEventContent, relation::Annotation},
2272        user_id,
2273    };
2274    use serde_json::json;
2275    use wiremock::{
2276        Mock, MockServer, Request, ResponseTemplate,
2277        matchers::{header, method, path_regex},
2278    };
2279
2280    use crate::{
2281        Client, assert_next_matches_with_timeout,
2282        config::RequestConfig,
2283        encryption::{
2284            DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2285        },
2286        test_utils::{
2287            client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2288        },
2289    };
2290
2291    #[async_test]
2292    async fn test_reaction_sending() {
2293        let server = MockServer::start().await;
2294        let client = logged_in_client(Some(server.uri())).await;
2295
2296        let event_id = event_id!("$2:example.org");
2297
2298        Mock::given(method("GET"))
2299            .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2300            .and(header("authorization", "Bearer 1234"))
2301            .respond_with(
2302                ResponseTemplate::new(200)
2303                    .set_body_json(EventFactory::new().room_encryption().into_content()),
2304            )
2305            .mount(&server)
2306            .await;
2307
2308        Mock::given(method("PUT"))
2309            .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2310            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2311                "event_id": event_id,
2312            })))
2313            .mount(&server)
2314            .await;
2315
2316        let f = EventFactory::new().sender(user_id!("@example:localhost"));
2317        let response = SyncResponseBuilder::default()
2318            .add_joined_room(
2319                JoinedRoomBuilder::default()
2320                    .add_state_event(
2321                        f.member(user_id!("@example:localhost")).display_name("example"),
2322                    )
2323                    .add_state_event(f.default_power_levels())
2324                    .add_state_event(f.room_encryption()),
2325            )
2326            .build_sync_response();
2327
2328        client.base_client().receive_sync_response(response).await.unwrap();
2329
2330        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2331        assert!(
2332            room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2333        );
2334
2335        let event_id = event_id!("$1:example.org");
2336        let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2337        room.send(reaction).await.expect("Sending the reaction should not fail");
2338
2339        room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2340    }
2341
2342    #[cfg(feature = "sqlite")]
2343    #[async_test]
2344    async fn test_generation_counter_invalidates_olm_machine() {
2345        // Create two clients using the same sqlite database.
2346
2347        use matrix_sdk_base::store::RoomLoadSettings;
2348        let sqlite_path = std::env::temp_dir().join("generation_counter_sqlite.db");
2349        let session = mock_matrix_session();
2350
2351        let client1 = Client::builder()
2352            .homeserver_url("http://localhost:1234")
2353            .request_config(RequestConfig::new().disable_retry())
2354            .sqlite_store(&sqlite_path, None)
2355            .build()
2356            .await
2357            .unwrap();
2358        client1
2359            .matrix_auth()
2360            .restore_session(session.clone(), RoomLoadSettings::default())
2361            .await
2362            .unwrap();
2363
2364        let client2 = Client::builder()
2365            .homeserver_url("http://localhost:1234")
2366            .request_config(RequestConfig::new().disable_retry())
2367            .sqlite_store(sqlite_path, None)
2368            .build()
2369            .await
2370            .unwrap();
2371        client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2372
2373        // When the lock isn't enabled, any attempt at locking won't return a guard.
2374        let guard = client1.encryption().try_lock_store_once().await.unwrap();
2375        assert!(guard.is_none());
2376
2377        client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2378        client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2379
2380        // One client can take the lock.
2381        let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2382        assert!(acquired1.is_some());
2383
2384        // Keep the olm machine, so we can see if it's changed later, by comparing Arcs.
2385        let initial_olm_machine =
2386            client1.olm_machine().await.clone().expect("must have an olm machine");
2387
2388        // Also enable backup to check that new machine has the same backup keys.
2389        let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2390        let backup_key = decryption_key.megolm_v1_public_key();
2391        backup_key.set_version("1".to_owned());
2392        initial_olm_machine
2393            .backup_machine()
2394            .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2395            .await
2396            .expect("Should save");
2397
2398        initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2399
2400        assert!(client1.encryption().backups().are_enabled().await);
2401
2402        // The other client can't take the lock too.
2403        let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2404        assert!(acquired2.is_none());
2405
2406        // Now have the first client release the lock,
2407        drop(acquired1);
2408        tokio::time::sleep(Duration::from_millis(100)).await;
2409
2410        // And re-take it.
2411        let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2412        assert!(acquired1.is_some());
2413
2414        // In that case, the Olm Machine shouldn't change.
2415        let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2416        assert!(initial_olm_machine.same_as(&olm_machine));
2417
2418        // Ok, release again.
2419        drop(acquired1);
2420        tokio::time::sleep(Duration::from_millis(100)).await;
2421
2422        // Client2 can acquire the lock.
2423        let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2424        assert!(acquired2.is_some());
2425
2426        // And then release it.
2427        drop(acquired2);
2428        tokio::time::sleep(Duration::from_millis(100)).await;
2429
2430        // Client1 can acquire it again,
2431        let acquired1 = client1.encryption().try_lock_store_once().await.unwrap();
2432        assert!(acquired1.is_some());
2433
2434        // But now its olm machine has been invalidated and thus regenerated!
2435        let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2436
2437        assert!(!initial_olm_machine.same_as(&olm_machine));
2438
2439        let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2440        assert!(backup_key_new.decryption_key.is_some());
2441        assert_eq!(
2442            backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2443            backup_key.to_base64()
2444        );
2445        assert!(client1.encryption().backups().are_enabled().await);
2446    }
2447
2448    #[cfg(feature = "sqlite")]
2449    #[async_test]
2450    async fn test_generation_counter_no_spurious_invalidation() {
2451        // Create two clients using the same sqlite database.
2452
2453        use matrix_sdk_base::store::RoomLoadSettings;
2454        let sqlite_path =
2455            std::env::temp_dir().join("generation_counter_no_spurious_invalidations.db");
2456        let session = mock_matrix_session();
2457
2458        let client = Client::builder()
2459            .homeserver_url("http://localhost:1234")
2460            .request_config(RequestConfig::new().disable_retry())
2461            .sqlite_store(&sqlite_path, None)
2462            .build()
2463            .await
2464            .unwrap();
2465        client
2466            .matrix_auth()
2467            .restore_session(session.clone(), RoomLoadSettings::default())
2468            .await
2469            .unwrap();
2470
2471        let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2472
2473        client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2474
2475        // Enabling the lock doesn't update the olm machine.
2476        let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2477        assert!(initial_olm_machine.same_as(&after_enabling_lock));
2478
2479        {
2480            // Simulate that another client hold the lock before.
2481            let client2 = Client::builder()
2482                .homeserver_url("http://localhost:1234")
2483                .request_config(RequestConfig::new().disable_retry())
2484                .sqlite_store(sqlite_path, None)
2485                .build()
2486                .await
2487                .unwrap();
2488            client2
2489                .matrix_auth()
2490                .restore_session(session, RoomLoadSettings::default())
2491                .await
2492                .unwrap();
2493
2494            client2
2495                .encryption()
2496                .enable_cross_process_store_lock("client2".to_owned())
2497                .await
2498                .unwrap();
2499
2500            let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2501            assert!(guard.is_some());
2502
2503            drop(guard);
2504            tokio::time::sleep(Duration::from_millis(100)).await;
2505        }
2506
2507        {
2508            let acquired = client.encryption().try_lock_store_once().await.unwrap();
2509            assert!(acquired.is_some());
2510        }
2511
2512        // Taking the lock the first time will update the olm machine.
2513        let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2514        assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2515
2516        {
2517            let acquired = client.encryption().try_lock_store_once().await.unwrap();
2518            assert!(acquired.is_some());
2519        }
2520
2521        // Re-taking the lock doesn't update the olm machine.
2522        let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2523        assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2524    }
2525
2526    #[async_test]
2527    async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2528        // Given a client and a server
2529        let client = no_retry_test_client(None).await;
2530        let server = MockServer::start().await;
2531
2532        // When we subscribe to its verification state
2533        let mut verification_state = client.encryption().verification_state();
2534
2535        // We can get its initial value, and it's Unknown
2536        assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2537
2538        // We set up a mocked request to check this endpoint is not called before
2539        // reading the new state
2540        let keys_requested = Arc::new(AtomicBool::new(false));
2541        let inner_bool = keys_requested.clone();
2542
2543        Mock::given(method("GET"))
2544            .and(path_regex(
2545                r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2546            ))
2547            .respond_with(move |_req: &Request| {
2548                inner_bool.fetch_or(true, Ordering::SeqCst);
2549                ResponseTemplate::new(200).set_body_json(json!({}))
2550            })
2551            .mount(&server)
2552            .await;
2553
2554        // When the session is initialised and the encryption tasks spawn
2555        set_client_session(&client).await;
2556
2557        // Then we can get an updated value without waiting for any network requests
2558        assert!(keys_requested.load(Ordering::SeqCst).not());
2559        assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2560    }
2561
2562    #[test]
2563    fn test_oauth_reset_info_from_uiaa_info() {
2564        let auth_info = json!({
2565            "session": "dummy",
2566            "flows": [
2567                {
2568                    "stages": [
2569                        "org.matrix.cross_signing_reset"
2570                    ]
2571                }
2572            ],
2573            "params": {
2574                "org.matrix.cross_signing_reset": {
2575                    "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2576                }
2577            },
2578            "msg": "To reset..."
2579        });
2580
2581        let auth_info = serde_json::from_value(auth_info)
2582            .expect("We should be able to deserialize the UiaaInfo");
2583        OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2584            .expect("We should be able to fetch the cross-signing reset info from the auth info");
2585    }
2586
2587    #[test]
2588    fn test_duplicate_one_time_key_error_parsing() {
2589        let message = concat!(
2590            r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2591            r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2592            r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2593            r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2594            r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2595            r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2596            r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2597        );
2598        let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2599            .expect("We should be able to parse the error message");
2600
2601        assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2602        assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2603
2604        DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2605            .expect_err("We shouldn't be able to parse an incomplete error message");
2606    }
2607
2608    // Helper function for the test_devices_to_verify_against_* tests.  Make a
2609    // response to a /keys/query request using the given device keys and a
2610    // pre-defined set of cross-signing keys.
2611    fn devices_to_verify_against_keys_query_response(
2612        devices: Vec<serde_json::Value>,
2613    ) -> serde_json::Value {
2614        let device_keys: serde_json::Map<String, serde_json::Value> = devices
2615            .into_iter()
2616            .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2617            .collect();
2618        json!({
2619            "device_keys": {
2620                "@example:localhost": device_keys,
2621            },
2622            "master_keys": {
2623                "@example:localhost": {
2624                    "keys": {
2625                        "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2626                    },
2627                    "usage": ["master"],
2628                    "user_id": "@example:localhost",
2629                },
2630            },
2631            "self_signing_keys": {
2632                "@example:localhost": {
2633                    "keys": {
2634                        "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2635                    },
2636                    "usage": ["self_signing"],
2637                    "user_id": "@example:localhost",
2638                    "signatures": {
2639                        "@example:localhost": {
2640                            "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2641                        },
2642                    },
2643                },
2644            },
2645            "user_signing_keys": {
2646                "@example:localhost": {
2647                    "keys": {
2648                        "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2649                    },
2650                    "usage": ["user_signing"],
2651                    "user_id": "@example:localhost",
2652                    "signatures": {
2653                        "@example:localhost": {
2654                            "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2655                        },
2656                    },
2657                },
2658            }
2659        })
2660    }
2661
2662    // The following three tests test that we can detect whether the user has
2663    // other devices that they can verify against under different conditions.
2664    #[async_test]
2665    /// Test that we detect that can't verify against another device if we have
2666    /// no devices.
2667    async fn test_devices_to_verify_against_no_devices() {
2668        let server = MockServer::start().await;
2669        let client = logged_in_client(Some(server.uri())).await;
2670
2671        Mock::given(method("POST"))
2672            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2673            .respond_with(
2674                ResponseTemplate::new(200)
2675                    .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2676            )
2677            .mount(&server)
2678            .await;
2679
2680        assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2681    }
2682
2683    #[async_test]
2684    /// Test that we detect that we can verify against another cross-signed
2685    /// regular device.
2686    async fn test_devices_to_verify_against_cross_signed() {
2687        let server = MockServer::start().await;
2688        let client = logged_in_client(Some(server.uri())).await;
2689
2690        Mock::given(method("POST"))
2691            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2692            .respond_with(ResponseTemplate::new(200).set_body_json(
2693                devices_to_verify_against_keys_query_response(vec![
2694                    json!({
2695                        "algorithms": [
2696                            "m.olm.v1.curve25519-aes-sha2",
2697                            "m.megolm.v1.aes-sha2",
2698                        ],
2699                        "user_id": "@example:localhost",
2700                        "device_id": "SIGNEDDEVICE",
2701                        "keys": {
2702                            "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2703                            "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2704                        },
2705                        "signatures": {
2706                            "@example:localhost": {
2707                                "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2708                                "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2709                            },
2710                        },
2711                    })
2712                ])
2713            ))
2714            .mount(&server)
2715            .await;
2716
2717        assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2718    }
2719
2720    #[async_test]
2721    /// Test that we detect that we can't verify against a dehydrated or
2722    /// unsigned device.
2723    async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2724        let server = MockServer::start().await;
2725        let client = logged_in_client(Some(server.uri())).await;
2726        let user_id = client.user_id().unwrap();
2727        let olm_machine = client.olm_machine().await;
2728        let olm_machine = olm_machine.as_ref().unwrap();
2729
2730        Mock::given(method("POST"))
2731            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2732            .respond_with(ResponseTemplate::new(200).set_body_json(
2733                devices_to_verify_against_keys_query_response(vec![
2734                    json!({
2735                        "algorithms": [
2736                            "m.olm.v1.curve25519-aes-sha2",
2737                            "m.megolm.v1.aes-sha2",
2738                        ],
2739                        "user_id": "@example:localhost",
2740                        "device_id": "DEHYDRATEDDEVICE",
2741                        "keys": {
2742                            "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2743                            "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2744                        },
2745                        "dehydrated": true,
2746                        "signatures": {
2747                            "@example:localhost": {
2748                                "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2749                                "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2750                            },
2751                        },
2752                    }),
2753                    json!({
2754                        "algorithms": [
2755                            "m.olm.v1.curve25519-aes-sha2",
2756                            "m.megolm.v1.aes-sha2",
2757                        ],
2758                        "user_id": "@example:localhost",
2759                        "device_id": "UNSIGNEDDEVICE",
2760                        "keys": {
2761                            "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2762                            "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2763                        },
2764                        "signatures": {
2765                            "@example:localhost": {
2766                                "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2767                            },
2768                        },
2769                    }),
2770                ])
2771            ))
2772            .mount(&server)
2773            .await;
2774
2775        let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2776        client.keys_query(&request_id, request.device_keys).await.unwrap();
2777
2778        assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2779    }
2780}