crypto: Add initial support to decrypt megolm encrypted events.

master
Damir Jelić 2020-03-25 15:03:10 +01:00
parent ae4d90057a
commit bb3b59ac37
5 changed files with 104 additions and 23 deletions

View File

@ -1,3 +1,7 @@
all: build
build:
cargo build --features 'encryption sqlite-cryptostore'
test: test:
cargo test --features 'encryption sqlite-cryptostore' cargo test --features 'encryption sqlite-cryptostore'

View File

@ -25,7 +25,6 @@ use tracing::{debug, info, instrument, trace};
use http::Method as HttpMethod; use http::Method as HttpMethod;
use http::Response as HttpResponse; use http::Response as HttpResponse;
use js_int::UInt;
use reqwest::header::{HeaderValue, InvalidHeaderValue}; use reqwest::header::{HeaderValue, InvalidHeaderValue};
use url::Url; use url::Url;
@ -382,28 +381,33 @@ impl AsyncClient {
let mut response = self.send(request).await?; let mut response = self.send(request).await?;
for (room_id, room) in &response.rooms.join { for (room_id, room) in &mut response.rooms.join {
let room_id = room_id.to_string(); let room_id_string = room_id.to_string();
let matrix_room = { let matrix_room = {
let mut client = self.base_client.write().await; let mut client = self.base_client.write().await;
for event in &room.state.events { for event in &room.state.events {
if let EventResult::Ok(e) = event { 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; 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 callbacks = {
let mut cb_futures = self.event_callbacks.lock().unwrap(); let mut cb_futures = self.event_callbacks.lock().unwrap();

View File

@ -33,9 +33,9 @@ use tokio::sync::Mutex;
use crate::crypto::{OlmMachine, OneTimeKeys}; use crate::crypto::{OlmMachine, OneTimeKeys};
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
use ruma_client_api::r0::keys::{upload_keys::Response as KeysUploadResponse, DeviceKeys}; use ruma_client_api::r0::keys::{upload_keys::Response as KeysUploadResponse, DeviceKeys};
use ruma_identifiers::RoomId;
pub type Token = String; pub type Token = String;
pub type RoomId = String;
pub type UserId = String; pub type UserId = String;
#[derive(Debug)] #[derive(Debug)]
@ -55,7 +55,7 @@ pub struct RoomMember {
/// A Matrix rooom. /// A Matrix rooom.
pub struct Room { pub struct Room {
/// The unique id of the room. /// The unique id of the room.
pub room_id: RoomId, pub room_id: String,
/// The mxid of our own user. /// The mxid of our own user.
pub own_user_id: UserId, pub own_user_id: UserId,
/// The mxid of the room creator. /// 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. /// The current sync token that should be used for the next sync call.
pub sync_token: Option<Token>, pub sync_token: Option<Token>,
/// A map of the rooms our user is joined in. /// A map of the rooms our user is joined in.
pub joined_rooms: HashMap<RoomId, Arc<RwLock<Room>>>, pub joined_rooms: HashMap<String, Arc<RwLock<Room>>>,
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
olm: Arc<Mutex<Option<OlmMachine>>>, olm: Arc<Mutex<Option<OlmMachine>>>,
} }
@ -267,25 +267,49 @@ impl Client {
/// Receive a timeline event for a joined room and update the client state. /// Receive a timeline event for a joined room and update the client state.
/// ///
/// Returns true if the membership list of the room changed, false /// If the event was a encrypted room event and decryption was successful
/// otherwise. /// the decrypted event will be returned, otherwise None.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `room_id` - The unique id of the room the event belongs to. /// * `room_id` - The unique id of the room the event belongs to.
/// ///
/// * `event` - The event that should be handled by the client. /// * `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, &mut self,
room_id: &str, room_id: &RoomId,
event: &EventResult<RoomEvent>, event: &mut EventResult<RoomEvent>,
) -> bool { ) -> Option<EventResult<RoomEvent>> {
match event { match event {
EventResult::Ok(e) => { EventResult::Ok(e) => {
let mut room = self.get_or_create_room(room_id).write().unwrap(); #[cfg(feature = "encryption")]
room.receive_timeline_event(e) 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,
} }
} }

View File

@ -35,6 +35,8 @@ pub enum OlmError {
UnsupportedAlgorithm, UnsupportedAlgorithm,
#[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("decryption failed because the session to decrypt the message is missing")]
MissingSession,
#[error("can't finish Olm Session operation {0}")] #[error("can't finish Olm Session operation {0}")]
OlmSession(#[from] OlmSessionError), OlmSession(#[from] OlmSessionError),
#[error("can't finish Olm Session operation {0}")] #[error("can't finish Olm Session operation {0}")]

View File

@ -41,7 +41,8 @@ use ruma_client_api::r0::keys::{
}; };
use ruma_client_api::r0::sync::sync_events::IncomingResponse as SyncResponse; use ruma_client_api::r0::sync::sync_events::IncomingResponse as SyncResponse;
use ruma_events::{ use ruma_events::{
room::encrypted::EncryptedEventContent, collections::all::RoomEvent,
room::encrypted::{EncryptedEvent, EncryptedEventContent},
to_device::{ to_device::{
AnyToDeviceEvent as ToDeviceEvent, ToDeviceEncrypted, ToDeviceForwardedRoomKey, AnyToDeviceEvent as ToDeviceEvent, ToDeviceEncrypted, ToDeviceForwardedRoomKey,
ToDeviceRoomKey, ToDeviceRoomKeyRequest, ToDeviceRoomKey, ToDeviceRoomKeyRequest,
@ -623,6 +624,52 @@ impl OlmMachine {
} }
} }
} }
pub async fn decrypt_room_event(
&self,
event: &EncryptedEvent,
) -> Result<EventResult<RoomEvent>> {
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::<Value>(&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::<EventResult<RoomEvent>>(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)] #[cfg(test)]