crypto: Allow cross signing identities to be stored/restored.
parent
78d7f6c10b
commit
f60dc7ed78
|
@ -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]
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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<()>;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue