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"
optional = true
default-features = false
features = ["runtime-tokio", "sqlite"]
features = ["runtime-tokio", "sqlite", "macros"]
[dev-dependencies]
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 {
fn from(key: &CrossSigningKey) -> Self {
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 {
/// The unique user id of this identity.
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
/// type.
pub fn own(&self) -> Option<&OwnUserIdentity> {
@ -383,6 +454,11 @@ impl UserIdentity {
&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.
///
/// # Arguments
@ -483,6 +559,16 @@ impl OwnUserIdentity {
&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.
///
/// # Arguments
@ -760,6 +846,16 @@ pub(crate) mod test {
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]
fn own_identity_create() {
let user_id = user_id!("@example:localhost");

View File

@ -21,7 +21,7 @@ use std::{
};
use async_trait::async_trait;
use dashmap::{DashMap, DashSet};
use dashmap::DashSet;
use matrix_sdk_common::{
api::r0::keys::{CrossSigningKey, KeyUsage},
identifiers::{
@ -39,7 +39,7 @@ use super::{
CryptoStore, CryptoStoreError, Result,
};
use crate::{
identities::{LocalTrust, ReadOnlyDevice, UserIdentities},
identities::{LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserIdentities, UserIdentity},
olm::{
Account, AccountPickle, IdentityKeys, InboundGroupSession, InboundGroupSessionPickle,
PickledAccount, PickledInboundGroupSession, PickledSession, PicklingMode, Session,
@ -72,6 +72,24 @@ struct AccountInfo {
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";
impl SqliteStore {
@ -329,6 +347,23 @@ impl SqliteStore {
)
.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
.execute(
r#"
@ -336,14 +371,12 @@ impl SqliteStore {
"id" INTEGER NOT NULL PRIMARY KEY,
"key" TEXT NOT NULL,
"key_id" TEXT NOT NULL,
"key_type" TEXT NOT NULL,
"usage" TEXT NOT NULL,
"user_id" INTEGER NOT NULL,
FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE
UNIQUE(user_id, key_id, key_type)
"cross_signing_key" INTEGER NOT NULL,
FOREIGN KEY ("cross_signing_key") REFERENCES "cross_signing_keys" ("id") ON DELETE CASCADE
UNIQUE(cross_signing_key, key_id)
);
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?;
@ -356,13 +389,13 @@ impl SqliteStore {
"user_id" TEXT NOT NULL,
"key_id" INTEGER NOT NULL,
"signature" TEXT NOT NULL,
"user_key" INTEGER NOT NULL,
FOREIGN KEY ("user_key") REFERENCES "user_keys" ("id")
"cross_signing_key" INTEGER NOT NULL,
FOREIGN KEY ("cross_signing_key") REFERENCES "cross_signing_keys" ("id")
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?;
@ -728,49 +761,42 @@ impl SqliteStore {
}
}
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)
async fn load_cross_signing_key(
connection: &mut SqliteConnection,
user_id: &UserId,
user_row_id: i64,
key_type: CrosssigningKeyType,
) -> Result<CrossSigningKey> {
let row: (i64, String) =
query_as("SELECT id, usage FROM cross_signing_keys WHERE user_id =? and key_type =?")
.bind(user_row_id)
.bind(key_type)
.fetch_one(&mut *connection)
.await?;
let user_row_id = if let Some(row) = row {
row.0
} else {
return Ok(None);
};
let key_row_id = row.0;
let usage: Vec<KeyUsage> = serde_json::from_str(&row.1)?;
let key_rows: Vec<(i64, String, String, String)> = query_as(
"SELECT id, key_id, key, usage FROM user_keys WHERE user_id = ? and key_type = ?",
)
.bind(user_row_id)
.bind("master_key")
let key_rows: Vec<(String, String)> =
query_as("SELECT key_id, key FROM user_keys WHERE cross_signing_key = ?")
.bind(key_row_id)
.fetch_all(&mut *connection)
.await?;
let mut keys = BTreeMap::new();
let mut signatures = BTreeMap::new();
let mut key_usage = HashSet::new();
for row in key_rows {
let key_row_id = row.0;
let key_id = row.1;
let key = row.2;
let usage: Vec<String> = serde_json::from_str(&row.3)?;
let key_id = row.0;
let key = row.1;
keys.insert(key_id, key);
key_usage.extend(usage);
}
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("master_key")
.bind(key_row_id)
.fetch_all(&mut *connection)
.await?;
@ -789,21 +815,130 @@ impl SqliteStore {
.or_insert_with(BTreeMap::new)
.insert(key_id, signature);
}
}
let usage: Vec<KeyUsage> = key_usage
.iter()
.filter_map(|u| serde_json::from_str(u).ok())
.collect();
let key = CrossSigningKey {
Ok(CrossSigningKey {
user_id: user_id.to_owned(),
usage,
keys,
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<()> {
@ -811,12 +946,7 @@ impl SqliteStore {
let mut connection = self.connection.lock().await;
query(
"INSERT OR IGNORE INTO users (
account_id, user_id
) VALUES (?1, ?2)
",
)
query("REPLACE INTO users (account_id, user_id) VALUES (?1, ?2)")
.bind(account_id)
.bind(user.user_id().as_str())
.execute(&mut *connection)
@ -833,49 +963,29 @@ impl SqliteStore {
let user_row_id = row.0;
for (key_id, key) in user.master_key() {
query(
"INSERT OR IGNORE INTO user_keys (
user_id, key_type, key_id, key, usage
) VALUES (?1, ?2, ?3, ?4, ?5)
",
SqliteStore::save_cross_signing_key(
&mut connection,
user_row_id,
CrosssigningKeyType::Master,
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?;
let row: (i64,) = query_as(
"SELECT id FROM user_keys
WHERE user_id = ? and key_id = ? and key_type = ?",
if let Some(user_signing_key) = user.user_signing_key() {
SqliteStore::save_cross_signing_key(
&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?;
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(())
@ -1136,7 +1246,10 @@ impl std::fmt::Debug for SqliteStore {
#[cfg(test)]
mod test {
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},
};
use matrix_sdk_common::{
@ -1528,38 +1641,69 @@ mod test {
#[tokio::test]
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();
store
.save_user_identities(&[own_identity.into()])
.save_user_identities(&[own_identity.clone().into()])
.await
.expect("Can't save identity");
drop(store);
// let store = SqliteStore::open(&alice_id(), &alice_device_id(), dir.path())
// .await
// .expect("Can't create store");
let store = SqliteStore::open(&user_id, &device_id, dir.path())
.await
.expect("Can't create store");
// store.load_account().await.unwrap();
store.load_account().await.unwrap();
// let loaded_device = store
// .get_device(device.user_id(), device.device_id())
// .await
// .unwrap()
// .unwrap();
let loaded_user = store
.get_user_identity(own_identity.user_id())
.await
.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() {
// assert!(device.algorithms().contains(algorithm));
// }
// assert_eq!(device.algorithms().len(), loaded_device.algorithms().len());
// assert_eq!(device.keys(), loaded_device.keys());
let other_identity = get_other_identity();
// let user_devices = store.get_user_devices(device.user_id()).await.unwrap();
// assert_eq!(user_devices.keys().next().unwrap(), device.device_id());
// assert_eq!(user_devices.devices().next().unwrap(), &device);
store
.save_user_identities(&[other_identity.clone().into()])
.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());
}
}