Skip to content

Commit a3065e2

Browse files
committed
feat(crypto): Refuse to decrypt to-device messages from unverified devices (when in exclude insecure mode)
1 parent 92d064e commit a3065e2

File tree

4 files changed

+387
-14
lines changed

4 files changed

+387
-14
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,15 @@ pub enum ProcessedToDeviceEvent {
11961196
/// required information to be processed (like no event `type` for
11971197
/// example)
11981198
Invalid(Raw<AnyToDeviceEvent>),
1199+
1200+
/// A to device event that was ignored because the sender device was not
1201+
/// verified.
1202+
UnverifiedSender {
1203+
/// The raw decrypted event
1204+
raw: Raw<AnyToDeviceEvent>,
1205+
/// The Olm encryption info
1206+
encryption_info: EncryptionInfo,
1207+
},
11991208
}
12001209

12011210
impl ProcessedToDeviceEvent {
@@ -1207,6 +1216,7 @@ impl ProcessedToDeviceEvent {
12071216
ProcessedToDeviceEvent::UnableToDecrypt(event) => event.clone(),
12081217
ProcessedToDeviceEvent::PlainText(event) => event.clone(),
12091218
ProcessedToDeviceEvent::Invalid(event) => event.clone(),
1219+
ProcessedToDeviceEvent::UnverifiedSender { raw, .. } => raw.clone(),
12101220
}
12111221
}
12121222

@@ -1217,6 +1227,7 @@ impl ProcessedToDeviceEvent {
12171227
ProcessedToDeviceEvent::UnableToDecrypt(event) => event,
12181228
ProcessedToDeviceEvent::PlainText(event) => event,
12191229
ProcessedToDeviceEvent::Invalid(event) => event,
1230+
ProcessedToDeviceEvent::UnverifiedSender { raw, .. } => raw,
12201231
}
12211232
}
12221233
}

crates/matrix-sdk-crypto/CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ All notable changes to this project will be documented in this file.
1111
- [**breaking**] Add a new `VerificationLevel::MismatchedSender` to indicate that the sender of an event appears to have been tampered with.
1212
([#5219](https://github.com/matrix-org/matrix-rust-sdk/pull/5219))
1313

14+
- [**breaking**]: When in "exclude insecure devices" mode, refuse to decrypt
15+
incoming to-device messages from unverified devices, except for some
16+
exceptions for certain event types (see the documentation on
17+
`OlmMachine::decrypt_to_device_event` for details). To support this, a new
18+
variant has been added to `ProcessedToDeviceEvent`: `UnverifiedSender`, which
19+
is returned from `OlmMachine::receive_encrypted_to_device_event` when we are
20+
excluding insecure devices and the sender's device is not verified. Also,
21+
several methods now take a `DecryptionSettings` argument to allow controlling
22+
the processing of to-device events based on those settings. To recreate the
23+
previous behaviour pass in: `DecryptionSettings {
24+
sender_device_trust_requirement: TrustRequirement::Untrusted }`. Affected
25+
methods are `OlmMachine::receive_sync_changes`,
26+
`RehydratedDevice::receive_events`, and several internal methods.
27+
([#5319](https://github.com/matrix-org/matrix-rust-sdk/pull/5319)
28+
1429
### Refactor
1530

1631
- [**breaking**] The `PendingChanges`, `Changes`, `StoredRoomKeyBundleData`,

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

Lines changed: 144 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -845,9 +845,17 @@ impl OlmMachine {
845845

846846
/// Decrypt and handle a to-device event.
847847
///
848-
/// If decryption (or checking the sender device) fails, returns
848+
/// If decryption (or checking the sender device) fails, returns an
849849
/// `Err(DecryptToDeviceError::OlmError)`.
850850
///
851+
/// If we are in strict "exclude insecure devices" mode and the sender
852+
/// device is not verified, and the decrypted event type is not on the
853+
/// allow list, returns `Err(DecryptToDeviceError::UnverifiedSender)`
854+
///
855+
/// (The allow list of types that are processed even if the sender is
856+
/// unverified is: `m.room_key`, `m.room_key.withheld`,
857+
/// `m.room_key_request`, `m.secret.request` and `m.key.verification.*`.)
858+
///
851859
/// If the sender device is dehydrated, does no handling and immediately
852860
/// returns `Err(DecryptToDeviceError::FromDehydratedDevice)`.
853861
///
@@ -862,12 +870,17 @@ impl OlmMachine {
862870
transaction: &mut StoreTransaction,
863871
event: &EncryptedToDeviceEvent,
864872
changes: &mut Changes,
865-
_decryption_settings: &DecryptionSettings,
873+
decryption_settings: &DecryptionSettings,
866874
) -> Result<OlmDecryptionInfo, DecryptToDeviceError> {
867875
// Decrypt the event
868-
let mut decrypted =
876+
let decrypted =
869877
transaction.account().await?.decrypt_to_device_event(&self.inner.store, event).await?;
870878

879+
let mut decrypted = self.check_to_device_is_from_verified_device_or_allowed_type(
880+
decryption_settings,
881+
decrypted,
882+
)?;
883+
871884
// Return early if the sending device is decrypted
872885
self.check_to_device_event_is_not_from_dehydrated_device(&decrypted, &event.sender).await?;
873886

@@ -1324,7 +1337,10 @@ impl OlmMachine {
13241337

13251338
match event {
13261339
// These are handled here because we accept them either plaintext or
1327-
// encrypted
1340+
// encrypted.
1341+
//
1342+
// Note: this list should match the allowed types in
1343+
// check_to_device_is_from_verified_device_or_allowed_type
13281344
RoomKeyRequest(e) => self.inner.key_request_machine.receive_incoming_key_request(e),
13291345
SecretRequest(e) => self.inner.key_request_machine.receive_incoming_secret_request(e),
13301346
RoomKeyWithheld(e) => self.add_withheld_info(changes, e),
@@ -1426,8 +1442,14 @@ impl OlmMachine {
14261442
///
14271443
/// Return the same event, decrypted if possible.
14281444
///
1429-
/// If we can identify that this to-device event came from a dehydrated
1430-
/// device, this method does not process it, and returns `None`.
1445+
/// If we are in strict "exclude insecure devices" mode and the sender
1446+
/// device is not verified, and the decrypted event type is not on the
1447+
/// allow list, or if this event comes from a dehydrated device, this method
1448+
/// does not process it, and returns `None`.
1449+
///
1450+
/// (The allow list of types that are processed even if the sender is
1451+
/// unverified is: `m.room_key`, `m.room_key.withheld`,
1452+
/// `m.room_key_request`, `m.secret.request` and `m.key.verification.*`.)
14311453
async fn receive_encrypted_to_device_event(
14321454
&self,
14331455
transaction: &mut StoreTransaction,
@@ -1456,6 +1478,12 @@ impl OlmMachine {
14561478
return Some(ProcessedToDeviceEvent::UnableToDecrypt(raw_event));
14571479
}
14581480
Err(DecryptToDeviceError::FromDehydratedDevice) => return None,
1481+
Err(DecryptToDeviceError::UnverifiedSender(olm_decryption_info)) => {
1482+
return Some(ProcessedToDeviceEvent::UnverifiedSender {
1483+
raw: olm_decryption_info.result.raw_event,
1484+
encryption_info: olm_decryption_info.result.encryption_info,
1485+
})
1486+
}
14591487
};
14601488

14611489
// New sessions modify the account so we need to save that
@@ -1493,6 +1521,66 @@ impl OlmMachine {
14931521
})
14941522
}
14951523

1524+
fn check_to_device_is_from_verified_device_or_allowed_type(
1525+
&self,
1526+
decryption_settings: &DecryptionSettings,
1527+
decrypted: OlmDecryptionInfo,
1528+
) -> Result<OlmDecryptionInfo, DecryptToDeviceError> {
1529+
let event_type = decrypted.result.event.event_type();
1530+
1531+
// If we're in "exclude insecure devices" mode, we prevent most
1532+
// to-device events with unverified senders from being allowed
1533+
// through here, but there are some exceptions:
1534+
//
1535+
// * m.room_key - we hold on to these until later, so if the sender becomes
1536+
// verified later we can still use the key.
1537+
//
1538+
// * m.room_key_request, m.room_key.withheld, m.key.verification.*,
1539+
// m.secret.request - these are allowed as plaintext events, so we also allow
1540+
// them encrypted from insecure devices. Note: the list of allowed types here
1541+
// should match with what is allowed in handle_to_device_event.
1542+
match event_type {
1543+
"m.room_key"
1544+
| "m.room_key.withheld"
1545+
| "m.room_key_request"
1546+
| "m.secret.request"
1547+
| "m.key.verification.key"
1548+
| "m.key.verification.mac"
1549+
| "m.key.verification.done"
1550+
| "m.key.verification.ready"
1551+
| "m.key.verification.start"
1552+
| "m.key.verification.accept"
1553+
| "m.key.verification.cancel"
1554+
| "m.key.verification.request" => {
1555+
// This is one of the exception types - we allow it even if the sender device is
1556+
// not verified.
1557+
Ok(decrypted)
1558+
}
1559+
_ => {
1560+
// This is not an exception type - check for "exclude insecure devices" mode,
1561+
// and whether the sender is verified.
1562+
1563+
let exclude_unverified_devices = match decryption_settings
1564+
.sender_device_trust_requirement
1565+
{
1566+
TrustRequirement::Untrusted => false,
1567+
TrustRequirement::CrossSignedOrLegacy | TrustRequirement::CrossSigned => true,
1568+
};
1569+
1570+
if exclude_unverified_devices
1571+
&& sending_device_is_unverified(&decrypted.result.encryption_info)
1572+
{
1573+
// Refuse to decrypt - the sender is unverified
1574+
Err(DecryptToDeviceError::UnverifiedSender(Box::new(decrypted)))
1575+
} else {
1576+
// Either we're not excluding insecure devices, or the sender is verified, so
1577+
// allow decryption to continue.
1578+
Ok(decrypted)
1579+
}
1580+
}
1581+
}
1582+
}
1583+
14961584
/// Return an error if the supplied to-device event was sent from a
14971585
/// dehydrated device.
14981586
async fn check_to_device_event_is_not_from_dehydrated_device(
@@ -1594,6 +1682,15 @@ impl OlmMachine {
15941682
/// If any of the to-device events in the supplied changes were sent from
15951683
/// dehydrated devices, these are not processed, and are omitted from
15961684
/// the returned list, as per MSC3814.
1685+
///
1686+
/// If we are in strict "exclude insecure devices" mode and the sender
1687+
/// device of any event is not verified, and the decrypted event type is not
1688+
/// on the allow list, these events are not processed and are omitted from
1689+
/// the returned list.
1690+
///
1691+
/// (The allow list of types that are processed even if the sender is
1692+
/// unverified is: `m.room_key`, `m.room_key.withheld`,
1693+
/// `m.room_key_request`, `m.secret.request` and `m.key.verification.*`.)
15971694
pub(crate) async fn preprocess_sync_changes(
15981695
&self,
15991696
transaction: &mut StoreTransaction,
@@ -2842,6 +2939,28 @@ impl OlmMachine {
28422939
}
28432940
}
28442941

2942+
fn sending_device_is_unverified(encryption_info: &EncryptionInfo) -> bool {
2943+
if let VerificationState::Unverified(level) = &encryption_info.verification_state {
2944+
// Sender is not fully verified
2945+
match level {
2946+
VerificationLevel::UnverifiedIdentity => {
2947+
// The user's identity is only pinned: fine
2948+
false
2949+
}
2950+
VerificationLevel::VerificationViolation
2951+
| VerificationLevel::UnsignedDevice
2952+
| VerificationLevel::None(_)
2953+
| VerificationLevel::MismatchedSender => {
2954+
// The device is not verified or the user is in verification violation: not fine
2955+
true
2956+
}
2957+
}
2958+
} else {
2959+
// Verified sender: fine
2960+
false
2961+
}
2962+
}
2963+
28452964
fn sender_data_to_verification_state(
28462965
sender_data: SenderData,
28472966
session_has_been_imported: bool,
@@ -2960,14 +3079,27 @@ fn megolm_error_to_utd_info(
29603079
Ok(UnableToDecryptInfo { session_id, reason })
29613080
}
29623081

2963-
/// An error that can occur during [`OlmMachine::decrypt_to_device_event`] -
2964-
/// either because decryption failed, or because the sender device was a
2965-
/// dehydrated device, which should never send any to-device messages.
3082+
/// An error that can occur during [`OlmMachine::decrypt_to_device_event`]:
3083+
///
3084+
/// * because decryption failed, or
3085+
///
3086+
/// * because the sender device was not verified when we are in strict "exclude
3087+
/// insecure devices" mode, or
3088+
///
3089+
/// * because the sender device was a dehydrated device, which should never send
3090+
/// any to-device messages.
29663091
#[derive(Debug, thiserror::Error)]
29673092
pub(crate) enum DecryptToDeviceError {
29683093
#[error("An Olm error occurred meaning we failed to decrypt the event")]
29693094
OlmError(#[from] OlmError),
29703095

3096+
#[error(
3097+
"We were able to decrypt the event, but the sender device was not \
3098+
verified, and we have 'exclude insecure devices' enabled, so we \
3099+
choose not to use this event."
3100+
)]
3101+
UnverifiedSender(Box<OlmDecryptionInfo>),
3102+
29713103
#[error("The event was sent from a dehydrated device")]
29723104
FromDehydratedDevice,
29733105
}
@@ -2988,6 +3120,9 @@ impl From<DecryptToDeviceError> for OlmError {
29883120
DecryptToDeviceError::FromDehydratedDevice => {
29893121
panic!("Expected an OlmError but found FromDehydratedDevice")
29903122
}
3123+
DecryptToDeviceError::UnverifiedSender(_) => {
3124+
panic!("Expected and OlmError but found UnverifiedSender")
3125+
}
29913126
}
29923127
}
29933128
}

0 commit comments

Comments
 (0)