From f8cd2310befeebe07f873eb6a52a284e38398665 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 14 Jun 2022 16:16:54 +0200 Subject: [PATCH] feat(crypto-js): Implement `OlmMachine.decryptRoomEvent` & siblings. --- crates/matrix-sdk-crypto-js/Cargo.toml | 1 + crates/matrix-sdk-crypto-js/src/encryption.rs | 26 ++++++ .../matrix-sdk-crypto-js/src/identifiers.rs | 18 ++-- crates/matrix-sdk-crypto-js/src/machine.rs | 40 +++++++-- crates/matrix-sdk-crypto-js/src/responses.rs | 82 ++++++++++++++++++- .../matrix-sdk-crypto-js/src/sync_events.rs | 4 +- .../tests/encryption.test.js | 10 ++- .../tests/machine.test.js | 4 +- 8 files changed, 161 insertions(+), 24 deletions(-) diff --git a/crates/matrix-sdk-crypto-js/Cargo.toml b/crates/matrix-sdk-crypto-js/Cargo.toml index 8a918b105..7c58d489f 100644 --- a/crates/matrix-sdk-crypto-js/Cargo.toml +++ b/crates/matrix-sdk-crypto-js/Cargo.toml @@ -28,6 +28,7 @@ docsrs = [] [dependencies] matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto" } +matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" } ruma = { version = "0.6.2", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "d0e744287a14319c2a9148fef3747548c740fc36", features = ["js"] } wasm-bindgen = "0.2.80" diff --git a/crates/matrix-sdk-crypto-js/src/encryption.rs b/crates/matrix-sdk-crypto-js/src/encryption.rs index 85cbf23b8..4ee6d6200 100644 --- a/crates/matrix-sdk-crypto-js/src/encryption.rs +++ b/crates/matrix-sdk-crypto-js/src/encryption.rs @@ -96,3 +96,29 @@ impl From for EncryptionAlgorithm { } } } + +/// The verification state of the device that sent an event to us. +#[wasm_bindgen] +#[derive(Debug)] +pub enum VerificationState { + /// The device is trusted. + Trusted, + + /// The device is not trusted. + Untrusted, + + /// The device is not known to us. + UnknownDevice, +} + +impl From<&matrix_sdk_common::deserialized_responses::VerificationState> for VerificationState { + fn from(value: &matrix_sdk_common::deserialized_responses::VerificationState) -> Self { + use matrix_sdk_common::deserialized_responses::VerificationState::*; + + match value { + Trusted => Self::Trusted, + Untrusted => Self::Untrusted, + UnknownDevice => Self::UnknownDevice, + } + } +} diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 528ae9f1c..e594d1a6a 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -12,8 +12,8 @@ pub struct UserId { pub(crate) inner: ruma::OwnedUserId, } -impl UserId { - pub(crate) fn new_with(inner: ruma::OwnedUserId) -> Self { +impl From for UserId { + fn from(inner: ruma::OwnedUserId) -> Self { Self { inner } } } @@ -23,7 +23,7 @@ impl UserId { /// Parse/validate and create a new `UserId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> Result { - Ok(Self::new_with(ruma::UserId::parse(id)?)) + Ok(Self::from(ruma::UserId::parse(id)?)) } /// Returns the user's localpart. @@ -66,8 +66,8 @@ pub struct DeviceId { pub(crate) inner: ruma::OwnedDeviceId, } -impl DeviceId { - pub(crate) fn new_with(inner: ruma::OwnedDeviceId) -> Self { +impl From for DeviceId { + fn from(inner: ruma::OwnedDeviceId) -> Self { Self { inner } } } @@ -77,7 +77,7 @@ impl DeviceId { /// Create a new `DeviceId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> DeviceId { - Self::new_with(id.into()) + Self::from(ruma::OwnedDeviceId::from(id)) } /// Return the device ID as a string. @@ -97,8 +97,8 @@ pub struct RoomId { pub(crate) inner: ruma::OwnedRoomId, } -impl RoomId { - pub(crate) fn new_with(inner: ruma::OwnedRoomId) -> Self { +impl From for RoomId { + fn from(inner: ruma::OwnedRoomId) -> Self { Self { inner } } } @@ -108,7 +108,7 @@ impl RoomId { /// Parse/validate and create a new `RoomId`. #[wasm_bindgen(constructor)] pub fn new(id: &str) -> Result { - Ok(Self::new_with(ruma::RoomId::parse(id)?)) + Ok(Self::from(ruma::RoomId::parse(id)?)) } /// Returns the user's localpart. diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index ec0c40525..1dafec33d 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -3,7 +3,10 @@ use std::collections::BTreeMap; use js_sys::{Array, Map, Promise, Set}; -use ruma::{DeviceKeyAlgorithm, OwnedTransactionId, UInt}; +use ruma::{ + events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm, + OwnedTransactionId, UInt, +}; use serde_json::Value as JsonValue; use wasm_bindgen::prelude::*; @@ -51,13 +54,13 @@ impl OlmMachine { /// The unique user ID that owns this `OlmMachine` instance. #[wasm_bindgen(getter, js_name = "userId")] pub fn user_id(&self) -> identifiers::UserId { - identifiers::UserId::new_with(self.inner.user_id().to_owned()) + identifiers::UserId::from(self.inner.user_id().to_owned()) } /// The unique device ID that identifies this `OlmMachine`. #[wasm_bindgen(getter, js_name = "deviceId")] pub fn device_id(&self) -> identifiers::DeviceId { - identifiers::DeviceId::new_with(self.inner.device_id().to_owned()) + identifiers::DeviceId::from(self.inner.device_id().to_owned()) } /// Get the public parts of our Olm identity keys. @@ -81,11 +84,9 @@ impl OlmMachine { pub fn tracked_users(&self) -> Set { let set = Set::new(&JsValue::UNDEFINED); - self.inner.tracked_users().into_iter().map(identifiers::UserId::new_with).for_each( - |user| { - set.add(&user.into()); - }, - ); + self.inner.tracked_users().into_iter().map(identifiers::UserId::from).for_each(|user| { + set.add(&user.into()); + }); set } @@ -261,6 +262,29 @@ impl OlmMachine { })) } + /// Decrypt an event from a room timeline. + /// + /// # Arguments + /// + /// * `event`, the event that should be decrypted. + /// * `room_id`, the ID of the room where the event was sent to. + #[wasm_bindgen(js_name = "decryptRoomEvent")] + pub fn decrypt_room_event( + &self, + event: &str, + room_id: &identifiers::RoomId, + ) -> Result { + let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?; + let room_id = room_id.inner.clone(); + let me = self.inner.clone(); + + Ok(future_to_promise(async move { + let room_event = me.decrypt_room_event(&event, room_id.as_ref()).await?; + + Ok(responses::DecryptedRoomEvent::from(room_event)) + })) + } + /// Invalidate the currently active outbound group session for the /// given room. /// diff --git a/crates/matrix-sdk-crypto-js/src/responses.rs b/crates/matrix-sdk-crypto-js/src/responses.rs index 080347dbb..e7baf986b 100644 --- a/crates/matrix-sdk-crypto-js/src/responses.rs +++ b/crates/matrix-sdk-crypto-js/src/responses.rs @@ -1,5 +1,7 @@ //! Types related to responses. +use js_sys::{Array, JsString}; +use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo}; use matrix_sdk_crypto::IncomingResponse; pub(crate) use ruma::api::client::{ backup::add_backup_keys::v3::Response as KeysBackupResponse, @@ -14,7 +16,7 @@ pub(crate) use ruma::api::client::{ use ruma::api::IncomingResponse as RumaIncomingResponse; use wasm_bindgen::prelude::*; -use crate::requests::RequestType; +use crate::{encryption, identifiers, requests::RequestType}; pub(crate) fn response_from_string(body: &str) -> http::Result>> { http::Response::builder().status(200).body(body.as_bytes().to_vec()) @@ -126,3 +128,81 @@ impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> { } } } + +/// A decrypted room event. +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug)] +pub struct DecryptedRoomEvent { + /// The JSON-encoded decrypted event. + #[wasm_bindgen(readonly)] + pub event: JsString, + + encryption_info: Option, +} + +#[wasm_bindgen] +impl DecryptedRoomEvent { + /// The user ID of the event sender, note this is untrusted data + /// unless the `verification_state` is as well trusted. + #[wasm_bindgen(getter)] + pub fn sender(&self) -> Option { + Some(identifiers::UserId::from(self.encryption_info.as_ref()?.sender.clone())) + } + + /// The device ID of the device that sent us the event, note this + /// is untrusted data unless `verification_state` is as well + /// trusted. + #[wasm_bindgen(getter, js_name = "senderDevice")] + pub fn sender_device(&self) -> Option { + Some(identifiers::DeviceId::from(self.encryption_info.as_ref()?.sender_device.clone())) + } + + /// The Curve25519 key of the device that created the megolm + /// decryption key originally. + #[wasm_bindgen(getter, js_name = "senderCurve25519Key")] + pub fn sender_curve25519_key(&self) -> Option { + Some(match &self.encryption_info.as_ref()?.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone().into(), + }) + } + + /// The signing Ed25519 key that have created the megolm key that + /// was used to decrypt this session. + #[wasm_bindgen(getter, js_name = "senderClaimedEd25519Key")] + pub fn sender_claimed_ed25519_key(&self) -> Option { + match &self.encryption_info.as_ref()?.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => { + sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned().map(Into::into) + } + } + } + + /// Chain of Curve25519 keys through which this session was + /// forwarded, via `m.forwarded_room_key` events. + #[wasm_bindgen(getter, js_name = "forwardingCurve25519KeyChain")] + pub fn forwarding_curve25519_key_chain(&self) -> Option { + Some(match &self.encryption_info.as_ref()?.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => { + forwarding_curve25519_key_chain.iter().map(JsValue::from).collect() + } + }) + } + + /// The verification state of the device that sent us the event, + /// note this is the state of the device at the time of + /// decryption. It may change in the future if a device gets + /// verified or deleted. + #[wasm_bindgen(getter, js_name = "verificationState")] + pub fn verification_state(&self) -> Option { + Some((&self.encryption_info.as_ref()?.verification_state).into()) + } +} + +impl From for DecryptedRoomEvent { + fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self { + Self { + event: value.event.json().get().to_owned().into(), + encryption_info: value.encryption_info, + } + } +} diff --git a/crates/matrix-sdk-crypto-js/src/sync_events.rs b/crates/matrix-sdk-crypto-js/src/sync_events.rs index f4e40c153..d58906eb2 100644 --- a/crates/matrix-sdk-crypto-js/src/sync_events.rs +++ b/crates/matrix-sdk-crypto-js/src/sync_events.rs @@ -50,7 +50,7 @@ impl DeviceLists { self.inner .changed .iter() - .map(|user| identifiers::UserId::new_with(user.clone())) + .map(|user| identifiers::UserId::from(user.clone())) .map(JsValue::from) .collect() } @@ -62,7 +62,7 @@ impl DeviceLists { self.inner .left .iter() - .map(|user| identifiers::UserId::new_with(user.clone())) + .map(|user| identifiers::UserId::from(user.clone())) .map(JsValue::from) .collect() } diff --git a/crates/matrix-sdk-crypto-js/tests/encryption.test.js b/crates/matrix-sdk-crypto-js/tests/encryption.test.js index 4eeaf2c31..75374822b 100644 --- a/crates/matrix-sdk-crypto-js/tests/encryption.test.js +++ b/crates/matrix-sdk-crypto-js/tests/encryption.test.js @@ -1,4 +1,4 @@ -const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility } = require('../pkg/matrix_sdk_crypto'); +const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto'); describe('EncryptionAlgorithm', () => { test('has the correct variant values', () => { @@ -26,3 +26,11 @@ describe(EncryptionSettings.name, () => { expect(() => { es.historyVisibility = 42 }).toThrow(); }); }); + +describe('VerificationState', () => { + test('has the correct variant values', () => { + expect(VerificationState.Trusted).toStrictEqual(0); + expect(VerificationState.Untrusted).toStrictEqual(1); + expect(VerificationState.UnknownDevice).toStrictEqual(2); + }); +}); diff --git a/crates/matrix-sdk-crypto-js/tests/machine.test.js b/crates/matrix-sdk-crypto-js/tests/machine.test.js index a9ddb8c57..7a851dc4c 100644 --- a/crates/matrix-sdk-crypto-js/tests/machine.test.js +++ b/crates/matrix-sdk-crypto-js/tests/machine.test.js @@ -1,4 +1,4 @@ -const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings } = require('../pkg/matrix_sdk_crypto'); +const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState } = require('../pkg/matrix_sdk_crypto'); describe(OlmMachine.name, () => { test('can be instantiated with the async initializer', async () => { @@ -310,7 +310,6 @@ describe(OlmMachine.name, () => { expect(encrypted.session_id).toBeDefined(); }); - /* test('can decrypt an event', async () => { const decrypted = await m.decryptRoomEvent( JSON.stringify({ @@ -338,6 +337,5 @@ describe(OlmMachine.name, () => { expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0); expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted); }); - */ }); });