crypto: Add all the missing docs and deny missing docs from now on.
parent
5dc0842f49
commit
addb455d16
|
@ -19,36 +19,59 @@ use thiserror::Error;
|
||||||
|
|
||||||
use super::store::CryptoStoreError;
|
use super::store::CryptoStoreError;
|
||||||
|
|
||||||
pub type OlmResult<T> = std::result::Result<T, OlmError>;
|
pub type OlmResult<T> = Result<T, OlmError>;
|
||||||
pub type MegolmResult<T> = std::result::Result<T, MegolmError>;
|
pub type MegolmResult<T> = Result<T, MegolmError>;
|
||||||
pub type VerificationResult<T> = std::result::Result<T, SignatureError>;
|
|
||||||
|
|
||||||
|
/// Error representing a failure during a device to device cryptographic
|
||||||
|
/// operation.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum OlmError {
|
pub enum OlmError {
|
||||||
|
/// The event that should have been decrypted is malformed.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
EventError(#[from] EventError),
|
EventError(#[from] EventError),
|
||||||
|
|
||||||
|
/// The received decrypted event couldn't be deserialized.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
JsonError(#[from] SerdeError),
|
JsonError(#[from] SerdeError),
|
||||||
|
|
||||||
|
/// The underlying Olm session operation returned an error.
|
||||||
#[error("can't finish Olm Session operation {0}")]
|
#[error("can't finish Olm Session operation {0}")]
|
||||||
OlmSession(#[from] OlmSessionError),
|
OlmSession(#[from] OlmSessionError),
|
||||||
|
|
||||||
|
/// The underlying group session operation returned an error.
|
||||||
#[error("can't finish Olm Session operation {0}")]
|
#[error("can't finish Olm Session operation {0}")]
|
||||||
OlmGroupSession(#[from] OlmGroupSessionError),
|
OlmGroupSession(#[from] OlmGroupSessionError),
|
||||||
|
|
||||||
|
/// The storage layer returned an error.
|
||||||
#[error("failed to read or write to the crypto store {0}")]
|
#[error("failed to read or write to the crypto store {0}")]
|
||||||
Store(#[from] CryptoStoreError),
|
Store(#[from] CryptoStoreError),
|
||||||
|
|
||||||
|
/// The session with a device has become corrupted.
|
||||||
#[error("decryption failed likely because a Olm session was wedged")]
|
#[error("decryption failed likely because a Olm session was wedged")]
|
||||||
SessionWedged,
|
SessionWedged,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error representing a failure during a group encryption operation.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum MegolmError {
|
pub enum MegolmError {
|
||||||
#[error("decryption failed because the session to decrypt the message is missing")]
|
/// The event that should have been decrypted is malformed.
|
||||||
MissingSession,
|
|
||||||
#[error(transparent)]
|
|
||||||
JsonError(#[from] SerdeError),
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
EventError(#[from] EventError),
|
EventError(#[from] EventError),
|
||||||
|
|
||||||
|
/// The received decrypted event couldn't be deserialized.
|
||||||
|
#[error(transparent)]
|
||||||
|
JsonError(#[from] SerdeError),
|
||||||
|
|
||||||
|
/// Decryption failed because the session needed to decrypt the event is
|
||||||
|
/// missing.
|
||||||
|
#[error("decryption failed because the session to decrypt the message is missing")]
|
||||||
|
MissingSession,
|
||||||
|
|
||||||
|
/// The underlying group session operation returned an error.
|
||||||
#[error("can't finish Olm group session operation {0}")]
|
#[error("can't finish Olm group session operation {0}")]
|
||||||
OlmGroupSession(#[from] OlmGroupSessionError),
|
OlmGroupSession(#[from] OlmGroupSessionError),
|
||||||
|
|
||||||
|
/// The storage layer returned an error.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Store(#[from] CryptoStoreError),
|
Store(#[from] CryptoStoreError),
|
||||||
}
|
}
|
||||||
|
@ -57,30 +80,40 @@ pub enum MegolmError {
|
||||||
pub enum EventError {
|
pub enum EventError {
|
||||||
#[error("the Olm message has a unsupported type")]
|
#[error("the Olm message has a unsupported type")]
|
||||||
UnsupportedOlmType,
|
UnsupportedOlmType,
|
||||||
|
|
||||||
#[error("the Encrypted message has been encrypted with a unsupported algorithm.")]
|
#[error("the Encrypted message has been encrypted with a unsupported algorithm.")]
|
||||||
UnsupportedAlgorithm,
|
UnsupportedAlgorithm,
|
||||||
|
|
||||||
#[error("the provided JSON value isn't an object")]
|
#[error("the provided JSON value isn't an object")]
|
||||||
NotAnObject,
|
NotAnObject,
|
||||||
|
|
||||||
#[error("the Encrypted message doesn't contain a ciphertext for our device")]
|
#[error("the Encrypted message doesn't contain a ciphertext for our device")]
|
||||||
MissingCiphertext,
|
MissingCiphertext,
|
||||||
|
|
||||||
#[error("the Encrypted message is missing the signing key of the sender")]
|
#[error("the Encrypted message is missing the signing key of the sender")]
|
||||||
MissingSigningKey,
|
MissingSigningKey,
|
||||||
|
|
||||||
#[error("the Encrypted message is missing the field {0}")]
|
#[error("the Encrypted message is missing the field {0}")]
|
||||||
MissingField(String),
|
MissingField(String),
|
||||||
|
|
||||||
#[error("the sender of the plaintext doesn't match the sender of the encrypted message.")]
|
#[error("the sender of the plaintext doesn't match the sender of the encrypted message.")]
|
||||||
MissmatchedSender,
|
MissmatchedSender,
|
||||||
|
|
||||||
#[error("the keys of the message don't match the keys in our database.")]
|
#[error("the keys of the message don't match the keys in our database.")]
|
||||||
MissmatchedKeys,
|
MissmatchedKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum SignatureError {
|
pub(crate) enum SignatureError {
|
||||||
#[error("the provided JSON value isn't an object")]
|
#[error("the provided JSON value isn't an object")]
|
||||||
NotAnObject,
|
NotAnObject,
|
||||||
|
|
||||||
#[error("the provided JSON object doesn't contain a signatures field")]
|
#[error("the provided JSON object doesn't contain a signatures field")]
|
||||||
NoSignatureFound,
|
NoSignatureFound,
|
||||||
|
|
||||||
#[error("the provided JSON object can't be converted to a canonical representation")]
|
#[error("the provided JSON object can't be converted to a canonical representation")]
|
||||||
CanonicalJsonError(CjsonError),
|
CanonicalJsonError(CjsonError),
|
||||||
|
|
||||||
#[error("the signature didn't match the provided key")]
|
#[error("the signature didn't match the provided key")]
|
||||||
VerificationError,
|
VerificationError,
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,17 @@
|
||||||
//! This is the encryption part of the matrix-sdk. It contains a state machine
|
//! This is the encryption part of the matrix-sdk. It contains a state machine
|
||||||
//! that will aid in adding encryption support to a client library.
|
//! that will aid in adding encryption support to a client library.
|
||||||
|
|
||||||
|
#![deny(
|
||||||
|
missing_debug_implementations,
|
||||||
|
dead_code,
|
||||||
|
missing_docs,
|
||||||
|
trivial_casts,
|
||||||
|
trivial_numeric_casts,
|
||||||
|
unused_extern_crates,
|
||||||
|
unused_import_braces,
|
||||||
|
unused_qualifications
|
||||||
|
)]
|
||||||
|
|
||||||
mod device;
|
mod device;
|
||||||
mod error;
|
mod error;
|
||||||
mod machine;
|
mod machine;
|
||||||
|
|
|
@ -21,17 +21,15 @@ use std::result::Result as StdResult;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::error::{
|
use super::error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult, SignatureError};
|
||||||
EventError, MegolmError, MegolmResult, OlmError, OlmResult, SignatureError, VerificationResult,
|
|
||||||
};
|
|
||||||
use super::olm::{
|
use super::olm::{
|
||||||
Account, GroupSessionKey, IdentityKeys, InboundGroupSession, OlmMessage, OlmUtility,
|
Account, GroupSessionKey, IdentityKeys, InboundGroupSession, OlmMessage, OlmUtility,
|
||||||
OutboundGroupSession, Session,
|
OutboundGroupSession, Session,
|
||||||
};
|
};
|
||||||
use super::store::memorystore::MemoryStore;
|
use super::store::memorystore::MemoryStore;
|
||||||
#[cfg(feature = "sqlite-cryptostore")]
|
#[cfg(feature = "sqlite-cryptostore")]
|
||||||
use super::store::{sqlite::SqliteStore, Result as StoreError};
|
use super::store::sqlite::SqliteStore;
|
||||||
use super::{device::Device, CryptoStore};
|
use super::{device::Device, store::Result as StoreError, CryptoStore};
|
||||||
|
|
||||||
use matrix_sdk_types::api;
|
use matrix_sdk_types::api;
|
||||||
use matrix_sdk_types::events::{
|
use matrix_sdk_types::events::{
|
||||||
|
@ -60,8 +58,13 @@ use cjson;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use tracing::{debug, error, info, instrument, trace, warn};
|
use tracing::{debug, error, info, instrument, trace, warn};
|
||||||
|
|
||||||
|
/// A map from the algorithm and device id to a one-time key.
|
||||||
|
///
|
||||||
|
/// These keys need to be periodically uploaded to the server.
|
||||||
pub type OneTimeKeys = BTreeMap<AlgorithmAndDeviceId, OneTimeKey>;
|
pub type OneTimeKeys = BTreeMap<AlgorithmAndDeviceId, OneTimeKey>;
|
||||||
|
|
||||||
|
/// State machine implementation of the Olm/Megolm encryption protocol used for
|
||||||
|
/// Matrix end to end encryption.
|
||||||
pub struct OlmMachine {
|
pub struct OlmMachine {
|
||||||
/// The unique user id that owns this account.
|
/// The unique user id that owns this account.
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
|
@ -75,7 +78,7 @@ pub struct OlmMachine {
|
||||||
/// client to upload new keys.
|
/// client to upload new keys.
|
||||||
uploaded_signed_key_count: Option<AtomicU64>,
|
uploaded_signed_key_count: Option<AtomicU64>,
|
||||||
/// Store for the encryption keys.
|
/// Store for the encryption keys.
|
||||||
/// Persists all the encrytpion keys so a client can resume the session
|
/// Persists all the encryption keys so a client can resume the session
|
||||||
/// without the need to create new keys.
|
/// without the need to create new keys.
|
||||||
store: Box<dyn CryptoStore>,
|
store: Box<dyn CryptoStore>,
|
||||||
/// Set of users that we need to query keys for. This is a subset of
|
/// Set of users that we need to query keys for. This is a subset of
|
||||||
|
@ -103,7 +106,16 @@ impl OlmMachine {
|
||||||
|
|
||||||
const MAX_TO_DEVICE_MESSAGES: usize = 20;
|
const MAX_TO_DEVICE_MESSAGES: usize = 20;
|
||||||
|
|
||||||
/// Create a new account.
|
/// Create a new memory based OlmMachine.
|
||||||
|
///
|
||||||
|
/// The created machine will keep the encryption keys only in memory and
|
||||||
|
/// once the object is dropped the keys will be lost.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `user_id` - The unique id of the user that owns this machine.
|
||||||
|
///
|
||||||
|
/// * `device_id` - The unique id of the device that owns this machine.
|
||||||
pub fn new(user_id: &UserId, device_id: &str) -> Self {
|
pub fn new(user_id: &UserId, device_id: &str) -> Self {
|
||||||
OlmMachine {
|
OlmMachine {
|
||||||
user_id: user_id.clone(),
|
user_id: user_id.clone(),
|
||||||
|
@ -116,17 +128,28 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sqlite-cryptostore")]
|
/// Create a new OlmMachine with the given `CryptoStore`.
|
||||||
#[instrument(skip(path, passphrase))]
|
///
|
||||||
pub async fn new_with_sqlite_store<P: AsRef<Path>>(
|
/// The created machine will keep the encryption keys only in memory and
|
||||||
|
/// once the object is dropped the keys will be lost.
|
||||||
|
///
|
||||||
|
/// If the store already contains encryption keys for the given user/device
|
||||||
|
/// pair those will be re-used. Otherwise new ones will be created and
|
||||||
|
/// stored.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `user_id` - The unique id of the user that owns this machine.
|
||||||
|
///
|
||||||
|
/// * `device_id` - The unique id of the device that owns this machine.
|
||||||
|
///
|
||||||
|
/// * `store` - A `Cryptostore` implementation that will be used to store
|
||||||
|
/// the encryption keys.
|
||||||
|
pub async fn new_with_store(
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
device_id: &str,
|
device_id: &str,
|
||||||
path: P,
|
mut store: impl CryptoStore + 'static,
|
||||||
passphrase: String,
|
|
||||||
) -> StoreError<Self> {
|
) -> StoreError<Self> {
|
||||||
let mut store =
|
|
||||||
SqliteStore::open_with_passphrase(&user_id, device_id, path, passphrase).await?;
|
|
||||||
|
|
||||||
let account = match store.load_account().await? {
|
let account = match store.load_account().await? {
|
||||||
Some(a) => {
|
Some(a) => {
|
||||||
debug!("Restored account");
|
debug!("Restored account");
|
||||||
|
@ -149,6 +172,29 @@ impl OlmMachine {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite-cryptostore")]
|
||||||
|
#[instrument(skip(path, passphrase))]
|
||||||
|
/// Create a new machine with the default crypto store.
|
||||||
|
///
|
||||||
|
/// The default store uses a SQLite database to store the encryption keys.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `user_id` - The unique id of the user that owns this machine.
|
||||||
|
///
|
||||||
|
/// * `device_id` - The unique id of the device that owns this machine.
|
||||||
|
pub async fn new_with_default_store<P: AsRef<Path>>(
|
||||||
|
user_id: &UserId,
|
||||||
|
device_id: &str,
|
||||||
|
path: P,
|
||||||
|
passphrase: String,
|
||||||
|
) -> StoreError<Self> {
|
||||||
|
let store =
|
||||||
|
SqliteStore::open_with_passphrase(&user_id, device_id, path, passphrase).await?;
|
||||||
|
|
||||||
|
OlmMachine::new_with_store(user_id, device_id, store).await
|
||||||
|
}
|
||||||
|
|
||||||
/// The unique user id that owns this identity.
|
/// The unique user id that owns this identity.
|
||||||
pub fn user_id(&self) -> &UserId {
|
pub fn user_id(&self) -> &UserId {
|
||||||
&self.user_id
|
&self.user_id
|
||||||
|
@ -182,6 +228,7 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the count of one-time keys that are currently on the server.
|
||||||
fn update_key_count(&mut self, count: u64) {
|
fn update_key_count(&mut self, count: u64) {
|
||||||
match &self.uploaded_signed_key_count {
|
match &self.uploaded_signed_key_count {
|
||||||
Some(c) => c.store(count, Ordering::Relaxed),
|
Some(c) => c.store(count, Ordering::Relaxed),
|
||||||
|
@ -225,6 +272,25 @@ impl OlmMachine {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the user/device pairs for which no Olm session exists.
|
||||||
|
///
|
||||||
|
/// Returns a map from the user id, to a map from the device id to a key
|
||||||
|
/// algorithm.
|
||||||
|
///
|
||||||
|
/// This can be used to make a key claiming request to the server.
|
||||||
|
///
|
||||||
|
/// Sessions need to be established between devices so group sessions for a
|
||||||
|
/// room can be shared with them.
|
||||||
|
///
|
||||||
|
/// This should be called every time a group session needs to be shared.
|
||||||
|
///
|
||||||
|
/// The response of a successful key claiming requests needs to be passed to
|
||||||
|
/// the `OlmMachine` with the `receive_keys_claim_response()`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// `users` - The list of users that we should check if we lack a session
|
||||||
|
/// with one of their devices.
|
||||||
pub async fn get_missing_sessions(
|
pub async fn get_missing_sessions(
|
||||||
&mut self,
|
&mut self,
|
||||||
users: impl Iterator<Item = &UserId>,
|
users: impl Iterator<Item = &UserId>,
|
||||||
|
@ -251,11 +317,11 @@ impl OlmMachine {
|
||||||
|
|
||||||
if is_missing {
|
if is_missing {
|
||||||
if !missing.contains_key(user_id) {
|
if !missing.contains_key(user_id) {
|
||||||
missing.insert(user_id.clone(), BTreeMap::new());
|
let _ = missing.insert(user_id.clone(), BTreeMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_map = missing.get_mut(user_id).unwrap();
|
let user_map = missing.get_mut(user_id).unwrap();
|
||||||
user_map.insert(
|
let _ = user_map.insert(
|
||||||
device.device_id().to_owned(),
|
device.device_id().to_owned(),
|
||||||
KeyAlgorithm::SignedCurve25519,
|
KeyAlgorithm::SignedCurve25519,
|
||||||
);
|
);
|
||||||
|
@ -266,6 +332,12 @@ impl OlmMachine {
|
||||||
Ok(missing)
|
Ok(missing)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receive a successful key claim response and create new Olm sessions with
|
||||||
|
/// the claimed keys.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `response` - The response containing the claimed one-time keys.
|
||||||
pub async fn receive_keys_claim_response(
|
pub async fn receive_keys_claim_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
response: &keys::claim_keys::Response,
|
response: &keys::claim_keys::Response,
|
||||||
|
@ -620,7 +692,7 @@ impl OlmMachine {
|
||||||
device_id: &str,
|
device_id: &str,
|
||||||
user_key: &str,
|
user_key: &str,
|
||||||
json: &mut Value,
|
json: &mut Value,
|
||||||
) -> VerificationResult<()> {
|
) -> Result<(), SignatureError> {
|
||||||
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
||||||
let unsigned = json_object.remove("unsigned");
|
let unsigned = json_object.remove("unsigned");
|
||||||
let signatures = json_object.remove("signatures");
|
let signatures = json_object.remove("signatures");
|
||||||
|
@ -685,7 +757,11 @@ impl OlmMachine {
|
||||||
Ok((device_keys, one_time_keys))
|
Ok((device_keys, one_time_keys))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn try_decrypt_olm_event(
|
/// Try to decrypt an Olm message.
|
||||||
|
///
|
||||||
|
/// This try to decrypt an Olm message using all the sessions we share
|
||||||
|
/// have with the given sender.
|
||||||
|
async fn try_decrypt_olm_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender: &UserId,
|
sender: &UserId,
|
||||||
sender_key: &str,
|
sender_key: &str,
|
||||||
|
@ -755,10 +831,10 @@ impl OlmMachine {
|
||||||
) -> OlmResult<(EventJson<ToDeviceEvent>, String)> {
|
) -> OlmResult<(EventJson<ToDeviceEvent>, String)> {
|
||||||
// First try to decrypt using an existing session.
|
// First try to decrypt using an existing session.
|
||||||
let plaintext = if let Some(p) = self
|
let plaintext = if let Some(p) = self
|
||||||
.try_decrypt_olm_event(sender, sender_key, &message)
|
.try_decrypt_olm_message(sender, sender_key, &message)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
// Decryption succeeded, destructure the plaintext out of the
|
// Decryption succeeded, de-structure the plaintext out of the
|
||||||
// Option.
|
// Option.
|
||||||
p
|
p
|
||||||
} else {
|
} else {
|
||||||
|
@ -815,6 +891,8 @@ impl OlmMachine {
|
||||||
Ok(self.parse_decrypted_to_device_event(sender, &plaintext)?)
|
Ok(self.parse_decrypted_to_device_event(sender, &plaintext)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a decrypted Olm message, check that the plaintext and encrypted
|
||||||
|
/// senders match and that the message was meant for us.
|
||||||
fn parse_decrypted_to_device_event(
|
fn parse_decrypted_to_device_event(
|
||||||
&self,
|
&self,
|
||||||
sender: &UserId,
|
sender: &UserId,
|
||||||
|
@ -913,7 +991,7 @@ impl OlmMachine {
|
||||||
|
|
||||||
debug!("Decrypted a to-device event {:?}", decrypted_event);
|
debug!("Decrypted a to-device event {:?}", decrypted_event);
|
||||||
|
|
||||||
// Handle the decrypted event, e.g. fetch out megolm sessions out of
|
// Handle the decrypted event, e.g. fetch out Megolm sessions out of
|
||||||
// the event.
|
// the event.
|
||||||
self.handle_decrypted_to_device_event(
|
self.handle_decrypted_to_device_event(
|
||||||
&content.sender_key,
|
&content.sender_key,
|
||||||
|
@ -929,6 +1007,7 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a group session from a room key and add it to our crypto store.
|
||||||
async fn add_room_key(
|
async fn add_room_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender_key: &str,
|
sender_key: &str,
|
||||||
|
@ -945,7 +1024,7 @@ impl OlmMachine {
|
||||||
&event.content.room_id,
|
&event.content.room_id,
|
||||||
session_key,
|
session_key,
|
||||||
)?;
|
)?;
|
||||||
self.store.save_inbound_group_session(session).await?;
|
let _ = self.store.save_inbound_group_session(session).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -958,6 +1037,10 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new outbound group session.
|
||||||
|
///
|
||||||
|
/// This also creates a matching inbound group session and saves that one in
|
||||||
|
/// the store.
|
||||||
async fn create_outbound_group_session(&mut self, room_id: &RoomId) -> OlmResult<()> {
|
async fn create_outbound_group_session(&mut self, room_id: &RoomId) -> OlmResult<()> {
|
||||||
let session = OutboundGroupSession::new(room_id);
|
let session = OutboundGroupSession::new(room_id);
|
||||||
let identity_keys = self.account.identity_keys();
|
let identity_keys = self.account.identity_keys();
|
||||||
|
@ -971,15 +1054,38 @@ impl OlmMachine {
|
||||||
&room_id,
|
&room_id,
|
||||||
session.session_key().await,
|
session.session_key().await,
|
||||||
)?;
|
)?;
|
||||||
self.store
|
let _ = self
|
||||||
|
.store
|
||||||
.save_inbound_group_session(inbound_session)
|
.save_inbound_group_session(inbound_session)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.outbound_group_sessions
|
let _ = self
|
||||||
|
.outbound_group_sessions
|
||||||
.insert(room_id.to_owned(), session);
|
.insert(room_id.to_owned(), session);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encrypt a room message for the given room.
|
||||||
|
///
|
||||||
|
/// Beware that a group session needs to be shared before this method can be
|
||||||
|
/// called using the `share_group_session()` method.
|
||||||
|
///
|
||||||
|
/// Since group sessions can expire or become invalid if the room membership
|
||||||
|
/// changes client authors should check with the
|
||||||
|
/// `should_share_group_session()` method if a new group session needs to
|
||||||
|
/// be shared.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `room_id` - The id of the room for which the message should be
|
||||||
|
/// encrypted.
|
||||||
|
///
|
||||||
|
/// * `content` - The plaintext content of the message that should be
|
||||||
|
/// encrypted.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if a group session for the given room wasn't shared beforehand.
|
||||||
pub async fn encrypt(
|
pub async fn encrypt(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
|
@ -1023,6 +1129,7 @@ impl OlmMachine {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encrypt some JSON content using the given Olm session.
|
||||||
async fn olm_encrypt(
|
async fn olm_encrypt(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut session: Session,
|
mut session: Session,
|
||||||
|
@ -1155,7 +1262,7 @@ impl OlmMachine {
|
||||||
user_map.push((session.clone(), device.clone()));
|
user_map.push((session.clone(), device.clone()));
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"Trying to encrypt a megolm session for user
|
"Trying to encrypt a Megolm session for user
|
||||||
{} on device {}, but no Olm session is found",
|
{} on device {}, but no Olm session is found",
|
||||||
user_id,
|
user_id,
|
||||||
device.device_id()
|
device.device_id()
|
||||||
|
@ -1211,6 +1318,15 @@ impl OlmMachine {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receive and properly handle a decrypted to-device event.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `sender_key` - The sender (curve25519) key of the event sender.
|
||||||
|
///
|
||||||
|
/// * `signing_key` - The signing (ed25519) key of the event sender.
|
||||||
|
///
|
||||||
|
/// * `event` - The decrypted to-device event.
|
||||||
async fn handle_decrypted_to_device_event(
|
async fn handle_decrypted_to_device_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender_key: &str,
|
sender_key: &str,
|
||||||
|
@ -1246,14 +1362,17 @@ impl OlmMachine {
|
||||||
// TODO handle to-device verification events here.
|
// TODO handle to-device verification events here.
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(response))]
|
|
||||||
/// Handle a sync response and update the internal state of the Olm machine.
|
/// Handle a sync response and update the internal state of the Olm machine.
|
||||||
///
|
///
|
||||||
/// This will decrypt to-device events but will not touch room messages.
|
/// This will decrypt to-device events but will not touch events in the room
|
||||||
|
/// timeline.
|
||||||
|
///
|
||||||
|
/// To decrypt an event from the room timeline call `decrypt_room_event()`.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `response` - The sync latest sync response.
|
/// * `response` - The sync latest sync response.
|
||||||
|
#[instrument(skip(response))]
|
||||||
pub async fn receive_sync_response(&mut self, response: &mut SyncResponse) {
|
pub async fn receive_sync_response(&mut self, response: &mut SyncResponse) {
|
||||||
let one_time_key_count = response
|
let one_time_key_count = response
|
||||||
.device_one_time_keys_count
|
.device_one_time_keys_count
|
||||||
|
@ -1304,6 +1423,11 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decrypt an event from a room timeline.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `event` - The event that should be decrypted.
|
||||||
pub async fn decrypt_room_event(
|
pub async fn decrypt_room_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &EncryptedEvent,
|
event: &EncryptedEvent,
|
||||||
|
@ -1319,7 +1443,7 @@ impl OlmMachine {
|
||||||
.store
|
.store
|
||||||
.get_inbound_group_session(&room_id, &content.sender_key, &content.session_id)
|
.get_inbound_group_session(&room_id, &content.sender_key, &content.session_id)
|
||||||
.await?;
|
.await?;
|
||||||
// TODO check if the olm session is wedged and re-request the key.
|
// TODO check if the Olm session is wedged and re-request the key.
|
||||||
let session = session.ok_or(MegolmError::MissingSession)?;
|
let session = session.ok_or(MegolmError::MissingSession)?;
|
||||||
|
|
||||||
let (plaintext, _) = session.decrypt(content.ciphertext.clone()).await?;
|
let (plaintext, _) = session.decrypt(content.ciphertext.clone()).await?;
|
||||||
|
@ -1349,7 +1473,7 @@ impl OlmMachine {
|
||||||
);
|
);
|
||||||
|
|
||||||
let decrypted_event = serde_json::from_value::<EventJson<RoomEvent>>(decrypted_value)?;
|
let decrypted_event = serde_json::from_value::<EventJson<RoomEvent>>(decrypted_value)?;
|
||||||
trace!("Successfully decrypted megolm event {:?}", decrypted_event);
|
trace!("Successfully decrypted Megolm event {:?}", decrypted_event);
|
||||||
// TODO set the encryption info on the event (is it verified, was it
|
// TODO set the encryption info on the event (is it verified, was it
|
||||||
// decrypted, sender key...)
|
// decrypted, sender key...)
|
||||||
|
|
||||||
|
@ -1358,6 +1482,11 @@ impl OlmMachine {
|
||||||
|
|
||||||
/// Update the tracked users.
|
/// Update the tracked users.
|
||||||
///
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `users` - An iterator over user ids that should be marked for
|
||||||
|
/// tracking.
|
||||||
|
///
|
||||||
/// This will only not already seen users for a key query and user tracking.
|
/// This will only not already seen users for a key query and user tracking.
|
||||||
/// If the user is already known to the Olm machine it will not be
|
/// If the user is already known to the Olm machine it will not be
|
||||||
/// considered for a key query.
|
/// considered for a key query.
|
||||||
|
@ -1384,12 +1513,14 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should a key query be done.
|
/// Should the client perform a key query request.
|
||||||
pub fn should_query_keys(&self) -> bool {
|
pub fn should_query_keys(&self) -> bool {
|
||||||
!self.users_for_key_query.is_empty()
|
!self.users_for_key_query.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the set of users that we need to query keys for.
|
/// Get the set of users that we need to query keys for.
|
||||||
|
///
|
||||||
|
/// Returns a hash set of users that need to be queried for keys.
|
||||||
pub fn users_for_key_query(&self) -> HashSet<UserId> {
|
pub fn users_for_key_query(&self) -> HashSet<UserId> {
|
||||||
self.users_for_key_query.clone()
|
self.users_for_key_query.clone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,6 +133,7 @@ pub struct DeviceStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A read only view over all devices belonging to a user.
|
/// A read only view over all devices belonging to a user.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct UserDevices {
|
pub struct UserDevices {
|
||||||
entries: ReadOnlyView<DeviceId, Device>,
|
entries: ReadOnlyView<DeviceId, Device>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,10 @@ pub use olm_rs::{
|
||||||
use matrix_sdk_types::api::r0::keys::SignedKey;
|
use matrix_sdk_types::api::r0::keys::SignedKey;
|
||||||
use matrix_sdk_types::identifiers::RoomId;
|
use matrix_sdk_types::identifiers::RoomId;
|
||||||
|
|
||||||
/// The Olm account.
|
/// Account holding identity keys for which sessions can be created.
|
||||||
|
///
|
||||||
/// An account is the central identity for encrypted communication between two
|
/// An account is the central identity for encrypted communication between two
|
||||||
/// devices. It holds the two identity key pairs for a device.
|
/// devices.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
inner: Arc<Mutex<OlmAccount>>,
|
inner: Arc<Mutex<OlmAccount>>,
|
||||||
|
@ -66,7 +67,7 @@ impl Default for Account {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
/// Create a new account.
|
/// Create a fresh new account, this will generate the identity key-pair.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let account = OlmAccount::new();
|
let account = OlmAccount::new();
|
||||||
let identity_keys = account.parsed_identity_keys();
|
let identity_keys = account.parsed_identity_keys();
|
||||||
|
@ -242,10 +243,8 @@ impl PartialEq for Account {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Olm Session.
|
/// Cryptographic session that enables secure communication between two
|
||||||
///
|
/// `Account`s
|
||||||
/// Sessions are used to exchange encrypted messages between two
|
|
||||||
/// accounts/devices.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
inner: Arc<Mutex<OlmSession>>,
|
inner: Arc<Mutex<OlmSession>>,
|
||||||
|
@ -378,7 +377,7 @@ impl PartialEq for Session {
|
||||||
|
|
||||||
/// The private session key of a group session.
|
/// The private session key of a group session.
|
||||||
/// Can be used to create a new inbound group session.
|
/// Can be used to create a new inbound group session.
|
||||||
#[derive(Clone, Serialize, Zeroize)]
|
#[derive(Clone, Debug, Serialize, Zeroize)]
|
||||||
#[zeroize(drop)]
|
#[zeroize(drop)]
|
||||||
pub struct GroupSessionKey(pub String);
|
pub struct GroupSessionKey(pub String);
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ impl CryptoStore for MemoryStore {
|
||||||
|
|
||||||
async fn save_sessions(&mut self, sessions: &[Session]) -> Result<()> {
|
async fn save_sessions(&mut self, sessions: &[Session]) -> Result<()> {
|
||||||
for session in sessions {
|
for session in sessions {
|
||||||
self.sessions.add(session.clone()).await;
|
let _ = self.sessions.add(session.clone()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -93,7 +93,7 @@ impl CryptoStore for MemoryStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_device(&self, device: Device) -> Result<()> {
|
async fn delete_device(&self, device: Device) -> Result<()> {
|
||||||
self.devices.remove(device.user_id(), device.device_id());
|
let _ = self.devices.remove(device.user_id(), device.device_id());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ impl CryptoStore for MemoryStore {
|
||||||
|
|
||||||
async fn save_devices(&self, devices: &[Device]) -> Result<()> {
|
async fn save_devices(&self, devices: &[Device]) -> Result<()> {
|
||||||
for device in devices {
|
for device in devices {
|
||||||
self.devices.add(device.clone());
|
let _ = self.devices.add(device.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -157,7 +157,7 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut store = MemoryStore::new();
|
let mut store = MemoryStore::new();
|
||||||
store
|
let _ = store
|
||||||
.save_inbound_group_session(inbound.clone())
|
.save_inbound_group_session(inbound.clone())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -212,6 +212,6 @@ mod test {
|
||||||
|
|
||||||
let tracked_users = store.tracked_users();
|
let tracked_users = store.tracked_users();
|
||||||
|
|
||||||
tracked_users.contains(device.user_id());
|
let _ = tracked_users.contains(device.user_id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,33 +37,54 @@ pub mod sqlite;
|
||||||
use sqlx::Error as SqlxError;
|
use sqlx::Error as SqlxError;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
/// The crypto store's error type.
|
||||||
pub enum CryptoStoreError {
|
pub enum CryptoStoreError {
|
||||||
#[error("can't read or write from the store")]
|
/// The account that owns the sessions, group sessions, and devices wasn't
|
||||||
Io(#[from] IoError),
|
/// found.
|
||||||
#[error("can't finish Olm Account operation {0}")]
|
#[error("can't save/load sessions or group sessions in the store before an account is stored")]
|
||||||
OlmAccount(#[from] OlmAccountError),
|
|
||||||
#[error("can't finish Olm Session operation {0}")]
|
|
||||||
OlmSession(#[from] OlmSessionError),
|
|
||||||
#[error("can't finish Olm GruoupSession operation {0}")]
|
|
||||||
OlmGroupSession(#[from] OlmGroupSessionError),
|
|
||||||
#[error("URL can't be parsed")]
|
|
||||||
UrlParse(#[from] ParseError),
|
|
||||||
#[error("error serializing data for the database")]
|
|
||||||
Serialization(#[from] SerdeError),
|
|
||||||
#[error("can't load session timestamps")]
|
|
||||||
SessionTimestampError,
|
|
||||||
#[error("can't save/load sessions or group sessions in the store before a account is stored")]
|
|
||||||
AccountUnset,
|
AccountUnset,
|
||||||
|
|
||||||
|
/// SQL error occurred.
|
||||||
// TODO flatten the SqlxError to make it easier for other store
|
// TODO flatten the SqlxError to make it easier for other store
|
||||||
// implementations.
|
// implementations.
|
||||||
#[cfg(feature = "sqlite-cryptostore")]
|
#[cfg(feature = "sqlite-cryptostore")]
|
||||||
#[error("database error")]
|
#[error(transparent)]
|
||||||
DatabaseError(#[from] SqlxError),
|
DatabaseError(#[from] SqlxError),
|
||||||
|
|
||||||
|
/// An IO error occurred.
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] IoError),
|
||||||
|
|
||||||
|
/// The underlying Olm Account operation returned an error.
|
||||||
|
#[error(transparent)]
|
||||||
|
OlmAccount(#[from] OlmAccountError),
|
||||||
|
|
||||||
|
/// The underlying Olm session operation returned an error.
|
||||||
|
#[error(transparent)]
|
||||||
|
OlmSession(#[from] OlmSessionError),
|
||||||
|
|
||||||
|
/// The underlying Olm group session operation returned an error.
|
||||||
|
#[error(transparent)]
|
||||||
|
OlmGroupSession(#[from] OlmGroupSessionError),
|
||||||
|
|
||||||
|
/// A session time-stamp couldn't be loaded.
|
||||||
|
#[error("can't load session timestamps")]
|
||||||
|
SessionTimestampError,
|
||||||
|
|
||||||
|
/// The store failed to (de)serialize a data type.
|
||||||
|
#[error(transparent)]
|
||||||
|
Serialization(#[from] SerdeError),
|
||||||
|
|
||||||
|
/// An error occurred while parsing an URL.
|
||||||
|
#[error(transparent)]
|
||||||
|
UrlParse(#[from] ParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, CryptoStoreError>;
|
pub type Result<T> = std::result::Result<T, CryptoStoreError>;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
/// Trait abstracting a store that the `OlmMachine` uses to store cryptographic
|
||||||
|
/// keys.
|
||||||
pub trait CryptoStore: Debug + Send + Sync {
|
pub trait CryptoStore: Debug + Send + Sync {
|
||||||
/// Load an account that was previously stored.
|
/// Load an account that was previously stored.
|
||||||
async fn load_account(&mut self) -> Result<Option<Account>>;
|
async fn load_account(&mut self) -> Result<Option<Account>>;
|
||||||
|
|
|
@ -35,6 +35,7 @@ use matrix_sdk_types::api::r0::keys::KeyAlgorithm;
|
||||||
use matrix_sdk_types::events::Algorithm;
|
use matrix_sdk_types::events::Algorithm;
|
||||||
use matrix_sdk_types::identifiers::{DeviceId, RoomId, UserId};
|
use matrix_sdk_types::identifiers::{DeviceId, RoomId, UserId};
|
||||||
|
|
||||||
|
/// SQLite based implementation of a `CryptoStore`.
|
||||||
pub struct SqliteStore {
|
pub struct SqliteStore {
|
||||||
user_id: Arc<String>,
|
user_id: Arc<String>,
|
||||||
device_id: Arc<String>,
|
device_id: Arc<String>,
|
||||||
|
@ -53,6 +54,17 @@ pub struct SqliteStore {
|
||||||
static DATABASE_NAME: &str = "matrix-sdk-crypto.db";
|
static DATABASE_NAME: &str = "matrix-sdk-crypto.db";
|
||||||
|
|
||||||
impl SqliteStore {
|
impl SqliteStore {
|
||||||
|
/// Open a new `SqliteStore`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `user_id` - The unique id of the user for which the store should be
|
||||||
|
/// opened.
|
||||||
|
///
|
||||||
|
/// * `device_id` - The unique id of the device for which the store should
|
||||||
|
/// be opened.
|
||||||
|
///
|
||||||
|
/// * `path` - The path where the database file should reside in.
|
||||||
pub async fn open<P: AsRef<Path>>(
|
pub async fn open<P: AsRef<Path>>(
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
device_id: &str,
|
device_id: &str,
|
||||||
|
@ -61,6 +73,20 @@ impl SqliteStore {
|
||||||
SqliteStore::open_helper(user_id, device_id, path, None).await
|
SqliteStore::open_helper(user_id, device_id, path, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open a new `SqliteStore`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `user_id` - The unique id of the user for which the store should be
|
||||||
|
/// opened.
|
||||||
|
///
|
||||||
|
/// * `device_id` - The unique id of the device for which the store should
|
||||||
|
/// be opened.
|
||||||
|
///
|
||||||
|
/// * `path` - The path where the database file should reside in.
|
||||||
|
///
|
||||||
|
/// * `passphrase` - The passphrase that should be used to securely store
|
||||||
|
/// the encryption keys.
|
||||||
pub async fn open_with_passphrase<P: AsRef<Path>>(
|
pub async fn open_with_passphrase<P: AsRef<Path>>(
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
device_id: &str,
|
device_id: &str,
|
||||||
|
@ -321,7 +347,8 @@ impl SqliteStore {
|
||||||
|
|
||||||
for row in rows {
|
for row in rows {
|
||||||
let device_row_id = row.0;
|
let device_row_id = row.0;
|
||||||
let user_id = if let Ok(u) = UserId::try_from(&row.1 as &str) {
|
let user_id: &str = &row.1;
|
||||||
|
let user_id = if let Ok(u) = UserId::try_from(user_id) {
|
||||||
u
|
u
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -339,7 +366,10 @@ impl SqliteStore {
|
||||||
|
|
||||||
let algorithms = algorithm_rows
|
let algorithms = algorithm_rows
|
||||||
.iter()
|
.iter()
|
||||||
.map(|row| Algorithm::from(&row.0 as &str))
|
.map(|row| {
|
||||||
|
let algorithm: &str = &row.0;
|
||||||
|
Algorithm::from(algorithm)
|
||||||
|
})
|
||||||
.collect::<Vec<Algorithm>>();
|
.collect::<Vec<Algorithm>>();
|
||||||
|
|
||||||
let key_rows: Vec<(String, String)> =
|
let key_rows: Vec<(String, String)> =
|
||||||
|
@ -351,7 +381,8 @@ impl SqliteStore {
|
||||||
let mut keys = BTreeMap::new();
|
let mut keys = BTreeMap::new();
|
||||||
|
|
||||||
for row in key_rows {
|
for row in key_rows {
|
||||||
let algorithm = if let Ok(a) = KeyAlgorithm::try_from(&row.0 as &str) {
|
let algorithm: &str = &row.0;
|
||||||
|
let algorithm = if let Ok(a) = KeyAlgorithm::try_from(algorithm) {
|
||||||
a
|
a
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
|
Loading…
Reference in New Issue