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