crypto: Finish up the cross signing storing for the sqlite store.

master
Damir Jelić 2020-09-08 14:30:23 +02:00
parent d35cf56dc8
commit 9810a2f630
3 changed files with 373 additions and 133 deletions

View File

@ -54,7 +54,7 @@ features = ["std", "std-future"]
version = "0.3.5" version = "0.3.5"
optional = true optional = true
default-features = false default-features = false
features = ["runtime-tokio", "sqlite"] features = ["runtime-tokio", "sqlite", "macros"]
[dev-dependencies] [dev-dependencies]
tokio = { version = "0.2.22", features = ["rt-threaded", "macros"] } tokio = { version = "0.2.22", features = ["rt-threaded", "macros"] }

View File

@ -56,6 +56,54 @@ impl PartialEq for MasterPubkey {
} }
} }
impl PartialEq for SelfSigningPubkey {
fn eq(&self, other: &SelfSigningPubkey) -> bool {
self.0.user_id == other.0.user_id && self.0.keys == other.0.keys
}
}
impl PartialEq for UserSigningPubkey {
fn eq(&self, other: &UserSigningPubkey) -> bool {
self.0.user_id == other.0.user_id && self.0.keys == other.0.keys
}
}
impl From<CrossSigningKey> for MasterPubkey {
fn from(key: CrossSigningKey) -> Self {
Self(Arc::new(key))
}
}
impl From<CrossSigningKey> for SelfSigningPubkey {
fn from(key: CrossSigningKey) -> Self {
Self(Arc::new(key))
}
}
impl From<CrossSigningKey> for UserSigningPubkey {
fn from(key: CrossSigningKey) -> Self {
Self(Arc::new(key))
}
}
impl AsRef<CrossSigningKey> for MasterPubkey {
fn as_ref(&self) -> &CrossSigningKey {
&self.0
}
}
impl AsRef<CrossSigningKey> for SelfSigningPubkey {
fn as_ref(&self) -> &CrossSigningKey {
&self.0
}
}
impl AsRef<CrossSigningKey> for UserSigningPubkey {
fn as_ref(&self) -> &CrossSigningKey {
&self.0
}
}
impl From<&CrossSigningKey> for MasterPubkey { impl From<&CrossSigningKey> for MasterPubkey {
fn from(key: &CrossSigningKey) -> Self { fn from(key: &CrossSigningKey) -> Self {
Self(Arc::new(key.clone())) Self(Arc::new(key.clone()))
@ -304,6 +352,12 @@ impl From<OwnUserIdentity> for UserIdentities {
} }
} }
impl From<UserIdentity> for UserIdentities {
fn from(identity: UserIdentity) -> Self {
UserIdentities::Other(identity)
}
}
impl UserIdentities { impl UserIdentities {
/// The unique user id of this identity. /// The unique user id of this identity.
pub fn user_id(&self) -> &UserId { pub fn user_id(&self) -> &UserId {
@ -321,6 +375,23 @@ impl UserIdentities {
} }
} }
/// Get the self-signing key of the identity.
pub fn self_signing_key(&self) -> &SelfSigningPubkey {
match self {
UserIdentities::Own(i) => &i.self_signing_key,
UserIdentities::Other(i) => &i.self_signing_key,
}
}
/// Get the user-signing key of the identity, this is only present for our
/// own user identity..
pub fn user_signing_key(&self) -> Option<&UserSigningPubkey> {
match self {
UserIdentities::Own(i) => Some(&i.user_signing_key),
UserIdentities::Other(_) => None,
}
}
/// Destructure the enum into an `OwnUserIdentity` if it's of the correct /// Destructure the enum into an `OwnUserIdentity` if it's of the correct
/// type. /// type.
pub fn own(&self) -> Option<&OwnUserIdentity> { pub fn own(&self) -> Option<&OwnUserIdentity> {
@ -383,6 +454,11 @@ impl UserIdentity {
&self.master_key &self.master_key
} }
/// Get the public self-signing key of the identity.
pub fn self_signing_key(&self) -> &SelfSigningPubkey {
&self.self_signing_key
}
/// Update the identity with a new master key and self signing key. /// Update the identity with a new master key and self signing key.
/// ///
/// # Arguments /// # Arguments
@ -483,6 +559,16 @@ impl OwnUserIdentity {
&self.master_key &self.master_key
} }
/// Get the public self-signing key of the identity.
pub fn self_signing_key(&self) -> &SelfSigningPubkey {
&self.self_signing_key
}
/// Get the public user-signing key of the identity.
pub fn user_signing_key(&self) -> &UserSigningPubkey {
&self.user_signing_key
}
/// Check if the given identity has been signed by this identity. /// Check if the given identity has been signed by this identity.
/// ///
/// # Arguments /// # Arguments
@ -760,6 +846,16 @@ pub(crate) mod test {
own_identity(&own_key_query()) own_identity(&own_key_query())
} }
pub(crate) fn get_other_identity() -> UserIdentity {
let user_id = user_id!("@example2:localhost");
let response = other_key_query();
let master_key = response.master_keys.get(&user_id).unwrap();
let self_signing = response.self_signing_keys.get(&user_id).unwrap();
UserIdentity::new(master_key.into(), self_signing.into()).unwrap()
}
#[test] #[test]
fn own_identity_create() { fn own_identity_create() {
let user_id = user_id!("@example:localhost"); let user_id = user_id!("@example:localhost");

View File

@ -21,7 +21,7 @@ use std::{
}; };
use async_trait::async_trait; use async_trait::async_trait;
use dashmap::{DashMap, DashSet}; use dashmap::DashSet;
use matrix_sdk_common::{ use matrix_sdk_common::{
api::r0::keys::{CrossSigningKey, KeyUsage}, api::r0::keys::{CrossSigningKey, KeyUsage},
identifiers::{ identifiers::{
@ -39,7 +39,7 @@ use super::{
CryptoStore, CryptoStoreError, Result, CryptoStore, CryptoStoreError, Result,
}; };
use crate::{ use crate::{
identities::{LocalTrust, ReadOnlyDevice, UserIdentities}, identities::{LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserIdentities, UserIdentity},
olm::{ olm::{
Account, AccountPickle, IdentityKeys, InboundGroupSession, InboundGroupSessionPickle, Account, AccountPickle, IdentityKeys, InboundGroupSession, InboundGroupSessionPickle,
PickledAccount, PickledInboundGroupSession, PickledSession, PicklingMode, Session, PickledAccount, PickledInboundGroupSession, PickledSession, PicklingMode, Session,
@ -72,6 +72,24 @@ struct AccountInfo {
identity_keys: Arc<IdentityKeys>, identity_keys: Arc<IdentityKeys>,
} }
#[derive(Debug, PartialEq, Copy, Clone, sqlx::Type)]
#[repr(i32)]
enum CrosssigningKeyType {
Master = 0,
SelfSigning = 1,
UserSigning = 2,
}
impl Into<KeyUsage> for CrosssigningKeyType {
fn into(self) -> KeyUsage {
match self {
CrosssigningKeyType::Master => KeyUsage::Master,
CrosssigningKeyType::SelfSigning => KeyUsage::SelfSigning,
CrosssigningKeyType::UserSigning => KeyUsage::UserSigning,
}
}
}
static DATABASE_NAME: &str = "matrix-sdk-crypto.db"; static DATABASE_NAME: &str = "matrix-sdk-crypto.db";
impl SqliteStore { impl SqliteStore {
@ -329,6 +347,23 @@ impl SqliteStore {
) )
.await?; .await?;
connection
.execute(
r#"
CREATE TABLE IF NOT EXISTS cross_signing_keys (
"id" INTEGER NOT NULL PRIMARY KEY,
"key_type" INTEGER NOT NULL,
"usage" STRING NOT NULL,
"user_id" INTEGER NOT NULL,
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
UNIQUE(user_id, key_type)
);
CREATE INDEX IF NOT EXISTS "cross_signing_keys_users" ON "users" ("user_id");
"#,
)
.await?;
connection connection
.execute( .execute(
r#" r#"
@ -336,14 +371,12 @@ impl SqliteStore {
"id" INTEGER NOT NULL PRIMARY KEY, "id" INTEGER NOT NULL PRIMARY KEY,
"key" TEXT NOT NULL, "key" TEXT NOT NULL,
"key_id" TEXT NOT NULL, "key_id" TEXT NOT NULL,
"key_type" TEXT NOT NULL, "cross_signing_key" INTEGER NOT NULL,
"usage" TEXT NOT NULL, FOREIGN KEY ("cross_signing_key") REFERENCES "cross_signing_keys" ("id") ON DELETE CASCADE
"user_id" INTEGER NOT NULL, UNIQUE(cross_signing_key, key_id)
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
UNIQUE(user_id, key_id, key_type)
); );
CREATE INDEX IF NOT EXISTS "user_keys_user_id" ON "users" ("user_id"); CREATE INDEX IF NOT EXISTS "cross_signing_keys_keys" ON "cross_signing_keys" ("cross_signing_key");
"#, "#,
) )
.await?; .await?;
@ -356,13 +389,13 @@ impl SqliteStore {
"user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL,
"key_id" INTEGER NOT NULL, "key_id" INTEGER NOT NULL,
"signature" TEXT NOT NULL, "signature" TEXT NOT NULL,
"user_key" INTEGER NOT NULL, "cross_signing_key" INTEGER NOT NULL,
FOREIGN KEY ("user_key") REFERENCES "user_keys" ("id") FOREIGN KEY ("cross_signing_key") REFERENCES "cross_signing_keys" ("id")
ON DELETE CASCADE ON DELETE CASCADE
UNIQUE(user_id, key_id, user_key) UNIQUE(user_id, key_id, cross_signing_key)
); );
CREATE INDEX IF NOT EXISTS "user_keys_device_id" ON "device_keys" ("device_id"); CREATE INDEX IF NOT EXISTS "cross_signing_keys_signatures" ON "cross_signing_keys" ("cross_signing_key");
"#, "#,
) )
.await?; .await?;
@ -728,49 +761,42 @@ impl SqliteStore {
} }
} }
async fn load_user(&self, user_id: &UserId) -> Result<Option<UserIdentities>> { async fn load_cross_signing_key(
let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; connection: &mut SqliteConnection,
let mut connection = self.connection.lock().await; user_id: &UserId,
user_row_id: i64,
let row: Option<(i64,)> = key_type: CrosssigningKeyType,
query_as("SELECT id FROM users WHERE account_id = ? and user_id = ?") ) -> Result<CrossSigningKey> {
.bind(account_id) let row: (i64, String) =
.bind(user_id.as_str()) query_as("SELECT id, usage FROM cross_signing_keys WHERE user_id =? and key_type =?")
.fetch_optional(&mut *connection) .bind(user_row_id)
.bind(key_type)
.fetch_one(&mut *connection)
.await?; .await?;
let user_row_id = if let Some(row) = row { let key_row_id = row.0;
row.0 let usage: Vec<KeyUsage> = serde_json::from_str(&row.1)?;
} else {
return Ok(None);
};
let key_rows: Vec<(i64, String, String, String)> = query_as( let key_rows: Vec<(String, String)> =
"SELECT id, key_id, key, usage FROM user_keys WHERE user_id = ? and key_type = ?", query_as("SELECT key_id, key FROM user_keys WHERE cross_signing_key = ?")
) .bind(key_row_id)
.bind(user_row_id)
.bind("master_key")
.fetch_all(&mut *connection) .fetch_all(&mut *connection)
.await?; .await?;
let mut keys = BTreeMap::new(); let mut keys = BTreeMap::new();
let mut signatures = BTreeMap::new(); let mut signatures = BTreeMap::new();
let mut key_usage = HashSet::new();
for row in key_rows { for row in key_rows {
let key_row_id = row.0; let key_id = row.0;
let key_id = row.1; let key = row.1;
let key = row.2;
let usage: Vec<String> = serde_json::from_str(&row.3)?;
keys.insert(key_id, key); keys.insert(key_id, key);
key_usage.extend(usage); }
let mut signature_rows: Vec<(String, String, String)> = query_as( let mut signature_rows: Vec<(String, String, String)> = query_as(
"SELECT user_id, key_id, signature, FROM user_key_signatures WHERE user_key = ?", "SELECT user_id, key_id, signature FROM user_key_signatures WHERE cross_signing_key = ?",
) )
.bind(user_row_id) .bind(key_row_id)
.bind("master_key")
.fetch_all(&mut *connection) .fetch_all(&mut *connection)
.await?; .await?;
@ -789,21 +815,130 @@ impl SqliteStore {
.or_insert_with(BTreeMap::new) .or_insert_with(BTreeMap::new)
.insert(key_id, signature); .insert(key_id, signature);
} }
}
let usage: Vec<KeyUsage> = key_usage Ok(CrossSigningKey {
.iter()
.filter_map(|u| serde_json::from_str(u).ok())
.collect();
let key = CrossSigningKey {
user_id: user_id.to_owned(), user_id: user_id.to_owned(),
usage, usage,
keys, keys,
signatures, signatures,
})
}
async fn load_user(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?;
let mut connection = self.connection.lock().await;
let row: Option<(i64,)> =
query_as("SELECT id FROM users WHERE account_id = ? and user_id = ?")
.bind(account_id)
.bind(user_id.as_str())
.fetch_optional(&mut *connection)
.await?;
let user_row_id = if let Some(row) = row {
row.0
} else {
return Ok(None);
}; };
Ok(None) let master = SqliteStore::load_cross_signing_key(
&mut connection,
user_id,
user_row_id,
CrosssigningKeyType::Master,
)
.await?;
let self_singing = SqliteStore::load_cross_signing_key(
&mut connection,
user_id,
user_row_id,
CrosssigningKeyType::SelfSigning,
)
.await?;
if user_id == &*self.user_id {
let user_signing = SqliteStore::load_cross_signing_key(
&mut connection,
user_id,
user_row_id,
CrosssigningKeyType::UserSigning,
)
.await?;
Ok(Some(UserIdentities::Own(
OwnUserIdentity::new(master.into(), self_singing.into(), user_signing.into())
.unwrap(),
)))
} else {
Ok(Some(UserIdentities::Other(
UserIdentity::new(master.into(), self_singing.into()).unwrap(),
)))
}
}
async fn save_cross_signing_key(
connection: &mut SqliteConnection,
user_row_id: i64,
key_type: CrosssigningKeyType,
cross_signing_key: impl AsRef<CrossSigningKey>,
) -> Result<()> {
let cross_signing_key: &CrossSigningKey = cross_signing_key.as_ref();
query(
"REPLACE INTO cross_signing_keys (
user_id, key_type, usage
) VALUES (?1, ?2, ?3)
",
)
.bind(user_row_id)
.bind(key_type)
.bind(serde_json::to_string(&cross_signing_key.usage)?)
.execute(&mut *connection)
.await?;
let row: (i64,) = query_as(
"SELECT id FROM cross_signing_keys
WHERE user_id = ? and key_type = ?",
)
.bind(user_row_id)
.bind(key_type)
.fetch_one(&mut *connection)
.await?;
let key_row_id = row.0;
for (key_id, key) in &cross_signing_key.keys {
query(
"REPLACE INTO user_keys (
cross_signing_key, key_id, key
) VALUES (?1, ?2, ?3)
",
)
.bind(key_row_id)
.bind(key_id.as_str())
.bind(key)
.execute(&mut *connection)
.await?;
}
for (user_id, signature_map) in &cross_signing_key.signatures {
for (key_id, signature) in signature_map {
query(
"REPLACE INTO user_key_signatures (
cross_signing_key, user_id, key_id, signature
) VALUES (?1, ?2, ?3, ?4)
",
)
.bind(key_row_id)
.bind(user_id.as_str())
.bind(key_id.as_str())
.bind(signature)
.execute(&mut *connection)
.await?;
}
}
Ok(())
} }
async fn save_user_helper(&self, user: &UserIdentities) -> Result<()> { async fn save_user_helper(&self, user: &UserIdentities) -> Result<()> {
@ -811,12 +946,7 @@ impl SqliteStore {
let mut connection = self.connection.lock().await; let mut connection = self.connection.lock().await;
query( query("REPLACE INTO users (account_id, user_id) VALUES (?1, ?2)")
"INSERT OR IGNORE INTO users (
account_id, user_id
) VALUES (?1, ?2)
",
)
.bind(account_id) .bind(account_id)
.bind(user.user_id().as_str()) .bind(user.user_id().as_str())
.execute(&mut *connection) .execute(&mut *connection)
@ -833,49 +963,29 @@ impl SqliteStore {
let user_row_id = row.0; let user_row_id = row.0;
for (key_id, key) in user.master_key() { SqliteStore::save_cross_signing_key(
query( &mut connection,
"INSERT OR IGNORE INTO user_keys ( user_row_id,
user_id, key_type, key_id, key, usage CrosssigningKeyType::Master,
) VALUES (?1, ?2, ?3, ?4, ?5) user.master_key(),
", )
.await?;
SqliteStore::save_cross_signing_key(
&mut connection,
user_row_id,
CrosssigningKeyType::SelfSigning,
user.self_signing_key(),
) )
.bind(user_row_id)
.bind("master_key")
.bind(key_id.as_str())
.bind(key)
.bind(serde_json::to_string(user.master_key().usage())?)
.execute(&mut *connection)
.await?; .await?;
let row: (i64,) = query_as( if let Some(user_signing_key) = user.user_signing_key() {
"SELECT id FROM user_keys SqliteStore::save_cross_signing_key(
WHERE user_id = ? and key_id = ? and key_type = ?", &mut connection,
user_row_id,
CrosssigningKeyType::UserSigning,
user_signing_key,
) )
.bind(user_row_id)
.bind(key_id.as_str())
.bind("master_key")
.fetch_one(&mut *connection)
.await?; .await?;
let key_row_id = row.0;
for (user_id, signature_map) in user.master_key().signatures() {
for (key_id, signature) in signature_map {
query(
"INSERT OR IGNORE INTO user_key_signatures (
user_key, user_id, key_id, signature
) VALUES (?1, ?2, ?3, ?4)
",
)
.bind(key_row_id)
.bind(user_id.as_str())
.bind(key_id.as_str())
.bind(signature)
.execute(&mut *connection)
.await?;
}
}
} }
Ok(()) Ok(())
@ -1136,7 +1246,10 @@ impl std::fmt::Debug for SqliteStore {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{ use crate::{
identities::{device::test::get_device, user::test::get_own_identity}, identities::{
device::test::get_device,
user::test::{get_other_identity, get_own_identity},
},
olm::{Account, GroupSessionKey, InboundGroupSession, Session}, olm::{Account, GroupSessionKey, InboundGroupSession, Session},
}; };
use matrix_sdk_common::{ use matrix_sdk_common::{
@ -1528,38 +1641,69 @@ mod test {
#[tokio::test] #[tokio::test]
async fn user_saving() { async fn user_saving() {
let (_account, store, dir) = get_loaded_store().await; let dir = tempdir().unwrap();
let tmpdir_path = dir.path().to_str().unwrap();
let user_id = user_id!("@example:localhost");
let device_id: &DeviceId = "WSKKLTJZCL".into();
let store = SqliteStore::open(&user_id, &device_id, tmpdir_path)
.await
.expect("Can't create store");
let account = Account::new(&user_id, &device_id);
store
.save_account(account.clone())
.await
.expect("Can't save account");
let own_identity = get_own_identity(); let own_identity = get_own_identity();
store store
.save_user_identities(&[own_identity.into()]) .save_user_identities(&[own_identity.clone().into()])
.await .await
.expect("Can't save identity"); .expect("Can't save identity");
drop(store); drop(store);
// let store = SqliteStore::open(&alice_id(), &alice_device_id(), dir.path()) let store = SqliteStore::open(&user_id, &device_id, dir.path())
// .await .await
// .expect("Can't create store"); .expect("Can't create store");
// store.load_account().await.unwrap(); store.load_account().await.unwrap();
// let loaded_device = store let loaded_user = store
// .get_device(device.user_id(), device.device_id()) .get_user_identity(own_identity.user_id())
// .await .await
// .unwrap() .unwrap()
// .unwrap(); .unwrap();
// assert_eq!(device, loaded_device); assert_eq!(loaded_user.master_key(), own_identity.master_key());
assert_eq!(
loaded_user.self_signing_key(),
own_identity.self_signing_key()
);
assert_eq!(loaded_user, own_identity.into());
// for algorithm in loaded_device.algorithms() { let other_identity = get_other_identity();
// assert!(device.algorithms().contains(algorithm));
// }
// assert_eq!(device.algorithms().len(), loaded_device.algorithms().len());
// assert_eq!(device.keys(), loaded_device.keys());
// let user_devices = store.get_user_devices(device.user_id()).await.unwrap(); store
// assert_eq!(user_devices.keys().next().unwrap(), device.device_id()); .save_user_identities(&[other_identity.clone().into()])
// assert_eq!(user_devices.devices().next().unwrap(), &device); .await
.unwrap();
let loaded_user = store
.load_user(other_identity.user_id())
.await
.unwrap()
.unwrap();
assert_eq!(loaded_user.master_key(), other_identity.master_key());
assert_eq!(
loaded_user.self_signing_key(),
other_identity.self_signing_key()
);
assert_eq!(loaded_user, other_identity.into());
} }
} }