Skip to content

Commit

Permalink
feat(crypto): Supports new UtdCause variants for withheld keys
Browse files Browse the repository at this point in the history
Adds new UtdCause variants for withheld keys, enabling applications to display customized messages when an Unable-To-Decrypt message is expected.
  • Loading branch information
BillCarsonFr committed Nov 28, 2024
1 parent d2ecd74 commit ea5fcdb
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 24 deletions.
2 changes: 1 addition & 1 deletion crates/matrix-sdk-base/src/sliding_sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2656,7 +2656,7 @@ mod tests {
.unwrap(),
UnableToDecryptInfo {
session_id: Some("".to_owned()),
reason: UnableToDecryptReason::MissingMegolmSession,
reason: UnableToDecryptReason::MissingMegolmSession(None),
},
)
}
Expand Down
28 changes: 24 additions & 4 deletions crates/matrix-sdk-common/src/deserialized_responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,23 @@ fn unknown_utd_reason() -> UnableToDecryptReason {
UnableToDecryptReason::Unknown
}

/// A room key might be missing because the sender refused to share it or was
/// not technically able to share it.
/// In the protocol this is reflected by clients sending `m.room_key.withheld`
/// to the participant that won't receive the key.
/// We only want here to consider the subset of codes that clients should
/// use to display specific UX.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum WithheldReason {
/// When the sender refuses to send the key because his security settings in
/// that room are not met by your device. Could be that your device is
/// not cross-signed (insecure device) or that you are not verified by
/// the sender.
TrustRequirementMismatch,
/// Other reasons. Like fail to establish a secure channel to share the key,
/// or if for some reason our device is blocked.
Other,
}
/// Reason code for a decryption failure
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum UnableToDecryptReason {
Expand All @@ -684,9 +701,7 @@ pub enum UnableToDecryptReason {

/// Decryption failed because we're missing the megolm session that was used
/// to encrypt the event.
///
/// TODO: support withheld codes?
MissingMegolmSession,
MissingMegolmSession(Option<WithheldReason>),

/// Decryption failed because, while we have the megolm session that was
/// used to encrypt the message, it is ratcheted too far forward.
Expand Down Expand Up @@ -718,7 +733,12 @@ impl UnableToDecryptReason {
/// Returns true if this UTD is due to a missing room key (and hence might
/// resolve itself if we wait a bit.)
pub fn is_missing_room_key(&self) -> bool {
matches!(self, Self::MissingMegolmSession | Self::UnknownMegolmMessageIndex)
match self {
// Return true only if the key is not withheld on purpose.
Self::MissingMegolmSession(None) => true,
Self::UnknownMegolmMessageIndex => true,
_ => false,
}
}
}

Expand Down
11 changes: 9 additions & 2 deletions crates/matrix-sdk-crypto/src/machine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use matrix_sdk_common::{
deserialized_responses::{
AlgorithmInfo, DecryptedRoomEvent, DeviceLinkProblem, EncryptionInfo, UnableToDecryptInfo,
UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation, VerificationLevel,
VerificationState,
VerificationState, WithheldReason,
},
BoxFuture,
};
Expand Down Expand Up @@ -86,6 +86,7 @@ use crate::{
room_key::{MegolmV1AesSha2Content, RoomKeyContent},
room_key_withheld::{
MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent, RoomKeyWithheldEvent,
WithheldCode,
},
ToDeviceEvents,
},
Expand Down Expand Up @@ -2582,7 +2583,13 @@ fn megolm_error_to_utd_info(
let reason = match error {
EventError(_) => UnableToDecryptReason::MalformedEncryptedEvent,
Decode(_) => UnableToDecryptReason::MalformedEncryptedEvent,
MissingRoomKey(_) => UnableToDecryptReason::MissingMegolmSession,
MissingRoomKey(maybe_withheld) => match maybe_withheld {
Some(WithheldCode::Unverified) => UnableToDecryptReason::MissingMegolmSession(Some(
WithheldReason::TrustRequirementMismatch,
)),
Some(_) => UnableToDecryptReason::MissingMegolmSession(Some(WithheldReason::Other)),
_ => UnableToDecryptReason::MissingMegolmSession(None),
},
Decryption(DecryptionError::UnknownMessageIndex(_, _)) => {
UnableToDecryptReason::UnknownMegolmMessageIndex
}
Expand Down
10 changes: 7 additions & 3 deletions crates/matrix-sdk-crypto/src/machine/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use futures_util::{pin_mut, FutureExt, StreamExt};
use itertools::Itertools;
use matrix_sdk_common::deserialized_responses::{
UnableToDecryptInfo, UnableToDecryptReason, UnsignedDecryptionResult, UnsignedEventLocation,
WithheldReason,
};
use matrix_sdk_test::{async_test, message_like_event_content, ruma_response_from_json, test_json};
use ruma::{
Expand Down Expand Up @@ -683,7 +684,10 @@ async fn test_withheld_unverified() {
bob.try_decrypt_room_event(&room_event, room_id, &decryption_settings).await.unwrap();
assert_let!(RoomEventDecryptionResult::UnableToDecrypt(utd_info) = decrypt_result);
assert!(utd_info.session_id.is_some());
assert_eq!(utd_info.reason, UnableToDecryptReason::MissingMegolmSession);
assert_eq!(
utd_info.reason,
UnableToDecryptReason::MissingMegolmSession(Some(WithheldReason::TrustRequirementMismatch))
);
}

/// Test what happens when we feed an unencrypted event into the decryption
Expand Down Expand Up @@ -1362,7 +1366,7 @@ async fn test_unsigned_decryption() {
replace_encryption_result,
UnsignedDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
session_id: Some(second_room_key_session_id),
reason: UnableToDecryptReason::MissingMegolmSession,
reason: UnableToDecryptReason::MissingMegolmSession(None),
})
);

Expand Down Expand Up @@ -1468,7 +1472,7 @@ async fn test_unsigned_decryption() {
thread_encryption_result,
UnsignedDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
session_id: Some(third_room_key_session_id),
reason: UnableToDecryptReason::MissingMegolmSession,
reason: UnableToDecryptReason::MissingMegolmSession(None),
})
);

Expand Down
42 changes: 30 additions & 12 deletions crates/matrix-sdk-crypto/src/types/events/utd_cause.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

use matrix_sdk_common::deserialized_responses::{
UnableToDecryptInfo, UnableToDecryptReason, VerificationLevel,
UnableToDecryptInfo, UnableToDecryptReason, VerificationLevel, WithheldReason,
};
use ruma::{events::AnySyncTimelineEvent, serde::Raw, MilliSecondsSinceUnixEpoch};
use serde::Deserialize;
Expand Down Expand Up @@ -57,6 +57,18 @@ pub enum UtdCause {
/// be confused with pre-join or pre-invite messages (see
/// [`UtdCause::SentBeforeWeJoined`] for that).
HistoricalMessage = 5,

/// The keys for this event are intentionally withheld.
/// The sender has refused to share the key because our device does not meet
/// the sender's security requirements.
WithheldForUnverifiedOrInsecureDevice = 6,

/// The keys for this event are missing, likely because the sender was
/// unable to share them (e.g., failure to establish an Olm 1:1
/// channel). Alternatively, the sender may have deliberately excluded
/// this device by cherry-picking and blocking it, no action can be taken on
/// our side.
WithheldBySender = 7,
}

/// MSC4115 membership info in the unsigned area.
Expand Down Expand Up @@ -98,7 +110,13 @@ impl UtdCause {
) -> Self {
// TODO: in future, use more information to give a richer answer. E.g.
match unable_to_decrypt_info.reason {
UnableToDecryptReason::MissingMegolmSession
UnableToDecryptReason::MissingMegolmSession(Some(reason)) => match reason {
WithheldReason::TrustRequirementMismatch => {
UtdCause::WithheldForUnverifiedOrInsecureDevice
}
WithheldReason::Other => UtdCause::WithheldBySender,
},
UnableToDecryptReason::MissingMegolmSession(None)
| UnableToDecryptReason::UnknownMegolmMessageIndex => {
// Look in the unsigned area for a `membership` field.
if let Some(unsigned) =
Expand Down Expand Up @@ -162,7 +180,7 @@ mod tests {
some_crypto_context_info(),
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession,
reason: UnableToDecryptReason::MissingMegolmSession(None),
}
),
UtdCause::Unknown
Expand All @@ -178,7 +196,7 @@ mod tests {
some_crypto_context_info(),
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::Unknown
Expand All @@ -195,7 +213,7 @@ mod tests {
some_crypto_context_info(),
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::Unknown
Expand All @@ -212,7 +230,7 @@ mod tests {
some_crypto_context_info(),
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::Unknown
Expand All @@ -229,7 +247,7 @@ mod tests {
some_crypto_context_info(),
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::Unknown
Expand All @@ -246,7 +264,7 @@ mod tests {
some_crypto_context_info(),
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::SentBeforeWeJoined
Expand Down Expand Up @@ -280,7 +298,7 @@ mod tests {
some_crypto_context_info(),
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::SentBeforeWeJoined
Expand Down Expand Up @@ -356,7 +374,7 @@ mod tests {
older_than_event_device,
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::Unknown
Expand All @@ -375,7 +393,7 @@ mod tests {
newer_than_event_device,
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::HistoricalMessage
Expand Down Expand Up @@ -493,7 +511,7 @@ mod tests {
crypto_context_info,
&UnableToDecryptInfo {
session_id: None,
reason: UnableToDecryptReason::MissingMegolmSession
reason: UnableToDecryptReason::MissingMegolmSession(None)
}
),
UtdCause::Unknown
Expand Down
2 changes: 1 addition & 1 deletion crates/matrix-sdk-ui/src/timeline/tests/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ fn utd_event_with_unsigned(unsigned: serde_json::Value) -> SyncTimelineEvent {
raw,
matrix_sdk::deserialized_responses::UnableToDecryptInfo {
session_id: Some("SESSION_ID".into()),
reason: UnableToDecryptReason::MissingMegolmSession,
reason: UnableToDecryptReason::MissingMegolmSession(None),
},
)
}
5 changes: 4 additions & 1 deletion testing/matrix-sdk-test/src/event_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,10 @@ impl EventBuilder<RoomEncryptedEventContent> {

SyncTimelineEvent::new_utd_event(
self.into(),
UnableToDecryptInfo { session_id, reason: UnableToDecryptReason::MissingMegolmSession },
UnableToDecryptInfo {
session_id,
reason: UnableToDecryptReason::MissingMegolmSession(None),
},
)
}
}
Expand Down

0 comments on commit ea5fcdb

Please sign in to comment.