diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 7302ebfc..4a074fb7 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -21,7 +21,7 @@ js_int = "0.1.9" [dependencies.ruma] version = "0.0.1" git = "https://github.com/ruma/ruma" -rev = "50eb700571480d1440e15a387d10f98be8abab59" +rev = "db2f58032953ccb6d8ae712d64d713ebdf412598" features = ["client-api", "unstable-pre-spec", "unstable-exhaustive-types"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/matrix_sdk_crypto/src/olm/mod.rs b/matrix_sdk_crypto/src/olm/mod.rs index df2b387a..d87b1092 100644 --- a/matrix_sdk_crypto/src/olm/mod.rs +++ b/matrix_sdk_crypto/src/olm/mod.rs @@ -32,7 +32,7 @@ pub use group_sessions::{ pub(crate) use group_sessions::{GroupSessionKey, OutboundGroupSession}; pub use olm_rs::{account::IdentityKeys, PicklingMode}; pub use session::{PickledSession, Session, SessionPickle}; -pub(crate) use signing::PrivateCrossSigningIdentity; +pub use signing::{PrivateCrossSigningIdentity, PickledCrossSigningIdentity}; pub(crate) use utility::Utility; #[cfg(test)] diff --git a/matrix_sdk_crypto/src/olm/signing.rs b/matrix_sdk_crypto/src/olm/signing.rs index d6272a20..f92f1246 100644 --- a/matrix_sdk_crypto/src/olm/signing.rs +++ b/matrix_sdk_crypto/src/olm/signing.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(dead_code)] +#![allow(dead_code,missing_docs)] use aes_gcm::{ aead::{generic_array::GenericArray, Aead, NewAead}, @@ -213,9 +213,13 @@ pub struct PrivateCrossSigningIdentity { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PickledCrossSigningIdentity { - user_id: UserId, - shared: bool, + pub user_id: UserId, + pub shared: bool, + pub pickle: String, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +struct PickledSignings { master_key: Option, user_signing_key: Option, self_signing_key: Option, @@ -348,6 +352,10 @@ impl Signing { } impl PrivateCrossSigningIdentity { + pub fn user_id(&self) -> &UserId { + &self.user_id + } + pub(crate) fn empty(user_id: UserId) -> Self { Self { user_id: Arc::new(user_id), @@ -402,7 +410,7 @@ impl PrivateCrossSigningIdentity { self.shared.load(Ordering::SeqCst) } - pub async fn pickle(&self, pickle_key: &[u8]) -> PickledCrossSigningIdentity { + pub async fn pickle(&self, pickle_key: &[u8]) -> Result { let master_key = if let Some(m) = self.master_key.lock().await.as_ref() { Some(m.pickle(pickle_key).await) } else { @@ -421,32 +429,49 @@ impl PrivateCrossSigningIdentity { None }; - PickledCrossSigningIdentity { + let pickle = PickledSignings { + master_key, + user_signing_key, + self_signing_key, + }; + + println!("HELOOO {:#?}", pickle); + + let pickle = serde_json::to_string(&pickle)?; + + Ok(PickledCrossSigningIdentity { user_id: self.user_id.as_ref().to_owned(), shared: self.shared(), - master_key, - self_signing_key, - user_signing_key, - } + pickle, + }) } + /// Restore the private cross signing identity from a pickle. + /// + /// # Panic + /// + /// Panics if the pickle_key isn't 32 bytes long. pub async fn from_pickle( pickle: PickledCrossSigningIdentity, pickle_key: &[u8], ) -> Result { - let master = if let Some(m) = pickle.master_key { + println!("HELOOO UNPICKLED {:#?}", pickle.pickle); + let signings: PickledSignings = serde_json::from_str(&pickle.pickle)?; + + + let master = if let Some(m) = signings.master_key { Some(MasterSigning::from_pickle(m, pickle_key)?) } else { None }; - let self_signing = if let Some(s) = pickle.self_signing_key { + let self_signing = if let Some(s) = signings.self_signing_key { Some(SelfSigning::from_pickle(s, pickle_key)?) } else { None }; - let user_signing = if let Some(u) = pickle.user_signing_key { + let user_signing = if let Some(u) = signings.user_signing_key { Some(UserSigning::from_pickle(u, pickle_key)?) } else { None @@ -573,7 +598,7 @@ mod test { async fn identity_pickling() { let identity = PrivateCrossSigningIdentity::new(user_id()).await; - let pickled = identity.pickle(pickle_key()).await; + let pickled = identity.pickle(pickle_key()).await.unwrap(); let unpickled = PrivateCrossSigningIdentity::from_pickle(pickled, pickle_key()) .await diff --git a/matrix_sdk_crypto/src/store/memorystore.rs b/matrix_sdk_crypto/src/store/memorystore.rs index f41d4579..487f1d24 100644 --- a/matrix_sdk_crypto/src/store/memorystore.rs +++ b/matrix_sdk_crypto/src/store/memorystore.rs @@ -29,6 +29,7 @@ use super::{ Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session, }; use crate::identities::{ReadOnlyDevice, UserIdentities}; +use crate::olm::PrivateCrossSigningIdentity; /// An in-memory only store that will forget all the E2EE key once it's dropped. #[derive(Debug, Clone)] @@ -207,6 +208,14 @@ impl CryptoStore for MemoryStore { async fn get_value(&self, key: &str) -> Result> { Ok(self.values.get(key).map(|v| v.to_owned())) } + + async fn save_identity(&self, _: PrivateCrossSigningIdentity) -> Result<()> { + Ok(()) + } + + async fn load_identity(&self) -> Result> { + Ok(None) + } } #[cfg(test)] diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index 1f24a49d..c4e4365a 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -79,6 +79,7 @@ use matrix_sdk_common_macros::async_trait; #[cfg(not(target_arch = "wasm32"))] use matrix_sdk_common_macros::send_sync; +use crate::olm::PrivateCrossSigningIdentity; use crate::{ error::SessionUnpicklingError, identities::{Device, ReadOnlyDevice, UserDevices, UserIdentities}, @@ -337,6 +338,12 @@ pub trait CryptoStore: Debug { /// * `account` - The account that should be stored. async fn save_account(&self, account: ReadOnlyAccount) -> Result<()>; + /// TODO + async fn save_identity(&self, identity: PrivateCrossSigningIdentity) -> Result<()>; + + /// TODO + async fn load_identity(&self) -> Result>; + /// TODO async fn save_changes(&self, changes: Changes) -> Result<()>; diff --git a/matrix_sdk_crypto/src/store/pickle_key.rs b/matrix_sdk_crypto/src/store/pickle_key.rs index 1781e363..c49f6352 100644 --- a/matrix_sdk_crypto/src/store/pickle_key.rs +++ b/matrix_sdk_crypto/src/store/pickle_key.rs @@ -116,6 +116,11 @@ impl PickleKey { } } + /// Get the raw AES256 key. + pub fn key(&self) -> &[u8] { + &self.aes256_key + } + /// Encrypt and export our pickle key using the given passphrase. /// /// # Arguments diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index d2d8d77e..d4182bad 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -38,6 +38,8 @@ use super::{ pickle_key::{EncryptedPickleKey, PickleKey}, Changes, CryptoStore, CryptoStoreError, Result, }; +use crate::olm::PickledCrossSigningIdentity; +use crate::olm::PrivateCrossSigningIdentity; use crate::{ identities::{LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserIdentities, UserIdentity}, olm::{ @@ -190,6 +192,23 @@ impl SqliteStore { ) .await?; + connection + .execute( + r#" + CREATE TABLE IF NOT EXISTS private_identities ( + "id" INTEGER NOT NULL PRIMARY KEY, + "account_id" INTEGER NOT NULL, + "user_id" TEXT NOT NULL, + "pickle" TEXT NOT NULL, + "shared" INTEGER NOT NULL, + FOREIGN KEY ("account_id") REFERENCES "accounts" ("id") + ON DELETE CASCADE + UNIQUE(account_id, user_id) + ); + "#, + ) + .await?; + connection .execute( r#" @@ -1054,6 +1073,10 @@ impl SqliteStore { self.pickle_key.pickle_mode() } + fn get_pickle_key(&self) -> &[u8] { + self.pickle_key.key() + } + async fn save_inbound_group_session_helper( &self, account_id: i64, @@ -1583,6 +1606,62 @@ impl CryptoStore for SqliteStore { Ok(()) } + async fn save_identity(&self, identity: PrivateCrossSigningIdentity) -> Result<()> { + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; + + let pickle = identity.pickle(self.get_pickle_key()).await?; + + let mut connection = self.connection.lock().await; + + query( + "INSERT INTO private_identities ( + account_id, user_id, pickle, shared + ) VALUES (?1, ?2, ?3, ?4) + ON CONFLICT(account_id, user_id) DO UPDATE SET + pickle = excluded.pickle, + shared = excluded.shared + ", + ) + .bind(account_id) + .bind(pickle.user_id.as_str()) + .bind(pickle.pickle) + .bind(pickle.shared) + .execute(&mut *connection) + .await?; + + Ok(()) + } + + async fn load_identity(&self) -> Result> { + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; + let mut connection = self.connection.lock().await; + + let row: Option<(String, bool)> = query_as( + "SELECT pickle, shared FROM private_identities + WHERE account_id = ?", + ) + .bind(account_id) + .fetch_optional(&mut *connection) + .await?; + + if let Some(row) = row { + let pickle = PickledCrossSigningIdentity { + user_id: (&*self.user_id).clone(), + pickle: row.0, + shared: row.1, + }; + + // TODO remove this unwrap + let identity = PrivateCrossSigningIdentity::from_pickle(pickle, self.get_pickle_key()) + .await + .unwrap(); + + Ok(Some(identity)) + } else { + Ok(None) + } + } + async fn save_changes(&self, changes: Changes) -> Result<()> { let mut connection = self.connection.lock().await; let mut transaction = connection.begin().await?; @@ -1734,6 +1813,7 @@ impl std::fmt::Debug for SqliteStore { #[cfg(test)] mod test { + use crate::olm::PrivateCrossSigningIdentity; use crate::{ identities::{ device::test::get_device, @@ -2266,6 +2346,17 @@ mod test { assert!(loaded_user.own().unwrap().is_verified()) } + #[tokio::test(threaded_scheduler)] + async fn private_identity_saving() { + let (_, store, _dir) = get_loaded_store().await; + assert!(store.load_identity().await.unwrap().is_none()); + let identity = PrivateCrossSigningIdentity::new((&*store.user_id).clone()).await; + + store.save_identity(identity.clone()).await.unwrap(); + let loaded_identity = store.load_identity().await.unwrap().unwrap(); + assert_eq!(identity.user_id(), loaded_identity.user_id()); + } + #[tokio::test(threaded_scheduler)] async fn key_value_saving() { let (_, store, _dir) = get_loaded_store().await;