diff --git a/crates/matrix-sdk-indexeddb/src/cryptostore.rs b/crates/matrix-sdk-indexeddb/src/cryptostore.rs index d5ae528f3..55a7be518 100644 --- a/crates/matrix-sdk-indexeddb/src/cryptostore.rs +++ b/crates/matrix-sdk-indexeddb/src/cryptostore.rs @@ -35,6 +35,7 @@ use matrix_sdk_store_encryption::StoreCipher; use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, RoomId, TransactionId, UserId}; use serde::{Deserialize, Serialize}; use wasm_bindgen::JsValue; +use web_sys::IdbKeyRange; use crate::safe_encode::SafeEncode; @@ -57,6 +58,7 @@ mod KEYS { pub const OUTGOING_SECRET_REQUESTS: &str = "outgoing_secret_requests"; 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"; // KEYS pub const STORE_CIPHER: &str = "store_cipher"; @@ -186,6 +188,35 @@ impl IndexeddbStore { IndexeddbStore::open_with_store_cipher("crypto".to_owned(), None).await } + fn encode_key(&self, table_name: &str, key: T) -> JsValue + where + T: SafeEncode, + { + match &self.store_cipher { + Some(cipher) => key.encode_secure(table_name, cipher), + None => key.encode(), + } + } + + fn encode_to_range( + &self, + table_name: &str, + key: T, + ) -> Result + where + T: SafeEncode, + { + match &self.store_cipher { + Some(cipher) => key.encode_to_range_secure(table_name, cipher), + None => key.encode_to_range(), + } + .map_err(|e| IndexeddbStoreError::DomException { + code: 0, + name: "IdbKeyRangeMakeError".to_owned(), + message: e, + }) + } + /// Open a new IndexeddbStore with given name and passphrase pub async fn open_with_passphrase(prefix: String, passphrase: &str) -> Result { let name = format!("{:0}::matrix-sdk-crypto-meta", prefix); @@ -359,7 +390,7 @@ impl IndexeddbStore { let session_id = session.session_id(); let pickle = session.pickle().await; - let key = (&sender_key, session_id).encode(); + let key = self.encode_key(KEYS::SESSION, (&sender_key, session_id)); sessions.put_key_val(&key, &self.serialize_value(&pickle)?)?; } @@ -372,7 +403,8 @@ impl IndexeddbStore { let room_id = session.room_id(); let sender_key = session.sender_key(); let session_id = session.session_id(); - let key = (room_id, sender_key, session_id).encode(); + let key = self + .encode_key(KEYS::INBOUND_GROUP_SESSIONS, (room_id, sender_key, session_id)); let pickle = session.pickle().await; sessions.put_key_val(&key, &self.serialize_value(&pickle)?)?; @@ -385,7 +417,10 @@ impl IndexeddbStore { for session in changes.outbound_group_sessions { let room_id = session.room_id(); let pickle = session.pickle().await; - sessions.put_key_val(&room_id.encode(), &self.serialize_value(&pickle)?)?; + sessions.put_key_val( + &self.encode_key(KEYS::OUTBOUND_GROUP_SESSIONS, &room_id), + &self.serialize_value(&pickle)?, + )?; } } @@ -397,7 +432,7 @@ impl IndexeddbStore { if !device_changes.new.is_empty() || !device_changes.changed.is_empty() { let device_store = tx.object_store(KEYS::DEVICES)?; for device in device_changes.new.iter().chain(&device_changes.changed) { - let key = (device.user_id(), device.device_id()).encode(); + let key = self.encode_key(KEYS::DEVICES, (device.user_id(), device.device_id())); let device = self.serialize_value(&device)?; device_store.put_key_val(&key, &device)?; @@ -408,7 +443,7 @@ impl IndexeddbStore { let device_store = tx.object_store(KEYS::DEVICES)?; for device in &device_changes.deleted { - let key = (device.user_id(), device.device_id()).encode(); + let key = self.encode_key(KEYS::DEVICES, (device.user_id(), device.device_id())); device_store.delete(&key)?; } } @@ -416,15 +451,20 @@ impl IndexeddbStore { if !identity_changes.changed.is_empty() || !identity_changes.new.is_empty() { let identities = tx.object_store(KEYS::IDENTITIES)?; for identity in identity_changes.changed.iter().chain(&identity_changes.new) { - identities - .put_key_val(&identity.user_id().encode(), &self.serialize_value(&identity)?)?; + identities.put_key_val( + &self.encode_key(KEYS::IDENTITIES, &identity.user_id()), + &self.serialize_value(&identity)?, + )?; } } if !olm_hashes.is_empty() { let hashes = tx.object_store(KEYS::OLM_HASHES)?; for hash in &olm_hashes { - hashes.put_key_val(&(&hash.sender_key, &hash.hash).encode(), &JsValue::TRUE)?; + hashes.put_key_val( + &self.encode_key(KEYS::OLM_HASHES, (&hash.sender_key, &hash.hash)), + &JsValue::TRUE, + )?; } } @@ -433,9 +473,12 @@ impl IndexeddbStore { let unsent_secret_requests = tx.object_store(KEYS::UNSENT_SECRET_REQUESTS)?; let outgoing_secret_requests = tx.object_store(KEYS::OUTGOING_SECRET_REQUESTS)?; for key_request in &key_requests { - let key_request_id = key_request.request_id.encode(); - secret_requests_by_info - .put_key_val(&key_request.info.as_key().encode(), &key_request_id)?; + let key_request_id = + self.encode_key(KEYS::KEY_REQUEST, key_request.request_id.as_str()); + secret_requests_by_info.put_key_val( + &self.encode_key(KEYS::KEY_REQUEST, key_request.info.as_key()), + &key_request_id, + )?; if key_request.sent_out { unsent_secret_requests.delete(&key_request_id)?; @@ -494,7 +537,7 @@ impl IndexeddbStore { IdbTransactionMode::Readonly, )? .object_store(KEYS::OUTBOUND_GROUP_SESSIONS)? - .get(&room_id.encode())? + .get(&self.encode_key(KEYS::OUTBOUND_GROUP_SESSIONS, room_id))? .await? { Ok(Some( @@ -510,7 +553,7 @@ impl IndexeddbStore { } } async fn get_outgoing_key_request_helper(&self, key: &str) -> Result> { - // in this internal we expect key to already be escaped. + // in this internal we expect key to already be escaped or encrypted let jskey = JsValue::from_str(key); let dbs = [KEYS::OUTGOING_SECRET_REQUESTS, KEYS::UNSENT_SECRET_REQUESTS]; let tx = self.inner.transaction_on_multi_with_mode(&dbs, IdbTransactionMode::Readonly)?; @@ -621,7 +664,7 @@ impl IndexeddbStore { sender_key: &str, session_id: &str, ) -> Result> { - let key = (room_id, sender_key, session_id).encode(); + let key = self.encode_key(KEYS::INBOUND_GROUP_SESSIONS, (room_id, sender_key, session_id)); if let Some(pickle) = self .inner .transaction_on_one_with_mode( @@ -735,7 +778,7 @@ impl IndexeddbStore { user_id: &UserId, device_id: &DeviceId, ) -> Result> { - let key = (user_id, device_id).encode(); + let key = self.encode_key(KEYS::DEVICES, (user_id, device_id)); Ok(self .inner .transaction_on_one_with_mode(KEYS::DEVICES, IdbTransactionMode::Readonly)? @@ -750,11 +793,7 @@ impl IndexeddbStore { &self, user_id: &UserId, ) -> Result> { - let range = user_id.encode_to_range().map_err(|e| IndexeddbStoreError::DomException { - code: 0, - name: "IdbKeyRangeMakeError".to_owned(), - message: e, - })?; + let range = self.encode_to_range(KEYS::DEVICES, user_id)?; Ok(self .inner .transaction_on_one_with_mode(KEYS::DEVICES, IdbTransactionMode::Readonly)? @@ -774,7 +813,7 @@ impl IndexeddbStore { .inner .transaction_on_one_with_mode(KEYS::IDENTITIES, IdbTransactionMode::Readonly)? .object_store(KEYS::IDENTITIES)? - .get(&user_id.encode())? + .get(&self.encode_key(KEYS::IDENTITIES, user_id))? .await? .map(|i| self.deserialize_value(i)) .transpose()?) @@ -785,7 +824,7 @@ impl IndexeddbStore { .inner .transaction_on_one_with_mode(KEYS::OLM_HASHES, IdbTransactionMode::Readonly)? .object_store(KEYS::OLM_HASHES)? - .get(&(&hash.sender_key, &hash.hash).encode())? + .get(&self.encode_key(KEYS::OLM_HASHES, (&hash.sender_key, &hash.hash)))? .await? .is_some()) } @@ -801,7 +840,7 @@ impl IndexeddbStore { IdbTransactionMode::Readonly, )? .object_store(KEYS::SECRET_REQUESTS_BY_INFO)? - .get(&key_info.as_key().encode())? + .get(&self.encode_key(KEYS::KEY_REQUEST, key_info.as_key()))? .await? .and_then(|i| i.as_string()); if let Some(id) = id { @@ -827,7 +866,7 @@ impl IndexeddbStore { } async fn delete_outgoing_secret_requests(&self, request_id: &TransactionId) -> Result<()> { - let jskey = request_id.as_str().encode(); + let jskey = self.encode_key(KEYS::KEY_REQUEST, request_id); //.as_str()); let dbs = [ KEYS::OUTGOING_SECRET_REQUESTS, KEYS::UNSENT_SECRET_REQUESTS, @@ -854,7 +893,7 @@ impl IndexeddbStore { if let Some(inner) = request { tx.object_store(KEYS::SECRET_REQUESTS_BY_INFO)? - .delete(&inner.info.as_key().encode())?; + .delete(&self.encode_key(KEYS::KEY_REQUEST, &inner.info.as_key()))?; } tx.object_store(KEYS::UNSENT_SECRET_REQUESTS)?.delete(&jskey)?; @@ -1035,10 +1074,12 @@ impl CryptoStore for IndexeddbStore { #[cfg(test)] mod tests { - use super::IndexeddbStore; - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); use matrix_sdk_crypto::cryptostore_integration_tests; + use super::IndexeddbStore; + + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + async fn get_store(name: String, passphrase: Option<&str>) -> IndexeddbStore { match passphrase { Some(pass) => IndexeddbStore::open_with_passphrase(name, pass) @@ -1051,3 +1092,22 @@ mod tests { } cryptostore_integration_tests! { integration } } + +#[cfg(test)] +#[rustfmt::skip] +mod encrypted_tests { + use super::IndexeddbStore; + use matrix_sdk_crypto::cryptostore_integration_tests; + + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + + async fn get_store(name: String, passphrase: Option<&str>) -> IndexeddbStore { + let pass = passphrase.unwrap_or_else(|| name.as_str()); + IndexeddbStore::open_with_passphrase(name.clone(), pass) + .await + .expect("Can't create a passphrase protected store") + } +// FIXME: the tests pass, if run one by one, but run all together locally, +// as well as CI fails... see matrix-org/matrix-rust-sdk#661 +// cryptostore_integration_tests! { integration } +} diff --git a/crates/matrix-sdk-indexeddb/src/state_store.rs b/crates/matrix-sdk-indexeddb/src/state_store.rs index 055d55c06..b505478d6 100644 --- a/crates/matrix-sdk-indexeddb/src/state_store.rs +++ b/crates/matrix-sdk-indexeddb/src/state_store.rs @@ -269,8 +269,8 @@ impl IndexeddbStore { &self, event: &impl Serialize, ) -> std::result::Result { - Ok(match self.store_cipher { - Some(ref cipher) => JsValue::from_serde(&cipher.encrypt_value_typed(event)?)?, + Ok(match &self.store_cipher { + Some(cipher) => JsValue::from_serde(&cipher.encrypt_value_typed(event)?)?, None => JsValue::from_serde(event)?, }) } @@ -279,8 +279,8 @@ impl IndexeddbStore { &self, event: JsValue, ) -> std::result::Result { - match self.store_cipher { - Some(ref cipher) => Ok(cipher.decrypt_value_typed(event.into_serde()?)?), + match &self.store_cipher { + Some(cipher) => Ok(cipher.decrypt_value_typed(event.into_serde()?)?), None => Ok(event.into_serde()?), } } @@ -289,8 +289,8 @@ impl IndexeddbStore { where T: SafeEncode, { - match self.store_cipher { - Some(ref cipher) => key.encode_secure(table_name, cipher), + match &self.store_cipher { + Some(cipher) => key.encode_secure(table_name, cipher), None => key.encode(), } } @@ -303,8 +303,8 @@ impl IndexeddbStore { where T: SafeEncode, { - match self.store_cipher { - Some(ref cipher) => key.encode_to_range_secure(table_name, cipher), + match &self.store_cipher { + Some(cipher) => key.encode_to_range_secure(table_name, cipher), None => key.encode_to_range(), } .map_err(|e| SerializationError::StoreError(StoreError::Backend(anyhow!(e).into()))) @@ -315,8 +315,8 @@ impl IndexeddbStore { where T: SafeEncode, { - match self.store_cipher { - Some(ref cipher) => key.encode_with_counter_secure(table_name, cipher, i), + match &self.store_cipher { + Some(cipher) => key.encode_with_counter_secure(table_name, cipher, i), None => key.encode_with_counter(i), } } diff --git a/crates/matrix-sdk-sled/src/cryptostore.rs b/crates/matrix-sdk-sled/src/cryptostore.rs index 3c616b115..550536374 100644 --- a/crates/matrix-sdk-sled/src/cryptostore.rs +++ b/crates/matrix-sdk-sled/src/cryptostore.rs @@ -1039,3 +1039,25 @@ mod tests { cryptostore_integration_tests! { integration } } + +#[cfg(test)] +mod encrypted_tests { + use matrix_sdk_crypto::cryptostore_integration_tests; + use once_cell::sync::Lazy; + use tempfile::{tempdir, TempDir}; + + use super::SledStore; + + static TMP_DIR: Lazy = Lazy::new(|| tempdir().unwrap()); + + async fn get_store(name: String, passphrase: Option<&str>) -> SledStore { + let tmpdir_path = TMP_DIR.path().join(name); + let pass = passphrase.unwrap_or("default_test_password"); + + let store = SledStore::open_with_passphrase(tmpdir_path.to_str().unwrap(), Some(pass)) + .expect("Can't create a passphrase protected store"); + + store + } + cryptostore_integration_tests! { integration } +}