pub mod caches;
mod memorystore;
mod pickle_key;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "sqlite_cryptostore")]
pub(crate) mod sqlite;
pub use memorystore::MemoryStore;
pub use pickle_key::{EncryptedPickleKey, PickleKey};
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "sqlite_cryptostore")]
pub use sqlite::SqliteStore;
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
io::Error as IoError,
ops::Deref,
sync::Arc,
};
use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError};
use serde::{Deserialize, Serialize};
use serde_json::Error as SerdeError;
use thiserror::Error;
#[cfg_attr(feature = "docs", doc(cfg(r#sqlite_cryptostore)))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "sqlite_cryptostore")]
use sqlx::Error as SqlxError;
use matrix_sdk_common::{
async_trait,
identifiers::{
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, Error as IdentifierValidationError, RoomId,
UserId,
},
locks::Mutex,
AsyncTraitDeps,
};
use crate::{
error::SessionUnpicklingError,
identities::{Device, ReadOnlyDevice, UserDevices, UserIdentities},
olm::{
InboundGroupSession, OlmMessageHash, PrivateCrossSigningIdentity, ReadOnlyAccount, Session,
},
verification::VerificationMachine,
};
pub type Result<T> = std::result::Result<T, CryptoStoreError>;
#[derive(Debug, Clone)]
pub(crate) struct Store {
user_id: Arc<UserId>,
identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
inner: Arc<Box<dyn CryptoStore>>,
verification_machine: VerificationMachine,
}
#[derive(Debug, Default)]
#[allow(missing_docs)]
pub struct Changes {
pub account: Option<ReadOnlyAccount>,
pub private_identity: Option<PrivateCrossSigningIdentity>,
pub sessions: Vec<Session>,
pub message_hashes: Vec<OlmMessageHash>,
pub inbound_group_sessions: Vec<InboundGroupSession>,
pub identities: IdentityChanges,
pub devices: DeviceChanges,
}
#[derive(Debug, Clone, Default)]
#[allow(missing_docs)]
pub struct IdentityChanges {
pub new: Vec<UserIdentities>,
pub changed: Vec<UserIdentities>,
}
#[derive(Debug, Clone, Default)]
#[allow(missing_docs)]
pub struct DeviceChanges {
pub new: Vec<ReadOnlyDevice>,
pub changed: Vec<ReadOnlyDevice>,
pub deleted: Vec<ReadOnlyDevice>,
}
impl Store {
pub fn new(
user_id: Arc<UserId>,
identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
store: Arc<Box<dyn CryptoStore>>,
verification_machine: VerificationMachine,
) -> Self {
Self {
user_id,
identity,
inner: store,
verification_machine,
}
}
pub async fn get_readonly_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>> {
self.inner.get_device(user_id, device_id).await
}
pub async fn save_sessions(&self, sessions: &[Session]) -> Result<()> {
let changes = Changes {
sessions: sessions.to_vec(),
..Default::default()
};
self.save_changes(changes).await
}
#[cfg(test)]
pub async fn save_devices(&self, devices: &[ReadOnlyDevice]) -> Result<()> {
let changes = Changes {
devices: DeviceChanges {
changed: devices.to_vec(),
..Default::default()
},
..Default::default()
};
self.save_changes(changes).await
}
#[cfg(test)]
pub async fn save_inbound_group_sessions(
&self,
sessions: &[InboundGroupSession],
) -> Result<()> {
let changes = Changes {
inbound_group_sessions: sessions.to_vec(),
..Default::default()
};
self.save_changes(changes).await
}
pub async fn get_readonly_devices(
&self,
user_id: &UserId,
) -> Result<HashMap<DeviceIdBox, ReadOnlyDevice>> {
self.inner.get_user_devices(user_id).await
}
pub async fn get_device_from_curve_key(
&self,
user_id: &UserId,
curve_key: &str,
) -> Result<Option<Device>> {
self.get_user_devices(user_id).await.map(|d| {
d.devices().find(|d| {
d.get_key(DeviceKeyAlgorithm::Curve25519)
.map_or(false, |k| k == curve_key)
})
})
}
pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices> {
let devices = self.inner.get_user_devices(user_id).await?;
let own_identity = self
.inner
.get_user_identity(&self.user_id)
.await?
.map(|i| i.own().cloned())
.flatten();
let device_owner_identity = self.inner.get_user_identity(user_id).await.ok().flatten();
Ok(UserDevices {
inner: devices,
private_identity: self.identity.clone(),
verification_machine: self.verification_machine.clone(),
own_identity,
device_owner_identity,
})
}
pub async fn get_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
) -> Result<Option<Device>> {
let own_identity = self
.get_user_identity(&self.user_id)
.await?
.map(|i| i.own().cloned())
.flatten();
let device_owner_identity = self.get_user_identity(user_id).await?;
Ok(self
.inner
.get_device(user_id, device_id)
.await?
.map(|d| Device {
inner: d,
private_identity: self.identity.clone(),
verification_machine: self.verification_machine.clone(),
own_identity,
device_owner_identity,
}))
}
pub async fn get_object<V: for<'b> Deserialize<'b>>(&self, key: &str) -> Result<Option<V>> {
if let Some(value) = self.get_value(key).await? {
Ok(Some(serde_json::from_str(&value)?))
} else {
Ok(None)
}
}
pub async fn save_object(&self, key: &str, value: &impl Serialize) -> Result<()> {
let value = serde_json::to_string(value)?;
self.save_value(key.to_owned(), value).await
}
pub async fn delete_object(&self, key: &str) -> Result<()> {
self.inner.remove_value(key).await?;
Ok(())
}
}
impl Deref for Store {
type Target = dyn CryptoStore;
fn deref(&self) -> &Self::Target {
&**self.inner
}
}
#[derive(Error, Debug)]
pub enum CryptoStoreError {
#[error("can't save/load sessions or group sessions in the store before an account is stored")]
AccountUnset,
#[cfg(feature = "sqlite_cryptostore")]
#[error(transparent)]
DatabaseError(#[from] SqlxError),
#[error(transparent)]
Io(#[from] IoError),
#[error(transparent)]
OlmAccount(#[from] OlmAccountError),
#[error(transparent)]
OlmSession(#[from] OlmSessionError),
#[error(transparent)]
OlmGroupSession(#[from] OlmGroupSessionError),
#[error(transparent)]
SessionUnpickling(#[from] SessionUnpicklingError),
#[error("An object failed to be decrypted while unpickling")]
UnpicklingError,
#[error(transparent)]
IdentifierValidation(#[from] IdentifierValidationError),
#[error(transparent)]
Serialization(#[from] SerdeError),
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait CryptoStore: AsyncTraitDeps {
async fn load_account(&self) -> Result<Option<ReadOnlyAccount>>;
async fn save_account(&self, account: ReadOnlyAccount) -> Result<()>;
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>>;
async fn save_changes(&self, changes: Changes) -> Result<()>;
async fn get_sessions(&self, sender_key: &str) -> Result<Option<Arc<Mutex<Vec<Session>>>>>;
async fn get_inbound_group_session(
&self,
room_id: &RoomId,
sender_key: &str,
session_id: &str,
) -> Result<Option<InboundGroupSession>>;
async fn get_inbound_group_sessions(&self) -> Result<Vec<InboundGroupSession>>;
fn is_user_tracked(&self, user_id: &UserId) -> bool;
fn has_users_for_key_query(&self) -> bool;
fn users_for_key_query(&self) -> HashSet<UserId>;
async fn update_tracked_user(&self, user: &UserId, dirty: bool) -> Result<bool>;
async fn get_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>>;
async fn get_user_devices(
&self,
user_id: &UserId,
) -> Result<HashMap<DeviceIdBox, ReadOnlyDevice>>;
async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentities>>;
async fn save_value(&self, key: String, value: String) -> Result<()>;
async fn remove_value(&self, key: &str) -> Result<()>;
async fn get_value(&self, key: &str) -> Result<Option<String>>;
async fn is_message_known(&self, message_hash: &OlmMessageHash) -> Result<bool>;
}