Skip to content

Commit 4363105

Browse files
BillCarsonFrpoljar
authored andcommitted
crypto: Add variants for plain text and encrypted to-device events
fixup: post rebase
1 parent 3b13386 commit 4363105

File tree

10 files changed

+314
-30
lines changed

10 files changed

+314
-30
lines changed

bindings/matrix-sdk-crypto-ffi/src/machine.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,8 +554,10 @@ impl OlmMachine {
554554
}),
555555
)?;
556556

557-
let to_device_events =
558-
to_device_events.into_iter().map(|event| event.json().get().to_owned()).collect();
557+
let to_device_events = to_device_events
558+
.into_iter()
559+
.map(|event| event.to_raw().json().get().to_owned())
560+
.collect();
559561
let room_key_infos = room_key_infos.into_iter().map(|info| info.into()).collect();
560562

561563
Ok(SyncChangesResult { to_device_events, room_key_infos })
@@ -930,6 +932,8 @@ impl OlmMachine {
930932
},
931933
}
932934
}
935+
// Should not happen
936+
_ => panic!("Unsupported algorithm in room"),
933937
})
934938
}
935939

crates/matrix-sdk-base/src/response_processors/e2ee/to_device.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ async fn process(
9999
let (events, room_key_updates) =
100100
olm_machine.receive_sync_changes(encryption_sync_changes).await?;
101101

102+
let events = events
103+
.iter()
104+
// XXX There is loss of information here, after calling `to_raw` it is not
105+
// possible to make the difference between a successfully decrypted event and a plain
106+
// text event. This information needs to be propagated to top layer at some point if
107+
// clients relies on custom encrypted to device events.
108+
.map(|p| p.to_raw())
109+
.collect();
110+
102111
Output { decrypted_to_device_events: events, room_key_updates: Some(room_key_updates) }
103112
} else {
104113
// If we have no `OlmMachine`, just return the events that were passed in.

crates/matrix-sdk-common/src/deserialized_responses.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::{collections::BTreeMap, fmt};
1717
#[cfg(doc)]
1818
use ruma::events::AnyTimelineEvent;
1919
use ruma::{
20-
events::{AnyMessageLikeEvent, AnySyncTimelineEvent},
20+
events::{AnyMessageLikeEvent, AnySyncTimelineEvent, AnyToDeviceEvent},
2121
push::Action,
2222
serde::{
2323
AsRefStr, AsStrAsRefStr, DebugAsRefStr, DeserializeFromCowStr, FromString, JsonObject, Raw,
@@ -282,6 +282,11 @@ pub enum AlgorithmInfo {
282282
/// key.
283283
sender_claimed_keys: BTreeMap<DeviceKeyAlgorithm, String>,
284284
},
285+
286+
OlmV1Curve25519AesSha2 {
287+
// The sender key of the device that encrypted the message
288+
curve25519_key: String,
289+
},
285290
}
286291

287292
/// Struct containing information on how an event was decrypted.
@@ -615,6 +620,40 @@ impl fmt::Debug for DecryptedRoomEvent {
615620
}
616621
}
617622

623+
#[derive(Clone, Debug, Serialize, Deserialize)]
624+
pub enum ProcessedToDeviceEvent {
625+
/// A successfully-decrypted encrypted event.
626+
Decrypted {
627+
/// The decrypted to device event
628+
decrypted_event: Raw<AnyToDeviceEvent>,
629+
/// Sender encryption information
630+
encryption_info: EncryptionInfo,
631+
},
632+
633+
/// An encrypted event which could not be decrypted.
634+
UnableToDecrypt {
635+
/// The `m.room.encrypted` to device event.
636+
event: Raw<AnyToDeviceEvent>,
637+
},
638+
639+
/// An unencrypted event.
640+
PlainText(Raw<AnyToDeviceEvent>),
641+
642+
/// An invalid to device event that was ignored
643+
NotProcessed(Raw<AnyToDeviceEvent>),
644+
}
645+
646+
impl ProcessedToDeviceEvent {
647+
pub fn to_raw(&self) -> Raw<AnyToDeviceEvent> {
648+
match self {
649+
ProcessedToDeviceEvent::Decrypted { decrypted_event, .. } => decrypted_event.clone(),
650+
ProcessedToDeviceEvent::UnableToDecrypt { event } => event.clone(),
651+
ProcessedToDeviceEvent::PlainText(event) => event.clone(),
652+
ProcessedToDeviceEvent::NotProcessed(event) => event.clone(),
653+
}
654+
}
655+
}
656+
618657
/// The location of an event bundled in an `unsigned` object.
619658
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
620659
pub enum UnsignedEventLocation {

crates/matrix-sdk-crypto/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ All notable changes to this project will be documented in this file.
2020

2121
### Features
2222

23+
- [**breaking**] `OlmMachine.receive_sync_changes` returns now a list of `ProcessedToDeviceEvent`
24+
instead of a list of `Raw<AnyToDeviceEvent>`. With variants like `Decrypted`|`UnableToDecrypt`|`PlainText`|`NotProcessed`.
25+
This allows for example to make the difference between an event sent in clear and an event successfully decrypted.
26+
A `ProcessedToDeviceEvent::Decrypted` now contains the `encryption_info` and `verification_state` of the event.
27+
For quick compatibility a helper `ProcessedToDeviceEvent::to_raw` allows to map back to the previous behaviour.
28+
2329
- [**breaking**] Add support for the shared history flag defined in
2430
[MSC3061](https://github.com/matrix-org/matrix-spec-proposals/pull/3061).
2531
The shared history flag is now respected when room keys are received as an

crates/matrix-sdk-crypto/src/machine/mod.rs

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ use std::{
2121
use itertools::Itertools;
2222
use matrix_sdk_common::{
2323
deserialized_responses::{
24-
AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, UnableToDecryptInfo,
25-
UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel,
26-
VerificationState,
24+
AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo,
25+
ProcessedToDeviceEvent, UnableToDecryptInfo, UnableToDecryptReason,
26+
UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel, VerificationState,
2727
},
2828
locks::RwLock as StdRwLock,
2929
BoxFuture,
@@ -1286,16 +1286,15 @@ impl OlmMachine {
12861286
transaction: &mut StoreTransaction,
12871287
changes: &mut Changes,
12881288
mut raw_event: Raw<AnyToDeviceEvent>,
1289-
) -> Option<Raw<AnyToDeviceEvent>> {
1289+
) -> Option<ProcessedToDeviceEvent> {
12901290
Self::record_message_id(&raw_event);
12911291

12921292
let event: ToDeviceEvents = match raw_event.deserialize_as() {
12931293
Ok(e) => e,
12941294
Err(e) => {
12951295
// Skip invalid events.
12961296
warn!("Received an invalid to-device event: {e}");
1297-
1298-
return Some(raw_event);
1297+
return Some(ProcessedToDeviceEvent::NotProcessed(raw_event));
12991298
}
13001299
};
13011300

@@ -1320,7 +1319,7 @@ impl OlmMachine {
13201319
}
13211320
}
13221321

1323-
return Some(raw_event);
1322+
return Some(ProcessedToDeviceEvent::UnableToDecrypt { event: raw_event });
13241323
}
13251324
};
13261325

@@ -1372,12 +1371,75 @@ impl OlmMachine {
13721371
raw_event = decrypted.result.raw_event;
13731372
}
13741373
}
1374+
1375+
let encryption_info =
1376+
self.get_olm_encryption_info(&e.sender, decrypted.result.sender_key).await;
1377+
1378+
Some(ProcessedToDeviceEvent::Decrypted {
1379+
decrypted_event: raw_event,
1380+
encryption_info,
1381+
})
13751382
}
13761383

1377-
e => self.handle_to_device_event(changes, &e).await,
1384+
e => {
1385+
self.handle_to_device_event(changes, &e).await;
1386+
Some(ProcessedToDeviceEvent::PlainText(raw_event))
1387+
}
13781388
}
1389+
}
13791390

1380-
Some(raw_event)
1391+
/// Get the sender information for a successfully decrypted olm message.
1392+
///
1393+
/// # Arguments
1394+
///
1395+
/// * `sender` - The claimed user_id retrieved from the event.
1396+
///
1397+
/// * `sender_key` - The `Curve25519PublicKey` linked to the olm session
1398+
/// that decrypted the message.
1399+
///
1400+
/// # Returns
1401+
///
1402+
/// A [`EncryptionInfo`] struct.
1403+
async fn get_olm_encryption_info(
1404+
&self,
1405+
sender: &UserId,
1406+
sender_key: Curve25519PublicKey,
1407+
) -> EncryptionInfo {
1408+
let device =
1409+
self.store().get_device_from_curve_key(sender, sender_key).await.unwrap_or(None);
1410+
1411+
let state = if let Some(device) = &device {
1412+
if device.is_cross_signed_by_owner() {
1413+
if device.is_device_owner_verified() {
1414+
VerificationState::Verified
1415+
} else {
1416+
let identity = device
1417+
.device_owner_identity
1418+
.as_ref()
1419+
.expect("This device is cross-signed, so the identity exists");
1420+
if identity.was_previously_verified() {
1421+
VerificationState::Unverified(VerificationLevel::VerificationViolation)
1422+
} else {
1423+
VerificationState::Unverified(VerificationLevel::UnverifiedIdentity)
1424+
}
1425+
}
1426+
} else {
1427+
VerificationState::Unverified(VerificationLevel::UnsignedDevice)
1428+
}
1429+
} else {
1430+
VerificationState::Unverified(VerificationLevel::None(DeviceLinkProblem::MissingDevice))
1431+
};
1432+
1433+
EncryptionInfo {
1434+
sender: sender.to_owned(),
1435+
sender_device: device.map(|d| d.device_id().to_owned()),
1436+
algorithm_info: AlgorithmInfo::OlmV1Curve25519AesSha2 {
1437+
curve25519_key: sender_key.to_base64(),
1438+
},
1439+
verification_state: state,
1440+
// Only relevant for megolm
1441+
session_id: None,
1442+
}
13811443
}
13821444

13831445
/// Decide whether a decrypted to-device event was sent from a dehydrated
@@ -1435,7 +1497,7 @@ impl OlmMachine {
14351497
pub async fn receive_sync_changes(
14361498
&self,
14371499
sync_changes: EncryptionSyncChanges<'_>,
1438-
) -> OlmResult<(Vec<Raw<AnyToDeviceEvent>>, Vec<RoomKeyInfo>)> {
1500+
) -> OlmResult<(Vec<ProcessedToDeviceEvent>, Vec<RoomKeyInfo>)> {
14391501
let mut store_transaction = self.inner.store.transaction().await;
14401502

14411503
let (events, changes) =
@@ -1464,10 +1526,18 @@ impl OlmMachine {
14641526
&self,
14651527
transaction: &mut StoreTransaction,
14661528
sync_changes: EncryptionSyncChanges<'_>,
1467-
) -> OlmResult<(Vec<Raw<AnyToDeviceEvent>>, Changes)> {
1529+
) -> OlmResult<(Vec<ProcessedToDeviceEvent>, Changes)> {
14681530
// Remove verification objects that have expired or are done.
1469-
let mut events = self.inner.verification_machine.garbage_collect();
1470-
1531+
let mut events: Vec<ProcessedToDeviceEvent> = self
1532+
.inner
1533+
.verification_machine
1534+
.garbage_collect()
1535+
.iter()
1536+
// These are `fake` to device events just serving as local echo
1537+
// in order for own client to react quickly to cancelled transaction.
1538+
// Just use PlainText for that.
1539+
.map(|e| ProcessedToDeviceEvent::PlainText(e.clone()))
1540+
.collect();
14711541
// The account is automatically saved by the store transaction created by the
14721542
// caller.
14731543
let mut changes = Default::default();

crates/matrix-sdk-crypto/src/machine/tests/megolm_sender_data.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ use std::{fmt::Debug, iter, pin::Pin};
1717
use assert_matches::assert_matches;
1818
use futures_core::Stream;
1919
use futures_util::{FutureExt, StreamExt};
20+
use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
2021
use matrix_sdk_test::async_test;
21-
use ruma::{events::AnyToDeviceEvent, room_id, serde::Raw, user_id, RoomId, TransactionId, UserId};
22+
use ruma::{room_id, user_id, RoomId, TransactionId, UserId};
2223
use serde::Serialize;
2324
use serde_json::json;
2425
use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
@@ -286,7 +287,7 @@ async fn create_and_share_session_without_sender_data(
286287
pub async fn receive_to_device_event<C>(
287288
machine: &OlmMachine,
288289
event: &ToDeviceEvent<C>,
289-
) -> (Vec<Raw<AnyToDeviceEvent>>, Vec<RoomKeyInfo>)
290+
) -> (Vec<ProcessedToDeviceEvent>, Vec<RoomKeyInfo>)
290291
where
291292
C: EventType + Serialize + Debug,
292293
{

crates/matrix-sdk-crypto/src/machine/tests/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ use assert_matches2::{assert_let, assert_matches};
1818
use futures_util::{pin_mut, FutureExt, StreamExt};
1919
use itertools::Itertools;
2020
use matrix_sdk_common::deserialized_responses::{
21-
AlgorithmInfo, UnableToDecryptInfo, UnableToDecryptReason, UnsignedDecryptionResult,
22-
UnsignedEventLocation, VerificationLevel, VerificationState, WithheldCode,
21+
AlgorithmInfo, ProcessedToDeviceEvent, UnableToDecryptInfo, UnableToDecryptReason,
22+
UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel, VerificationState,
23+
WithheldCode,
2324
};
2425
use matrix_sdk_test::{async_test, message_like_event_content, ruma_response_from_json, test_json};
2526
use ruma::{
@@ -397,7 +398,7 @@ async fn test_room_key_sharing() {
397398
let (decrypted, room_key_updates) =
398399
send_room_key_to_device(&alice, &bob, room_id).await.unwrap();
399400

400-
let event = decrypted[0].deserialize().unwrap();
401+
let event = decrypted[0].to_raw().deserialize().unwrap();
401402

402403
if let AnyToDeviceEvent::RoomKey(event) = event {
403404
assert_eq!(&event.sender, alice.user_id());
@@ -489,7 +490,7 @@ async fn send_room_key_to_device(
489490
sender: &OlmMachine,
490491
receiver: &OlmMachine,
491492
room_id: &RoomId,
492-
) -> OlmResult<(Vec<Raw<AnyToDeviceEvent>>, Vec<RoomKeyInfo>)> {
493+
) -> OlmResult<(Vec<ProcessedToDeviceEvent>, Vec<RoomKeyInfo>)> {
493494
let to_device_requests = sender
494495
.share_room_key(room_id, iter::once(receiver.user_id()), EncryptionSettings::default())
495496
.await

crates/matrix-sdk-crypto/src/machine/tests/olm_encryption.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::{
1818
};
1919

2020
use assert_matches2::assert_let;
21+
use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
2122
use matrix_sdk_test::async_test;
2223
use ruma::{
2324
device_id,
@@ -292,7 +293,11 @@ async fn test_decrypt_to_device_message_with_unsigned_sender_keys() {
292293
// Bob receives the to-device message
293294
let (to_device_events, _) = receive_to_device_event(&bob, &event).await;
294295

296+
let event = to_device_events.first().expect("Bob did not get a to-device event").clone();
297+
295298
// The to-device event should remain decrypted.
296-
let event = to_device_events.first().expect("Bob did not get a to-device event");
299+
let ProcessedToDeviceEvent::UnableToDecrypt { event } = event else {
300+
panic!("Should refuse to decrypt")
301+
};
297302
assert_eq!(event.get_field("type").unwrap(), Some("m.room.encrypted"));
298303
}

0 commit comments

Comments
 (0)