Skip to main content

matrix_sdk_crypto/store/
mod.rs

1// Copyright 2020, 2026 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types and traits to implement the storage layer for the [`OlmMachine`]
16//!
17//! The storage layer for the [`OlmMachine`] can be customized using a trait.
18//! Implementing your own [`CryptoStore`]
19//!
20//! An in-memory only store is provided as well as an SQLite-based one,
21//! depending on your needs and targets a custom store may be implemented, e.g.
22//! for `wasm-unknown-unknown` an indexeddb store would be needed
23//!
24//! ```
25//! # use std::sync::Arc;
26//! # use matrix_sdk_crypto::{
27//! #     OlmMachine,
28//! #     OlmMachineBuilder,
29//! #     store::MemoryStore,
30//! # };
31//! # use ruma::{device_id, user_id};
32//! # let user_id = user_id!("@example:localhost");
33//! # let device_id = device_id!("TEST");
34//! let store = Arc::new(MemoryStore::new());
35//!
36//! let machine = OlmMachineBuilder::new(user_id, device_id)
37//!     .with_crypto_store(store)
38//!     .build();
39//! ```
40//!
41//! [`OlmMachine`]: /matrix_sdk_crypto/struct.OlmMachine.html
42//! [`CryptoStore`]: trait.Cryptostore.html
43
44use std::{
45    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
46    fmt::Debug,
47    ops::Deref,
48    pin::pin,
49    sync::{Arc, atomic::Ordering},
50    time::Duration,
51};
52
53use as_variant::as_variant;
54use futures_core::Stream;
55use futures_util::StreamExt;
56use itertools::{Either, Itertools};
57use ruma::{
58    DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, RoomId, UserId,
59    encryption::KeyUsage, events::secret::request::SecretName,
60};
61use serde::{Serialize, de::DeserializeOwned};
62use thiserror::Error;
63use tokio::sync::{Mutex, Notify, OwnedRwLockWriteGuard, RwLock};
64use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
65use tracing::{info, instrument, trace, warn};
66use types::{RoomKeyBundleInfo, StoredRoomKeyBundleData};
67use vodozemac::{Curve25519PublicKey, megolm::SessionOrdering};
68
69use self::types::{
70    Changes, CrossSigningKeyExport, DeviceChanges, DeviceUpdates, IdentityChanges, IdentityUpdates,
71    PendingChanges, RoomKeyInfo, RoomKeyWithheldInfo, RoomPendingKeyBundleDetails,
72    UserKeyQueryResult,
73};
74use crate::{
75    CrossSigningStatus, OwnUserIdentityData, RoomKeyImportResult,
76    gossiping::GossippedSecret,
77    identities::{Device, DeviceData, UserDevices, UserIdentityData, user::UserIdentity},
78    olm::{
79        Account, ExportedRoomKey, ForwarderData, InboundGroupSession, PrivateCrossSigningIdentity,
80        SenderData, Session, StaticAccountData,
81    },
82    store::types::{RoomKeyWithheldEntry, SecretsInboxItem},
83    types::{
84        BackupSecrets, CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport,
85        SecretsBundle,
86    },
87    verification::VerificationMachine,
88};
89#[cfg(doc)]
90use crate::{backups::BackupMachine, identities::OwnUserIdentity};
91
92pub mod caches;
93mod crypto_store_wrapper;
94mod error;
95mod memorystore;
96mod traits;
97pub mod types;
98
99#[cfg(any(test, feature = "testing"))]
100#[macro_use]
101#[allow(missing_docs)]
102pub mod integration_tests;
103
104pub(crate) use crypto_store_wrapper::CryptoStoreWrapper;
105pub use error::{CryptoStoreError, Result};
106use matrix_sdk_common::{
107    cross_process_lock::{CrossProcessLock, CrossProcessLockConfig, CrossProcessLockGeneration},
108    deserialized_responses::WithheldCode,
109    timeout::timeout,
110};
111pub use memorystore::MemoryStore;
112pub use traits::{CryptoStore, DynCryptoStore, IntoCryptoStore};
113
114use self::caches::{SequenceNumber, StoreCache, StoreCacheGuard, UsersForKeyQuery};
115use crate::types::{
116    events::room_key_withheld::RoomKeyWithheldContent, room_history::RoomKeyBundle,
117};
118#[cfg(feature = "experimental-x509-identity-verification")]
119use crate::x509::{X509Signer, X509Verifier};
120pub use crate::{
121    dehydrated_devices::DehydrationError,
122    gossiping::{GossipRequest, SecretInfo},
123};
124
125/// A wrapper for our CryptoStore trait object.
126///
127/// This is needed because we want to have a generic interface so we can
128/// store/restore objects that we can serialize. Since trait objects and
129/// generics don't mix let the CryptoStore store strings and this wrapper
130/// adds the generic interface on top.
131#[derive(Debug, Clone)]
132pub struct Store {
133    inner: Arc<StoreInner>,
134}
135
136#[derive(Debug, Default)]
137pub(crate) struct KeyQueryManager {
138    /// Record of the users that are waiting for a /keys/query.
139    users_for_key_query: Mutex<UsersForKeyQuery>,
140
141    /// Notifier that is triggered each time an update is received for a user.
142    users_for_key_query_notify: Notify,
143}
144
145impl KeyQueryManager {
146    pub async fn synced<'a>(&'a self, cache: &'a StoreCache) -> Result<SyncedKeyQueryManager<'a>> {
147        self.ensure_sync_tracked_users(cache).await?;
148        Ok(SyncedKeyQueryManager { cache, manager: self })
149    }
150
151    /// Load the list of users for whom we are tracking their device lists and
152    /// fill out our caches.
153    ///
154    /// This method ensures that we're only going to load the users from the
155    /// actual [`CryptoStore`] once, it will also make sure that any
156    /// concurrent calls to this method get deduplicated.
157    async fn ensure_sync_tracked_users(&self, cache: &StoreCache) -> Result<()> {
158        // Check if the users are loaded, and in that case do nothing.
159        let loaded = cache.loaded_tracked_users.read().await;
160        if *loaded {
161            return Ok(());
162        }
163
164        // Otherwise, we may load the users.
165        drop(loaded);
166        let mut loaded = cache.loaded_tracked_users.write().await;
167
168        // Check again if the users have been loaded, in case another call to this
169        // method loaded the tracked users between the time we tried to
170        // acquire the lock and the time we actually acquired the lock.
171        if *loaded {
172            return Ok(());
173        }
174
175        let tracked_users = cache.store.load_tracked_users().await?;
176
177        let mut query_users_lock = self.users_for_key_query.lock().await;
178        let mut tracked_users_cache = cache.tracked_users.write();
179        for user in tracked_users {
180            tracked_users_cache.insert(user.user_id.to_owned());
181
182            if user.dirty {
183                query_users_lock.insert_user(&user.user_id);
184            }
185        }
186
187        *loaded = true;
188
189        Ok(())
190    }
191
192    /// Wait for a `/keys/query` response to be received if one is expected for
193    /// the given user.
194    ///
195    /// If the given timeout elapses, the method will stop waiting and return
196    /// [`UserKeyQueryResult::TimeoutExpired`].
197    ///
198    /// Requires a [`StoreCacheGuard`] to make sure the users for which a key
199    /// query is pending are up to date, but doesn't hold on to it
200    /// thereafter: the lock is short-lived in this case.
201    pub async fn wait_if_user_key_query_pending(
202        &self,
203        cache: StoreCacheGuard,
204        timeout_duration: Duration,
205        user: &UserId,
206    ) -> Result<UserKeyQueryResult> {
207        {
208            // Drop the cache early, so we don't keep it while waiting (since writing the
209            // results requires to write in the cache, thus take another lock).
210            self.ensure_sync_tracked_users(&cache).await?;
211            drop(cache);
212        }
213
214        let mut users_for_key_query = self.users_for_key_query.lock().await;
215        let Some(waiter) = users_for_key_query.maybe_register_waiting_task(user) else {
216            return Ok(UserKeyQueryResult::WasNotPending);
217        };
218
219        let wait_for_completion = async {
220            while !waiter.completed.load(Ordering::Relaxed) {
221                // Register for being notified before releasing the mutex, so
222                // it's impossible to miss a wakeup between the last check for
223                // whether we should wait, and starting to wait.
224                let mut notified = pin!(self.users_for_key_query_notify.notified());
225                notified.as_mut().enable();
226                drop(users_for_key_query);
227
228                // Wait for a notification
229                notified.await;
230
231                // Reclaim the lock before checking the flag to avoid races
232                // when two notifications happen right after each other and the
233                // second one sets the flag we want to wait for.
234                users_for_key_query = self.users_for_key_query.lock().await;
235            }
236        };
237
238        match timeout(Box::pin(wait_for_completion), timeout_duration).await {
239            Err(_) => {
240                warn!(
241                    user_id = ?user,
242                    "The user has a pending `/keys/query` request which did \
243                    not finish yet, some devices might be missing."
244                );
245
246                Ok(UserKeyQueryResult::TimeoutExpired)
247            }
248            _ => Ok(UserKeyQueryResult::WasPending),
249        }
250    }
251}
252
253pub(crate) struct SyncedKeyQueryManager<'a> {
254    cache: &'a StoreCache,
255    manager: &'a KeyQueryManager,
256}
257
258impl SyncedKeyQueryManager<'_> {
259    /// Add entries to the list of users being tracked for device changes
260    ///
261    /// Any users not already on the list are flagged as awaiting a key query.
262    /// Users that were already in the list are unaffected.
263    pub async fn update_tracked_users(&self, users: impl Iterator<Item = &UserId>) -> Result<()> {
264        let mut store_updates = Vec::new();
265        let mut key_query_lock = self.manager.users_for_key_query.lock().await;
266
267        {
268            let mut tracked_users = self.cache.tracked_users.write();
269            for user_id in users {
270                if tracked_users.insert(user_id.to_owned()) {
271                    key_query_lock.insert_user(user_id);
272                    store_updates.push((user_id, true))
273                }
274            }
275        }
276
277        self.cache.store.save_tracked_users(&store_updates).await
278    }
279
280    /// Process notifications that users have changed devices.
281    ///
282    /// This is used to handle the list of device-list updates that is received
283    /// from the `/sync` response. Any users *whose device lists we are
284    /// tracking* are flagged as needing a key query. Users whose devices we
285    /// are not tracking are ignored.
286    pub async fn mark_tracked_users_as_changed(
287        &self,
288        users: impl Iterator<Item = &UserId>,
289    ) -> Result<()> {
290        let mut store_updates: Vec<(&UserId, bool)> = Vec::new();
291        let mut key_query_lock = self.manager.users_for_key_query.lock().await;
292
293        {
294            let tracked_users = &self.cache.tracked_users.read();
295            for user_id in users {
296                if tracked_users.contains(user_id) {
297                    key_query_lock.insert_user(user_id);
298                    store_updates.push((user_id, true));
299                }
300            }
301        }
302
303        self.cache.store.save_tracked_users(&store_updates).await
304    }
305
306    /// Flag that the given users devices are now up-to-date.
307    ///
308    /// This is called after processing the response to a /keys/query request.
309    /// Any users whose device lists we are tracking are removed from the
310    /// list of those pending a /keys/query.
311    pub async fn mark_tracked_users_as_up_to_date(
312        &self,
313        users: impl Iterator<Item = &UserId>,
314        sequence_number: SequenceNumber,
315    ) -> Result<()> {
316        let mut store_updates: Vec<(&UserId, bool)> = Vec::new();
317        let mut key_query_lock = self.manager.users_for_key_query.lock().await;
318
319        {
320            let tracked_users = self.cache.tracked_users.read();
321            for user_id in users {
322                if tracked_users.contains(user_id) {
323                    let clean = key_query_lock.maybe_remove_user(user_id, sequence_number);
324                    store_updates.push((user_id, !clean));
325                }
326            }
327        }
328
329        self.cache.store.save_tracked_users(&store_updates).await?;
330        // wake up any tasks that may have been waiting for updates
331        self.manager.users_for_key_query_notify.notify_waiters();
332
333        Ok(())
334    }
335
336    /// Get the set of users that has the outdate/dirty flag set for their list
337    /// of devices.
338    ///
339    /// This set should be included in a `/keys/query` request which will update
340    /// the device list.
341    ///
342    /// # Returns
343    ///
344    /// A pair `(users, sequence_number)`, where `users` is the list of users to
345    /// be queried, and `sequence_number` is the current sequence number,
346    /// which should be returned in `mark_tracked_users_as_up_to_date`.
347    pub async fn users_for_key_query(&self) -> (HashSet<OwnedUserId>, SequenceNumber) {
348        self.manager.users_for_key_query.lock().await.users_for_key_query()
349    }
350
351    /// See the docs for [`crate::OlmMachine::tracked_users()`].
352    pub fn tracked_users(&self) -> HashSet<OwnedUserId> {
353        self.cache.tracked_users.read().iter().cloned().collect()
354    }
355
356    /// Mark the given user as being tracked for device lists, and mark that it
357    /// has an outdated device list.
358    ///
359    /// This means that the user will be considered for a `/keys/query` request
360    /// next time [`Store::users_for_key_query()`] is called.
361    pub async fn mark_user_as_changed(&self, user: &UserId) -> Result<()> {
362        self.manager.users_for_key_query.lock().await.insert_user(user);
363        self.cache.tracked_users.write().insert(user.to_owned());
364
365        self.cache.store.save_tracked_users(&[(user, true)]).await
366    }
367}
368
369/// Convert the devices and vectors contained in the [`DeviceChanges`] into
370/// a [`DeviceUpdates`] struct.
371///
372/// The [`DeviceChanges`] will contain vectors of [`DeviceData`]s which
373/// we want to convert to a [`Device`].
374fn collect_device_updates(
375    verification_machine: VerificationMachine,
376    own_identity: Option<OwnUserIdentityData>,
377    identities: IdentityChanges,
378    devices: DeviceChanges,
379    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<X509Verifier>,
380) -> DeviceUpdates {
381    let mut new: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new();
382    let mut changed: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new();
383
384    let (new_identities, changed_identities, unchanged_identities) = identities.into_maps();
385
386    let map_device = |device: DeviceData| {
387        let device_owner_identity = new_identities
388            .get(device.user_id())
389            .or_else(|| changed_identities.get(device.user_id()))
390            .or_else(|| unchanged_identities.get(device.user_id()))
391            .cloned();
392
393        Device {
394            inner: device,
395            verification_machine: verification_machine.to_owned(),
396            own_identity: own_identity.to_owned(),
397            device_owner_identity,
398            #[cfg(feature = "experimental-x509-identity-verification")]
399            x509_verifier: x509_verifier.to_owned(),
400        }
401    };
402
403    for device in devices.new {
404        let device = map_device(device);
405
406        new.entry(device.user_id().to_owned())
407            .or_default()
408            .insert(device.device_id().to_owned(), device);
409    }
410
411    for device in devices.changed {
412        let device = map_device(device);
413
414        changed
415            .entry(device.user_id().to_owned())
416            .or_default()
417            .insert(device.device_id().to_owned(), device.to_owned());
418    }
419
420    DeviceUpdates { new, changed }
421}
422
423/// A temporary transaction (that implies a write) to the underlying store.
424#[allow(missing_debug_implementations)]
425pub struct StoreTransaction {
426    store: Store,
427    changes: PendingChanges,
428    // TODO hold onto the cross-process crypto store lock + cache.
429    cache: OwnedRwLockWriteGuard<StoreCache>,
430}
431
432impl StoreTransaction {
433    /// Starts a new `StoreTransaction`.
434    async fn new(store: Store) -> Self {
435        let cache = store.inner.cache.clone();
436
437        Self { store, changes: PendingChanges::default(), cache: cache.clone().write_owned().await }
438    }
439
440    pub(crate) fn cache(&self) -> &StoreCache {
441        &self.cache
442    }
443
444    /// Returns a reference to the current `Store`.
445    pub fn store(&self) -> &Store {
446        &self.store
447    }
448
449    /// Gets a `Account` for update.
450    ///
451    /// Note: since it's guaranteed that one can't have both a
452    /// `StoreTransaction` and a `StoreCacheGuard` at runtime (since the
453    /// underlying `StoreCache` is guarded by a `RwLock` mutex), this ensures
454    /// that we can't have two copies of an `Account` alive at the same time.
455    pub async fn account(&mut self) -> Result<&mut Account> {
456        if self.changes.account.is_none() {
457            // Make sure the cache loaded the account.
458            let _ = self.cache.account().await?;
459            self.changes.account = self.cache.account.lock().await.take();
460        }
461        Ok(self.changes.account.as_mut().unwrap())
462    }
463
464    /// Commits all dirty fields to the store, and maintains the cache so it
465    /// reflects the current state of the database.
466    pub async fn commit(self) -> Result<()> {
467        if self.changes.is_empty() {
468            return Ok(());
469        }
470
471        // Save changes in the database.
472        let account = self.changes.account.as_ref().map(|acc| acc.deep_clone());
473
474        self.store.save_pending_changes(self.changes).await?;
475
476        // Make the cache coherent with the database.
477        if let Some(account) = account {
478            *self.cache.account.lock().await = Some(account);
479        }
480
481        Ok(())
482    }
483}
484
485#[derive(Debug)]
486struct StoreInner {
487    identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
488    store: Arc<CryptoStoreWrapper>,
489
490    /// In-memory cache for the current crypto store.
491    ///
492    /// ⚠ Must remain private.
493    cache: Arc<RwLock<StoreCache>>,
494
495    verification_machine: VerificationMachine,
496
497    /// If we can verify peoples' identities via X.509 certificates, a handle to
498    /// the implementation.
499    #[cfg(feature = "experimental-x509-identity-verification")]
500    x509_verifier: Option<X509Verifier>,
501
502    /// If we can sign our own identity via X.509 certificates, a handle to the
503    /// implementation.
504    #[cfg(feature = "experimental-x509-identity-verification")]
505    x509_signer: Option<X509Signer>,
506
507    /// Static account data that never changes (and thus can be loaded once and
508    /// for all when creating the store).
509    static_account: StaticAccountData,
510}
511
512/// Error describing what went wrong when importing private cross signing keys
513/// or the key backup key.
514#[derive(Debug, Error)]
515pub enum SecretImportError {
516    /// The key that we tried to import was invalid.
517    #[error("Error while importing {name}: {error}")]
518    Key {
519        /// The name of the secret that was being imported.
520        name: SecretName,
521        /// The key error that occurred.
522        error: vodozemac::KeyError,
523    },
524    /// The public key of the imported private key doesn't match the public
525    /// key that was uploaded to the server.
526    #[error(
527        "Error while importing {name}: The public key of the imported private \
528            key doesn't match the public key that was uploaded to the server"
529    )]
530    MismatchedPublicKeys {
531        /// The name of the secret that was being imported.
532        name: SecretName,
533    },
534    /// The new version of the identity couldn't be stored.
535    #[error(transparent)]
536    Store(#[from] CryptoStoreError),
537}
538
539/// Error describing what went wrong when exporting a [`SecretsBundle`].
540///
541/// The [`SecretsBundle`] can only be exported if we have all cross-signing
542/// private keys in the store.
543#[derive(Debug, Error)]
544pub enum SecretsBundleExportError {
545    /// The store itself had an error.
546    #[error(transparent)]
547    Store(#[from] CryptoStoreError),
548    /// We're missing one or multiple cross-signing keys.
549    #[error("The store is missing one or multiple cross-signing keys")]
550    MissingCrossSigningKey(KeyUsage),
551    /// We're missing all cross-signing keys.
552    #[error("The store doesn't contain any cross-signing keys")]
553    MissingCrossSigningKeys,
554    /// We have a backup key stored, but we don't know the version of the
555    /// backup.
556    #[error("The store contains a backup key, but no backup version")]
557    MissingBackupVersion,
558}
559
560impl Store {
561    /// Create a new Store. A thin wrapper for [`Store::new_with_x509`].
562    pub(crate) fn new(
563        account: StaticAccountData,
564        identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
565        store: Arc<CryptoStoreWrapper>,
566        verification_machine: VerificationMachine,
567    ) -> Self {
568        Self::new_with_x509(
569            account,
570            identity,
571            store,
572            verification_machine,
573            #[cfg(feature = "experimental-x509-identity-verification")]
574            None,
575            #[cfg(feature = "experimental-x509-identity-verification")]
576            None,
577        )
578    }
579
580    /// Create a new Store, allowing specification of X.509 parameters.
581    pub(crate) fn new_with_x509(
582        account: StaticAccountData,
583        identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
584        store: Arc<CryptoStoreWrapper>,
585        verification_machine: VerificationMachine,
586        #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
587            X509Verifier,
588        >,
589        #[cfg(feature = "experimental-x509-identity-verification")] x509_signer: Option<X509Signer>,
590    ) -> Self {
591        Self {
592            inner: Arc::new(StoreInner {
593                static_account: account,
594                identity,
595                store: store.clone(),
596                verification_machine,
597                #[cfg(feature = "experimental-x509-identity-verification")]
598                x509_verifier,
599                #[cfg(feature = "experimental-x509-identity-verification")]
600                x509_signer,
601                cache: Arc::new(RwLock::new(StoreCache {
602                    store,
603                    tracked_users: Default::default(),
604                    loaded_tracked_users: Default::default(),
605                    account: Default::default(),
606                })),
607            }),
608        }
609    }
610
611    /// UserId associated with this store
612    pub(crate) fn user_id(&self) -> &UserId {
613        &self.inner.static_account.user_id
614    }
615
616    /// DeviceId associated with this store
617    pub(crate) fn device_id(&self) -> &DeviceId {
618        self.inner.verification_machine.own_device_id()
619    }
620
621    /// The static data for the account associated with this store.
622    pub(crate) fn static_account(&self) -> &StaticAccountData {
623        &self.inner.static_account
624    }
625
626    /// If we can sign our own identity via X.509 certificates, a handle to the
627    /// implementation.
628    #[cfg(feature = "experimental-x509-identity-verification")]
629    pub(crate) fn x509_signer(&self) -> Option<&X509Signer> {
630        self.inner.x509_signer.as_ref()
631    }
632
633    /// If we can verify peoples' identities via X.509 certificates, a handle to
634    /// the implementation.
635    #[cfg(feature = "experimental-x509-identity-verification")]
636    pub(crate) fn x509_verifier(&self) -> Option<&X509Verifier> {
637        self.inner.x509_verifier.as_ref()
638    }
639
640    pub(crate) async fn cache(&self) -> Result<StoreCacheGuard> {
641        Ok(StoreCacheGuard { cache: self.inner.cache.clone().read_owned().await })
642    }
643
644    pub(crate) async fn transaction(&self) -> StoreTransaction {
645        StoreTransaction::new(self.clone()).await
646    }
647
648    pub(crate) async fn with_transaction<T>(
649        &self,
650        func: impl AsyncFnOnce(&mut StoreTransaction) -> Result<T, crate::OlmError>,
651    ) -> Result<T, crate::OlmError> {
652        let mut tr = self.transaction().await;
653        let res = func(&mut tr).await?;
654        tr.commit().await?;
655        Ok(res)
656    }
657
658    #[cfg(test)]
659    /// test helper to reset the cross signing identity
660    pub(crate) async fn reset_cross_signing_identity(&self) {
661        self.inner.identity.lock().await.reset();
662    }
663
664    /// PrivateCrossSigningIdentity associated with this store
665    pub(crate) fn private_identity(&self) -> Arc<Mutex<PrivateCrossSigningIdentity>> {
666        self.inner.identity.clone()
667    }
668
669    /// Save the given Sessions to the store
670    pub(crate) async fn save_sessions(&self, sessions: &[Session]) -> Result<()> {
671        let changes = Changes { sessions: sessions.to_vec(), ..Default::default() };
672
673        self.save_changes(changes).await
674    }
675
676    pub(crate) async fn get_sessions(
677        &self,
678        sender_key: &str,
679    ) -> Result<Option<Arc<Mutex<Vec<Session>>>>> {
680        self.inner.store.get_sessions(sender_key).await
681    }
682
683    pub(crate) async fn save_changes(&self, changes: Changes) -> Result<()> {
684        self.inner.store.save_changes(changes).await
685    }
686
687    /// Given an `InboundGroupSession` which we have just received, see if we
688    /// have a matching session already in the store, and determine how to
689    /// handle it.
690    ///
691    /// If the store already has everything we can gather from the new session,
692    /// returns `None`. Otherwise, returns a merged session which should be
693    /// persisted to the store.
694    pub(crate) async fn merge_received_group_session(
695        &self,
696        session: InboundGroupSession,
697    ) -> Result<Option<InboundGroupSession>> {
698        let old_session = self
699            .inner
700            .store
701            .get_inbound_group_session(session.room_id(), session.session_id())
702            .await?;
703
704        // If there is no old session, just use the new session.
705        let Some(old_session) = old_session else {
706            info!("Received a new megolm room key");
707            return Ok(Some(session));
708        };
709
710        let index_comparison = session.compare_ratchet(&old_session).await;
711        let trust_level_comparison =
712            session.sender_data.compare_trust_level(&old_session.sender_data);
713
714        let result = match (index_comparison, trust_level_comparison) {
715            (SessionOrdering::Unconnected, _) => {
716                // If this happens, it means that we have two sessions purporting to have the
717                // same session id, but where the ratchets do not match up.
718                // In other words, someone is playing silly buggers.
719                warn!(
720                    "Received a group session with an ratchet that does not connect to the one in the store, discarding"
721                );
722                None
723            }
724
725            (SessionOrdering::Better, std::cmp::Ordering::Greater)
726            | (SessionOrdering::Better, std::cmp::Ordering::Equal)
727            | (SessionOrdering::Equal, std::cmp::Ordering::Greater) => {
728                // The new session is unambiguously better than what we have in the store.
729                info!(
730                    ?index_comparison,
731                    ?trust_level_comparison,
732                    "Received a megolm room key that we have a worse version of, merging"
733                );
734                Some(session)
735            }
736
737            (SessionOrdering::Worse, std::cmp::Ordering::Less)
738            | (SessionOrdering::Worse, std::cmp::Ordering::Equal)
739            | (SessionOrdering::Equal, std::cmp::Ordering::Less) => {
740                // The new session is unambiguously worse than the one we have in the store.
741                warn!(
742                    ?index_comparison,
743                    ?trust_level_comparison,
744                    "Received a megolm room key that we already have a better version \
745                     of, discarding"
746                );
747                None
748            }
749
750            (SessionOrdering::Equal, std::cmp::Ordering::Equal) => {
751                // The new session is the same as what we have.
752                info!("Received a megolm room key that we already have, discarding");
753                None
754            }
755
756            (SessionOrdering::Better, std::cmp::Ordering::Less) => {
757                // We need to take the ratchet from the new session, and the
758                // sender data from the old session.
759                info!("Upgrading a previously-received megolm session with new ratchet");
760                let result = old_session.with_ratchet(&session);
761                // We'll need to back it up again.
762                result.reset_backup_state();
763                Some(result)
764            }
765
766            (SessionOrdering::Worse, std::cmp::Ordering::Greater) => {
767                // We need to take the ratchet from the old session, and the
768                // sender data from the new session.
769                info!("Upgrading a previously-received megolm session with new sender data");
770                Some(session.with_ratchet(&old_session))
771            }
772        };
773
774        Ok(result)
775    }
776
777    #[cfg(test)]
778    /// Testing helper to allow to save only a set of devices
779    pub(crate) async fn save_device_data(&self, devices: &[DeviceData]) -> Result<()> {
780        use types::DeviceChanges;
781
782        let changes = Changes {
783            devices: DeviceChanges { changed: devices.to_vec(), ..Default::default() },
784            ..Default::default()
785        };
786
787        self.save_changes(changes).await
788    }
789
790    /// Convenience helper to persist an array of [`InboundGroupSession`]s.
791    pub(crate) async fn save_inbound_group_sessions(
792        &self,
793        sessions: &[InboundGroupSession],
794    ) -> Result<()> {
795        let changes = Changes { inbound_group_sessions: sessions.to_vec(), ..Default::default() };
796
797        self.save_changes(changes).await
798    }
799
800    /// Get the display name of our own device.
801    pub(crate) async fn device_display_name(&self) -> Result<Option<String>, CryptoStoreError> {
802        Ok(self
803            .inner
804            .store
805            .get_device(self.user_id(), self.device_id())
806            .await?
807            .and_then(|d| d.display_name().map(|d| d.to_owned())))
808    }
809
810    /// Get the device data for the given [`UserId`] and [`DeviceId`].
811    ///
812    /// *Note*: This method will include our own device which is always present
813    /// in the store.
814    pub(crate) async fn get_device_data(
815        &self,
816        user_id: &UserId,
817        device_id: &DeviceId,
818    ) -> Result<Option<DeviceData>> {
819        self.inner.store.get_device(user_id, device_id).await
820    }
821
822    /// Get the device data for the given [`UserId`] and [`DeviceId`].
823    ///
824    /// *Note*: This method will **not** include our own device.
825    ///
826    /// Use this method if you need a list of recipients for a given user, since
827    /// we don't want to encrypt for our own device, otherwise take a look at
828    /// the [`Store::get_device_data_for_user`] method.
829    pub(crate) async fn get_device_data_for_user_filtered(
830        &self,
831        user_id: &UserId,
832    ) -> Result<HashMap<OwnedDeviceId, DeviceData>> {
833        self.inner.store.get_user_devices(user_id).await.map(|mut d| {
834            if user_id == self.user_id() {
835                d.remove(self.device_id());
836            }
837            d
838        })
839    }
840
841    /// Get the [`DeviceData`] for all the devices a user has.
842    ///
843    /// *Note*: This method will include our own device which is always present
844    /// in the store.
845    ///
846    /// Use this method if you need to operate on or update all devices of a
847    /// user, otherwise take a look at the
848    /// [`Store::get_device_data_for_user_filtered`] method.
849    pub(crate) async fn get_device_data_for_user(
850        &self,
851        user_id: &UserId,
852    ) -> Result<HashMap<OwnedDeviceId, DeviceData>> {
853        self.inner.store.get_user_devices(user_id).await
854    }
855
856    /// Get a [`Device`] for the given user with the given
857    /// [`Curve25519PublicKey`] key.
858    ///
859    /// *Note*: This method will include our own device which is always present
860    /// in the store.
861    pub(crate) async fn get_device_from_curve_key(
862        &self,
863        user_id: &UserId,
864        curve_key: Curve25519PublicKey,
865    ) -> Result<Option<Device>> {
866        self.get_user_devices(user_id)
867            .await
868            .map(|d| d.devices().find(|d| d.curve25519_key() == Some(curve_key)))
869    }
870
871    /// Get all devices associated with the given [`UserId`].
872    ///
873    /// This method is more expensive than the
874    /// [`Store::get_device_data_for_user`] method, since a [`Device`]
875    /// requires the [`OwnUserIdentityData`] and the [`UserIdentityData`] of the
876    /// device owner to be fetched from the store as well.
877    ///
878    /// *Note*: This method will include our own device which is always present
879    /// in the store.
880    pub(crate) async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices> {
881        let devices = self.get_device_data_for_user(user_id).await?;
882
883        let own_identity = self
884            .inner
885            .store
886            .get_user_identity(self.user_id())
887            .await?
888            .and_then(|i| i.own().cloned());
889        let device_owner_identity = self.inner.store.get_user_identity(user_id).await?;
890
891        Ok(UserDevices {
892            inner: devices,
893            verification_machine: self.inner.verification_machine.clone(),
894            own_identity,
895            device_owner_identity,
896            #[cfg(feature = "experimental-x509-identity-verification")]
897            x509_verifier: self.x509_verifier().cloned(),
898        })
899    }
900
901    /// Get a [`Device`] for the given user with the given [`DeviceId`].
902    ///
903    /// This method is more expensive than the [`Store::get_device_data`] method
904    /// since a [`Device`] requires the [`OwnUserIdentityData`] and the
905    /// [`UserIdentityData`] of the device owner to be fetched from the
906    /// store as well.
907    ///
908    /// *Note*: This method will include our own device which is always present
909    /// in the store.
910    pub(crate) async fn get_device(
911        &self,
912        user_id: &UserId,
913        device_id: &DeviceId,
914    ) -> Result<Option<Device>> {
915        if let Some(device_data) = self.inner.store.get_device(user_id, device_id).await? {
916            Ok(Some(self.wrap_device_data(device_data).await?))
917        } else {
918            Ok(None)
919        }
920    }
921
922    /// Create a new device using the supplied [`DeviceData`]. Normally we would
923    /// call [`Self::get_device`] to find an existing device inside this
924    /// store. Only call this if you have some existing DeviceData and want
925    /// to wrap it with the extra information provided by a [`Device`].
926    pub(crate) async fn wrap_device_data(&self, device_data: DeviceData) -> Result<Device> {
927        let own_identity = self
928            .inner
929            .store
930            .get_user_identity(self.user_id())
931            .await?
932            .and_then(|i| i.own().cloned());
933
934        let device_owner_identity =
935            self.inner.store.get_user_identity(device_data.user_id()).await?;
936
937        Ok(Device {
938            inner: device_data,
939            verification_machine: self.inner.verification_machine.clone(),
940            own_identity,
941            device_owner_identity,
942            #[cfg(feature = "experimental-x509-identity-verification")]
943            x509_verifier: self.x509_verifier().cloned(),
944        })
945    }
946
947    ///  Get the Identity of `user_id`
948    pub(crate) async fn get_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
949        let own_identity = self
950            .inner
951            .store
952            .get_user_identity(self.user_id())
953            .await?
954            .and_then(as_variant!(UserIdentityData::Own));
955
956        Ok(self.inner.store.get_user_identity(user_id).await?.map(|i| {
957            UserIdentity::new(
958                self.clone(),
959                i,
960                self.inner.verification_machine.to_owned(),
961                own_identity,
962            )
963        }))
964    }
965
966    /// Try to export the secret with the given secret name.
967    ///
968    /// The exported secret will be encoded as unpadded base64. Returns `Null`
969    /// if the secret can't be found.
970    ///
971    /// # Arguments
972    ///
973    /// * `secret_name` - The name of the secret that should be exported.
974    pub async fn export_secret(
975        &self,
976        secret_name: &SecretName,
977    ) -> Result<Option<String>, CryptoStoreError> {
978        Ok(match secret_name {
979            SecretName::CrossSigningMasterKey
980            | SecretName::CrossSigningUserSigningKey
981            | SecretName::CrossSigningSelfSigningKey => {
982                self.inner.identity.lock().await.export_secret(secret_name).await
983            }
984            SecretName::RecoveryKey => {
985                if let Some(key) = self.load_backup_keys().await?.decryption_key {
986                    let exported = key.to_base64();
987                    Some(exported)
988                } else {
989                    None
990                }
991            }
992            name => {
993                warn!(secret = ?name, "Unknown secret was requested");
994                None
995            }
996        })
997    }
998
999    /// Export all the private cross signing keys we have.
1000    ///
1001    /// The export will contain the seed for the ed25519 keys as a unpadded
1002    /// base64 encoded string.
1003    ///
1004    /// This method returns `None` if we don't have any private cross signing
1005    /// keys.
1006    pub async fn export_cross_signing_keys(
1007        &self,
1008    ) -> Result<Option<CrossSigningKeyExport>, CryptoStoreError> {
1009        let master_key = self.export_secret(&SecretName::CrossSigningMasterKey).await?;
1010        let self_signing_key = self.export_secret(&SecretName::CrossSigningSelfSigningKey).await?;
1011        let user_signing_key = self.export_secret(&SecretName::CrossSigningUserSigningKey).await?;
1012
1013        Ok(if master_key.is_none() && self_signing_key.is_none() && user_signing_key.is_none() {
1014            None
1015        } else {
1016            Some(CrossSigningKeyExport { master_key, self_signing_key, user_signing_key })
1017        })
1018    }
1019
1020    /// Import our private cross signing keys.
1021    ///
1022    /// The export needs to contain the seed for the Ed25519 keys as an unpadded
1023    /// base64 encoded string.
1024    pub async fn import_cross_signing_keys(
1025        &self,
1026        export: CrossSigningKeyExport,
1027    ) -> Result<CrossSigningStatus, SecretImportError> {
1028        if let Some(public_identity) =
1029            self.get_identity(self.user_id()).await?.and_then(|i| i.own())
1030        {
1031            let identity = self.inner.identity.lock().await;
1032
1033            identity
1034                .import_secrets(
1035                    public_identity.to_owned(),
1036                    export.master_key.as_deref(),
1037                    export.self_signing_key.as_deref(),
1038                    export.user_signing_key.as_deref(),
1039                )
1040                .await?;
1041
1042            let status = identity.status().await;
1043
1044            let diff = identity.get_public_identity_diff(&public_identity.inner).await;
1045
1046            let mut changes =
1047                Changes { private_identity: Some(identity.clone()), ..Default::default() };
1048
1049            if diff.none_differ() {
1050                public_identity.mark_as_verified();
1051                changes.identities.changed.push(UserIdentityData::Own(public_identity.inner));
1052            }
1053
1054            info!(?status, "Successfully imported the private cross-signing keys");
1055
1056            self.save_changes(changes).await?;
1057        } else {
1058            warn!(
1059                "No public identity found while importing cross-signing keys, \
1060                 a /keys/query needs to be done"
1061            );
1062        }
1063
1064        Ok(self.inner.identity.lock().await.status().await)
1065    }
1066
1067    /// Export all the secrets we have in the store into a [`SecretsBundle`].
1068    ///
1069    /// This method will export all the private cross-signing keys and, if
1070    /// available, the private part of a backup key and its accompanying
1071    /// version.
1072    ///
1073    /// The method will fail if we don't have all three private cross-signing
1074    /// keys available.
1075    ///
1076    /// **Warning**: Only export this and share it with a trusted recipient,
1077    /// i.e. if an existing device is sharing this with a new device.
1078    pub async fn export_secrets_bundle(&self) -> Result<SecretsBundle, SecretsBundleExportError> {
1079        let Some(cross_signing) = self.export_cross_signing_keys().await? else {
1080            return Err(SecretsBundleExportError::MissingCrossSigningKeys);
1081        };
1082
1083        let Some(master_key) = cross_signing.master_key.clone() else {
1084            return Err(SecretsBundleExportError::MissingCrossSigningKey(KeyUsage::Master));
1085        };
1086
1087        let Some(user_signing_key) = cross_signing.user_signing_key.clone() else {
1088            return Err(SecretsBundleExportError::MissingCrossSigningKey(KeyUsage::UserSigning));
1089        };
1090
1091        let Some(self_signing_key) = cross_signing.self_signing_key.clone() else {
1092            return Err(SecretsBundleExportError::MissingCrossSigningKey(KeyUsage::SelfSigning));
1093        };
1094
1095        let backup_keys = self.load_backup_keys().await?;
1096
1097        let backup = if let Some(key) = backup_keys.decryption_key {
1098            if let Some(backup_version) = backup_keys.backup_version {
1099                Some(BackupSecrets::MegolmBackupV1Curve25519AesSha2(
1100                    MegolmBackupV1Curve25519AesSha2Secrets { key, backup_version },
1101                ))
1102            } else {
1103                return Err(SecretsBundleExportError::MissingBackupVersion);
1104            }
1105        } else {
1106            None
1107        };
1108
1109        Ok(SecretsBundle {
1110            cross_signing: CrossSigningSecrets { master_key, user_signing_key, self_signing_key },
1111            backup,
1112        })
1113    }
1114
1115    /// Import and persists secrets from a [`SecretsBundle`].
1116    ///
1117    /// This method will import all the private cross-signing keys and, if
1118    /// available, the private part of a backup key and its accompanying
1119    /// version into the store.
1120    ///
1121    /// **Warning**: Only import this from a trusted source, i.e. if an existing
1122    /// device is sharing this with a new device. The imported cross-signing
1123    /// keys will create a [`OwnUserIdentity`] and mark it as verified.
1124    ///
1125    /// The backup key will be persisted in the store and can be enabled using
1126    /// the [`BackupMachine`].
1127    pub async fn import_secrets_bundle(
1128        &self,
1129        bundle: &SecretsBundle,
1130    ) -> Result<(), SecretImportError> {
1131        let mut changes = Changes::default();
1132
1133        if let Some(backup_bundle) = &bundle.backup {
1134            match backup_bundle {
1135                BackupSecrets::MegolmBackupV1Curve25519AesSha2(bundle) => {
1136                    changes.backup_decryption_key = Some(bundle.key.clone());
1137                    changes.backup_version = Some(bundle.backup_version.clone());
1138                }
1139            }
1140        }
1141
1142        let identity = self.inner.identity.lock().await;
1143
1144        identity
1145            .import_secrets_unchecked(
1146                Some(&bundle.cross_signing.master_key),
1147                Some(&bundle.cross_signing.self_signing_key),
1148                Some(&bundle.cross_signing.user_signing_key),
1149            )
1150            .await?;
1151
1152        let public_identity = identity.to_public_identity().await.expect(
1153            "We should be able to create a new public identity since we just imported \
1154             all the private cross-signing keys",
1155        );
1156
1157        changes.private_identity = Some(identity.clone());
1158        changes.identities.new.push(UserIdentityData::Own(public_identity));
1159
1160        Ok(self.save_changes(changes).await?)
1161    }
1162
1163    /// Import the given `secret` named `secret_name` into the keystore.
1164    pub async fn import_secret(&self, secret: &GossippedSecret) -> Result<(), SecretImportError> {
1165        match &secret.secret_name {
1166            SecretName::CrossSigningMasterKey
1167            | SecretName::CrossSigningUserSigningKey
1168            | SecretName::CrossSigningSelfSigningKey => {
1169                if let Some(public_identity) =
1170                    self.get_identity(self.user_id()).await?.and_then(|i| i.own())
1171                {
1172                    let identity = self.inner.identity.lock().await;
1173
1174                    identity
1175                        .import_secret(
1176                            public_identity,
1177                            &secret.secret_name,
1178                            &secret.event.content.secret,
1179                        )
1180                        .await?;
1181                    info!(
1182                        secret_name = ?secret.secret_name,
1183                        "Successfully imported a private cross signing key"
1184                    );
1185
1186                    let changes =
1187                        Changes { private_identity: Some(identity.clone()), ..Default::default() };
1188
1189                    self.save_changes(changes).await?;
1190                }
1191            }
1192            SecretName::RecoveryKey => {
1193                // We don't import the decryption key here since we'll want to
1194                // check if the public key matches to the latest version on the
1195                // server. We instead put the secret into a secret inbox where
1196                // it will stay until it either gets overwritten
1197                // or the user accepts the secret.
1198            }
1199            name => {
1200                warn!(secret = ?name, "Tried to import an unknown secret");
1201            }
1202        }
1203
1204        Ok(())
1205    }
1206
1207    /// Check whether there is a global flag to only encrypt messages for
1208    /// trusted devices or for everyone.
1209    pub async fn get_only_allow_trusted_devices(&self) -> Result<bool> {
1210        let value = self.get_value("only_allow_trusted_devices").await?.unwrap_or_default();
1211        Ok(value)
1212    }
1213
1214    /// Set global flag whether to encrypt messages for untrusted devices, or
1215    /// whether they should be excluded from the conversation.
1216    pub async fn set_only_allow_trusted_devices(
1217        &self,
1218        block_untrusted_devices: bool,
1219    ) -> Result<()> {
1220        self.set_value("only_allow_trusted_devices", &block_untrusted_devices).await
1221    }
1222
1223    /// Get custom stored value associated with a key
1224    pub async fn get_value<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
1225        let Some(value) = self.get_custom_value(key).await? else {
1226            return Ok(None);
1227        };
1228        let deserialized = self.deserialize_value(&value)?;
1229        Ok(Some(deserialized))
1230    }
1231
1232    /// Store custom value associated with a key
1233    pub async fn set_value(&self, key: &str, value: &impl Serialize) -> Result<()> {
1234        let serialized = self.serialize_value(value)?;
1235        self.set_custom_value(key, serialized).await?;
1236        Ok(())
1237    }
1238
1239    fn serialize_value(&self, value: &impl Serialize) -> Result<Vec<u8>> {
1240        let serialized =
1241            rmp_serde::to_vec_named(value).map_err(|x| CryptoStoreError::Backend(x.into()))?;
1242        Ok(serialized)
1243    }
1244
1245    fn deserialize_value<T: DeserializeOwned>(&self, value: &[u8]) -> Result<T> {
1246        let deserialized =
1247            rmp_serde::from_slice(value).map_err(|e| CryptoStoreError::Backend(e.into()))?;
1248        Ok(deserialized)
1249    }
1250
1251    /// Receive notifications of room keys being received as a [`Stream`].
1252    ///
1253    /// Each time a room key is updated in any way, an update will be sent to
1254    /// the stream. Updates that happen at the same time are batched into a
1255    /// [`Vec`].
1256    ///
1257    /// If the reader of the stream lags too far behind an error will be sent to
1258    /// the reader.
1259    ///
1260    /// The stream will terminate once all references to the underlying
1261    /// `CryptoStoreWrapper` are dropped.
1262    pub fn room_keys_received_stream(
1263        &self,
1264    ) -> impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<> {
1265        self.inner.store.room_keys_received_stream()
1266    }
1267
1268    /// Receive notifications of received `m.room_key.withheld` messages.
1269    ///
1270    /// Each time an `m.room_key.withheld` is received and stored, an update
1271    /// will be sent to the stream. Updates that happen at the same time are
1272    /// batched into a [`Vec`].
1273    ///
1274    /// If the reader of the stream lags too far behind, a warning will be
1275    /// logged and items will be dropped.
1276    pub fn room_keys_withheld_received_stream(
1277        &self,
1278    ) -> impl Stream<Item = Vec<RoomKeyWithheldInfo>> + use<> {
1279        self.inner.store.room_keys_withheld_received_stream()
1280    }
1281
1282    /// Returns a stream of user identity updates, allowing users to listen for
1283    /// notifications about new or changed user identities.
1284    ///
1285    /// The stream produced by this method emits updates whenever a new user
1286    /// identity is discovered or when an existing identities information is
1287    /// changed. Users can subscribe to this stream and receive updates in
1288    /// real-time.
1289    ///
1290    /// Caution: the returned stream will never terminate, and it holds a
1291    /// reference to the [`CryptoStore`]. Listeners should be careful to avoid
1292    /// resource leaks.
1293    ///
1294    /// # Examples
1295    ///
1296    /// ```no_run
1297    /// # use matrix_sdk_crypto::OlmMachine;
1298    /// # use ruma::{device_id, user_id};
1299    /// # use futures_util::{pin_mut, StreamExt};
1300    /// # let machine: OlmMachine = unimplemented!();
1301    /// # futures_executor::block_on(async {
1302    /// let identities_stream = machine.store().user_identities_stream();
1303    /// pin_mut!(identities_stream);
1304    ///
1305    /// for identity_updates in identities_stream.next().await {
1306    ///     for (_, identity) in identity_updates.new {
1307    ///         println!("A new identity has been added {}", identity.user_id());
1308    ///     }
1309    /// }
1310    /// # });
1311    /// ```
1312    pub fn user_identities_stream(&self) -> impl Stream<Item = IdentityUpdates> + use<> {
1313        let verification_machine = self.inner.verification_machine.to_owned();
1314
1315        let this = self.clone();
1316        self.inner.store.identities_stream().map(move |(own_identity, identities, _)| {
1317            let (new_identities, changed_identities, unchanged_identities) = identities.into_maps();
1318
1319            let map_identity = |(user_id, identity)| {
1320                (
1321                    user_id,
1322                    UserIdentity::new(
1323                        this.clone(),
1324                        identity,
1325                        verification_machine.to_owned(),
1326                        own_identity.to_owned(),
1327                    ),
1328                )
1329            };
1330
1331            let new = new_identities.into_iter().map(map_identity).collect();
1332            let changed = changed_identities.into_iter().map(map_identity).collect();
1333            let unchanged = unchanged_identities.into_iter().map(map_identity).collect();
1334
1335            IdentityUpdates { new, changed, unchanged }
1336        })
1337    }
1338
1339    /// Returns a stream of device updates, allowing users to listen for
1340    /// notifications about new or changed devices.
1341    ///
1342    /// The stream produced by this method emits updates whenever a new device
1343    /// is discovered or when an existing device's information is changed. Users
1344    /// can subscribe to this stream and receive updates in real-time.
1345    ///
1346    /// Caution: the returned stream will never terminate, and it holds a
1347    /// reference to the [`CryptoStore`]. Listeners should be careful to avoid
1348    /// resource leaks.
1349    ///
1350    /// # Examples
1351    ///
1352    /// ```no_run
1353    /// # use matrix_sdk_crypto::OlmMachine;
1354    /// # use ruma::{device_id, user_id};
1355    /// # use futures_util::{pin_mut, StreamExt};
1356    /// # let machine: OlmMachine = unimplemented!();
1357    /// # futures_executor::block_on(async {
1358    /// let devices_stream = machine.store().devices_stream();
1359    /// pin_mut!(devices_stream);
1360    ///
1361    /// for device_updates in devices_stream.next().await {
1362    ///     if let Some(user_devices) = device_updates.new.get(machine.user_id()) {
1363    ///         for device in user_devices.values() {
1364    ///             println!("A new device has been added {}", device.device_id());
1365    ///         }
1366    ///     }
1367    /// }
1368    /// # });
1369    /// ```
1370    pub fn devices_stream(&self) -> impl Stream<Item = DeviceUpdates> + use<> {
1371        let verification_machine = self.inner.verification_machine.to_owned();
1372        #[cfg(feature = "experimental-x509-identity-verification")]
1373        let x509_verifier = self.x509_verifier().cloned();
1374
1375        self.inner.store.identities_stream().map(move |(own_identity, identities, devices)| {
1376            collect_device_updates(
1377                verification_machine.to_owned(),
1378                own_identity,
1379                identities,
1380                devices,
1381                #[cfg(feature = "experimental-x509-identity-verification")]
1382                x509_verifier.to_owned(),
1383            )
1384        })
1385    }
1386
1387    /// Returns a [`Stream`] of user identity and device updates
1388    ///
1389    /// The stream returned by this method returns the same data as
1390    /// [`Store::user_identities_stream`] and [`Store::devices_stream`] but does
1391    /// not include references to the `VerificationMachine`. It is therefore a
1392    /// lower-level view on that data.
1393    ///
1394    /// The stream will terminate once all references to the underlying
1395    /// `CryptoStoreWrapper` are dropped.
1396    pub fn identities_stream_raw(
1397        &self,
1398    ) -> impl Stream<Item = (IdentityChanges, DeviceChanges)> + use<> {
1399        self.inner.store.identities_stream().map(|(_, identities, devices)| (identities, devices))
1400    }
1401
1402    /// Creates a [`CrossProcessLock`] for this store, that will contain the
1403    /// given key and value when hold.
1404    pub fn create_store_lock(
1405        &self,
1406        lock_key: String,
1407        config: CrossProcessLockConfig,
1408    ) -> CrossProcessLock<LockableCryptoStore> {
1409        self.inner.store.create_store_lock(lock_key, config)
1410    }
1411
1412    /// Receive notifications of gossipped secrets being received and stored in
1413    /// the secret inbox as a [`Stream`].
1414    ///
1415    /// The gossipped secrets are received using the `m.secret.send` event type
1416    /// and are guaranteed to have been received over a 1-to-1 Olm
1417    /// [`Session`] from a verified [`Device`].
1418    ///
1419    /// The [`GossippedSecret`] can also be later found in the secret inbox and
1420    /// retrieved using the [`CryptoStore::get_secrets_from_inbox()`] method.
1421    ///
1422    /// After a suitable secret of a certain type has been found it can be
1423    /// removed from the store
1424    /// using the [`CryptoStore::delete_secrets_from_inbox()`] method.
1425    ///
1426    /// The only secret this will currently broadcast is the
1427    /// `m.megolm_backup.v1`.
1428    ///
1429    /// If the reader of the stream lags too far behind, a warning will be
1430    /// logged and items will be dropped.
1431    ///
1432    /// # Examples
1433    ///
1434    /// ```no_run
1435    /// # use matrix_sdk_crypto::OlmMachine;
1436    /// # use ruma::{device_id, owned_user_id};
1437    /// # use futures_util::{pin_mut, StreamExt};
1438    /// # let alice = owned_user_id!("@alice:example.org");
1439    /// # futures_executor::block_on(async {
1440    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1441    ///
1442    /// let secret_stream = machine.store().secrets_stream();
1443    /// pin_mut!(secret_stream);
1444    ///
1445    /// for secret in secret_stream.next().await {
1446    ///     // Accept the secret if it's valid, then delete all the secrets of this type.
1447    ///     machine.store().delete_secrets_from_inbox(&secret.secret_name);
1448    /// }
1449    /// # });
1450    /// ```
1451    pub fn secrets_stream(&self) -> impl Stream<Item = SecretsInboxItem> + use<> {
1452        self.inner.store.secrets_stream()
1453    }
1454
1455    /// Receive notifications of historic room key bundles as a [`Stream`].
1456    ///
1457    /// Historic room key bundles are defined in [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
1458    ///
1459    /// Each time a historic room key bundle was received, an update will be
1460    /// sent to the stream. This stream can be used to accept historic room key
1461    /// bundles that arrive out of order, i.e. the bundle arrives after the
1462    /// user has already accepted a room invitation.
1463    ///
1464    /// # Examples
1465    ///
1466    /// ```no_run
1467    /// # use matrix_sdk_crypto::{
1468    /// #    OlmMachine,
1469    /// #    store::types::StoredRoomKeyBundleData,
1470    /// #    types::room_history::RoomKeyBundle
1471    /// # };
1472    /// # use ruma::{device_id, owned_user_id};
1473    /// # use futures_util::{pin_mut, StreamExt};
1474    /// # let alice = owned_user_id!("@alice:example.org");
1475    /// # async {
1476    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1477    /// let bundle_stream = machine.store().historic_room_key_stream();
1478    /// pin_mut!(bundle_stream);
1479    ///
1480    /// while let Some(bundle_info) = bundle_stream.next().await {
1481    ///     // Try to find the bundle content in the store and if it's valid accept it.
1482    ///     if let Some(bundle_data) = machine.store().get_received_room_key_bundle_data(&bundle_info.room_id, &bundle_info.sender).await? {
1483    ///         // Download the bundle now and import it.
1484    ///         let bundle: RoomKeyBundle = todo!("Download the bundle");
1485    ///         machine.store().receive_room_key_bundle(
1486    ///             &bundle_data,
1487    ///             bundle,
1488    ///             |_, _| {},
1489    ///         ).await?;
1490    ///     }
1491    /// }
1492    /// # anyhow::Ok(()) };
1493    /// ```
1494    pub fn historic_room_key_stream(&self) -> impl Stream<Item = RoomKeyBundleInfo> + use<> {
1495        self.inner.store.historic_room_key_stream()
1496    }
1497
1498    /// Import the given room keys into the store.
1499    ///
1500    /// # Arguments
1501    ///
1502    /// * `exported_keys` - The keys to be imported.
1503    /// * `from_backup_version` - If the keys came from key backup, the key
1504    ///   backup version. This will cause the keys to be marked as already
1505    ///   backed up, and therefore not requiring another backup.
1506    /// * `progress_listener` - Callback which will be called after each key is
1507    ///   processed. Called with arguments `(processed, total)` where
1508    ///   `processed` is the number of keys processed so far, and `total` is the
1509    ///   total number of keys (i.e., `exported_keys.len()`).
1510    pub async fn import_room_keys(
1511        &self,
1512        exported_keys: Vec<ExportedRoomKey>,
1513        from_backup_version: Option<&str>,
1514        progress_listener: impl Fn(usize, usize),
1515    ) -> Result<RoomKeyImportResult> {
1516        let exported_keys = exported_keys.iter().filter_map(|key| {
1517            key.try_into()
1518                .map_err(|e| {
1519                    warn!(
1520                        sender_key = key.sender_key().to_base64(),
1521                        room_id = ?key.room_id(),
1522                        session_id = key.session_id(),
1523                        error = ?e,
1524                        "Couldn't import a room key from a file export."
1525                    );
1526                })
1527                .ok()
1528        });
1529        self.import_sessions_impl(exported_keys, from_backup_version, progress_listener).await
1530    }
1531
1532    /// Import the given room keys into our store.
1533    ///
1534    /// # Arguments
1535    ///
1536    /// * `exported_keys` - A list of previously exported keys that should be
1537    ///   imported into our store. If we already have a better version of a key
1538    ///   the key will *not* be imported.
1539    ///
1540    /// Returns a tuple of numbers that represent the number of sessions that
1541    /// were imported and the total number of sessions that were found in the
1542    /// key export.
1543    ///
1544    /// # Examples
1545    ///
1546    /// ```no_run
1547    /// # use std::io::Cursor;
1548    /// # use matrix_sdk_crypto::{OlmMachine, decrypt_room_key_export};
1549    /// # use ruma::{device_id, user_id};
1550    /// # let alice = user_id!("@alice:example.org");
1551    /// # async {
1552    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1553    /// # let export = Cursor::new("".to_owned());
1554    /// let exported_keys = decrypt_room_key_export(export, "1234").unwrap();
1555    /// machine.store().import_exported_room_keys(exported_keys, |_, _| {}).await.unwrap();
1556    /// # };
1557    /// ```
1558    pub async fn import_exported_room_keys(
1559        &self,
1560        exported_keys: Vec<ExportedRoomKey>,
1561        progress_listener: impl Fn(usize, usize),
1562    ) -> Result<RoomKeyImportResult> {
1563        self.import_room_keys(exported_keys, None, progress_listener).await
1564    }
1565
1566    async fn import_sessions_impl(
1567        &self,
1568        sessions: impl Iterator<Item = InboundGroupSession>,
1569        from_backup_version: Option<&str>,
1570        progress_listener: impl Fn(usize, usize),
1571    ) -> Result<RoomKeyImportResult> {
1572        let sessions: Vec<_> = sessions.collect();
1573        let mut imported_sessions = Vec::new();
1574
1575        let total_count = sessions.len();
1576        let mut keys = BTreeMap::new();
1577
1578        for (i, session) in sessions.into_iter().enumerate() {
1579            // Only import the session if we didn't have this session or
1580            // if it's a better version of the same session.
1581            if let Some(merged) = self.merge_received_group_session(session).await? {
1582                if from_backup_version.is_some() {
1583                    merged.mark_as_backed_up();
1584                }
1585
1586                keys.entry(merged.room_id().to_owned())
1587                    .or_insert_with(BTreeMap::new)
1588                    .entry(merged.sender_key().to_base64())
1589                    .or_insert_with(BTreeSet::new)
1590                    .insert(merged.session_id().to_owned());
1591
1592                imported_sessions.push(merged);
1593            }
1594
1595            progress_listener(i, total_count);
1596        }
1597
1598        let imported_count = imported_sessions.len();
1599
1600        self.inner
1601            .store
1602            .save_inbound_group_sessions(imported_sessions, from_backup_version)
1603            .await?;
1604
1605        info!(total_count, imported_count, room_keys = ?keys, "Successfully imported room keys");
1606
1607        Ok(RoomKeyImportResult::new(imported_count, total_count, keys))
1608    }
1609
1610    pub(crate) fn crypto_store(&self) -> Arc<CryptoStoreWrapper> {
1611        self.inner.store.clone()
1612    }
1613
1614    /// Export the keys that match the given predicate.
1615    ///
1616    /// # Arguments
1617    ///
1618    /// * `predicate` - A closure that will be called for every known
1619    ///   `InboundGroupSession`, which represents a room key. If the closure
1620    ///   returns `true` the `InboundGroupSession` will be included in the
1621    ///   export, if the closure returns `false` it will not be included.
1622    ///
1623    /// # Examples
1624    ///
1625    /// ```no_run
1626    /// # use matrix_sdk_crypto::{OlmMachine, encrypt_room_key_export};
1627    /// # use ruma::{device_id, user_id, room_id};
1628    /// # let alice = user_id!("@alice:example.org");
1629    /// # async {
1630    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1631    /// let room_id = room_id!("!test:localhost");
1632    /// let exported_keys = machine.store().export_room_keys(|s| s.room_id() == room_id).await.unwrap();
1633    /// let encrypted_export = encrypt_room_key_export(&exported_keys, "1234", 1);
1634    /// # };
1635    /// ```
1636    pub async fn export_room_keys(
1637        &self,
1638        predicate: impl FnMut(&InboundGroupSession) -> bool,
1639    ) -> Result<Vec<ExportedRoomKey>> {
1640        let mut exported = Vec::new();
1641
1642        let mut sessions = self.get_inbound_group_sessions().await?;
1643        sessions.retain(predicate);
1644
1645        for session in sessions {
1646            let export = session.export().await;
1647            exported.push(export);
1648        }
1649
1650        Ok(exported)
1651    }
1652
1653    /// Export room keys matching a predicate, providing them as an async
1654    /// `Stream`.
1655    ///
1656    /// # Arguments
1657    ///
1658    /// * `predicate` - A closure that will be called for every known
1659    ///   `InboundGroupSession`, which represents a room key. If the closure
1660    ///   returns `true` the `InboundGroupSession` will be included in the
1661    ///   export, if the closure returns `false` it will not be included.
1662    ///
1663    /// # Examples
1664    ///
1665    /// ```no_run
1666    /// use std::pin::pin;
1667    ///
1668    /// use matrix_sdk_crypto::{OlmMachine, olm::ExportedRoomKey};
1669    /// use ruma::{device_id, room_id, user_id};
1670    /// use tokio_stream::StreamExt;
1671    /// # async {
1672    /// let alice = user_id!("@alice:example.org");
1673    /// let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1674    /// let room_id = room_id!("!test:localhost");
1675    /// let mut keys = pin!(
1676    ///     machine
1677    ///         .store()
1678    ///         .export_room_keys_stream(|s| s.room_id() == room_id)
1679    ///         .await
1680    ///         .unwrap()
1681    /// );
1682    /// while let Some(key) = keys.next().await {
1683    ///     println!("{}", key.room_id);
1684    /// }
1685    /// # };
1686    /// ```
1687    pub async fn export_room_keys_stream(
1688        &self,
1689        predicate: impl FnMut(&InboundGroupSession) -> bool,
1690    ) -> Result<impl Stream<Item = ExportedRoomKey>> {
1691        // TODO: if/when there is a get_inbound_group_sessions_stream, use that here.
1692        let sessions = self.get_inbound_group_sessions().await?;
1693        Ok(futures_util::stream::iter(sessions.into_iter().filter(predicate))
1694            .then(|session| async move { session.export().await }))
1695    }
1696
1697    /// Assemble a room key bundle for sharing encrypted history, as per
1698    /// [MSC4268].
1699    ///
1700    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1701    pub async fn build_room_key_bundle(
1702        &self,
1703        room_id: &RoomId,
1704    ) -> std::result::Result<RoomKeyBundle, CryptoStoreError> {
1705        let sessions = self.get_inbound_group_sessions_by_room_id(room_id).await?;
1706
1707        let mut bundle = RoomKeyBundle::default();
1708        for session in sessions {
1709            if session.shared_history() {
1710                bundle.room_keys.push(session.export().await.into());
1711            } else {
1712                bundle.withheld.push(RoomKeyWithheldContent::new(
1713                    session.algorithm().to_owned(),
1714                    WithheldCode::HistoryNotShared,
1715                    session.room_id().to_owned(),
1716                    session.session_id().to_owned(),
1717                    session.sender_key().to_owned(),
1718                    self.device_id().to_owned(),
1719                ));
1720            }
1721        }
1722
1723        // If we received a key bundle ourselves, in which one or more sessions was
1724        // marked as "history not shared", pass that on to the new user.
1725        let withhelds = self.get_withheld_sessions_by_room_id(room_id).await?;
1726        for withheld in withhelds {
1727            if withheld.content.withheld_code() == WithheldCode::HistoryNotShared {
1728                bundle.withheld.push(withheld.content);
1729            }
1730        }
1731
1732        Ok(bundle)
1733    }
1734
1735    /// Import the contents of a downloaded and decrypted [MSC4268] key bundle.
1736    ///
1737    /// # Arguments
1738    ///
1739    /// * `bundle_info` - The [`StoredRoomKeyBundleData`] of the bundle that is
1740    ///   being received.
1741    /// * `bundle` - The decrypted and deserialized bundle itself.
1742    ///
1743    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1744    #[instrument(skip(self, bundle, progress_listener), fields(bundle_size = bundle.room_keys.len(), sender_data))]
1745    pub async fn receive_room_key_bundle(
1746        &self,
1747        bundle_info: &StoredRoomKeyBundleData,
1748        bundle: RoomKeyBundle,
1749        progress_listener: impl Fn(usize, usize),
1750    ) -> Result<(), CryptoStoreError> {
1751        let sender_data = if bundle_info.sender_data.should_recalculate() {
1752            let device = self
1753                .get_device_from_curve_key(&bundle_info.sender_user, bundle_info.sender_key)
1754                .await?;
1755
1756            device
1757                .as_ref()
1758                .map(SenderData::from_device)
1759                .unwrap_or_else(|| bundle_info.sender_data.clone())
1760        } else {
1761            bundle_info.sender_data.clone()
1762        };
1763
1764        tracing::Span::current().record("sender_data", tracing::field::debug(&sender_data));
1765
1766        // The sender's device must be either `SenderData::SenderUnverified` (i.e.,
1767        // TOFU-trusted) or `SenderData::SenderVerified` (i.e., fully verified
1768        // via user verification and cross-signing).
1769        let Ok(forwarder_data) = (&sender_data).try_into() else {
1770            warn!(
1771                "Not accepting a historic room key bundle due to insufficient trust in the sender"
1772            );
1773            return Ok(());
1774        };
1775
1776        self.import_room_key_bundle_sessions(
1777            bundle_info,
1778            &bundle,
1779            &forwarder_data,
1780            progress_listener,
1781        )
1782        .await?;
1783        self.import_room_key_bundle_withheld_info(bundle_info, &bundle).await?;
1784
1785        Ok(())
1786    }
1787
1788    async fn import_room_key_bundle_sessions(
1789        &self,
1790        bundle_info: &StoredRoomKeyBundleData,
1791        bundle: &RoomKeyBundle,
1792        forwarder_data: &ForwarderData,
1793        progress_listener: impl Fn(usize, usize),
1794    ) -> Result<(), CryptoStoreError> {
1795        let (good, bad): (Vec<_>, Vec<_>) = bundle.room_keys.iter().partition_map(|key| {
1796            if key.room_id != bundle_info.bundle_data.room_id {
1797                trace!("Ignoring key for incorrect room {} in bundle", key.room_id);
1798                Either::Right(key)
1799            } else {
1800                Either::Left(key)
1801            }
1802        });
1803
1804        match (bad.is_empty(), good.is_empty()) {
1805            // Case 1: Completely empty bundle.
1806            (true, true) => {
1807                warn!("Received a completely empty room key bundle");
1808            }
1809
1810            // Case 2: A bundle for the wrong room.
1811            (false, true) => {
1812                let bad_keys: Vec<_> =
1813                    bad.iter().map(|&key| (&key.room_id, &key.session_id)).collect();
1814
1815                warn!(
1816                    ?bad_keys,
1817                    "Received a room key bundle for the wrong room, ignoring all room keys from the bundle"
1818                );
1819            }
1820
1821            // Case 3: A bundle containing useful room keys.
1822            (_, false) => {
1823                // We have at least some good keys, if we also have some bad ones let's
1824                // mention that here.
1825                if !bad.is_empty() {
1826                    warn!(
1827                        bad_key_count = bad.len(),
1828                        "The room key bundle contained some room keys \
1829                         that were meant for a different room"
1830                    );
1831                }
1832
1833                let keys = good.iter().filter_map(|key| {
1834                    key.try_into_inbound_group_session(forwarder_data)
1835                        .map_err(|e| {
1836                            warn!(
1837                                sender_key = ?key.sender_key().to_base64(),
1838                                room_id = ?key.room_id(),
1839                                session_id = key.session_id(),
1840                                error = ?e,
1841                                "Couldn't import a room key from a key bundle."
1842                            );
1843                        })
1844                        .ok()
1845                });
1846
1847                self.import_sessions_impl(keys, None, progress_listener).await?;
1848            }
1849        }
1850
1851        Ok(())
1852    }
1853
1854    async fn import_room_key_bundle_withheld_info(
1855        &self,
1856        bundle_info: &StoredRoomKeyBundleData,
1857        bundle: &RoomKeyBundle,
1858    ) -> Result<(), CryptoStoreError> {
1859        let mut session_id_to_withheld_code_map = BTreeMap::new();
1860
1861        let mut changes = Changes::default();
1862        for withheld in &bundle.withheld {
1863            let (room_id, session_id) = match withheld {
1864                RoomKeyWithheldContent::MegolmV1AesSha2(c) => match (c.room_id(), c.session_id()) {
1865                    (Some(room_id), Some(session_id)) => (room_id, session_id),
1866                    _ => continue,
1867                },
1868                #[cfg(feature = "experimental-algorithms")]
1869                RoomKeyWithheldContent::MegolmV2AesSha2(c) => match (c.room_id(), c.session_id()) {
1870                    (Some(room_id), Some(session_id)) => (room_id, session_id),
1871                    _ => continue,
1872                },
1873                RoomKeyWithheldContent::Unknown(_) => continue,
1874            };
1875
1876            if room_id != bundle_info.bundle_data.room_id {
1877                trace!("Ignoring withheld info for incorrect room {} in bundle", room_id);
1878                continue;
1879            }
1880
1881            changes.withheld_session_info.entry(room_id.to_owned()).or_default().insert(
1882                session_id.to_owned(),
1883                RoomKeyWithheldEntry {
1884                    sender: bundle_info.sender_user.clone(),
1885                    content: withheld.to_owned(),
1886                },
1887            );
1888            session_id_to_withheld_code_map.insert(session_id, withheld.withheld_code());
1889        }
1890
1891        self.save_changes(changes).await?;
1892
1893        info!(
1894            room_id = ?bundle_info.bundle_data.room_id,
1895            ?session_id_to_withheld_code_map,
1896            "Successfully imported withheld info from room key bundle",
1897        );
1898
1899        Ok(())
1900    }
1901
1902    /// Store the fact that we have accepted an invite for a given room on this
1903    /// client, so should accept an [MSC4268] key bundle if one arrives
1904    /// soon.
1905    ///
1906    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1907    pub async fn store_room_pending_key_bundle(
1908        &self,
1909        room_id: &RoomId,
1910        inviter: &UserId,
1911    ) -> Result<(), CryptoStoreError> {
1912        let invite_accepted_at = MilliSecondsSinceUnixEpoch::now();
1913        self.save_changes(Changes {
1914            rooms_pending_key_bundle: HashMap::from([(
1915                room_id.to_owned(),
1916                Some(RoomPendingKeyBundleDetails {
1917                    room_id: room_id.to_owned(),
1918                    invite_accepted_at,
1919                    inviter: inviter.to_owned(),
1920                }),
1921            )]),
1922            ..Default::default()
1923        })
1924        .await?;
1925
1926        Ok(())
1927    }
1928
1929    /// Clear the record of accepting an invite for a specific room.
1930    ///
1931    /// The counterpart of [`Store::store_room_pending_key_bundle`].
1932    pub async fn clear_room_pending_key_bundle(&self, room_id: &RoomId) -> Result<()> {
1933        self.save_changes(Changes {
1934            rooms_pending_key_bundle: HashMap::from([(room_id.to_owned(), None)]),
1935            ..Default::default()
1936        })
1937        .await
1938    }
1939}
1940
1941impl Deref for Store {
1942    type Target = DynCryptoStore;
1943
1944    fn deref(&self) -> &Self::Target {
1945        self.inner.store.deref().deref()
1946    }
1947}
1948
1949/// A crypto store that implements primitives for cross-process locking.
1950#[derive(Clone, Debug)]
1951pub struct LockableCryptoStore(Arc<dyn CryptoStore<Error = CryptoStoreError>>);
1952
1953impl matrix_sdk_common::cross_process_lock::TryLock for LockableCryptoStore {
1954    type LockError = CryptoStoreError;
1955
1956    async fn try_lock(
1957        &self,
1958        lease_duration_ms: u32,
1959        key: &str,
1960        holder: &str,
1961    ) -> std::result::Result<Option<CrossProcessLockGeneration>, Self::LockError> {
1962        self.0.try_take_leased_lock(lease_duration_ms, key, holder).await
1963    }
1964}
1965
1966#[cfg(test)]
1967mod tests {
1968    use std::{collections::BTreeMap, pin::pin};
1969
1970    use assert_matches2::{assert_let, assert_matches};
1971    use futures_util::StreamExt;
1972    use insta::{_macro_support::Content, assert_json_snapshot, internals::ContentPath};
1973    use matrix_sdk_test::async_test;
1974    use rand::RngExt;
1975    use ruma::{
1976        RoomId, device_id,
1977        events::room::{EncryptedFile, EncryptedFileHashes, V2EncryptedFileInfo},
1978        owned_device_id, owned_mxc_uri, room_id, user_id,
1979    };
1980    use serde_json::json;
1981    use vodozemac::{Ed25519Keypair, megolm::SessionKey};
1982
1983    use crate::{
1984        Account, OlmMachine,
1985        machine::test_helpers::get_machine_pair,
1986        olm::{InboundGroupSession, SenderData},
1987        store::types::{DehydratedDeviceKey, RoomKeyWithheldEntry, StoredRoomKeyBundleData},
1988        types::{
1989            EventEncryptionAlgorithm,
1990            events::{
1991                room_key_bundle::RoomKeyBundleContent,
1992                room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent},
1993            },
1994        },
1995    };
1996
1997    #[async_test]
1998    async fn test_merge_received_group_session() {
1999        let alice_account = Account::with_device_id(user_id!("@a:s.co"), device_id!("ABC"));
2000        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("DEF")).await;
2001
2002        let room_id = room_id!("!test:localhost");
2003
2004        let megolm_signing_key = Ed25519Keypair::new();
2005        let inbound = make_inbound_group_session(&alice_account, &megolm_signing_key, room_id);
2006
2007        // Bob already knows about the session, at index 5, with the device keys.
2008        let mut inbound_at_index_5 =
2009            InboundGroupSession::from_export(&inbound.export_at_index(5).await).unwrap();
2010        inbound_at_index_5.sender_data = inbound.sender_data.clone();
2011        bob.store().save_inbound_group_sessions(&[inbound_at_index_5.clone()]).await.unwrap();
2012
2013        // No changes if we get a disconnected session.
2014        let disconnected = make_inbound_group_session(&alice_account, &megolm_signing_key, room_id);
2015        assert_eq!(bob.store().merge_received_group_session(disconnected).await.unwrap(), None);
2016
2017        // No changes needed when we receive a worse copy of the session
2018        let mut worse =
2019            InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
2020        worse.sender_data = inbound.sender_data.clone();
2021        assert_eq!(bob.store().merge_received_group_session(worse).await.unwrap(), None);
2022
2023        // Nor when we receive an exact copy of what we already have
2024        let mut copy = InboundGroupSession::from_pickle(inbound_at_index_5.pickle().await).unwrap();
2025        copy.sender_data = inbound.sender_data.clone();
2026        assert_eq!(bob.store().merge_received_group_session(copy).await.unwrap(), None);
2027
2028        // But when we receive a better copy of the session, we should get it back
2029        let mut better =
2030            InboundGroupSession::from_export(&inbound.export_at_index(0).await).unwrap();
2031        better.sender_data = inbound.sender_data.clone();
2032        assert_let!(Some(update) = bob.store().merge_received_group_session(better).await.unwrap());
2033        assert_eq!(update.first_known_index(), 0);
2034
2035        // A worse copy of the ratchet, but better trust data
2036        {
2037            let mut worse_ratchet_better_trust =
2038                InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
2039            let updated_sender_data = SenderData::sender_verified(
2040                alice_account.user_id(),
2041                alice_account.device_id(),
2042                Ed25519Keypair::new().public_key(),
2043            );
2044            worse_ratchet_better_trust.sender_data = updated_sender_data.clone();
2045            assert_let!(
2046                Some(update) = bob
2047                    .store()
2048                    .merge_received_group_session(worse_ratchet_better_trust)
2049                    .await
2050                    .unwrap()
2051            );
2052            assert_eq!(update.sender_data, updated_sender_data);
2053            assert_eq!(update.first_known_index(), 5);
2054            assert_eq!(
2055                update.export_at_index(0).await.session_key.to_bytes(),
2056                inbound.export_at_index(5).await.session_key.to_bytes()
2057            );
2058        }
2059
2060        // A better copy of the ratchet, but worse trust data
2061        {
2062            let mut better_ratchet_worse_trust =
2063                InboundGroupSession::from_export(&inbound.export_at_index(0).await).unwrap();
2064            let updated_sender_data = SenderData::unknown();
2065            better_ratchet_worse_trust.sender_data = updated_sender_data.clone();
2066            assert_let!(
2067                Some(update) = bob
2068                    .store()
2069                    .merge_received_group_session(better_ratchet_worse_trust)
2070                    .await
2071                    .unwrap()
2072            );
2073            assert_eq!(update.sender_data, inbound.sender_data);
2074            assert_eq!(update.first_known_index(), 0);
2075            assert_eq!(
2076                update.export_at_index(0).await.session_key.to_bytes(),
2077                inbound.export_at_index(0).await.session_key.to_bytes()
2078            );
2079        }
2080    }
2081
2082    /// Create an [`InboundGroupSession`] for the given room, using the given
2083    /// Ed25519 key as the signing key/session ID.
2084    fn make_inbound_group_session(
2085        sender_account: &Account,
2086        signing_key: &Ed25519Keypair,
2087        room_id: &RoomId,
2088    ) -> InboundGroupSession {
2089        InboundGroupSession::new(
2090            sender_account.identity_keys.curve25519,
2091            sender_account.identity_keys.ed25519,
2092            room_id,
2093            &make_session_key(signing_key),
2094            SenderData::device_info(crate::types::DeviceKeys::new(
2095                sender_account.user_id().to_owned(),
2096                sender_account.device_id().to_owned(),
2097                vec![],
2098                BTreeMap::new(),
2099                crate::types::Signatures::new(),
2100            )),
2101            None,
2102            EventEncryptionAlgorithm::MegolmV1AesSha2,
2103            Some(ruma::events::room::history_visibility::HistoryVisibility::Shared),
2104            true,
2105        )
2106        .unwrap()
2107    }
2108
2109    /// Make a Megolm [`SessionKey`] using the given Ed25519 key as a signing
2110    /// key/session ID.
2111    fn make_session_key(signing_key: &Ed25519Keypair) -> SessionKey {
2112        // `SessionKey::new` is not public, so the easiest way to construct a Megolm
2113        // session using a known Ed25519 key is to build a byte array in the export
2114        // format.
2115
2116        let mut session_key_bytes = vec![0u8; 229];
2117        // 0: version
2118        session_key_bytes[0] = 2;
2119        // 1..5: index
2120        // 5..133: ratchet key
2121        rand::rng().fill(&mut session_key_bytes[5..133]);
2122        // 133..165: public ed25519 key
2123        session_key_bytes[133..165].copy_from_slice(signing_key.public_key().as_bytes());
2124        // 165..229: signature
2125        let sig = signing_key.sign(&session_key_bytes[0..165]);
2126        session_key_bytes[165..229].copy_from_slice(&sig.to_bytes());
2127
2128        SessionKey::from_bytes(&session_key_bytes).unwrap()
2129    }
2130
2131    #[async_test]
2132    async fn test_import_room_keys_notifies_stream() {
2133        use futures_util::FutureExt;
2134
2135        let (alice, bob, _) =
2136            get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2137
2138        let room1_id = room_id!("!room1:localhost");
2139        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2140        let exported_sessions = alice.store().export_room_keys(|_| true).await.unwrap();
2141
2142        let mut room_keys_received_stream = Box::pin(bob.store().room_keys_received_stream());
2143        bob.store().import_room_keys(exported_sessions, None, |_, _| {}).await.unwrap();
2144
2145        let room_keys = room_keys_received_stream
2146            .next()
2147            .now_or_never()
2148            .flatten()
2149            .expect("We should have received an update of room key infos")
2150            .unwrap();
2151        assert_eq!(room_keys.len(), 1);
2152        assert_eq!(room_keys[0].room_id, "!room1:localhost");
2153    }
2154
2155    #[async_test]
2156    async fn test_export_room_keys_provides_selected_keys() {
2157        // Given an OlmMachine with room keys in it
2158        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2159        let room1_id = room_id!("!room1:localhost");
2160        let room2_id = room_id!("!room2:localhost");
2161        let room3_id = room_id!("!room3:localhost");
2162        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2163        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2164        alice.create_outbound_group_session_with_defaults_test_helper(room3_id).await.unwrap();
2165
2166        // When I export some of the keys
2167        let keys = alice
2168            .store()
2169            .export_room_keys(|s| s.room_id() == room2_id || s.room_id() == room3_id)
2170            .await
2171            .unwrap();
2172
2173        // Then the requested keys were provided
2174        assert_eq!(keys.len(), 2);
2175        assert_eq!(keys[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2176        assert_eq!(keys[1].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2177        assert_eq!(keys[0].room_id, "!room2:localhost");
2178        assert_eq!(keys[1].room_id, "!room3:localhost");
2179        assert_eq!(keys[0].session_key.to_base64().len(), 220);
2180        assert_eq!(keys[1].session_key.to_base64().len(), 220);
2181    }
2182
2183    #[async_test]
2184    async fn test_export_room_keys_stream_can_provide_all_keys() {
2185        // Given an OlmMachine with room keys in it
2186        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2187        let room1_id = room_id!("!room1:localhost");
2188        let room2_id = room_id!("!room2:localhost");
2189        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2190        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2191
2192        // When I export the keys as a stream
2193        let mut keys = pin!(alice.store().export_room_keys_stream(|_| true).await.unwrap());
2194
2195        // And collect them
2196        let mut collected = vec![];
2197        while let Some(key) = keys.next().await {
2198            collected.push(key);
2199        }
2200
2201        // Then all the keys were provided
2202        assert_eq!(collected.len(), 2);
2203        assert_eq!(collected[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2204        assert_eq!(collected[1].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2205        assert_eq!(collected[0].room_id, "!room1:localhost");
2206        assert_eq!(collected[1].room_id, "!room2:localhost");
2207        assert_eq!(collected[0].session_key.to_base64().len(), 220);
2208        assert_eq!(collected[1].session_key.to_base64().len(), 220);
2209    }
2210
2211    #[async_test]
2212    async fn test_export_room_keys_stream_can_provide_a_subset_of_keys() {
2213        // Given an OlmMachine with room keys in it
2214        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2215        let room1_id = room_id!("!room1:localhost");
2216        let room2_id = room_id!("!room2:localhost");
2217        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2218        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2219
2220        // When I export the keys as a stream
2221        let mut keys =
2222            pin!(alice.store().export_room_keys_stream(|s| s.room_id() == room1_id).await.unwrap());
2223
2224        // And collect them
2225        let mut collected = vec![];
2226        while let Some(key) = keys.next().await {
2227            collected.push(key);
2228        }
2229
2230        // Then all the keys matching our predicate were provided, and no others
2231        assert_eq!(collected.len(), 1);
2232        assert_eq!(collected[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2233        assert_eq!(collected[0].room_id, "!room1:localhost");
2234        assert_eq!(collected[0].session_key.to_base64().len(), 220);
2235    }
2236
2237    #[async_test]
2238    async fn test_export_secrets_bundle() {
2239        let user_id = user_id!("@alice:example.com");
2240        let (first, second, _) = get_machine_pair(user_id, user_id, false).await;
2241
2242        let _ = first
2243            .bootstrap_cross_signing(false)
2244            .await
2245            .expect("We should be able to bootstrap cross-signing");
2246
2247        let bundle = first.store().export_secrets_bundle().await.expect(
2248            "We should be able to export the secrets bundle, now that we \
2249             have the cross-signing keys",
2250        );
2251
2252        assert!(bundle.backup.is_none(), "The bundle should not contain a backup key");
2253
2254        second
2255            .store()
2256            .import_secrets_bundle(&bundle)
2257            .await
2258            .expect("We should be able to import the secrets bundle");
2259
2260        let status = second.cross_signing_status().await;
2261        let identity = second.get_identity(user_id, None).await.unwrap().unwrap().own().unwrap();
2262
2263        assert!(identity.is_verified(), "The public identity should be marked as verified.");
2264
2265        assert!(status.is_complete(), "We should have imported all the cross-signing keys");
2266    }
2267
2268    #[async_test]
2269    async fn test_create_dehydrated_device_key() {
2270        let pickle_key = DehydratedDeviceKey::new();
2271
2272        let to_vec = pickle_key.inner.to_vec();
2273        let pickle_key_from_slice = DehydratedDeviceKey::from_slice(to_vec.as_slice())
2274            .expect("Should be able to create a dehydrated device key from slice");
2275
2276        assert_eq!(pickle_key_from_slice.to_base64(), pickle_key.to_base64());
2277    }
2278
2279    #[async_test]
2280    async fn test_create_dehydrated_errors() {
2281        let too_small = [0u8; 22];
2282        let pickle_key = DehydratedDeviceKey::from_slice(&too_small);
2283
2284        assert!(pickle_key.is_err());
2285
2286        let too_big = [0u8; 40];
2287        let pickle_key = DehydratedDeviceKey::from_slice(&too_big);
2288
2289        assert!(pickle_key.is_err());
2290    }
2291
2292    #[async_test]
2293    async fn test_build_room_key_bundle() {
2294        // Given: Alice has sent a number of room keys to Bob, including some in the
2295        // wrong room, and some that are not marked as shared...
2296        let alice = OlmMachine::new(user_id!("@a:s.co"), device_id!("ALICE")).await;
2297        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("BOB")).await;
2298
2299        let room1_id = room_id!("!room1:localhost");
2300        let room2_id = room_id!("!room2:localhost");
2301
2302        /* We use hardcoded megolm session data, to get a stable output snapshot. These were all created with:
2303
2304           println!("{}", vodozemac::megolm::GroupSession::new(Default::default()).session_key().to_base64());
2305        */
2306        let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw";
2307        let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ";
2308        let session_key3 = "AgAAAAAM9KFsliaUUhGSXgwOzM5UemjkNH4n8NHgvC/y8hhw13zTF+ooGD4uIYEXYX630oNvQm/EvgZo+dkoc0re+vsqsx4sQeNODdSjcBsWOa0oDF+irQn9oYoLUDPI1IBtY1rX+FV99Zm/xnG7uFOX7aTVlko2GSdejy1w9mfobmfxu5aUc04A9zaKJP1pOthZvRAlhpymGYHgsDtWPrrjyc/yypMflE4kIUEEEtu1kT6mrAmcl615XYRAHYK9G2+fZsGvokwzbkl4nulGwcZMpQEoM0nD2o3GWgX81HW3nGfKBg";
2309        let session_key4 = "AgAAAAA4Kkesxq2h4v9PLD6Sm3Smxspz1PXTqytQPCMQMkkrHNmzV2bHlJ+6/Al9cu8vh1Oj69AK0WUAeJOJuaiskEeg/PI3P03+UYLeC379RzgqwSHdBgdQ41G2vD6zpgmE/8vYToe+qpCZACtPOswZxyqxHH+T/Iq0nv13JmlFGIeA6fEPfr5Y28B49viG74Fs9rxV9EH5PfjbuPM/p+Sz5obShuaBPKQBX1jT913nEXPoIJ06exNZGr0285nw/LgVvNlmWmbqNnbzO2cNZjQWA+xZYz5FSfyCxwqEBbEdUCuRCQ";
2310
2311        let sessions = [
2312            create_inbound_group_session_with_visibility(
2313                &alice,
2314                room1_id,
2315                &SessionKey::from_base64(session_key1).unwrap(),
2316                true,
2317            ),
2318            create_inbound_group_session_with_visibility(
2319                &alice,
2320                room1_id,
2321                &SessionKey::from_base64(session_key2).unwrap(),
2322                true,
2323            ),
2324            create_inbound_group_session_with_visibility(
2325                &alice,
2326                room1_id,
2327                &SessionKey::from_base64(session_key3).unwrap(),
2328                false,
2329            ),
2330            create_inbound_group_session_with_visibility(
2331                &alice,
2332                room2_id,
2333                &SessionKey::from_base64(session_key4).unwrap(),
2334                true,
2335            ),
2336        ];
2337        bob.store().save_inbound_group_sessions(&sessions).await.unwrap();
2338
2339        // When I build the bundle
2340        let mut bundle = bob.store().build_room_key_bundle(room1_id).await.unwrap();
2341
2342        // Then the bundle matches the snapshot.
2343
2344        // We sort the sessions in the bundle, so that the snapshot is stable.
2345        bundle.room_keys.sort_by_key(|session| session.session_id.clone());
2346
2347        // We substitute the algorithm, since this changes based on feature flags.
2348        let algorithm = if cfg!(feature = "experimental-algorithms") {
2349            "m.megolm.v2.aes-sha2"
2350        } else {
2351            "m.megolm.v1.aes-sha2"
2352        };
2353        let map_algorithm = move |value: Content, _path: ContentPath<'_>| {
2354            assert_eq!(value.as_str().unwrap(), algorithm);
2355            "[algorithm]"
2356        };
2357
2358        // We also substitute alice's keys in the snapshot with placeholders
2359        let alice_curve_key = alice.identity_keys().curve25519.to_base64();
2360        let map_alice_curve_key = move |value: Content, _path: ContentPath<'_>| {
2361            assert_eq!(value.as_str().unwrap(), alice_curve_key);
2362            "[alice curve key]"
2363        };
2364        let alice_ed25519_key = alice.identity_keys().ed25519.to_base64();
2365        let map_alice_ed25519_key = move |value: Content, _path: ContentPath<'_>| {
2366            assert_eq!(value.as_str().unwrap(), alice_ed25519_key);
2367            "[alice ed25519 key]"
2368        };
2369
2370        insta::with_settings!({ sort_maps => true }, {
2371            assert_json_snapshot!(bundle, {
2372                ".withheld[].algorithm" => insta::dynamic_redaction(map_algorithm),
2373                ".room_keys[].algorithm" => insta::dynamic_redaction(map_algorithm),
2374                ".room_keys[].sender_key" => insta::dynamic_redaction(map_alice_curve_key.clone()),
2375                ".withheld[].sender_key" => insta::dynamic_redaction(map_alice_curve_key),
2376                ".room_keys[].sender_claimed_keys.ed25519" => insta::dynamic_redaction(map_alice_ed25519_key),
2377            });
2378        });
2379    }
2380
2381    #[async_test]
2382    async fn test_receive_room_key_bundle() {
2383        let alice = OlmMachine::new(user_id!("@a:s.co"), device_id!("ALICE")).await;
2384        let alice_key = alice.identity_keys().curve25519;
2385        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("BOB")).await;
2386
2387        let room_id = room_id!("!room1:localhost");
2388
2389        let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw";
2390        let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ";
2391
2392        let sessions = [
2393            create_inbound_group_session_with_visibility(
2394                &alice,
2395                room_id,
2396                &SessionKey::from_base64(session_key1).unwrap(),
2397                true,
2398            ),
2399            create_inbound_group_session_with_visibility(
2400                &alice,
2401                room_id,
2402                &SessionKey::from_base64(session_key2).unwrap(),
2403                false,
2404            ),
2405        ];
2406
2407        alice.store().save_inbound_group_sessions(&sessions).await.unwrap();
2408        let bundle = alice.store().build_room_key_bundle(room_id).await.unwrap();
2409
2410        bob.store()
2411            .receive_room_key_bundle(
2412                &StoredRoomKeyBundleData {
2413                    sender_user: alice.user_id().to_owned(),
2414                    sender_key: alice_key,
2415                    sender_data: SenderData::sender_verified(
2416                        alice.user_id(),
2417                        device_id!("ALICE"),
2418                        alice.identity_keys().ed25519,
2419                    ),
2420
2421                    bundle_data: RoomKeyBundleContent {
2422                        room_id: room_id.to_owned(),
2423                        // This isn't used at all in the method call, so we can fill it with
2424                        // garbage.
2425                        file: EncryptedFile::new(
2426                            owned_mxc_uri!("mxc://example.com/0"),
2427                            V2EncryptedFileInfo::encode([0; 32], [0; 16]).into(),
2428                            EncryptedFileHashes::with_sha256([0; 32]),
2429                        ),
2430                    },
2431                },
2432                bundle,
2433                |_, _| {},
2434            )
2435            .await
2436            .unwrap();
2437
2438        // The room key should be imported successfully
2439        let imported_sessions =
2440            bob.store().get_inbound_group_sessions_by_room_id(room_id).await.unwrap();
2441
2442        assert_eq!(imported_sessions.len(), 1);
2443        assert_eq!(imported_sessions[0].room_id(), room_id);
2444
2445        // The session forwarder data should be set correctly.
2446        assert_eq!(
2447            imported_sessions[0]
2448                .forwarder_data
2449                .as_ref()
2450                .expect("Session should contain forwarder data.")
2451                .user_id(),
2452            alice.user_id()
2453        );
2454
2455        assert_matches!(
2456            bob.store()
2457                .get_withheld_info(room_id, sessions[1].session_id())
2458                .await
2459                .unwrap()
2460                .expect("Withheld info should be present in the store."),
2461            RoomKeyWithheldEntry {
2462                #[cfg(not(feature = "experimental-algorithms"))]
2463                content: RoomKeyWithheldContent::MegolmV1AesSha2(
2464                    MegolmV1AesSha2WithheldContent::HistoryNotShared(_)
2465                ),
2466                #[cfg(feature = "experimental-algorithms")]
2467                content: RoomKeyWithheldContent::MegolmV2AesSha2(
2468                    MegolmV1AesSha2WithheldContent::HistoryNotShared(_)
2469                ),
2470                ..
2471            }
2472        );
2473    }
2474
2475    /// Tests that the new store format introduced in [#5737][#5737] does not
2476    /// conflict with items already in the store that were serialised with the
2477    /// older format.
2478    ///
2479    /// [#5737]: https://github.com/matrix-org/matrix-rust-sdk/pull/5737
2480    #[async_test]
2481    async fn test_deserialize_room_key_withheld_entry_from_to_device_event() {
2482        let entry: RoomKeyWithheldEntry = serde_json::from_value(json!(
2483            {
2484              "content": {
2485                "algorithm": "m.megolm.v1.aes-sha2",
2486                "code": "m.unauthorised",
2487                "from_device": "ALICE",
2488                "reason": "You are not authorised to read the message.",
2489                "room_id": "!roomid:s.co",
2490                "sender_key": "7hIcOrEroXYdzjtCBvBjUiqvT0Me7g+ymeXqoc65RS0",
2491                "session_id": "session123"
2492              },
2493              "sender": "@alice:s.co",
2494              "type": "m.room_key.withheld"
2495            }
2496        ))
2497        .unwrap();
2498
2499        assert_matches!(
2500            entry,
2501            RoomKeyWithheldEntry {
2502                sender,
2503                content: RoomKeyWithheldContent::MegolmV1AesSha2(
2504                    MegolmV1AesSha2WithheldContent::Unauthorised(withheld_content,)
2505                ),
2506            }
2507        );
2508
2509        assert_eq!(sender, "@alice:s.co");
2510        assert_eq!(withheld_content.room_id, "!roomid:s.co");
2511        assert_eq!(withheld_content.session_id, "session123");
2512        assert_eq!(
2513            withheld_content.sender_key.to_base64(),
2514            "7hIcOrEroXYdzjtCBvBjUiqvT0Me7g+ymeXqoc65RS0"
2515        );
2516        assert_eq!(withheld_content.from_device, Some(owned_device_id!("ALICE")));
2517    }
2518
2519    /// Create an inbound Megolm session for the given room.
2520    ///
2521    /// `olm_machine` is used to set the `sender_key` and `signing_key`
2522    /// fields of the resultant session.
2523    ///
2524    /// The encryption algorithm used for the session depends on the
2525    /// `experimental-algorithms` feature flag:
2526    ///
2527    /// - When not set, the session uses `m.megolm.v1.aes-sha2`.
2528    /// - When set, the session uses `m.megolm.v2.aes-sha2`.
2529    fn create_inbound_group_session_with_visibility(
2530        olm_machine: &OlmMachine,
2531        room_id: &RoomId,
2532        session_key: &SessionKey,
2533        shared_history: bool,
2534    ) -> InboundGroupSession {
2535        let identity_keys = &olm_machine.store().static_account().identity_keys;
2536        InboundGroupSession::new(
2537            identity_keys.curve25519,
2538            identity_keys.ed25519,
2539            room_id,
2540            session_key,
2541            SenderData::unknown(),
2542            None,
2543            #[cfg(not(feature = "experimental-algorithms"))]
2544            EventEncryptionAlgorithm::MegolmV1AesSha2,
2545            #[cfg(feature = "experimental-algorithms")]
2546            EventEncryptionAlgorithm::MegolmV2AesSha2,
2547            None,
2548            shared_history,
2549        )
2550        .unwrap()
2551    }
2552}