Skip to content

Commit b88cc8c

Browse files
committed
Expose EncryptionInfo on decrypted to-device events
Add a new `ToDeviceEncryptionInfo` struct, which is a member of `DecryptedToDeviceEvent`.
1 parent ae223fa commit b88cc8c

File tree

4 files changed

+110
-10
lines changed

4 files changed

+110
-10
lines changed

src/machine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ impl OlmMachine {
362362

363363
Ok(processed_to_device_events
364364
.into_iter()
365-
.map(processed_to_device_event_to_js_value)
365+
.filter_map(processed_to_device_event_to_js_value)
366366
.collect::<Vec<_>>())
367367
}))
368368
}

src/responses.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl TryFrom<matrix_sdk_common::deserialized_responses::TimelineEvent> for Decry
230230
}
231231
}
232232

233-
/// Struct containing information on how an event was decrypted.
233+
/// Struct containing information on how a room event was decrypted.
234234
#[wasm_bindgen()]
235235
#[derive(Debug)]
236236
pub struct EncryptionInfo {
@@ -312,3 +312,66 @@ impl TryFrom<Arc<matrix_sdk_common::deserialized_responses::EncryptionInfo>> for
312312
}
313313
}
314314
}
315+
316+
/// Struct containing information on how a to-device message was decrypted.
317+
#[wasm_bindgen]
318+
#[derive(Debug, Clone)]
319+
pub struct ToDeviceEncryptionInfo {
320+
/// The base64-encoded public Curve25519 key of the device that encrypted
321+
/// the message.
322+
#[wasm_bindgen(getter_with_clone, js_name = "senderCurve25519Key")]
323+
pub sender_curve25519_key_base64: String,
324+
325+
/// The user ID of the sender of the event.
326+
///
327+
/// Note this is untrusted data unless {@link isSenderVerified} is true.
328+
#[wasm_bindgen(getter_with_clone)]
329+
pub sender: identifiers::UserId,
330+
331+
/// The device ID of the device that sent us the to-device message.
332+
///
333+
/// Could be `undefined` in the case where the to-device message sender
334+
/// checks are delayed. There is no delay for to-device messages other
335+
/// than `m.room_key`, so this will always be truthy for other
336+
/// message types (the decryption would fail if the sender device keys
337+
/// cannot be found).
338+
///
339+
/// Note this is untrusted data unless {@link isSenderVerified} is true.
340+
#[wasm_bindgen(getter_with_clone, js_name = "senderDevice")]
341+
pub sender_device: Option<identifiers::DeviceId>,
342+
343+
verification_state: matrix_sdk_common::deserialized_responses::VerificationState,
344+
}
345+
346+
impl TryFrom<matrix_sdk_common::deserialized_responses::EncryptionInfo> for ToDeviceEncryptionInfo {
347+
type Error = anyhow::Error;
348+
349+
fn try_from(
350+
value: matrix_sdk_common::deserialized_responses::EncryptionInfo,
351+
) -> Result<Self, Self::Error> {
352+
match &value.algorithm_info {
353+
AlgorithmInfo::MegolmV1AesSha2 { .. } => Err(anyhow!(
354+
"AlgorithmInfo::MegolmV1AesSha2 is not applicable for ToDeviceEncryptionInfo",
355+
)),
356+
AlgorithmInfo::OlmV1Curve25519AesSha2 { curve25519_public_key_base64 } => Ok(Self {
357+
sender_curve25519_key_base64: curve25519_public_key_base64.clone(),
358+
sender: value.sender.clone().into(),
359+
sender_device: value.sender_device.clone().map(Into::into),
360+
verification_state: value.verification_state.clone(),
361+
}),
362+
}
363+
}
364+
}
365+
366+
#[wasm_bindgen]
367+
impl ToDeviceEncryptionInfo {
368+
/// Returns whether the sender device is in a verified state.
369+
/// This reflects the state at the time of decryption.
370+
#[wasm_bindgen(js_name = "isSenderVerified")]
371+
pub fn is_sender_verified(&self) -> bool {
372+
matches!(
373+
&self.verification_state,
374+
matrix_sdk_common::deserialized_responses::VerificationState::Verified
375+
)
376+
}
377+
}

src/types.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ use matrix_sdk_common::ruma::OwnedRoomId;
1010
use matrix_sdk_crypto::backups::{
1111
SignatureState as InnerSignatureState, SignatureVerification as InnerSignatureVerification,
1212
};
13+
use tracing::warn;
1314
use wasm_bindgen::prelude::*;
1415

1516
use crate::{
1617
encryption::EncryptionAlgorithm,
1718
identifiers::{DeviceKeyId, UserId},
1819
impl_from_to_inner,
20+
responses::ToDeviceEncryptionInfo,
1921
vodozemac::Ed25519Signature,
2022
};
2123

@@ -395,6 +397,10 @@ pub struct DecryptedToDeviceEvent {
395397
/// zeroized).
396398
#[wasm_bindgen(readonly, getter_with_clone, js_name = "decryptedRawEvent")]
397399
pub decrypted_raw_event: JsString,
400+
401+
/// The encryption information for the event.
402+
#[wasm_bindgen(readonly, getter_with_clone, js_name = "encryptionInfo")]
403+
pub encryption_info: ToDeviceEncryptionInfo,
398404
}
399405

400406
#[wasm_bindgen]
@@ -473,15 +479,41 @@ impl InvalidToDeviceEvent {
473479
/// Convert an `ProcessedToDeviceEvent` into a `JsValue`, ready to return to
474480
/// JavaScript.
475481
///
476-
/// JavaScript has no complex enums like Rust. To return structs of
477-
/// different types, we have no choice other than hiding everything behind a
478-
/// `JsValue`.
482+
/// JavaScript has no complex enums like Rust. To return structs of different
483+
/// types, we have no choice other than hiding everything behind a `JsValue`.
484+
///
485+
/// We attempt to map the event onto one of the following types:
486+
/// * [`DecryptedToDeviceEvent`]
487+
/// * [`UTDToDeviceEvent`]
488+
/// * [`PlainTextToDeviceEvent`]
489+
/// * [`InvalidToDeviceEvent`].
490+
///
491+
/// We then convert that result into a [`JsValue`].
492+
///
493+
/// If the event cannot be mapped into one of those types, we instead return
494+
/// `None`, indicating the event should be discarded.
479495
pub fn processed_to_device_event_to_js_value(
480496
processed_to_device_event: matrix_sdk_crypto::types::ProcessedToDeviceEvent,
481-
) -> JsValue {
482-
match processed_to_device_event {
483-
matrix_sdk_crypto::types::ProcessedToDeviceEvent::Decrypted { raw, .. } => {
484-
DecryptedToDeviceEvent { decrypted_raw_event: raw.json().get().into() }.into()
497+
) -> Option<JsValue> {
498+
let result = match processed_to_device_event {
499+
matrix_sdk_crypto::types::ProcessedToDeviceEvent::Decrypted { raw, encryption_info } => {
500+
match encryption_info.try_into() {
501+
Ok(encryption_info) => DecryptedToDeviceEvent {
502+
decrypted_raw_event: raw.json().get().into(),
503+
encryption_info,
504+
}
505+
.into(),
506+
Err(e) => {
507+
// This can only happen if we receive an encrypted to-device event which is
508+
// encrypted with an algorithm we don't recognise. This
509+
// shouldn't really happen, unless the wasm bindings have
510+
// gotten way out of step with the underlying SDK.
511+
//
512+
// There's not a lot we can do here: we just throw away the event.
513+
warn!("Dropping incoming to-device event with invalid encryption_info: {e:?}");
514+
return None;
515+
}
516+
}
485517
}
486518
matrix_sdk_crypto::types::ProcessedToDeviceEvent::UnableToDecrypt(utd) => {
487519
UTDToDeviceEvent { wire_event: utd.json().get().into() }.into()
@@ -492,5 +524,6 @@ pub fn processed_to_device_event_to_js_value(
492524
matrix_sdk_crypto::types::ProcessedToDeviceEvent::Invalid(invalid) => {
493525
InvalidToDeviceEvent { wire_event: invalid.json().get().into() }.into()
494526
}
495-
}
527+
};
528+
Some(result)
496529
}

tests/machine.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,10 @@ describe(OlmMachine.name, () => {
18511851
expect(toDeviceEvent.sender).toEqual("@alice:example.org");
18521852
expect(toDeviceEvent.type).toEqual("custom.type");
18531853
expect(toDeviceEvent.content.foo).toEqual("bar");
1854+
1855+
const encryptionInfo = (processed as DecryptedToDeviceEvent).encryptionInfo;
1856+
expect(encryptionInfo.senderCurve25519Key).toEqual(alice.identityKeys.curve25519.toBase64());
1857+
expect(encryptionInfo.isSenderVerified()).toBe(false);
18541858
});
18551859
});
18561860
});

0 commit comments

Comments
 (0)