Skip to content

Commit 17818dd

Browse files
committed
feat: Add naive state key verification to OlmMachine
Modifies `OlmMachine::decrypt_room_event_inner` to call a new method `OlmMachine::verify_packed_state_key` which, if the event is a state event, verifies that the original event's state key, when unpacked, matches the state key and event type in the decrypted event content. Introduces MegolmError::StateKeyVerificationFailed and UnableToDecryptReason::StateKeyVerificationFailed which are thrown when the verification fails. Signed-off-by: Skye Elliot <[email protected]>
1 parent a2b8756 commit 17818dd

File tree

6 files changed

+76
-3
lines changed

6 files changed

+76
-3
lines changed

crates/matrix-sdk-common/src/deserialized_responses.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,11 @@ pub enum UnableToDecryptReason {
998998
/// cross-signing identity did not satisfy the requested
999999
/// `TrustRequirement`.
10001000
SenderIdentityNotTrusted(VerificationLevel),
1001+
1002+
/// The outer state key could not be verified against the inner encrypted
1003+
/// state key and type.
1004+
#[cfg(feature = "experimental-encrypted-state-events")]
1005+
StateKeyVerificationFailed,
10011006
}
10021007

10031008
impl UnableToDecryptReason {

crates/matrix-sdk-crypto/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ pub enum MegolmError {
133133
/// The nested value is the sender's current verification level.
134134
#[error("decryption failed because trust requirement not satisfied: {0}")]
135135
SenderIdentityNotTrusted(VerificationLevel),
136+
137+
/// The outer state key could not be verified against the inner encrypted
138+
/// state key and type.
139+
#[cfg(feature = "experimental-encrypted-state-events")]
140+
#[error("decryption failed because the state key failed to validate")]
141+
StateKeyVerificationFailed,
136142
}
137143

138144
/// Decryption failed because of a mismatch between the identity keys of the

crates/matrix-sdk-crypto/src/gossiping/machine.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,8 @@ mod tests {
13641364
EncryptedEvent {
13651365
sender: sender.to_owned(),
13661366
event_id: event_id!("$143273582443PhrSn:example.org").to_owned(),
1367+
#[cfg(feature = "experimental-encrypted-state-events")]
1368+
state_key: None,
13671369
content,
13681370
origin_server_ts: ruma::MilliSecondsSinceUnixEpoch::now(),
13691371
unsigned: Default::default(),

crates/matrix-sdk-crypto/src/machine/mod.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use matrix_sdk_common::{
3434
BoxFuture,
3535
};
3636
#[cfg(feature = "experimental-encrypted-state-events")]
37-
use ruma::events::{AnyStateEventContent, StateEventContent};
37+
use ruma::events::{AnyStateEvent, AnyStateEventContent, StateEventContent};
3838
use ruma::{
3939
api::client::{
4040
dehydrated_device::DehydratedDeviceData,
@@ -2261,9 +2261,62 @@ impl OlmMachine {
22612261
.await;
22622262
}
22632263

2264-
let event = serde_json::from_value::<Raw<AnyTimelineEvent>>(decrypted_event.into())?;
2264+
let decrypted_event =
2265+
serde_json::from_value::<Raw<AnyTimelineEvent>>(decrypted_event.into())?;
22652266

2266-
Ok(DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info })
2267+
#[cfg(feature = "experimental-encrypted-state-events")]
2268+
self.verify_packed_state_key(&event, &decrypted_event)?;
2269+
2270+
Ok(DecryptedRoomEvent { event: decrypted_event, encryption_info, unsigned_encryption_info })
2271+
}
2272+
2273+
/// If the passed event is a state event, verify its outer packed state key
2274+
/// matches the inner state key once unpacked.
2275+
///
2276+
/// * `original` - The original encrypted event received over the wire.
2277+
/// * `decrypted` - The decrypted event.
2278+
///
2279+
/// # Errors
2280+
///
2281+
/// Returns an error if
2282+
///
2283+
/// * The original event's state key failed to unpack;
2284+
/// * The decrypted event could not be deserialised;
2285+
/// * The unpacked event type does not match the type of the decrypted
2286+
/// event;
2287+
/// * The unpacked event state key does not match the state key of the
2288+
/// decrypted event.
2289+
#[cfg(feature = "experimental-encrypted-state-events")]
2290+
pub fn verify_packed_state_key(
2291+
&self,
2292+
original: &EncryptedEvent,
2293+
decrypted: &Raw<AnyTimelineEvent>,
2294+
) -> MegolmResult<()> {
2295+
// We only need to verify state events.
2296+
let Some(raw_state_key) = &original.state_key else { return Ok(()) };
2297+
2298+
// Unpack event type and state key from the raw state key.
2299+
let (outer_event_type, outer_state_key) =
2300+
raw_state_key.split_once(":").ok_or(MegolmError::StateKeyVerificationFailed)?;
2301+
2302+
// Deserialize the decrypted event.
2303+
let AnyTimelineEvent::State(inner) =
2304+
decrypted.deserialize().map_err(MegolmError::JsonError)?
2305+
else {
2306+
return Err(MegolmError::StateKeyVerificationFailed);
2307+
};
2308+
2309+
// Check event types match, discard if not.
2310+
let inner_event_type = inner.event_type().to_string();
2311+
if outer_event_type != inner_event_type {
2312+
return Err(MegolmError::StateKeyVerificationFailed);
2313+
}
2314+
2315+
// Check state keys match, discard if not.
2316+
if outer_state_key != inner.state_key() {
2317+
return Err(MegolmError::StateKeyVerificationFailed);
2318+
}
2319+
Ok(())
22672320
}
22682321

22692322
/// Try to decrypt the events bundled in the `unsigned` object of the given
@@ -3034,6 +3087,8 @@ fn megolm_error_to_utd_info(
30343087
JsonError(_) => UnableToDecryptReason::PayloadDeserializationFailure,
30353088
MismatchedIdentityKeys(_) => UnableToDecryptReason::MismatchedIdentityKeys,
30363089
SenderIdentityNotTrusted(level) => UnableToDecryptReason::SenderIdentityNotTrusted(level),
3090+
#[cfg(feature = "experimental-encrypted-state-events")]
3091+
StateKeyVerificationFailed => UnableToDecryptReason::StateKeyVerificationFailed,
30373092

30383093
// Pass through crypto store errors, which indicate a problem with our
30393094
// application, rather than a UTD.

crates/matrix-sdk-crypto/src/machine/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ async fn test_megolm_state_encryption() {
800800
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
801801
"sender": alice.user_id(),
802802
"type": "m.room.encrypted",
803+
"state_key": "m.room.topic:",
803804
"content": encrypted_content,
804805
});
805806

crates/matrix-sdk-crypto/src/types/events/room/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ where
3636
/// The globally unique identifier for this event.
3737
pub event_id: OwnedEventId,
3838

39+
/// Present if and only if this event is a state event.
40+
#[cfg(feature = "experimental-encrypted-state-events")]
41+
pub state_key: Option<String>,
42+
3943
/// The body of this event, as created by the client which sent it.
4044
pub content: C,
4145

0 commit comments

Comments
 (0)