From 269cfc3d342356c8fe8bd86b53d33fd5399a91c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 2 Sep 2020 09:37:10 +0200 Subject: [PATCH] crypto: Add a pickled account struct making account storing easier. --- matrix_sdk_crypto/src/olm/account.rs | 73 +++++++++++++++----- matrix_sdk_crypto/src/olm/mod.rs | 2 +- matrix_sdk_crypto/src/store/sqlite.rs | 95 +++++++++++++-------------- 3 files changed, 102 insertions(+), 68 deletions(-) diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 74c42e08..d199f81d 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -37,6 +37,7 @@ use olm_rs::{ errors::{OlmAccountError, OlmSessionError}, PicklingMode, }; +use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; pub use olm_rs::{ @@ -66,6 +67,42 @@ pub struct Account { uploaded_signed_key_count: Arc, } +#[derive(Debug, Clone, Serialize, Deserialize)] +/// A typed representation of a base64 encoded string containing the account +/// pickle. +pub struct AccountPickle(String); + +impl AccountPickle { + /// Get the string representation of the pickle. + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl From for AccountPickle { + fn from(value: String) -> Self { + Self(value) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +/// A pickled version of an `Account`. +/// +/// Holds all the information that needs to be stored in a database to restore +/// an account. +pub struct PickledAccount { + /// The user id of the account owner. + pub user_id: UserId, + /// The device id of the account owner. + pub device_id: DeviceIdBox, + /// The pickled version of the Olm account. + pub pickle: AccountPickle, + /// Was the account shared. + pub shared: bool, + /// The number of uploaded one-time keys we have on the server. + pub uploaded_signed_key_count: i64, +} + #[cfg(not(tarpaulin_include))] impl fmt::Debug for Account { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -241,40 +278,40 @@ impl Account { /// /// * `pickle_mode` - The mode that was used to pickle the account, either an /// unencrypted mode or an encrypted using passphrase. - pub async fn pickle(&self, pickle_mode: PicklingMode) -> String { - self.inner.lock().await.pickle(pickle_mode) + pub async fn pickle(&self, pickle_mode: PicklingMode) -> PickledAccount { + let pickle = AccountPickle(self.inner.lock().await.pickle(pickle_mode)); + + PickledAccount { + user_id: self.user_id().to_owned(), + device_id: self.device_id().to_owned(), + pickle, + shared: self.shared(), + uploaded_signed_key_count: self.uploaded_key_count(), + } } - /// Restore an account from a previously pickled string. + /// Restore an account from a previously pickled one. /// /// # Arguments /// - /// * `pickle` - The pickled string of the account. + /// * `pickle` - The pickled version of the Account. /// /// * `pickle_mode` - The mode that was used to pickle the account, either an /// unencrypted mode or an encrypted using passphrase. - /// - /// * `shared` - Boolean determining if the account was uploaded to the - /// server. - #[allow(clippy::ptr_arg)] pub fn from_pickle( - pickle: String, + pickle: PickledAccount, pickle_mode: PicklingMode, - shared: bool, - uploaded_signed_key_count: i64, - user_id: &UserId, - device_id: &DeviceId, ) -> Result { - let account = OlmAccount::unpickle(pickle, pickle_mode)?; + let account = OlmAccount::unpickle(pickle.pickle.0, pickle_mode)?; let identity_keys = account.parsed_identity_keys(); Ok(Account { - user_id: Arc::new(user_id.to_owned()), - device_id: Arc::new(device_id.into()), + user_id: Arc::new(pickle.user_id), + device_id: Arc::new(pickle.device_id), inner: Arc::new(Mutex::new(account)), identity_keys: Arc::new(identity_keys), - shared: Arc::new(AtomicBool::from(shared)), - uploaded_signed_key_count: Arc::new(AtomicI64::new(uploaded_signed_key_count)), + shared: Arc::new(AtomicBool::from(pickle.shared)), + uploaded_signed_key_count: Arc::new(AtomicI64::new(pickle.uploaded_signed_key_count)), }) } diff --git a/matrix_sdk_crypto/src/olm/mod.rs b/matrix_sdk_crypto/src/olm/mod.rs index 495a51f9..0feafbc1 100644 --- a/matrix_sdk_crypto/src/olm/mod.rs +++ b/matrix_sdk_crypto/src/olm/mod.rs @@ -21,7 +21,7 @@ mod account; mod group_sessions; mod session; -pub use account::{Account, IdentityKeys}; +pub use account::{Account, AccountPickle, IdentityKeys, PickledAccount}; pub use group_sessions::{EncryptionSettings, InboundGroupSession}; pub(crate) use group_sessions::{GroupSessionKey, OutboundGroupSession}; pub use olm_rs::PicklingMode; diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index 3e2e6352..52b302dc 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -29,7 +29,6 @@ use matrix_sdk_common::{ instant::{Duration, Instant}, locks::Mutex, }; -use olm_rs::PicklingMode; use sqlx::{query, query_as, sqlite::SqliteQueryAs, Connect, Executor, SqliteConnection}; use url::Url; use zeroize::Zeroizing; @@ -38,7 +37,10 @@ use super::{CryptoStore, CryptoStoreError, Result}; use crate::{ device::{LocalTrust, ReadOnlyDevice}, memory_stores::{DeviceStore, GroupSessionStore, ReadOnlyUserDevices, SessionStore}, - olm::{Account, IdentityKeys, InboundGroupSession, Session}, + olm::{ + Account, AccountPickle, IdentityKeys, InboundGroupSession, PickledAccount, PicklingMode, + Session, + }, user_identity::UserIdentities, }; @@ -679,14 +681,15 @@ impl CryptoStore for SqliteStore { .await?; let result = if let Some((id, pickle, shared, uploaded_key_count)) = row { - let account = Account::from_pickle( - pickle, - self.get_pickle_mode(), + let pickle = PickledAccount { + user_id: (&*self.user_id).clone(), + device_id: (&*self.device_id).clone(), + pickle: AccountPickle::from(pickle), shared, - uploaded_key_count, - &self.user_id, - &self.device_id, - )?; + uploaded_signed_key_count: uploaded_key_count, + }; + + let account = Account::from_pickle(pickle, self.get_pickle_mode())?; *self.account_info.lock().unwrap() = Some(AccountInfo { account_id: id, @@ -720,18 +723,18 @@ impl CryptoStore for SqliteStore { shared = excluded.shared ", ) - .bind(&*self.user_id.to_string()) - .bind(&*self.device_id.to_string()) - .bind(&pickle) - .bind(account.shared()) - .bind(account.uploaded_key_count()) + .bind(pickle.user_id.as_str()) + .bind(pickle.device_id.as_str()) + .bind(pickle.pickle.as_str()) + .bind(pickle.shared) + .bind(pickle.uploaded_signed_key_count) .execute(&mut *connection) .await?; let account_id: (i64,) = query_as("SELECT id FROM accounts WHERE user_id = ? and device_id = ?") - .bind(&*self.user_id.to_string()) - .bind(&*self.device_id.to_string()) + .bind(self.user_id.as_str()) + .bind(self.device_id.as_str()) .fetch_one(&mut *connection) .await?; @@ -921,27 +924,37 @@ mod test { use super::{CryptoStore, SqliteStore}; - fn example_user_id() -> UserId { - user_id!("@example:localhost") + fn alice_id() -> UserId { + user_id!("@alice:example.org") } - fn example_device_id() -> &'static DeviceId { - "DEVICEID".into() + 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>) -> (SqliteStore, tempfile::TempDir) { let tmpdir = tempdir().unwrap(); let tmpdir_path = tmpdir.path().to_str().unwrap(); - let user_id = &example_user_id(); - let device_id = example_device_id(); - let store = if let Some(passphrase) = passphrase { - SqliteStore::open_with_passphrase(&user_id, device_id, tmpdir_path, passphrase) - .await - .expect("Can't create a passphrase protected store") + SqliteStore::open_with_passphrase( + &alice_id(), + &alice_device_id(), + tmpdir_path, + passphrase, + ) + .await + .expect("Can't create a passphrase protected store") } else { - SqliteStore::open(&user_id, device_id, tmpdir_path) + SqliteStore::open(&alice_id(), &alice_device_id(), tmpdir_path) .await .expect("Can't create store") }; @@ -960,22 +973,6 @@ mod test { (account, store, dir) } - 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() - } - fn get_account() -> Account { Account::new(&alice_id(), &alice_device_id()) } @@ -1011,7 +1008,7 @@ mod test { async fn create_store() { let tmpdir = tempdir().unwrap(); let tmpdir_path = tmpdir.path().to_str().unwrap(); - let _ = SqliteStore::open(&example_user_id(), "DEVICEID".into(), tmpdir_path) + let _ = SqliteStore::open(&alice_id(), &alice_device_id(), tmpdir_path) .await .expect("Can't create store"); } @@ -1138,7 +1135,7 @@ mod test { drop(store); - let store = SqliteStore::open(&example_user_id(), example_device_id(), dir.path()) + let store = SqliteStore::open(&alice_id(), &alice_device_id(), dir.path()) .await .expect("Can't create store"); @@ -1224,7 +1221,7 @@ mod test { assert!(store.users_for_key_query().contains(device.user_id())); drop(store); - let store = SqliteStore::open(&example_user_id(), example_device_id(), dir.path()) + let store = SqliteStore::open(&alice_id(), &alice_device_id(), dir.path()) .await .expect("Can't create store"); @@ -1239,7 +1236,7 @@ mod test { .unwrap(); assert!(!store.users_for_key_query().contains(device.user_id())); - let store = SqliteStore::open(&example_user_id(), example_device_id(), dir.path()) + let store = SqliteStore::open(&alice_id(), &alice_device_id(), dir.path()) .await .expect("Can't create store"); @@ -1257,7 +1254,7 @@ mod test { drop(store); - let store = SqliteStore::open(&example_user_id(), example_device_id(), dir.path()) + let store = SqliteStore::open(&alice_id(), &alice_device_id(), dir.path()) .await .expect("Can't create store"); @@ -1290,7 +1287,7 @@ mod test { store.save_devices(&[device.clone()]).await.unwrap(); store.delete_device(device.clone()).await.unwrap(); - let store = SqliteStore::open(&example_user_id(), example_device_id(), dir.path()) + let store = SqliteStore::open(&alice_id(), &alice_device_id(), dir.path()) .await .expect("Can't create store");