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 ;
1517use 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 } ;
3438use 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 ///
@@ -2197,9 +2261,72 @@ impl OlmMachine {
21972261 . await ;
21982262 }
21992263
2200- 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 ( ) ) ?;
2266+
2267+ #[ cfg( feature = "experimental-encrypted-state-events" ) ]
2268+ self . verify_packed_state_key ( & event, & decrypted_event) ?;
22012269
2202- Ok ( DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info } )
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 any of the following are true:
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+ fn verify_packed_state_key (
2291+ & self ,
2292+ original : & EncryptedEvent ,
2293+ decrypted : & Raw < AnyTimelineEvent > ,
2294+ ) -> MegolmResult < ( ) > {
2295+ use serde:: Deserialize ;
2296+
2297+ // We only need to verify state events.
2298+ let Some ( raw_state_key) = & original. state_key else { return Ok ( ( ) ) } ;
2299+
2300+ // Unpack event type and state key from the raw state key.
2301+ let ( outer_event_type, outer_state_key) =
2302+ raw_state_key. split_once ( ":" ) . ok_or ( MegolmError :: StateKeyVerificationFailed ) ?;
2303+
2304+ // Helper for deserializing.
2305+ #[ derive( Deserialize ) ]
2306+ struct PayloadDeserializationHelper {
2307+ state_key : String ,
2308+ #[ serde( rename = "type" ) ]
2309+ event_type : String ,
2310+ }
2311+
2312+ // Deserialize the decrypted event.
2313+ let PayloadDeserializationHelper {
2314+ state_key : inner_state_key,
2315+ event_type : inner_event_type,
2316+ } = decrypted
2317+ . deserialize_as_unchecked ( )
2318+ . map_err ( |_| MegolmError :: StateKeyVerificationFailed ) ?;
2319+
2320+ // Check event types match, discard if not.
2321+ if outer_event_type != inner_event_type {
2322+ return Err ( MegolmError :: StateKeyVerificationFailed ) ;
2323+ }
2324+
2325+ // Check state keys match, discard if not.
2326+ if outer_state_key != inner_state_key {
2327+ return Err ( MegolmError :: StateKeyVerificationFailed ) ;
2328+ }
2329+ Ok ( ( ) )
22032330 }
22042331
22052332 /// Try to decrypt the events bundled in the `unsigned` object of the given
@@ -2970,6 +3097,8 @@ fn megolm_error_to_utd_info(
29703097 JsonError ( _) => UnableToDecryptReason :: PayloadDeserializationFailure ,
29713098 MismatchedIdentityKeys ( _) => UnableToDecryptReason :: MismatchedIdentityKeys ,
29723099 SenderIdentityNotTrusted ( level) => UnableToDecryptReason :: SenderIdentityNotTrusted ( level) ,
3100+ #[ cfg( feature = "experimental-encrypted-state-events" ) ]
3101+ StateKeyVerificationFailed => UnableToDecryptReason :: StateKeyVerificationFailed ,
29733102
29743103 // Pass through crypto store errors, which indicate a problem with our
29753104 // application, rather than a UTD.
0 commit comments