-
Notifications
You must be signed in to change notification settings - Fork 260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Configurable sender trust checking on decrypting #3701
Changes from all commits
bb7383d
e08dfab
9415308
67af31a
8253618
b7241c3
4d66828
c045d26
bfb8818
fb39a9e
1d4cefb
de60e6e
3731190
7803617
9b00de3
e309219
945f844
f0e9a18
7254e7e
3f7b840
ee9edf9
62531c8
b8b8c8b
a09ede9
73300aa
0a8acce
b0994a5
c53674b
773c8fe
4cbfd6b
65ba067
e85bf3b
14d1e07
e5c0c06
24a3ebb
a119d79
fa61076
368699a
0a1f80b
04c81e3
0e9b581
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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; | ||||||
|
@@ -115,6 +116,10 @@ pub enum MegolmError { | |||||
/// The storage layer returned an error. | ||||||
#[error(transparent)] | ||||||
Store(#[from] CryptoStoreError), | ||||||
|
||||||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// The sender's cross-signing identity isn't trusted | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(This should maybe also link to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, isn't it more "because the message could not be linked to a verified device" than specific to the sender's identity? |
||||||
#[error("message quarantined because sender's cross-signing identity is not trusted: {0}")] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what does "quarantined" mean here? Is it stored somewhere within the store? |
||||||
SenderIdentity(#[from] VerificationLevel), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
/// Decryption failed because of a mismatch between the identity keys of the | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,6 +77,7 @@ use crate::{ | |
StoreCache, StoreTransaction, | ||
}, | ||
types::{ | ||
decryption::{DecryptionSettings, TrustRequirement}, | ||
events::{ | ||
olm_v1::{AnyDecryptedOlmEvent, DecryptedRoomKeyEvent}, | ||
room::encrypted::{ | ||
|
@@ -934,8 +935,6 @@ impl OlmMachine { | |
&self, | ||
room_id: &RoomId, | ||
) -> OlmResult<()> { | ||
use crate::olm::SenderData; | ||
|
||
let (_, session) = self | ||
.inner | ||
.group_session_manager | ||
|
@@ -957,8 +956,6 @@ impl OlmMachine { | |
&self, | ||
room_id: &RoomId, | ||
) -> OlmResult<InboundGroupSession> { | ||
use crate::olm::SenderData; | ||
|
||
let (_, session) = self | ||
.inner | ||
.group_session_manager | ||
|
@@ -1570,11 +1567,69 @@ impl OlmMachine { | |
self.get_encryption_info(&session, &event.sender).await | ||
} | ||
|
||
/// Check whether the sender of a Megolm session is trusted, based on the | ||
/// verification state. | ||
/// | ||
/// This is used by `check_sender_trust_requirement`, and ensures that the | ||
/// sending device is cross-signed. | ||
fn check_sender_trusted(&self, encryption_info: &EncryptionInfo) -> MegolmResult<()> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. per a discussion today in |
||
match &encryption_info.verification_state { | ||
// Device is cross-signed, and identity is verified | ||
VerificationState::Verified => Ok(()), | ||
// Device is cross-signed, but identity is not verified | ||
VerificationState::Unverified(VerificationLevel::UnverifiedIdentity) => Ok(()), | ||
// Device is not cross-signed | ||
VerificationState::Unverified(verification_level) => { | ||
Err(MegolmError::SenderIdentity(verification_level.clone())) | ||
} | ||
} | ||
} | ||
|
||
/// 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, | ||
decryption_settings: &DecryptionSettings, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this just checks the trust requirement, maybe it should take a |
||
) -> MegolmResult<()> { | ||
match decryption_settings.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::SenderKnown { | ||
master_key_verified: false, | ||
previously_verified: true, | ||
.. | ||
} => Err(MegolmError::SenderIdentity(VerificationLevel::PreviouslyVerified)), | ||
SenderData::SenderKnown { .. } => Ok(()), | ||
SenderData::DeviceInfo { legacy_session: true, .. } => Ok(()), | ||
SenderData::UnknownDevice { legacy_session: true, .. } => Ok(()), | ||
_ => self.check_sender_trusted(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::SenderKnown { | ||
master_key_verified: false, | ||
previously_verified: true, | ||
.. | ||
} => Err(MegolmError::SenderIdentity(VerificationLevel::PreviouslyVerified)), | ||
SenderData::SenderKnown { .. } => Ok(()), | ||
_ => self.check_sender_trusted(encryption_info), | ||
}, | ||
Comment on lines
+1599
to
+1623
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm finding all this rather hard to follow, and I think it could do with some more comments, at the very least. As far as I can tell, the general gist is that (with the exception of I'm probably being stupid, but can you add some comments to explain this more clearly? (also I wonder if we could reduce the duplication between the |
||
} | ||
} | ||
|
||
async fn decrypt_megolm_events( | ||
&self, | ||
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?; | ||
|
@@ -1590,6 +1645,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, | ||
)?; | ||
|
||
Ok((decrypted_event, encryption_info)) | ||
} | ||
Err(error) => Err( | ||
|
@@ -1648,8 +1710,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))] | ||
|
@@ -1658,6 +1721,7 @@ impl OlmMachine { | |
event: &Raw<EncryptedEvent>, | ||
room_id: &RoomId, | ||
decrypt_unsigned: bool, | ||
decryption_settings: &DecryptionSettings, | ||
) -> MegolmResult<TimelineEvent> { | ||
let event = event.deserialize()?; | ||
|
||
|
@@ -1685,7 +1749,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")] | ||
|
@@ -1710,8 +1775,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())?; | ||
|
@@ -1737,6 +1803,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< | ||
|
@@ -1746,7 +1813,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); | ||
|
@@ -1757,7 +1826,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) | ||
|
@@ -1778,6 +1847,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?; | ||
|
@@ -1791,7 +1861,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()?; | ||
|
@@ -2359,6 +2432,12 @@ fn sender_data_to_verification_state( | |
VerificationState::Unverified(VerificationLevel::UnsignedDevice), | ||
Some(device_keys.device_id), | ||
), | ||
SenderData::SenderKnown { | ||
master_key_verified: false, | ||
previously_verified: true, | ||
device_id, | ||
.. | ||
} => (VerificationState::Unverified(VerificationLevel::PreviouslyVerified), device_id), | ||
Comment on lines
+2435
to
+2440
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't this be opt-in, according to the |
||
SenderData::SenderKnown { master_key_verified: false, device_id, .. } => { | ||
(VerificationState::Unverified(VerificationLevel::UnverifiedIdentity), device_id) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably also document some of the changes to publicly-visible types (
MegolmError
,VerificationLevel
, etc)