mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-19 06:04:31 -04:00
Merge pull request #5219 from matrix-org/rav/megolm_sender_verification_main
crypto: new `VerificationLevel::MismatchedSender`
This commit is contained in:
@@ -42,6 +42,8 @@ const VERIFICATION_VIOLATION: &str =
|
||||
"Encrypted by a previously-verified user who is no longer verified.";
|
||||
const UNSIGNED_DEVICE: &str = "Encrypted by a device not verified by its owner.";
|
||||
const UNKNOWN_DEVICE: &str = "Encrypted by an unknown or deleted device.";
|
||||
const MISMATCHED_SENDER: &str =
|
||||
"The sender of the event does not match the owner of the device that created the Megolm session.";
|
||||
pub const SENT_IN_CLEAR: &str = "Not encrypted.";
|
||||
|
||||
/// Represents the state of verification for a decrypted message sent by a
|
||||
@@ -117,6 +119,10 @@ impl VerificationState {
|
||||
message: AUTHENTICITY_NOT_GUARANTEED,
|
||||
},
|
||||
},
|
||||
VerificationLevel::MismatchedSender => ShieldState::Red {
|
||||
code: ShieldStateCode::MismatchedSender,
|
||||
message: MISMATCHED_SENDER,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -171,6 +177,10 @@ impl VerificationState {
|
||||
}
|
||||
}
|
||||
},
|
||||
VerificationLevel::MismatchedSender => ShieldState::Red {
|
||||
code: ShieldStateCode::MismatchedSender,
|
||||
message: MISMATCHED_SENDER,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -198,6 +208,10 @@ pub enum VerificationLevel {
|
||||
/// deleted) or because the key to decrypt the message was obtained from
|
||||
/// an insecure source.
|
||||
None(DeviceLinkProblem),
|
||||
|
||||
/// The `sender` field on the event does not match the owner of the device
|
||||
/// that established the Megolm session.
|
||||
MismatchedSender,
|
||||
}
|
||||
|
||||
impl fmt::Display for VerificationLevel {
|
||||
@@ -211,6 +225,7 @@ impl fmt::Display for VerificationLevel {
|
||||
"The sending device was not signed by the user's identity"
|
||||
}
|
||||
VerificationLevel::None(..) => "The sending device is not known",
|
||||
VerificationLevel::MismatchedSender => MISMATCHED_SENDER,
|
||||
};
|
||||
write!(f, "{display}")
|
||||
}
|
||||
@@ -271,6 +286,9 @@ pub enum ShieldStateCode {
|
||||
/// The sender was previously verified but changed their identity.
|
||||
#[serde(alias = "PreviouslyVerified")]
|
||||
VerificationViolation,
|
||||
/// The `sender` field on the event does not match the owner of the device
|
||||
/// that established the Megolm session.
|
||||
MismatchedSender,
|
||||
}
|
||||
|
||||
/// The algorithm specific information of a decrypted event.
|
||||
|
||||
@@ -6,17 +6,20 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased] - ReleaseDate
|
||||
|
||||
- [**breaking**] Add a new `VerificationLevel::MismatchedSender` to indicate that the sender of an event appears to have been tampered with.
|
||||
([#5219](https://github.com/matrix-org/matrix-rust-sdk/pull/5219))
|
||||
|
||||
## [0.12.0] - 2025-06-10
|
||||
|
||||
### Features
|
||||
|
||||
- [**breaking**] The `ProcessedToDeviceEvent::Decrypted` variant now also have an `EncryptionInfo` field.
|
||||
Format changed from `Decrypted(Raw<AnyToDeviceEvent>)` to `Decrypted { raw: Raw<AnyToDeviceEvent>, encryption_info: EncryptionInfo) }`
|
||||
([5074](https://github.com/matrix-org/matrix-rust-sdk/pull/5074))
|
||||
([#5074](https://github.com/matrix-org/matrix-rust-sdk/pull/5074))
|
||||
|
||||
- [**breaking**] Move `session_id` from `EncryptionInfo` to `AlgorithmInfo` as it is megolm specific.
|
||||
Use `EncryptionInfo::session_id()` helper for quick access.
|
||||
([4981](https://github.com/matrix-org/matrix-rust-sdk/pull/4981))
|
||||
([#4981](https://github.com/matrix-org/matrix-rust-sdk/pull/4981))
|
||||
|
||||
- Send stable identifier `sender_device_keys` for MSC4147 (Including device
|
||||
keys with Olm-encrypted events).
|
||||
|
||||
@@ -1663,14 +1663,7 @@ impl OlmMachine {
|
||||
// `DeviceLinkProblem` for `VerificationLevel::None`.
|
||||
let (verification_state, device_id) = match sender_data.user_id() {
|
||||
Some(i) if i != sender => {
|
||||
// For backwards compatibility, we treat this the same as "Unknown device".
|
||||
// TODO: use a dedicated VerificationLevel here.
|
||||
(
|
||||
VerificationState::Unverified(VerificationLevel::None(
|
||||
DeviceLinkProblem::MissingDevice,
|
||||
)),
|
||||
None,
|
||||
)
|
||||
(VerificationState::Unverified(VerificationLevel::MismatchedSender), None)
|
||||
}
|
||||
|
||||
Some(_) | None => {
|
||||
@@ -1967,6 +1960,7 @@ impl OlmMachine {
|
||||
|
||||
// Case 4
|
||||
(VerificationLevel::VerificationViolation, _)
|
||||
| (VerificationLevel::MismatchedSender, _)
|
||||
| (VerificationLevel::UnsignedDevice, false)
|
||||
| (VerificationLevel::None(_), false) => false,
|
||||
}
|
||||
@@ -1978,6 +1972,7 @@ impl OlmMachine {
|
||||
VerificationLevel::UnverifiedIdentity => true,
|
||||
|
||||
VerificationLevel::VerificationViolation
|
||||
| VerificationLevel::MismatchedSender
|
||||
| VerificationLevel::UnsignedDevice
|
||||
| VerificationLevel::None(_) => false,
|
||||
},
|
||||
@@ -2270,6 +2265,7 @@ impl OlmMachine {
|
||||
///
|
||||
/// * `event` - The event to get information for.
|
||||
/// * `room_id` - The ID of the room where the event was sent to.
|
||||
#[instrument(skip(self, event), fields(event_id, sender, session_id))]
|
||||
pub async fn get_room_event_encryption_info(
|
||||
&self,
|
||||
event: &Raw<EncryptedEvent>,
|
||||
@@ -2286,6 +2282,11 @@ impl OlmMachine {
|
||||
}
|
||||
};
|
||||
|
||||
Span::current()
|
||||
.record("sender", debug(&event.sender))
|
||||
.record("event_id", debug(&event.event_id))
|
||||
.record("session_id", content.session_id());
|
||||
|
||||
self.get_session_encryption_info(room_id, content.session_id(), &event.sender).await
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ use crate::{
|
||||
CrossSigningKey, DeviceKeys, EventEncryptionAlgorithm, MasterPubkey, SelfSigningPubkey,
|
||||
},
|
||||
utilities::json_convert,
|
||||
CryptoStoreError, DecryptionSettings, DeviceData, EncryptionSettings, LocalTrust, OlmMachine,
|
||||
OtherUserIdentityData, TrustRequirement, UserIdentity,
|
||||
CryptoStoreError, DecryptionSettings, DeviceData, EncryptionSettings, LocalTrust, MegolmError,
|
||||
OlmMachine, OtherUserIdentityData, TrustRequirement, UserIdentity,
|
||||
};
|
||||
|
||||
#[async_test]
|
||||
@@ -311,23 +311,37 @@ pub async fn mark_alice_identity_as_verified_test_helper(alice: &OlmMachine, bob
|
||||
.is_verified());
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_verification_states_spoofed_sender_untrusted() {
|
||||
test_verification_states_spoofed_sender(TrustRequirement::Untrusted).await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_verification_states_spoofed_sender_cross_signed() {
|
||||
test_verification_states_spoofed_sender(TrustRequirement::CrossSigned).await;
|
||||
}
|
||||
|
||||
/// Test that the verification state is set correctly when the sender of an
|
||||
/// event does not match the owner of the device that sent us the session.
|
||||
///
|
||||
/// In this test, Bob receives an event from Alice, but the HS admin has
|
||||
/// rewritten the `sender` of the event to look like another user.
|
||||
#[async_test]
|
||||
async fn test_verification_states_spoofed_sender() {
|
||||
///
|
||||
/// We run this test a couple of times, with different [`TrustRequirement`]s.
|
||||
async fn test_verification_states_spoofed_sender(
|
||||
sender_device_trust_requirement: TrustRequirement,
|
||||
) {
|
||||
let (alice, bob) = get_machine_pair_with_setup_sessions_test_helper(
|
||||
tests::alice_id(),
|
||||
tests::user_id(),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
bob.bootstrap_cross_signing(false).await.unwrap();
|
||||
set_up_alice_cross_signing(&alice, &bob).await;
|
||||
|
||||
let room_id = room_id!("!test:example.org");
|
||||
let decryption_settings =
|
||||
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
|
||||
let decryption_settings = DecryptionSettings { sender_device_trust_requirement };
|
||||
|
||||
// Alice sends a message to Bob.
|
||||
let (event, _) = encrypt_message(&alice, room_id, &bob, "Secret message").await;
|
||||
@@ -337,7 +351,7 @@ async fn test_verification_states_spoofed_sender() {
|
||||
let event_encryption_info = bob.get_room_event_encryption_info(&event, room_id).await.unwrap();
|
||||
assert_matches!(
|
||||
&event_encryption_info.verification_state,
|
||||
VerificationState::Unverified(VerificationLevel::UnsignedDevice)
|
||||
VerificationState::Unverified(VerificationLevel::UnverifiedIdentity)
|
||||
);
|
||||
|
||||
// Alice now sends a second message to Bob, using the same room key, but the HS
|
||||
@@ -360,18 +374,28 @@ async fn test_verification_states_spoofed_sender() {
|
||||
});
|
||||
let event = json_convert(&event).unwrap();
|
||||
|
||||
bob.decrypt_room_event(&event, room_id, &decryption_settings)
|
||||
.await
|
||||
.expect("Bob could not decrypt spoofed event");
|
||||
let decryption_result = bob.decrypt_room_event(&event, room_id, &decryption_settings).await;
|
||||
|
||||
// The verification_state of the event should be `MissingDevice` (since it
|
||||
// manifests as a message from Charlie which does not correspond to one of
|
||||
// Charlie's devices).
|
||||
let event_encryption_info = bob.get_room_event_encryption_info(&event, room_id).await.unwrap();
|
||||
assert_matches!(
|
||||
&event_encryption_info.verification_state,
|
||||
VerificationState::Unverified(VerificationLevel::None(DeviceLinkProblem::MissingDevice))
|
||||
);
|
||||
if matches!(sender_device_trust_requirement, TrustRequirement::Untrusted) {
|
||||
// In "Untrusted" mode, the event is decrypted correctly, but the
|
||||
// verification_state should be `MismatchedSender`.
|
||||
decryption_result.expect("Bob could not decrypt spoofed event");
|
||||
|
||||
let event_encryption_info =
|
||||
bob.get_room_event_encryption_info(&event, room_id).await.unwrap();
|
||||
assert_matches!(
|
||||
&event_encryption_info.verification_state,
|
||||
VerificationState::Unverified(VerificationLevel::MismatchedSender)
|
||||
);
|
||||
} else {
|
||||
// In "CrossSigned" mode, we refuse to decrypt the event altogether.
|
||||
let err =
|
||||
decryption_result.expect_err("Bob was unexpectedly able to decrypt spoofed event");
|
||||
assert_matches!(
|
||||
err,
|
||||
MegolmError::SenderIdentityNotTrusted(VerificationLevel::MismatchedSender)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
||||
Reference in New Issue
Block a user