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