-
Notifications
You must be signed in to change notification settings - Fork 331
feat: Encrypted state events (MSC3414) #5456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
aa63ada
2173e36
c5fabf5
3192d17
bbf047d
7e2c076
bde0ef1
9ef0419
1eef067
d63f0ee
89b367f
cd56ffc
7023b68
53d3a2e
a51bec6
61d40bd
d6e15b6
b707517
a8b7d11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,8 @@ use serde::Deserialize; | |
use tracing::warn; | ||
|
||
use super::Context; | ||
#[cfg(feature = "e2e-encryption")] | ||
use super::e2ee; | ||
use crate::store::BaseStateStore; | ||
|
||
/// Collect [`AnySyncStateEvent`]. | ||
|
@@ -89,6 +91,7 @@ pub mod sync { | |
ambiguity_cache: &mut AmbiguityCache, | ||
new_users: &mut U, | ||
state_store: &BaseStateStore, | ||
#[cfg(feature = "e2e-encryption")] e2ee: super::e2ee::E2EE<'_>, | ||
) -> StoreResult<()> | ||
where | ||
U: NewUsers, | ||
|
@@ -142,6 +145,80 @@ pub mod sync { | |
} | ||
} | ||
|
||
#[cfg(feature = "e2e-encryption")] | ||
AnySyncStateEvent::RoomEncrypted(outer) => { | ||
use matrix_sdk_crypto::RoomEventDecryptionResult; | ||
use tracing::{debug, warn}; | ||
|
||
debug!(event_id = ?outer.event_id(), "Received encrypted state event, attempting decryption..."); | ||
|
||
let Some(olm_machine) = e2ee.olm_machine else { | ||
panic!(); | ||
}; | ||
|
||
let decrypted_event = olm_machine | ||
.try_decrypt_room_event( | ||
raw_event.cast_ref_unchecked(), | ||
&room_info.room_id, | ||
e2ee.decryption_settings, | ||
) | ||
.await | ||
.expect("OlmMachine was not started"); | ||
|
||
// Skip state events that failed to decrypt. | ||
let RoomEventDecryptionResult::Decrypted(room_event) = decrypted_event else { | ||
warn!(event_id = ?outer.event_id(), "Failed to decrypt state event"); | ||
continue; | ||
}; | ||
|
||
// Unpack event type and state key from outer, or discard if this fails. | ||
let Some((outer_event_type, outer_state_key)) = | ||
outer.state_key().split_once(":") | ||
else { | ||
warn!( | ||
event_id = outer.event_id().as_str(), | ||
state_key = event.state_key(), | ||
"Malformed state key" | ||
); | ||
continue; | ||
}; | ||
|
||
let inner = | ||
match room_event.event.deserialize_as_unchecked::<AnySyncStateEvent>() { | ||
Ok(inner) => inner, | ||
Err(e) => { | ||
warn!("Malformed event body: {e}"); | ||
continue; | ||
} | ||
}; | ||
|
||
// Check event types match, discard if not. | ||
let inner_event_type = inner.event_type().to_string(); | ||
if outer_event_type != inner_event_type { | ||
warn!( | ||
event_id = outer.event_id().as_str(), | ||
expected = outer_event_type, | ||
found = inner_event_type, | ||
"Mismatched event type" | ||
); | ||
continue; | ||
} | ||
|
||
// Check state keys match, discard if not. | ||
if outer_state_key != inner.state_key() { | ||
warn!( | ||
event_id = outer.event_id().as_str(), | ||
expected = outer_state_key, | ||
found = inner.state_key(), | ||
"Mismatched state key" | ||
); | ||
continue; | ||
} | ||
|
||
debug!(event_id = ?outer.event_id(), "Decrypted state event successfully."); | ||
room_info.handle_state_event(&inner); | ||
Comment on lines
+150
to
+219
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this looks a bit complicated to be in the middle of a |
||
} | ||
|
||
_ => { | ||
room_info.handle_state_event(event); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
// limitations under the License. | ||
|
||
use std::{ | ||
borrow::Borrow, | ||
collections::{BTreeMap, HashMap, HashSet}, | ||
sync::Arc, | ||
time::Duration, | ||
|
@@ -44,8 +45,9 @@ use ruma::{ | |
}, | ||
assign, | ||
events::{ | ||
room::encrypted::unstable_state::StateRoomEncryptedEventContent, | ||
secret::request::SecretName, AnyMessageLikeEvent, AnyMessageLikeEventContent, | ||
AnyToDeviceEvent, MessageLikeEventContent, | ||
AnyStateEventContent, AnyToDeviceEvent, MessageLikeEventContent, StateEventContent, | ||
}, | ||
serde::{JsonObject, Raw}, | ||
DeviceId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OwnedDeviceId, OwnedDeviceKeyId, | ||
|
@@ -1100,6 +1102,62 @@ impl OlmMachine { | |
self.inner.group_session_manager.encrypt(room_id, event_type, content).await | ||
} | ||
|
||
/// Encrypt a state event for the given room. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `room_id` - The id of the room for which the event should be | ||
/// encrypted. | ||
/// | ||
/// * `content` - The plaintext content of the event that should be | ||
/// encrypted. | ||
/// | ||
/// * `state_key` - The associated state key of the event. | ||
pub async fn encrypt_state_event<C, K>( | ||
&self, | ||
room_id: &RoomId, | ||
content: C, | ||
state_key: K, | ||
) -> MegolmResult<Raw<StateRoomEncryptedEventContent>> | ||
where | ||
C: StateEventContent, | ||
C::StateKey: Borrow<K>, | ||
K: AsRef<str>, | ||
{ | ||
let event_type = content.event_type().to_string(); | ||
let content = Raw::new(&content)?.cast_unchecked(); | ||
self.encrypt_state_event_raw(room_id, &event_type, state_key.as_ref(), &content).await | ||
} | ||
|
||
/// Encrypt a state event for the given state event using its raw JSON | ||
/// content and state key. | ||
/// | ||
/// This method is equivalent to [`OlmMachine::encrypt_state_event`] | ||
/// method but operates on an arbitrary JSON value instead of strongly-typed | ||
/// event content struct. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `room_id` - The id of the room for which the message should be | ||
/// encrypted. | ||
/// | ||
/// * `content` - The plaintext content of the event that should be | ||
/// encrypted as a raw JSON value. | ||
/// | ||
/// * `state_key` - The associated state key of the event. | ||
Comment on lines
+1141
to
+1147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
pub async fn encrypt_state_event_raw( | ||
&self, | ||
room_id: &RoomId, | ||
event_type: &str, | ||
state_key: &str, | ||
content: &Raw<AnyStateEventContent>, | ||
) -> MegolmResult<Raw<StateRoomEncryptedEventContent>> { | ||
self.inner | ||
.group_session_manager | ||
.encrypt_state(room_id, event_type, state_key, content) | ||
.await | ||
} | ||
|
||
/// Forces the currently active room key, which is used to encrypt messages, | ||
/// to be rotated. | ||
/// | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This functionality belongs in the crypto crate: it is not specific to /sync, but should happen whereever we decrypt a state event.
It seems like we we need an
olm_machine.decrypt_state_event
which does these checks somehow. Not sure exactly what the types for that look like off the top of my head: see if you can figure something out, if not give me a ping to see if we can figure something out together.