From 2f7ec887ba40ea3fb61ec339eba16fe228a4de8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Apr 2020 10:41:08 +0200 Subject: [PATCH 01/13] crypto: Clean up the imports. --- src/crypto/machine.rs | 37 ++++++++++++++++++------------------- src/crypto/olm.rs | 7 ++++++- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index 52c6a39c..f42b6aa8 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -21,28 +21,17 @@ use std::result::Result as StdResult; use uuid::Uuid; use super::error::{OlmError, Result, SignatureError, VerificationResult}; -use super::olm::{Account, GroupSessionKey, InboundGroupSession, OutboundGroupSession, Session}; +use super::olm::{ + Account, GroupSessionKey, InboundGroupSession, OlmMessage, OlmUtility, OutboundGroupSession, + Session, +}; use super::store::memorystore::MemoryStore; #[cfg(feature = "sqlite-cryptostore")] use super::store::sqlite::SqliteStore; use super::{device::Device, CryptoStore}; + use crate::api; - -use api::r0::keys; - -use cjson; -use olm_rs::{session::OlmMessage, utility::OlmUtility}; -use serde_json::{json, Value}; -use tracing::{debug, error, info, instrument, trace, warn}; - -use ruma_client_api::r0::client_exchange::{ - send_event_to_device::Request as ToDeviceRequest, DeviceIdOrAllDevices, -}; -use ruma_client_api::r0::keys::{ - AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey, SignedKey, -}; -use ruma_client_api::r0::sync::sync_events::IncomingResponse as SyncResponse; -use ruma_events::{ +use crate::events::{ collections::all::RoomEvent, room::encrypted::{ CiphertextInfo, EncryptedEvent, EncryptedEventContent, MegolmV1AesSha2Content, @@ -55,8 +44,18 @@ use ruma_events::{ }, Algorithm, EventResult, EventType, }; -use ruma_identifiers::RoomId; -use ruma_identifiers::{DeviceId, UserId}; +use crate::identifiers::{DeviceId, RoomId, UserId}; + +use api::r0::keys; +use api::r0::{ + client_exchange::{send_event_to_device::Request as ToDeviceRequest, DeviceIdOrAllDevices}, + keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey, SignedKey}, + sync::sync_events::IncomingResponse as SyncResponse, +}; + +use cjson; +use serde_json::{json, Value}; +use tracing::{debug, error, info, instrument, trace, warn}; pub type OneTimeKeys = HashMap; diff --git a/src/crypto/olm.rs b/src/crypto/olm.rs index 614f5bda..3d6d4ac4 100644 --- a/src/crypto/olm.rs +++ b/src/crypto/olm.rs @@ -26,9 +26,14 @@ use olm_rs::account::{IdentityKeys, OlmAccount, OneTimeKeys}; use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError}; use olm_rs::inbound_group_session::OlmInboundGroupSession; use olm_rs::outbound_group_session::OlmOutboundGroupSession; -use olm_rs::session::{OlmMessage, OlmSession, PreKeyMessage}; +use olm_rs::session::OlmSession; use olm_rs::PicklingMode; +pub use olm_rs::{ + session::{OlmMessage, PreKeyMessage}, + utility::OlmUtility, +}; + use crate::api::r0::keys::SignedKey; use crate::identifiers::RoomId; From 877b880ded86ba68a7681c716398a048dd4561b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Apr 2020 11:03:28 +0200 Subject: [PATCH 02/13] crypto: Expose the devices publicly. --- src/crypto/device.rs | 1 + src/crypto/mod.rs | 5 ++--- src/lib.rs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/crypto/device.rs b/src/crypto/device.rs index d0b2fcf1..87ad25c4 100644 --- a/src/crypto/device.rs +++ b/src/crypto/device.rs @@ -23,6 +23,7 @@ use crate::api::r0::keys::{DeviceKeys, KeyAlgorithm}; use crate::events::Algorithm; use crate::identifiers::{DeviceId, UserId}; +/// A device represents a E2EE capable client of an user. #[derive(Debug, Clone)] pub struct Device { user_id: Arc, diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 01df02bc..992ac022 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -12,15 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod error; -// TODO remove this. mod device; +mod error; mod machine; mod memory_stores; -#[allow(dead_code)] mod olm; mod store; +pub use device::{Device, TrustState}; pub use error::OlmError; pub use machine::{OlmMachine, OneTimeKeys}; pub use store::{CryptoStore, CryptoStoreError}; diff --git a/src/lib.rs b/src/lib.rs index b3c13bdb..93bfa375 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,8 @@ mod crypto; pub use async_client::{AsyncClient, AsyncClientConfig, SyncSettings}; pub use base_client::Client; +#[cfg(feature = "encryption")] +pub use crypto::{Device, TrustState}; pub use event_emitter::EventEmitter; pub use models::Room; pub use request_builder::{MessagesRequestBuilder, RoomBuilder}; From cb6e43b340e293f041edc5e37d81007219abe5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Apr 2020 12:12:47 +0200 Subject: [PATCH 03/13] crypto: Allow devices to be deleted from the crypto store. --- src/crypto/machine.rs | 5 +++-- src/crypto/store/memorystore.rs | 12 ++++++++++++ src/crypto/store/mod.rs | 7 +++++++ src/crypto/store/sqlite.rs | 4 ++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index f42b6aa8..cb2d7657 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -427,12 +427,13 @@ impl OlmMachine { for device_id in deleted_devices { if let Some(device) = stored_devices.get(device_id) { device.mark_as_deleted(); - // TODO change this to a delete device. - self.store.save_device(device).await?; + // TODO change this to a bulk deletion. + self.store.delete_device(device).await?; } } } + // TODO change this to a bulk operation. for device in &changed_devices { self.store.save_device(device.clone()).await?; } diff --git a/src/crypto/store/memorystore.rs b/src/crypto/store/memorystore.rs index 0e889786..3100a2e3 100644 --- a/src/crypto/store/memorystore.rs +++ b/src/crypto/store/memorystore.rs @@ -88,6 +88,11 @@ impl CryptoStore for MemoryStore { Ok(self.devices.get(user_id, device_id)) } + async fn delete_device(&self, device: Device) -> Result<()> { + self.devices.remove(device.user_id(), device.device_id()); + Ok(()) + } + async fn get_user_devices(&self, user_id: &UserId) -> Result { Ok(self.devices.user_devices(user_id)) } @@ -181,6 +186,13 @@ mod test { let loaded_device = user_devices.get(device.device_id()).unwrap(); assert_eq!(device, loaded_device); + + store.delete_device(device.clone()).await.unwrap(); + assert!(store + .get_device(device.user_id(), device.device_id()) + .await + .unwrap() + .is_none()); } #[tokio::test] diff --git a/src/crypto/store/mod.rs b/src/crypto/store/mod.rs index 7181aff1..cf92a1f2 100644 --- a/src/crypto/store/mod.rs +++ b/src/crypto/store/mod.rs @@ -133,6 +133,13 @@ pub trait CryptoStore: Debug + Send + Sync { /// * `device` - The device that should be stored. async fn save_device(&self, device: Device) -> Result<()>; + /// Delete the given device from the store. + /// + /// # Arguments + /// + /// * `device` - The device that should be stored. + async fn delete_device(&self, device: Device) -> Result<()>; + /// Get the device for the given user with the given device id. /// /// # Arguments diff --git a/src/crypto/store/sqlite.rs b/src/crypto/store/sqlite.rs index df7f336d..5f768e18 100644 --- a/src/crypto/store/sqlite.rs +++ b/src/crypto/store/sqlite.rs @@ -613,6 +613,10 @@ impl CryptoStore for SqliteStore { self.save_device_helper(device).await } + async fn delete_device(&self, device: Device) -> Result<()> { + todo!() + } + async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result> { Ok(self.devices.get(user_id, device_id)) } From 4576e93663a919b7d182ec1ee87902829f7c07a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Apr 2020 12:54:49 +0200 Subject: [PATCH 04/13] crypto: Add some more debug logs. --- src/crypto/machine.rs | 76 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index cb2d7657..45788819 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -655,11 +655,13 @@ impl OlmMachine { async fn try_decrypt_olm_event( &mut self, + sender: &str, sender_key: &str, message: &OlmMessage, ) -> Result> { let s = self.store.get_sessions(sender_key).await?; + /// We don't have any existing sessions, return early. let sessions = if let Some(s) = s { s } else { @@ -669,8 +671,11 @@ impl OlmMachine { for session in &mut *sessions.lock().await { let mut matches = false; + // If this is a pre-key message check if it was encrypted for our + // session, if it wasn't decryption will fail so no need to try. if let OlmMessage::PreKey(m) = &message { matches = session.matches(sender_key, m.clone()).await?; + if !matches { continue; } @@ -679,10 +684,20 @@ impl OlmMachine { let ret = session.decrypt(message.clone()).await; if let Ok(p) = ret { + // Decryption was successful, save the new ratchet state of the + // session. self.store.save_session(session.clone()).await?; + return Ok(Some(p)); } else { + // Decryption failed with a matching session, the session is + // likely wedged and needs to be rotated. if matches { + warn!( + "Found a matching Olm session yet decryption failed + for sender {} and sender_key {}", + sender, sender_key + ); return Err(OlmError::SessionWedged); } } @@ -693,31 +708,74 @@ impl OlmMachine { async fn decrypt_olm_message( &mut self, - _sender: &str, + sender: &str, sender_key: &str, message: OlmMessage, ) -> Result> { - let plaintext = if let Some(p) = self.try_decrypt_olm_event(sender_key, &message).await? { + // First try to decrypt using an existing session. + let plaintext = if let Some(p) = self + .try_decrypt_olm_event(sender, sender_key, &message) + .await? + { + // Decryption succeeded, destructure the plaintext out of the + // Option. p } else { + // Decryption failed with every known session, let's try to create a + // new session. let mut session = match &message { - OlmMessage::Message(_) => return Err(OlmError::SessionWedged), + /// A new session can only be created using a pre-key message, + /// return with an error if it isn't one. + OlmMessage::Message(_) => { + warn!( + "Failed to decrypt a non-pre-key message with all + available sessions {} {}", + sender, sender_key + ); + return Err(OlmError::SessionWedged); + } + OlmMessage::PreKey(m) => { - let session = self + /// Create the new session. + let session = match self .account .create_inbound_session(sender_key, m.clone()) - .await?; + .await + { + Ok(s) => s, + Err(e) => { + warn!( + "Failed to create a new Olm session for {} {} + from a prekey message: {}", + sender, sender_key, e + ); + return Err(OlmError::SessionWedged); + } + }; + + /// Save the account since we remove the one-time key that + /// was used to create this session. self.store.save_account(self.account.clone()).await?; session } }; + /// Decrypt our message, this shouldn't fail since we're using a + /// newly created Session. let plaintext = session.decrypt(message).await?; + + /// Save the new ratcheted state of the session. self.store.save_session(session).await?; plaintext }; trace!("Successfully decrypted a Olm message: {}", plaintext); + + // TODO get the recipient, recipient_keys, and keys out of here + // separately. + // TODO verify that the recipient, recipient_keys match with us. + // TODO verify that the sender in the decrypted event matches the one in + // the unencrypted one. Ok(serde_json::from_str::>( &plaintext, )?) @@ -749,19 +807,27 @@ impl OlmMachine { let own_key = identity_keys.curve25519(); let own_ciphertext = content.ciphertext.get(own_key); + // Try to find a ciphertext that was meant for our device. if let Some(ciphertext) = own_ciphertext { let message_type: u8 = ciphertext .message_type .try_into() .map_err(|_| OlmError::UnsupportedOlmType)?; + + // Create a OlmMessage from the ciphertext and the type. let message = OlmMessage::from_type_and_ciphertext(message_type.into(), ciphertext.body.clone()) .map_err(|_| OlmError::UnsupportedOlmType)?; + // Decrypt the OlmMessage and get a Ruma event out of it. let mut decrypted_event = self .decrypt_olm_message(&event.sender.to_string(), &content.sender_key, message) .await?; + debug!("Decrypted a to-device event {:?}", decrypted_event); + + // Handle the decrypted event, e.g. fetch out megolm sessions out of + // the event. self.handle_decrypted_to_device_event(&content.sender_key, &mut decrypted_event) .await?; From 0eab02a941363b62df685e35dc2ccd623dd5d96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Apr 2020 13:43:45 +0200 Subject: [PATCH 05/13] crypto: Remove a stale comment. --- src/crypto/machine.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index 45788819..40a1b865 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -841,7 +841,6 @@ impl OlmMachine { async fn add_room_key(&mut self, sender_key: &str, event: &mut ToDeviceRoomKey) -> Result<()> { match event.content.algorithm { Algorithm::MegolmV1AesSha2 => { - // TODO check for all the valid fields. let signing_key = event .keys .get("ed25519") From 27ae8bccb97417890aad14d6071d7a9e1fd2e423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Apr 2020 15:15:08 +0200 Subject: [PATCH 06/13] crypto: Check the recipient and recipient keys for decrypted events. --- src/crypto/machine.rs | 119 ++++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 32 deletions(-) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index 40a1b865..343cd458 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -655,13 +655,13 @@ impl OlmMachine { async fn try_decrypt_olm_event( &mut self, - sender: &str, + sender: &UserId, sender_key: &str, message: &OlmMessage, ) -> Result> { let s = self.store.get_sessions(sender_key).await?; - /// We don't have any existing sessions, return early. + // We don't have any existing sessions, return early. let sessions = if let Some(s) = s { s } else { @@ -708,10 +708,10 @@ impl OlmMachine { async fn decrypt_olm_message( &mut self, - sender: &str, + sender: &UserId, sender_key: &str, message: OlmMessage, - ) -> Result> { + ) -> Result<(EventResult, String)> { // First try to decrypt using an existing session. let plaintext = if let Some(p) = self .try_decrypt_olm_event(sender, sender_key, &message) @@ -724,8 +724,8 @@ impl OlmMachine { // Decryption failed with every known session, let's try to create a // new session. let mut session = match &message { - /// A new session can only be created using a pre-key message, - /// return with an error if it isn't one. + // A new session can only be created using a pre-key message, + // return with an error if it isn't one. OlmMessage::Message(_) => { warn!( "Failed to decrypt a non-pre-key message with all @@ -736,7 +736,7 @@ impl OlmMachine { } OlmMessage::PreKey(m) => { - /// Create the new session. + // Create the new session. let session = match self .account .create_inbound_session(sender_key, m.clone()) @@ -753,32 +753,79 @@ impl OlmMachine { } }; - /// Save the account since we remove the one-time key that - /// was used to create this session. + // Save the account since we remove the one-time key that + // was used to create this session. self.store.save_account(self.account.clone()).await?; session } }; - /// Decrypt our message, this shouldn't fail since we're using a - /// newly created Session. + // Decrypt our message, this shouldn't fail since we're using a + // newly created Session. let plaintext = session.decrypt(message).await?; - /// Save the new ratcheted state of the session. + // Save the new ratcheted state of the session. self.store.save_session(session).await?; plaintext }; trace!("Successfully decrypted a Olm message: {}", plaintext); - // TODO get the recipient, recipient_keys, and keys out of here - // separately. - // TODO verify that the recipient, recipient_keys match with us. - // TODO verify that the sender in the decrypted event matches the one in - // the unencrypted one. - Ok(serde_json::from_str::>( - &plaintext, - )?) + Ok(self.parse_decrypted_to_device_event(sender, &plaintext)?) + } + + fn parse_decrypted_to_device_event( + &self, + sender: &UserId, + plaintext: &str, + ) -> Result<(EventResult, String)> { + // TODO make the errors a bit more specific. + let decrypted_json: Value = serde_json::from_str(&plaintext)?; + + let encrytped_sender = decrypted_json + .get("sender") + .cloned() + .ok_or(OlmError::MissingCiphertext)?; + let encrytped_sender: UserId = serde_json::from_value(encrytped_sender)?; + let recipient = decrypted_json + .get("recipient") + .cloned() + .ok_or(OlmError::MissingCiphertext)?; + let recipient: UserId = serde_json::from_value(recipient)?; + + let recipient_keys: HashMap = serde_json::from_value( + decrypted_json + .get("recipient_keys") + .cloned() + .ok_or(OlmError::MissingCiphertext)?, + )?; + let keys: HashMap = serde_json::from_value( + decrypted_json + .get("keys") + .cloned() + .ok_or(OlmError::MissingCiphertext)?, + )?; + + if recipient != self.user_id || sender != &encrytped_sender { + return Err(OlmError::MissingCiphertext); + } + + if self.account.identity_keys().ed25519() + != recipient_keys + .get(&KeyAlgorithm::Ed25519) + .ok_or(OlmError::MissingCiphertext)? + { + return Err(OlmError::MissingCiphertext); + } + + let signing_key = keys + .get(&KeyAlgorithm::Ed25519) + .ok_or(OlmError::MissingSigningKey)?; + + Ok(( + serde_json::from_value::>(decrypted_json)?, + signing_key.to_owned(), + )) } /// Decrypt a to-device event. @@ -820,16 +867,20 @@ impl OlmMachine { .map_err(|_| OlmError::UnsupportedOlmType)?; // Decrypt the OlmMessage and get a Ruma event out of it. - let mut decrypted_event = self - .decrypt_olm_message(&event.sender.to_string(), &content.sender_key, message) + let (mut decrypted_event, signing_key) = self + .decrypt_olm_message(&event.sender, &content.sender_key, message) .await?; debug!("Decrypted a to-device event {:?}", decrypted_event); // Handle the decrypted event, e.g. fetch out megolm sessions out of // the event. - self.handle_decrypted_to_device_event(&content.sender_key, &mut decrypted_event) - .await?; + self.handle_decrypted_to_device_event( + &content.sender_key, + &signing_key, + &mut decrypted_event, + ) + .await?; Ok(decrypted_event) } else { @@ -838,14 +889,14 @@ impl OlmMachine { } } - async fn add_room_key(&mut self, sender_key: &str, event: &mut ToDeviceRoomKey) -> Result<()> { + async fn add_room_key( + &mut self, + sender_key: &str, + signing_key: &str, + event: &mut ToDeviceRoomKey, + ) -> Result<()> { match event.content.algorithm { Algorithm::MegolmV1AesSha2 => { - let signing_key = event - .keys - .get("ed25519") - .ok_or(OlmError::MissingSigningKey)?; - let session_key = GroupSessionKey(mem::take(&mut event.content.session_key)); let session = InboundGroupSession::new( @@ -1101,6 +1152,7 @@ impl OlmMachine { fn add_forwarded_room_key( &self, _sender_key: &str, + _signing_key: &str, _event: &ToDeviceForwardedRoomKey, ) -> Result<()> { Ok(()) @@ -1110,6 +1162,7 @@ impl OlmMachine { async fn handle_decrypted_to_device_event( &mut self, sender_key: &str, + signing_key: &str, event: &mut EventResult, ) -> Result<()> { let event = if let EventResult::Ok(e) = event { @@ -1120,8 +1173,10 @@ impl OlmMachine { }; match event { - ToDeviceEvent::RoomKey(e) => self.add_room_key(sender_key, e).await, - ToDeviceEvent::ForwardedRoomKey(e) => self.add_forwarded_room_key(sender_key, e), + ToDeviceEvent::RoomKey(e) => self.add_room_key(sender_key, signing_key, e).await, + ToDeviceEvent::ForwardedRoomKey(e) => { + self.add_forwarded_room_key(sender_key, signing_key, e) + } _ => { warn!("Received a unexpected encrypted to-device event"); Ok(()) From b56b720c0cb9ac1a0e69ea8f287071fab73f09c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Apr 2020 15:22:43 +0200 Subject: [PATCH 07/13] crypto: Return a EncryptedEventContent when encrypting instead of a specific one. --- src/crypto/machine.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index 343cd458..f6576c6b 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -944,7 +944,7 @@ impl OlmMachine { &self, room_id: &RoomId, content: MessageEventContent, - ) -> Result { + ) -> Result { let session = self.outbound_group_session.get(room_id); let session = if let Some(s) = session { @@ -972,13 +972,15 @@ impl OlmMachine { let ciphertext = session.encrypt(plaintext).await; - Ok(MegolmV1AesSha2Content { - algorithm: Algorithm::MegolmV1AesSha2, - ciphertext, - sender_key: self.account.identity_keys().curve25519().to_owned(), - session_id: session.session_id().to_owned(), - device_id: self.device_id.to_owned(), - }) + Ok(EncryptedEventContent::MegolmV1AesSha2( + MegolmV1AesSha2Content { + algorithm: Algorithm::MegolmV1AesSha2, + ciphertext, + sender_key: self.account.identity_keys().curve25519().to_owned(), + session_id: session.session_id().to_owned(), + device_id: self.device_id.to_owned(), + }, + )) } async fn olm_encrypt( From 4369d0b854eda85003757a239dc3934ab930a999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Apr 2020 15:38:42 +0200 Subject: [PATCH 08/13] crypto: Return a EncryptedEventContent when encrypting using Olm instead of a specific one. --- src/crypto/machine.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index f6576c6b..3f7353c9 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -989,7 +989,7 @@ impl OlmMachine { recipient_device: &Device, event_type: EventType, content: Value, - ) -> Result { + ) -> Result { let identity_keys = self.account.identity_keys(); let recipient_signing_key = recipient_device @@ -1030,11 +1030,13 @@ impl OlmMachine { content.insert(recipient_sender_key.to_owned(), ciphertext); - Ok(OlmV1Curve25519AesSha2Content { - algorithm: Algorithm::OlmV1Curve25519AesSha2, - sender_key: identity_keys.curve25519().to_owned(), - ciphertext: content, - }) + Ok(EncryptedEventContent::OlmV1Curve25519AesSha2( + OlmV1Curve25519AesSha2Content { + algorithm: Algorithm::OlmV1Curve25519AesSha2, + sender_key: identity_keys.curve25519().to_owned(), + ciphertext: content, + }, + )) } /// Should the client share a group session for the given room. @@ -1137,12 +1139,12 @@ impl OlmMachine { user_messages.insert( DeviceIdOrAllDevices::DeviceId(device.device_id().clone()), - encrypted_content, + MessageEventContent::Encrypted(encrypted_content), ); } message_vec.push(ToDeviceRequest { - event_type: "m.room.encrypted".to_owned(), + event_type: EventType::RoomEncrypted, txn_id: Uuid::new_v4().to_string(), messages, }); From 1de791c20729713c5e2f5dc530dafbba4e95590d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Apr 2020 10:52:47 +0200 Subject: [PATCH 09/13] rust-sdk: Update to the latest ruma releases. --- Cargo.toml | 8 ++-- examples/command_bot.rs | 2 + src/async_client.rs | 65 +++++++++++++----------------- src/base_client.rs | 27 ++++++------- src/crypto/device.rs | 12 +++--- src/crypto/machine.rs | 81 ++++++++++++++++++++++---------------- src/crypto/olm.rs | 6 +-- src/crypto/store/sqlite.rs | 8 ++-- src/models/room.rs | 4 +- src/request_builder.rs | 14 ++++--- src/test_builder.rs | 24 +++++------ 11 files changed, 126 insertions(+), 125 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6aec8a38..a284189a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,10 @@ async-trait = "0.1.30" # Ruma dependencies js_int = "0.1.4" -ruma-api = "0.15.1" -ruma-client-api = { git = "https://github.com/matrix-org/ruma-client-api/", version = "0.7.0" } -ruma-events = { git = "https://github.com/matrix-org/ruma-events", version = "0.18.0" } -ruma-identifiers = "0.14.1" +ruma-api = "0.16.0-rc.2" +ruma-client-api = { version = "0.8.0-rc.5" } +ruma-events = { version = "0.21.0-beta.1" } +ruma-identifiers = "0.16.0" uuid = { version = "0.8.1", features = ["v4"] } # Dependencies for the encryption support diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 220b0ea1..fded958e 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -101,6 +101,8 @@ async fn login_and_sync( #[tokio::main] async fn main() -> Result<(), matrix_sdk::Error> { + tracing_subscriber::fmt::init(); + let (homeserver_url, username, password) = match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) { (Some(a), Some(b), Some(c)) => (a, b, c), diff --git a/src/async_client.rs b/src/async_client.rs index 94fc33bb..8a4a5d60 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::convert::{TryFrom, TryInto}; use std::result::Result as StdResult; use std::sync::Arc; @@ -33,9 +33,9 @@ use http::Response as HttpResponse; use reqwest::header::{HeaderValue, InvalidHeaderValue}; use url::Url; -use ruma_api::{Endpoint, Outgoing}; +use ruma_api::Endpoint; use ruma_events::room::message::MessageEventContent; -use ruma_events::EventResult; +use ruma_events::EventJson; pub use ruma_events::EventType; use ruma_identifiers::{RoomId, RoomIdOrAliasId, UserId}; @@ -370,9 +370,11 @@ impl AsyncClient { pub async fn join_room_by_id_or_alias( &self, alias: &RoomIdOrAliasId, + server_name: &str, ) -> Result { let request = join_room_by_id_or_alias::Request { room_id_or_alias: alias.clone(), + server_name: server_name.to_owned(), third_party_signed: None, }; self.send(request).await @@ -538,7 +540,7 @@ impl AsyncClient { pub async fn room_messages>( &self, request: R, - ) -> Result { + ) -> Result { let req = request.into(); self.send(req).await } @@ -549,7 +551,7 @@ impl AsyncClient { /// /// * `sync_settings` - Settings for the sync call. #[instrument] - pub async fn sync(&self, sync_settings: SyncSettings) -> Result { + pub async fn sync(&self, sync_settings: SyncSettings) -> Result { let request = sync_events::Request { filter: None, since: sync_settings.token, @@ -564,7 +566,7 @@ impl AsyncClient { let matrix_room = { let mut client = self.base_client.write().await; for event in &room.state.events { - if let EventResult::Ok(e) = event { + if let Ok(e) = event.deserialize() { client.receive_joined_state_event(&room_id, &e).await; } } @@ -577,9 +579,9 @@ impl AsyncClient { // re looping is not ideal here for event in &mut room.state.events { - if let EventResult::Ok(e) = event { + if let Ok(e) = event.deserialize() { let client = self.base_client.read().await; - client.emit_state_event(room_id, e).await; + client.emit_state_event(&room_id, &e).await; } } @@ -595,19 +597,19 @@ impl AsyncClient { *event = e; } - if let EventResult::Ok(e) = event { + if let Ok(e) = event.deserialize() { let client = self.base_client.read().await; - client.emit_timeline_event(room_id, e).await; + client.emit_timeline_event(&room_id, &e).await; } } // look at AccountData to further cut down users by collecting ignored users for account_data in &mut room.account_data.events { { - if let EventResult::Ok(e) = account_data { + if let Ok(e) = account_data.deserialize() { let mut client = self.base_client.write().await; - client.receive_account_data_event(&room_id, e).await; - client.emit_account_data_event(room_id, e).await; + client.receive_account_data_event(&room_id, &e).await; + client.emit_account_data_event(&room_id, &e).await; } } } @@ -617,22 +619,22 @@ impl AsyncClient { // efficient but we need a room_id so we would loop through now or later. for presence in &mut response.presence.events { { - if let EventResult::Ok(e) = presence { + if let Ok(e) = presence.deserialize() { let mut client = self.base_client.write().await; - client.receive_presence_event(&room_id, e).await; + client.receive_presence_event(&room_id, &e).await; - client.emit_presence_event(room_id, e).await; + client.emit_presence_event(&room_id, &e).await; } } } for ephemeral in &mut room.ephemeral.events { { - if let EventResult::Ok(e) = ephemeral { + if let Ok(e) = ephemeral.deserialize() { let mut client = self.base_client.write().await; - client.receive_ephemeral_event(&room_id, e).await; + client.receive_ephemeral_event(&room_id, &e).await; - client.emit_ephemeral_event(room_id, e).await; + client.emit_ephemeral_event(&room_id, &e).await; } } } @@ -703,7 +705,7 @@ impl AsyncClient { pub async fn sync_forever( &self, sync_settings: SyncSettings, - callback: impl Fn(sync_events::IncomingResponse) -> C + Send, + callback: impl Fn(sync_events::Response) -> C + Send, ) where C: Future, { @@ -759,18 +761,7 @@ impl AsyncClient { async fn send + std::fmt::Debug>( &self, request: Request, - ) -> Result<::Incoming> - where - Request::Incoming: - TryFrom>, Error = ruma_api::error::FromHttpRequestError>, - ::Incoming: TryFrom< - http::Response>, - Error = ruma_api::error::FromHttpResponseError< - ::ResponseError, - >, - >, - ::ResponseError: std::fmt::Debug, - { + ) -> Result { let request: http::Request> = request.try_into()?; let url = request.uri(); let path_and_query = url.path_and_query().unwrap(); @@ -828,9 +819,7 @@ impl AsyncClient { let body = response.bytes().await?.as_ref().to_owned(); let http_response = http_builder.body(body).unwrap(); - Ok(::Incoming::try_from( - http_response, - )?) + Ok(::try_from(http_response)?) } /// Send a room message to the homeserver. @@ -940,7 +929,7 @@ impl AsyncClient { room_id: room_id.clone(), event_type, txn_id: txn_id.unwrap_or_else(Uuid::new_v4).to_string(), - data: content, + data: EventJson::from(content), }; let response = self.send(request).await?; @@ -962,7 +951,7 @@ impl AsyncClient { #[instrument] async fn claim_one_time_keys( &self, - one_time_keys: HashMap>, + one_time_keys: BTreeMap>, ) -> Result { let request = claim_keys::Request { timeout: None, @@ -1076,7 +1065,7 @@ impl AsyncClient { users_for_query ); - let mut device_keys: HashMap> = HashMap::new(); + let mut device_keys: BTreeMap> = BTreeMap::new(); for user in users_for_query.drain() { device_keys.insert(user, Vec::new()); diff --git a/src/base_client.rs b/src/base_client.rs index 30cefc79..d139667d 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; #[cfg(feature = "encryption")] -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::fmt; use std::sync::Arc; @@ -30,7 +30,7 @@ use crate::events::presence::PresenceEvent; use crate::events::collections::only::Event as NonRoomEvent; use crate::events::ignored_user_list::IgnoredUserListEvent; use crate::events::push_rules::{PushRulesEvent, Ruleset}; -use crate::events::EventResult; +use crate::events::EventJson; use crate::identifiers::{RoomId, UserId}; use crate::models::Room; use crate::session::Session; @@ -234,10 +234,10 @@ impl Client { pub async fn receive_joined_timeline_event( &mut self, room_id: &RoomId, - event: &mut EventResult, - ) -> Option> { - match event { - EventResult::Ok(e) => { + event: &mut EventJson, + ) -> Option> { + match event.deserialize() { + Ok(mut e) => { #[cfg(feature = "encryption")] let mut decrypted_event = None; #[cfg(not(feature = "encryption"))] @@ -246,12 +246,12 @@ impl Client { #[cfg(feature = "encryption")] { match e { - RoomEvent::RoomEncrypted(e) => { + RoomEvent::RoomEncrypted(ref mut e) => { e.room_id = Some(room_id.to_owned()); let mut olm = self.olm.lock().await; if let Some(o) = &mut *olm { - decrypted_event = o.decrypt_room_event(e).await.ok(); + decrypted_event = o.decrypt_room_event(&e).await.ok(); } } _ => (), @@ -259,7 +259,7 @@ impl Client { } let mut room = self.get_or_create_room(&room_id).write().await; - room.receive_timeline_event(e); + room.receive_timeline_event(&e); decrypted_event } _ => None, @@ -358,10 +358,7 @@ impl Client { /// # Arguments /// /// * `response` - The response that we received after a successful sync. - pub async fn receive_sync_response( - &mut self, - response: &mut api::sync::sync_events::IncomingResponse, - ) { + pub async fn receive_sync_response(&mut self, response: &mut api::sync::sync_events::Response) { self.sync_token = Some(response.next_batch.clone()); #[cfg(feature = "encryption")] @@ -437,12 +434,12 @@ impl Client { pub async fn get_missing_sessions( &self, users: impl Iterator, - ) -> HashMap> { + ) -> BTreeMap> { let mut olm = self.olm.lock().await; match &mut *olm { Some(o) => o.get_missing_sessions(users).await, - None => HashMap::new(), + None => BTreeMap::new(), } } diff --git a/src/crypto/device.rs b/src/crypto/device.rs index 87ad25c4..6eb69b70 100644 --- a/src/crypto/device.rs +++ b/src/crypto/device.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use std::mem; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -29,7 +29,7 @@ pub struct Device { user_id: Arc, device_id: Arc, algorithms: Arc>, - keys: Arc>, + keys: Arc>, display_name: Arc>, deleted: Arc, trust_state: Arc>, @@ -68,7 +68,7 @@ impl Device { display_name: Option, trust_state: TrustState, algorithms: Vec, - keys: HashMap, + keys: BTreeMap, ) -> Self { Device { user_id: Arc::new(user_id), @@ -102,7 +102,7 @@ impl Device { } /// Get a map containing all the device keys. - pub fn keys(&self) -> &HashMap { + pub fn keys(&self) -> &BTreeMap { &self.keys } @@ -123,7 +123,7 @@ impl Device { /// Update a device with a new device keys struct. pub(crate) fn update_device(&mut self, device_keys: &DeviceKeys) { - let mut keys = HashMap::new(); + let mut keys = BTreeMap::new(); for (key_id, key) in device_keys.keys.iter() { let key_id = key_id.0; @@ -153,7 +153,7 @@ impl Device { impl From<&DeviceKeys> for Device { fn from(device_keys: &DeviceKeys) -> Self { - let mut keys = HashMap::new(); + let mut keys = BTreeMap::new(); for (key_id, key) in device_keys.keys.iter() { let key_id = key_id.0; diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index 3f7353c9..068dd5e6 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryInto; use std::mem; #[cfg(feature = "sqlite-cryptostore")] @@ -42,7 +42,7 @@ use crate::events::{ AnyToDeviceEvent as ToDeviceEvent, ToDeviceEncrypted, ToDeviceForwardedRoomKey, ToDeviceRoomKey, ToDeviceRoomKeyRequest, }, - Algorithm, EventResult, EventType, + Algorithm, EventJson, EventType, }; use crate::identifiers::{DeviceId, RoomId, UserId}; @@ -50,14 +50,14 @@ use api::r0::keys; use api::r0::{ client_exchange::{send_event_to_device::Request as ToDeviceRequest, DeviceIdOrAllDevices}, keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey, SignedKey}, - sync::sync_events::IncomingResponse as SyncResponse, + sync::sync_events::Response as SyncResponse, }; use cjson; use serde_json::{json, Value}; use tracing::{debug, error, info, instrument, trace, warn}; -pub type OneTimeKeys = HashMap; +pub type OneTimeKeys = BTreeMap; #[derive(Debug)] pub struct OlmMachine { @@ -193,8 +193,8 @@ impl OlmMachine { pub async fn get_missing_sessions( &mut self, users: impl Iterator, - ) -> HashMap> { - let mut missing = HashMap::new(); + ) -> BTreeMap> { + let mut missing = BTreeMap::new(); for user_id in users { let user_devices = self.store.get_user_devices(user_id).await.unwrap(); @@ -216,7 +216,7 @@ impl OlmMachine { if is_missing { if !missing.contains_key(user_id) { - missing.insert(user_id.clone(), HashMap::new()); + missing.insert(user_id.clone(), BTreeMap::new()); } let user_map = missing.get_mut(user_id).unwrap(); @@ -472,7 +472,7 @@ impl OlmMachine { async fn device_keys(&self) -> DeviceKeys { let identity_keys = self.account.identity_keys(); - let mut keys = HashMap::new(); + let mut keys = BTreeMap::new(); keys.insert( AlgorithmAndDeviceId(KeyAlgorithm::Curve25519, self.device_id.clone()), @@ -490,9 +490,9 @@ impl OlmMachine { "keys": keys, }); - let mut signatures = HashMap::new(); + let mut signatures = BTreeMap::new(); - let mut signature = HashMap::new(); + let mut signature = BTreeMap::new(); signature.insert( AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, self.device_id.clone()), self.sign_json(&device_keys).await, @@ -518,7 +518,7 @@ impl OlmMachine { async fn signed_one_time_keys(&self) -> StdResult { let _ = self.generate_one_time_keys().await?; let one_time_keys = self.account.one_time_keys().await; - let mut one_time_key_map = HashMap::new(); + let mut one_time_key_map = BTreeMap::new(); for (key_id, key) in one_time_keys.curve25519().iter() { let key_json = json!({ @@ -527,14 +527,14 @@ impl OlmMachine { let signature = self.sign_json(&key_json).await; - let mut signature_map = HashMap::new(); + let mut signature_map = BTreeMap::new(); signature_map.insert( AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, self.device_id.clone()), signature, ); - let mut signatures = HashMap::new(); + let mut signatures = BTreeMap::new(); signatures.insert(self.user_id.clone(), signature_map); let signed_key = SignedKey { @@ -711,7 +711,7 @@ impl OlmMachine { sender: &UserId, sender_key: &str, message: OlmMessage, - ) -> Result<(EventResult, String)> { + ) -> Result<(EventJson, String)> { // First try to decrypt using an existing session. let plaintext = if let Some(p) = self .try_decrypt_olm_event(sender, sender_key, &message) @@ -778,7 +778,7 @@ impl OlmMachine { &self, sender: &UserId, plaintext: &str, - ) -> Result<(EventResult, String)> { + ) -> Result<(EventJson, String)> { // TODO make the errors a bit more specific. let decrypted_json: Value = serde_json::from_str(&plaintext)?; @@ -793,13 +793,13 @@ impl OlmMachine { .ok_or(OlmError::MissingCiphertext)?; let recipient: UserId = serde_json::from_value(recipient)?; - let recipient_keys: HashMap = serde_json::from_value( + let recipient_keys: BTreeMap = serde_json::from_value( decrypted_json .get("recipient_keys") .cloned() .ok_or(OlmError::MissingCiphertext)?, )?; - let keys: HashMap = serde_json::from_value( + let keys: BTreeMap = serde_json::from_value( decrypted_json .get("keys") .cloned() @@ -823,7 +823,7 @@ impl OlmMachine { .ok_or(OlmError::MissingSigningKey)?; Ok(( - serde_json::from_value::>(decrypted_json)?, + serde_json::from_value::>(decrypted_json)?, signing_key.to_owned(), )) } @@ -840,7 +840,7 @@ impl OlmMachine { async fn decrypt_to_device_event( &mut self, event: &ToDeviceEncrypted, - ) -> Result> { + ) -> Result> { info!("Decrypting to-device event"); let content = if let EncryptedEventContent::OlmV1Curve25519AesSha2(c) = &event.content { @@ -1026,7 +1026,7 @@ impl OlmMachine { message_type: (message_type as u32).into(), }; - let mut content = HashMap::new(); + let mut content = BTreeMap::new(); content.insert(recipient_sender_key.to_owned(), ciphertext); @@ -1119,11 +1119,11 @@ impl OlmMachine { let mut message_vec = Vec::new(); for user_map_chunk in user_map.chunks(OlmMachine::MAX_TO_DEVICE_MESSAGES) { - let mut messages = HashMap::new(); + let mut messages = BTreeMap::new(); for (session, device) in user_map_chunk { if !messages.contains_key(device.user_id()) { - messages.insert(device.user_id().clone(), HashMap::new()); + messages.insert(device.user_id().clone(), BTreeMap::new()); }; let user_messages = messages.get_mut(device.user_id()).unwrap(); @@ -1139,7 +1139,7 @@ impl OlmMachine { user_messages.insert( DeviceIdOrAllDevices::DeviceId(device.device_id().clone()), - MessageEventContent::Encrypted(encrypted_content), + EventJson::from(MessageEventContent::Encrypted(encrypted_content)), ); } @@ -1167,9 +1167,9 @@ impl OlmMachine { &mut self, sender_key: &str, signing_key: &str, - event: &mut EventResult, + event: &mut EventJson, ) -> Result<()> { - let event = if let EventResult::Ok(e) = event { + let event = if let Ok(e) = event.deserialize() { e } else { warn!("Decrypted to-device event failed to be parsed correctly"); @@ -1177,9 +1177,11 @@ impl OlmMachine { }; match event { - ToDeviceEvent::RoomKey(e) => self.add_room_key(sender_key, signing_key, e).await, - ToDeviceEvent::ForwardedRoomKey(e) => { - self.add_forwarded_room_key(sender_key, signing_key, e) + ToDeviceEvent::RoomKey(mut e) => { + self.add_room_key(sender_key, signing_key, &mut e).await + } + ToDeviceEvent::ForwardedRoomKey(mut e) => { + self.add_forwarded_room_key(sender_key, signing_key, &mut e) } _ => { warn!("Received a unexpected encrypted to-device event"); @@ -1213,7 +1215,7 @@ impl OlmMachine { self.uploaded_signed_key_count = Some(count); for event_result in &mut response.to_device.events { - let event = if let EventResult::Ok(e) = &event_result { + let event = if let Ok(e) = event_result.deserialize() { e } else { // Skip invalid events. @@ -1223,7 +1225,7 @@ impl OlmMachine { info!("Received a to-device event {:?}", event); - match event { + match &event { ToDeviceEvent::RoomEncrypted(e) => { let decrypted_event = match self.decrypt_to_device_event(e).await { Ok(e) => e, @@ -1248,7 +1250,7 @@ impl OlmMachine { | ToDeviceEvent::KeyVerificationKey(..) | ToDeviceEvent::KeyVerificationMac(..) | ToDeviceEvent::KeyVerificationRequest(..) - | ToDeviceEvent::KeyVerificationStart(..) => self.handle_verification_event(event), + | ToDeviceEvent::KeyVerificationStart(..) => self.handle_verification_event(&event), _ => continue, } } @@ -1257,7 +1259,7 @@ impl OlmMachine { pub async fn decrypt_room_event( &mut self, event: &EncryptedEvent, - ) -> Result> { + ) -> Result> { let content = match &event.content { EncryptedEventContent::MegolmV1AesSha2(c) => c, _ => return Err(OlmError::UnsupportedAlgorithm), @@ -1281,15 +1283,24 @@ impl OlmMachine { .as_object_mut() .ok_or(OlmError::NotAnObject)?; - let server_ts: u64 = event.origin_server_ts.into(); + // TODO better number conversion here. + let server_ts = event + .origin_server_ts + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_millis(); + let server_ts: i64 = server_ts.try_into().unwrap_or_default(); decrypted_object.insert("sender".to_owned(), event.sender.to_string().into()); decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into()); decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into()); - decrypted_object.insert("unsigned".to_owned(), event.unsigned.clone().into()); + decrypted_object.insert( + "unsigned".to_owned(), + serde_json::to_value(&event.unsigned).unwrap_or_default(), + ); - let decrypted_event = serde_json::from_value::>(decrypted_value)?; + let decrypted_event = serde_json::from_value::>(decrypted_value)?; trace!("Successfully decrypted megolm event {:?}", decrypted_event); // TODO set the encryption info on the event (is it verified, was it // decrypted, sender key...) diff --git a/src/crypto/olm.rs b/src/crypto/olm.rs index 3d6d4ac4..64e247af 100644 --- a/src/crypto/olm.rs +++ b/src/crypto/olm.rs @@ -623,7 +623,7 @@ pub(crate) mod test { use crate::identifiers::RoomId; use olm_rs::session::OlmMessage; use ruma_client_api::r0::keys::SignedKey; - use std::collections::HashMap; + use std::collections::BTreeMap; use std::convert::TryFrom; pub(crate) async fn get_account_and_session() -> (Account, Session) { @@ -643,7 +643,7 @@ pub(crate) mod test { .to_owned(); let one_time_key = SignedKey { key: one_time_key, - signatures: HashMap::new(), + signatures: BTreeMap::new(), }; let sender_key = bob.identity_keys().curve25519().to_owned(); let session = alice @@ -721,7 +721,7 @@ pub(crate) mod test { let one_time_key = SignedKey { key: one_time_key, - signatures: HashMap::new(), + signatures: BTreeMap::new(), }; let mut bob_session = bob diff --git a/src/crypto/store/sqlite.rs b/src/crypto/store/sqlite.rs index 5f768e18..2c92d4fd 100644 --- a/src/crypto/store/sqlite.rs +++ b/src/crypto/store/sqlite.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use std::convert::TryFrom; use std::mem; use std::path::{Path, PathBuf}; @@ -348,7 +348,7 @@ impl SqliteStore { .fetch_all(&mut *connection) .await?; - let mut keys = HashMap::new(); + let mut keys = BTreeMap::new(); for row in key_rows { let algorithm = if let Ok(a) = KeyAlgorithm::try_from(&row.0 as &str) { @@ -643,7 +643,7 @@ mod test { use crate::crypto::device::test::get_device; use crate::crypto::olm::GroupSessionKey; use olm_rs::outbound_group_session::OlmOutboundGroupSession; - use std::collections::HashMap; + use std::collections::BTreeMap; use tempfile::tempdir; use super::{ @@ -709,7 +709,7 @@ mod test { .to_owned(); let one_time_key = SignedKey { key: one_time_key, - signatures: HashMap::new(), + signatures: BTreeMap::new(), }; let sender_key = bob.identity_keys().curve25519().to_owned(); let session = alice diff --git a/src/models/room.rs b/src/models/room.rs index a12405aa..06d9c69a 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::convert::TryFrom; use super::RoomMember; @@ -63,7 +63,7 @@ pub struct PowerLevels { /// The level required to send specific event types. /// /// This is a mapping from event type to power level required. - pub events: HashMap, + pub events: BTreeMap, /// The default level required to send message events. pub events_default: Int, /// The level required to invite a user. diff --git a/src/request_builder.rs b/src/request_builder.rs index 795e8534..8d61d05d 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -1,5 +1,6 @@ use crate::api; use crate::events::room::power_levels::PowerLevelsEventContent; +use crate::events::EventJson; use crate::identifiers::{RoomId, UserId}; use api::r0::filter::RoomEventFilter; use api::r0::membership::Invite3pid; @@ -163,7 +164,7 @@ impl Into for RoomBuilder { invite_3pid: self.invite_3pid, is_direct: self.is_direct, name: self.name, - power_level_content_override: self.power_level_content_override, + power_level_content_override: self.power_level_content_override.map(EventJson::from), preset: self.preset, room_alias_name: self.room_alias_name, room_version: self.room_version, @@ -177,6 +178,7 @@ impl Into for RoomBuilder { /// /// # Examples /// ``` +/// # use std::convert::TryFrom; /// # use matrix_sdk::{AsyncClient, MessagesRequestBuilder}; /// # use matrix_sdk::api::r0::message::get_message_events::{self, Direction}; /// # use matrix_sdk::identifiers::RoomId; @@ -184,8 +186,8 @@ impl Into for RoomBuilder { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # let mut rt = tokio::runtime::Runtime::new().unwrap(); /// # rt.block_on(async { -/// # let room_id = RoomId::new(homeserver.as_str()).unwrap(); -/// # let last_sync_token = "".to_string();; +/// # let room_id = RoomId::try_from("!test:localhost").unwrap(); +/// # let last_sync_token = "".to_string(); /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); /// /// let mut builder = MessagesRequestBuilder::new(); @@ -288,7 +290,7 @@ impl Into for MessagesRequestBuilder { #[cfg(test)] mod test { - use std::collections::HashMap; + use std::collections::{BTreeMap, HashMap}; use super::*; use crate::events::room::power_levels::NotificationPowerLevels; @@ -325,7 +327,7 @@ mod test { .is_direct(true) .power_level_override(PowerLevelsEventContent { ban: Int::max_value(), - events: HashMap::default(), + events: BTreeMap::default(), events_default: Int::min_value(), invite: Int::min_value(), kick: Int::min_value(), @@ -335,7 +337,7 @@ mod test { notifications: NotificationPowerLevels { room: Int::min_value(), }, - users: HashMap::default(), + users: BTreeMap::default(), }) .preset(RoomPreset::PrivateChat) .room_alias_name("room_alias") diff --git a/src/test_builder.rs b/src/test_builder.rs index 4849cfb1..ff637b1c 100644 --- a/src/test_builder.rs +++ b/src/test_builder.rs @@ -10,7 +10,7 @@ use crate::events::{ only::Event, }, presence::PresenceEvent, - EventResult, TryFromRaw, + EventJson, TryFromRaw, }; use crate::identifiers::{RoomId, UserId}; use crate::AsyncClient; @@ -97,9 +97,9 @@ impl EventBuilder { ) -> Self { let val = fs::read_to_string(path.as_ref()) .expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val) + let event = serde_json::from_str::>(&val) .unwrap() - .into_result() + .deserialize() .unwrap(); self.ephemeral.push(variant(event)); self @@ -113,9 +113,9 @@ impl EventBuilder { ) -> Self { let val = fs::read_to_string(path.as_ref()) .expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val) + let event = serde_json::from_str::>(&val) .unwrap() - .into_result() + .deserialize() .unwrap(); self.account_data.push(variant(event)); self @@ -129,9 +129,9 @@ impl EventBuilder { ) -> Self { let val = fs::read_to_string(path.as_ref()) .expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val) + let event = serde_json::from_str::>(&val) .unwrap() - .into_result() + .deserialize() .unwrap(); self.room_events.push(variant(event)); self @@ -145,9 +145,9 @@ impl EventBuilder { ) -> Self { let val = fs::read_to_string(path.as_ref()) .expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val) + let event = serde_json::from_str::>(&val) .unwrap() - .into_result() + .deserialize() .unwrap(); self.state_events.push(variant(event)); self @@ -157,9 +157,9 @@ impl EventBuilder { pub fn add_presence_event_from_file>(mut self, path: P) -> Self { let val = fs::read_to_string(path.as_ref()) .expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val) + let event = serde_json::from_str::>(&val) .unwrap() - .into_result() + .deserialize() .unwrap(); self.presence_events.push(event); self @@ -344,7 +344,7 @@ impl ClientTestRunner { } for event in &self.room_events { - cli.receive_joined_timeline_event(room_id, &mut EventResult::Ok(event.clone())) + cli.receive_joined_timeline_event(room_id, &mut EventJson::from(event.clone())) .await; } for event in &self.presence_events { From cb235c47a1fc5cd766096ee58e2324f9aaa1f562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Apr 2020 11:37:47 +0200 Subject: [PATCH 10/13] machine: Remove a deadlock when decrypting Olm messages using an existing session. --- src/base_client.rs | 1 + src/crypto/machine.rs | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/base_client.rs b/src/base_client.rs index d139667d..335897b4 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -237,6 +237,7 @@ impl Client { event: &mut EventJson, ) -> Option> { match event.deserialize() { + #[allow(unused_mut)] Ok(mut e) => { #[cfg(feature = "encryption")] let mut decrypted_event = None; diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index 068dd5e6..c6e6dc9a 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -59,7 +59,6 @@ use tracing::{debug, error, info, instrument, trace, warn}; pub type OneTimeKeys = BTreeMap; -#[derive(Debug)] pub struct OlmMachine { /// The unique user id that owns this account. user_id: UserId, @@ -83,6 +82,15 @@ pub struct OlmMachine { outbound_group_session: HashMap, } +impl std::fmt::Debug for OlmMachine { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OlmMachine") + .field("user_id", &self.user_id) + .field("device_id", &self.device_id) + .finish() + } +} + impl OlmMachine { const ALGORITHMS: &'static [&'static ruma_events::Algorithm] = &[ &Algorithm::OlmV1Curve25519AesSha2, @@ -668,6 +676,9 @@ impl OlmMachine { return Ok(None); }; + let mut session_to_save = None; + let mut plaintext = None; + for session in &mut *sessions.lock().await { let mut matches = false; @@ -684,11 +695,10 @@ impl OlmMachine { let ret = session.decrypt(message.clone()).await; if let Ok(p) = ret { - // Decryption was successful, save the new ratchet state of the - // session. - self.store.save_session(session.clone()).await?; + plaintext = Some(p); + session_to_save = Some(session.clone()); - return Ok(Some(p)); + break; } else { // Decryption failed with a matching session, the session is // likely wedged and needs to be rotated. @@ -703,7 +713,14 @@ impl OlmMachine { } } - Ok(None) + if let Some(session) = session_to_save { + // Decryption was successful, save the new ratchet state of the + // session that was used to decrypt the message. + trace!("Saved the new session state for {}", sender); + self.store.save_session(session).await?; + } + + Ok(plaintext) } async fn decrypt_olm_message( @@ -836,7 +853,6 @@ impl OlmMachine { /// # Arguments /// /// * `event` - The to-device event that should be decrypted. - #[instrument] async fn decrypt_to_device_event( &mut self, event: &ToDeviceEncrypted, From a16d3b3d29bfbb448dcda3ce4c77515ecc027c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Apr 2020 11:58:21 +0200 Subject: [PATCH 11/13] crypto: Don't instrument the debug implementation. --- src/crypto/machine.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index c6e6dc9a..357975b2 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -82,6 +82,7 @@ pub struct OlmMachine { outbound_group_session: HashMap, } +#[cfg_attr(tarpaulin, skip)] impl std::fmt::Debug for OlmMachine { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("OlmMachine") From 986985464a9cab66075614b9a3d8ae375aaac745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Apr 2020 12:43:59 +0200 Subject: [PATCH 12/13] rust-sdk: Remove some unused imports. --- src/async_client.rs | 4 +++- src/request_builder.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index 8a4a5d60..beae3f49 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -13,7 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, HashMap}; +#[cfg(feature = "encryption")] +use std::collections::BTreeMap; +use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::result::Result as StdResult; use std::sync::Arc; diff --git a/src/request_builder.rs b/src/request_builder.rs index 8d61d05d..f9d58d14 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -290,7 +290,7 @@ impl Into for MessagesRequestBuilder { #[cfg(test)] mod test { - use std::collections::{BTreeMap, HashMap}; + use std::collections::BTreeMap; use super::*; use crate::events::room::power_levels::NotificationPowerLevels; From 6d12ed2046dcc6e495e04260437014e3ab6b93d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 23 Apr 2020 14:56:59 +0200 Subject: [PATCH 13/13] async_client: Don't request a write lock for crypto based requests. The crypto state machine is in the base client behind a separate lock so there's no need to require write access. This removes a deadlock if we're trying to send an encrypted message from a event emitter callback, since emitting an event fetches a read lock as well. --- src/async_client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index beae3f49..54098166 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -962,7 +962,7 @@ impl AsyncClient { let response = self.send(request).await?; self.base_client - .write() + .read() .await .receive_keys_claim_response(&response) .await?; @@ -1032,7 +1032,7 @@ impl AsyncClient { let response = self.send(request).await?; self.base_client - .write() + .read() .await .receive_keys_upload_response(&response) .await?; @@ -1081,7 +1081,7 @@ impl AsyncClient { let response = self.send(request).await?; self.base_client - .write() + .read() .await .receive_keys_query_response(&response) .await?;