Skip to content

Commit 4a93d4a

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 161b165 commit 4a93d4a

File tree

6 files changed

+74
-4
lines changed

6 files changed

+74
-4
lines changed

crates/matrix-sdk-common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ rustdoc-args = ["--generate-link-to-definition"]
1919
[features]
2020
js = ["wasm-bindgen-futures"]
2121
uniffi = ["dep:uniffi"]
22+
experimental-encrypted-state-events = []
2223
# Private feature, see
2324
# https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823 for the gory
2425
# details.

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/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
1818
default = []
1919
automatic-room-key-forwarding = []
2020
experimental-send-custom-to-device = []
21-
experimental-encrypted-state-events = []
21+
experimental-encrypted-state-events = ["matrix-sdk-common/experimental-encrypted-state-events"]
2222
js = ["ruma/js", "vodozemac/js", "matrix-sdk-common/js"]
2323
qrcode = ["dep:matrix-sdk-qrcode"]
2424
experimental-algorithms = []

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/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,
@@ -2259,9 +2259,62 @@ impl OlmMachine {
22592259
.await;
22602260
}
22612261

2262-
let event = serde_json::from_value::<Raw<AnyTimelineEvent>>(decrypted_event.into())?;
2262+
let decrypted_event =
2263+
serde_json::from_value::<Raw<AnyTimelineEvent>>(decrypted_event.into())?;
22632264

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

22672320
/// Try to decrypt the events bundled in the `unsigned` object of the given
@@ -3032,6 +3085,8 @@ fn megolm_error_to_utd_info(
30323085
JsonError(_) => UnableToDecryptReason::PayloadDeserializationFailure,
30333086
MismatchedIdentityKeys(_) => UnableToDecryptReason::MismatchedIdentityKeys,
30343087
SenderIdentityNotTrusted(level) => UnableToDecryptReason::SenderIdentityNotTrusted(level),
3088+
#[cfg(feature = "experimental-encrypted-state-events")]
3089+
StateKeyVerificationFailed => UnableToDecryptReason::StateKeyVerificationFailed,
30353090

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ 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+
pub state_key: Option<String>,
41+
3942
/// The body of this event, as created by the client which sent it.
4043
pub content: C,
4144

0 commit comments

Comments
 (0)