fix(crypto): Report inner-outer state key differences as invalid.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
This commit is contained in:
Skye Elliot
2025-09-12 16:29:55 +01:00
committed by Damir Jelić
parent bdc564bb55
commit acaff39594
2 changed files with 93 additions and 8 deletions

View File

@@ -2297,17 +2297,10 @@ impl OlmMachine {
) -> MegolmResult<()> {
use serde::Deserialize;
// We only need to verify state events.
let Some(raw_state_key) = &original.state_key else { return Ok(()) };
// Unpack event type and state key from the raw state key.
let (outer_event_type, outer_state_key) =
raw_state_key.split_once(":").ok_or(MegolmError::StateKeyVerificationFailed)?;
// Helper for deserializing.
#[derive(Deserialize)]
struct PayloadDeserializationHelper {
state_key: String,
state_key: Option<String>,
#[serde(rename = "type")]
event_type: String,
}
@@ -2320,6 +2313,17 @@ impl OlmMachine {
.deserialize_as_unchecked()
.map_err(|_| MegolmError::StateKeyVerificationFailed)?;
// Ensure we have a state key on the outer event iff there is one in the inner.
let (raw_state_key, inner_state_key) = match (&original.state_key, &inner_state_key) {
(Some(raw_state_key), Some(inner_state_key)) => (raw_state_key, inner_state_key),
(None, None) => return Ok(()),
_ => return Err(MegolmError::StateKeyVerificationFailed),
};
// Unpack event type and state key from the raw state key.
let (outer_event_type, outer_state_key) =
raw_state_key.split_once(":").ok_or(MegolmError::StateKeyVerificationFailed)?;
// Check event types match, discard if not.
if outer_event_type != inner_event_type {
return Err(MegolmError::StateKeyVerificationFailed);

View File

@@ -920,6 +920,87 @@ async fn test_megolm_state_encryption_bad_state_key() {
);
}
#[cfg(feature = "experimental-encrypted-state-events")]
#[async_test]
async fn test_megolm_state_encryption_outer_state_key_no_inner() {
let room_id = room_id!("!test:example.org");
let (alice, bob) = megolm_encryption_setup_helper(room_id).await;
// Construct an inner message-like event and encrypt it.
let plaintext = "It is a secret to everybody";
let content = RoomMessageEventContent::text_plain(plaintext);
let encrypted_content = alice
.encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content))
.await
.unwrap();
// Construct an outer event that has `state_key` defined.
let event = json!({
"event_id": "$xxxxx:example.org",
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"state_key": "m.room.message:",
"content": encrypted_content,
});
let event = json_convert(&event).unwrap();
let decryption_settings =
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
let decryption_result =
bob.try_decrypt_room_event(&event, room_id, &decryption_settings).await.unwrap();
assert_matches!(
decryption_result,
RoomEventDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
reason: UnableToDecryptReason::StateKeyVerificationFailed,
..
})
);
}
#[cfg(feature = "experimental-encrypted-state-events")]
#[async_test]
async fn test_megolm_state_encryption_inner_state_key_no_outer() {
use ruma::events::EmptyStateKey;
let room_id = room_id!("!test:example.org");
let (alice, bob) = megolm_encryption_setup_helper(room_id).await;
// Construct an inner state event (with state key) and encrypt it.
let plaintext = "It is a secret to everybody";
let content = RoomTopicEventContent::new(plaintext.to_owned());
let encrypted_content =
alice.encrypt_state_event(room_id, content, EmptyStateKey).await.unwrap();
// Construct an outer event without a state key.
let event = json!({
"event_id": "$xxxxx:example.org",
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"content": encrypted_content,
});
let event = json_convert(&event).unwrap();
let decryption_settings =
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
let decryption_result =
bob.try_decrypt_room_event(&event, room_id, &decryption_settings).await.unwrap();
assert_matches!(
decryption_result,
RoomEventDecryptionResult::UnableToDecrypt(UnableToDecryptInfo {
reason: UnableToDecryptReason::StateKeyVerificationFailed,
..
})
);
}
#[async_test]
async fn test_withheld_unverified() {
let (alice, bob) =