crypto: Allow cross signing identities to be stored/restored.

master
Damir Jelić 2020-10-22 14:52:19 +02:00
parent 78d7f6c10b
commit f60dc7ed78
7 changed files with 152 additions and 15 deletions

View File

@ -21,7 +21,7 @@ js_int = "0.1.9"
[dependencies.ruma] [dependencies.ruma]
version = "0.0.1" version = "0.0.1"
git = "https://github.com/ruma/ruma" git = "https://github.com/ruma/ruma"
rev = "50eb700571480d1440e15a387d10f98be8abab59" rev = "db2f58032953ccb6d8ae712d64d713ebdf412598"
features = ["client-api", "unstable-pre-spec", "unstable-exhaustive-types"] features = ["client-api", "unstable-pre-spec", "unstable-exhaustive-types"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View File

@ -32,7 +32,7 @@ pub use group_sessions::{
pub(crate) use group_sessions::{GroupSessionKey, OutboundGroupSession}; pub(crate) use group_sessions::{GroupSessionKey, OutboundGroupSession};
pub use olm_rs::{account::IdentityKeys, PicklingMode}; pub use olm_rs::{account::IdentityKeys, PicklingMode};
pub use session::{PickledSession, Session, SessionPickle}; pub use session::{PickledSession, Session, SessionPickle};
pub(crate) use signing::PrivateCrossSigningIdentity; pub use signing::{PrivateCrossSigningIdentity, PickledCrossSigningIdentity};
pub(crate) use utility::Utility; pub(crate) use utility::Utility;
#[cfg(test)] #[cfg(test)]

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![allow(dead_code)] #![allow(dead_code,missing_docs)]
use aes_gcm::{ use aes_gcm::{
aead::{generic_array::GenericArray, Aead, NewAead}, aead::{generic_array::GenericArray, Aead, NewAead},
@ -213,9 +213,13 @@ pub struct PrivateCrossSigningIdentity {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PickledCrossSigningIdentity { pub struct PickledCrossSigningIdentity {
user_id: UserId, pub user_id: UserId,
shared: bool, pub shared: bool,
pub pickle: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PickledSignings {
master_key: Option<PickledMasterSigning>, master_key: Option<PickledMasterSigning>,
user_signing_key: Option<PickledUserSigning>, user_signing_key: Option<PickledUserSigning>,
self_signing_key: Option<PickledSelfSigning>, self_signing_key: Option<PickledSelfSigning>,
@ -348,6 +352,10 @@ impl Signing {
} }
impl PrivateCrossSigningIdentity { impl PrivateCrossSigningIdentity {
pub fn user_id(&self) -> &UserId {
&self.user_id
}
pub(crate) fn empty(user_id: UserId) -> Self { pub(crate) fn empty(user_id: UserId) -> Self {
Self { Self {
user_id: Arc::new(user_id), user_id: Arc::new(user_id),
@ -402,7 +410,7 @@ impl PrivateCrossSigningIdentity {
self.shared.load(Ordering::SeqCst) self.shared.load(Ordering::SeqCst)
} }
pub async fn pickle(&self, pickle_key: &[u8]) -> PickledCrossSigningIdentity { pub async fn pickle(&self, pickle_key: &[u8]) -> Result<PickledCrossSigningIdentity, JsonError> {
let master_key = if let Some(m) = self.master_key.lock().await.as_ref() { let master_key = if let Some(m) = self.master_key.lock().await.as_ref() {
Some(m.pickle(pickle_key).await) Some(m.pickle(pickle_key).await)
} else { } else {
@ -421,32 +429,49 @@ impl PrivateCrossSigningIdentity {
None 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(), user_id: self.user_id.as_ref().to_owned(),
shared: self.shared(), shared: self.shared(),
master_key, pickle,
self_signing_key, })
user_signing_key,
}
} }
/// 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( pub async fn from_pickle(
pickle: PickledCrossSigningIdentity, pickle: PickledCrossSigningIdentity,
pickle_key: &[u8], pickle_key: &[u8],
) -> Result<Self, SigningError> { ) -> Result<Self, SigningError> {
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)?) Some(MasterSigning::from_pickle(m, pickle_key)?)
} else { } else {
None 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)?) Some(SelfSigning::from_pickle(s, pickle_key)?)
} else { } else {
None 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)?) Some(UserSigning::from_pickle(u, pickle_key)?)
} else { } else {
None None
@ -573,7 +598,7 @@ mod test {
async fn identity_pickling() { async fn identity_pickling() {
let identity = PrivateCrossSigningIdentity::new(user_id()).await; 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()) let unpickled = PrivateCrossSigningIdentity::from_pickle(pickled, pickle_key())
.await .await

View File

@ -29,6 +29,7 @@ use super::{
Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session, Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session,
}; };
use crate::identities::{ReadOnlyDevice, UserIdentities}; 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. /// An in-memory only store that will forget all the E2EE key once it's dropped.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -207,6 +208,14 @@ impl CryptoStore for MemoryStore {
async fn get_value(&self, key: &str) -> Result<Option<String>> { async fn get_value(&self, key: &str) -> Result<Option<String>> {
Ok(self.values.get(key).map(|v| v.to_owned())) Ok(self.values.get(key).map(|v| v.to_owned()))
} }
async fn save_identity(&self, _: PrivateCrossSigningIdentity) -> Result<()> {
Ok(())
}
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>> {
Ok(None)
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -79,6 +79,7 @@ use matrix_sdk_common_macros::async_trait;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use matrix_sdk_common_macros::send_sync; use matrix_sdk_common_macros::send_sync;
use crate::olm::PrivateCrossSigningIdentity;
use crate::{ use crate::{
error::SessionUnpicklingError, error::SessionUnpicklingError,
identities::{Device, ReadOnlyDevice, UserDevices, UserIdentities}, identities::{Device, ReadOnlyDevice, UserDevices, UserIdentities},
@ -337,6 +338,12 @@ pub trait CryptoStore: Debug {
/// * `account` - The account that should be stored. /// * `account` - The account that should be stored.
async fn save_account(&self, account: ReadOnlyAccount) -> Result<()>; async fn save_account(&self, account: ReadOnlyAccount) -> Result<()>;
/// TODO
async fn save_identity(&self, identity: PrivateCrossSigningIdentity) -> Result<()>;
/// TODO
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>>;
/// TODO /// TODO
async fn save_changes(&self, changes: Changes) -> Result<()>; async fn save_changes(&self, changes: Changes) -> Result<()>;

View File

@ -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. /// Encrypt and export our pickle key using the given passphrase.
/// ///
/// # Arguments /// # Arguments

View File

@ -38,6 +38,8 @@ use super::{
pickle_key::{EncryptedPickleKey, PickleKey}, pickle_key::{EncryptedPickleKey, PickleKey},
Changes, CryptoStore, CryptoStoreError, Result, Changes, CryptoStore, CryptoStoreError, Result,
}; };
use crate::olm::PickledCrossSigningIdentity;
use crate::olm::PrivateCrossSigningIdentity;
use crate::{ use crate::{
identities::{LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserIdentities, UserIdentity}, identities::{LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserIdentities, UserIdentity},
olm::{ olm::{
@ -190,6 +192,23 @@ impl SqliteStore {
) )
.await?; .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 connection
.execute( .execute(
r#" r#"
@ -1054,6 +1073,10 @@ impl SqliteStore {
self.pickle_key.pickle_mode() self.pickle_key.pickle_mode()
} }
fn get_pickle_key(&self) -> &[u8] {
self.pickle_key.key()
}
async fn save_inbound_group_session_helper( async fn save_inbound_group_session_helper(
&self, &self,
account_id: i64, account_id: i64,
@ -1583,6 +1606,62 @@ impl CryptoStore for SqliteStore {
Ok(()) 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<Option<PrivateCrossSigningIdentity>> {
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<()> { async fn save_changes(&self, changes: Changes) -> Result<()> {
let mut connection = self.connection.lock().await; let mut connection = self.connection.lock().await;
let mut transaction = connection.begin().await?; let mut transaction = connection.begin().await?;
@ -1734,6 +1813,7 @@ impl std::fmt::Debug for SqliteStore {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::olm::PrivateCrossSigningIdentity;
use crate::{ use crate::{
identities::{ identities::{
device::test::get_device, device::test::get_device,
@ -2266,6 +2346,17 @@ mod test {
assert!(loaded_user.own().unwrap().is_verified()) 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)] #[tokio::test(threaded_scheduler)]
async fn key_value_saving() { async fn key_value_saving() {
let (_, store, _dir) = get_loaded_store().await; let (_, store, _dir) = get_loaded_store().await;