crypto: Allow cross signing identities to be stored/restored.
parent
78d7f6c10b
commit
f60dc7ed78
|
@ -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]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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<PickledMasterSigning>,
|
||||
user_signing_key: Option<PickledUserSigning>,
|
||||
self_signing_key: Option<PickledSelfSigning>,
|
||||
|
@ -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<PickledCrossSigningIdentity, JsonError> {
|
||||
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<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)?)
|
||||
} 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
|
||||
|
|
|
@ -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<Option<String>> {
|
||||
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)]
|
||||
|
|
|
@ -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<Option<PrivateCrossSigningIdentity>>;
|
||||
|
||||
/// TODO
|
||||
async fn save_changes(&self, changes: Changes) -> Result<()>;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<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<()> {
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue