Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions bindings/matrix-sdk-crypto-ffi/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use matrix_sdk_crypto::{
decrypt_room_key_export, encrypt_room_key_export,
olm::ExportedRoomKey,
store::{BackupDecryptionKey, Changes},
LocalTrust, OlmMachine as InnerMachine, ToDeviceRequest, UserIdentities,
DecryptionSettings, LocalTrust, OlmMachine as InnerMachine, ToDeviceRequest, TrustRequirement,
UserIdentities,
};
use ruma::{
api::{
Expand Down Expand Up @@ -881,7 +882,13 @@ impl OlmMachine {
let event: Raw<_> = serde_json::from_str(&event)?;
let room_id = RoomId::parse(room_id)?;

let decrypted = self.runtime.block_on(self.inner.decrypt_room_event(&event, &room_id))?;
let decryption_settings =
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
let decrypted = self.runtime.block_on(self.inner.decrypt_room_event(
&event,
&room_id,
&decryption_settings,
))?;

if handle_verification_events {
if let Ok(AnyTimelineEvent::MessageLike(e)) = decrypted.event.deserialize() {
Expand Down
8 changes: 5 additions & 3 deletions crates/matrix-sdk-base/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use eyeball_im::{Vector, VectorDiff};
use futures_util::Stream;
#[cfg(feature = "e2e-encryption")]
use matrix_sdk_crypto::{
store::DynCryptoStore, CollectStrategy, EncryptionSettings, EncryptionSyncChanges, OlmError,
OlmMachine, ToDeviceRequest,
store::DynCryptoStore, CollectStrategy, DecryptionSettings, EncryptionSettings,
EncryptionSyncChanges, OlmError, OlmMachine, ToDeviceRequest, TrustRequirement,
};
#[cfg(feature = "e2e-encryption")]
use ruma::events::{
Expand Down Expand Up @@ -324,8 +324,10 @@ impl BaseClient {
let olm = self.olm_machine().await;
let Some(olm) = olm.as_ref() else { return Ok(None) };

let decryption_settings =
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
let event: SyncTimelineEvent =
olm.decrypt_room_event(event.cast_ref(), room_id).await?.into();
olm.decrypt_room_event(event.cast_ref(), room_id, &decryption_settings).await?.into();

if let Ok(AnySyncTimelineEvent::MessageLike(e)) = event.event.deserialize() {
match &e {
Expand Down
16 changes: 16 additions & 0 deletions crates/matrix-sdk-common/src/deserialized_responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,22 @@ pub enum VerificationLevel {
None(DeviceLinkProblem),
}

impl fmt::Display for VerificationLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let display = match self {
VerificationLevel::UnverifiedIdentity => "The sender's identity was not verified",
VerificationLevel::PreviouslyVerified => {
"The sender's identity was previously verified but has changed"
}
VerificationLevel::UnsignedDevice => {
"The sending device was not signed by the user's identity"
}
VerificationLevel::None(..) => "The sending device is not known",
};
write!(f, "{}", display)
}
}

/// The sub-enum containing detailed information on why we were not able to link
/// a message back to a device.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
Expand Down
8 changes: 8 additions & 0 deletions crates/matrix-sdk-crypto/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ Breaking changes:
the CryptoStore, meaning that, once upgraded, it will not be possible to roll
back applications to earlier versions without breaking user sessions.

- `OlmMachine::decrypt_room_event` now takes a `DecryptionSettings` argument,
which includes a `TrustRequirement` indicating the required trust level for
the sending device. When it is called with `TrustRequirement` other than
`TrustRequirement::Unverified`, it may return the new
`MegolmError::SenderIdentityNotTrusted` variant if the sending device does not
satisfy the required trust level.
([#3899](https://github.com/matrix-org/matrix-rust-sdk/pull/3899))

- Change the structure of the `SenderData` enum to separate variants for
previously-verified, unverified and verified.
([#3877](https://github.com/matrix-org/matrix-rust-sdk/pull/3877))
Expand Down
7 changes: 7 additions & 0 deletions crates/matrix-sdk-crypto/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use std::collections::BTreeMap;

use matrix_sdk_common::deserialized_responses::VerificationLevel;
use ruma::{CanonicalJsonError, IdParseError, OwnedDeviceId, OwnedRoomId, OwnedUserId};
use serde::{ser::SerializeMap, Serializer};
use serde_json::Error as SerdeError;
Expand Down Expand Up @@ -115,6 +116,12 @@ pub enum MegolmError {
/// The storage layer returned an error.
#[error(transparent)]
Store(#[from] CryptoStoreError),

/// An encrypted message wasn't decrypted, because the sender's
/// cross-signing identity did not satisfy the requested
/// [`crate::TrustRequirement`].
#[error("decryption failed because trust requirement not satisfied: {0}")]
SenderIdentityNotTrusted(VerificationLevel),
}

/// Decryption failed because of a mismatch between the identity keys of the
Expand Down
23 changes: 23 additions & 0 deletions crates/matrix-sdk-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ pub use requests::{
IncomingResponse, KeysBackupRequest, KeysQueryRequest, OutgoingRequest, OutgoingRequests,
OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest, UploadSigningKeysRequest,
};
use serde::{Deserialize, Serialize};
pub use session_manager::CollectStrategy;
pub use store::{
CrossSigningKeyExport, CryptoStoreError, SecretImportError, SecretInfo, TrackedUser,
Expand All @@ -112,3 +113,25 @@ matrix_sdk_test::init_tracing_for_tests!();

#[cfg(feature = "uniffi")]
uniffi::setup_scaffolding!();

/// The trust level in the sender's device that is required to decrypt an
/// event.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub enum TrustRequirement {
/// Decrypt events from everyone regardless of trust.
Untrusted,
/// Only decrypt events from cross-signed devices or legacy sessions (Megolm
/// sessions created before we started collecting trust information).
CrossSignedOrLegacy,
/// Only decrypt events from cross-signed devices.
CrossSigned,
}

/// Settings for decrypting messages
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DecryptionSettings {
/// The trust level in the sender's device that is required to decrypt the
/// event. If the sender's device is not sufficiently trusted,
/// [`MegolmError::SenderIdentityNotTrusted`] will be returned.
pub sender_device_trust_requirement: TrustRequirement,
}
90 changes: 77 additions & 13 deletions crates/matrix-sdk-crypto/src/machine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ use crate::{
},
utilities::timestamp_to_iso8601,
verification::{Verification, VerificationMachine, VerificationRequest},
CrossSigningKeyExport, CryptoStoreError, DeviceData, KeysQueryRequest, LocalTrust,
SignatureError, ToDeviceRequest,
CrossSigningKeyExport, CryptoStoreError, DecryptionSettings, DeviceData, KeysQueryRequest,
LocalTrust, SignatureError, ToDeviceRequest, TrustRequirement,
};

/// State machine implementation of the Olm/Megolm encryption protocol used for
Expand Down Expand Up @@ -934,8 +934,6 @@ impl OlmMachine {
&self,
room_id: &RoomId,
) -> OlmResult<()> {
use crate::olm::SenderData;

let (_, session) = self
.inner
.group_session_manager
Expand All @@ -957,8 +955,6 @@ impl OlmMachine {
&self,
room_id: &RoomId,
) -> OlmResult<InboundGroupSession> {
use crate::olm::SenderData;

let (_, session) = self
.inner
.group_session_manager
Expand Down Expand Up @@ -1593,6 +1589,7 @@ impl OlmMachine {
room_id: &RoomId,
event: &EncryptedEvent,
content: &SupportedEventEncryptionSchemes<'_>,
decryption_settings: &DecryptionSettings,
) -> MegolmResult<(JsonObject, EncryptionInfo)> {
let session =
self.get_inbound_group_session_or_error(room_id, content.session_id()).await?;
Expand All @@ -1608,6 +1605,13 @@ impl OlmMachine {
match result {
Ok((decrypted_event, _)) => {
let encryption_info = self.get_encryption_info(&session, &event.sender).await?;

self.check_sender_trust_requirement(
&session,
&encryption_info,
&decryption_settings.sender_device_trust_requirement,
)?;

Ok((decrypted_event, encryption_info))
}
Err(error) => Err(
Expand All @@ -1632,6 +1636,55 @@ impl OlmMachine {
}
}

/// Check that the sender of a Megolm session satisfies the trust
/// requirement from the decryption settings.
fn check_sender_trust_requirement(
&self,
session: &InboundGroupSession,
encryption_info: &EncryptionInfo,
trust_requirement: &TrustRequirement,
) -> MegolmResult<()> {
/// Get the error from the encryption information.
fn encryption_info_to_error(encryption_info: &EncryptionInfo) -> MegolmResult<()> {
// When this is called, the verification state *must* be unverified,
// otherwise the sender_data would have been SenderVerified
let VerificationState::Unverified(verification_level) =
&encryption_info.verification_state
else {
unreachable!("inconsistent verification state");
};
Err(MegolmError::SenderIdentityNotTrusted(verification_level.clone()))
}

match trust_requirement {
TrustRequirement::Untrusted => Ok(()),

TrustRequirement::CrossSignedOrLegacy => match &session.sender_data {
// Reject if the sender was previously verified, but changed
// their identity and is not verified any more.
SenderData::SenderUnverifiedButPreviouslyVerified(..) => Err(
MegolmError::SenderIdentityNotTrusted(VerificationLevel::PreviouslyVerified),
),
SenderData::SenderUnverified(..) => Ok(()),
SenderData::SenderVerified(..) => Ok(()),
SenderData::DeviceInfo { legacy_session: true, .. } => Ok(()),
SenderData::UnknownDevice { legacy_session: true, .. } => Ok(()),
_ => encryption_info_to_error(encryption_info),
},

TrustRequirement::CrossSigned => match &session.sender_data {
// Reject if the sender was previously verified, but changed
// their identity and is not verified any more.
SenderData::SenderUnverifiedButPreviouslyVerified(..) => Err(
MegolmError::SenderIdentityNotTrusted(VerificationLevel::PreviouslyVerified),
),
SenderData::SenderUnverified(..) => Ok(()),
SenderData::SenderVerified(..) => Ok(()),
_ => encryption_info_to_error(encryption_info),
},
}
}

/// Attempt to retrieve an inbound group session from the store.
///
/// If the session is not found, checks for withheld reports, and returns a
Expand Down Expand Up @@ -1666,8 +1719,9 @@ impl OlmMachine {
&self,
event: &Raw<EncryptedEvent>,
room_id: &RoomId,
decryption_settings: &DecryptionSettings,
) -> MegolmResult<TimelineEvent> {
self.decrypt_room_event_inner(event, room_id, true).await
self.decrypt_room_event_inner(event, room_id, true, decryption_settings).await
}

#[instrument(name = "decrypt_room_event", skip_all, fields(?room_id, event_id, origin_server_ts, sender, algorithm, session_id, sender_key))]
Expand All @@ -1676,6 +1730,7 @@ impl OlmMachine {
event: &Raw<EncryptedEvent>,
room_id: &RoomId,
decrypt_unsigned: bool,
decryption_settings: &DecryptionSettings,
) -> MegolmResult<TimelineEvent> {
let event = event.deserialize()?;

Expand Down Expand Up @@ -1703,7 +1758,8 @@ impl OlmMachine {
};

Span::current().record("session_id", content.session_id());
let result = self.decrypt_megolm_events(room_id, &event, &content).await;
let result =
self.decrypt_megolm_events(room_id, &event, &content, decryption_settings).await;

if let Err(e) = &result {
#[cfg(feature = "automatic-room-key-forwarding")]
Expand All @@ -1728,8 +1784,9 @@ impl OlmMachine {
let mut unsigned_encryption_info = None;
if decrypt_unsigned {
// Try to decrypt encrypted unsigned events.
unsigned_encryption_info =
self.decrypt_unsigned_events(&mut decrypted_event, room_id).await;
unsigned_encryption_info = self
.decrypt_unsigned_events(&mut decrypted_event, room_id, decryption_settings)
.await;
}

let event = serde_json::from_value::<Raw<AnyTimelineEvent>>(decrypted_event.into())?;
Expand All @@ -1755,6 +1812,7 @@ impl OlmMachine {
&self,
main_event: &mut JsonObject,
room_id: &RoomId,
decryption_settings: &DecryptionSettings,
) -> Option<BTreeMap<UnsignedEventLocation, UnsignedDecryptionResult>> {
let unsigned = main_event.get_mut("unsigned")?.as_object_mut()?;
let mut unsigned_encryption_info: Option<
Expand All @@ -1764,7 +1822,9 @@ impl OlmMachine {
// Search for an encrypted event in `m.replace`, an edit.
let location = UnsignedEventLocation::RelationsReplace;
let replace = location.find_mut(unsigned);
if let Some(decryption_result) = self.decrypt_unsigned_event(replace, room_id).await {
if let Some(decryption_result) =
self.decrypt_unsigned_event(replace, room_id, decryption_settings).await
{
unsigned_encryption_info
.get_or_insert_with(Default::default)
.insert(location, decryption_result);
Expand All @@ -1775,7 +1835,7 @@ impl OlmMachine {
let location = UnsignedEventLocation::RelationsThreadLatestEvent;
let thread_latest_event = location.find_mut(unsigned);
if let Some(decryption_result) =
self.decrypt_unsigned_event(thread_latest_event, room_id).await
self.decrypt_unsigned_event(thread_latest_event, room_id, decryption_settings).await
{
unsigned_encryption_info
.get_or_insert_with(Default::default)
Expand All @@ -1796,6 +1856,7 @@ impl OlmMachine {
&'a self,
event: Option<&'a mut Value>,
room_id: &'a RoomId,
decryption_settings: &'a DecryptionSettings,
) -> BoxFuture<'a, Option<UnsignedDecryptionResult>> {
Box::pin(async move {
let event = event?;
Expand All @@ -1809,7 +1870,10 @@ impl OlmMachine {
}

let raw_event = serde_json::from_value(event.clone()).ok()?;
match self.decrypt_room_event_inner(&raw_event, room_id, false).await {
match self
.decrypt_room_event_inner(&raw_event, room_id, false, decryption_settings)
.await
{
Ok(decrypted_event) => {
// Replace the encrypted event.
*event = serde_json::to_value(decrypted_event.event).ok()?;
Expand Down
Loading