mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-09 00:15:23 -04:00
Merge pull request #3448 from matrix-org/rav/fix_backup_import
Crypto: fix backed-up keys being re-backed-up
This commit is contained in:
@@ -991,7 +991,7 @@ impl OlmMachine {
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
let keys = Cursor::new(keys);
|
||||
let keys = decrypt_room_key_export(keys, &passphrase)?;
|
||||
self.import_room_keys_helper(keys, false, progress_listener)
|
||||
self.import_room_keys_helper(keys, None, progress_listener)
|
||||
}
|
||||
|
||||
/// Import room keys from the given serialized unencrypted key export.
|
||||
@@ -1001,6 +1001,9 @@ impl OlmMachine {
|
||||
/// should be used if the room keys are coming from the server-side backup,
|
||||
/// the method will mark all imported room keys as backed up.
|
||||
///
|
||||
/// **Note**: This has been deprecated. Use
|
||||
/// [`OlmMachine::import_room_keys_from_backup`] instead.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keys` - The serialized version of the unencrypted key export.
|
||||
@@ -1012,11 +1015,38 @@ impl OlmMachine {
|
||||
keys: String,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
// Assume that the keys came from the current backup version.
|
||||
let backup_version = self.runtime.block_on(self.inner.backup_machine().backup_version());
|
||||
let keys: Vec<Value> = serde_json::from_str(&keys)?;
|
||||
|
||||
let keys = keys.into_iter().map(serde_json::from_value).filter_map(|k| k.ok()).collect();
|
||||
self.import_room_keys_helper(keys, backup_version.as_deref(), progress_listener)
|
||||
}
|
||||
|
||||
self.import_room_keys_helper(keys, true, progress_listener)
|
||||
/// Import room keys from the given serialized unencrypted key export.
|
||||
///
|
||||
/// This method is the same as [`OlmMachine::import_room_keys`] but the
|
||||
/// decryption step is skipped and should be performed by the caller. This
|
||||
/// should be used if the room keys are coming from the server-side backup.
|
||||
/// The method will mark all imported room keys as backed up.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `keys` - The serialized version of the unencrypted key export.
|
||||
///
|
||||
/// * `backup_version` - The version of the backup that these keys came
|
||||
/// from.
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the key import.
|
||||
pub fn import_room_keys_from_backup(
|
||||
&self,
|
||||
keys: String,
|
||||
backup_version: String,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
let keys: Vec<Value> = serde_json::from_str(&keys)?;
|
||||
let keys = keys.into_iter().map(serde_json::from_value).filter_map(|k| k.ok()).collect();
|
||||
self.import_room_keys_helper(keys, Some(&backup_version), progress_listener)
|
||||
}
|
||||
|
||||
/// Discard the currently active room key for the given room if there is
|
||||
@@ -1506,16 +1536,18 @@ impl OlmMachine {
|
||||
fn import_room_keys_helper(
|
||||
&self,
|
||||
keys: Vec<ExportedRoomKey>,
|
||||
from_backup: bool,
|
||||
from_backup_version: Option<&str>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
let listener = |progress: usize, total: usize| {
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
let result =
|
||||
self.runtime.block_on(self.inner.import_room_keys(keys, from_backup, listener))?;
|
||||
let result = self.runtime.block_on(self.inner.store().import_room_keys(
|
||||
keys,
|
||||
from_backup_version,
|
||||
listener,
|
||||
))?;
|
||||
|
||||
Ok(KeysImportResult {
|
||||
imported: result.imported_count as i64,
|
||||
|
||||
@@ -31,8 +31,19 @@ Breaking changes:
|
||||
- Add new `dehydrated` property to `olm::account::PickledAccount`.
|
||||
([#3164](https://github.com/matrix-org/matrix-rust-sdk/pull/3164))
|
||||
|
||||
- Remove deprecated `OlmMachine::import_room_keys`.
|
||||
([#3448](https://github.com/matrix-org/matrix-rust-sdk/pull/3448))
|
||||
|
||||
Deprecations:
|
||||
|
||||
- Deprecate `BackupMachine::import_backed_up_room_keys`.
|
||||
([#3448](https://github.com/matrix-org/matrix-rust-sdk/pull/3448))
|
||||
|
||||
Additions:
|
||||
|
||||
- Expose new method `CryptoStore::import_room_keys`.
|
||||
([#3448](https://github.com/matrix-org/matrix-rust-sdk/pull/3448))
|
||||
|
||||
- Expose new method `BackupMachine::backup_version`.
|
||||
([#3320](https://github.com/matrix-org/matrix-rust-sdk/pull/3320))
|
||||
|
||||
|
||||
@@ -600,6 +600,7 @@ impl BackupMachine {
|
||||
///
|
||||
/// Returns a [`RoomKeyImportResult`] containing information about room keys
|
||||
/// which were imported.
|
||||
#[deprecated(note = "Use the OlmMachine::store::import_room_keys method instead")]
|
||||
pub async fn import_backed_up_room_keys(
|
||||
&self,
|
||||
room_keys: BTreeMap<OwnedRoomId, BTreeMap<String, BackedUpRoomKey>>,
|
||||
@@ -619,7 +620,15 @@ impl BackupMachine {
|
||||
}
|
||||
}
|
||||
|
||||
self.store.import_room_keys(decrypted_room_keys, true, progress_listener).await
|
||||
// FIXME: This method is a bit flawed: we have no real idea which backup version
|
||||
// these keys came from. For example, we might have reset the backup
|
||||
// since the keys were downloaded. For now, let's assume they came from
|
||||
// the "current" backup version.
|
||||
let backup_version = self.backup_version().await;
|
||||
|
||||
self.store
|
||||
.import_room_keys(decrypted_room_keys, backup_version.as_deref(), progress_listener)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,6 +835,12 @@ mod tests {
|
||||
let machine = OlmMachine::new(alice_id(), alice_device_id()).await;
|
||||
let backup_machine = machine.backup_machine();
|
||||
|
||||
// We set up a backup key, so that we can test `backup_machine.backup()` later.
|
||||
let decryption_key = BackupDecryptionKey::new().expect("Couldn't create new recovery key");
|
||||
let backup_key = decryption_key.megolm_v1_public_key();
|
||||
backup_key.set_version("1".to_owned());
|
||||
backup_machine.enable_backup_v1(backup_key).await.expect("Couldn't enable backup");
|
||||
|
||||
let room_id = room_id!("!DovneieKSTkdHKpIXy:morpheus.localhost");
|
||||
let session_id = "gM8i47Xhu0q52xLfgUXzanCMpLinoyVyH7R58cBuVBU";
|
||||
let room_key = room_key();
|
||||
@@ -839,19 +854,29 @@ mod tests {
|
||||
|
||||
assert!(session.is_none(), "Initially we should not have the session in the store");
|
||||
|
||||
#[allow(deprecated)]
|
||||
backup_machine
|
||||
.import_backed_up_room_keys(room_keys, |_, _| {})
|
||||
.await
|
||||
.expect("We should be able to import a room key");
|
||||
|
||||
// Now check that the session was correctly imported, and that it is marked as
|
||||
// backed up
|
||||
let session = machine.store().get_inbound_group_session(room_id, session_id).await.unwrap();
|
||||
|
||||
assert_let!(Some(session) = session);
|
||||
assert!(
|
||||
session.backed_up(),
|
||||
"If a session was imported from a backup, it should be considered to be backed up"
|
||||
);
|
||||
assert!(session.has_been_imported());
|
||||
|
||||
// Also check that it is not returned by a backup request.
|
||||
let backup_request =
|
||||
backup_machine.backup().await.expect("We should be able to create a backup request");
|
||||
assert!(
|
||||
backup_request.is_none(),
|
||||
"If a session was imported from backup, it should not be backed up again."
|
||||
);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
||||
@@ -74,7 +74,7 @@ pub enum KeyExportError {
|
||||
/// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
|
||||
/// # let export = Cursor::new("".to_owned());
|
||||
/// let exported_keys = decrypt_room_key_export(export, "1234").unwrap();
|
||||
/// machine.import_room_keys(exported_keys, false, |_, _| {}).await.unwrap();
|
||||
/// machine.store().import_room_keys(exported_keys, None, |_, _| {}).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub fn decrypt_room_key_export(
|
||||
|
||||
@@ -62,9 +62,8 @@ use crate::{
|
||||
gossiping::GossipMachine,
|
||||
identities::{user::UserIdentities, Device, IdentityManager, UserDevices},
|
||||
olm::{
|
||||
Account, CrossSigningStatus, EncryptionSettings, ExportedRoomKey, IdentityKeys,
|
||||
InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity, SessionType,
|
||||
StaticAccountData,
|
||||
Account, CrossSigningStatus, EncryptionSettings, IdentityKeys, InboundGroupSession,
|
||||
OlmDecryptionInfo, PrivateCrossSigningIdentity, SessionType, StaticAccountData,
|
||||
},
|
||||
requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest},
|
||||
session_manager::{GroupSessionManager, SessionManager},
|
||||
@@ -91,7 +90,7 @@ use crate::{
|
||||
utilities::timestamp_to_iso8601,
|
||||
verification::{Verification, VerificationMachine, VerificationRequest},
|
||||
CrossSigningKeyExport, CryptoStoreError, KeysQueryRequest, LocalTrust, ReadOnlyDevice,
|
||||
RoomKeyImportResult, SignatureError, ToDeviceRequest,
|
||||
SignatureError, ToDeviceRequest,
|
||||
};
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol used for
|
||||
@@ -1783,49 +1782,6 @@ impl OlmMachine {
|
||||
self.store().get_user_devices(user_id).await
|
||||
}
|
||||
|
||||
/// Import the given room keys into our store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `exported_keys` - A list of previously exported keys that should be
|
||||
/// imported into our store. If we already have a better version of a key
|
||||
/// the key will *not* be imported.
|
||||
///
|
||||
/// * `from_backup` - Were the room keys imported from the backup, if true
|
||||
/// will mark the room keys as already backed up. This will prevent backing
|
||||
/// up keys that are already backed up.
|
||||
///
|
||||
/// Returns a tuple of numbers that represent the number of sessions that
|
||||
/// were imported and the total number of sessions that were found in the
|
||||
/// key export.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::io::Cursor;
|
||||
/// # use matrix_sdk_crypto::{OlmMachine, decrypt_room_key_export};
|
||||
/// # use ruma::{device_id, user_id};
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # async {
|
||||
/// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
|
||||
/// # let export = Cursor::new("".to_owned());
|
||||
/// let exported_keys = decrypt_room_key_export(export, "1234").unwrap();
|
||||
/// machine.import_room_keys(exported_keys, false, |_, _| {}).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
#[deprecated(
|
||||
since = "0.7.0",
|
||||
note = "Use the OlmMachine::store::import_exported_room_keys method instead"
|
||||
)]
|
||||
pub async fn import_room_keys(
|
||||
&self,
|
||||
exported_keys: Vec<ExportedRoomKey>,
|
||||
from_backup: bool,
|
||||
progress_listener: impl Fn(usize, usize),
|
||||
) -> StoreResult<RoomKeyImportResult> {
|
||||
self.store().import_room_keys(exported_keys, from_backup, progress_listener).await
|
||||
}
|
||||
|
||||
/// Get the status of the private cross signing keys.
|
||||
///
|
||||
/// This can be used to check which private cross signing keys we have
|
||||
|
||||
@@ -98,7 +98,10 @@ pub struct ExportedRoomKey {
|
||||
}
|
||||
|
||||
impl ExportedRoomKey {
|
||||
pub(crate) fn from_backed_up_room_key(
|
||||
/// Create an `ExportedRoomKey` from a `BackedUpRoomKey`.
|
||||
///
|
||||
/// This can be used when importing the keys from a backup into the store.
|
||||
pub fn from_backed_up_room_key(
|
||||
room_id: OwnedRoomId,
|
||||
session_id: String,
|
||||
room_key: BackedUpRoomKey,
|
||||
|
||||
@@ -290,8 +290,9 @@ macro_rules! cryptostore_integration_tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that we can import an inbound group session via [`CryptoStore::save_changes`]
|
||||
#[async_test]
|
||||
async fn save_inbound_group_session() {
|
||||
async fn save_changes_save_inbound_group_session() {
|
||||
let (account, store) = get_loaded_store("save_inbound_group_session").await;
|
||||
|
||||
let room_id = &room_id!("!test:localhost");
|
||||
@@ -303,31 +304,96 @@ macro_rules! cryptostore_integration_tests {
|
||||
store.save_changes(changes).await.expect("Can't save group session");
|
||||
}
|
||||
|
||||
/// Test that we can import a backed-up group session via
|
||||
/// [`CryptoStore::save_inbound_group_sessions`]
|
||||
#[async_test]
|
||||
async fn save_inbound_group_session_for_backup() {
|
||||
async fn save_inbound_group_session_from_backup() {
|
||||
let (account, store) =
|
||||
get_loaded_store("save_inbound_group_session_for_backup").await;
|
||||
get_loaded_store("save_inbound_group_session_from_backup").await;
|
||||
|
||||
let room_id = &room_id!("!test:localhost");
|
||||
let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
|
||||
|
||||
let changes =
|
||||
Changes { inbound_group_sessions: vec![session.clone()], ..Default::default() };
|
||||
|
||||
store.save_changes(changes).await.expect("Can't save group session");
|
||||
session.mark_as_backed_up();
|
||||
store
|
||||
.save_inbound_group_sessions(vec![session.clone()], Some(&"bkpver1"))
|
||||
.await
|
||||
.expect("could not save sessions");
|
||||
|
||||
let loaded_session = store
|
||||
.get_inbound_group_session(&session.room_id, session.session_id())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
.expect("error when loading session")
|
||||
.expect("session not found in store");
|
||||
assert_eq!(session, loaded_session);
|
||||
assert_eq!(store.get_inbound_group_sessions().await.unwrap().len(), 1);
|
||||
assert_eq!(store.inbound_group_session_counts(None).await.unwrap().total, 1);
|
||||
|
||||
// It should *not* be returned by a request for backup for the same backup version
|
||||
let to_back_up = store.inbound_group_sessions_for_backup("bkpver1", 1).await.unwrap();
|
||||
assert_eq!(to_back_up.len(), 0, "backup was returned by backup query");
|
||||
assert_eq!(
|
||||
store.inbound_group_session_counts(Some(&"bkpver1")).await.unwrap().backed_up, 1,
|
||||
"backed_up count",
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that the behaviour of a key imported from an *old* backup is correct
|
||||
///
|
||||
/// This currently only works on the MemoryStore, so is ignored. The other stores
|
||||
/// are waiting for more work on https://github.com/element-hq/element-web/issues/26892.
|
||||
#[ignore]
|
||||
#[async_test]
|
||||
async fn save_inbound_group_session_from_old_backup() {
|
||||
let (account, store) =
|
||||
get_loaded_store("save_inbound_group_session_from_old_backup").await;
|
||||
|
||||
let room_id = &room_id!("!test:localhost");
|
||||
let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
|
||||
|
||||
session.mark_as_backed_up();
|
||||
store
|
||||
.save_inbound_group_sessions(vec![session.clone()], Some(&"bkpver1"))
|
||||
.await
|
||||
.expect("could not save sessions");
|
||||
|
||||
// The session should be returned by a request for backup from a different backup version.
|
||||
let to_back_up = store.inbound_group_sessions_for_backup("bkpver2", 1).await.unwrap();
|
||||
assert_eq!(to_back_up, vec![session]);
|
||||
assert_eq!(
|
||||
store.inbound_group_session_counts(Some(&"bkpver2")).await.unwrap().backed_up, 0,
|
||||
"backed_up count for backup version 2",
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that we can import a not-backed-up group session via
|
||||
/// [`CryptoStore::save_inbound_group_sessions`]
|
||||
#[async_test]
|
||||
async fn save_inbound_group_session_from_import() {
|
||||
let (account, store) =
|
||||
get_loaded_store("save_inbound_group_session_from_import").await;
|
||||
|
||||
let room_id = &room_id!("!test:localhost");
|
||||
let (_, session) = account.create_group_session_pair_with_defaults(room_id).await;
|
||||
|
||||
store
|
||||
.save_inbound_group_sessions(vec![session.clone()], None)
|
||||
.await
|
||||
.expect("could not save sessions");
|
||||
|
||||
let loaded_session = store
|
||||
.get_inbound_group_session(&session.room_id, session.session_id())
|
||||
.await
|
||||
.expect("error when loading session")
|
||||
.expect("session not found in store");
|
||||
assert_eq!(session, loaded_session);
|
||||
assert_eq!(store.get_inbound_group_sessions().await.unwrap().len(), 1);
|
||||
assert_eq!(store.inbound_group_session_counts(None).await.unwrap().total, 1);
|
||||
assert_eq!(store.inbound_group_session_counts(None).await.unwrap().backed_up, 0);
|
||||
|
||||
let to_back_up = store.inbound_group_sessions_for_backup("bkpver", 1).await.unwrap();
|
||||
assert_eq!(to_back_up, vec![session])
|
||||
// It should be returned by a request for backup
|
||||
let to_back_up = store.inbound_group_sessions_for_backup("bkpver1", 1).await.unwrap();
|
||||
assert_eq!(to_back_up, vec![session]);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
||||
@@ -25,6 +25,7 @@ use ruma::{
|
||||
OwnedUserId, RoomId, TransactionId, UserId,
|
||||
};
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tracing::warn;
|
||||
|
||||
use super::{
|
||||
caches::{DeviceStore, GroupSessionStore, SessionStore},
|
||||
@@ -144,12 +145,6 @@ impl MemoryStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn save_inbound_group_sessions(&self, sessions: Vec<InboundGroupSession>) {
|
||||
for session in sessions {
|
||||
self.inbound_group_sessions.add(session);
|
||||
}
|
||||
}
|
||||
|
||||
fn save_outbound_group_sessions(&self, sessions: Vec<OutboundGroupSession>) {
|
||||
self.outbound_group_sessions
|
||||
.write()
|
||||
@@ -225,7 +220,7 @@ impl CryptoStore for MemoryStore {
|
||||
|
||||
async fn save_changes(&self, changes: Changes) -> Result<()> {
|
||||
self.save_sessions(changes.sessions).await;
|
||||
self.save_inbound_group_sessions(changes.inbound_group_sessions);
|
||||
self.save_inbound_group_sessions(changes.inbound_group_sessions, None).await?;
|
||||
self.save_outbound_group_sessions(changes.outbound_group_sessions);
|
||||
self.save_private_identity(changes.private_identity);
|
||||
|
||||
@@ -299,6 +294,39 @@ impl CryptoStore for MemoryStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_inbound_group_sessions(
|
||||
&self,
|
||||
sessions: Vec<InboundGroupSession>,
|
||||
backed_up_to_version: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let mut inbound_group_sessions_backed_up_to =
|
||||
self.inbound_group_sessions_backed_up_to.write().unwrap();
|
||||
|
||||
for session in sessions {
|
||||
let room_id = session.room_id();
|
||||
let session_id = session.session_id();
|
||||
|
||||
// Sanity-check that the data in the sessions corresponds to backed_up_version
|
||||
let backed_up = session.backed_up();
|
||||
if backed_up != backed_up_to_version.is_some() {
|
||||
warn!(
|
||||
backed_up,
|
||||
backed_up_to_version,
|
||||
"Session backed-up flag does not correspond to backup version setting",
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(backup_version) = backed_up_to_version {
|
||||
inbound_group_sessions_backed_up_to
|
||||
.entry(room_id.to_owned())
|
||||
.or_default()
|
||||
.insert(session_id.to_owned(), BackupVersion::from(backup_version));
|
||||
}
|
||||
self.inbound_group_sessions.add(session);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_sessions(&self, sender_key: &str) -> Result<Option<Arc<Mutex<Vec<Session>>>>> {
|
||||
Ok(self.sessions.get(sender_key))
|
||||
}
|
||||
@@ -632,7 +660,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let store = MemoryStore::new();
|
||||
store.save_inbound_group_sessions(vec![inbound.clone()]);
|
||||
store.save_inbound_group_sessions(vec![inbound.clone()], None).await.unwrap();
|
||||
|
||||
let loaded_session =
|
||||
store.get_inbound_group_session(room_id, outbound.session_id()).await.unwrap().unwrap();
|
||||
@@ -1007,7 +1035,7 @@ mod tests {
|
||||
sessions.sort_by_key(|s| s.session_id().to_owned());
|
||||
|
||||
let store = MemoryStore::new();
|
||||
store.save_inbound_group_sessions(sessions.clone());
|
||||
store.save_inbound_group_sessions(sessions.clone(), None).await.unwrap();
|
||||
|
||||
(store, sessions)
|
||||
}
|
||||
@@ -1137,6 +1165,14 @@ mod integration_tests {
|
||||
self.0.save_pending_changes(changes).await
|
||||
}
|
||||
|
||||
async fn save_inbound_group_sessions(
|
||||
&self,
|
||||
sessions: Vec<InboundGroupSession>,
|
||||
backed_up_to_version: Option<&str>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.0.save_inbound_group_sessions(sessions, backed_up_to_version).await
|
||||
}
|
||||
|
||||
async fn get_sessions(
|
||||
&self,
|
||||
sender_key: &str,
|
||||
|
||||
@@ -1630,10 +1630,22 @@ impl Store {
|
||||
self.inner.store.secrets_stream()
|
||||
}
|
||||
|
||||
pub(crate) async fn import_room_keys(
|
||||
/// Import the given room keys into the store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `exported_keys` - The keys to be imported.
|
||||
/// * `from_backup_version` - If the keys came from key backup, the key
|
||||
/// backup version. This will cause the keys to be marked as already
|
||||
/// backed up, and therefore not requiring another backup.
|
||||
/// * `progress_listener` - Callback which will be called after each key is
|
||||
/// processed. Called with arguments `(processed, total)` where
|
||||
/// `processed` is the number of keys processed so far, and `total` is the
|
||||
/// total number of keys (i.e., `exported_keys.len()`).
|
||||
pub async fn import_room_keys(
|
||||
&self,
|
||||
exported_keys: Vec<ExportedRoomKey>,
|
||||
from_backup: bool,
|
||||
from_backup_version: Option<&str>,
|
||||
progress_listener: impl Fn(usize, usize),
|
||||
) -> Result<RoomKeyImportResult> {
|
||||
let mut sessions = Vec::new();
|
||||
@@ -1664,7 +1676,7 @@ impl Store {
|
||||
// Only import the session if we didn't have this session or
|
||||
// if it's a better version of the same session.
|
||||
if new_session_better(&session, old_session).await {
|
||||
if from_backup {
|
||||
if from_backup_version.is_some() {
|
||||
session.mark_as_backed_up();
|
||||
}
|
||||
|
||||
@@ -1693,9 +1705,7 @@ impl Store {
|
||||
|
||||
let imported_count = sessions.len();
|
||||
|
||||
let changes = Changes { inbound_group_sessions: sessions, ..Default::default() };
|
||||
|
||||
self.save_changes(changes).await?;
|
||||
self.inner.store.save_inbound_group_sessions(sessions, from_backup_version).await?;
|
||||
|
||||
info!(total_count, imported_count, room_keys = ?keys, "Successfully imported room keys");
|
||||
|
||||
@@ -1725,7 +1735,7 @@ impl Store {
|
||||
/// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
|
||||
/// # let export = Cursor::new("".to_owned());
|
||||
/// let exported_keys = decrypt_room_key_export(export, "1234").unwrap();
|
||||
/// machine.import_room_keys(exported_keys, false, |_, _| {}).await.unwrap();
|
||||
/// machine.store().import_exported_room_keys(exported_keys, |_, _| {}).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub async fn import_exported_room_keys(
|
||||
@@ -1733,7 +1743,7 @@ impl Store {
|
||||
exported_keys: Vec<ExportedRoomKey>,
|
||||
progress_listener: impl Fn(usize, usize),
|
||||
) -> Result<RoomKeyImportResult> {
|
||||
self.import_room_keys(exported_keys, false, progress_listener).await
|
||||
self.import_room_keys(exported_keys, None, progress_listener).await
|
||||
}
|
||||
|
||||
pub(crate) fn crypto_store(&self) -> Arc<CryptoStoreWrapper> {
|
||||
|
||||
@@ -65,6 +65,22 @@ pub trait CryptoStore: AsyncTraitDeps {
|
||||
/// * `changes` - The set of changes that should be stored.
|
||||
async fn save_pending_changes(&self, changes: PendingChanges) -> Result<(), Self::Error>;
|
||||
|
||||
/// Save a list of inbound group sessions to the store.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `sessions` - The sessions to be saved.
|
||||
/// * `backed_up_to_version` - If the keys should be marked as having been
|
||||
/// backed up, the version of the backup.
|
||||
///
|
||||
/// Note: some implementations ignore `backup_version` and assume the
|
||||
/// current backup version, which is normally the same.
|
||||
async fn save_inbound_group_sessions(
|
||||
&self,
|
||||
sessions: Vec<InboundGroupSession>,
|
||||
backed_up_to_version: Option<&str>,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Get all the sessions that belong to the given sender key.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -347,6 +363,14 @@ impl<T: CryptoStore> CryptoStore for EraseCryptoStoreError<T> {
|
||||
self.0.save_pending_changes(changes).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn save_inbound_group_sessions(
|
||||
&self,
|
||||
sessions: Vec<InboundGroupSession>,
|
||||
backed_up_to_version: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.0.save_inbound_group_sessions(sessions, backed_up_to_version).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_sessions(&self, sender_key: &str) -> Result<Option<Arc<Mutex<Vec<Session>>>>> {
|
||||
self.0.get_sessions(sender_key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -721,6 +721,27 @@ impl_crypto_store! {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_inbound_group_sessions(
|
||||
&self,
|
||||
sessions: Vec<InboundGroupSession>,
|
||||
backed_up_to_version: Option<&str>,
|
||||
) -> Result<()> {
|
||||
// Sanity-check that the data in the sessions corresponds to backed_up_version
|
||||
sessions.iter().for_each(|s| {
|
||||
let backed_up = s.backed_up();
|
||||
if backed_up != backed_up_to_version.is_some() {
|
||||
warn!(
|
||||
backed_up, backed_up_to_version,
|
||||
"Session backed-up flag does not correspond to backup version setting",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Currently, this store doesn't save the backup version separately, so this
|
||||
// just delegates to save_changes.
|
||||
self.save_changes(Changes { inbound_group_sessions: sessions, ..Changes::default() }).await
|
||||
}
|
||||
|
||||
async fn load_tracked_users(&self) -> Result<Vec<TrackedUser>> {
|
||||
let tx = self
|
||||
.inner
|
||||
|
||||
@@ -886,6 +886,28 @@ impl CryptoStore for SqliteCryptoStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_inbound_group_sessions(
|
||||
&self,
|
||||
sessions: Vec<InboundGroupSession>,
|
||||
backed_up_to_version: Option<&str>,
|
||||
) -> matrix_sdk_crypto::store::Result<(), Self::Error> {
|
||||
// Sanity-check that the data in the sessions corresponds to backed_up_version
|
||||
sessions.iter().for_each(|s| {
|
||||
let backed_up = s.backed_up();
|
||||
if backed_up != backed_up_to_version.is_some() {
|
||||
warn!(
|
||||
backed_up,
|
||||
backed_up_to_version,
|
||||
"Session backed-up flag does not correspond to backup version setting",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Currently, this store doesn't save the backup version separately, so this
|
||||
// just delegates to save_changes.
|
||||
self.save_changes(Changes { inbound_group_sessions: sessions, ..Changes::default() }).await
|
||||
}
|
||||
|
||||
async fn get_sessions(&self, sender_key: &str) -> Result<Option<Arc<Mutex<Vec<Session>>>>> {
|
||||
let account_info = self.get_static_account().ok_or(Error::AccountUnset)?;
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ pub(crate) mod types;
|
||||
pub use types::{BackupState, UploadState};
|
||||
|
||||
use self::futures::WaitForSteadyState;
|
||||
use crate::{encryption::BackupDownloadStrategy, Client, Error, Room};
|
||||
use crate::{
|
||||
crypto::olm::ExportedRoomKey, encryption::BackupDownloadStrategy, Client, Error, Room,
|
||||
};
|
||||
|
||||
/// The backups manager for the [`Client`].
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -370,7 +372,7 @@ impl Backups {
|
||||
if let Some(decryption_key) = backup_keys.decryption_key {
|
||||
if let Some(version) = backup_keys.backup_version {
|
||||
let request =
|
||||
get_backup_keys_for_room::v3::Request::new(version, room_id.to_owned());
|
||||
get_backup_keys_for_room::v3::Request::new(version.clone(), room_id.to_owned());
|
||||
let response = self.client.send(request, Default::default()).await?;
|
||||
|
||||
// Transform response to standard format (map of room ID -> room key).
|
||||
@@ -379,7 +381,8 @@ impl Backups {
|
||||
RoomKeyBackup::new(response.sessions),
|
||||
)]));
|
||||
|
||||
self.handle_downloaded_room_keys(response, decryption_key, olm_machine).await?;
|
||||
self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +399,7 @@ impl Backups {
|
||||
if let Some(decryption_key) = backup_keys.decryption_key {
|
||||
if let Some(version) = backup_keys.backup_version {
|
||||
let request = get_backup_keys_for_session::v3::Request::new(
|
||||
version,
|
||||
version.clone(),
|
||||
room_id.to_owned(),
|
||||
session_id.to_owned(),
|
||||
);
|
||||
@@ -411,7 +414,8 @@ impl Backups {
|
||||
)])),
|
||||
)]));
|
||||
|
||||
self.handle_downloaded_room_keys(response, decryption_key, olm_machine).await?;
|
||||
self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,9 +449,10 @@ impl Backups {
|
||||
&self,
|
||||
backed_up_keys: get_backup_keys::v3::Response,
|
||||
backup_decryption_key: BackupDecryptionKey,
|
||||
backup_version: &str,
|
||||
olm_machine: &OlmMachine,
|
||||
) -> Result<(), Error> {
|
||||
let mut decrypted_room_keys: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new();
|
||||
let mut decrypted_room_keys: Vec<_> = Vec::new();
|
||||
|
||||
for (room_id, room_keys) in backed_up_keys.rooms {
|
||||
for (session_id, room_key) in room_keys.sessions {
|
||||
@@ -474,16 +479,17 @@ impl Backups {
|
||||
}
|
||||
};
|
||||
|
||||
decrypted_room_keys
|
||||
.entry(room_id.to_owned())
|
||||
.or_default()
|
||||
.insert(session_id, room_key);
|
||||
decrypted_room_keys.push(ExportedRoomKey::from_backed_up_room_key(
|
||||
room_id.to_owned(),
|
||||
session_id,
|
||||
room_key,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let result = olm_machine
|
||||
.backup_machine()
|
||||
.import_backed_up_room_keys(decrypted_room_keys, |_, _| {})
|
||||
.store()
|
||||
.import_room_keys(decrypted_room_keys, Some(backup_version), |_, _| {})
|
||||
.await?;
|
||||
|
||||
// Since we can't use the usual room keys stream from the `OlmMachine`
|
||||
@@ -499,13 +505,13 @@ impl Backups {
|
||||
decryption_key: BackupDecryptionKey,
|
||||
version: String,
|
||||
) -> Result<(), Error> {
|
||||
let request = get_backup_keys::v3::Request::new(version);
|
||||
let request = get_backup_keys::v3::Request::new(version.clone());
|
||||
let response = self.client.send(request, Default::default()).await?;
|
||||
|
||||
let olm_machine = self.client.olm_machine().await;
|
||||
let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
|
||||
|
||||
self.handle_downloaded_room_keys(response, decryption_key, olm_machine).await?;
|
||||
self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -920,7 +926,6 @@ impl Backups {
|
||||
mod test {
|
||||
use std::time::Duration;
|
||||
|
||||
use matrix_sdk_base::crypto::olm::ExportedRoomKey;
|
||||
use matrix_sdk_test::async_test;
|
||||
use serde_json::json;
|
||||
use wiremock::{
|
||||
|
||||
Reference in New Issue
Block a user