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:
|
test:
|
||||||
cargo test --features 'encryption sqlite-cryptostore'
|
cargo test --features 'encryption sqlite-cryptostore'
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}")]
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in New Issue