Skip to content

Commit 9ab886f

Browse files
authored
crypto: Merge inbound Megolm sessions [#5865]
When we receive two copies of the same inbound Megolm session from two sources, merge them together intelligently. Fixes: #5108, #4698
2 parents 17df3f8 + 60072b3 commit 9ab886f

File tree

6 files changed

+449
-101
lines changed

6 files changed

+449
-101
lines changed

crates/matrix-sdk-crypto/CHANGELOG.md

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

99
### Features
1010

11+
- When we receive an inbound Megolm session from two different sources, merge the two copies together to get the best of both.
12+
([#5865](https://github.com/matrix-org/matrix-rust-sdk/pull/5865)
1113
- When constructing a key bundle for history sharing, if we had received a key bundle ourselves, in which one or more sessions was marked as "history not shared", pass that on to the new user.
1214
([#5820](https://github.com/matrix-org/matrix-rust-sdk/pull/5820)
1315
- Expose new method `CryptoStore::get_withheld_sessions_by_room_id`.

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

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use ruma::{
3939
TransactionId, UserId,
4040
};
4141
use tracing::{debug, field::debug, info, instrument, trace, warn, Span};
42-
use vodozemac::{megolm::SessionOrdering, Curve25519PublicKey};
42+
use vodozemac::Curve25519PublicKey;
4343

4444
use super::{GossipRequest, GossippedSecret, RequestEvent, RequestInfo, SecretInfo, WaitQueue};
4545
use crate::{
@@ -964,6 +964,7 @@ impl GossipMachine {
964964
Ok(name)
965965
}
966966

967+
#[tracing::instrument(skip_all)]
967968
async fn accept_forwarded_room_key(
968969
&self,
969970
info: &GossipRequest,
@@ -972,33 +973,11 @@ impl GossipMachine {
972973
) -> Result<Option<InboundGroupSession>, CryptoStoreError> {
973974
match InboundGroupSession::try_from(event) {
974975
Ok(session) => {
975-
if self.inner.store.compare_group_session(&session).await?
976-
== SessionOrdering::Better
977-
{
976+
let new_session = self.inner.store.merge_received_group_session(session).await?;
977+
if new_session.is_some() {
978978
self.mark_as_done(info).await?;
979-
980-
info!(
981-
?sender_key,
982-
claimed_sender_key = ?session.sender_key(),
983-
room_id = ?session.room_id(),
984-
session_id = session.session_id(),
985-
algorithm = ?session.algorithm(),
986-
"Received a forwarded room key",
987-
);
988-
989-
Ok(Some(session))
990-
} else {
991-
info!(
992-
?sender_key,
993-
claimed_sender_key = ?session.sender_key(),
994-
room_id = ?session.room_id(),
995-
session_id = session.session_id(),
996-
algorithm = ?session.algorithm(),
997-
"Received a forwarded room key but we already have a better version of it",
998-
);
999-
1000-
Ok(None)
1001979
}
980+
Ok(new_session)
1002981
}
1003982
Err(e) => {
1004983
warn!(?sender_key, "Couldn't create a group session from a received room key");

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

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ use tracing::{
6262
field::{debug, display},
6363
info, instrument, trace, warn, Span,
6464
};
65-
use vodozemac::{
66-
megolm::{DecryptionError, SessionOrdering},
67-
Curve25519PublicKey, Ed25519Signature,
68-
};
65+
use vodozemac::{megolm::DecryptionError, Curve25519PublicKey, Ed25519Signature};
6966

7067
#[cfg(feature = "experimental-send-custom-to-device")]
7168
use crate::session_manager::split_devices_for_share_strategy;
@@ -916,25 +913,9 @@ impl OlmMachine {
916913
let sender_data =
917914
SenderDataFinder::find_using_event(self.store(), sender_key, event, &session)
918915
.await?;
919-
920916
session.sender_data = sender_data;
921917

922-
match self.store().compare_group_session(&session).await? {
923-
SessionOrdering::Better => {
924-
info!("Received a new megolm room key");
925-
926-
Ok(Some(session))
927-
}
928-
comparison_result => {
929-
warn!(
930-
?comparison_result,
931-
"Received a megolm room key that we already have a better version \
932-
of, discarding"
933-
);
934-
935-
Ok(None)
936-
}
937-
}
918+
Ok(self.store().merge_received_group_session(session).await?)
938919
}
939920
Err(e) => {
940921
Span::current().record("session_id", &content.session_id);

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

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,36 @@ impl InboundGroupSession {
340340
Self::try_from(exported_session)
341341
}
342342

343+
/// Create a new [`InboundGroupSession`] which is a copy of this one, except
344+
/// that its Megolm ratchet is replaced with a copy of that from another
345+
/// [`InboundGroupSession`].
346+
///
347+
/// This can be useful, for example, when we receive a new copy of the room
348+
/// key, but at an earlier ratchet index.
349+
///
350+
/// # Panics
351+
///
352+
/// If the two sessions are for different room IDs, or have different
353+
/// session IDs, this function will panic. It is up to the caller to ensure
354+
/// that it only attempts to merge related sessions.
355+
pub(crate) fn with_ratchet(mut self, other: &InboundGroupSession) -> Self {
356+
if self.session_id != other.session_id {
357+
panic!(
358+
"Attempt to merge Megolm sessions with different session IDs: {} vs {}",
359+
self.session_id, other.session_id
360+
);
361+
}
362+
if self.room_id != other.room_id {
363+
panic!(
364+
"Attempt to merge Megolm sessions with different room IDs: {} vs {}",
365+
self.room_id, other.room_id,
366+
);
367+
}
368+
self.inner = other.inner.clone();
369+
self.first_known_index = other.first_known_index;
370+
self
371+
}
372+
343373
/// Convert the [`InboundGroupSession`] into a
344374
/// [`PickledInboundGroupSession`] which can be serialized.
345375
pub async fn pickle(&self) -> PickledInboundGroupSession {
@@ -488,7 +518,34 @@ impl InboundGroupSession {
488518

489519
/// Check if the [`InboundGroupSession`] is better than the given other
490520
/// [`InboundGroupSession`]
521+
#[deprecated(
522+
note = "Sessions cannot be compared on a linear scale. Consider calling `compare_ratchet`, as well as comparing the `sender_data`."
523+
)]
491524
pub async fn compare(&self, other: &InboundGroupSession) -> SessionOrdering {
525+
match self.compare_ratchet(other).await {
526+
SessionOrdering::Equal => {
527+
match self.sender_data.compare_trust_level(&other.sender_data) {
528+
Ordering::Less => SessionOrdering::Worse,
529+
Ordering::Equal => SessionOrdering::Equal,
530+
Ordering::Greater => SessionOrdering::Better,
531+
}
532+
}
533+
result => result,
534+
}
535+
}
536+
537+
/// Check if the [`InboundGroupSession`]'s ratchet index is better than that
538+
/// of the given other [`InboundGroupSession`].
539+
///
540+
/// If the two sessions are not connected (i.e., they are from different
541+
/// senders, or if advancing the ratchets to the same index does not
542+
/// give the same ratchet value), returns [`SessionOrdering::Unconnected`].
543+
///
544+
/// Otherwise, returns [`SessionOrdering::Equal`],
545+
/// [`SessionOrdering::Better`], or [`SessionOrdering::Worse`] respectively
546+
/// depending on whether this session's first known index is equal to,
547+
/// lower than, or higher than, that of `other`.
548+
pub async fn compare_ratchet(&self, other: &InboundGroupSession) -> SessionOrdering {
492549
// If this is the same object the ordering is the same, we can't compare because
493550
// we would deadlock while trying to acquire the same lock twice.
494551
if Arc::ptr_eq(&self.inner, &other.inner) {
@@ -501,17 +558,7 @@ impl InboundGroupSession {
501558
SessionOrdering::Unconnected
502559
} else {
503560
let mut other_inner = other.inner.lock().await;
504-
505-
match self.inner.lock().await.compare(&mut other_inner) {
506-
SessionOrdering::Equal => {
507-
match self.sender_data.compare_trust_level(&other.sender_data) {
508-
Ordering::Less => SessionOrdering::Worse,
509-
Ordering::Equal => SessionOrdering::Equal,
510-
Ordering::Greater => SessionOrdering::Better,
511-
}
512-
}
513-
result => result,
514-
}
561+
self.inner.lock().await.compare(&mut other_inner)
515562
}
516563
}
517564

@@ -1057,6 +1104,7 @@ mod tests {
10571104
}
10581105

10591106
#[async_test]
1107+
#[allow(deprecated)]
10601108
async fn test_session_comparison() {
10611109
let alice = Account::with_device_id(alice_id(), alice_device_id());
10621110
let room_id = room_id!("!test:localhost");
@@ -1067,18 +1115,24 @@ mod tests {
10671115
let mut copy = InboundGroupSession::from_pickle(inbound.pickle().await).unwrap();
10681116

10691117
assert_eq!(inbound.compare(&worse).await, SessionOrdering::Better);
1118+
assert_eq!(inbound.compare_ratchet(&worse).await, SessionOrdering::Better);
10701119
assert_eq!(worse.compare(&inbound).await, SessionOrdering::Worse);
1120+
assert_eq!(worse.compare_ratchet(&inbound).await, SessionOrdering::Worse);
10711121
assert_eq!(inbound.compare(&inbound).await, SessionOrdering::Equal);
1122+
assert_eq!(inbound.compare_ratchet(&inbound).await, SessionOrdering::Equal);
10721123
assert_eq!(inbound.compare(&copy).await, SessionOrdering::Equal);
1124+
assert_eq!(inbound.compare_ratchet(&copy).await, SessionOrdering::Equal);
10731125

10741126
copy.creator_info.curve25519_key =
10751127
Curve25519PublicKey::from_base64("XbmrPa1kMwmdtNYng1B2gsfoo8UtF+NklzsTZiaVKyY")
10761128
.unwrap();
10771129

10781130
assert_eq!(inbound.compare(&copy).await, SessionOrdering::Unconnected);
1131+
assert_eq!(inbound.compare_ratchet(&copy).await, SessionOrdering::Unconnected);
10791132
}
10801133

10811134
#[async_test]
1135+
#[allow(deprecated)]
10821136
async fn test_session_comparison_sender_data() {
10831137
let alice = Account::with_device_id(alice_id(), alice_device_id());
10841138
let room_id = room_id!("!test:localhost");

0 commit comments

Comments
 (0)