diff --git a/CHANGELOG.md b/CHANGELOG.md index ea97c6e07..868413aff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # UNRELEASED +- Update matrix-rusk-sdk to `0f73ffde`, which includes: + + - feat(crypto): Add EncryptionInfo to Decrypted to-device variant ([#5074](https://github.com/matrix-org/matrix-rust-sdk/pull/5074)) + - Add variants for plain text and encrypted to-device events ([#4935](https://github.com/matrix-org/matrix-rust-sdk/pull/4935)) + +- **BREAKING**: `OlmMachine.receiveSyncChanges` now returns a list of + `ProcessedToDeviceEvent` instead of a JSON-encoded list of JSON-encoded events. + This allows making the difference between an event that was sent in clear and + the same event successfully decrypted. + # matrix-sdk-crypto-wasm v14.2.0 - Log warnings when we fail to parse a backed-up room key @@ -7,7 +17,7 @@ # matrix-sdk-crypto-wasm v14.1.0 -- Update matrix-rusk-sdk to `0.11.0`, which includees: +- Update matrix-rusk-sdk to `0.11.0`, which includes: - Add support for the shared history flag defined in [MSC3061](https://github.com/matrix-org/matrix-spec-proposals/pull/3061). diff --git a/Cargo.lock b/Cargo.lock index c4a8c446e..ad127a326 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1113,10 +1113,8 @@ dependencies = [ [[package]] name = "matrix-sdk-common" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75aa988a96e4a85e2b854f4f6487d31f1833e8abf3eebc1b8c13274a68dacf45" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=0f73ffde689fe07e8989068ecc59b140694ae5ed#0f73ffde689fe07e8989068ecc59b140694ae5ed" dependencies = [ - "async-trait", "eyeball-im", "futures-core", "futures-util", @@ -1137,8 +1135,7 @@ dependencies = [ [[package]] name = "matrix-sdk-crypto" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe2a8a811f9809f37c360f7005350e76661cb2bed2624b87b8d772c444bf572a" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=0f73ffde689fe07e8989068ecc59b140694ae5ed#0f73ffde689fe07e8989068ecc59b140694ae5ed" dependencies = [ "aes", "aquamarine", @@ -1206,8 +1203,7 @@ dependencies = [ [[package]] name = "matrix-sdk-indexeddb" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63da8893fc9062f4b7874f766fd5dcf048f8ea456b08ba89320cc68926b1112e" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=0f73ffde689fe07e8989068ecc59b140694ae5ed#0f73ffde689fe07e8989068ecc59b140694ae5ed" dependencies = [ "anyhow", "async-trait", @@ -1235,8 +1231,7 @@ dependencies = [ [[package]] name = "matrix-sdk-qrcode" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fc0b2a90a9c986d53fb772e49ec1f135748e2b4a7d38b253ee45f97ecb818e" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=0f73ffde689fe07e8989068ecc59b140694ae5ed#0f73ffde689fe07e8989068ecc59b140694ae5ed" dependencies = [ "byteorder", "qrcode", @@ -1248,8 +1243,7 @@ dependencies = [ [[package]] name = "matrix-sdk-store-encryption" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b14e1db0b31db81c5d20c20d24c84421893491da30e8e0218e991c3a350188e" +source = "git+https://github.com/matrix-org/matrix-rust-sdk?rev=0f73ffde689fe07e8989068ecc59b140694ae5ed#0f73ffde689fe07e8989068ecc59b140694ae5ed" dependencies = [ "base64", "blake3", @@ -1640,9 +1634,9 @@ dependencies = [ [[package]] name = "ruma" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fdaae631940eda62844a8a3026aba2ba84c22588c888ebec44861ba4d0c18" +checksum = "d910a9b75cbf0e88f74295997c1a41c3ab7a117879a029c72db815192c167a0d" dependencies = [ "assign", "js_int", @@ -1650,14 +1644,15 @@ dependencies = [ "ruma-client-api", "ruma-common", "ruma-events", + "ruma-html", "web-time", ] [[package]] name = "ruma-client-api" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9a89ac03a0f4451f946ed9aed6fdd16ef5a78a3a2849e87af4b2474a176b2fb" +checksum = "09cc4ff88a70a3d1e7a2c5b51cca7499cb889b42687608ab664b9a216c49314d" dependencies = [ "as_variant", "assign", diff --git a/Cargo.toml b/Cargo.toml index b75b77458..fb51047bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,9 +65,9 @@ futures-util = "0.3.27" getrandom = { version = "0.3.0", features = ["wasm_js"] } http = "1.1.0" js-sys = "0.3.49" -matrix-sdk-common = { version = "0.11.0", features = ["js"] } -matrix-sdk-indexeddb = { version = "0.11.0", default-features = false, features = ["e2e-encryption"] } -matrix-sdk-qrcode = { version = "0.11.0", optional = true } +matrix-sdk-common = { git = "https://github.com/matrix-org/matrix-rust-sdk", rev = "0f73ffde689fe07e8989068ecc59b140694ae5ed", features = ["js"] } +matrix-sdk-indexeddb = { git = "https://github.com/matrix-org/matrix-rust-sdk", rev = "0f73ffde689fe07e8989068ecc59b140694ae5ed", default-features = false, features = ["e2e-encryption"] } +matrix-sdk-qrcode = { git = "https://github.com/matrix-org/matrix-rust-sdk", rev = "0f73ffde689fe07e8989068ecc59b140694ae5ed", optional = true } serde = "1.0.91" serde_json = "1.0.91" serde-wasm-bindgen = "0.6.5" @@ -83,7 +83,8 @@ wasm-bindgen-test = "0.3.37" vergen-gitcl = { version = "1.0.0", features = ["build"] } [dependencies.matrix-sdk-crypto] -version = "0.11.0" +git = "https://github.com/matrix-org/matrix-rust-sdk" +rev = "0f73ffde689fe07e8989068ecc59b140694ae5ed" default-features = false features = ["js", "automatic-room-key-forwarding"] diff --git a/index.d.ts b/index.d.ts index 12b211ed7..e5466cf45 100644 --- a/index.d.ts +++ b/index.d.ts @@ -61,6 +61,13 @@ declare module "./pkg/matrix_sdk_crypto_wasm.js" { | RoomMessageRequest | KeysBackupRequest; + /** The types returned by {@link OlmMachine.receiveSyncChanges}. */ + type ProcessedToDeviceEvent = + | DecryptedToDeviceEvent + | PlainTextToDeviceEvent + | InvalidToDeviceEvent + | UTDToDeviceEvent; + interface OlmMachine { trackedUsers(): Promise>; updateTrackedUsers(users: UserId[]): Promise; @@ -69,7 +76,7 @@ declare module "./pkg/matrix_sdk_crypto_wasm.js" { changed_devices: DeviceLists, one_time_keys_counts: Map, unused_fallback_keys?: Set | null, - ): Promise; + ): Promise>; outgoingRequests(): Promise>; markRequestAsSent(request_id: string, request_type: RequestType, response: string): Promise; encryptRoomEvent(room_id: RoomId, event_type: string, content: string): Promise; diff --git a/src/attachment.rs b/src/attachment.rs index 7bf0b8cda..675503ac1 100644 --- a/src/attachment.rs +++ b/src/attachment.rs @@ -102,7 +102,7 @@ impl EncryptedAttachment { self.encrypted_data.clone() } - /// Return the media encryption info as a JSON-encoded string. The + /// Return the media encryption info as a JSON-encoded object. The /// structure is fully valid. /// /// If the media encryption info have been consumed already, it diff --git a/src/machine.rs b/src/machine.rs index d137c3e63..69f9e2794 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -42,7 +42,10 @@ use crate::{ store, store::{RoomKeyInfo, RoomKeyWithheldInfo, StoreHandle}, sync_events, - types::{self, RoomKeyImportResult, RoomSettings, SignatureVerification}, + types::{ + self, processed_to_device_event_to_js_value, RoomKeyImportResult, RoomSettings, + SignatureVerification, + }, verification, vodozemac, }; @@ -298,7 +301,11 @@ impl OlmMachine { /// /// # Returns /// - /// A list of JSON strings, containing the decrypted to-device events. + /// This returns a list of values, each of which can be any of: + /// * {@link DecryptedToDeviceEvent} + /// * {@link PlainTextToDeviceEvent} + /// * {@link UTDToDeviceEvent} + /// * {@link InvalidToDeviceEvent} #[wasm_bindgen(js_name = "receiveSyncChanges")] pub fn receive_sync_changes( &self, @@ -341,7 +348,7 @@ impl OlmMachine { // we discard the list of updated room keys in the result; JS applications are // expected to use register_room_key_updated_callback to receive updated room // keys. - let (decrypted_to_device_events, _) = me + let (processed_to_device_events, _) = me .receive_sync_changes(EncryptionSyncChanges { to_device_events, changed_devices: &changed_devices, @@ -353,20 +360,23 @@ impl OlmMachine { }) .await?; - Ok(serde_json::to_string(&decrypted_to_device_events)?) + Ok(processed_to_device_events + .into_iter() + .map(processed_to_device_event_to_js_value) + .collect::>()) })) } /// Get the outgoing requests that need to be sent out. /// /// This returns a list of values, each of which can be any of: - /// * {@link KeysUploadRequest}, - /// * {@link KeysQueryRequest}, - /// * {@link KeysClaimRequest}, - /// * {@link ToDeviceRequest}, - /// * {@link SignatureUploadRequest}, - /// * {@link RoomMessageRequest}, or - /// * {@link KeysBackupRequest}. + /// * {@link KeysUploadRequest} + /// * {@link KeysQueryRequest} + /// * {@link KeysClaimRequest} + /// * {@link ToDeviceRequest} + /// * {@link SignatureUploadRequest} + /// * {@link RoomMessageRequest} or + /// * {@link KeysBackupRequest} /// /// Those requests need to be sent out to the server and the /// responses need to be passed back to the state machine @@ -535,7 +545,8 @@ impl OlmMachine { Ok(future_to_promise(async move { let encryption_info = me.get_room_event_encryption_info(&event, room_id.as_ref()).await?; - Ok(responses::EncryptionInfo::from(encryption_info)) + Ok(responses::EncryptionInfo::try_from(encryption_info.as_ref().clone()) + .expect("Room messages are megolm encrypted")) })) } diff --git a/src/requests.rs b/src/requests.rs index 55963381b..1aaccbe01 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -42,7 +42,7 @@ pub struct KeysUploadRequest { #[wasm_bindgen(readonly)] pub id: JsString, - /// A JSON-encoded string containing the rest of the payload: `device_keys`, + /// A JSON-encoded object containing the rest of the payload: `device_keys`, /// `one_time_keys`, `fallback_keys`. /// /// It represents the body of the HTTP request. @@ -78,7 +78,7 @@ pub struct KeysQueryRequest { #[wasm_bindgen(readonly)] pub id: JsString, - /// A JSON-encoded string containing the rest of the payload: `timeout`, + /// A JSON-encoded object containing the rest of the payload: `timeout`, /// `device_keys`, `token`. /// /// It represents the body of the HTTP request. @@ -115,7 +115,7 @@ pub struct KeysClaimRequest { #[wasm_bindgen(readonly)] pub id: JsString, - /// A JSON-encoded string containing the rest of the payload: `timeout`, + /// A JSON-encoded object containing the rest of the payload: `timeout`, /// `one_time_keys`. /// /// It represents the body of the HTTP request. @@ -163,7 +163,7 @@ pub struct ToDeviceRequest { #[wasm_bindgen(readonly)] pub txn_id: JsString, - /// A JSON-encoded string containing the rest of the payload: `messages`. + /// A JSON-encoded object containing the rest of the payload: `messages`. /// /// It represents the body of the HTTP request. #[wasm_bindgen(readonly)] @@ -206,7 +206,7 @@ pub struct SignatureUploadRequest { #[wasm_bindgen(readonly)] pub id: Option, - /// A JSON-encoded string containing the payload of the request + /// A JSON-encoded object containing the payload of the request /// /// It represents the body of the HTTP request. #[wasm_bindgen(readonly, js_name = "body")] @@ -255,7 +255,7 @@ pub struct RoomMessageRequest { #[wasm_bindgen(readonly)] pub event_type: JsString, - /// A JSON-encoded string containing the message's content. + /// A JSON-encoded object containing the message's content. #[wasm_bindgen(readonly, js_name = "body")] pub content: JsString, } @@ -292,7 +292,7 @@ pub struct KeysBackupRequest { #[wasm_bindgen(readonly)] pub id: JsString, - /// A JSON-encoded string containing the rest of the payload: `rooms`. + /// A JSON-encoded object containing the rest of the payload: `rooms`. /// /// It represents the body of the HTTP request. #[wasm_bindgen(readonly)] @@ -513,6 +513,7 @@ pub fn outgoing_request_to_js_value( } AnyOutgoingRequest::RoomMessage(request) => { + let request = request.as_ref(); JsValue::from(RoomMessageRequest::try_from((request_id, request))?) } }) @@ -552,7 +553,7 @@ pub enum RequestType { #[wasm_bindgen(getter_with_clone)] #[derive(Debug, Clone)] pub struct UploadSigningKeysRequest { - /// A JSON-encoded string containing the rest of the payload: `master_key`, + /// A JSON-encoded object containing the rest of the payload: `master_key`, /// `self_signing_key`, `user_signing_key`. /// /// It represents the body of the HTTP request. @@ -600,7 +601,7 @@ impl TryFrom<&OriginalUploadSigningKeysRequest> for UploadSigningKeysRequest { #[derive(Debug)] /// A request that will upload a dehydrated device to the server. pub struct PutDehydratedDeviceRequest { - /// A JSON-encoded string containing the rest of the payload: `rooms`. + /// A JSON-encoded object containing the rest of the payload: `rooms`. /// /// It represents the body of the HTTP request. #[wasm_bindgen(readonly)] diff --git a/src/responses.rs b/src/responses.rs index 8bf65cbb0..174e8fee7 100644 --- a/src/responses.rs +++ b/src/responses.rs @@ -12,13 +12,13 @@ pub(crate) use matrix_sdk_common::ruma::api::client::{ to_device::send_event_to_device::v3::Response as ToDeviceResponse, }; use matrix_sdk_common::{ - deserialized_responses::AlgorithmInfo, + deserialized_responses::{AlgorithmInfo, VerificationState}, ruma::{self, api::IncomingResponse as RumaIncomingResponse}, }; use matrix_sdk_crypto::types::requests::AnyIncomingResponse; use wasm_bindgen::prelude::*; -use crate::{encryption, identifiers, impl_from_to_inner, 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()) @@ -216,19 +216,34 @@ impl From for Decrypte fn from(value: matrix_sdk_common::deserialized_responses::TimelineEvent) -> Self { Self { event: value.raw().json().get().to_owned().into(), - encryption_info: value.encryption_info().map(|e| e.clone().into()), + encryption_info: value.encryption_info().map(|e| { + e.as_ref().clone().try_into().expect("Timeline events are megolm encrypted") + }), } } } /// Struct containing information on how an event was decrypted. #[wasm_bindgen()] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EncryptionInfo { inner: matrix_sdk_common::deserialized_responses::EncryptionInfo, } -impl_from_to_inner!(matrix_sdk_common::deserialized_responses::EncryptionInfo => EncryptionInfo); +impl TryFrom for EncryptionInfo { + type Error = JsError; + + fn try_from( + value: matrix_sdk_common::deserialized_responses::EncryptionInfo, + ) -> Result { + match value.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { .. } => Ok(Self { inner: value }), + AlgorithmInfo::OlmV1Curve25519AesSha2 { .. } => Err(JsError::new( + "AlgorithmInfo::OlmV1Curve25519AesSha2 is not applicable for megolm EncryptionInfo", + )), + } + } +} #[wasm_bindgen()] impl EncryptionInfo { @@ -251,9 +266,12 @@ impl EncryptionInfo { /// decryption key originally. #[wasm_bindgen(getter, js_name = "senderCurve25519Key")] pub fn sender_curve25519_key(&self) -> Option { - Some(match &self.inner.algorithm_info { - AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone().into(), - }) + match &self.inner.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => { + Some(curve25519_key.clone().into()) + } + AlgorithmInfo::OlmV1Curve25519AesSha2 { .. } => None, + } } /// The signing Ed25519 key that created the megolm key that @@ -264,6 +282,8 @@ impl EncryptionInfo { AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => { sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned().map(Into::into) } + // Not applicable for olm info + AlgorithmInfo::OlmV1Curve25519AesSha2 { .. } => None, } } @@ -290,3 +310,68 @@ impl EncryptionInfo { .into() } } + +/// Struct containing information on how a to-device message was decrypted. +#[wasm_bindgen()] +#[derive(Debug, Clone)] +pub struct OlmEncryptionInfo { + inner: matrix_sdk_common::deserialized_responses::EncryptionInfo, +} + +impl TryFrom for OlmEncryptionInfo { + type Error = JsError; + + fn try_from( + value: matrix_sdk_common::deserialized_responses::EncryptionInfo, + ) -> Result { + match value.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { .. } => Err(JsError::new( + "AlgorithmInfo::MegolmV1AesSha2 is not applicable for OlmEncryptionInfo", + )), + AlgorithmInfo::OlmV1Curve25519AesSha2 { .. } => Ok(Self { inner: value }), + } + } +} + +#[wasm_bindgen()] +impl OlmEncryptionInfo { + /// The user ID of the to-device message sender. Note this is untrusted data + /// unless `isSenderVerified` is also true. + #[wasm_bindgen(getter)] + pub fn sender(&self) -> identifiers::UserId { + identifiers::UserId::from(self.inner.sender.clone()) + } + + /// The device ID of the device that sent us the to-device message. + /// + /// Could be None in the case where the to-device message sender checks are + /// delayed (for room keys for example). For custom to-device messages there is + /// no delay, so this will not be `undefined` (the decryption would fail if the + /// sender device keys cannot be found). + /// + /// Note this is untrusted data unless `isSenderVerified` is also true. + #[wasm_bindgen(getter, js_name = "senderDevice")] + pub fn sender_device(&self) -> Option { + Some(self.inner.sender_device.as_ref()?.clone().into()) + } + + /// The base64-encoded public Curve25519 key of the device that encrypted the message. + #[wasm_bindgen(getter, js_name = "senderCurve25519Key")] + pub fn sender_curve25519_key(&self) -> Option { + match &self.inner.algorithm_info { + AlgorithmInfo::MegolmV1AesSha2 { .. } => { + panic!("MegolmV1AesSha2 variant cannot be converted to OlmEncryptionInfo") + } + AlgorithmInfo::OlmV1Curve25519AesSha2 { curve25519_public_key_base64 } => { + Some(curve25519_public_key_base64.clone().into()) + } + } + } + + /// Returns whether the sender device is in a verified state. + /// This reflects the state at the time of decryption. + #[wasm_bindgen(js_name = "isSenderVerified")] + pub fn is_sender_verified(&self) -> bool { + matches!(&self.inner.verification_state, VerificationState::Verified) + } +} diff --git a/src/types.rs b/src/types.rs index 9117b9fc0..e28f319c0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -16,6 +16,7 @@ use crate::{ encryption::EncryptionAlgorithm, identifiers::{DeviceKeyId, UserId}, impl_from_to_inner, + responses::OlmEncryptionInfo, vodozemac::Ed25519Signature, }; @@ -362,3 +363,170 @@ impl From<&RoomSettings> for matrix_sdk_crypto::store::RoomSettings { } } } + +/// The type of a {@link ProcessedToDeviceEvent}. +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub enum ProcessedToDeviceEventType { + /// A successfully-decrypted encrypted to-device message. + Decrypted, + + /// An encrypted to-device message which could not be decrypted. + UnableToDecrypt, + + /// An unencrypted to-device message (sent in clear). + PlainText, + + /// An invalid to-device message that was ignored because it is missing some + /// required information to be processed (like no event `type` for + /// example) + Invalid, +} + +/// Represent a to-device event after it has been decrypted by {@link +/// OlmMachine#receiveSyncChanges}. +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct DecryptedToDeviceEvent { + /// A JSON-encoded object containing the decrypted event, as if it had been + /// sent in the clear. + /// + /// Typically contains properties `type`, `sender` and `content`. + /// + /// (For room keys or secrets, some part of the content might have been zeroized). + #[wasm_bindgen(readonly, js_name = "decryptedRawEvent")] + pub decrypted_raw_event: JsString, + + #[wasm_bindgen(readonly, js_name = "encryptionInfo")] + /// The olm encryption information for the event. + pub encryption_info: OlmEncryptionInfo, +} + +#[wasm_bindgen] +impl DecryptedToDeviceEvent { + /// The type of processed to-device event. Always {@link + /// ProcessedToDeviceEventType.Decrypted} for this type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn processed_type(&self) -> ProcessedToDeviceEventType { + ProcessedToDeviceEventType::Decrypted + } + + /// Create a new `DecryptedToDeviceEvent`. + #[wasm_bindgen(constructor)] + pub fn new(decrypted_raw_event: JsString, encryption_info: OlmEncryptionInfo) -> Self { + Self { decrypted_raw_event, encryption_info } + } +} + +/// Represent a ToDevice event sent in clear after it has been processed {@link +/// OlmMachine#receiveSyncChanges}. +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct PlainTextToDeviceEvent { + /// A JSON-encoded object containing the Matrix to-device message with + /// `type`, `sender` and `content` fields. + #[wasm_bindgen(readonly, js_name = "rawEvent")] + pub raw_event: JsString, +} + +#[wasm_bindgen] +impl PlainTextToDeviceEvent { + /// The type of processed to-device event. Always {@link + /// ProcessedToDeviceEventType.PlainText} for this type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn processed_type(&self) -> ProcessedToDeviceEventType { + ProcessedToDeviceEventType::PlainText + } + + /// Create a new `PlainTextToDeviceEvent`. + #[wasm_bindgen(constructor)] + pub fn new(raw_event: JsString) -> Self { + Self { raw_event } + } +} + +/// Represent a ToDevice event that could not be decrypted after it has been +/// processed. +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct UTDToDeviceEvent { + /// A JSON-encoded object containing the original message of type + /// `m.room.encrypted` that failed to be decrypted. + #[wasm_bindgen(readonly, js_name = "wireEvent")] + pub wire_event: JsString, + // TODO: Add some OlmError in the future +} + +#[wasm_bindgen] +impl UTDToDeviceEvent { + /// The type of processed to-device event. Always {@link + /// ProcessedToDeviceEventType.UnableToDecrypt} for this type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn processed_type(&self) -> ProcessedToDeviceEventType { + ProcessedToDeviceEventType::UnableToDecrypt + } + + /// Create a new `UTDToDeviceEvent`. + #[wasm_bindgen(constructor)] + pub fn new(wire_event: JsString) -> Self { + Self { wire_event } + } +} + +/// Represent an invalid ToDevice event that was ignored because it is missing +/// some mandatory fields for example. +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct InvalidToDeviceEvent { + /// A JSON-encoded object containing the original message as received from + /// sync. + #[wasm_bindgen(readonly, js_name = "wireEvent")] + pub wire_event: JsString, + // TODO: Add some error information here? +} + +#[wasm_bindgen] +impl InvalidToDeviceEvent { + /// The type of processed to-device event. Always {@link + /// ProcessedToDeviceEventType.Invalid} for this type. + #[wasm_bindgen(getter, js_name = "type")] + pub fn processed_type(&self) -> ProcessedToDeviceEventType { + ProcessedToDeviceEventType::Invalid + } + + /// Create a new `InvalidToDeviceEvent`. + #[wasm_bindgen(constructor)] + pub fn new(wire_event: JsString) -> Self { + Self { wire_event } + } +} + +/// Convert an `ProcessedToDeviceEvent` into a `JsValue`, ready to return to +/// JavaScript. +/// +/// JavaScript has no complex enums like Rust. To return structs of +/// different types, we have no choice that hiding everything behind a +/// `JsValue`. +pub fn processed_to_device_event_to_js_value( + processed_to_device_event: matrix_sdk_crypto::types::ProcessedToDeviceEvent, +) -> JsValue { + match processed_to_device_event { + matrix_sdk_crypto::types::ProcessedToDeviceEvent::Decrypted { raw, encryption_info } => { + JsValue::from(DecryptedToDeviceEvent::new( + raw.json().get().into(), + encryption_info + .try_into() + .expect("to-device encryption info will always convert to OlmEncryptionInfo"), + )) + } + matrix_sdk_crypto::types::ProcessedToDeviceEvent::UnableToDecrypt(utd) => { + JsValue::from(UTDToDeviceEvent::new(utd.json().get().into())) + } + matrix_sdk_crypto::types::ProcessedToDeviceEvent::PlainText(plain) => { + JsValue::from(PlainTextToDeviceEvent::new(plain.json().get().into())) + } + matrix_sdk_crypto::types::ProcessedToDeviceEvent::Invalid(invalid) => { + JsValue::from(InvalidToDeviceEvent::new(invalid.json().get().into())) + } + } +} diff --git a/src/verification.rs b/src/verification.rs index a12d52422..02ffeedc1 100644 --- a/src/verification.rs +++ b/src/verification.rs @@ -104,10 +104,10 @@ impl TryFrom for JsValue { use matrix_sdk_crypto::Verification::*; Ok(match verification.0 { - SasV1(sas) => JsValue::from(Sas { inner: sas }), + SasV1(sas) => JsValue::from(Sas { inner: *sas }), #[cfg(feature = "qrcode")] - QrV1(qr) => JsValue::from(Qr { inner: qr }), + QrV1(qr) => JsValue::from(Qr { inner: *qr }), _ => { return Err(JsError::new( @@ -1094,7 +1094,7 @@ impl TryFrom for JsValue { } InRoom(request) => { - JsValue::from(requests::RoomMessageRequest::try_from((request_id, &request))?) + JsValue::from(requests::RoomMessageRequest::try_from((request_id, &*request))?) } }) } diff --git a/tests/helper.js b/tests/helper.js index 61645df3d..7bfeb8746 100644 --- a/tests/helper.js +++ b/tests/helper.js @@ -16,8 +16,11 @@ async function addMachineToMachine(machineToAdd, machine) { const oneTimeKeyCounts = new Map(); const unusedFallbackKeys = new Set(); - const receiveSyncChanges = JSON.parse( - await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys), + const receiveSyncChanges = await machineToAdd.receiveSyncChanges( + toDeviceEvents, + changedDevices, + oneTimeKeyCounts, + unusedFallbackKeys, ); expect(receiveSyncChanges).toEqual([]); diff --git a/tests/machine.test.ts b/tests/machine.test.ts index edf6a9d5c..6c67055e4 100644 --- a/tests/machine.test.ts +++ b/tests/machine.test.ts @@ -7,11 +7,13 @@ import { DeviceId, DeviceKeyId, DeviceLists, + DecryptedToDeviceEvent, EncryptionAlgorithm, EncryptionSettings, EventId, getVersions, InboundGroupSession, + InvalidToDeviceEvent, KeysBackupRequest, KeysClaimRequest, KeysQueryRequest, @@ -20,6 +22,8 @@ import { MegolmDecryptionError, OlmMachine, OwnUserIdentity, + PlainTextToDeviceEvent, + ProcessedToDeviceEventType, RequestType, RoomId, RoomKeyWithheldInfo, @@ -33,6 +37,7 @@ import { ToDeviceRequest, TrustRequirement, UserId, + UTDToDeviceEvent, OtherUserIdentity, VerificationRequest, Versions, @@ -306,8 +311,11 @@ describe(OlmMachine.name, () => { const oneTimeKeyCounts = new Map(); const unusedFallbackKeys = new Set(); - const receiveSyncChanges = JSON.parse( - await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys), + const receiveSyncChanges = await m.receiveSyncChanges( + toDeviceEvents, + changedDevices, + oneTimeKeyCounts, + unusedFallbackKeys, ); expect(receiveSyncChanges).toEqual([]); @@ -319,8 +327,11 @@ describe(OlmMachine.name, () => { const changedDevices = new DeviceLists(); const oneTimeKeyCounts = new Map(); - const receiveSyncChanges = JSON.parse( - await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, undefined), + const receiveSyncChanges = await m.receiveSyncChanges( + toDeviceEvents, + changedDevices, + oneTimeKeyCounts, + undefined, ); expect(receiveSyncChanges).toEqual([]); @@ -333,8 +344,11 @@ describe(OlmMachine.name, () => { const oneTimeKeyCounts = new Map(); const unusedFallbackKeys = new Set(); - const receiveSyncChanges = JSON.parse( - await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys), + const receiveSyncChanges = await m.receiveSyncChanges( + toDeviceEvents, + changedDevices, + oneTimeKeyCounts, + unusedFallbackKeys, ); expect(receiveSyncChanges).toEqual([]); @@ -1629,11 +1643,13 @@ describe(OlmMachine.name, () => { undefined, ); - const receivedToDevices = JSON.parse(received); - expect(receivedToDevices).toBeInstanceOf(Array); - const receivedToDeviceArray: Array = receivedToDevices; - expect(receivedToDeviceArray.length).toBe(1); - const toDeviceEvent = receivedToDeviceArray[0]; + expect(received.length).toBe(1); + const processed = received[0]; + expect(processed.type).toEqual(ProcessedToDeviceEventType.PlainText); + expect(processed).toBeInstanceOf(PlainTextToDeviceEvent); + + let typedEvent = processed as PlainTextToDeviceEvent; + let toDeviceEvent = JSON.parse(typedEvent.rawEvent); expect(toDeviceEvent.sender).toEqual("@alice:example.com"); expect(toDeviceEvent.type).toEqual("custom.type"); @@ -1649,7 +1665,7 @@ describe(OlmMachine.name, () => { content: { "algorithm": "m.olm.v1.curve25519-aes-sha2", "ciphertext": { - aliceCurve: { + [aliceCurve]: { // this payload is just captured from a sync of some other element web with other users body: "Awogjvpx458CGhuo77HX/+tp1sxgRoCi7iAlzMvfrpbWoREQAiKACysX/p+ojr5QitCi9WRXNyamW2v2LTvoyWKtVaA2oHnYGR5s5RYhDfnIgh5MMSqqKlAbfqLvrbLovTYcKagCBbFnbA43f6zYM44buGgy8q70hMVH5WP6aK1E9Z3DVZ+8PnXQGpsrxvz2IsL6w0Nzl/qUyBEQFcgkjoDPawbsZRCllMgq2LQUyqlun6IgDTCozqsfxhDWpdfYGde4z16m34Ang7f5pH+BmPrFs6E1AO5+UbhhhS6NwWlfEtA6/9yfMxWLz1d2OrLh+QG7lYFAU9/CzIoPxaHKKr4JxgL9CjsmUPyDymWOWHP0jLi1NwpOv6hGpx0FgM7jJIMk6gWGgC5rEgEeTIwdrJh3F9OKTNSva5hvD9LomGk6tZgzQG6oap1e3wiOUyTt6S7BlyMppIu3RlIiNihZ9e17JEGiGDXOXzMJ6ISAgvGVgTP7+EvyEt2Wt4du7uBo/UvljRvVNu3I8tfItizPAOlvz460+aBDxk+sflJWt7OnhiyPnOCfopb+1RzqKVCnnPyVaP2f4BPf8qpn/f5YZk+5jJgBrGPiHzzmb3sQ5pC470s6+U3MpVFlFTG/xPBtMRMwPsbKoHfnRPqIqGu5dQ1Sw7T6taDXWjP450TvjxgHK5t2z1rLA2SXzAB1P8xbi6YXqQwxL6PvMNHn/TM0jiIQHYuqg5/RKLyhHybfP8JAjgNBw9z16wfKR/YoYFr7c+S4McQaMNa8v2SxGzhpCC3duAoK2qCWLEkYRO5cMCsGm/9bf8Q+//OykygBU/hdkT1eHUbexgALPLdfhzduutU7pbChg4T7SH7euh/3NLmS/SQvkmPfm3ckbh/Vlcj9CsXws/7MX/VJbhpbyzgBNtMnbG6tAeAofMa6Go/yMgiNBZIhLpAm31iUbUhaGm2IIlF/lsmSYEiBPoSVfFU44tetX2I/PBDGiBlzyU+yC2TOEBwMGxBE3WHbIe5/7sKW8xJF9t+HBfxIyW1QRtY3EKdEcuVOTyMxYzq3L5OKOOtPDHObYiiXg00mAgdQqgfkEAIfoRCOa2NYfTedwwo0S77eQ1sPvW5Hhf+Cm+bLibkWzaYHEZF+vyE9/Tn0tZGtH07RXfUyhp1vtTH49OBZHGkb/r+L8OjYJTST1dDCGqeGXO3uwYjoWHXtezLVHYgL+UOwcLJfMF5s9DQiqcfYXzp2kEWGsaetBFXcUWqq4RMHqlr6QfbxyuYLlQzc/AYA/MrT3J6nDpNLcvozH3RcIs8NcKcjdtjvgL0QGThy3RcecJQEDx3STrkkePL3dlyFCtVsmtQ0vjBBCxUgdySfxiobGGnpezZYi7q+Xz61GOZ9QqYmkcZOPzfNWeqtmzB7gqlH1gkFsK2yMAzKq2XCDFHvA7YAT3yMGiY06FcQ+2jyg7Bk2Q+AvjTG8hlPlmt6BZfW5cz1qx1apQn1qHXHrgfWcI52rApYQlNPOU1Uc8kZ8Ee6XUhhXBGY1rvZiKjKFG0PPuS8xo4/P7/u+gH5gItmEVDFL6giYPFsPpqAQkUN7hFoGiVZEjO4PwrLOmydsEcNOfACqrnUs08FQtvPg0sjHnxh6nh6FUQv93ukKl6+c9d+pCsN2xukrQ7Dog3nrjFZ6PrS5J0k9rDAOwTB55sfGXPZ2rATOK1WS4XcpsCtqwnYm4sGNc8ALMQkQ97zCnw8TcQwLvdUMlfbqQ5ykDQpQD68fITEDDHmBAeTCjpC713E6AhvOMwTJvjhd7hSkeOTRTmn9zXIVGNo1jSr8u0xO9uLGeWsV0+UlRLgp7/nsgfermjwNN8wj6MW3DHGS8UzzYfe9TGCeywqqIUTqgfXY48leGgB7twh4cl4jcOQniLATTvigIAQIvq/Uv8L45BGnkpKTdQ5F73gehXdVA", type: 1, @@ -1671,17 +1687,77 @@ describe(OlmMachine.name, () => { undefined, ); - const receivedToDevices = JSON.parse(received); - expect(receivedToDevices).toBeInstanceOf(Array); - const receivedToDeviceArray: Array = receivedToDevices; - expect(receivedToDeviceArray.length).toBe(1); - const toDeviceEvent = receivedToDeviceArray[0]; + expect(received.length).toBe(1); + const processed = received[0]; + expect(processed).toBeInstanceOf(UTDToDeviceEvent); + expect(processed.type).toEqual(ProcessedToDeviceEventType.UnableToDecrypt); + + const typedEvent = processed as UTDToDeviceEvent; + let toDeviceEvent = JSON.parse(typedEvent.wireEvent); expect(toDeviceEvent.sender).toEqual("@bob:example.org"); expect(toDeviceEvent.type).toEqual("m.room.encrypted"); expect(toDeviceEvent.content.ciphertext).toBeDefined(); }); + test("Should return invalid event properly", async () => { + const m = await machine(); + + let eventWithNoType = { + sender: "@alice:example.com", + content: { + algorithm: "m.megolm.v1.aes-sha2", + code: "m.unverified", + reason: "Device not verified", + room_id: "!Cuyf34gef24t:localhost", + sender_key: "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", + session_id: "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ", + }, + }; + + const eventWithMalformedCurveKey = { + content: { + "algorithm": "m.olm.v1.curve25519-aes-sha2", + "ciphertext": { + INVALID_CURVE: { + // this payload is just captured from a sync of some other element web with other users + body: "Awogjvpx458CGhuo77HX/", + type: 1, + }, + }, + "org.matrix.msgid": "93ee0170aa7740d0ac9ee137e820302d", + "sender_key": "WJ6Ce7U67a6jqkHYHd8o0+5H4bqdi9hInZdk0+swuXs", + }, + type: "m.room.encrypted", + sender: "@bob:example.org", + }; + + let toDeviceEvents = [eventWithNoType, eventWithMalformedCurveKey]; + + const received = await m.receiveSyncChanges( + JSON.stringify(toDeviceEvents), + new DeviceLists(), + new Map(), + undefined, + ); + + expect(received.length).toBe(2); + const processed0 = received[0]; + expect(processed0.type).toEqual(ProcessedToDeviceEventType.Invalid); + expect(processed0).toBeInstanceOf(InvalidToDeviceEvent); + const processed1 = received[1]; + expect(processed1.type).toEqual(ProcessedToDeviceEventType.Invalid); + + let toDeviceEvent0 = JSON.parse((processed0 as InvalidToDeviceEvent).wireEvent); + expect(toDeviceEvent0.sender).toEqual("@alice:example.com"); + expect(toDeviceEvent0.content).toBeDefined(); + expect(toDeviceEvent0.type).toBeUndefined(); + + let toDeviceEvent1 = JSON.parse((processed1 as InvalidToDeviceEvent).wireEvent); + expect(toDeviceEvent1.sender).toEqual("@bob:example.org"); + expect(toDeviceEvent1.type).toEqual("m.room.encrypted"); + }); + test("Should return the clear text version of decrypted events", async () => { const aliceUserId = new UserId("@alice:example.org"); const aliceDeviceId = new DeviceId("ALICE_DEV"); @@ -1776,15 +1852,19 @@ describe(OlmMachine.name, () => { undefined, ); - const receivedToDevices = JSON.parse(received); - expect(receivedToDevices).toBeInstanceOf(Array); - const receivedToDeviceArray: Array = receivedToDevices; - expect(receivedToDeviceArray.length).toBe(1); - const toDeviceEvent = receivedToDeviceArray[0]; + expect(received.length).toBe(1); + expect(received[0]).toBeInstanceOf(DecryptedToDeviceEvent); + const processed = received[0] as DecryptedToDeviceEvent; + expect(processed.type).toEqual(ProcessedToDeviceEventType.Decrypted); + let toDeviceEvent = JSON.parse(processed.decryptedRawEvent); expect(toDeviceEvent.sender).toEqual("@alice:example.org"); expect(toDeviceEvent.type).toEqual("custom.type"); expect(toDeviceEvent.content.foo).toEqual("bar"); + + let encryptionInfo = processed.encryptionInfo; + expect(encryptionInfo.senderCurve25519Key).toEqual(alice.identityKeys.curve25519.toBase64()); + expect(encryptionInfo.isSenderVerified()).toBe(false); }); }); });