// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::{ collections::{HashMap, HashSet}, convert::TryFrom, path::{Path, PathBuf}, sync::{Arc, RwLock}, }; use dashmap::DashSet; use matrix_sdk_common::{async_trait, locks::Mutex, uuid}; use olm_rs::{account::IdentityKeys, PicklingMode}; use ruma::{ events::{room_key_request::RequestedKeyInfo, secret::request::SecretName}, DeviceId, DeviceIdBox, RoomId, UserId, }; pub use sled::Error; use sled::{ transaction::{ConflictableTransactionError, TransactionError}, Config, Db, Transactional, Tree, }; use uuid::Uuid; use super::{ caches::SessionStore, Changes, CryptoStore, CryptoStoreError, InboundGroupSession, PickleKey, ReadOnlyAccount, Result, Session, }; use crate::{ identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, key_request::{OutgoingKeyRequest, SecretInfo}, olm::{OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity}, }; /// This needs to be 32 bytes long since AES-GCM requires it, otherwise we will /// panic once we try to pickle a Signing object. const DEFAULT_PICKLE: &str = "DEFAULT_PICKLE_PASSPHRASE_123456"; trait EncodeKey { const SEPARATOR: u8 = 0xff; fn encode(&self) -> Vec; } impl EncodeKey for Uuid { fn encode(&self) -> Vec { self.as_u128().to_be_bytes().to_vec() } } impl EncodeKey for SecretName { fn encode(&self) -> Vec { [self.as_ref().as_bytes(), &[Self::SEPARATOR]].concat() } } impl EncodeKey for SecretInfo { fn encode(&self) -> Vec { match self { SecretInfo::KeyRequest(k) => k.encode(), SecretInfo::SecretRequest(s) => s.encode(), } } } impl EncodeKey for &RequestedKeyInfo { fn encode(&self) -> Vec { [ self.room_id.as_bytes(), &[Self::SEPARATOR], self.sender_key.as_bytes(), &[Self::SEPARATOR], self.algorithm.as_ref().as_bytes(), &[Self::SEPARATOR], self.session_id.as_bytes(), &[Self::SEPARATOR], ] .concat() } } impl EncodeKey for &UserId { fn encode(&self) -> Vec { self.as_str().encode() } } impl EncodeKey for &RoomId { fn encode(&self) -> Vec { self.as_str().encode() } } impl EncodeKey for &str { fn encode(&self) -> Vec { [self.as_bytes(), &[Self::SEPARATOR]].concat() } } impl EncodeKey for (&str, &str) { fn encode(&self) -> Vec { [self.0.as_bytes(), &[Self::SEPARATOR], self.1.as_bytes(), &[Self::SEPARATOR]].concat() } } impl EncodeKey for (&str, &str, &str) { fn encode(&self) -> Vec { [ self.0.as_bytes(), &[Self::SEPARATOR], self.1.as_bytes(), &[Self::SEPARATOR], self.2.as_bytes(), &[Self::SEPARATOR], ] .concat() } } #[derive(Clone, Debug)] pub struct AccountInfo { user_id: Arc, device_id: Arc, identity_keys: Arc, } /// An in-memory only store that will forget all the E2EE key once it's dropped. #[derive(Clone)] pub struct SledStore { account_info: Arc>>, path: Option, inner: Db, pickle_key: Arc, session_cache: SessionStore, tracked_users_cache: Arc>, users_for_key_query_cache: Arc>, account: Tree, private_identity: Tree, olm_hashes: Tree, sessions: Tree, inbound_group_sessions: Tree, outbound_group_sessions: Tree, outgoing_secret_requests: Tree, unsent_secret_requests: Tree, secret_requests_by_info: Tree, devices: Tree, identities: Tree, tracked_users: Tree, users_for_key_query: Tree, } impl std::fmt::Debug for SledStore { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(path) = &self.path { f.debug_struct("SledStore").field("path", &path).finish() } else { f.debug_struct("SledStore").field("path", &"memory store").finish() } } } impl From> for CryptoStoreError { fn from(e: TransactionError) -> Self { match e { TransactionError::Abort(e) => CryptoStoreError::Serialization(e), TransactionError::Storage(e) => CryptoStoreError::Database(e), } } } impl SledStore { /// Open the sled based cryptostore at the given path using the given /// passphrase to encrypt private data. pub fn open_with_passphrase(path: impl AsRef, passphrase: Option<&str>) -> Result { let path = path.as_ref().join("matrix-sdk-crypto"); let db = Config::new().temporary(false).path(&path).open()?; SledStore::open_helper(db, Some(path), passphrase) } /// Create a sled based cryptostore using the given sled database. /// The given passphrase will be used to encrypt private data. pub fn open_with_database(db: Db, passphrase: Option<&str>) -> Result { SledStore::open_helper(db, None, passphrase) } fn get_account_info(&self) -> Option { self.account_info.read().unwrap().clone() } fn open_helper(db: Db, path: Option, passphrase: Option<&str>) -> Result { let account = db.open_tree("account")?; let private_identity = db.open_tree("private_identity")?; let sessions = db.open_tree("session")?; let inbound_group_sessions = db.open_tree("inbound_group_sessions")?; let outbound_group_sessions = db.open_tree("outbound_group_sessions")?; let tracked_users = db.open_tree("tracked_users")?; let users_for_key_query = db.open_tree("users_for_key_query")?; let olm_hashes = db.open_tree("olm_hashes")?; let devices = db.open_tree("devices")?; let identities = db.open_tree("identities")?; let outgoing_secret_requests = db.open_tree("outgoing_secret_requests")?; let unsent_secret_requests = db.open_tree("unsent_secret_requests")?; let secret_requests_by_info = db.open_tree("secret_requests_by_info")?; let session_cache = SessionStore::new(); let pickle_key = if let Some(passphrase) = passphrase { Self::get_or_create_pickle_key(passphrase, &db)? } else { PickleKey::try_from(DEFAULT_PICKLE.as_bytes().to_vec()) .expect("Can't create default pickle key") }; Ok(Self { account_info: RwLock::new(None).into(), path, inner: db, pickle_key: pickle_key.into(), account, private_identity, sessions, session_cache, tracked_users_cache: DashSet::new().into(), users_for_key_query_cache: DashSet::new().into(), inbound_group_sessions, outbound_group_sessions, outgoing_secret_requests, unsent_secret_requests, secret_requests_by_info, devices, tracked_users, users_for_key_query, olm_hashes, identities, }) } fn get_or_create_pickle_key(passphrase: &str, database: &Db) -> Result { let key = if let Some(key) = database.get("pickle_key".encode())?.map(|v| serde_json::from_slice(&v)) { PickleKey::from_encrypted(passphrase, key?) .map_err(|_| CryptoStoreError::UnpicklingError)? } else { let key = PickleKey::new(); let encrypted = key.encrypt(passphrase); database.insert("pickle_key".encode(), serde_json::to_vec(&encrypted)?)?; key }; Ok(key) } fn get_pickle_mode(&self) -> PicklingMode { self.pickle_key.pickle_mode() } fn get_pickle_key(&self) -> &[u8] { self.pickle_key.key() } async fn load_tracked_users(&self) -> Result<()> { for value in self.tracked_users.iter() { let (user, dirty) = value?; let user = UserId::try_from(String::from_utf8_lossy(&user).to_string())?; let dirty = dirty.get(0).map(|d| *d == 1).unwrap_or(true); self.tracked_users_cache.insert(user.clone()); if dirty { self.users_for_key_query_cache.insert(user); } } Ok(()) } async fn load_outbound_group_session( &self, room_id: &RoomId, ) -> Result> { let account_info = self.get_account_info().ok_or(CryptoStoreError::AccountUnset)?; self.outbound_group_sessions .get(room_id.encode())? .map(|p| serde_json::from_slice(&p).map_err(CryptoStoreError::Serialization)) .transpose()? .map(|p| { OutboundGroupSession::from_pickle( account_info.device_id, account_info.identity_keys, p, self.get_pickle_mode(), ) .map_err(CryptoStoreError::OlmGroupSession) }) .transpose() } async fn save_changes(&self, changes: Changes) -> Result<()> { let account_pickle = if let Some(a) = changes.account { Some(a.pickle(self.get_pickle_mode()).await) } else { None }; let private_identity_pickle = if let Some(i) = changes.private_identity { Some(i.pickle(self.get_pickle_key()).await?) } else { None }; let device_changes = changes.devices; let mut session_changes = HashMap::new(); for session in changes.sessions { let sender_key = session.sender_key(); let session_id = session.session_id(); let pickle = session.pickle(self.get_pickle_mode()).await; let key = (sender_key, session_id).encode(); self.session_cache.add(session).await; session_changes.insert(key, pickle); } let mut inbound_session_changes = HashMap::new(); for session in changes.inbound_group_sessions { let room_id = session.room_id(); let sender_key = session.sender_key(); let session_id = session.session_id(); let key = (room_id.as_str(), sender_key, session_id).encode(); let pickle = session.pickle(self.get_pickle_mode()).await; inbound_session_changes.insert(key, pickle); } let mut outbound_session_changes = HashMap::new(); for session in changes.outbound_group_sessions { let room_id = session.room_id(); let pickle = session.pickle(self.get_pickle_mode()).await; outbound_session_changes.insert(room_id.clone(), pickle); } let identity_changes = changes.identities; let olm_hashes = changes.message_hashes; let key_requests = changes.key_requests; let ret: Result<(), TransactionError> = ( &self.account, &self.private_identity, &self.devices, &self.identities, &self.sessions, &self.inbound_group_sessions, &self.outbound_group_sessions, &self.olm_hashes, &self.outgoing_secret_requests, &self.unsent_secret_requests, &self.secret_requests_by_info, ) .transaction( |( account, private_identity, devices, identities, sessions, inbound_sessions, outbound_sessions, hashes, outgoing_secret_requests, unsent_secret_requests, secret_requests_by_info, )| { if let Some(a) = &account_pickle { account.insert( "account".encode(), serde_json::to_vec(a).map_err(ConflictableTransactionError::Abort)?, )?; } if let Some(i) = &private_identity_pickle { private_identity.insert( "identity".encode(), serde_json::to_vec(&i).map_err(ConflictableTransactionError::Abort)?, )?; } for device in device_changes.new.iter().chain(&device_changes.changed) { let key = (device.user_id().as_str(), device.device_id().as_str()).encode(); let device = serde_json::to_vec(&device) .map_err(ConflictableTransactionError::Abort)?; devices.insert(key, device)?; } for device in &device_changes.deleted { let key = (device.user_id().as_str(), device.device_id().as_str()).encode(); devices.remove(key)?; } for identity in identity_changes.changed.iter().chain(&identity_changes.new) { identities.insert( identity.user_id().encode(), serde_json::to_vec(&identity) .map_err(ConflictableTransactionError::Abort)?, )?; } for (key, session) in &session_changes { sessions.insert( key.as_slice(), serde_json::to_vec(&session) .map_err(ConflictableTransactionError::Abort)?, )?; } for (key, session) in &inbound_session_changes { inbound_sessions.insert( key.as_slice(), serde_json::to_vec(&session) .map_err(ConflictableTransactionError::Abort)?, )?; } for (key, session) in &outbound_session_changes { outbound_sessions.insert( key.encode(), serde_json::to_vec(&session) .map_err(ConflictableTransactionError::Abort)?, )?; } for hash in &olm_hashes { hashes.insert( serde_json::to_vec(&hash) .map_err(ConflictableTransactionError::Abort)?, &[0], )?; } for key_request in &key_requests { secret_requests_by_info.insert( (&key_request.info).encode(), key_request.request_id.encode(), )?; let key_request_id = key_request.request_id.encode(); if key_request.sent_out { unsent_secret_requests.remove(key_request_id.clone())?; outgoing_secret_requests.insert( key_request_id, serde_json::to_vec(&key_request) .map_err(ConflictableTransactionError::Abort)?, )?; } else { outgoing_secret_requests.remove(key_request_id.clone())?; unsent_secret_requests.insert( key_request_id, serde_json::to_vec(&key_request) .map_err(ConflictableTransactionError::Abort)?, )?; } } Ok(()) }, ); ret?; self.inner.flush_async().await?; Ok(()) } async fn get_outgoing_key_request_helper( &self, id: &[u8], ) -> Result> { let request = self .outgoing_secret_requests .get(id)? .map(|r| serde_json::from_slice(&r)) .transpose()?; let request = if request.is_none() { self.unsent_secret_requests.get(id)?.map(|r| serde_json::from_slice(&r)).transpose()? } else { request }; Ok(request) } } #[async_trait] impl CryptoStore for SledStore { async fn load_account(&self) -> Result> { if let Some(pickle) = self.account.get("account".encode())? { let pickle = serde_json::from_slice(&pickle)?; self.load_tracked_users().await?; let account = ReadOnlyAccount::from_pickle(pickle, self.get_pickle_mode())?; let account_info = AccountInfo { user_id: account.user_id.clone(), device_id: account.device_id.clone(), identity_keys: account.identity_keys.clone(), }; *self.account_info.write().unwrap() = Some(account_info); Ok(Some(account)) } else { Ok(None) } } async fn save_account(&self, account: ReadOnlyAccount) -> Result<()> { let account_info = AccountInfo { user_id: account.user_id.clone(), device_id: account.device_id.clone(), identity_keys: account.identity_keys.clone(), }; *self.account_info.write().unwrap() = Some(account_info); let changes = Changes { account: Some(account), ..Default::default() }; self.save_changes(changes).await } async fn load_identity(&self) -> Result> { if let Some(i) = self.private_identity.get("identity".encode())? { let pickle = serde_json::from_slice(&i)?; Ok(Some( PrivateCrossSigningIdentity::from_pickle(pickle, self.get_pickle_key()) .await .map_err(|_| CryptoStoreError::UnpicklingError)?, )) } else { Ok(None) } } async fn save_changes(&self, changes: Changes) -> Result<()> { self.save_changes(changes).await } async fn get_sessions(&self, sender_key: &str) -> Result>>>> { let account_info = self.get_account_info().ok_or(CryptoStoreError::AccountUnset)?; if self.session_cache.get(sender_key).is_none() { let sessions: Result> = self .sessions .scan_prefix(sender_key.encode()) .map(|s| serde_json::from_slice(&s?.1).map_err(CryptoStoreError::Serialization)) .map(|p| { Session::from_pickle( account_info.user_id.clone(), account_info.device_id.clone(), account_info.identity_keys.clone(), p?, self.get_pickle_mode(), ) .map_err(CryptoStoreError::SessionUnpickling) }) .collect(); self.session_cache.set_for_sender(sender_key, sessions?); } Ok(self.session_cache.get(sender_key)) } async fn get_inbound_group_session( &self, room_id: &RoomId, sender_key: &str, session_id: &str, ) -> Result> { let key = (room_id.as_str(), sender_key, session_id).encode(); let pickle = self.inbound_group_sessions.get(&key)?.map(|p| serde_json::from_slice(&p)); if let Some(pickle) = pickle { Ok(Some(InboundGroupSession::from_pickle(pickle?, self.get_pickle_mode())?)) } else { Ok(None) } } async fn get_inbound_group_sessions(&self) -> Result> { let pickles: Result> = self .inbound_group_sessions .iter() .map(|p| serde_json::from_slice(&p?.1).map_err(CryptoStoreError::Serialization)) .collect(); Ok(pickles? .into_iter() .filter_map(|p| InboundGroupSession::from_pickle(p, self.get_pickle_mode()).ok()) .collect()) } async fn get_outbound_group_sessions( &self, room_id: &RoomId, ) -> Result> { self.load_outbound_group_session(room_id).await } fn is_user_tracked(&self, user_id: &UserId) -> bool { self.tracked_users_cache.contains(user_id) } fn has_users_for_key_query(&self) -> bool { !self.users_for_key_query_cache.is_empty() } fn users_for_key_query(&self) -> HashSet { #[allow(clippy::map_clone)] self.users_for_key_query_cache.iter().map(|u| u.clone()).collect() } async fn update_tracked_user(&self, user: &UserId, dirty: bool) -> Result { let already_added = self.tracked_users_cache.insert(user.clone()); if dirty { self.users_for_key_query_cache.insert(user.clone()); } else { self.users_for_key_query_cache.remove(user); } self.tracked_users.insert(user.as_str(), &[dirty as u8])?; Ok(already_added) } async fn get_device( &self, user_id: &UserId, device_id: &DeviceId, ) -> Result> { let key = (user_id.as_str(), device_id.as_str()).encode(); if let Some(d) = self.devices.get(key)? { Ok(Some(serde_json::from_slice(&d)?)) } else { Ok(None) } } async fn get_user_devices( &self, user_id: &UserId, ) -> Result> { self.devices .scan_prefix(user_id.encode()) .map(|d| serde_json::from_slice(&d?.1).map_err(CryptoStoreError::Serialization)) .map(|d| { let d: ReadOnlyDevice = d?; Ok((d.device_id().to_owned(), d)) }) .collect() } async fn get_user_identity(&self, user_id: &UserId) -> Result> { Ok(self .identities .get(user_id.encode())? .map(|i| serde_json::from_slice(&i)) .transpose()?) } async fn is_message_known(&self, message_hash: &crate::olm::OlmMessageHash) -> Result { Ok(self.olm_hashes.contains_key(serde_json::to_vec(message_hash)?)?) } async fn get_outgoing_secret_requests( &self, request_id: Uuid, ) -> Result> { let request_id = request_id.encode(); self.get_outgoing_key_request_helper(&request_id).await } async fn get_secret_request_by_info( &self, key_info: &SecretInfo, ) -> Result> { let id = self.secret_requests_by_info.get(key_info.encode())?; if let Some(id) = id { self.get_outgoing_key_request_helper(&id).await } else { Ok(None) } } async fn get_unsent_secret_requests(&self) -> Result> { let requests: Result> = self .unsent_secret_requests .iter() .map(|i| serde_json::from_slice(&i?.1).map_err(CryptoStoreError::from)) .collect(); requests } async fn delete_outgoing_secret_requests(&self, request_id: Uuid) -> Result<()> { let ret: Result<(), TransactionError> = ( &self.outgoing_secret_requests, &self.unsent_secret_requests, &self.secret_requests_by_info, ) .transaction( |(outgoing_key_requests, unsent_key_requests, key_requests_by_info)| { let sent_request: Option = outgoing_key_requests .remove(request_id.encode())? .map(|r| serde_json::from_slice(&r)) .transpose() .map_err(ConflictableTransactionError::Abort)?; let unsent_request: Option = unsent_key_requests .remove(request_id.encode())? .map(|r| serde_json::from_slice(&r)) .transpose() .map_err(ConflictableTransactionError::Abort)?; if let Some(request) = sent_request { key_requests_by_info.remove((&request.info).encode())?; } if let Some(request) = unsent_request { key_requests_by_info.remove((&request.info).encode())?; } Ok(()) }, ); ret?; self.inner.flush_async().await?; Ok(()) } } #[cfg(test)] mod test { use std::collections::BTreeMap; use matrix_sdk_common::uuid::Uuid; use matrix_sdk_test::async_test; use olm_rs::outbound_group_session::OlmOutboundGroupSession; use ruma::{ encryption::SignedKey, events::room_key_request::RequestedKeyInfo, room_id, user_id, DeviceId, EventEncryptionAlgorithm, UserId, }; use tempfile::tempdir; use super::{CryptoStore, OutgoingKeyRequest, SledStore}; use crate::{ identities::{ device::test::get_device, user::test::{get_other_identity, get_own_identity}, }, key_request::SecretInfo, olm::{ GroupSessionKey, InboundGroupSession, OlmMessageHash, PrivateCrossSigningIdentity, ReadOnlyAccount, Session, }, store::{Changes, DeviceChanges, IdentityChanges}, }; fn alice_id() -> UserId { user_id!("@alice:example.org") } fn alice_device_id() -> Box { "ALICEDEVICE".into() } fn bob_id() -> UserId { user_id!("@bob:example.org") } fn bob_device_id() -> Box { "BOBDEVICE".into() } async fn get_store(passphrase: Option<&str>) -> (SledStore, tempfile::TempDir) { let tmpdir = tempdir().unwrap(); let tmpdir_path = tmpdir.path().to_str().unwrap(); let store = SledStore::open_with_passphrase(tmpdir_path, passphrase) .expect("Can't create a passphrase protected store"); (store, tmpdir) } async fn get_loaded_store() -> (ReadOnlyAccount, SledStore, tempfile::TempDir) { let (store, dir) = get_store(None).await; let account = get_account(); store.save_account(account.clone()).await.expect("Can't save account"); (account, store, dir) } fn get_account() -> ReadOnlyAccount { ReadOnlyAccount::new(&alice_id(), &alice_device_id()) } async fn get_account_and_session() -> (ReadOnlyAccount, Session) { let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id()); let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id()); bob.generate_one_time_keys_helper(1).await; let one_time_key = bob.one_time_keys().await.curve25519().iter().next().unwrap().1.to_owned(); let one_time_key = SignedKey::new(one_time_key, BTreeMap::new()); let sender_key = bob.identity_keys().curve25519().to_owned(); let session = alice.create_outbound_session_helper(&sender_key, &one_time_key).await.unwrap(); (alice, session) } #[async_test] async fn create_store() { let tmpdir = tempdir().unwrap(); let tmpdir_path = tmpdir.path().to_str().unwrap(); let _ = SledStore::open_with_passphrase(tmpdir_path, None).expect("Can't create store"); } #[async_test] async fn save_account() { let (store, _dir) = get_store(None).await; assert!(store.load_account().await.unwrap().is_none()); let account = get_account(); store.save_account(account).await.expect("Can't save account"); } #[async_test] async fn load_account() { let (store, _dir) = get_store(None).await; let account = get_account(); store.save_account(account.clone()).await.expect("Can't save account"); let loaded_account = store.load_account().await.expect("Can't load account"); let loaded_account = loaded_account.unwrap(); assert_eq!(account, loaded_account); } #[async_test] async fn load_account_with_passphrase() { let (store, _dir) = get_store(Some("secret_passphrase")).await; let account = get_account(); store.save_account(account.clone()).await.expect("Can't save account"); let loaded_account = store.load_account().await.expect("Can't load account"); let loaded_account = loaded_account.unwrap(); assert_eq!(account, loaded_account); } #[async_test] async fn save_and_share_account() { let (store, _dir) = get_store(None).await; let account = get_account(); store.save_account(account.clone()).await.expect("Can't save account"); account.mark_as_shared(); account.update_uploaded_key_count(50); store.save_account(account.clone()).await.expect("Can't save account"); let loaded_account = store.load_account().await.expect("Can't load account"); let loaded_account = loaded_account.unwrap(); assert_eq!(account, loaded_account); assert_eq!(account.uploaded_key_count(), loaded_account.uploaded_key_count()); } #[async_test] async fn load_sessions() { let (store, _dir) = get_store(None).await; let (account, session) = get_account_and_session().await; store.save_account(account.clone()).await.expect("Can't save account"); let changes = Changes { sessions: vec![session.clone()], ..Default::default() }; store.save_changes(changes).await.unwrap(); let sessions = store.get_sessions(&session.sender_key).await.expect("Can't load sessions").unwrap(); let loaded_session = sessions.lock().await.get(0).cloned().unwrap(); assert_eq!(&session, &loaded_session); } #[async_test] async fn add_and_save_session() { let (store, dir) = get_store(None).await; let (account, session) = get_account_and_session().await; let sender_key = session.sender_key.to_owned(); let session_id = session.session_id().to_owned(); store.save_account(account.clone()).await.expect("Can't save account"); let changes = Changes { sessions: vec![session.clone()], ..Default::default() }; store.save_changes(changes).await.unwrap(); let sessions = store.get_sessions(&sender_key).await.unwrap().unwrap(); let sessions_lock = sessions.lock().await; let session = &sessions_lock[0]; assert_eq!(session_id, session.session_id()); drop(store); let store = SledStore::open_with_passphrase(dir.path(), None).expect("Can't create store"); let loaded_account = store.load_account().await.unwrap().unwrap(); assert_eq!(account, loaded_account); let sessions = store.get_sessions(&sender_key).await.unwrap().unwrap(); let sessions_lock = sessions.lock().await; let session = &sessions_lock[0]; assert_eq!(session_id, session.session_id()); } #[async_test] async fn save_inbound_group_session() { let (account, store, _dir) = get_loaded_store().await; let identity_keys = account.identity_keys(); let outbound_session = OlmOutboundGroupSession::new(); let session = InboundGroupSession::new( identity_keys.curve25519(), identity_keys.ed25519(), &room_id!("!test:localhost"), GroupSessionKey(outbound_session.session_key()), None, ) .expect("Can't create session"); let changes = Changes { inbound_group_sessions: vec![session], ..Default::default() }; store.save_changes(changes).await.expect("Can't save group session"); } #[async_test] async fn load_inbound_group_session() { let (account, store, dir) = get_loaded_store().await; let identity_keys = account.identity_keys(); let outbound_session = OlmOutboundGroupSession::new(); let session = InboundGroupSession::new( identity_keys.curve25519(), identity_keys.ed25519(), &room_id!("!test:localhost"), GroupSessionKey(outbound_session.session_key()), None, ) .expect("Can't create session"); let mut export = session.export().await; export.forwarding_curve25519_key_chain = vec!["some_chain".to_owned()]; let session = InboundGroupSession::from_export(export).unwrap(); let changes = Changes { inbound_group_sessions: vec![session.clone()], ..Default::default() }; store.save_changes(changes).await.expect("Can't save group session"); drop(store); let store = SledStore::open_with_passphrase(dir.path(), None).expect("Can't create store"); store.load_account().await.unwrap(); let loaded_session = store .get_inbound_group_session(&session.room_id, &session.sender_key, session.session_id()) .await .unwrap() .unwrap(); assert_eq!(session, loaded_session); let export = loaded_session.export().await; assert!(!export.forwarding_curve25519_key_chain.is_empty()) } #[async_test] async fn test_tracked_users() { let (_account, store, dir) = get_loaded_store().await; let device = get_device(); assert!(store.update_tracked_user(device.user_id(), false).await.unwrap()); assert!(!store.update_tracked_user(device.user_id(), false).await.unwrap()); assert!(store.is_user_tracked(device.user_id())); assert!(!store.users_for_key_query().contains(device.user_id())); assert!(!store.update_tracked_user(device.user_id(), true).await.unwrap()); assert!(store.users_for_key_query().contains(device.user_id())); drop(store); let store = SledStore::open_with_passphrase(dir.path(), None).expect("Can't create store"); store.load_account().await.unwrap(); assert!(store.is_user_tracked(device.user_id())); assert!(store.users_for_key_query().contains(device.user_id())); store.update_tracked_user(device.user_id(), false).await.unwrap(); assert!(!store.users_for_key_query().contains(device.user_id())); drop(store); let store = SledStore::open_with_passphrase(dir.path(), None).expect("Can't create store"); store.load_account().await.unwrap(); assert!(!store.users_for_key_query().contains(device.user_id())); } #[async_test] async fn device_saving() { let (_account, store, dir) = get_loaded_store().await; let device = get_device(); let changes = Changes { devices: DeviceChanges { changed: vec![device.clone()], ..Default::default() }, ..Default::default() }; store.save_changes(changes).await.unwrap(); drop(store); let store = SledStore::open_with_passphrase(dir.path(), None).expect("Can't create store"); store.load_account().await.unwrap(); let loaded_device = store.get_device(device.user_id(), device.device_id()).await.unwrap().unwrap(); assert_eq!(device, loaded_device); for algorithm in loaded_device.algorithms() { assert!(device.algorithms().contains(algorithm)); } assert_eq!(device.algorithms().len(), loaded_device.algorithms().len()); assert_eq!(device.keys(), loaded_device.keys()); let user_devices = store.get_user_devices(device.user_id()).await.unwrap(); assert_eq!(&**user_devices.keys().next().unwrap(), device.device_id()); assert_eq!(user_devices.values().next().unwrap(), &device); } #[async_test] async fn device_deleting() { let (_account, store, dir) = get_loaded_store().await; let device = get_device(); let changes = Changes { devices: DeviceChanges { changed: vec![device.clone()], ..Default::default() }, ..Default::default() }; store.save_changes(changes).await.unwrap(); let changes = Changes { devices: DeviceChanges { deleted: vec![device.clone()], ..Default::default() }, ..Default::default() }; store.save_changes(changes).await.unwrap(); drop(store); let store = SledStore::open_with_passphrase(dir.path(), None).expect("Can't create store"); store.load_account().await.unwrap(); let loaded_device = store.get_device(device.user_id(), device.device_id()).await.unwrap(); assert!(loaded_device.is_none()); } #[async_test] async fn user_saving() { let dir = tempdir().unwrap(); let tmpdir_path = dir.path().to_str().unwrap(); let user_id = user_id!("@example:localhost"); let device_id: &DeviceId = "WSKKLTJZCL".into(); let store = SledStore::open_with_passphrase(tmpdir_path, None).expect("Can't create store"); let account = ReadOnlyAccount::new(&user_id, device_id); store.save_account(account.clone()).await.expect("Can't save account"); let own_identity = get_own_identity(); let changes = Changes { identities: IdentityChanges { changed: vec![own_identity.clone().into()], ..Default::default() }, ..Default::default() }; store.save_changes(changes).await.expect("Can't save identity"); drop(store); let store = SledStore::open_with_passphrase(dir.path(), None).expect("Can't create store"); store.load_account().await.unwrap(); let loaded_user = store.get_user_identity(own_identity.user_id()).await.unwrap().unwrap(); assert_eq!(loaded_user.master_key(), own_identity.master_key()); assert_eq!(loaded_user.self_signing_key(), own_identity.self_signing_key()); assert_eq!(loaded_user, own_identity.clone().into()); let other_identity = get_other_identity(); let changes = Changes { identities: IdentityChanges { changed: vec![other_identity.clone().into()], ..Default::default() }, ..Default::default() }; store.save_changes(changes).await.unwrap(); let loaded_user = store.get_user_identity(other_identity.user_id()).await.unwrap().unwrap(); assert_eq!(loaded_user.master_key(), other_identity.master_key()); assert_eq!(loaded_user.self_signing_key(), other_identity.self_signing_key()); assert_eq!(loaded_user, other_identity.into()); own_identity.mark_as_verified(); let changes = Changes { identities: IdentityChanges { changed: vec![own_identity.into()], ..Default::default() }, ..Default::default() }; store.save_changes(changes).await.unwrap(); let loaded_user = store.get_user_identity(&user_id).await.unwrap().unwrap(); assert!(loaded_user.own().unwrap().is_verified()) } #[async_test] async fn private_identity_saving() { let (_, store, _dir) = get_loaded_store().await; assert!(store.load_identity().await.unwrap().is_none()); let identity = PrivateCrossSigningIdentity::new(alice_id()).await; let changes = Changes { private_identity: Some(identity.clone()), ..Default::default() }; store.save_changes(changes).await.unwrap(); let loaded_identity = store.load_identity().await.unwrap().unwrap(); assert_eq!(identity.user_id(), loaded_identity.user_id()); } #[async_test] async fn olm_hash_saving() { let (_, store, _dir) = get_loaded_store().await; let hash = OlmMessageHash { sender_key: "test_sender".to_owned(), hash: "test_hash".to_owned() }; let mut changes = Changes::default(); changes.message_hashes.push(hash.clone()); assert!(!store.is_message_known(&hash).await.unwrap()); store.save_changes(changes).await.unwrap(); assert!(store.is_message_known(&hash).await.unwrap()); } #[async_test] async fn key_request_saving() { let (account, store, _dir) = get_loaded_store().await; let id = Uuid::new_v4(); let info: SecretInfo = RequestedKeyInfo::new( EventEncryptionAlgorithm::MegolmV1AesSha2, room_id!("!test:localhost"), "test_sender_key".to_string(), "test_session_id".to_string(), ) .into(); let request = OutgoingKeyRequest { request_recipient: account.user_id().to_owned(), request_id: id, info: info.clone(), sent_out: false, }; assert!(store.get_outgoing_secret_requests(id).await.unwrap().is_none()); let mut changes = Changes::default(); changes.key_requests.push(request.clone()); store.save_changes(changes).await.unwrap(); let request = Some(request); let stored_request = store.get_outgoing_secret_requests(id).await.unwrap(); assert_eq!(request, stored_request); let stored_request = store.get_secret_request_by_info(&info).await.unwrap(); assert_eq!(request, stored_request); assert!(!store.get_unsent_secret_requests().await.unwrap().is_empty()); let request = OutgoingKeyRequest { request_recipient: account.user_id().to_owned(), request_id: id, info: info.clone(), sent_out: true, }; let mut changes = Changes::default(); changes.key_requests.push(request.clone()); store.save_changes(changes).await.unwrap(); assert!(store.get_unsent_secret_requests().await.unwrap().is_empty()); let stored_request = store.get_outgoing_secret_requests(id).await.unwrap(); assert_eq!(Some(request), stored_request); store.delete_outgoing_secret_requests(id).await.unwrap(); let stored_request = store.get_outgoing_secret_requests(id).await.unwrap(); assert_eq!(None, stored_request); let stored_request = store.get_secret_request_by_info(&info).await.unwrap(); assert_eq!(None, stored_request); assert!(store.get_unsent_secret_requests().await.unwrap().is_empty()); } }