-
Notifications
You must be signed in to change notification settings - Fork 354
Configurable sender trust checking on decrypting #3701
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
Changes from 30 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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |||||
| // See the License for the specific language governing permissions and | ||||||
| // limitations under the License. | ||||||
|
|
||||||
| 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; | ||||||
|
|
@@ -70,8 +71,34 @@ pub enum OlmError { | |||||
| have a valid Olm session with us" | ||||||
| )] | ||||||
| MissingSession, | ||||||
| #[error(transparent)] | ||||||
| /// The room key that should be shared was not due to an error. | ||||||
| KeyDistributionError(RoomKeyDistributionError), | ||||||
| } | ||||||
|
|
||||||
| /// Depending on the sharing strategy for room keys, the distribution of the | ||||||
| /// room key could fail. | ||||||
| #[derive(Error, Debug)] | ||||||
| pub enum RoomKeyDistributionError { | ||||||
| /// When encrypting using the IdentityBased strategy. | ||||||
| /// Will be thrown when sharing room keys when there is a new identity for a | ||||||
| /// user that has not been confirmed by the user. | ||||||
| /// Application should display identity changes to the user as soon as | ||||||
| /// possible to avoid hitting this case. If it happens the app might | ||||||
| /// just retry automatically after the identity change has been | ||||||
| /// notified, or offer option to cancel. | ||||||
| #[error("Encryption failed because there are key pinning violation, please re-pin or verify the problematic users")] | ||||||
| KeyPinningViolation(Vec<OwnedUserId>), | ||||||
|
|
||||||
| /// Cross-signing is required for encryption with invisible crypto | ||||||
| #[error("Encryption failed: Setup cross-signing on your account")] | ||||||
| CrossSigningNotSetup, | ||||||
| /// The current device needs to be verified when encrypting using the | ||||||
| /// IdentityBased strategy. Apps should prevent sending in the UI to | ||||||
| /// avoid hitting this case. | ||||||
| #[error("Encryption failed: Verify your device to send encrypted messages")] | ||||||
| SendingFromUnverifiedDevice, | ||||||
| } | ||||||
| /// Error representing a failure during a group encryption operation. | ||||||
| #[derive(Error, Debug)] | ||||||
| pub enum MegolmError { | ||||||
|
|
@@ -107,6 +134,10 @@ pub enum MegolmError { | |||||
| /// The storage layer returned an error. | ||||||
| #[error(transparent)] | ||||||
| Store(#[from] CryptoStoreError), | ||||||
|
|
||||||
| /// The sender's cross-signing identity isn't trusted | ||||||
|
Member
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
Member
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}")] | ||||||
|
Member
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), | ||||||
|
Member
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,61 @@ impl OlmMachine { | |
| self.get_encryption_info(&session, &event.sender).await | ||
| } | ||
|
|
||
| /// Check whether the sender of a Megolm session is trusted. | ||
| /// | ||
| /// Checks that the device is cross-signed, that the sender's identity is | ||
| /// cross-signed, and that the sender's identity is pinned. If | ||
| /// `require_verified` is `true`, then also checks if we have verified the | ||
| /// sender's identity | ||
| fn check_sender_trusted( | ||
| &self, | ||
| encryption_info: &EncryptionInfo, | ||
| require_verified: bool, | ||
| ) -> MegolmResult<()> { | ||
| match dbg!(&encryption_info.verification_state) { | ||
| VerificationState::Verified => Ok(()), | ||
| VerificationState::Unverified(VerificationLevel::UnverifiedIdentity) => { | ||
| if require_verified { | ||
| Err(MegolmError::SenderIdentity(VerificationLevel::UnverifiedIdentity)) | ||
| } else { | ||
| Ok(()) | ||
| } | ||
| } | ||
| 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, | ||
|
Member
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 { | ||
| SenderData::SenderKnown { .. } => Ok(()), | ||
| SenderData::DeviceInfo { legacy_session: true, .. } => Ok(()), | ||
| SenderData::UnknownDevice { legacy_session: true, .. } => Ok(()), | ||
| _ => self.check_sender_trusted(encryption_info, false), | ||
| }, | ||
| TrustRequirement::CrossSigned => match session.sender_data { | ||
| SenderData::SenderKnown { .. } => Ok(()), | ||
| _ => self.check_sender_trusted(encryption_info, false), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| 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 +1637,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 +1702,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 +1713,7 @@ impl OlmMachine { | |
| event: &Raw<EncryptedEvent>, | ||
| room_id: &RoomId, | ||
| decrypt_unsigned: bool, | ||
| decryption_settings: &DecryptionSettings, | ||
| ) -> MegolmResult<TimelineEvent> { | ||
| let event = event.deserialize()?; | ||
|
|
||
|
|
@@ -1685,7 +1741,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 +1767,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 +1795,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 +1805,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 +1818,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 +1839,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 +1853,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 +2424,12 @@ fn sender_data_to_verification_state( | |
| VerificationState::Unverified(VerificationLevel::UnsignedDevice), | ||
| Some(device_keys.device_id), | ||
| ), | ||
| SenderData::SenderKnown { | ||
| master_key_verified: false, | ||
| identity_needs_user_approval: true, | ||
| device_id, | ||
| .. | ||
| } => (VerificationState::Unverified(VerificationLevel::ChangedIdentity), device_id), | ||
| SenderData::SenderKnown { master_key_verified: false, device_id, .. } => { | ||
| (VerificationState::Unverified(VerificationLevel::UnverifiedIdentity), device_id) | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.