Skip to content

Commit 23db199

Browse files
committed
Merge branch 'release-0.11'
2 parents 7cdfb0d + 76d1f8b commit 23db199

File tree

8 files changed

+189
-17
lines changed

8 files changed

+189
-17
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ zeroize = "1.8.1"
108108
matrix-sdk = { path = "crates/matrix-sdk", version = "0.11.0", default-features = false }
109109
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.11.0" }
110110
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.11.0" }
111-
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.11.0" }
111+
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.11.1" }
112112
matrix-sdk-ffi-macros = { path = "bindings/matrix-sdk-ffi-macros", version = "0.7.0" }
113113
matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.11.0", default-features = false }
114114
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.11.0" }

crates/matrix-sdk-crypto/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ All notable changes to this project will be documented in this file.
4242
For quick compatibility a helper `ProcessedToDeviceEvent::to_raw` allows to map back to the previous behaviour.
4343
([#4935](https://github.com/matrix-org/matrix-rust-sdk/pull/4935))
4444

45+
## [0.11.1] - 2025-06-10
46+
47+
### Security Fixes
48+
- Check the sender of an event matches owner of session, preventing sender
49+
spoofing by homeserver owners.
50+
[13c1d20](https://github.com/matrix-org/matrix-rust-sdk/commit/13c1d2048286bbabf5e7bc6b015aafee98f04d55) (High, [GHSA-x958-rvg6-956w](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-x958-rvg6-956w)).
51+
52+
### Bug Fixes
53+
- Remove a wildcard enum variant import which breaks compilation if used with
54+
`tracing-attributes` version `0.1.29`. This is a workaround for a bug in
55+
`tracing-attributes`.
56+
([#5190](https://github.com/matrix-org/matrix-rust-sdk/issues/5190)) ([#5191](https://github.com/matrix-org/matrix-rust-sdk/issues/5191)) ([#5193](https://github.com/matrix-org/matrix-rust-sdk/issues/5193))
57+
4558
## [0.11.0] - 2025-04-11
4659

4760
### Features

crates/matrix-sdk-crypto/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ license = "Apache-2.0"
88
name = "matrix-sdk-crypto"
99
readme = "README.md"
1010
repository = "https://github.com/matrix-org/matrix-rust-sdk"
11-
rust-version.workspace = true
12-
version = "0.11.0"
11+
rust-version = { workspace = true }
12+
version = "0.11.1"
1313

1414
[package.metadata.docs.rs]
1515
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]

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

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,20 +1630,72 @@ impl OlmMachine {
16301630
self.inner.key_request_machine.request_key(room_id, &event).await
16311631
}
16321632

1633-
/// Find whether the supplied session is verified, and provide
1634-
/// explanation of what is missing/wrong if not.
1633+
/// Find whether an event decrypted via the supplied session is verified,
1634+
/// and provide explanation of what is missing/wrong if not.
1635+
///
1636+
/// Stores the updated [`SenderData`] for the session in the store
1637+
/// if we find an updated value for it.
1638+
///
1639+
/// # Arguments
1640+
///
1641+
/// * `session` - The inbound Megolm session that was used to decrypt the
1642+
/// event.
1643+
/// * `sender` - The `sender` of that event (as claimed by the envelope of
1644+
/// the event).
1645+
async fn get_room_event_verification_state(
1646+
&self,
1647+
session: &InboundGroupSession,
1648+
sender: &UserId,
1649+
) -> MegolmResult<(VerificationState, Option<OwnedDeviceId>)> {
1650+
let sender_data = self.get_or_update_sender_data(session, sender).await?;
1651+
1652+
// If the user ID in the sender data doesn't match that in the event envelope,
1653+
// this event is not from who it appears to be from.
1654+
//
1655+
// If `sender_data.user_id()` returns `None`, that means we don't have any
1656+
// information about the owner of the session (i.e. we have
1657+
// `SenderData::UnknownDevice`); in that case we fall through to the
1658+
// logic in `sender_data_to_verification_state` which will pick an appropriate
1659+
// `DeviceLinkProblem` for `VerificationLevel::None`.
1660+
let (verification_state, device_id) = match sender_data.user_id() {
1661+
Some(i) if i != sender => {
1662+
// For backwards compatibility, we treat this the same as "Unknown device".
1663+
// TODO: use a dedicated VerificationLevel here.
1664+
(
1665+
VerificationState::Unverified(VerificationLevel::None(
1666+
DeviceLinkProblem::MissingDevice,
1667+
)),
1668+
None,
1669+
)
1670+
}
1671+
1672+
Some(_) | None => {
1673+
sender_data_to_verification_state(sender_data, session.has_been_imported())
1674+
}
1675+
};
1676+
1677+
Ok((verification_state, device_id))
1678+
}
1679+
1680+
/// Get an up-to-date [`SenderData`] for the given session, suitable for
1681+
/// determining if messages decrypted using that session are verified.
16351682
///
16361683
/// Checks both the stored verification state of the session and a
16371684
/// recalculated verification state based on our current knowledge, and
16381685
/// returns the more trusted of the two.
16391686
///
1640-
/// Store the updated [`SenderData`] for this session in the store
1687+
/// Stores the updated [`SenderData`] for the session in the store
16411688
/// if we find an updated value for it.
1642-
async fn get_or_update_verification_state(
1689+
///
1690+
/// # Arguments
1691+
///
1692+
/// * `session` - The Megolm session that was used to decrypt the event.
1693+
/// * `sender` - The claimed sender of that event.
1694+
async fn get_or_update_sender_data(
16431695
&self,
16441696
session: &InboundGroupSession,
16451697
sender: &UserId,
1646-
) -> MegolmResult<(VerificationState, Option<OwnedDeviceId>)> {
1698+
) -> MegolmResult<SenderData> {
16471699
/// Whether we should recalculate the Megolm sender's data, given the
16481700
/// current sender data. We only want to recalculate if it might
16491701
/// increase trust and allow us to decrypt messages that we
@@ -1664,7 +1716,24 @@ impl OlmMachine {
16641716
}
16651717

16661718
let sender_data = if should_recalculate_sender_data(&session.sender_data) {
1667-
// The session is not sure of the sender yet. Calculate it.
1719+
// The session is not sure of the sender yet. Try to find a matching device
1720+
// belonging to the claimed sender of the recently-received event.
1721+
//
1722+
// It's worth noting that this could in theory result in unintuitive changes,
1723+
// like a session which initially appears to belong to Alice turning into a
1724+
// session which belongs to Bob [1]. This could mean that a session initially
1725+
// successfully decrypts events from Alice, but then stops decrypting those same
1726+
// events once we get an update.
1727+
//
1728+
// That's ok though: if we get good evidence that the session belongs to Bob,
1729+
// it's correct to update the session even if we previously had weak
1730+
// evidence it belonged to Alice.
1731+
//
1732+
// [1] For example: maybe Alice and Bob both publish devices with the *same*
1733+
// keys (presumably because they are colluding). Initially we think
1734+
// the session belongs to Alice, but then we do a device lookup for
1735+
// Bob, we find a matching device with a cross-signature, so prefer
1736+
// that.
16681737
let calculated_sender_data = SenderDataFinder::find_using_curve_key(
16691738
self.store(),
16701739
session.sender_key(),
@@ -1690,7 +1759,7 @@ impl OlmMachine {
16901759
session.sender_data.clone()
16911760
};
16921761

1693-
Ok(sender_data_to_verification_state(sender_data, session.has_been_imported()))
1762+
Ok(sender_data)
16941763
}
16951764

16961765
/// Request missing local secrets from our devices (cross signing private
@@ -1763,7 +1832,7 @@ impl OlmMachine {
17631832
sender: &UserId,
17641833
) -> MegolmResult<Arc<EncryptionInfo>> {
17651834
let (verification_state, device_id) =
1766-
self.get_or_update_verification_state(session, sender).await?;
1835+
self.get_room_event_verification_state(session, sender).await?;
17671836

17681837
let sender = sender.to_owned();
17691838

@@ -2216,7 +2285,7 @@ impl OlmMachine {
22162285
self.get_session_encryption_info(room_id, content.session_id(), &event.sender).await
22172286
}
22182287

2219-
/// Get encryption info for a megolm session.
2288+
/// Get encryption info for an event decrypted with a megolm session.
22202289
///
22212290
/// This recalculates the [`EncryptionInfo`] data that is returned by
22222291
/// [`OlmMachine::decrypt_room_event`], based on the current
@@ -2228,7 +2297,8 @@ impl OlmMachine {
22282297
///
22292298
/// * `room_id` - The ID of the room where the session is being used.
22302299
/// * `session_id` - The ID of the session to get information for.
2231-
/// * `sender` - The user ID of the sender who created this session.
2300+
/// * `sender` - The (claimed) sender of the event where the session was
2301+
/// used.
22322302
pub async fn get_session_encryption_info(
22332303
&self,
22342304
room_id: &RoomId,

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,69 @@ pub async fn mark_alice_identity_as_verified_test_helper(alice: &OlmMachine, bob
311311
.is_verified());
312312
}
313313

314+
/// Test that the verification state is set correctly when the sender of an
315+
/// event does not match the owner of the device that sent us the session.
316+
///
317+
/// In this test, Bob receives an event from Alice, but the HS admin has
318+
/// rewritten the `sender` of the event to look like another user.
319+
#[async_test]
320+
async fn test_verification_states_spoofed_sender() {
321+
let (alice, bob) = get_machine_pair_with_setup_sessions_test_helper(
322+
tests::alice_id(),
323+
tests::user_id(),
324+
false,
325+
)
326+
.await;
327+
328+
let room_id = room_id!("!test:example.org");
329+
let decryption_settings =
330+
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
331+
332+
// Alice sends a message to Bob.
333+
let (event, _) = encrypt_message(&alice, room_id, &bob, "Secret message").await;
334+
bob.decrypt_room_event(&event, room_id, &decryption_settings)
335+
.await
336+
.expect("Bob could not decrypt event");
337+
let event_encryption_info = bob.get_room_event_encryption_info(&event, room_id).await.unwrap();
338+
assert_matches!(
339+
&event_encryption_info.verification_state,
340+
VerificationState::Unverified(VerificationLevel::UnsignedDevice)
341+
);
342+
343+
// Alice now sends a second message to Bob, using the same room key, but the HS
344+
// admin rewrites the 'sender' to Charlie.
345+
let encrypted_content = alice
346+
.encrypt_room_event(
347+
room_id,
348+
AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::text_plain(
349+
"spoofed message",
350+
)),
351+
)
352+
.await
353+
.unwrap();
354+
let event = json!({
355+
"event_id": "$xxxxy:example.org",
356+
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
357+
"sender": "@charlie:example.org", // Note! spoofed sender
358+
"type": "m.room.encrypted",
359+
"content": encrypted_content,
360+
});
361+
let event = json_convert(&event).unwrap();
362+
363+
bob.decrypt_room_event(&event, room_id, &decryption_settings)
364+
.await
365+
.expect("Bob could not decrypt spoofed event");
366+
367+
// The verification_state of the event should be `MissingDevice` (since it
368+
// manifests as a message from Charlie which does not correspond to one of
369+
// Charlie's devices).
370+
let event_encryption_info = bob.get_room_event_encryption_info(&event, room_id).await.unwrap();
371+
assert_matches!(
372+
&event_encryption_info.verification_state,
373+
VerificationState::Unverified(VerificationLevel::None(DeviceLinkProblem::MissingDevice))
374+
);
375+
}
376+
314377
#[async_test]
315378
async fn test_verification_states_multiple_device() {
316379
let (bob, _) = get_prepared_machine_test_helper(tests::user_id(), false).await;
@@ -358,7 +421,7 @@ async fn test_verification_states_multiple_device() {
358421
.unwrap();
359422

360423
let (state, _) = bob
361-
.get_or_update_verification_state(&web_unverified_inbound_session, other_user_id)
424+
.get_room_event_verification_state(&web_unverified_inbound_session, other_user_id)
362425
.await
363426
.unwrap();
364427
assert_eq!(VerificationState::Unverified(VerificationLevel::UnsignedDevice), state);
@@ -376,7 +439,7 @@ async fn test_verification_states_multiple_device() {
376439
.unwrap();
377440

378441
let (state, _) = bob
379-
.get_or_update_verification_state(&web_signed_inbound_session, other_user_id)
442+
.get_room_event_verification_state(&web_signed_inbound_session, other_user_id)
380443
.await
381444
.unwrap();
382445

crates/matrix-sdk-crypto/src/olm/group_sessions/sender_data.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,26 @@ impl SenderData {
308308
Self::SenderVerified { .. } => SenderDataType::SenderVerified,
309309
}
310310
}
311+
312+
/// Return our best guess of the owner of the associated megolm session.
313+
///
314+
/// For `SenderData::UnknownDevice`, we don't record any information about
315+
/// the owner of the sender, so returns `None`.
316+
pub(crate) fn user_id(&self) -> Option<OwnedUserId> {
317+
match &self {
318+
SenderData::UnknownDevice { .. } => None,
319+
SenderData::DeviceInfo { device_keys, .. } => Some(device_keys.user_id.clone()),
320+
SenderData::VerificationViolation(known_sender_data) => {
321+
Some(known_sender_data.user_id.clone())
322+
}
323+
SenderData::SenderUnverified(known_sender_data) => {
324+
Some(known_sender_data.user_id.clone())
325+
}
326+
SenderData::SenderVerified(known_sender_data) => {
327+
Some(known_sender_data.user_id.clone())
328+
}
329+
}
330+
}
311331
}
312332

313333
/// Used when deserialising and the sender_data property is missing.

crates/matrix-sdk/src/room/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1520,12 +1520,18 @@ impl Room {
15201520
}
15211521
}
15221522

1523-
/// Fetches the [`EncryptionInfo`] for the supplied session_id.
1523+
/// Fetches the [`EncryptionInfo`] for an event decrypted with the supplied
1524+
/// session_id.
15241525
///
15251526
/// This may be used when we receive an update for a session, and we want to
15261527
/// reflect the changes in messages we have received that were encrypted
15271528
/// with that session, e.g. to remove a warning shield because a device is
15281529
/// now verified.
1530+
///
1531+
/// # Arguments
1532+
/// * `session_id` - The ID of the Megolm session to get information for.
1533+
/// * `sender` - The (claimed) sender of the event where the session was
1534+
/// used.
15291535
#[cfg(feature = "e2e-encryption")]
15301536
pub async fn get_encryption_info(
15311537
&self,

0 commit comments

Comments
 (0)