crypto: Initial WIP user identity storing logic.

master
Damir Jelić 2020-09-07 16:57:58 +02:00
parent faaf3f7a29
commit 083cebe735
2 changed files with 261 additions and 6 deletions

View File

@ -756,6 +756,10 @@ pub(crate) mod test {
OwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into()).unwrap() OwnUserIdentity::new(master_key.into(), self_signing.into(), user_signing.into()).unwrap()
} }
pub(crate) fn get_own_identity() -> OwnUserIdentity {
own_identity(&own_key_query())
}
#[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,8 +21,9 @@ use std::{
}; };
use async_trait::async_trait; use async_trait::async_trait;
use dashmap::DashSet; use dashmap::{DashMap, DashSet};
use matrix_sdk_common::{ use matrix_sdk_common::{
api::r0::keys::{CrossSigningKey, KeyUsage},
identifiers::{ identifiers::{
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId, UserId, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, RoomId, UserId,
}, },
@ -135,8 +136,8 @@ impl SqliteStore {
passphrase: Option<Zeroizing<String>>, passphrase: Option<Zeroizing<String>>,
) -> Result<SqliteStore> { ) -> Result<SqliteStore> {
let url = SqliteStore::path_to_url(path.as_ref())?; let url = SqliteStore::path_to_url(path.as_ref())?;
let connection = SqliteConnection::connect(url.as_ref()).await?; let connection = SqliteConnection::connect(url.as_ref()).await?;
let store = SqliteStore { let store = SqliteStore {
user_id: Arc::new(user_id.to_owned()), user_id: Arc::new(user_id.to_owned()),
device_id: Arc::new(device_id.into()), device_id: Arc::new(device_id.into()),
@ -151,6 +152,7 @@ impl SqliteStore {
users_for_key_query: Arc::new(DashSet::new()), users_for_key_query: Arc::new(DashSet::new()),
}; };
store.create_tables().await?; store.create_tables().await?;
Ok(store) Ok(store)
} }
@ -310,6 +312,61 @@ impl SqliteStore {
) )
.await?; .await?;
connection
.execute(
r#"
CREATE TABLE IF NOT EXISTS users (
"id" INTEGER NOT NULL PRIMARY KEY,
"account_id" INTEGER NOT NULL,
"user_id" TEXT NOT NULL,
FOREIGN KEY ("account_id") REFERENCES "accounts" ("id")
ON DELETE CASCADE
UNIQUE(account_id,user_id)
);
CREATE INDEX IF NOT EXISTS "users_account_id" ON "users" ("account_id");
"#,
)
.await?;
connection
.execute(
r#"
CREATE TABLE IF NOT EXISTS user_keys (
"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)
);
CREATE INDEX IF NOT EXISTS "user_keys_user_id" ON "users" ("user_id");
"#,
)
.await?;
connection
.execute(
r#"
CREATE TABLE IF NOT EXISTS user_key_signatures (
"id" INTEGER NOT NULL PRIMARY KEY,
"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")
ON DELETE CASCADE
UNIQUE(user_id, key_id, user_key)
);
CREATE INDEX IF NOT EXISTS "user_keys_device_id" ON "device_keys" ("device_id");
"#,
)
.await?;
Ok(()) Ok(())
} }
@ -670,6 +727,159 @@ impl SqliteStore {
None => PicklingMode::Unencrypted, None => PicklingMode::Unencrypted,
} }
} }
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);
};
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")
.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)?;
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 = ?",
)
.bind(user_row_id)
.bind("master_key")
.fetch_all(&mut *connection)
.await?;
for row in signature_rows.drain(..) {
let user_id = if let Ok(u) = UserId::try_from(row.0) {
u
} else {
continue;
};
let key_id = row.1;
let signature = row.2;
signatures
.entry(user_id)
.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 {
user_id: user_id.to_owned(),
usage,
keys,
signatures,
};
Ok(None)
}
async fn save_user_helper(&self, user: &UserIdentities) -> Result<()> {
let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?;
let mut connection = self.connection.lock().await;
query(
"INSERT OR IGNORE INTO users (
account_id, user_id
) VALUES (?1, ?2)
",
)
.bind(account_id)
.bind(user.user_id().as_str())
.execute(&mut *connection)
.await?;
let row: (i64,) = query_as(
"SELECT id FROM users
WHERE account_id = ? and user_id = ?",
)
.bind(account_id)
.bind(user.user_id().as_str())
.fetch_one(&mut *connection)
.await?;
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)
",
)
.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 = ?",
)
.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(())
}
} }
#[async_trait] #[async_trait]
@ -899,11 +1109,15 @@ impl CryptoStore for SqliteStore {
Ok(self.devices.user_devices(user_id)) Ok(self.devices.user_devices(user_id))
} }
async fn get_user_identity(&self, _user_id: &UserId) -> Result<Option<UserIdentities>> { async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>> {
Ok(None) self.load_user(user_id).await
}
async fn save_user_identities(&self, users: &[UserIdentities]) -> Result<()> {
for user in users {
self.save_user_helper(user).await?;
} }
async fn save_user_identities(&self, _users: &[UserIdentities]) -> Result<()> {
Ok(()) Ok(())
} }
} }
@ -922,7 +1136,7 @@ impl std::fmt::Debug for SqliteStore {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{ use crate::{
identities::device::test::get_device, identities::{device::test::get_device, user::test::get_own_identity},
olm::{Account, GroupSessionKey, InboundGroupSession, Session}, olm::{Account, GroupSessionKey, InboundGroupSession, Session},
}; };
use matrix_sdk_common::{ use matrix_sdk_common::{
@ -1311,4 +1525,41 @@ mod test {
assert!(loaded_device.is_none()); assert!(loaded_device.is_none());
} }
#[tokio::test]
async fn user_saving() {
let (_account, store, dir) = get_loaded_store().await;
let own_identity = get_own_identity();
store
.save_user_identities(&[own_identity.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");
// store.load_account().await.unwrap();
// let loaded_device = store
// .get_device(device.user_id(), device.device_id())
// .await
// .unwrap()
// .unwrap();
// assert_eq!(device, loaded_device);
// 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 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);
}
} }