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:
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::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();

View File

@ -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<Token>,
/// 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")]
olm: Arc<Mutex<Option<OlmMachine>>>,
}
@ -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<RoomEvent>,
) -> bool {
room_id: &RoomId,
event: &mut EventResult<RoomEvent>,
) -> Option<EventResult<RoomEvent>> {
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,
}
}

View File

@ -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}")]

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_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<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)]