Skip to content

Commit 756d507

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

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-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: 83 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,84 @@ 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 decryption_settings =
755+
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
756+
757+
let group_session = bob
758+
.store()
759+
.with_transaction(|mut tr| async {
760+
let res = bob
761+
.decrypt_to_device_event(
762+
&mut tr,
763+
&event,
764+
&mut Changes::default(),
765+
&decryption_settings,
766+
)
767+
.await?;
768+
Ok((tr, res))
769+
})
770+
.await
771+
.unwrap()
772+
.inbound_group_session
773+
.unwrap();
774+
let sessions = std::slice::from_ref(&group_session);
775+
bob.store().save_inbound_group_sessions(sessions).await.unwrap();
776+
777+
let plaintext = "It is a secret to everybody";
778+
779+
let content = RoomTopicEventContent::new(plaintext.to_owned());
780+
781+
let encrypted_content =
782+
alice.encrypt_state_event(room_id, content, EmptyStateKey).await.unwrap();
783+
784+
let event = json!({
785+
"event_id": "$xxxxx:example.org",
786+
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
787+
"sender": alice.user_id(),
788+
"type": "m.room.encrypted",
789+
"content": encrypted_content,
790+
});
791+
792+
let event = json_convert(&event).unwrap();
793+
794+
let decryption_settings =
795+
DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
796+
797+
let decryption_result =
798+
bob.try_decrypt_room_event(&event, room_id, &decryption_settings).await.unwrap();
799+
assert_let!(RoomEventDecryptionResult::Decrypted(decrypted_event) = decryption_result);
800+
let decrypted_event = decrypted_event.event.deserialize().unwrap();
801+
802+
if let AnyTimelineEvent::State(AnyStateEvent::RoomTopic(StateEvent::Original(
803+
OriginalRoomTopicEvent { sender, content, .. },
804+
))) = decrypted_event
805+
{
806+
assert_eq!(&sender, alice.user_id());
807+
assert_eq!(&content.topic, plaintext);
808+
} else {
809+
panic!("Decrypted room event has the wrong type");
810+
}
811+
}
812+
730813
#[async_test]
731814
async fn test_withheld_unverified() {
732815
let (alice, bob) =

0 commit comments

Comments
 (0)