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 futures_util::{pin_mut, StreamExt};
1304    /// # let machine: OlmMachine = unimplemented!();
1305    /// # futures_executor::block_on(async {
1306    /// let identities_stream = machine.store().user_identities_stream();
1307    /// pin_mut!(identities_stream);
1308    ///
1309    /// for identity_updates in identities_stream.next().await {
1310    ///     for (_, identity) in identity_updates.new {
1311    ///         println!("A new identity has been added {}", identity.user_id());
1312    ///     }
1313    /// }
1314    /// # });
1315    /// ```
1316    pub fn user_identities_stream(&self) -> impl Stream<Item = IdentityUpdates> + use<> {
1317        let verification_machine = self.inner.verification_machine.to_owned();
1318
1319        let this = self.clone();
1320        self.inner.store.identities_stream().map(move |(own_identity, identities, _)| {
1321            let (new_identities, changed_identities, unchanged_identities) = identities.into_maps();
1322
1323            let map_identity = |(user_id, identity)| {
1324                (
1325                    user_id,
1326                    UserIdentity::new(
1327                        this.clone(),
1328                        identity,
1329                        verification_machine.to_owned(),
1330                        own_identity.to_owned(),
1331                    ),
1332                )
1333            };
1334
1335            let new = new_identities.into_iter().map(map_identity).collect();
1336            let changed = changed_identities.into_iter().map(map_identity).collect();
1337            let unchanged = unchanged_identities.into_iter().map(map_identity).collect();
1338
1339            IdentityUpdates { new, changed, unchanged }
1340        })
1341    }
1342
1343    /// Returns a stream of device updates, allowing users to listen for
1344    /// notifications about new or changed devices.
1345    ///
1346    /// The stream produced by this method emits updates whenever a new device
1347    /// is discovered or when an existing device's information is changed. Users
1348    /// can subscribe to this stream and receive updates in real-time.
1349    ///
1350    /// Caution: the returned stream will never terminate, and it holds a
1351    /// reference to the [`CryptoStore`]. Listeners should be careful to avoid
1352    /// resource leaks.
1353    ///
1354    /// # Examples
1355    ///
1356    /// ```no_run
1357    /// # use matrix_sdk_crypto::OlmMachine;
1358    /// # use ruma::{device_id, user_id};
1359    /// # use futures_util::{pin_mut, StreamExt};
1360    /// # let machine: OlmMachine = unimplemented!();
1361    /// # futures_executor::block_on(async {
1362    /// let devices_stream = machine.store().devices_stream();
1363    /// pin_mut!(devices_stream);
1364    ///
1365    /// for device_updates in devices_stream.next().await {
1366    ///     if let Some(user_devices) = device_updates.new.get(machine.user_id()) {
1367    ///         for device in user_devices.values() {
1368    ///             println!("A new device has been added {}", device.device_id());
1369    ///         }
1370    ///     }
1371    /// }
1372    /// # });
1373    /// ```
1374    pub fn devices_stream(&self) -> impl Stream<Item = DeviceUpdates> + use<> {
1375        let verification_machine = self.inner.verification_machine.to_owned();
1376        #[cfg(feature = "experimental-x509-identity-verification")]
1377        let x509_verifier = self.x509_verifier().cloned();
1378
1379        self.inner.store.identities_stream().map(move |(own_identity, identities, devices)| {
1380            collect_device_updates(
1381                verification_machine.to_owned(),
1382                own_identity,
1383                identities,
1384                devices,
1385                #[cfg(feature = "experimental-x509-identity-verification")]
1386                x509_verifier.to_owned(),
1387            )
1388        })
1389    }
1390
1391    /// Returns a [`Stream`] of user identity and device updates
1392    ///
1393    /// The stream returned by this method returns the same data as
1394    /// [`Store::user_identities_stream`] and [`Store::devices_stream`] but does
1395    /// not include references to the `VerificationMachine`. It is therefore a
1396    /// lower-level view on that data.
1397    ///
1398    /// The stream will terminate once all references to the underlying
1399    /// `CryptoStoreWrapper` are dropped.
1400    pub fn identities_stream_raw(
1401        &self,
1402    ) -> impl Stream<Item = (IdentityChanges, DeviceChanges)> + use<> {
1403        self.inner.store.identities_stream().map(|(_, identities, devices)| (identities, devices))
1404    }
1405
1406    /// Creates a [`CrossProcessLock`] for this store, that will contain the
1407    /// given key and value when hold.
1408    pub fn create_store_lock(
1409        &self,
1410        lock_key: String,
1411        config: CrossProcessLockConfig,
1412    ) -> CrossProcessLock<LockableCryptoStore> {
1413        self.inner.store.create_store_lock(lock_key, config)
1414    }
1415
1416    /// Receive notifications of gossipped secrets being received and stored in
1417    /// the secret inbox as a [`Stream`].
1418    ///
1419    /// The gossipped secrets are received using the `m.secret.send` event type
1420    /// and are guaranteed to have been received over a 1-to-1 Olm [`Session`]
1421    /// from a verified [`Device`].
1422    ///
1423    /// The [`GossippedSecret`] can also be later found in the secret inbox and
1424    /// retrieved using the [`CryptoStore::get_secrets_from_inbox()`] method.
1425    ///
1426    /// After a suitable secret of a certain type has been found it can be
1427    /// removed from the store using the
1428    /// [`CryptoStore::delete_secrets_from_inbox()`] method.
1429    ///
1430    /// The only secret this will currently broadcast is the
1431    /// `m.megolm_backup.v1`.
1432    ///
1433    /// If the reader of the stream lags too far behind, a warning will be
1434    /// logged and items will be dropped.
1435    ///
1436    /// # Examples
1437    ///
1438    /// ```no_run
1439    /// # use matrix_sdk_crypto::OlmMachine;
1440    /// # use ruma::{device_id, owned_user_id};
1441    /// # use futures_util::{pin_mut, StreamExt};
1442    /// # let alice = owned_user_id!("@alice:example.org");
1443    /// # futures_executor::block_on(async {
1444    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1445    ///
1446    /// let secret_stream = machine.store().secrets_stream();
1447    /// pin_mut!(secret_stream);
1448    ///
1449    /// for secret in secret_stream.next().await {
1450    ///     // Accept the secret if it's valid, then delete all the secrets of this type.
1451    ///     machine.store().delete_secrets_from_inbox(&secret.secret_name);
1452    /// }
1453    /// # });
1454    /// ```
1455    pub fn secrets_stream(&self) -> impl Stream<Item = SecretsInboxItem> + use<> {
1456        self.inner.store.secrets_stream()
1457    }
1458
1459    /// Receive notifications of historic room key bundles as a [`Stream`].
1460    ///
1461    /// Historic room key bundles are defined in
1462    /// [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
1463    ///
1464    /// Each time a historic room key bundle was received, an update will be
1465    /// sent to the stream. This stream can be used to accept historic room key
1466    /// bundles that arrive out of order, i.e. the bundle arrives after the user
1467    /// has already accepted a room invitation.
1468    ///
1469    /// # Examples
1470    ///
1471    /// ```no_run
1472    /// # use matrix_sdk_crypto::{
1473    /// #    OlmMachine,
1474    /// #    store::types::StoredRoomKeyBundleData,
1475    /// #    types::room_history::RoomKeyBundle
1476    /// # };
1477    /// # use ruma::{device_id, owned_user_id};
1478    /// # use futures_util::{pin_mut, StreamExt};
1479    /// # let alice = owned_user_id!("@alice:example.org");
1480    /// # async {
1481    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1482    /// let bundle_stream = machine.store().historic_room_key_stream();
1483    /// pin_mut!(bundle_stream);
1484    ///
1485    /// while let Some(bundle_info) = bundle_stream.next().await {
1486    ///     // Try to find the bundle content in the store and if it's valid accept it.
1487    ///     if let Some(bundle_data) = machine.store().get_received_room_key_bundle_data(&bundle_info.room_id, &bundle_info.sender).await? {
1488    ///         // Download the bundle now and import it.
1489    ///         let bundle: RoomKeyBundle = todo!("Download the bundle");
1490    ///         machine.store().receive_room_key_bundle(
1491    ///             &bundle_data,
1492    ///             bundle,
1493    ///             |_, _| {},
1494    ///         ).await?;
1495    ///     }
1496    /// }
1497    /// # anyhow::Ok(()) };
1498    /// ```
1499    pub fn historic_room_key_stream(&self) -> impl Stream<Item = RoomKeyBundleInfo> + use<> {
1500        self.inner.store.historic_room_key_stream()
1501    }
1502
1503    /// Import the given room keys into the store.
1504    ///
1505    /// # Arguments
1506    ///
1507    /// - `exported_keys` - The keys to be imported.
1508    /// - `from_backup_version` - If the keys came from key backup, the key
1509    ///   backup version. This will cause the keys to be marked as already
1510    ///   backed up, and therefore not requiring another backup.
1511    /// - `progress_listener` - Callback which will be called after each key is
1512    ///   processed. Called with arguments `(processed, total)` where
1513    ///   `processed` is the number of keys processed so far, and `total` is the
1514    ///   total number of keys (i.e., `exported_keys.len()`).
1515    pub async fn import_room_keys(
1516        &self,
1517        exported_keys: Vec<ExportedRoomKey>,
1518        from_backup_version: Option<&str>,
1519        progress_listener: impl Fn(usize, usize),
1520    ) -> Result<RoomKeyImportResult> {
1521        let exported_keys = exported_keys.iter().filter_map(|key| {
1522            key.try_into()
1523                .map_err(|e| {
1524                    warn!(
1525                        sender_key = key.sender_key().to_base64(),
1526                        room_id = ?key.room_id(),
1527                        session_id = key.session_id(),
1528                        error = ?e,
1529                        "Couldn't import a room key from a file export."
1530                    );
1531                })
1532                .ok()
1533        });
1534        self.import_sessions_impl(exported_keys, from_backup_version, progress_listener).await
1535    }
1536
1537    /// Import the given room keys into our store.
1538    ///
1539    /// # Arguments
1540    ///
1541    /// - `exported_keys` - A list of previously exported keys that should be
1542    ///   imported into our store. If we already have a better version of a key
1543    ///   the key will _not_ be imported.
1544    ///
1545    /// Returns a tuple of numbers that represent the number of sessions that
1546    /// were imported and the total number of sessions that were found in the
1547    /// key export.
1548    ///
1549    /// # Examples
1550    ///
1551    /// ```no_run
1552    /// # use std::io::Cursor;
1553    /// # use matrix_sdk_crypto::{OlmMachine, decrypt_room_key_export};
1554    /// # use ruma::{device_id, user_id};
1555    /// # let alice = user_id!("@alice:example.org");
1556    /// # async {
1557    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1558    /// # let export = Cursor::new("".to_owned());
1559    /// let exported_keys = decrypt_room_key_export(export, "1234").unwrap();
1560    /// machine.store().import_exported_room_keys(exported_keys, |_, _| {}).await.unwrap();
1561    /// # };
1562    /// ```
1563    pub async fn import_exported_room_keys(
1564        &self,
1565        exported_keys: Vec<ExportedRoomKey>,
1566        progress_listener: impl Fn(usize, usize),
1567    ) -> Result<RoomKeyImportResult> {
1568        self.import_room_keys(exported_keys, None, progress_listener).await
1569    }
1570
1571    async fn import_sessions_impl(
1572        &self,
1573        sessions: impl Iterator<Item = InboundGroupSession>,
1574        from_backup_version: Option<&str>,
1575        progress_listener: impl Fn(usize, usize),
1576    ) -> Result<RoomKeyImportResult> {
1577        let sessions: Vec<_> = sessions.collect();
1578        let mut imported_sessions = Vec::new();
1579
1580        let total_count = sessions.len();
1581        let mut keys = BTreeMap::new();
1582
1583        for (i, session) in sessions.into_iter().enumerate() {
1584            // Only import the session if we didn't have this session or if it's
1585            // a better version of the same session.
1586            if let Some(merged) = self.merge_received_group_session(session).await? {
1587                if from_backup_version.is_some() {
1588                    merged.mark_as_backed_up();
1589                }
1590
1591                keys.entry(merged.room_id().to_owned())
1592                    .or_insert_with(BTreeMap::new)
1593                    .entry(merged.sender_key().to_base64())
1594                    .or_insert_with(BTreeSet::new)
1595                    .insert(merged.session_id().to_owned());
1596
1597                imported_sessions.push(merged);
1598            }
1599
1600            progress_listener(i, total_count);
1601        }
1602
1603        let imported_count = imported_sessions.len();
1604
1605        self.inner
1606            .store
1607            .save_inbound_group_sessions(imported_sessions, from_backup_version)
1608            .await?;
1609
1610        info!(total_count, imported_count, room_keys = ?keys, "Successfully imported room keys");
1611
1612        Ok(RoomKeyImportResult::new(imported_count, total_count, keys))
1613    }
1614
1615    pub(crate) fn crypto_store(&self) -> Arc<CryptoStoreWrapper> {
1616        self.inner.store.clone()
1617    }
1618
1619    /// Export the keys that match the given predicate.
1620    ///
1621    /// # Arguments
1622    ///
1623    /// - `predicate` - A closure that will be called for every known
1624    ///   `InboundGroupSession`, which represents a room key. If the closure
1625    ///   returns `true` the `InboundGroupSession` will be included in the
1626    ///   export, if the closure returns `false` it will not be included.
1627    ///
1628    /// # Examples
1629    ///
1630    /// ```no_run
1631    /// # use matrix_sdk_crypto::{OlmMachine, encrypt_room_key_export};
1632    /// # use ruma::{device_id, user_id, room_id};
1633    /// # let alice = user_id!("@alice:example.org");
1634    /// # async {
1635    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1636    /// let room_id = room_id!("!test:localhost");
1637    /// let exported_keys = machine.store().export_room_keys(|s| s.room_id() == room_id).await.unwrap();
1638    /// let encrypted_export = encrypt_room_key_export(&exported_keys, "1234", 1);
1639    /// # };
1640    /// ```
1641    pub async fn export_room_keys(
1642        &self,
1643        predicate: impl FnMut(&InboundGroupSession) -> bool,
1644    ) -> Result<Vec<ExportedRoomKey>> {
1645        let mut exported = Vec::new();
1646
1647        let mut sessions = self.get_inbound_group_sessions().await?;
1648        sessions.retain(predicate);
1649
1650        for session in sessions {
1651            let export = session.export().await;
1652            exported.push(export);
1653        }
1654
1655        Ok(exported)
1656    }
1657
1658    /// Export room keys matching a predicate, providing them as an async
1659    /// `Stream`.
1660    ///
1661    /// # Arguments
1662    ///
1663    /// - `predicate` - A closure that will be called for every known
1664    ///   `InboundGroupSession`, which represents a room key. If the closure
1665    ///   returns `true` the `InboundGroupSession` will be included in the
1666    ///   export, if the closure returns `false` it will not be included.
1667    ///
1668    /// # Examples
1669    ///
1670    /// ```no_run
1671    /// use std::pin::pin;
1672    ///
1673    /// use matrix_sdk_crypto::{OlmMachine, olm::ExportedRoomKey};
1674    /// use ruma::{device_id, room_id, user_id};
1675    /// use tokio_stream::StreamExt;
1676    /// # async {
1677    /// let alice = user_id!("@alice:example.org");
1678    /// let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1679    /// let room_id = room_id!("!test:localhost");
1680    /// let mut keys = pin!(
1681    ///     machine
1682    ///         .store()
1683    ///         .export_room_keys_stream(|s| s.room_id() == room_id)
1684    ///         .await
1685    ///         .unwrap()
1686    /// );
1687    /// while let Some(key) = keys.next().await {
1688    ///     println!("{}", key.room_id);
1689    /// }
1690    /// # };
1691    /// ```
1692    pub async fn export_room_keys_stream(
1693        &self,
1694        predicate: impl FnMut(&InboundGroupSession) -> bool,
1695    ) -> Result<impl Stream<Item = ExportedRoomKey>> {
1696        // TODO: if/when there is a get_inbound_group_sessions_stream, use that
1697        // here.
1698        let sessions = self.get_inbound_group_sessions().await?;
1699        Ok(futures_util::stream::iter(sessions.into_iter().filter(predicate))
1700            .then(|session| async move { session.export().await }))
1701    }
1702
1703    /// Assemble a room key bundle for sharing encrypted history, as per
1704    /// [MSC4268].
1705    ///
1706    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1707    pub async fn build_room_key_bundle(
1708        &self,
1709        room_id: &RoomId,
1710    ) -> std::result::Result<RoomKeyBundle, CryptoStoreError> {
1711        let sessions = self.get_inbound_group_sessions_by_room_id(room_id).await?;
1712
1713        let mut bundle = RoomKeyBundle::default();
1714        for session in sessions {
1715            if session.shared_history() {
1716                bundle.room_keys.push(session.export().await.into());
1717            } else {
1718                bundle.withheld.push(RoomKeyWithheldContent::new(
1719                    session.algorithm().to_owned(),
1720                    WithheldCode::HistoryNotShared,
1721                    session.room_id().to_owned(),
1722                    session.session_id().to_owned(),
1723                    session.sender_key().to_owned(),
1724                    self.device_id().to_owned(),
1725                ));
1726            }
1727        }
1728
1729        // If we received a key bundle ourselves, in which one or more sessions
1730        // was marked as "history not shared", pass that on to the new user.
1731        let withhelds = self.get_withheld_sessions_by_room_id(room_id).await?;
1732        for withheld in withhelds {
1733            if withheld.content.withheld_code() == WithheldCode::HistoryNotShared {
1734                bundle.withheld.push(withheld.content);
1735            }
1736        }
1737
1738        Ok(bundle)
1739    }
1740
1741    /// Import the contents of a downloaded and decrypted [MSC4268] key bundle.
1742    ///
1743    /// # Arguments
1744    ///
1745    /// - `bundle_info` - The [`StoredRoomKeyBundleData`] of the bundle that is
1746    ///   being received.
1747    /// - `bundle` - The decrypted and deserialized bundle itself.
1748    ///
1749    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1750    #[instrument(skip(self, bundle, progress_listener), fields(bundle_size = bundle.room_keys.len(), sender_data))]
1751    pub async fn receive_room_key_bundle(
1752        &self,
1753        bundle_info: &StoredRoomKeyBundleData,
1754        bundle: RoomKeyBundle,
1755        progress_listener: impl Fn(usize, usize),
1756    ) -> Result<(), CryptoStoreError> {
1757        let sender_data = if bundle_info.sender_data.should_recalculate() {
1758            let device = self
1759                .get_device_from_curve_key(&bundle_info.sender_user, bundle_info.sender_key)
1760                .await?;
1761
1762            device
1763                .as_ref()
1764                .map(SenderData::from_device)
1765                .unwrap_or_else(|| bundle_info.sender_data.clone())
1766        } else {
1767            bundle_info.sender_data.clone()
1768        };
1769
1770        tracing::Span::current().record("sender_data", tracing::field::debug(&sender_data));
1771
1772        // The sender's device must be either `SenderData::SenderUnverified`
1773        // (i.e., TOFU-trusted) or `SenderData::SenderVerified` (i.e., fully
1774        // verified via user verification and cross-signing).
1775        let Ok(forwarder_data) = (&sender_data).try_into() else {
1776            warn!(
1777                "Not accepting a historic room key bundle due to insufficient trust in the sender"
1778            );
1779            return Ok(());
1780        };
1781
1782        self.import_room_key_bundle_sessions(
1783            bundle_info,
1784            &bundle,
1785            &forwarder_data,
1786            progress_listener,
1787        )
1788        .await?;
1789        self.import_room_key_bundle_withheld_info(bundle_info, &bundle).await?;
1790
1791        Ok(())
1792    }
1793
1794    async fn import_room_key_bundle_sessions(
1795        &self,
1796        bundle_info: &StoredRoomKeyBundleData,
1797        bundle: &RoomKeyBundle,
1798        forwarder_data: &ForwarderData,
1799        progress_listener: impl Fn(usize, usize),
1800    ) -> Result<(), CryptoStoreError> {
1801        let (good, bad): (Vec<_>, Vec<_>) = bundle.room_keys.iter().partition_map(|key| {
1802            if key.room_id != bundle_info.bundle_data.room_id {
1803                trace!("Ignoring key for incorrect room {} in bundle", key.room_id);
1804                Either::Right(key)
1805            } else {
1806                Either::Left(key)
1807            }
1808        });
1809
1810        match (bad.is_empty(), good.is_empty()) {
1811            // Case 1: Completely empty bundle.
1812            (true, true) => {
1813                warn!("Received a completely empty room key bundle");
1814            }
1815
1816            // Case 2: A bundle for the wrong room.
1817            (false, true) => {
1818                let bad_keys: Vec<_> =
1819                    bad.iter().map(|&key| (&key.room_id, &key.session_id)).collect();
1820
1821                warn!(
1822                    ?bad_keys,
1823                    "Received a room key bundle for the wrong room, ignoring all room keys from the bundle"
1824                );
1825            }
1826
1827            // Case 3: A bundle containing useful room keys.
1828            (_, false) => {
1829                // We have at least some good keys, if we also have some bad
1830                // ones let's mention that here.
1831                if !bad.is_empty() {
1832                    warn!(
1833                        bad_key_count = bad.len(),
1834                        "The room key bundle contained some room keys \
1835                         that were meant for a different room"
1836                    );
1837                }
1838
1839                let keys = good.iter().filter_map(|key| {
1840                    key.try_into_inbound_group_session(forwarder_data)
1841                        .map_err(|e| {
1842                            warn!(
1843                                sender_key = ?key.sender_key().to_base64(),
1844                                room_id = ?key.room_id(),
1845                                session_id = key.session_id(),
1846                                error = ?e,
1847                                "Couldn't import a room key from a key bundle."
1848                            );
1849                        })
1850                        .ok()
1851                });
1852
1853                self.import_sessions_impl(keys, None, progress_listener).await?;
1854            }
1855        }
1856
1857        Ok(())
1858    }
1859
1860    async fn import_room_key_bundle_withheld_info(
1861        &self,
1862        bundle_info: &StoredRoomKeyBundleData,
1863        bundle: &RoomKeyBundle,
1864    ) -> Result<(), CryptoStoreError> {
1865        let mut session_id_to_withheld_code_map = BTreeMap::new();
1866
1867        let mut changes = Changes::default();
1868        for withheld in &bundle.withheld {
1869            let (room_id, session_id) = match withheld {
1870                RoomKeyWithheldContent::MegolmV1AesSha2(c) => match (c.room_id(), c.session_id()) {
1871                    (Some(room_id), Some(session_id)) => (room_id, session_id),
1872                    _ => continue,
1873                },
1874                #[cfg(feature = "experimental-algorithms")]
1875                RoomKeyWithheldContent::MegolmV2AesSha2(c) => match (c.room_id(), c.session_id()) {
1876                    (Some(room_id), Some(session_id)) => (room_id, session_id),
1877                    _ => continue,
1878                },
1879                RoomKeyWithheldContent::Unknown(_) => continue,
1880            };
1881
1882            if room_id != bundle_info.bundle_data.room_id {
1883                trace!("Ignoring withheld info for incorrect room {} in bundle", room_id);
1884                continue;
1885            }
1886
1887            changes.withheld_session_info.entry(room_id.to_owned()).or_default().insert(
1888                session_id.to_owned(),
1889                RoomKeyWithheldEntry {
1890                    sender: bundle_info.sender_user.clone(),
1891                    content: withheld.to_owned(),
1892                },
1893            );
1894            session_id_to_withheld_code_map.insert(session_id, withheld.withheld_code());
1895        }
1896
1897        self.save_changes(changes).await?;
1898
1899        info!(
1900            room_id = ?bundle_info.bundle_data.room_id,
1901            ?session_id_to_withheld_code_map,
1902            "Successfully imported withheld info from room key bundle",
1903        );
1904
1905        Ok(())
1906    }
1907
1908    /// Store the fact that we have accepted an invite for a given room on this
1909    /// client, so should accept an [MSC4268] key bundle if one arrives soon.
1910    ///
1911    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1912    pub async fn store_room_pending_key_bundle(
1913        &self,
1914        room_id: &RoomId,
1915        inviter: &UserId,
1916    ) -> Result<(), CryptoStoreError> {
1917        let invite_accepted_at = MilliSecondsSinceUnixEpoch::now();
1918        self.save_changes(Changes {
1919            rooms_pending_key_bundle: HashMap::from([(
1920                room_id.to_owned(),
1921                Some(RoomPendingKeyBundleDetails {
1922                    room_id: room_id.to_owned(),
1923                    invite_accepted_at,
1924                    inviter: inviter.to_owned(),
1925                }),
1926            )]),
1927            ..Default::default()
1928        })
1929        .await?;
1930
1931        Ok(())
1932    }
1933
1934    /// Clear the record of accepting an invite for a specific room.
1935    ///
1936    /// The counterpart of [`Store::store_room_pending_key_bundle`].
1937    pub async fn clear_room_pending_key_bundle(&self, room_id: &RoomId) -> Result<()> {
1938        self.save_changes(Changes {
1939            rooms_pending_key_bundle: HashMap::from([(room_id.to_owned(), None)]),
1940            ..Default::default()
1941        })
1942        .await
1943    }
1944}
1945
1946impl Deref for Store {
1947    type Target = DynCryptoStore;
1948
1949    fn deref(&self) -> &Self::Target {
1950        self.inner.store.deref().deref()
1951    }
1952}
1953
1954/// A crypto store that implements primitives for cross-process locking.
1955#[derive(Clone, Debug)]
1956pub struct LockableCryptoStore(Arc<dyn CryptoStore<Error = CryptoStoreError>>);
1957
1958impl matrix_sdk_common::cross_process_lock::TryLock for LockableCryptoStore {
1959    type LockError = CryptoStoreError;
1960
1961    async fn try_lock(
1962        &self,
1963        lease_duration_ms: u32,
1964        key: &str,
1965        holder: &str,
1966    ) -> std::result::Result<Option<CrossProcessLockGeneration>, Self::LockError> {
1967        self.0.try_take_leased_lock(lease_duration_ms, key, holder).await
1968    }
1969}
1970
1971#[cfg(test)]
1972mod tests {
1973    use std::{assert_matches, collections::BTreeMap, pin::pin};
1974
1975    use futures_util::StreamExt;
1976    use insta::{_macro_support::Content, assert_json_snapshot, internals::ContentPath};
1977    use matrix_sdk_test::async_test;
1978    use rand::RngExt;
1979    use ruma::{
1980        RoomId, device_id,
1981        events::room::{EncryptedFile, EncryptedFileHashes, V2EncryptedFileInfo},
1982        owned_device_id, owned_mxc_uri, room_id, user_id,
1983    };
1984    use serde_json::json;
1985    use strass::assert_let;
1986    use vodozemac::{Ed25519Keypair, megolm::SessionKey};
1987
1988    use crate::{
1989        Account, OlmMachine,
1990        machine::test_helpers::get_machine_pair,
1991        olm::{InboundGroupSession, SenderData},
1992        store::types::{DehydratedDeviceKey, RoomKeyWithheldEntry, StoredRoomKeyBundleData},
1993        types::{
1994            EventEncryptionAlgorithm,
1995            events::{
1996                room_key_bundle::RoomKeyBundleContent,
1997                room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent},
1998            },
1999        },
2000    };
2001
2002    #[async_test]
2003    async fn test_merge_received_group_session() {
2004        let alice_account = Account::with_device_id(user_id!("@a:s.co"), device_id!("ABC"));
2005        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("DEF")).await;
2006
2007        let room_id = room_id!("!test:localhost");
2008
2009        let megolm_signing_key = Ed25519Keypair::new();
2010        let inbound = make_inbound_group_session(&alice_account, &megolm_signing_key, room_id);
2011
2012        // Bob already knows about the session, at index 5, with the device
2013        // keys.
2014        let mut inbound_at_index_5 =
2015            InboundGroupSession::from_export(&inbound.export_at_index(5).await).unwrap();
2016        inbound_at_index_5.sender_data = inbound.sender_data.clone();
2017        bob.store().save_inbound_group_sessions(&[inbound_at_index_5.clone()]).await.unwrap();
2018
2019        // No changes if we get a disconnected session.
2020        let disconnected = make_inbound_group_session(&alice_account, &megolm_signing_key, room_id);
2021        assert_eq!(bob.store().merge_received_group_session(disconnected).await.unwrap(), None);
2022
2023        // No changes needed when we receive a worse copy of the session
2024        let mut worse =
2025            InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
2026        worse.sender_data = inbound.sender_data.clone();
2027        assert_eq!(bob.store().merge_received_group_session(worse).await.unwrap(), None);
2028
2029        // Nor when we receive an exact copy of what we already have
2030        let mut copy = InboundGroupSession::from_pickle(inbound_at_index_5.pickle().await).unwrap();
2031        copy.sender_data = inbound.sender_data.clone();
2032        assert_eq!(bob.store().merge_received_group_session(copy).await.unwrap(), None);
2033
2034        // But when we receive a better copy of the session, we should get it
2035        // back
2036        let mut better =
2037            InboundGroupSession::from_export(&inbound.export_at_index(0).await).unwrap();
2038        better.sender_data = inbound.sender_data.clone();
2039        assert_let!(Some(update) = bob.store().merge_received_group_session(better).await.unwrap());
2040        assert_eq!(update.first_known_index(), 0);
2041
2042        // A worse copy of the ratchet, but better trust data
2043        {
2044            let mut worse_ratchet_better_trust =
2045                InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
2046            let updated_sender_data = SenderData::sender_verified(
2047                alice_account.user_id(),
2048                alice_account.device_id(),
2049                Ed25519Keypair::new().public_key(),
2050            );
2051            worse_ratchet_better_trust.sender_data = updated_sender_data.clone();
2052            assert_let!(
2053                Some(update) = bob
2054                    .store()
2055                    .merge_received_group_session(worse_ratchet_better_trust)
2056                    .await
2057                    .unwrap()
2058            );
2059            assert_eq!(update.sender_data, updated_sender_data);
2060            assert_eq!(update.first_known_index(), 5);
2061            assert_eq!(
2062                update.export_at_index(0).await.session_key.to_bytes(),
2063                inbound.export_at_index(5).await.session_key.to_bytes()
2064            );
2065        }
2066
2067        // A better copy of the ratchet, but worse trust data
2068        {
2069            let mut better_ratchet_worse_trust =
2070                InboundGroupSession::from_export(&inbound.export_at_index(0).await).unwrap();
2071            let updated_sender_data = SenderData::unknown();
2072            better_ratchet_worse_trust.sender_data = updated_sender_data.clone();
2073            assert_let!(
2074                Some(update) = bob
2075                    .store()
2076                    .merge_received_group_session(better_ratchet_worse_trust)
2077                    .await
2078                    .unwrap()
2079            );
2080            assert_eq!(update.sender_data, inbound.sender_data);
2081            assert_eq!(update.first_known_index(), 0);
2082            assert_eq!(
2083                update.export_at_index(0).await.session_key.to_bytes(),
2084                inbound.export_at_index(0).await.session_key.to_bytes()
2085            );
2086        }
2087    }
2088
2089    /// Create an [`InboundGroupSession`] for the given room, using the given
2090    /// Ed25519 key as the signing key/session ID.
2091    fn make_inbound_group_session(
2092        sender_account: &Account,
2093        signing_key: &Ed25519Keypair,
2094        room_id: &RoomId,
2095    ) -> InboundGroupSession {
2096        InboundGroupSession::new(
2097            sender_account.identity_keys.curve25519,
2098            sender_account.identity_keys.ed25519,
2099            room_id,
2100            &make_session_key(signing_key),
2101            SenderData::device_info(crate::types::DeviceKeys::new(
2102                sender_account.user_id().to_owned(),
2103                sender_account.device_id().to_owned(),
2104                vec![],
2105                BTreeMap::new(),
2106                crate::types::Signatures::new(),
2107            )),
2108            None,
2109            EventEncryptionAlgorithm::MegolmV1AesSha2,
2110            Some(ruma::events::room::history_visibility::HistoryVisibility::Shared),
2111            true,
2112        )
2113        .unwrap()
2114    }
2115
2116    /// Make a Megolm [`SessionKey`] using the given Ed25519 key as a signing
2117    /// key/session ID.
2118    fn make_session_key(signing_key: &Ed25519Keypair) -> SessionKey {
2119        // `SessionKey::new` is not public, so the easiest way to construct a
2120        // Megolm session using a known Ed25519 key is to build a byte array in
2121        // the export format.
2122
2123        let mut session_key_bytes = vec![0u8; 229];
2124        // 0: version
2125        session_key_bytes[0] = 2;
2126        // 1..5: index 5..133: ratchet key
2127        rand::rng().fill(&mut session_key_bytes[5..133]);
2128        // 133..165: public ed25519 key
2129        session_key_bytes[133..165].copy_from_slice(signing_key.public_key().as_bytes());
2130        // 165..229: signature
2131        let sig = signing_key.sign(&session_key_bytes[0..165]);
2132        session_key_bytes[165..229].copy_from_slice(&sig.to_bytes());
2133
2134        SessionKey::from_bytes(&session_key_bytes).unwrap()
2135    }
2136
2137    #[async_test]
2138    async fn test_import_room_keys_notifies_stream() {
2139        use futures_util::FutureExt;
2140
2141        let (alice, bob, _) =
2142            get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2143
2144        let room1_id = room_id!("!room1:localhost");
2145        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2146        let exported_sessions = alice.store().export_room_keys(|_| true).await.unwrap();
2147
2148        let mut room_keys_received_stream = Box::pin(bob.store().room_keys_received_stream());
2149        bob.store().import_room_keys(exported_sessions, None, |_, _| {}).await.unwrap();
2150
2151        let room_keys = room_keys_received_stream
2152            .next()
2153            .now_or_never()
2154            .flatten()
2155            .expect("We should have received an update of room key infos")
2156            .unwrap();
2157        assert_eq!(room_keys.len(), 1);
2158        assert_eq!(room_keys[0].room_id, "!room1:localhost");
2159    }
2160
2161    #[async_test]
2162    async fn test_export_room_keys_provides_selected_keys() {
2163        // Given an OlmMachine with room keys in it
2164        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2165        let room1_id = room_id!("!room1:localhost");
2166        let room2_id = room_id!("!room2:localhost");
2167        let room3_id = room_id!("!room3:localhost");
2168        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2169        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2170        alice.create_outbound_group_session_with_defaults_test_helper(room3_id).await.unwrap();
2171
2172        // When I export some of the keys
2173        let keys = alice
2174            .store()
2175            .export_room_keys(|s| s.room_id() == room2_id || s.room_id() == room3_id)
2176            .await
2177            .unwrap();
2178
2179        // Then the requested keys were provided
2180        assert_eq!(keys.len(), 2);
2181        assert_eq!(keys[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2182        assert_eq!(keys[1].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2183        assert_eq!(keys[0].room_id, "!room2:localhost");
2184        assert_eq!(keys[1].room_id, "!room3:localhost");
2185        assert_eq!(keys[0].session_key.to_base64().len(), 220);
2186        assert_eq!(keys[1].session_key.to_base64().len(), 220);
2187    }
2188
2189    #[async_test]
2190    async fn test_export_room_keys_stream_can_provide_all_keys() {
2191        // Given an OlmMachine with room keys in it
2192        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2193        let room1_id = room_id!("!room1:localhost");
2194        let room2_id = room_id!("!room2:localhost");
2195        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2196        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2197
2198        // When I export the keys as a stream
2199        let mut keys = pin!(alice.store().export_room_keys_stream(|_| true).await.unwrap());
2200
2201        // And collect them
2202        let mut collected = vec![];
2203        while let Some(key) = keys.next().await {
2204            collected.push(key);
2205        }
2206
2207        // Then all the keys were provided
2208        assert_eq!(collected.len(), 2);
2209        assert_eq!(collected[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2210        assert_eq!(collected[1].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2211        assert_eq!(collected[0].room_id, "!room1:localhost");
2212        assert_eq!(collected[1].room_id, "!room2:localhost");
2213        assert_eq!(collected[0].session_key.to_base64().len(), 220);
2214        assert_eq!(collected[1].session_key.to_base64().len(), 220);
2215    }
2216
2217    #[async_test]
2218    async fn test_export_room_keys_stream_can_provide_a_subset_of_keys() {
2219        // Given an OlmMachine with room keys in it
2220        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2221        let room1_id = room_id!("!room1:localhost");
2222        let room2_id = room_id!("!room2:localhost");
2223        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2224        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2225
2226        // When I export the keys as a stream
2227        let mut keys =
2228            pin!(alice.store().export_room_keys_stream(|s| s.room_id() == room1_id).await.unwrap());
2229
2230        // And collect them
2231        let mut collected = vec![];
2232        while let Some(key) = keys.next().await {
2233            collected.push(key);
2234        }
2235
2236        // Then all the keys matching our predicate were provided, and no others
2237        assert_eq!(collected.len(), 1);
2238        assert_eq!(collected[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2239        assert_eq!(collected[0].room_id, "!room1:localhost");
2240        assert_eq!(collected[0].session_key.to_base64().len(), 220);
2241    }
2242
2243    #[async_test]
2244    async fn test_export_secrets_bundle() {
2245        let user_id = user_id!("@alice:example.com");
2246        let (first, second, _) = get_machine_pair(user_id, user_id, false).await;
2247
2248        let _ = first
2249            .bootstrap_cross_signing(false)
2250            .await
2251            .expect("We should be able to bootstrap cross-signing");
2252
2253        let bundle = first.store().export_secrets_bundle().await.expect(
2254            "We should be able to export the secrets bundle, now that we \
2255             have the cross-signing keys",
2256        );
2257
2258        assert!(bundle.backup.is_none(), "The bundle should not contain a backup key");
2259
2260        second
2261            .store()
2262            .import_secrets_bundle(&bundle)
2263            .await
2264            .expect("We should be able to import the secrets bundle");
2265
2266        let status = second.cross_signing_status().await;
2267        let identity = second.get_identity(user_id, None).await.unwrap().unwrap().own().unwrap();
2268
2269        assert!(identity.is_verified(), "The public identity should be marked as verified.");
2270
2271        assert!(status.is_complete(), "We should have imported all the cross-signing keys");
2272    }
2273
2274    #[async_test]
2275    async fn test_create_dehydrated_device_key() {
2276        let pickle_key = DehydratedDeviceKey::new();
2277
2278        let to_vec = pickle_key.inner.to_vec();
2279        let pickle_key_from_slice = DehydratedDeviceKey::from_slice(to_vec.as_slice())
2280            .expect("Should be able to create a dehydrated device key from slice");
2281
2282        assert_eq!(pickle_key_from_slice.to_base64(), pickle_key.to_base64());
2283    }
2284
2285    #[async_test]
2286    async fn test_create_dehydrated_errors() {
2287        let too_small = [0u8; 22];
2288        let pickle_key = DehydratedDeviceKey::from_slice(&too_small);
2289
2290        assert!(pickle_key.is_err());
2291
2292        let too_big = [0u8; 40];
2293        let pickle_key = DehydratedDeviceKey::from_slice(&too_big);
2294
2295        assert!(pickle_key.is_err());
2296    }
2297
2298    #[async_test]
2299    async fn test_build_room_key_bundle() {
2300        // Given: Alice has sent a number of room keys to Bob, including some in
2301        // the wrong room, and some that are not marked as shared...
2302        let alice = OlmMachine::new(user_id!("@a:s.co"), device_id!("ALICE")).await;
2303        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("BOB")).await;
2304
2305        let room1_id = room_id!("!room1:localhost");
2306        let room2_id = room_id!("!room2:localhost");
2307
2308        /* We use hardcoded megolm session data, to get a stable output snapshot. These were all created with:
2309
2310           println!("{}", vodozemac::megolm::GroupSession::new(Default::default()).session_key().to_base64());
2311        */
2312        let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw";
2313        let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ";
2314        let session_key3 = "AgAAAAAM9KFsliaUUhGSXgwOzM5UemjkNH4n8NHgvC/y8hhw13zTF+ooGD4uIYEXYX630oNvQm/EvgZo+dkoc0re+vsqsx4sQeNODdSjcBsWOa0oDF+irQn9oYoLUDPI1IBtY1rX+FV99Zm/xnG7uFOX7aTVlko2GSdejy1w9mfobmfxu5aUc04A9zaKJP1pOthZvRAlhpymGYHgsDtWPrrjyc/yypMflE4kIUEEEtu1kT6mrAmcl615XYRAHYK9G2+fZsGvokwzbkl4nulGwcZMpQEoM0nD2o3GWgX81HW3nGfKBg";
2315        let session_key4 = "AgAAAAA4Kkesxq2h4v9PLD6Sm3Smxspz1PXTqytQPCMQMkkrHNmzV2bHlJ+6/Al9cu8vh1Oj69AK0WUAeJOJuaiskEeg/PI3P03+UYLeC379RzgqwSHdBgdQ41G2vD6zpgmE/8vYToe+qpCZACtPOswZxyqxHH+T/Iq0nv13JmlFGIeA6fEPfr5Y28B49viG74Fs9rxV9EH5PfjbuPM/p+Sz5obShuaBPKQBX1jT913nEXPoIJ06exNZGr0285nw/LgVvNlmWmbqNnbzO2cNZjQWA+xZYz5FSfyCxwqEBbEdUCuRCQ";
2316
2317        let sessions = [
2318            create_inbound_group_session_with_visibility(
2319                &alice,
2320                room1_id,
2321                &SessionKey::from_base64(session_key1).unwrap(),
2322                true,
2323            ),
2324            create_inbound_group_session_with_visibility(
2325                &alice,
2326                room1_id,
2327                &SessionKey::from_base64(session_key2).unwrap(),
2328                true,
2329            ),
2330            create_inbound_group_session_with_visibility(
2331                &alice,
2332                room1_id,
2333                &SessionKey::from_base64(session_key3).unwrap(),
2334                false,
2335            ),
2336            create_inbound_group_session_with_visibility(
2337                &alice,
2338                room2_id,
2339                &SessionKey::from_base64(session_key4).unwrap(),
2340                true,
2341            ),
2342        ];
2343        bob.store().save_inbound_group_sessions(&sessions).await.unwrap();
2344
2345        // When I build the bundle
2346        let mut bundle = bob.store().build_room_key_bundle(room1_id).await.unwrap();
2347
2348        // Then the bundle matches the snapshot.
2349
2350        // We sort the sessions in the bundle, so that the snapshot is stable.
2351        bundle.room_keys.sort_by_key(|session| session.session_id.clone());
2352
2353        // We substitute the algorithm, since this changes based on feature
2354        // flags.
2355        let algorithm = if cfg!(feature = "experimental-algorithms") {
2356            "m.megolm.v2.aes-sha2"
2357        } else {
2358            "m.megolm.v1.aes-sha2"
2359        };
2360        let map_algorithm = move |value: Content, _path: ContentPath<'_>| {
2361            assert_eq!(value.as_str().unwrap(), algorithm);
2362            "[algorithm]"
2363        };
2364
2365        // We also substitute alice's keys in the snapshot with placeholders
2366        let alice_curve_key = alice.identity_keys().curve25519.to_base64();
2367        let map_alice_curve_key = move |value: Content, _path: ContentPath<'_>| {
2368            assert_eq!(value.as_str().unwrap(), alice_curve_key);
2369            "[alice curve key]"
2370        };
2371        let alice_ed25519_key = alice.identity_keys().ed25519.to_base64();
2372        let map_alice_ed25519_key = move |value: Content, _path: ContentPath<'_>| {
2373            assert_eq!(value.as_str().unwrap(), alice_ed25519_key);
2374            "[alice ed25519 key]"
2375        };
2376
2377        insta::with_settings!({ sort_maps => true }, {
2378            assert_json_snapshot!(bundle, {
2379                ".withheld[].algorithm" => insta::dynamic_redaction(map_algorithm),
2380                ".room_keys[].algorithm" => insta::dynamic_redaction(map_algorithm),
2381                ".room_keys[].sender_key" => insta::dynamic_redaction(map_alice_curve_key.clone()),
2382                ".withheld[].sender_key" => insta::dynamic_redaction(map_alice_curve_key),
2383                ".room_keys[].sender_claimed_keys.ed25519" => insta::dynamic_redaction(map_alice_ed25519_key),
2384            });
2385        });
2386    }
2387
2388    #[async_test]
2389    async fn test_receive_room_key_bundle() {
2390        let alice = OlmMachine::new(user_id!("@a:s.co"), device_id!("ALICE")).await;
2391        let alice_key = alice.identity_keys().curve25519;
2392        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("BOB")).await;
2393
2394        let room_id = room_id!("!room1:localhost");
2395
2396        let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw";
2397        let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ";
2398
2399        let sessions = [
2400            create_inbound_group_session_with_visibility(
2401                &alice,
2402                room_id,
2403                &SessionKey::from_base64(session_key1).unwrap(),
2404                true,
2405            ),
2406            create_inbound_group_session_with_visibility(
2407                &alice,
2408                room_id,
2409                &SessionKey::from_base64(session_key2).unwrap(),
2410                false,
2411            ),
2412        ];
2413
2414        alice.store().save_inbound_group_sessions(&sessions).await.unwrap();
2415        let bundle = alice.store().build_room_key_bundle(room_id).await.unwrap();
2416
2417        bob.store()
2418            .receive_room_key_bundle(
2419                &StoredRoomKeyBundleData {
2420                    sender_user: alice.user_id().to_owned(),
2421                    sender_key: alice_key,
2422                    sender_data: SenderData::sender_verified(
2423                        alice.user_id(),
2424                        device_id!("ALICE"),
2425                        alice.identity_keys().ed25519,
2426                    ),
2427
2428                    bundle_data: RoomKeyBundleContent {
2429                        room_id: room_id.to_owned(),
2430                        // This isn't used at all in the method call, so we can
2431                        // fill it with garbage.
2432                        file: EncryptedFile::new(
2433                            owned_mxc_uri!("mxc://example.com/0"),
2434                            V2EncryptedFileInfo::encode([0; 32], [0; 16]).into(),
2435                            EncryptedFileHashes::with_sha256([0; 32]),
2436                        ),
2437                    },
2438                },
2439                bundle,
2440                |_, _| {},
2441            )
2442            .await
2443            .unwrap();
2444
2445        // The room key should be imported successfully
2446        let imported_sessions =
2447            bob.store().get_inbound_group_sessions_by_room_id(room_id).await.unwrap();
2448
2449        assert_eq!(imported_sessions.len(), 1);
2450        assert_eq!(imported_sessions[0].room_id(), room_id);
2451
2452        // The session forwarder data should be set correctly.
2453        assert_eq!(
2454            imported_sessions[0]
2455                .forwarder_data
2456                .as_ref()
2457                .expect("Session should contain forwarder data.")
2458                .user_id(),
2459            alice.user_id()
2460        );
2461
2462        assert_matches!(
2463            bob.store()
2464                .get_withheld_info(room_id, sessions[1].session_id())
2465                .await
2466                .unwrap()
2467                .expect("Withheld info should be present in the store."),
2468            RoomKeyWithheldEntry {
2469                #[cfg(not(feature = "experimental-algorithms"))]
2470                content: RoomKeyWithheldContent::MegolmV1AesSha2(
2471                    MegolmV1AesSha2WithheldContent::HistoryNotShared(_)
2472                ),
2473                #[cfg(feature = "experimental-algorithms")]
2474                content: RoomKeyWithheldContent::MegolmV2AesSha2(
2475                    MegolmV1AesSha2WithheldContent::HistoryNotShared(_)
2476                ),
2477                ..
2478            }
2479        );
2480    }
2481
2482    /// Tests that the new store format introduced in [#5737][#5737] does not
2483    /// conflict with items already in the store that were serialised with the
2484    /// older format.
2485    ///
2486    /// [#5737]: https://github.com/matrix-org/matrix-rust-sdk/pull/5737
2487    #[async_test]
2488    async fn test_deserialize_room_key_withheld_entry_from_to_device_event() {
2489        let entry: RoomKeyWithheldEntry = serde_json::from_value(json!(
2490            {
2491              "content": {
2492                "algorithm": "m.megolm.v1.aes-sha2",
2493                "code": "m.unauthorised",
2494                "from_device": "ALICE",
2495                "reason": "You are not authorised to read the message.",
2496                "room_id": "!roomid:s.co",
2497                "sender_key": "7hIcOrEroXYdzjtCBvBjUiqvT0Me7g+ymeXqoc65RS0",
2498                "session_id": "session123"
2499              },
2500              "sender": "@alice:s.co",
2501              "type": "m.room_key.withheld"
2502            }
2503        ))
2504        .unwrap();
2505
2506        assert_let!(
2507            RoomKeyWithheldEntry {
2508                sender,
2509                content: RoomKeyWithheldContent::MegolmV1AesSha2(
2510                    MegolmV1AesSha2WithheldContent::Unauthorised(withheld_content)
2511                ),
2512            } = entry
2513        );
2514
2515        assert_eq!(sender, "@alice:s.co");
2516        assert_eq!(withheld_content.room_id, "!roomid:s.co");
2517        assert_eq!(withheld_content.session_id, "session123");
2518        assert_eq!(
2519            withheld_content.sender_key.to_base64(),
2520            "7hIcOrEroXYdzjtCBvBjUiqvT0Me7g+ymeXqoc65RS0"
2521        );
2522        assert_eq!(withheld_content.from_device, Some(owned_device_id!("ALICE")));
2523    }
2524
2525    /// Create an inbound Megolm session for the given room.
2526    ///
2527    /// `olm_machine` is used to set the `sender_key` and `signing_key` fields
2528    /// of the resultant session.
2529    ///
2530    /// The encryption algorithm used for the session depends on the
2531    /// `experimental-algorithms` feature flag:
2532    ///
2533    /// - When not set, the session uses `m.megolm.v1.aes-sha2`.
2534    /// - When set, the session uses `m.megolm.v2.aes-sha2`.
2535    fn create_inbound_group_session_with_visibility(
2536        olm_machine: &OlmMachine,
2537        room_id: &RoomId,
2538        session_key: &SessionKey,
2539        shared_history: bool,
2540    ) -> InboundGroupSession {
2541        let identity_keys = &olm_machine.store().static_account().identity_keys;
2542        InboundGroupSession::new(
2543            identity_keys.curve25519,
2544            identity_keys.ed25519,
2545            room_id,
2546            session_key,
2547            SenderData::unknown(),
2548            None,
2549            #[cfg(not(feature = "experimental-algorithms"))]
2550            EventEncryptionAlgorithm::MegolmV1AesSha2,
2551            #[cfg(feature = "experimental-algorithms")]
2552            EventEncryptionAlgorithm::MegolmV2AesSha2,
2553            None,
2554            shared_history,
2555        )
2556        .unwrap()
2557    }
2558}