Skip to content

Commit a2b8756

Browse files
committed
feat(crypto): Add state event encryption methods to OlmMachine
Signed-off-by: Skye Elliot <[email protected]>
1 parent c5fc7b1 commit a2b8756

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#[cfg(feature = "experimental-encrypted-state-events")]
16+
use std::borrow::Borrow;
1517
use std::{
1618
collections::{BTreeMap, HashMap, HashSet},
1719
sync::Arc,
@@ -31,6 +33,8 @@ use matrix_sdk_common::{
3133
locks::RwLock as StdRwLock,
3234
BoxFuture,
3335
};
36+
#[cfg(feature = "experimental-encrypted-state-events")]
37+
use ruma::events::{AnyStateEventContent, StateEventContent};
3438
use ruma::{
3539
api::client::{
3640
dehydrated_device::DehydratedDeviceData,
@@ -1102,6 +1106,66 @@ impl OlmMachine {
11021106
self.inner.group_session_manager.encrypt(room_id, event_type, content).await
11031107
}
11041108

1109+
/// Encrypt a state event for the given room.
1110+
///
1111+
/// # Arguments
1112+
///
1113+
/// * `room_id` - The id of the room for which the event should be
1114+
/// encrypted.
1115+
///
1116+
/// * `content` - The plaintext content of the event that should be
1117+
/// encrypted.
1118+
///
1119+
/// * `state_key` - The associated state key of the event.
1120+
#[cfg(feature = "experimental-encrypted-state-events")]
1121+
pub async fn encrypt_state_event<C, K>(
1122+
&self,
1123+
room_id: &RoomId,
1124+
content: C,
1125+
state_key: K,
1126+
) -> MegolmResult<Raw<RoomEncryptedEventContent>>
1127+
where
1128+
C: StateEventContent,
1129+
C::StateKey: Borrow<K>,
1130+
K: AsRef<str>,
1131+
{
1132+
let event_type = content.event_type().to_string();
1133+
let content = Raw::new(&content)?.cast_unchecked();
1134+
self.encrypt_state_event_raw(room_id, &event_type, state_key.as_ref(), &content).await
1135+
}
1136+
1137+
/// Encrypt a state event for the given state event using its raw JSON
1138+
/// content and state key.
1139+
///
1140+
/// This method is equivalent to [`OlmMachine::encrypt_state_event`]
1141+
/// method but operates on an arbitrary JSON value instead of strongly-typed
1142+
/// event content struct.
1143+
///
1144+
/// # Arguments
1145+
///
1146+
/// * `room_id` - The id of the room for which the message should be
1147+
/// encrypted.
1148+
///
1149+
/// * `event_type` - The type of the event.
1150+
///
1151+
/// * `state_key` - The associated state key of the event.
1152+
///
1153+
/// * `content` - The plaintext content of the event that should be
1154+
/// encrypted as a raw JSON value.
1155+
#[cfg(feature = "experimental-encrypted-state-events")]
1156+
pub async fn encrypt_state_event_raw(
1157+
&self,
1158+
room_id: &RoomId,
1159+
event_type: &str,
1160+
state_key: &str,
1161+
content: &Raw<AnyStateEventContent>,
1162+
) -> MegolmResult<Raw<RoomEncryptedEventContent>> {
1163+
self.inner
1164+
.group_session_manager
1165+
.encrypt_state(room_id, event_type, state_key, content)
1166+
.await
1167+
}
1168+
11051169
/// Forces the currently active room key, which is used to encrypt messages,
11061170
/// to be rotated.
11071171
///

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ use matrix_sdk_common::{
2626
executor::spawn,
2727
};
2828
use matrix_sdk_test::{async_test, message_like_event_content, ruma_response_from_json, test_json};
29+
#[cfg(feature = "experimental-encrypted-state-events")]
30+
use ruma::events::{
31+
room::topic::{OriginalRoomTopicEvent, RoomTopicEventContent},
32+
StateEvent,
33+
};
2934
use ruma::{
3035
api::client::{
3136
keys::{get_keys, upload_keys},
@@ -727,6 +732,104 @@ async fn test_megolm_encryption() {
727732
}
728733
}
729734

735+
#[cfg(feature = "experimental-encrypted-state-events")]
736+
#[async_test]
737+
async fn test_megolm_state_encryption() {
738+
use ruma::events::{AnyStateEvent, EmptyStateKey};
739+
740+
let (alice, bob) =
741+
get_machine_pair_with_setup_sessions_test_helper(alice_id(), user_id(), false).await;
742+
let room_id = room_id!("!test:example.org");
743+
744+
let to_device_requests = alice
745+
.share_room_key(room_id, iter::once(bob.user_id()), EncryptionSettings::default())
746+
.await
747+
.unwrap();
748+
749+
let event = ToDeviceEvent::new(
750+
alice.user_id().to_owned(),
751+
to_device_requests_to_content(to_device_requests),
752+
);
753+
754+
let mut room_keys_received_stream = Box::pin(bob.store().room_keys_received_stream());
755+
756+
let decryption_settings =
757+
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
758+
759+
let group_session = bob
760+
.store()
761+
.with_transaction(|mut tr| async {
762+
let res = bob
763+
.decrypt_to_device_event(
764+
&mut tr,
765+
&event,
766+
&mut Changes::default(),
767+
&decryption_settings,
768+
)
769+
.await?;
770+
Ok((tr, res))
771+
})
772+
.await
773+
.unwrap()
774+
.inbound_group_session
775+
.unwrap();
776+
let sessions = std::slice::from_ref(&group_session);
777+
bob.store().save_inbound_group_sessions(sessions).await.unwrap();
778+
779+
// when we decrypt the room key, the
780+
// inbound_group_session_streamroom_keys_received_stream should tell us
781+
// about it.
782+
let room_keys = room_keys_received_stream
783+
.next()
784+
.now_or_never()
785+
.flatten()
786+
.expect("We should have received an update of room key infos")
787+
.unwrap();
788+
assert_eq!(room_keys.len(), 1);
789+
assert_eq!(room_keys[0].session_id, group_session.session_id());
790+
791+
let plaintext = "It is a secret to everybody";
792+
793+
let content = RoomTopicEventContent::new(plaintext.to_owned());
794+
795+
let encrypted_content =
796+
alice.encrypt_state_event(room_id, content, EmptyStateKey).await.unwrap();
797+
798+
let event = json!({
799+
"event_id": "$xxxxx:example.org",
800+
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
801+
"sender": alice.user_id(),
802+
"type": "m.room.encrypted",
803+
"content": encrypted_content,
804+
});
805+
806+
let event = json_convert(&event).unwrap();
807+
808+
let decryption_settings =
809+
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
810+
811+
let decryption_result =
812+
bob.try_decrypt_room_event(&event, room_id, &decryption_settings).await.unwrap();
813+
assert_let!(RoomEventDecryptionResult::Decrypted(decrypted_event) = decryption_result);
814+
let decrypted_event = decrypted_event.event.deserialize().unwrap();
815+
816+
if let AnyTimelineEvent::State(AnyStateEvent::RoomTopic(StateEvent::Original(
817+
OriginalRoomTopicEvent { sender, content, .. },
818+
))) = decrypted_event
819+
{
820+
assert_eq!(&sender, alice.user_id());
821+
assert_eq!(&content.topic, plaintext);
822+
} else {
823+
panic!("Decrypted room event has the wrong type");
824+
}
825+
826+
// Just decrypting the event should *not* cause an update on the
827+
// inbound_group_session_stream.
828+
if let Some(igs) = room_keys_received_stream.next().now_or_never() {
829+
panic!("Session stream unexpectedly returned update: {igs:?}");
830+
}
831+
}
832+
730833
#[async_test]
731834
async fn test_withheld_unverified() {
732835
let (alice, bob) =

0 commit comments

Comments
 (0)