diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs index 3fd522267..f8ecb766f 100644 --- a/crates/matrix-sdk-crypto/src/machine/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/mod.rs @@ -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, #[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); diff --git a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs index 259b34544..a70f8bbcf 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs @@ -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) =