diff --git a/Makefile b/Makefile index ccc1cdfd..75052041 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +all: build + +build: + cargo build --features 'encryption sqlite-cryptostore' test: cargo test --features 'encryption sqlite-cryptostore' diff --git a/src/async_client.rs b/src/async_client.rs index fbbac73b..090fa8f5 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -25,7 +25,6 @@ use tracing::{debug, info, instrument, trace}; use http::Method as HttpMethod; use http::Response as HttpResponse; -use js_int::UInt; use reqwest::header::{HeaderValue, InvalidHeaderValue}; use url::Url; @@ -382,28 +381,33 @@ impl AsyncClient { let mut response = self.send(request).await?; - for (room_id, room) in &response.rooms.join { - let room_id = room_id.to_string(); + for (room_id, room) in &mut response.rooms.join { + let room_id_string = room_id.to_string(); let matrix_room = { let mut client = self.base_client.write().await; for event in &room.state.events { if let EventResult::Ok(e) = event { - client.receive_joined_state_event(&room_id, &e); + client.receive_joined_state_event(&room_id_string, &e); } } - client.get_or_create_room(&room_id).clone() + client.get_or_create_room(&room_id_string).clone() }; - for event in &room.timeline.events { - { + for mut event in &mut room.timeline.events { + let decrypted_event = { let mut client = self.base_client.write().await; - client.receive_joined_timeline_event(&room_id, &event); - } + client + .receive_joined_timeline_event(room_id, &mut event) + .await + }; - let event = Arc::new(event.clone()); + let event = match decrypted_event { + Some(e) => Arc::new(e.clone()), + None => Arc::new(event.clone()), + }; let callbacks = { let mut cb_futures = self.event_callbacks.lock().unwrap(); diff --git a/src/base_client.rs b/src/base_client.rs index 229a1629..8d3a0fae 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -33,9 +33,9 @@ use tokio::sync::Mutex; use crate::crypto::{OlmMachine, OneTimeKeys}; #[cfg(feature = "encryption")] use ruma_client_api::r0::keys::{upload_keys::Response as KeysUploadResponse, DeviceKeys}; +use ruma_identifiers::RoomId; pub type Token = String; -pub type RoomId = String; pub type UserId = String; #[derive(Debug)] @@ -55,7 +55,7 @@ pub struct RoomMember { /// A Matrix rooom. pub struct Room { /// The unique id of the room. - pub room_id: RoomId, + pub room_id: String, /// The mxid of our own user. pub own_user_id: UserId, /// The mxid of the room creator. @@ -191,7 +191,7 @@ pub struct Client { /// The current sync token that should be used for the next sync call. pub sync_token: Option, /// A map of the rooms our user is joined in. - pub joined_rooms: HashMap>>, + pub joined_rooms: HashMap>>, #[cfg(feature = "encryption")] olm: Arc>>, } @@ -267,25 +267,49 @@ impl Client { /// Receive a timeline event for a joined room and update the client state. /// - /// Returns true if the membership list of the room changed, false - /// otherwise. + /// If the event was a encrypted room event and decryption was successful + /// the decrypted event will be returned, otherwise None. /// /// # Arguments /// /// * `room_id` - The unique id of the room the event belongs to. /// /// * `event` - The event that should be handled by the client. - pub fn receive_joined_timeline_event( + pub async fn receive_joined_timeline_event( &mut self, - room_id: &str, - event: &EventResult, - ) -> bool { + room_id: &RoomId, + event: &mut EventResult, + ) -> Option> { match event { EventResult::Ok(e) => { - let mut room = self.get_or_create_room(room_id).write().unwrap(); - room.receive_timeline_event(e) + #[cfg(feature = "encryption")] + let mut decrypted_event = None; + #[cfg(not(feature = "encryption"))] + let decrypted_event = None; + + #[cfg(feature = "encryption")] + { + match e { + RoomEvent::RoomEncrypted(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(); + } + } + _ => (), + } + } + + let mut room = self + .get_or_create_room(&room_id.to_string()) + .write() + .unwrap(); + room.receive_timeline_event(e); + decrypted_event } - _ => false, + _ => None, } } diff --git a/src/crypto/error.rs b/src/crypto/error.rs index b03e8a14..ba46220b 100644 --- a/src/crypto/error.rs +++ b/src/crypto/error.rs @@ -35,6 +35,8 @@ pub enum OlmError { UnsupportedAlgorithm, #[error("the Encrypted message doesn't contain a ciphertext for our device")] MissingCiphertext, + #[error("decryption failed because the session to decrypt the message is missing")] + MissingSession, #[error("can't finish Olm Session operation {0}")] OlmSession(#[from] OlmSessionError), #[error("can't finish Olm Session operation {0}")] diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index b81ec861..f9e42693 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -41,7 +41,8 @@ use ruma_client_api::r0::keys::{ }; use ruma_client_api::r0::sync::sync_events::IncomingResponse as SyncResponse; use ruma_events::{ - room::encrypted::EncryptedEventContent, + collections::all::RoomEvent, + room::encrypted::{EncryptedEvent, EncryptedEventContent}, to_device::{ AnyToDeviceEvent as ToDeviceEvent, ToDeviceEncrypted, ToDeviceForwardedRoomKey, ToDeviceRoomKey, ToDeviceRoomKeyRequest, @@ -623,6 +624,52 @@ impl OlmMachine { } } } + + pub async fn decrypt_room_event( + &self, + event: &EncryptedEvent, + ) -> Result> { + let content = match &event.content { + EncryptedEventContent::MegolmV1AesSha2(c) => c, + _ => return Err(OlmError::UnsupportedAlgorithm), + }; + + let room_id = event.room_id.as_ref().unwrap(); + + let session = self.inbound_group_sessions.get( + &room_id.to_string(), + &content.sender_key, + &content.session_id, + ); + // TODO check if the olm session is wedged and re-request the key. + let session = session.ok_or(OlmError::MissingSession)?; + + let (plaintext, _) = session.decrypt(content.ciphertext.clone())?; + // TODO check the message index. + // TODO check if this is from a verified device. + + let mut decrypted_value = serde_json::from_str::(&plaintext)?; + let decrypted_object = decrypted_value + .as_object_mut() + .ok_or(OlmError::NotAnObject)?; + + let server_ts: u64 = event.origin_server_ts.into(); + + 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()); + + if let Some(unsigned) = &event.unsigned { + decrypted_object.insert("unsigned".to_owned(), unsigned.clone()); + } + + 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...) + + Ok(decrypted_event) + } } #[cfg(test)]