crypto: Add initial support to decrypt megolm encrypted events.
parent
ae4d90057a
commit
bb3b59ac37
4
Makefile
4
Makefile
|
@ -1,3 +1,7 @@
|
|||
all: build
|
||||
|
||||
build:
|
||||
cargo build --features 'encryption sqlite-cryptostore'
|
||||
test:
|
||||
cargo test --features 'encryption sqlite-cryptostore'
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}")]
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in New Issue