From b4b111f91f535068689965a4f52d50aaf1c09da6 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Mon, 6 Feb 2023 12:17:44 +0000 Subject: [PATCH 01/12] Store encryption settings --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 50 ++++++++++--- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 74 ++++++++++++++++--- crates/matrix-sdk-base/src/client.rs | 3 +- crates/matrix-sdk-crypto/src/machine.rs | 59 ++++++++++++++- .../src/session_manager/group_sessions.rs | 30 +++++++- .../src/store/memorystore.rs | 2 +- .../migrations/002_encryption_settings.sql | 4 + 7 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 3903289acb4..2c03a98301a 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -500,12 +500,18 @@ pub enum EventEncryptionAlgorithm { impl From for RustEventEncryptionAlgorithm { fn from(a: EventEncryptionAlgorithm) -> Self { match a { - EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => { - RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 - } - EventEncryptionAlgorithm::MegolmV1AesSha2 => { - RustEventEncryptionAlgorithm::MegolmV1AesSha2 - } + EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, + EventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2, + } + } +} + +impl From for EventEncryptionAlgorithm { + fn from(value: RustEventEncryptionAlgorithm) -> Self { + match value { + RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, + RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2, + _ => todo!(), } } } @@ -540,10 +546,22 @@ pub enum HistoryVisibility { impl From for RustHistoryVisibility { fn from(h: HistoryVisibility) -> Self { match h { - HistoryVisibility::Invited => RustHistoryVisibility::Invited, - HistoryVisibility::Joined => RustHistoryVisibility::Joined, - HistoryVisibility::Shared => RustHistoryVisibility::Shared, - HistoryVisibility::WorldReadable => RustHistoryVisibility::Shared, + HistoryVisibility::Invited => Self::Invited, + HistoryVisibility::Joined => Self::Joined, + HistoryVisibility::Shared => Self::Shared, + HistoryVisibility::WorldReadable => Self::Shared, + } + } +} + +impl From for HistoryVisibility { + fn from(h: RustHistoryVisibility) -> Self { + match h { + RustHistoryVisibility::Invited => Self::Invited, + RustHistoryVisibility::Joined => Self::Joined, + RustHistoryVisibility::Shared => Self::Shared, + RustHistoryVisibility::WorldReadable => Self::WorldReadable, + _ => todo!(), } } } @@ -583,6 +601,18 @@ impl From for RustEncryptionSettings { } } +impl From for EncryptionSettings { + fn from(value: RustEncryptionSettings) -> Self { + EncryptionSettings { + algorithm: value.algorithm.into(), + rotation_period: value.rotation_period.as_secs(), + rotation_period_msgs: value.rotation_period_msgs, + history_visibility: value.history_visibility.into(), + only_allow_trusted_devices: value.only_allow_trusted_devices, + } + } +} + /// An event that was successfully decrypted. pub struct DecryptedEvent { /// The decrypted version of the event. diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 814bf021a8f..1cd4b9fe4dd 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -53,9 +53,9 @@ use crate::{ responses::{response_from_string, OwnedResponse}, BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, CrossSigningKeyExport, CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, EncryptionSettings, - KeyImportError, KeysImportResult, MegolmV1BackupKey, ProgressListener, Request, RequestType, - RequestVerificationResult, RoomKeyCounts, Sas, SignatureUploadRequest, StartSasResult, - UserIdentity, Verification, VerificationRequest, + EventEncryptionAlgorithm, KeyImportError, KeysImportResult, MegolmV1BackupKey, + ProgressListener, Request, RequestType, RequestVerificationResult, RoomKeyCounts, Sas, + SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest, }; /// A high level state machine that handles E2EE for Matrix. @@ -563,6 +563,65 @@ impl OlmMachine { .map(|r| r.into())) } + /// TODO: docs + pub fn get_encryption_settings( + &self, + room_id: &str, + ) -> Result, CryptoStoreError> { + let room_id = RoomId::parse(room_id)?; + let settings = + self.runtime.block_on(self.inner.get_encryption_settings(&room_id))?.map(|v| v.into()); + Ok(settings) + } + + /// TODO: docs + pub fn set_event_encryption_algorithm( + &self, + room_id: &str, + algorithm: EventEncryptionAlgorithm, + ) -> Result<(), CryptoStoreError> { + let room_id = RoomId::parse(room_id)?; + self.runtime.block_on(async move { + let mut settings = + self.inner.get_encryption_settings(&room_id).await?.unwrap_or_default(); + settings.algorithm = algorithm.into(); + self.inner.set_encryption_settings(&room_id, settings).await?; + Ok(()) + }) + } + + /// TODO: docs + pub fn set_block_untrusted_devices_per_room( + &self, + room_id: &str, + block_untrusted_devices: bool, + ) -> Result<(), CryptoStoreError> { + let room_id = RoomId::parse(room_id)?; + self.runtime.block_on(async move { + let mut settings = + self.inner.get_encryption_settings(&room_id).await?.unwrap_or_default(); + settings.only_allow_trusted_devices = block_untrusted_devices; + self.inner.set_encryption_settings(&room_id, settings).await?; + Ok(()) + }) + } + + /// TODO: docs + pub fn set_block_untrusted_devices_globally( + &self, + block_untrusted_devices: bool, + ) -> Result<(), CryptoStoreError> { + self.runtime + .block_on(self.inner.set_block_untrusted_devices_globally(block_untrusted_devices))?; + Ok(()) + } + + /// TODO: docs + pub fn is_blocking_untrusted_devices_globally(&self) -> Result { + let block = self.runtime.block_on(self.inner.get_block_untrusted_devices_globally())?; + Ok(block) + } + /// Share a room key with the given list of users for the given room. /// /// After the request was sent out and a successful response was received @@ -586,17 +645,14 @@ impl OlmMachine { &self, room_id: String, users: Vec, - settings: EncryptionSettings, ) -> Result, CryptoStoreError> { let users: Vec = users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect(); let room_id = RoomId::parse(room_id)?; - let requests = self.runtime.block_on(self.inner.share_room_key( - &room_id, - users.iter().map(Deref::deref), - settings, - ))?; + let requests = self + .runtime + .block_on(self.inner.share_room_key(&room_id, users.iter().map(Deref::deref)))?; Ok(requests.into_iter().map(|r| r.as_ref().into()).collect()) } diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 8be3af22433..2450c9d3da7 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -1075,7 +1075,8 @@ impl BaseClient { let settings = settings.ok_or(Error::EncryptionNotEnabled)?; let settings = EncryptionSettings::new(settings, history_visibility, false); - Ok(o.share_room_key(room_id, members.map(Deref::deref), settings).await?) + Ok(o.share_room_key_with_settings(room_id, members.map(Deref::deref), settings) + .await?) } None => panic!("Olm machine wasn't started"), } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index f1c8766e1d5..f897fbf9af7 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -13,7 +13,7 @@ // limitations under the License. use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, + collections::{BTreeMap, BTreeSet, HashMap, HashSet}, sync::Arc, time::Duration, }; @@ -510,6 +510,50 @@ impl OlmMachine { self.session_manager.get_missing_sessions(users).await } + /// TODO: docs + pub async fn get_encryption_settings( + &self, + room_id: &RoomId, + ) -> StoreResult> { + let settings = self.store.load_encryption_settings(room_id).await?; + Ok(settings) + } + + /// TODO: docs + pub async fn set_encryption_settings( + &self, + room_id: &RoomId, + settings: EncryptionSettings, + ) -> StoreResult<()> { + self.store + .save_changes(Changes { + encryption_settings: HashMap::from([(room_id.into(), settings)]), + ..Default::default() + }) + .await?; + Ok(()) + } + + /// TODO: docs + pub async fn set_block_untrusted_devices_globally( + &self, + block_untrusted_devices: bool, + ) -> StoreResult<()> { + self.store + .save_changes(Changes { + block_untrusted_devices_globally: Some(block_untrusted_devices), + ..Default::default() + }) + .await?; + Ok(()) + } + + /// TODO: docs + pub async fn get_block_untrusted_devices_globally(&self) -> StoreResult { + let block = self.store.block_untrusted_devices_globally().await?; + Ok(block) + } + /// Receive a successful key claim response and create new Olm sessions with /// the claimed keys. /// @@ -754,9 +798,18 @@ impl OlmMachine { &self, room_id: &RoomId, users: impl Iterator, - encryption_settings: impl Into, ) -> OlmResult>> { - self.group_session_manager.share_room_key(room_id, users, encryption_settings).await + self.group_session_manager.share_room_key(room_id, users).await + } + + /// TODO: docs + pub async fn share_room_key_with_settings( + &self, + room_id: &RoomId, + users: impl Iterator, + settings: impl Into, + ) -> OlmResult>> { + self.group_session_manager.share_room_key_with_settings(room_id, users, settings).await } /// Receive an unencrypted verification event. diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs index af8783a67d4..002612e0387 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs @@ -441,6 +441,34 @@ impl GroupSessionManager { self.sessions.clone() } + pub async fn share_room_key( + &self, + room_id: &RoomId, + users: impl Iterator, + ) -> OlmResult>> { + // Load settings from store or create new + let mut encryption_settings = + if let Some(settings) = self.store.load_encryption_settings(room_id).await? { + settings + } else { + let settings = EncryptionSettings::default(); + let changes = Changes { + encryption_settings: HashMap::from([(room_id.into(), settings.clone())]), + ..Default::default() + }; + self.store.save_changes(changes).await?; + settings + }; + + // Combine per-room and global unverified devices block + if !encryption_settings.only_allow_trusted_devices { + encryption_settings.only_allow_trusted_devices = + self.store.block_untrusted_devices_globally().await?; + } + + self.share_room_key_with_settings(room_id, users, encryption_settings).await + } + /// Get to-device requests to share a room key with users in a room. /// /// # Arguments @@ -451,7 +479,7 @@ impl GroupSessionManager { /// /// `encryption_settings` - The settings that should be used for /// the room key. - pub async fn share_room_key( + pub async fn share_room_key_with_settings( &self, room_id: &RoomId, users: impl Iterator, diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 87b3e39f891..12de050448c 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -29,7 +29,7 @@ use crate::{ gossiping::{GossipRequest, SecretInfo}, identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, olm::{OutboundGroupSession, PrivateCrossSigningIdentity}, - TrackedUser, + EncryptionSettings, TrackedUser, }; fn encode_key_info(info: &SecretInfo) -> String { diff --git a/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql b/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql new file mode 100644 index 00000000000..ca6382280d1 --- /dev/null +++ b/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql @@ -0,0 +1,4 @@ +CREATE TABLE "encryption_settings"( + "room_id" BLOB PRIMARY KEY NOT NULL, + "data" BLOB NOT NULL +); From 7e7c91699faa3c87c0f3e14a7f01223e399b84ea Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 24 Feb 2023 15:10:04 +0000 Subject: [PATCH 02/12] Generic key-value API --- Cargo.lock | 1 + crates/matrix-sdk-crypto/Cargo.toml | 1 + crates/matrix-sdk-crypto/src/machine.rs | 34 +++++++------------ .../src/session_manager/group_sessions.rs | 26 +++++++------- .../src/store/memorystore.rs | 10 +++++- crates/matrix-sdk-crypto/src/store/mod.rs | 30 +++++++++++++++- crates/matrix-sdk-crypto/src/store/traits.rs | 24 +++++++++++++ .../migrations/002_encryption_settings.sql | 4 --- crates/matrix-sdk-sqlite/src/crypto_store.rs | 26 ++++++++++++++ 9 files changed, 115 insertions(+), 41 deletions(-) delete mode 100644 crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql diff --git a/Cargo.lock b/Cargo.lock index c4eb656732c..c9ae8743fbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2753,6 +2753,7 @@ dependencies = [ "pbkdf2", "proptest", "rand 0.8.5", + "rmp-serde", "ruma", "serde", "serde_json", diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index f8e8d0d747d..c787ca3c475 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -47,6 +47,7 @@ pbkdf2 = { version = "0.11.0", default-features = false } rand = "0.8.5" ruma = { workspace = true, features = ["rand", "canonical-json", "unstable-msc2677"] } serde = { workspace = true, features = ["derive", "rc"] } +rmp-serde = "1.1.1" serde_json = { workspace = true } sha2 = "0.10.2" thiserror = { workspace = true } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index f897fbf9af7..02845628697 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -13,7 +13,7 @@ // limitations under the License. use std::{ - collections::{BTreeMap, BTreeSet, HashMap, HashSet}, + collections::{BTreeMap, BTreeSet, HashSet}, sync::Arc, time::Duration, }; @@ -515,8 +515,9 @@ impl OlmMachine { &self, room_id: &RoomId, ) -> StoreResult> { - let settings = self.store.load_encryption_settings(room_id).await?; - Ok(settings) + let key = format!("encryption_settings-{room_id}"); + let value = self.store.get_value(&key).await?; + Ok(value) } /// TODO: docs @@ -525,35 +526,26 @@ impl OlmMachine { room_id: &RoomId, settings: EncryptionSettings, ) -> StoreResult<()> { - self.store - .save_changes(Changes { - encryption_settings: HashMap::from([(room_id.into(), settings)]), - ..Default::default() - }) - .await?; + let key = format!("encryption_settings-{room_id}"); + self.store.set_value(&key, &settings).await?; Ok(()) } + /// TODO: docs + pub async fn get_block_untrusted_devices_globally(&self) -> StoreResult { + let value = self.store.get_value("block_untrusted_devices").await?.unwrap_or_default(); + Ok(value) + } + /// TODO: docs pub async fn set_block_untrusted_devices_globally( &self, block_untrusted_devices: bool, ) -> StoreResult<()> { - self.store - .save_changes(Changes { - block_untrusted_devices_globally: Some(block_untrusted_devices), - ..Default::default() - }) - .await?; + self.store.set_value("block_untrusted_devices", &block_untrusted_devices).await?; Ok(()) } - /// TODO: docs - pub async fn get_block_untrusted_devices_globally(&self) -> StoreResult { - let block = self.store.block_untrusted_devices_globally().await?; - Ok(block) - } - /// Receive a successful key claim response and create new Olm sessions with /// the claimed keys. /// diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs index 002612e0387..676cf3acfb7 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs @@ -441,29 +441,27 @@ impl GroupSessionManager { self.sessions.clone() } + /// TODO: Docs pub async fn share_room_key( &self, room_id: &RoomId, users: impl Iterator, ) -> OlmResult>> { // Load settings from store or create new - let mut encryption_settings = - if let Some(settings) = self.store.load_encryption_settings(room_id).await? { - settings - } else { - let settings = EncryptionSettings::default(); - let changes = Changes { - encryption_settings: HashMap::from([(room_id.into(), settings.clone())]), - ..Default::default() - }; - self.store.save_changes(changes).await?; - settings - }; + let key = format!("encryption_settings-{room_id}"); + let mut encryption_settings = if let Some(settings) = self.store.get_value(&key).await? { + settings + } else { + let settings = EncryptionSettings::default(); + self.store.set_value(&key, &settings).await?; + settings + }; // Combine per-room and global unverified devices block if !encryption_settings.only_allow_trusted_devices { - encryption_settings.only_allow_trusted_devices = - self.store.block_untrusted_devices_globally().await?; + let block_globally = + self.store.get_value("block_untrusted_devices").await?.unwrap_or_default(); + encryption_settings.only_allow_trusted_devices = block_globally } self.share_room_key_with_settings(room_id, users, encryption_settings).await diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 12de050448c..6ef56f9a42e 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -29,7 +29,7 @@ use crate::{ gossiping::{GossipRequest, SecretInfo}, identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, olm::{OutboundGroupSession, PrivateCrossSigningIdentity}, - EncryptionSettings, TrackedUser, + TrackedUser, }; fn encode_key_info(info: &SecretInfo) -> String { @@ -270,6 +270,14 @@ impl CryptoStore for MemoryStore { async fn load_backup_keys(&self) -> Result { Ok(BackupKeys::default()) } + + async fn get_custom_value(&self, _key: &str) -> Result>> { + Ok(None) + } + + async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { + Ok(()) + } } #[cfg(test)] diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 265730b3274..5eac5a8dd59 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -49,7 +49,7 @@ use atomic::Ordering; use dashmap::DashSet; use matrix_sdk_common::locks::Mutex; use ruma::{events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, UserId}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; use tracing::{info, warn}; use vodozemac::{megolm::SessionOrdering, Curve25519PublicKey}; @@ -751,6 +751,34 @@ impl Store { Ok(self.tracked_users_cache.iter().map(|u| u.clone()).collect()) } + + /// TODO: docs + pub async fn get_value(&self, key: &str) -> Result> { + let Some(value) = self.get_custom_value(key).await? else { + return Ok(None); + }; + let deserialized = self.deserialize_value(&value)?; + Ok(Some(deserialized)) + } + + /// TODO: docs + pub async fn set_value(&self, key: &str, value: &impl Serialize) -> Result<()> { + let serialized = self.serialize_value(value)?; + self.set_custom_value(key, serialized).await?; + Ok(()) + } + + fn serialize_value(&self, value: &impl Serialize) -> Result> { + let serialized = + rmp_serde::to_vec_named(value).map_err(|x| CryptoStoreError::Backend(x.into()))?; // TODO: Serialization type + Ok(serialized) + } + + fn deserialize_value(&self, value: &[u8]) -> Result { + let deserialized = + rmp_serde::from_slice(value).map_err(|e| CryptoStoreError::Backend(e.into()))?; + Ok(deserialized) + } } impl Deref for Store { diff --git a/crates/matrix-sdk-crypto/src/store/traits.rs b/crates/matrix-sdk-crypto/src/store/traits.rs index 690abe2179f..e58ea79e2c6 100644 --- a/crates/matrix-sdk-crypto/src/store/traits.rs +++ b/crates/matrix-sdk-crypto/src/store/traits.rs @@ -186,6 +186,22 @@ pub trait CryptoStore: AsyncTraitDeps { &self, request_id: &TransactionId, ) -> Result<(), Self::Error>; + + /// Get arbitrary data from the store + /// + /// # Arguments + /// + /// * `key` - The key to fetch data for + async fn get_custom_value(&self, key: &str) -> Result>, Self::Error>; + + /// Put arbitrary data into the store + /// + /// # Arguments + /// + /// * `key` - The key to insert data into + /// + /// * `value` - The value to insert + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<(), Self::Error>; } #[repr(transparent)] @@ -312,6 +328,14 @@ impl CryptoStore for EraseCryptoStoreError { async fn delete_outgoing_secret_requests(&self, request_id: &TransactionId) -> Result<()> { self.0.delete_outgoing_secret_requests(request_id).await.map_err(Into::into) } + + async fn get_custom_value(&self, key: &str) -> Result>, Self::Error> { + self.0.get_custom_value(key).await.map_err(Into::into) + } + + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<(), Self::Error> { + self.0.set_custom_value(key, value).await.map_err(Into::into) + } } /// A type-erased [`CryptoStore`]. diff --git a/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql b/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql deleted file mode 100644 index ca6382280d1..00000000000 --- a/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE "encryption_settings"( - "room_id" BLOB PRIMARY KEY NOT NULL, - "data" BLOB NOT NULL -); diff --git a/crates/matrix-sdk-sqlite/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index 2b5523c1899..1c90b44b4b1 100644 --- a/crates/matrix-sdk-sqlite/src/crypto_store.rs +++ b/crates/matrix-sdk-sqlite/src/crypto_store.rs @@ -946,6 +946,32 @@ impl CryptoStore for SqliteCryptoStore { let request_id = self.encode_key("key_requests", request_id.as_bytes()); Ok(self.acquire().await?.delete_key_request(request_id).await?) } + + async fn get_custom_value(&self, key: &str) -> Result>> { + let Some(serialized) = self.acquire().await?.get_kv(key).await? else { + return Ok(None); + }; + let value = if let Some(cipher) = &self.store_cipher { + let encrypted = rmp_serde::from_slice(&serialized)?; + cipher.decrypt_value_data(encrypted)? + } else { + serialized + }; + + Ok(Some(value)) + } + + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<()> { + let serialized = if let Some(cipher) = &self.store_cipher { + let encrypted = cipher.encrypt_value_data(value)?; + rmp_serde::to_vec_named(&encrypted)? + } else { + value + }; + + self.acquire().await?.set_kv(key, serialized).await?; + Ok(()) + } } #[cfg(test)] From 33b348c3760092b08ed78cd81b9c6628a24558da Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Mon, 27 Feb 2023 16:45:56 +0000 Subject: [PATCH 03/12] Constrain new api to Store struct --- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 52 ++++++++++++----- crates/matrix-sdk-base/src/client.rs | 3 +- crates/matrix-sdk-crypto/src/machine.rs | 56 +++---------------- .../src/session_manager/group_sessions.rs | 28 +--------- crates/matrix-sdk-crypto/src/store/mod.rs | 41 +++++++++++++- 5 files changed, 87 insertions(+), 93 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 1cd4b9fe4dd..8e6051d533e 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -569,8 +569,10 @@ impl OlmMachine { room_id: &str, ) -> Result, CryptoStoreError> { let room_id = RoomId::parse(room_id)?; - let settings = - self.runtime.block_on(self.inner.get_encryption_settings(&room_id))?.map(|v| v.into()); + let settings = self + .runtime + .block_on(self.inner.store().get_encryption_settings(&room_id))? + .map(|v| v.into()); Ok(settings) } @@ -583,9 +585,9 @@ impl OlmMachine { let room_id = RoomId::parse(room_id)?; self.runtime.block_on(async move { let mut settings = - self.inner.get_encryption_settings(&room_id).await?.unwrap_or_default(); + self.inner.store().get_encryption_settings(&room_id).await?.unwrap_or_default(); settings.algorithm = algorithm.into(); - self.inner.set_encryption_settings(&room_id, settings).await?; + self.inner.store().set_encryption_settings(&room_id, settings).await?; Ok(()) }) } @@ -599,9 +601,9 @@ impl OlmMachine { let room_id = RoomId::parse(room_id)?; self.runtime.block_on(async move { let mut settings = - self.inner.get_encryption_settings(&room_id).await?.unwrap_or_default(); + self.inner.store().get_encryption_settings(&room_id).await?.unwrap_or_default(); settings.only_allow_trusted_devices = block_untrusted_devices; - self.inner.set_encryption_settings(&room_id, settings).await?; + self.inner.store().set_encryption_settings(&room_id, settings).await?; Ok(()) }) } @@ -611,14 +613,16 @@ impl OlmMachine { &self, block_untrusted_devices: bool, ) -> Result<(), CryptoStoreError> { - self.runtime - .block_on(self.inner.set_block_untrusted_devices_globally(block_untrusted_devices))?; + self.runtime.block_on( + self.inner.store().set_block_untrusted_devices_globally(block_untrusted_devices), + )?; Ok(()) } /// TODO: docs pub fn is_blocking_untrusted_devices_globally(&self) -> Result { - let block = self.runtime.block_on(self.inner.get_block_untrusted_devices_globally())?; + let block = + self.runtime.block_on(self.inner.store().get_block_untrusted_devices_globally())?; Ok(block) } @@ -639,8 +643,6 @@ impl OlmMachine { /// /// * `users` - The list of users which are considered to be members of the /// room and should receive the room key. - /// - /// * `settings` - The settings that should be used for the room key. pub fn share_room_key( &self, room_id: String, @@ -650,9 +652,31 @@ impl OlmMachine { users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect(); let room_id = RoomId::parse(room_id)?; - let requests = self - .runtime - .block_on(self.inner.share_room_key(&room_id, users.iter().map(Deref::deref)))?; + let requests = self.runtime.block_on(async move { + // Load settings from store or create new + let mut encryption_settings = if let Some(settings) = + self.inner.store().get_encryption_settings(room_id.into()).await? + { + settings + } else { + let settings = EncryptionSettings::default(); + self.inner.store().set_encryption_settings(room_id.into(), settings).await?; + settings + }; + + // Combine per-room and global unverified devices block + if !encryption_settings.only_allow_trusted_devices { + let block_globally = self + .inner + .store() + .get_block_untrusted_devices_globally() + .await? + .unwrap_or_default(); + encryption_settings.only_allow_trusted_devices = block_globally + } + + self.inner.share_room_key(&room_id, users.iter().map(Deref::deref), settings).await + }); Ok(requests.into_iter().map(|r| r.as_ref().into()).collect()) } diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 2450c9d3da7..8be3af22433 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -1075,8 +1075,7 @@ impl BaseClient { let settings = settings.ok_or(Error::EncryptionNotEnabled)?; let settings = EncryptionSettings::new(settings, history_visibility, false); - Ok(o.share_room_key_with_settings(room_id, members.map(Deref::deref), settings) - .await?) + Ok(o.share_room_key(room_id, members.map(Deref::deref), settings).await?) } None => panic!("Olm machine wasn't started"), } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 02845628697..cd1dbd1745c 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -296,6 +296,11 @@ impl OlmMachine { Ok(OlmMachine::new_helper(user_id, device_id, store, account, identity)) } + /// TODO: docs + pub fn store(&self) -> &Store { + &self.store + } + /// The unique user id that owns this `OlmMachine` instance. pub fn user_id(&self) -> &UserId { &self.user_id @@ -510,42 +515,6 @@ impl OlmMachine { self.session_manager.get_missing_sessions(users).await } - /// TODO: docs - pub async fn get_encryption_settings( - &self, - room_id: &RoomId, - ) -> StoreResult> { - let key = format!("encryption_settings-{room_id}"); - let value = self.store.get_value(&key).await?; - Ok(value) - } - - /// TODO: docs - pub async fn set_encryption_settings( - &self, - room_id: &RoomId, - settings: EncryptionSettings, - ) -> StoreResult<()> { - let key = format!("encryption_settings-{room_id}"); - self.store.set_value(&key, &settings).await?; - Ok(()) - } - - /// TODO: docs - pub async fn get_block_untrusted_devices_globally(&self) -> StoreResult { - let value = self.store.get_value("block_untrusted_devices").await?.unwrap_or_default(); - Ok(value) - } - - /// TODO: docs - pub async fn set_block_untrusted_devices_globally( - &self, - block_untrusted_devices: bool, - ) -> StoreResult<()> { - self.store.set_value("block_untrusted_devices", &block_untrusted_devices).await?; - Ok(()) - } - /// Receive a successful key claim response and create new Olm sessions with /// the claimed keys. /// @@ -786,22 +755,15 @@ impl OlmMachine { /// used. /// /// `users` - The list of users that should receive the room key. + /// + /// `settings` - TODO: pub async fn share_room_key( &self, room_id: &RoomId, users: impl Iterator, + encryption_settings: impl Into, ) -> OlmResult>> { - self.group_session_manager.share_room_key(room_id, users).await - } - - /// TODO: docs - pub async fn share_room_key_with_settings( - &self, - room_id: &RoomId, - users: impl Iterator, - settings: impl Into, - ) -> OlmResult>> { - self.group_session_manager.share_room_key_with_settings(room_id, users, settings).await + self.group_session_manager.share_room_key(room_id, users, encryption_settings).await } /// Receive an unencrypted verification event. diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs index 676cf3acfb7..af8783a67d4 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs @@ -441,32 +441,6 @@ impl GroupSessionManager { self.sessions.clone() } - /// TODO: Docs - pub async fn share_room_key( - &self, - room_id: &RoomId, - users: impl Iterator, - ) -> OlmResult>> { - // Load settings from store or create new - let key = format!("encryption_settings-{room_id}"); - let mut encryption_settings = if let Some(settings) = self.store.get_value(&key).await? { - settings - } else { - let settings = EncryptionSettings::default(); - self.store.set_value(&key, &settings).await?; - settings - }; - - // Combine per-room and global unverified devices block - if !encryption_settings.only_allow_trusted_devices { - let block_globally = - self.store.get_value("block_untrusted_devices").await?.unwrap_or_default(); - encryption_settings.only_allow_trusted_devices = block_globally - } - - self.share_room_key_with_settings(room_id, users, encryption_settings).await - } - /// Get to-device requests to share a room key with users in a room. /// /// # Arguments @@ -477,7 +451,7 @@ impl GroupSessionManager { /// /// `encryption_settings` - The settings that should be used for /// the room key. - pub async fn share_room_key_with_settings( + pub async fn share_room_key( &self, room_id: &RoomId, users: impl Iterator, diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 5eac5a8dd59..2585ad46ef7 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -48,7 +48,9 @@ use std::{ use atomic::Ordering; use dashmap::DashSet; use matrix_sdk_common::locks::Mutex; -use ruma::{events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, UserId}; +use ruma::{ + events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, RoomId, UserId, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; use tracing::{info, warn}; @@ -66,7 +68,7 @@ use crate::{ }, utilities::encode, verification::VerificationMachine, - CrossSigningStatus, + CrossSigningStatus, EncryptionSettings, }; pub mod caches; @@ -93,7 +95,7 @@ pub use crate::gossiping::{GossipRequest, SecretInfo}; /// generics don't mix let the CryptoStore store strings and this wrapper /// adds the generic interface on top. #[derive(Debug, Clone)] -pub(crate) struct Store { +pub struct Store { user_id: Arc, identity: Arc>, inner: Arc, @@ -752,6 +754,39 @@ impl Store { Ok(self.tracked_users_cache.iter().map(|u| u.clone()).collect()) } + /// TODO: docs + pub async fn get_encryption_settings( + &self, + room_id: &RoomId, + ) -> Result> { + let key = format!("encryption_settings-{room_id}"); + self.get_value(&key).await + } + + /// TODO: docs + pub async fn set_encryption_settings( + &self, + room_id: &RoomId, + settings: EncryptionSettings, + ) -> Result<()> { + let key = format!("encryption_settings-{room_id}"); + self.set_value(&key, &settings).await + } + + /// TODO: docs + pub async fn get_block_untrusted_devices_globally(&self) -> Result { + let value = self.get_value("block_untrusted_devices").await?.unwrap_or_default(); + Ok(value) + } + + /// TODO: docs + pub async fn set_block_untrusted_devices_globally( + &self, + block_untrusted_devices: bool, + ) -> Result<()> { + self.set_value("block_untrusted_devices", &block_untrusted_devices).await + } + /// TODO: docs pub async fn get_value(&self, key: &str) -> Result> { let Some(value) = self.get_custom_value(key).await? else { From 283137f190fd36c468da6043296efb0dc08b4bad Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 28 Feb 2023 09:50:40 +0000 Subject: [PATCH 04/12] Introduce RoomSettings --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 66 +++++----- bindings/matrix-sdk-crypto-ffi/src/logger.rs | 10 +- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 122 ++++++++++-------- crates/matrix-sdk-crypto/src/machine.rs | 5 +- .../src/store/integration_tests.rs | 56 +++++++- .../src/store/memorystore.rs | 11 +- crates/matrix-sdk-crypto/src/store/mod.rs | 64 ++++----- crates/matrix-sdk-crypto/src/store/traits.rs | 17 ++- .../matrix-sdk-indexeddb/src/crypto_store.rs | 17 +++ crates/matrix-sdk-sled/src/crypto_store.rs | 19 ++- .../migrations/003_room_settings.sql | 4 + crates/matrix-sdk-sqlite/src/crypto_store.rs | 49 ++++++- 12 files changed, 314 insertions(+), 126 deletions(-) create mode 100644 crates/matrix-sdk-sqlite/migrations/003_room_settings.sql diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 2c03a98301a..bc8759ef3a8 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -33,6 +33,7 @@ use matrix_sdk_crypto::{ backups::SignatureState, olm::{IdentityKeys, InboundGroupSession, Session}, store::{Changes, CryptoStore}, + store::RoomSettings as RustRoomSettings, types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey}, EncryptionSettings as RustEncryptionSettings, LocalTrust, }; @@ -506,12 +507,16 @@ impl From for RustEventEncryptionAlgorithm { } } -impl From for EventEncryptionAlgorithm { - fn from(value: RustEventEncryptionAlgorithm) -> Self { +impl TryFrom for EventEncryptionAlgorithm { + type Error = String; + + fn try_from(value: RustEventEncryptionAlgorithm) -> Result { match value { - RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, - RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2, - _ => todo!(), + RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => { + Ok(Self::OlmV1Curve25519AesSha2) + } + RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(Self::MegolmV1AesSha2), + _ => Err(format!("Unsupported algorithm {value}")), } } } @@ -554,18 +559,6 @@ impl From for RustHistoryVisibility { } } -impl From for HistoryVisibility { - fn from(h: RustHistoryVisibility) -> Self { - match h { - RustHistoryVisibility::Invited => Self::Invited, - RustHistoryVisibility::Joined => Self::Joined, - RustHistoryVisibility::Shared => Self::Shared, - RustHistoryVisibility::WorldReadable => Self::WorldReadable, - _ => todo!(), - } - } -} - /// Settings that should be used when a room key is shared. /// /// These settings control which algorithm the room key should use, how long a @@ -601,18 +594,6 @@ impl From for RustEncryptionSettings { } } -impl From for EncryptionSettings { - fn from(value: RustEncryptionSettings) -> Self { - EncryptionSettings { - algorithm: value.algorithm.into(), - rotation_period: value.rotation_period.as_secs(), - rotation_period_msgs: value.rotation_period_msgs, - history_visibility: value.history_visibility.into(), - only_allow_trusted_devices: value.only_allow_trusted_devices, - } - } -} - /// An event that was successfully decrypted. pub struct DecryptedEvent { /// The decrypted version of the event. @@ -739,6 +720,33 @@ impl From for CrossSigningStatus { } } +/// Room encryption settings which are modified by state events or user options +pub struct RoomSettings { + /// The encryption algorithm that should be used in the room. + pub algorithm: EventEncryptionAlgorithm, + /// Should untrusted devices receive the room key, or should they be + /// excluded from the conversation. + pub only_allow_trusted_devices: bool, +} + +impl TryFrom for RoomSettings { + type Error = String; + + fn try_from(value: RustRoomSettings) -> Result { + let algorithm = value.algorithm.try_into()?; + Ok(Self { algorithm, only_allow_trusted_devices: value.only_allow_trusted_devices }) + } +} + +impl From for RustRoomSettings { + fn from(value: RoomSettings) -> Self { + Self { + algorithm: value.algorithm.into(), + only_allow_trusted_devices: value.only_allow_trusted_devices, + } + } +} + fn parse_user_id(user_id: &str) -> Result { ruma::UserId::parse(user_id).map_err(|e| CryptoStoreError::InvalidUserId(user_id.to_owned(), e)) } diff --git a/bindings/matrix-sdk-crypto-ffi/src/logger.rs b/bindings/matrix-sdk-crypto-ffi/src/logger.rs index ce9ea068741..c2c99111953 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/logger.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/logger.rs @@ -44,9 +44,13 @@ pub struct LoggerWrapper { pub fn set_logger(logger: Box) { let logger = LoggerWrapper { inner: Arc::new(Mutex::new(logger)) }; - let filter = EnvFilter::from_default_env().add_directive( - "matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"), - ); + let filter = EnvFilter::from_default_env() + .add_directive( + "matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"), + ) + .add_directive( + "matrix_sdk_sqlite=debug".parse().expect("Can't parse logging filter directive"), + ); let _ = tracing_subscriber::fmt() .with_writer(logger) diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 8e6051d533e..1f261d4155d 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -16,7 +16,7 @@ use matrix_sdk_crypto::{ }, decrypt_room_key_export, encrypt_room_key_export, olm::ExportedRoomKey, - store::RecoveryKey, + store::{Changes, RecoveryKey}, LocalTrust, OlmMachine as InnerMachine, UserIdentities, }; use ruma::{ @@ -54,8 +54,8 @@ use crate::{ BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, CrossSigningKeyExport, CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, EncryptionSettings, EventEncryptionAlgorithm, KeyImportError, KeysImportResult, MegolmV1BackupKey, - ProgressListener, Request, RequestType, RequestVerificationResult, RoomKeyCounts, Sas, - SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest, + ProgressListener, Request, RequestType, RequestVerificationResult, RoomKeyCounts, RoomSettings, + Sas, SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest, }; /// A high level state machine that handles E2EE for Matrix. @@ -563,21 +563,28 @@ impl OlmMachine { .map(|r| r.into())) } - /// TODO: docs - pub fn get_encryption_settings( + /// Get the stored room settings, such as the encryption algorithm or + /// whether to encrypt only for trusted devices. + /// + /// These settings can be modified via + /// [set_room_algorithm()](#method.set_room_algorithm) and + /// [set_room_only_allow_trusted_devices()](#method. + /// set_room_only_allow_trusted_devices) methods. + pub fn get_room_settings( &self, room_id: &str, - ) -> Result, CryptoStoreError> { + ) -> Result, CryptoStoreError> { let room_id = RoomId::parse(room_id)?; let settings = self .runtime - .block_on(self.inner.store().get_encryption_settings(&room_id))? - .map(|v| v.into()); + .block_on(self.inner.store().get_room_settings(&room_id))? + .map(|v| v.try_into().unwrap()); Ok(settings) } - /// TODO: docs - pub fn set_event_encryption_algorithm( + /// Set the room algorithm used for encrypting messages to one of the + /// available variants + pub fn set_room_algorithm( &self, room_id: &str, algorithm: EventEncryptionAlgorithm, @@ -585,47 +592,71 @@ impl OlmMachine { let room_id = RoomId::parse(room_id)?; self.runtime.block_on(async move { let mut settings = - self.inner.store().get_encryption_settings(&room_id).await?.unwrap_or_default(); + self.inner.store().get_room_settings(&room_id).await?.unwrap_or_default(); settings.algorithm = algorithm.into(); - self.inner.store().set_encryption_settings(&room_id, settings).await?; + self.inner + .store() + .save_changes(Changes { + room_settings: HashMap::from([(room_id, settings)]), + ..Default::default() + }) + .await?; Ok(()) }) } - /// TODO: docs - pub fn set_block_untrusted_devices_per_room( + /// Set flag whether this room should encrypt messages for untrusted + /// devices, or whether they should be excluded from the conversation. + /// + /// Note that per-room setting may be overridden by a global + /// [set_only_allow_trusted_devices()](#method. + /// set_only_allow_trusted_devices) method. + pub fn set_room_only_allow_trusted_devices( &self, room_id: &str, - block_untrusted_devices: bool, + only_allow_trusted_devices: bool, ) -> Result<(), CryptoStoreError> { let room_id = RoomId::parse(room_id)?; self.runtime.block_on(async move { let mut settings = - self.inner.store().get_encryption_settings(&room_id).await?.unwrap_or_default(); - settings.only_allow_trusted_devices = block_untrusted_devices; - self.inner.store().set_encryption_settings(&room_id, settings).await?; + self.inner.store().get_room_settings(&room_id).await?.unwrap_or_default(); + settings.only_allow_trusted_devices = only_allow_trusted_devices; + self.inner + .store() + .save_changes(Changes { + room_settings: HashMap::from([(room_id, settings)]), + ..Default::default() + }) + .await?; Ok(()) }) } - /// TODO: docs - pub fn set_block_untrusted_devices_globally( + /// Check whether there is a global flag to only encrypt messages for + /// trusted devices or for everyone. + /// + /// Note that if the global flag is false, individual rooms may still be + /// encrypting only for trusted devices, depending on the per-room + /// `only_allow_trusted_devices` flag. + pub fn get_only_allow_trusted_devices(&self) -> Result { + let block = self.runtime.block_on(self.inner.store().get_only_allow_trusted_devices())?; + Ok(block) + } + + /// Set global flag whether to encrypt messages for untrusted devices, or + /// whether they should be excluded from the conversation. + /// + /// Note that if enabled, it will override any per-room settings. + pub fn set_only_allow_trusted_devices( &self, - block_untrusted_devices: bool, + only_allow_trusted_devices: bool, ) -> Result<(), CryptoStoreError> { self.runtime.block_on( - self.inner.store().set_block_untrusted_devices_globally(block_untrusted_devices), + self.inner.store().set_only_allow_trusted_devices(only_allow_trusted_devices), )?; Ok(()) } - /// TODO: docs - pub fn is_blocking_untrusted_devices_globally(&self) -> Result { - let block = - self.runtime.block_on(self.inner.store().get_block_untrusted_devices_globally())?; - Ok(block) - } - /// Share a room key with the given list of users for the given room. /// /// After the request was sent out and a successful response was received @@ -643,40 +674,23 @@ impl OlmMachine { /// /// * `users` - The list of users which are considered to be members of the /// room and should receive the room key. + /// + /// * `settings` - The settings that should be used for the room key. pub fn share_room_key( &self, room_id: String, users: Vec, + settings: EncryptionSettings, ) -> Result, CryptoStoreError> { let users: Vec = users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect(); let room_id = RoomId::parse(room_id)?; - let requests = self.runtime.block_on(async move { - // Load settings from store or create new - let mut encryption_settings = if let Some(settings) = - self.inner.store().get_encryption_settings(room_id.into()).await? - { - settings - } else { - let settings = EncryptionSettings::default(); - self.inner.store().set_encryption_settings(room_id.into(), settings).await?; - settings - }; - - // Combine per-room and global unverified devices block - if !encryption_settings.only_allow_trusted_devices { - let block_globally = self - .inner - .store() - .get_block_untrusted_devices_globally() - .await? - .unwrap_or_default(); - encryption_settings.only_allow_trusted_devices = block_globally - } - - self.inner.share_room_key(&room_id, users.iter().map(Deref::deref), settings).await - }); + let requests = self.runtime.block_on(self.inner.share_room_key( + &room_id, + users.iter().map(Deref::deref), + settings, + ))?; Ok(requests.into_iter().map(|r| r.as_ref().into()).collect()) } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index cd1dbd1745c..e43ab2a841d 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -296,7 +296,7 @@ impl OlmMachine { Ok(OlmMachine::new_helper(user_id, device_id, store, account, identity)) } - /// TODO: docs + /// Get the crypto store associated with this `OlmMachine` instance. pub fn store(&self) -> &Store { &self.store } @@ -756,7 +756,8 @@ impl OlmMachine { /// /// `users` - The list of users that should receive the room key. /// - /// `settings` - TODO: + /// `settings` - Encryption settings that affect when are room keys rotated + /// and who are they shared with pub async fn share_room_key( &self, room_id: &RoomId, diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index b80414ff204..bc700b08fab 100644 --- a/crates/matrix-sdk-crypto/src/store/integration_tests.rs +++ b/crates/matrix-sdk-crypto/src/store/integration_tests.rs @@ -17,10 +17,12 @@ macro_rules! cryptostore_integration_tests { }, store::{ Changes, CryptoStore, DeviceChanges, GossipRequest, IdentityChanges, - RecoveryKey, + RecoveryKey, RoomSettings, }, testing::{get_device, get_other_identity, get_own_identity}, - types::events::room_key_request::MegolmV1AesSha2Content, + types::{ + events::room_key_request::MegolmV1AesSha2Content, EventEncryptionAlgorithm, + }, ReadOnlyDevice, SecretInfo, TrackedUser, }; @@ -646,6 +648,56 @@ macro_rules! cryptostore_integration_tests { "The loaded version matches to the one we stored" ); } + + #[async_test] + async fn room_settings_saving() { + let (account, store) = get_loaded_store("room_settings_saving").await; + + let room_1 = room_id!("!test_1:localhost"); + let settings_1 = RoomSettings { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + only_allow_trusted_devices: true, + }; + + let room_2 = room_id!("!test_2:localhost"); + let settings_2 = RoomSettings { + algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, + only_allow_trusted_devices: false, + }; + + let room_3 = room_id!("!test_3:localhost"); + + let changes = Changes { + room_settings: HashMap::from([ + (room_1.into(), settings_1.clone()), + (room_2.into(), settings_2.clone()), + ]), + ..Default::default() + }; + + store.save_changes(changes).await.unwrap(); + + let loaded_settings_1 = store.get_room_settings(room_1).await.unwrap(); + assert_eq!(Some(settings_1), loaded_settings_1); + + let loaded_settings_2 = store.get_room_settings(room_2).await.unwrap(); + assert_eq!(Some(settings_2), loaded_settings_2); + + let loaded_settings_3 = store.get_room_settings(room_3).await.unwrap(); + assert_eq!(None, loaded_settings_3); + } + + #[async_test] + async fn custom_value_saving() { + let (account, store) = get_loaded_store("custom_value_saving").await; + store.set_custom_value("A", "Hello".as_bytes().to_vec()).await.unwrap(); + + let loaded_1 = store.get_custom_value("A").await.unwrap(); + assert_eq!(Some("Hello".as_bytes().to_vec()), loaded_1); + + let loaded_2 = store.get_custom_value("B").await.unwrap(); + assert_eq!(None, loaded_2); + } } }; } diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 6ef56f9a42e..7d31264358e 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -20,10 +20,12 @@ use matrix_sdk_common::locks::Mutex; use ruma::{ DeviceId, OwnedDeviceId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId, }; +use tracing::warn; use super::{ caches::{DeviceStore, GroupSessionStore, SessionStore}, - BackupKeys, Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, RoomKeyCounts, Session, + BackupKeys, Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, RoomKeyCounts, + RoomSettings, Session, }; use crate::{ gossiping::{GossipRequest, SecretInfo}, @@ -271,11 +273,18 @@ impl CryptoStore for MemoryStore { Ok(BackupKeys::default()) } + async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { + warn!("Method not implemented"); + Ok(None) + } + async fn get_custom_value(&self, _key: &str) -> Result>> { + warn!("Method not implemented"); Ok(None) } async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { + warn!("Method not implemented"); Ok(()) } } diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 2585ad46ef7..898234e397a 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -49,7 +49,7 @@ use atomic::Ordering; use dashmap::DashSet; use matrix_sdk_common::locks::Mutex; use ruma::{ - events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, RoomId, UserId, + events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedRoomId, OwnedUserId, UserId, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; @@ -66,9 +66,10 @@ use crate::{ InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity, ReadOnlyAccount, Session, }, + types::EventEncryptionAlgorithm, utilities::encode, verification::VerificationMachine, - CrossSigningStatus, EncryptionSettings, + CrossSigningStatus, }; pub mod caches; @@ -120,6 +121,7 @@ pub struct Changes { pub key_requests: Vec, pub identities: IdentityChanges, pub devices: DeviceChanges, + pub room_settings: HashMap, } /// A user for which we are tracking the list of devices. @@ -279,6 +281,25 @@ pub enum SecretImportError { Store(#[from] CryptoStoreError), } +/// Room encryption settings which are modified by state events or user options +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct RoomSettings { + /// The encryption algorithm that should be used in the room. + pub algorithm: EventEncryptionAlgorithm, + /// Should untrusted devices receive the room key, or should they be + /// excluded from the conversation. + pub only_allow_trusted_devices: bool, +} + +impl Default for RoomSettings { + fn default() -> Self { + Self { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + only_allow_trusted_devices: false, + } + } +} + impl Store { /// Create a new Store pub fn new( @@ -754,40 +775,23 @@ impl Store { Ok(self.tracked_users_cache.iter().map(|u| u.clone()).collect()) } - /// TODO: docs - pub async fn get_encryption_settings( - &self, - room_id: &RoomId, - ) -> Result> { - let key = format!("encryption_settings-{room_id}"); - self.get_value(&key).await - } - - /// TODO: docs - pub async fn set_encryption_settings( - &self, - room_id: &RoomId, - settings: EncryptionSettings, - ) -> Result<()> { - let key = format!("encryption_settings-{room_id}"); - self.set_value(&key, &settings).await - } - - /// TODO: docs - pub async fn get_block_untrusted_devices_globally(&self) -> Result { - let value = self.get_value("block_untrusted_devices").await?.unwrap_or_default(); + /// Check whether there is a global flag to only encrypt messages for + /// trusted devices or for everyone. + pub async fn get_only_allow_trusted_devices(&self) -> Result { + let value = self.get_value("only_allow_trusted_devices").await?.unwrap_or_default(); Ok(value) } - /// TODO: docs - pub async fn set_block_untrusted_devices_globally( + /// Set global flag whether to encrypt messages for untrusted devices, or + /// whether they should be excluded from the conversation. + pub async fn set_only_allow_trusted_devices( &self, block_untrusted_devices: bool, ) -> Result<()> { - self.set_value("block_untrusted_devices", &block_untrusted_devices).await + self.set_value("only_allow_trusted_devices", &block_untrusted_devices).await } - /// TODO: docs + /// Get custom stored value associated with a key pub async fn get_value(&self, key: &str) -> Result> { let Some(value) = self.get_custom_value(key).await? else { return Ok(None); @@ -796,7 +800,7 @@ impl Store { Ok(Some(deserialized)) } - /// TODO: docs + /// Store custom value associated with a key pub async fn set_value(&self, key: &str, value: &impl Serialize) -> Result<()> { let serialized = self.serialize_value(value)?; self.set_custom_value(key, serialized).await?; @@ -805,7 +809,7 @@ impl Store { fn serialize_value(&self, value: &impl Serialize) -> Result> { let serialized = - rmp_serde::to_vec_named(value).map_err(|x| CryptoStoreError::Backend(x.into()))?; // TODO: Serialization type + rmp_serde::to_vec_named(value).map_err(|x| CryptoStoreError::Backend(x.into()))?; Ok(serialized) } diff --git a/crates/matrix-sdk-crypto/src/store/traits.rs b/crates/matrix-sdk-crypto/src/store/traits.rs index e58ea79e2c6..43d7021b2da 100644 --- a/crates/matrix-sdk-crypto/src/store/traits.rs +++ b/crates/matrix-sdk-crypto/src/store/traits.rs @@ -18,7 +18,7 @@ use async_trait::async_trait; use matrix_sdk_common::{locks::Mutex, AsyncTraitDeps}; use ruma::{DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId}; -use super::{BackupKeys, Changes, CryptoStoreError, Result, RoomKeyCounts}; +use super::{BackupKeys, Changes, CryptoStoreError, Result, RoomKeyCounts, RoomSettings}; use crate::{ olm::{ InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity, @@ -187,6 +187,17 @@ pub trait CryptoStore: AsyncTraitDeps { request_id: &TransactionId, ) -> Result<(), Self::Error>; + /// Get the room settings, such as the encryption algorithm or whether to + /// encrypt only for trusted devices. + /// + /// # Arguments + /// + /// * `room_id` - The room id of the room + async fn get_room_settings( + &self, + room_id: &RoomId, + ) -> Result, Self::Error>; + /// Get arbitrary data from the store /// /// # Arguments @@ -329,6 +340,10 @@ impl CryptoStore for EraseCryptoStoreError { self.0.delete_outgoing_secret_requests(request_id).await.map_err(Into::into) } + async fn get_room_settings(&self, room_id: &RoomId) -> Result> { + self.0.get_room_settings(room_id).await.map_err(Into::into) + } + async fn get_custom_value(&self, key: &str) -> Result>, Self::Error> { self.0.get_custom_value(key).await.map_err(Into::into) } diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index cbb60e18e44..d862e328edd 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -28,6 +28,7 @@ use matrix_sdk_crypto::{ }, store::{ caches::SessionStore, BackupKeys, Changes, CryptoStore, CryptoStoreError, RoomKeyCounts, + RoomSettings, }, GossipRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo, TrackedUser, @@ -35,6 +36,7 @@ use matrix_sdk_crypto::{ use matrix_sdk_store_encryption::StoreCipher; use ruma::{DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId}; use serde::{de::DeserializeOwned, Serialize}; +use tracing::warn; use wasm_bindgen::JsValue; use web_sys::IdbKeyRange; @@ -950,6 +952,21 @@ impl_crypto_store! { Ok(key) } + + async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { + warn!("Method not implemented"); + Ok(None) + } + + async fn get_custom_value(&self, _key: &str) -> Result>> { + warn!("Method not implemented"); + Ok(None) + } + + async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { + warn!("Method not implemented"); + Ok(()) + } } impl Drop for IndexeddbCryptoStore { diff --git a/crates/matrix-sdk-sled/src/crypto_store.rs b/crates/matrix-sdk-sled/src/crypto_store.rs index 4b654824eb1..ef8c5a9931f 100644 --- a/crates/matrix-sdk-sled/src/crypto_store.rs +++ b/crates/matrix-sdk-sled/src/crypto_store.rs @@ -28,7 +28,7 @@ use matrix_sdk_crypto::{ }, store::{ caches::SessionStore, BackupKeys, Changes, CryptoStore, CryptoStoreError, Result, - RoomKeyCounts, + RoomKeyCounts, RoomSettings, }, types::{events::room_key_request::SupportedKeyInfo, EventEncryptionAlgorithm}, GossipRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo, @@ -41,7 +41,7 @@ use sled::{ transaction::{ConflictableTransactionError, TransactionError}, Batch, Config, Db, IVec, Transactional, Tree, }; -use tracing::debug; +use tracing::{debug, warn}; use super::OpenStoreError; use crate::encode_key::{EncodeKey, ENCODE_SEPARATOR}; @@ -1010,6 +1010,21 @@ impl CryptoStore for SledCryptoStore { Ok(key) } + + async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { + warn!("Method not implemented"); + Ok(None) + } + + async fn get_custom_value(&self, _key: &str) -> Result>> { + warn!("Method not implemented"); + Ok(None) + } + + async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { + warn!("Method not implemented"); + Ok(()) + } } #[cfg(test)] diff --git a/crates/matrix-sdk-sqlite/migrations/003_room_settings.sql b/crates/matrix-sdk-sqlite/migrations/003_room_settings.sql new file mode 100644 index 00000000000..91fcbae1fd4 --- /dev/null +++ b/crates/matrix-sdk-sqlite/migrations/003_room_settings.sql @@ -0,0 +1,4 @@ +CREATE TABLE room_settings( + "room_id" BLOB PRIMARY KEY NOT NULL, + "data" BLOB NOT NULL +); diff --git a/crates/matrix-sdk-sqlite/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index 1c90b44b4b1..eedf4f0132b 100644 --- a/crates/matrix-sdk-sqlite/src/crypto_store.rs +++ b/crates/matrix-sdk-sqlite/src/crypto_store.rs @@ -27,7 +27,7 @@ use matrix_sdk_crypto::{ IdentityKeys, InboundGroupSession, OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity, Session, }, - store::{caches::SessionStore, BackupKeys, Changes, CryptoStore, RoomKeyCounts}, + store::{caches::SessionStore, BackupKeys, Changes, CryptoStore, RoomKeyCounts, RoomSettings}, GossipRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo, TrackedUser, }; @@ -172,7 +172,7 @@ impl SqliteCryptoStore { } } -const DATABASE_VERSION: u8 = 2; +const DATABASE_VERSION: u8 = 3; async fn run_migrations(conn: &SqliteConn) -> rusqlite::Result<()> { let kv_exists = conn @@ -221,6 +221,13 @@ async fn run_migrations(conn: &SqliteConn) -> rusqlite::Result<()> { .await?; } + if version < 3 { + conn.with_transaction(|txn| { + txn.execute_batch(include_str!("../migrations/003_room_settings.sql")) + }) + .await?; + } + conn.set_kv("version", vec![DATABASE_VERSION]).await?; Ok(()) @@ -257,6 +264,8 @@ trait SqliteConnectionExt { sent_out: bool, data: &[u8], ) -> rusqlite::Result<()>; + + fn set_room_settings(&self, room_id: &[u8], data: &[u8]) -> rusqlite::Result<()>; } impl SqliteConnectionExt for rusqlite::Connection { @@ -348,6 +357,16 @@ impl SqliteConnectionExt for rusqlite::Connection { )?; Ok(()) } + + fn set_room_settings(&self, room_id: &[u8], data: &[u8]) -> rusqlite::Result<()> { + self.execute( + "INSERT INTO room_settings (room_id, data) + VALUES (?1, ?2) + ON CONFLICT (room_id) DO UPDATE SET data = ?2", + (room_id, data), + )?; + Ok(()) + } } #[async_trait] @@ -515,6 +534,15 @@ trait SqliteObjectCryptoStoreExt: SqliteObjectExt { self.execute("DELETE FROM key_requests WHERE request_id = ?", (request_id,)).await?; Ok(()) } + + async fn get_room_settings(&self, room_id: Key) -> Result>> { + Ok(self + .query_row("SELECT data FROM room_settings WHERE room_id = ?", (room_id,), |row| { + row.get(0) + }) + .await + .optional()?) + } } #[async_trait] @@ -690,6 +718,12 @@ impl CryptoStore for SqliteCryptoStore { txn.set_key_request(&request_id, request.sent_out, &serialized_request)?; } + for (room_id, settings) in changes.room_settings { + let room_id = this.encode_key("room_settings", room_id.as_bytes()); + let value = this.serialize_value(&settings)?; + txn.set_room_settings(&room_id, &value)?; + } + Ok::<_, Error>(()) }) .await?; @@ -947,6 +981,17 @@ impl CryptoStore for SqliteCryptoStore { Ok(self.acquire().await?.delete_key_request(request_id).await?) } + async fn get_room_settings(&self, room_id: &RoomId) -> Result> { + let room_id = self.encode_key("room_settings", room_id.as_bytes()); + let Some(value) = self.acquire().await?.get_room_settings(room_id).await? else { + return Ok(None); + }; + + let settings = self.deserialize_value(&value)?; + + return Ok(Some(settings)); + } + async fn get_custom_value(&self, key: &str) -> Result>> { let Some(serialized) = self.acquire().await?.get_kv(key).await? else { return Ok(None); From 51b3bf4c75b05369d744178376c813597fb76e32 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 2 Mar 2023 10:16:01 +0000 Subject: [PATCH 05/12] Add room settings migration --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 76 +++++++++++++++++++++- bindings/matrix-sdk-crypto-ffi/src/olm.udl | 1 + 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index bc8759ef3a8..62481a29a0b 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -76,6 +76,8 @@ pub struct MigrationData { cross_signing: CrossSigningKeyExport, /// The list of users that the Rust SDK should track. tracked_users: Vec, + /// Map of room settings + room_settings: HashMap, } /// Struct collecting data that is important to migrate sessions to the rust-sdk @@ -201,6 +203,48 @@ pub fn migrate( }) } +/// Migrate room settings, including room algorithm and whether to block +/// untrusted devices from legacy store to Sqlite store. +/// +/// Note that this method should only be used if a client has already migrated +/// account data via [migrate](#method.migrate) method, which did not include +/// room settings. For a brand new migration, the [migrate](#method.migrate) +/// method will take care of room settings automatically, if provided. +/// +/// # Arguments +/// +/// * `room_settings` - Map of room settings +/// +/// * `path` - The path where the Sqlite store should be created. +/// +/// * `passphrase` - The passphrase that should be used to encrypt the data at +/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store +/// and all its data will remain unencrypted. +pub fn migrate_room_settings( + room_settings: HashMap, + path: &str, + passphrase: Option, +) -> anyhow::Result<()> { + use matrix_sdk_crypto::store::{Changes as RustChanges, CryptoStore}; + use tokio::runtime::Runtime; + + let runtime = Runtime::new()?; + runtime.block_on(async move { + let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?; + + let mut rust_settings = HashMap::new(); + for (room_id, settings) in room_settings { + let room_id = RoomId::parse(room_id)?; + rust_settings.insert(room_id, settings.into()); + } + + let changes = RustChanges { room_settings: rust_settings, ..Default::default() }; + store.save_changes(changes).await?; + + Ok(()) + }) +} + async fn migrate_data( mut data: MigrationData, path: &str, @@ -297,6 +341,12 @@ async fn migrate_data( processed_steps += 1; listener(processed_steps, total_steps); + let mut room_settings = HashMap::new(); + for (room_id, settings) in data.room_settings { + let room_id = RoomId::parse(room_id)?; + room_settings.insert(room_id, settings.into()); + } + let changes = Changes { account: Some(account), private_identity: Some(cross_signing), @@ -304,6 +354,7 @@ async fn migrate_data( inbound_group_sessions, recovery_key, backup_version: data.backup_version, + room_settings, ..Default::default() }; @@ -491,6 +542,7 @@ impl ProgressListener for T { } /// An encryption algorithm to be used to encrypt messages sent to a room. +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub enum EventEncryptionAlgorithm { /// Olm version 1 using Curve25519, AES-256, and SHA-256. OlmV1Curve25519AesSha2, @@ -721,6 +773,7 @@ impl From for CrossSigningStatus { } /// Room encryption settings which are modified by state events or user options +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub struct RoomSettings { /// The encryption algorithm that should be used in the room. pub algorithm: EventEncryptionAlgorithm, @@ -774,7 +827,7 @@ mod test { use tempfile::tempdir; use super::MigrationData; - use crate::{migrate, OlmMachine}; + use crate::{migrate, OlmMachine, RoomSettings, EventEncryptionAlgorithm}; #[test] fn android_migration() -> Result<()> { @@ -857,7 +910,17 @@ mod test { "@this-is-me:matrix.org", "@Amandine:matrix.org", "@ganfra:matrix.org" - ] + ], + "room_settings": { + "!AZkqtjvtwPAuyNOXEt:matrix.org": { + "algorithm": "OlmV1Curve25519AesSha2", + "only_allow_trusted_devices": true + }, + "!CWLUCoEWXSFyTCOtfL:matrix.org": { + "algorithm": "MegolmV1AesSha2", + "only_allow_trusted_devices": false + }, + } }); let migration_data: MigrationData = serde_json::from_value(data)?; @@ -886,6 +949,15 @@ mod test { let backup_keys = machine.get_backup_keys()?; assert!(backup_keys.is_some()); + let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org")?; + assert_eq!(Some(RoomSettings { algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, only_allow_trusted_devices: true }), settings1); + + let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org")?; + assert_eq!(Some(RoomSettings { algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, only_allow_trusted_devices: false }), settings2); + + let settings3 = machine.get_room_settings("!XYZ:matrix.org")?; + assert!(settings3.is_none()); + Ok(()) } } diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index 62aa0ffbc30..679e504e3cf 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -429,6 +429,7 @@ dictionary MigrationData { sequence pickle_key; CrossSigningKeyExport cross_signing; sequence tracked_users; + record room_settings; }; dictionary SessionMigrationData { From 8f1302333311cd69ad1d19cd5770fbe96f97cb71 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 7 Mar 2023 10:16:30 +0000 Subject: [PATCH 06/12] Missing stores --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 20 +++++-- .../matrix-sdk-indexeddb/src/crypto_store.rs | 60 ++++++++++++++++--- crates/matrix-sdk-sled/src/crypto_store.rs | 42 +++++++++---- 3 files changed, 99 insertions(+), 23 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 62481a29a0b..c44ec6cf273 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -827,7 +827,7 @@ mod test { use tempfile::tempdir; use super::MigrationData; - use crate::{migrate, OlmMachine, RoomSettings, EventEncryptionAlgorithm}; + use crate::{migrate, EventEncryptionAlgorithm, OlmMachine, RoomSettings}; #[test] fn android_migration() -> Result<()> { @@ -950,13 +950,25 @@ mod test { assert!(backup_keys.is_some()); let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org")?; - assert_eq!(Some(RoomSettings { algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, only_allow_trusted_devices: true }), settings1); + assert_eq!( + Some(RoomSettings { + algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, + only_allow_trusted_devices: true + }), + settings1 + ); let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org")?; - assert_eq!(Some(RoomSettings { algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, only_allow_trusted_devices: false }), settings2); + assert_eq!( + Some(RoomSettings { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + only_allow_trusted_devices: false + }), + settings2 + ); let settings3 = machine.get_room_settings("!XYZ:matrix.org")?; - assert!(settings3.is_none()); + assert!(settings3.is_none()); Ok(()) } diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index d862e328edd..3403d1c01c3 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -36,7 +36,6 @@ use matrix_sdk_crypto::{ use matrix_sdk_store_encryption::StoreCipher; use ruma::{DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId}; use serde::{de::DeserializeOwned, Serialize}; -use tracing::warn; use wasm_bindgen::JsValue; use web_sys::IdbKeyRange; @@ -61,6 +60,7 @@ mod keys { pub const UNSENT_SECRET_REQUESTS: &str = "unsent_secret_requests"; pub const SECRET_REQUESTS_BY_INFO: &str = "secret_requests_by_info"; pub const KEY_REQUEST: &str = "key_request"; + pub const ROOM_SETTINGS: &str = "room_settings"; // keys pub const STORE_CIPHER: &str = "store_cipher"; @@ -167,6 +167,9 @@ impl IndexeddbCryptoStore { db.create_object_store(keys::SECRET_REQUESTS_BY_INFO)?; db.create_object_store(keys::BACKUP_KEYS)?; + + db.create_object_store(keys::ROOM_SETTINGS)?; + } else if old_version < 1.1 { // We changed how we store inbound group sessions, the key used to // be a trippled of `(room_id, sender_key, session_id)` now it's a @@ -365,9 +368,20 @@ impl_crypto_store! { !changes.identities.new.is_empty() || !changes.identities.changed.is_empty(), keys::IDENTITIES, ), +<<<<<<< HEAD (!changes.inbound_group_sessions.is_empty(), keys::INBOUND_GROUP_SESSIONS), (!changes.outbound_group_sessions.is_empty(), keys::OUTBOUND_GROUP_SESSIONS), (!changes.message_hashes.is_empty(), keys::OLM_HASHES), +||||||| parent of b0932a23c (Missing stores) + (!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS), + (!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS), + (!changes.message_hashes.is_empty(), KEYS::OLM_HASHES), +======= + (!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS), + (!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS), + (!changes.message_hashes.is_empty(), KEYS::OLM_HASHES), + (!changes.room_settings.is_empty(), KEYS::ROOM_SETTINGS), +>>>>>>> b0932a23c (Missing stores) ] .iter() .filter_map(|(id, key)| if *id { Some(*key) } else { None }) @@ -476,6 +490,7 @@ impl_crypto_store! { let identity_changes = changes.identities; let olm_hashes = changes.message_hashes; let key_requests = changes.key_requests; + let room_settings_changes = changes.room_settings; if !device_changes.new.is_empty() || !device_changes.changed.is_empty() { let device_store = tx.object_store(keys::DEVICES)?; @@ -540,6 +555,16 @@ impl_crypto_store! { } } + if !room_settings_changes.is_empty() { + let settings_store = tx.object_store(KEYS::ROOM_SETTINGS)?; + + for (room_id, settings) in &room_settings_changes { + let key = self.encode_key(KEYS::ROOM_SETTINGS, room_id); + let value = self.serialize_value(&settings)?; + settings_store.put_key_val(&key, &value)?; + } + } + tx.await.into_result()?; // all good, let's update our caches:indexeddb @@ -953,18 +978,35 @@ impl_crypto_store! { Ok(key) } - async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { - warn!("Method not implemented"); - Ok(None) + async fn get_room_settings(&self, room_id: &RoomId) -> Result> { + let key = self.encode_key(KEYS::ROOM_SETTINGS, room_id); + Ok(self + .inner + .transaction_on_one_with_mode(KEYS::ROOM_SETTINGS, IdbTransactionMode::Readonly)? + .object_store(KEYS::ROOM_SETTINGS)? + .get(&key)? + .await? + .map(|v| self.deserialize_value(v)) + .transpose()?) } - async fn get_custom_value(&self, _key: &str) -> Result>> { - warn!("Method not implemented"); - Ok(None) + async fn get_custom_value(&self, key: &str) -> Result>> { + Ok(self + .inner + .transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readonly)? + .object_store(KEYS::CORE)? + .get(&JsValue::from_str(key))? + .await? + .map(|v| self.deserialize_value(v)) + .transpose()?) } - async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { - warn!("Method not implemented"); + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<()> { + self + .inner + .transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readwrite)? + .object_store(KEYS::CORE)? + .put_key_val(&JsValue::from_str(key), &self.serialize_value(&value)?)?; Ok(()) } } diff --git a/crates/matrix-sdk-sled/src/crypto_store.rs b/crates/matrix-sdk-sled/src/crypto_store.rs index ef8c5a9931f..01652fb597b 100644 --- a/crates/matrix-sdk-sled/src/crypto_store.rs +++ b/crates/matrix-sdk-sled/src/crypto_store.rs @@ -41,12 +41,12 @@ use sled::{ transaction::{ConflictableTransactionError, TransactionError}, Batch, Config, Db, IVec, Transactional, Tree, }; -use tracing::{debug, warn}; +use tracing::debug; use super::OpenStoreError; use crate::encode_key::{EncodeKey, ENCODE_SEPARATOR}; -const DATABASE_VERSION: u8 = 6; +const DATABASE_VERSION: u8 = 7; // Table names that are used to derive a separate key for each tree. This ensure // that user ids encoded for different trees won't end up as the same byte @@ -58,6 +58,7 @@ const INBOUND_GROUP_TABLE_NAME: &str = "crypto-store-inbound-group-sessions"; const OUTBOUND_GROUP_TABLE_NAME: &str = "crypto-store-outbound-group-sessions"; const SECRET_REQUEST_BY_INFO_TABLE: &str = "crypto-store-secret-request-by-info"; const TRACKED_USERS_TABLE: &str = "crypto-store-secret-tracked-users"; +const ROOM_SETTINGS_TABLE: &str = "crypto-store-secret-room-settings"; impl EncodeKey for InboundGroupSession { fn encode(&self) -> Vec { @@ -185,6 +186,8 @@ pub struct SledCryptoStore { identities: Tree, tracked_users: Tree, + + room_settings: Tree, } impl std::fmt::Debug for SledCryptoStore { @@ -386,6 +389,8 @@ impl SledCryptoStore { let unsent_secret_requests = db.open_tree("unsent_secret_requests")?; let secret_requests_by_info = db.open_tree("secret_requests_by_info")?; + let room_settings = db.open_tree("room_settings")?; + let session_cache = SessionStore::new(); let database = Self { @@ -406,6 +411,7 @@ impl SledCryptoStore { tracked_users, olm_hashes, identities, + room_settings, }; database.upgrade().await?; @@ -499,6 +505,7 @@ impl SledCryptoStore { let olm_hashes = changes.message_hashes; let key_requests = changes.key_requests; let backup_version = changes.backup_version; + let room_settings_changes = changes.room_settings; let ret: Result<(), TransactionError> = ( &self.account, @@ -512,6 +519,7 @@ impl SledCryptoStore { &self.outgoing_secret_requests, &self.unsent_secret_requests, &self.secret_requests_by_info, + &self.room_settings, ) .transaction( |( @@ -526,6 +534,7 @@ impl SledCryptoStore { outgoing_secret_requests, unsent_secret_requests, secret_requests_by_info, + room_settings, )| { if let Some(a) = &account_pickle { account.insert( @@ -635,6 +644,15 @@ impl SledCryptoStore { } } + for (room_id, settings) in &room_settings_changes { + let key = self.encode_key(ROOM_SETTINGS_TABLE, room_id); + room_settings.insert( + key.as_slice(), + self.serialize_value(&settings) + .map_err(ConflictableTransactionError::Abort)?, + )?; + } + Ok(()) }, ); @@ -1011,18 +1029,22 @@ impl CryptoStore for SledCryptoStore { Ok(key) } - async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { - warn!("Method not implemented"); - Ok(None) + async fn get_room_settings(&self, room_id: &RoomId) -> Result> { + let key = self.encode_key(ROOM_SETTINGS_TABLE, room_id); + self.room_settings + .get(key) + .map_err(CryptoStoreError::backend)? + .map(|p| self.deserialize_value(&p)) + .transpose() } - async fn get_custom_value(&self, _key: &str) -> Result>> { - warn!("Method not implemented"); - Ok(None) + async fn get_custom_value(&self, key: &str) -> Result>> { + let value = self.inner.get(key).map_err(CryptoStoreError::backend)?.map(|v| v.to_vec()); + Ok(value) } - async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { - warn!("Method not implemented"); + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<()> { + self.inner.insert(key, value).map_err(CryptoStoreError::backend)?; Ok(()) } } From 98ca774118d82112db8de3e0b05be15539767bf8 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 7 Mar 2023 15:53:53 +0000 Subject: [PATCH 07/12] Constrain store methods --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 11 ++-- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 3 +- crates/matrix-sdk-crypto/src/store/mod.rs | 52 +++++++++---------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index c44ec6cf273..06ca4540414 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -225,9 +225,6 @@ pub fn migrate_room_settings( path: &str, passphrase: Option, ) -> anyhow::Result<()> { - use matrix_sdk_crypto::store::{Changes as RustChanges, CryptoStore}; - use tokio::runtime::Runtime; - let runtime = Runtime::new()?; runtime.block_on(async move { let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?; @@ -238,7 +235,7 @@ pub fn migrate_room_settings( rust_settings.insert(room_id, settings.into()); } - let changes = RustChanges { room_settings: rust_settings, ..Default::default() }; + let changes = Changes { room_settings: rust_settings, ..Default::default() }; store.save_changes(changes).await?; Ok(()) @@ -560,7 +557,7 @@ impl From for RustEventEncryptionAlgorithm { } impl TryFrom for EventEncryptionAlgorithm { - type Error = String; + type Error = serde_json::Error; fn try_from(value: RustEventEncryptionAlgorithm) -> Result { match value { @@ -568,7 +565,7 @@ impl TryFrom for EventEncryptionAlgorithm { Ok(Self::OlmV1Curve25519AesSha2) } RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(Self::MegolmV1AesSha2), - _ => Err(format!("Unsupported algorithm {value}")), + _ => Err(serde::de::Error::custom(format!("Unsupported algorithm {value}"))), } } } @@ -783,7 +780,7 @@ pub struct RoomSettings { } impl TryFrom for RoomSettings { - type Error = String; + type Error = serde_json::Error; fn try_from(value: RustRoomSettings) -> Result { let algorithm = value.algorithm.try_into()?; diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 1f261d4155d..fbade20e9d2 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -578,7 +578,8 @@ impl OlmMachine { let settings = self .runtime .block_on(self.inner.store().get_room_settings(&room_id))? - .map(|v| v.try_into().unwrap()); + .map(|v| v.try_into()) + .transpose()?; Ok(settings) } diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 898234e397a..6564d6bc6bc 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -302,7 +302,7 @@ impl Default for RoomSettings { impl Store { /// Create a new Store - pub fn new( + pub(crate) fn new( user_id: Arc, identity: Arc>, store: Arc, @@ -321,33 +321,33 @@ impl Store { } /// UserId associated with this store - pub fn user_id(&self) -> &UserId { + pub(crate) fn user_id(&self) -> &UserId { &self.user_id } /// DeviceId associated with this store - pub fn device_id(&self) -> &DeviceId { + pub(crate) fn device_id(&self) -> &DeviceId { self.verification_machine.own_device_id() } /// The Account associated with this store - pub fn account(&self) -> &ReadOnlyAccount { + pub(crate) fn account(&self) -> &ReadOnlyAccount { &self.verification_machine.store.account } #[cfg(test)] /// test helper to reset the cross signing identity - pub async fn reset_cross_signing_identity(&self) { + pub(crate) async fn reset_cross_signing_identity(&self) { self.identity.lock().await.reset().await; } /// PrivateCrossSigningIdentity associated with this store - pub fn private_identity(&self) -> Arc> { + pub(crate) fn private_identity(&self) -> Arc> { self.identity.clone() } /// Save the given Sessions to the store - pub async fn save_sessions(&self, sessions: &[Session]) -> Result<()> { + pub(crate) async fn save_sessions(&self, sessions: &[Session]) -> Result<()> { let changes = Changes { sessions: sessions.to_vec(), ..Default::default() }; self.save_changes(changes).await @@ -359,7 +359,7 @@ impl Store { /// This method returns `SessionOrdering::Better` if the given session is /// better than the one we already have or if we don't have such a /// session in the store. - pub async fn compare_group_session( + pub(crate) async fn compare_group_session( &self, session: &InboundGroupSession, ) -> Result { @@ -375,7 +375,7 @@ impl Store { #[cfg(test)] /// Testing helper to allow to save only a set of devices - pub async fn save_devices(&self, devices: &[ReadOnlyDevice]) -> Result<()> { + pub(crate) async fn save_devices(&self, devices: &[ReadOnlyDevice]) -> Result<()> { let changes = Changes { devices: DeviceChanges { changed: devices.to_vec(), ..Default::default() }, ..Default::default() @@ -386,7 +386,7 @@ impl Store { #[cfg(test)] /// Testing helper to allo to save only a set of InboundGroupSession - pub async fn save_inbound_group_sessions( + pub(crate) async fn save_inbound_group_sessions( &self, sessions: &[InboundGroupSession], ) -> Result<()> { @@ -396,7 +396,7 @@ impl Store { } /// Get the display name of our own device. - pub async fn device_display_name(&self) -> Result, CryptoStoreError> { + pub(crate) async fn device_display_name(&self) -> Result, CryptoStoreError> { Ok(self .inner .get_device(self.user_id(), self.device_id()) @@ -405,7 +405,7 @@ impl Store { } /// Get the read-only device associated with `device_id` for `user_id` - pub async fn get_readonly_device( + pub(crate) async fn get_readonly_device( &self, user_id: &UserId, device_id: &DeviceId, @@ -416,7 +416,7 @@ impl Store { /// Get the read-only version of all the devices that the given user has. /// /// *Note*: This doesn't return our own device. - pub async fn get_readonly_devices_filtered( + pub(crate) async fn get_readonly_devices_filtered( &self, user_id: &UserId, ) -> Result> { @@ -431,7 +431,7 @@ impl Store { /// Get the read-only version of all the devices that the given user has. /// /// *Note*: This does also return our own device. - pub async fn get_readonly_devices_unfiltered( + pub(crate) async fn get_readonly_devices_unfiltered( &self, user_id: &UserId, ) -> Result> { @@ -441,7 +441,7 @@ impl Store { /// Get a device for the given user with the given curve25519 key. /// /// *Note*: This doesn't return our own device. - pub async fn get_device_from_curve_key( + pub(crate) async fn get_device_from_curve_key( &self, user_id: &UserId, curve_key: Curve25519PublicKey, @@ -454,7 +454,7 @@ impl Store { /// Get all devices associated with the given `user_id` /// /// *Note*: This doesn't return our own device. - pub async fn get_user_devices_filtered(&self, user_id: &UserId) -> Result { + pub(crate) async fn get_user_devices_filtered(&self, user_id: &UserId) -> Result { self.get_user_devices(user_id).await.map(|mut d| { if user_id == self.user_id() { d.inner.remove(self.device_id()); @@ -466,7 +466,7 @@ impl Store { /// Get all devices associated with the given `user_id` /// /// *Note*: This does also return our own device. - pub async fn get_user_devices(&self, user_id: &UserId) -> Result { + pub(crate) async fn get_user_devices(&self, user_id: &UserId) -> Result { let devices = self.get_readonly_devices_unfiltered(user_id).await?; let own_identity = @@ -482,7 +482,7 @@ impl Store { } /// Get a Device copy associated with `device_id` for `user_id` - pub async fn get_device( + pub(crate) async fn get_device( &self, user_id: &UserId, device_id: &DeviceId, @@ -500,7 +500,7 @@ impl Store { } /// Get the Identity of `user_id` - pub async fn get_identity(&self, user_id: &UserId) -> Result> { + pub(crate) async fn get_identity(&self, user_id: &UserId) -> Result> { // let own_identity = // self.inner.get_user_identity(self.user_id()).await?.and_then(|i| i.own()); Ok(if let Some(identity) = self.inner.get_user_identity(user_id).await? { @@ -540,7 +540,7 @@ impl Store { /// # Arguments /// /// * `secret_name` - The name of the secret that should be exported. - pub async fn export_secret(&self, secret_name: &SecretName) -> Option { + pub(crate) async fn export_secret(&self, secret_name: &SecretName) -> Option { match secret_name { SecretName::CrossSigningMasterKey | SecretName::CrossSigningUserSigningKey @@ -567,7 +567,7 @@ impl Store { } /// Import the Cross Signing Keys - pub async fn import_cross_signing_keys( + pub(crate) async fn import_cross_signing_keys( &self, export: CrossSigningKeyExport, ) -> Result { @@ -597,7 +597,7 @@ impl Store { } /// Import the given `secret` named `secret_name` into the keystore. - pub async fn import_secret( + pub(crate) async fn import_secret( &self, secret_name: &SecretName, secret: &str, @@ -642,7 +642,7 @@ impl Store { /// /// This means that the user will be considered for a `/keys/query` request /// next time [`Store::users_for_key_query()`] is called. - pub async fn mark_user_as_changed(&self, user: &UserId) -> Result<()> { + pub(crate) async fn mark_user_as_changed(&self, user: &UserId) -> Result<()> { self.users_for_key_query.lock().await.insert_user(user); self.tracked_users_cache.insert(user.to_owned()); @@ -653,7 +653,7 @@ impl Store { /// /// Any users not already on the list are flagged as awaiting a key query. /// Users that were already in the list are unaffected. - pub async fn update_tracked_users(&self, users: impl Iterator) -> Result<()> { + pub(crate) async fn update_tracked_users(&self, users: impl Iterator) -> Result<()> { self.load_tracked_users().await?; let mut store_updates = Vec::new(); @@ -762,14 +762,14 @@ impl Store { /// A pair `(users, sequence_number)`, where `users` is the list of users to /// be queried, and `sequence_number` is the current sequence number, /// which should be returned in `mark_tracked_users_as_up_to_date`. - pub async fn users_for_key_query(&self) -> Result<(HashSet, SequenceNumber)> { + pub(crate) async fn users_for_key_query(&self) -> Result<(HashSet, SequenceNumber)> { self.load_tracked_users().await?; Ok(self.users_for_key_query.lock().await.users_for_key_query()) } /// See the docs for [`crate::OlmMachine::tracked_users()`]. - pub async fn tracked_users(&self) -> Result> { + pub(crate) async fn tracked_users(&self) -> Result> { self.load_tracked_users().await?; Ok(self.tracked_users_cache.iter().map(|u| u.clone()).collect()) From 1177bdd3942c20e7752a932ca368fe5b6cbefb4c Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 7 Mar 2023 15:58:21 +0000 Subject: [PATCH 08/12] Indexdb migration --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 3 +-- .../matrix-sdk-indexeddb/src/crypto_store.rs | 22 +++++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 06ca4540414..12cb12fe8c5 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -32,8 +32,7 @@ use matrix_sdk_common::deserialized_responses::VerificationState; use matrix_sdk_crypto::{ backups::SignatureState, olm::{IdentityKeys, InboundGroupSession, Session}, - store::{Changes, CryptoStore}, - store::RoomSettings as RustRoomSettings, + store::{Changes, CryptoStore, RoomSettings as RustRoomSettings}, types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey}, EncryptionSettings as RustEncryptionSettings, LocalTrust, }; diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index 3403d1c01c3..1d5c6cc6fbd 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -144,7 +144,7 @@ impl IndexeddbCryptoStore { let name = format!("{prefix:0}::matrix-sdk-crypto"); // Open my_db v1 - let mut db_req: OpenDbRequest = IdbDatabase::open_f64(&name, 1.1)?; + let mut db_req: OpenDbRequest = IdbDatabase::open_f64(&name, 2.0)?; db_req.set_on_upgrade_needed(Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> { let old_version = evt.old_version(); @@ -168,8 +168,6 @@ impl IndexeddbCryptoStore { db.create_object_store(keys::BACKUP_KEYS)?; - db.create_object_store(keys::ROOM_SETTINGS)?; - } else if old_version < 1.1 { // We changed how we store inbound group sessions, the key used to // be a trippled of `(room_id, sender_key, session_id)` now it's a @@ -183,6 +181,11 @@ impl IndexeddbCryptoStore { db.create_object_store(keys::INBOUND_GROUP_SESSIONS)?; } + if old_version < 2.0 { + let db = evt.db(); + db.create_object_store(keys::ROOM_SETTINGS)?; + } + Ok(()) })); @@ -368,20 +371,11 @@ impl_crypto_store! { !changes.identities.new.is_empty() || !changes.identities.changed.is_empty(), keys::IDENTITIES, ), -<<<<<<< HEAD + (!changes.inbound_group_sessions.is_empty(), keys::INBOUND_GROUP_SESSIONS), (!changes.outbound_group_sessions.is_empty(), keys::OUTBOUND_GROUP_SESSIONS), (!changes.message_hashes.is_empty(), keys::OLM_HASHES), -||||||| parent of b0932a23c (Missing stores) - (!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS), - (!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS), - (!changes.message_hashes.is_empty(), KEYS::OLM_HASHES), -======= - (!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS), - (!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS), - (!changes.message_hashes.is_empty(), KEYS::OLM_HASHES), - (!changes.room_settings.is_empty(), KEYS::ROOM_SETTINGS), ->>>>>>> b0932a23c (Missing stores) + (!changes.room_settings.is_empty(), keys::ROOM_SETTINGS), ] .iter() .filter_map(|(id, key)| if *id { Some(*key) } else { None }) From 5bf91825493745acd82c7af15ca645cd776d1037 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 10 Mar 2023 10:16:57 +0000 Subject: [PATCH 09/12] Fix issues after rebase --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 8 ++++---- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 6 +++--- bindings/matrix-sdk-crypto-ffi/src/olm.udl | 5 +++++ crates/matrix-sdk-crypto/src/store/mod.rs | 5 ++++- .../matrix-sdk-indexeddb/src/crypto_store.rs | 19 +++++++++---------- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 12cb12fe8c5..78756c96791 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -812,7 +812,7 @@ mod uniffi_types { RequestVerificationResult, StartSasResult, Verification, VerificationRequest, }, BackupKeys, CrossSigningKeyExport, CrossSigningStatus, DecryptedEvent, EncryptionSettings, - RoomKeyCounts, + EventEncryptionAlgorithm, RoomKeyCounts, RoomSettings, }; } @@ -945,7 +945,7 @@ mod test { let backup_keys = machine.get_backup_keys()?; assert!(backup_keys.is_some()); - let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org")?; + let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org".into())?; assert_eq!( Some(RoomSettings { algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, @@ -954,7 +954,7 @@ mod test { settings1 ); - let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org")?; + let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org".into())?; assert_eq!( Some(RoomSettings { algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, @@ -963,7 +963,7 @@ mod test { settings2 ); - let settings3 = machine.get_room_settings("!XYZ:matrix.org")?; + let settings3 = machine.get_room_settings("!XYZ:matrix.org".into())?; assert!(settings3.is_none()); Ok(()) diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index fbade20e9d2..bc218f42be3 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -572,7 +572,7 @@ impl OlmMachine { /// set_room_only_allow_trusted_devices) methods. pub fn get_room_settings( &self, - room_id: &str, + room_id: String, ) -> Result, CryptoStoreError> { let room_id = RoomId::parse(room_id)?; let settings = self @@ -587,7 +587,7 @@ impl OlmMachine { /// available variants pub fn set_room_algorithm( &self, - room_id: &str, + room_id: String, algorithm: EventEncryptionAlgorithm, ) -> Result<(), CryptoStoreError> { let room_id = RoomId::parse(room_id)?; @@ -614,7 +614,7 @@ impl OlmMachine { /// set_only_allow_trusted_devices) method. pub fn set_room_only_allow_trusted_devices( &self, - room_id: &str, + room_id: String, only_allow_trusted_devices: bool, ) -> Result<(), CryptoStoreError> { let room_id = RoomId::parse(room_id)?; diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index 679e504e3cf..edaa4097c2f 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -467,3 +467,8 @@ dictionary PickledInboundGroupSession { boolean imported; boolean backed_up; }; + +dictionary RoomSettings { + EventEncryptionAlgorithm algorithm; + boolean only_allow_trusted_devices; +}; \ No newline at end of file diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 6564d6bc6bc..6d2e0ccfa46 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -653,7 +653,10 @@ impl Store { /// /// Any users not already on the list are flagged as awaiting a key query. /// Users that were already in the list are unaffected. - pub(crate) async fn update_tracked_users(&self, users: impl Iterator) -> Result<()> { + pub(crate) async fn update_tracked_users( + &self, + users: impl Iterator, + ) -> Result<()> { self.load_tracked_users().await?; let mut store_updates = Vec::new(); diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index 1d5c6cc6fbd..17a2b60149d 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -167,7 +167,6 @@ impl IndexeddbCryptoStore { db.create_object_store(keys::SECRET_REQUESTS_BY_INFO)?; db.create_object_store(keys::BACKUP_KEYS)?; - } else if old_version < 1.1 { // We changed how we store inbound group sessions, the key used to // be a trippled of `(room_id, sender_key, session_id)` now it's a @@ -550,10 +549,10 @@ impl_crypto_store! { } if !room_settings_changes.is_empty() { - let settings_store = tx.object_store(KEYS::ROOM_SETTINGS)?; + let settings_store = tx.object_store(keys::ROOM_SETTINGS)?; for (room_id, settings) in &room_settings_changes { - let key = self.encode_key(KEYS::ROOM_SETTINGS, room_id); + let key = self.encode_key(keys::ROOM_SETTINGS, room_id); let value = self.serialize_value(&settings)?; settings_store.put_key_val(&key, &value)?; } @@ -973,11 +972,11 @@ impl_crypto_store! { } async fn get_room_settings(&self, room_id: &RoomId) -> Result> { - let key = self.encode_key(KEYS::ROOM_SETTINGS, room_id); + let key = self.encode_key(keys::ROOM_SETTINGS, room_id); Ok(self .inner - .transaction_on_one_with_mode(KEYS::ROOM_SETTINGS, IdbTransactionMode::Readonly)? - .object_store(KEYS::ROOM_SETTINGS)? + .transaction_on_one_with_mode(keys::ROOM_SETTINGS, IdbTransactionMode::Readonly)? + .object_store(keys::ROOM_SETTINGS)? .get(&key)? .await? .map(|v| self.deserialize_value(v)) @@ -987,8 +986,8 @@ impl_crypto_store! { async fn get_custom_value(&self, key: &str) -> Result>> { Ok(self .inner - .transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readonly)? - .object_store(KEYS::CORE)? + .transaction_on_one_with_mode(keys::CORE, IdbTransactionMode::Readonly)? + .object_store(keys::CORE)? .get(&JsValue::from_str(key))? .await? .map(|v| self.deserialize_value(v)) @@ -998,8 +997,8 @@ impl_crypto_store! { async fn set_custom_value(&self, key: &str, value: Vec) -> Result<()> { self .inner - .transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readwrite)? - .object_store(KEYS::CORE)? + .transaction_on_one_with_mode(keys::CORE, IdbTransactionMode::Readwrite)? + .object_store(keys::CORE)? .put_key_val(&JsValue::from_str(key), &self.serialize_value(&value)?)?; Ok(()) } From 0d082f6b96540bcf3bc814c297344f3976f0c2d8 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 10 Mar 2023 10:37:12 +0000 Subject: [PATCH 10/12] Update js tests --- bindings/matrix-sdk-crypto-js/tests/machine.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-crypto-js/tests/machine.test.js b/bindings/matrix-sdk-crypto-js/tests/machine.test.js index 7fd824c12d5..95c8ba24195 100644 --- a/bindings/matrix-sdk-crypto-js/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/machine.test.js @@ -51,7 +51,7 @@ describe(OlmMachine.name, () => { expect(databases).toHaveLength(2); expect(databases).toStrictEqual([ { name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 }, - { name: `${store_name}::matrix-sdk-crypto`, version: 1 }, + { name: `${store_name}::matrix-sdk-crypto`, version: 2 }, ]); // Creating a new Olm machine, with the stored state. @@ -142,7 +142,7 @@ describe(OlmMachine.name, () => { expect(databases).toHaveLength(2); expect(databases).toStrictEqual([ { name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 }, - { name: `${store_name}::matrix-sdk-crypto`, version: 1 }, + { name: `${store_name}::matrix-sdk-crypto`, version: 2 }, ]); // Let's force to close the `OlmMachine`. From 404b95ab848477a7717f526078f3aa8732a9a1b8 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 10 Mar 2023 10:39:06 +0000 Subject: [PATCH 11/12] Missing migration method in UDL --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 78 +++++++++++----------- bindings/matrix-sdk-crypto-ffi/src/olm.udl | 8 ++- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 78756c96791..3f872ede930 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -202,45 +202,6 @@ pub fn migrate( }) } -/// Migrate room settings, including room algorithm and whether to block -/// untrusted devices from legacy store to Sqlite store. -/// -/// Note that this method should only be used if a client has already migrated -/// account data via [migrate](#method.migrate) method, which did not include -/// room settings. For a brand new migration, the [migrate](#method.migrate) -/// method will take care of room settings automatically, if provided. -/// -/// # Arguments -/// -/// * `room_settings` - Map of room settings -/// -/// * `path` - The path where the Sqlite store should be created. -/// -/// * `passphrase` - The passphrase that should be used to encrypt the data at -/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store -/// and all its data will remain unencrypted. -pub fn migrate_room_settings( - room_settings: HashMap, - path: &str, - passphrase: Option, -) -> anyhow::Result<()> { - let runtime = Runtime::new()?; - runtime.block_on(async move { - let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?; - - let mut rust_settings = HashMap::new(); - for (room_id, settings) in room_settings { - let room_id = RoomId::parse(room_id)?; - rust_settings.insert(room_id, settings.into()); - } - - let changes = Changes { room_settings: rust_settings, ..Default::default() }; - store.save_changes(changes).await?; - - Ok(()) - }) -} - async fn migrate_data( mut data: MigrationData, path: &str, @@ -519,6 +480,45 @@ fn collect_sessions( Ok((sessions, inbound_group_sessions)) } +/// Migrate room settings, including room algorithm and whether to block +/// untrusted devices from legacy store to Sqlite store. +/// +/// Note that this method should only be used if a client has already migrated +/// account data via [migrate](#method.migrate) method, which did not include +/// room settings. For a brand new migration, the [migrate](#method.migrate) +/// method will take care of room settings automatically, if provided. +/// +/// # Arguments +/// +/// * `room_settings` - Map of room settings +/// +/// * `path` - The path where the Sqlite store should be created. +/// +/// * `passphrase` - The passphrase that should be used to encrypt the data at +/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store +/// and all its data will remain unencrypted. +pub fn migrate_room_settings( + room_settings: HashMap, + path: &str, + passphrase: Option, +) -> anyhow::Result<()> { + let runtime = Runtime::new()?; + runtime.block_on(async move { + let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?; + + let mut rust_settings = HashMap::new(); + for (room_id, settings) in room_settings { + let room_id = RoomId::parse(room_id)?; + rust_settings.insert(room_id, settings.into()); + } + + let changes = Changes { room_settings: rust_settings, ..Default::default() }; + store.save_changes(changes).await?; + + Ok(()) + }) +} + /// Callback that will be passed over the FFI to report progress pub trait ProgressListener { /// The callback that should be called on the Rust side diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index edaa4097c2f..9f5e5fc88ce 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -14,6 +14,12 @@ namespace matrix_sdk_crypto_ffi { string? passphrase, ProgressListener progress_listener ); + [Throws=MigrationError] + void migrate_room_settings( + record room_settings, + [ByRef] string path, + string? passphrase + ); }; [Error] @@ -471,4 +477,4 @@ dictionary PickledInboundGroupSession { dictionary RoomSettings { EventEncryptionAlgorithm algorithm; boolean only_allow_trusted_devices; -}; \ No newline at end of file +}; From d226738d15a53daaa7f2213886c51d1947a58559 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 14 Mar 2023 10:42:15 +0000 Subject: [PATCH 12/12] More pub(crate) methods --- crates/matrix-sdk-crypto/src/store/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 6d2e0ccfa46..c780359a2a0 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -679,7 +679,7 @@ impl Store { /// from the `/sync` response. Any users *whose device lists we are /// tracking* are flagged as needing a key query. Users whose devices we /// are not tracking are ignored. - pub async fn mark_tracked_users_as_changed( + pub(crate) async fn mark_tracked_users_as_changed( &self, users: impl Iterator, ) -> Result<()> { @@ -703,7 +703,7 @@ impl Store { /// This is called after processing the response to a /keys/query request. /// Any users whose device lists we are tracking are removed from the /// list of those pending a /keys/query. - pub async fn mark_tracked_users_as_up_to_date( + pub(crate) async fn mark_tracked_users_as_up_to_date( &self, users: impl Iterator, sequence_number: SequenceNumber, @@ -765,7 +765,9 @@ impl Store { /// A pair `(users, sequence_number)`, where `users` is the list of users to /// be queried, and `sequence_number` is the current sequence number, /// which should be returned in `mark_tracked_users_as_up_to_date`. - pub(crate) async fn users_for_key_query(&self) -> Result<(HashSet, SequenceNumber)> { + pub(crate) async fn users_for_key_query( + &self, + ) -> Result<(HashSet, SequenceNumber)> { self.load_tracked_users().await?; Ok(self.users_for_key_query.lock().await.users_for_key_query())