matrix_sdk_crypto/types/events/room/
encrypted.rs

1// Copyright 2022 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types for the `m.room.encrypted` room events.
16
17use std::collections::BTreeMap;
18
19use ruma::{OwnedDeviceId, RoomId, events::AnyToDeviceEventContent, serde::JsonCastable};
20use serde::{Deserialize, Serialize};
21use serde_json::Value;
22use vodozemac::{Curve25519PublicKey, megolm::MegolmMessage, olm::OlmMessage};
23
24use super::Event;
25use crate::types::{
26    EventEncryptionAlgorithm, deserialize_curve_key,
27    events::{
28        EventType, ToDeviceEvent,
29        room_key_request::{self, SupportedKeyInfo},
30    },
31    serde_curve_key_option, serialize_curve_key,
32};
33
34/// An m.room.encrypted room event.
35pub type EncryptedEvent = Event<RoomEncryptedEventContent>;
36
37impl EncryptedEvent {
38    /// Get the unique info about the room key that was used to encrypt this
39    /// event.
40    ///
41    /// Returns `None` if we do not understand the algorithm that was used to
42    /// encrypt the event.
43    pub fn room_key_info(&self, room_id: &RoomId) -> Option<SupportedKeyInfo> {
44        let room_id = room_id.to_owned();
45
46        match &self.content.scheme {
47            RoomEventEncryptionScheme::MegolmV1AesSha2(c) => Some(
48                room_key_request::MegolmV1AesSha2Content {
49                    room_id,
50                    sender_key: c.sender_key,
51                    session_id: c.session_id.clone(),
52                }
53                .into(),
54            ),
55            #[cfg(feature = "experimental-algorithms")]
56            RoomEventEncryptionScheme::MegolmV2AesSha2(c) => Some(
57                room_key_request::MegolmV2AesSha2Content {
58                    room_id,
59                    session_id: c.session_id.clone(),
60                }
61                .into(),
62            ),
63            RoomEventEncryptionScheme::Unknown(_) => None,
64        }
65    }
66}
67
68impl JsonCastable<EncryptedEvent>
69    for ruma::events::room::encrypted::OriginalSyncRoomEncryptedEvent
70{
71}
72
73#[cfg(feature = "experimental-encrypted-state-events")]
74impl JsonCastable<EncryptedEvent>
75    for ruma::events::room::encrypted::unstable_state::OriginalSyncStateRoomEncryptedEvent
76{
77}
78
79/// An m.room.encrypted to-device event.
80pub type EncryptedToDeviceEvent = ToDeviceEvent<ToDeviceEncryptedEventContent>;
81
82impl EncryptedToDeviceEvent {
83    /// Get the algorithm of the encrypted event content.
84    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
85        self.content.algorithm()
86    }
87}
88
89/// The content for `m.room.encrypted` to-device events.
90#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
91#[serde(try_from = "Helper")]
92pub enum ToDeviceEncryptedEventContent {
93    /// The event content for events encrypted with the m.megolm.v1.aes-sha2
94    /// algorithm.
95    OlmV1Curve25519AesSha2(Box<OlmV1Curve25519AesSha2Content>),
96    /// The event content for events encrypted with the m.olm.v2.aes-sha2
97    /// algorithm.
98    #[cfg(feature = "experimental-algorithms")]
99    OlmV2Curve25519AesSha2(Box<OlmV2Curve25519AesSha2Content>),
100    /// An event content that was encrypted with an unknown encryption
101    /// algorithm.
102    Unknown(UnknownEncryptedContent),
103}
104
105impl EventType for ToDeviceEncryptedEventContent {
106    const EVENT_TYPE: &'static str = "m.room.encrypted";
107}
108
109impl JsonCastable<AnyToDeviceEventContent> for ToDeviceEncryptedEventContent {}
110
111impl ToDeviceEncryptedEventContent {
112    /// Get the algorithm of the event content.
113    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
114        match self {
115            ToDeviceEncryptedEventContent::OlmV1Curve25519AesSha2(_) => {
116                EventEncryptionAlgorithm::OlmV1Curve25519AesSha2
117            }
118            #[cfg(feature = "experimental-algorithms")]
119            ToDeviceEncryptedEventContent::OlmV2Curve25519AesSha2(_) => {
120                EventEncryptionAlgorithm::OlmV2Curve25519AesSha2
121            }
122            ToDeviceEncryptedEventContent::Unknown(c) => c.algorithm.to_owned(),
123        }
124    }
125}
126
127/// The event content for events encrypted with the m.olm.v1.curve25519-aes-sha2
128/// algorithm.
129#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
130#[serde(try_from = "OlmHelper")]
131pub struct OlmV1Curve25519AesSha2Content {
132    /// The encrypted content of the event.
133    pub ciphertext: OlmMessage,
134
135    /// The Curve25519 key of the recipient device.
136    pub recipient_key: Curve25519PublicKey,
137
138    /// The Curve25519 key of the sender.
139    pub sender_key: Curve25519PublicKey,
140
141    /// The unique ID of this content.
142    pub message_id: Option<String>,
143}
144
145/// The event content for events encrypted with the m.olm.v2.curve25519-aes-sha2
146/// algorithm.
147#[cfg(feature = "experimental-algorithms")]
148#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
149pub struct OlmV2Curve25519AesSha2Content {
150    /// The encrypted content of the event.
151    pub ciphertext: OlmMessage,
152
153    /// The Curve25519 key of the sender.
154    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
155    pub sender_key: Curve25519PublicKey,
156
157    /// The unique ID of this content.
158    #[serde(default, skip_serializing_if = "Option::is_none", rename = "org.matrix.msgid")]
159    pub message_id: Option<String>,
160}
161
162#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
163struct OlmHelper {
164    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
165    sender_key: Curve25519PublicKey,
166    ciphertext: BTreeMap<String, OlmMessage>,
167    #[serde(default, skip_serializing_if = "Option::is_none", rename = "org.matrix.msgid")]
168    message_id: Option<String>,
169}
170
171impl Serialize for OlmV1Curve25519AesSha2Content {
172    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173    where
174        S: serde::Serializer,
175    {
176        let ciphertext =
177            BTreeMap::from([(self.recipient_key.to_base64(), self.ciphertext.clone())]);
178
179        OlmHelper {
180            sender_key: self.sender_key,
181            ciphertext,
182            message_id: self.message_id.to_owned(),
183        }
184        .serialize(serializer)
185    }
186}
187
188impl TryFrom<OlmHelper> for OlmV1Curve25519AesSha2Content {
189    type Error = serde_json::Error;
190
191    fn try_from(value: OlmHelper) -> Result<Self, Self::Error> {
192        let (recipient_key, ciphertext) = value.ciphertext.into_iter().next().ok_or_else(|| {
193            serde::de::Error::custom(
194                "The `m.room.encrypted` event is missing a ciphertext".to_owned(),
195            )
196        })?;
197
198        let recipient_key =
199            Curve25519PublicKey::from_base64(&recipient_key).map_err(serde::de::Error::custom)?;
200
201        Ok(Self {
202            ciphertext,
203            recipient_key,
204            sender_key: value.sender_key,
205            message_id: value.message_id,
206        })
207    }
208}
209
210/// The content for `m.room.encrypted` room events.
211#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
212pub struct RoomEncryptedEventContent {
213    /// Algorithm-specific fields.
214    #[serde(flatten)]
215    pub scheme: RoomEventEncryptionScheme,
216
217    /// Information about related events.
218    #[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
219    pub relates_to: Option<Value>,
220
221    /// The other data of the encrypted content.
222    #[serde(flatten)]
223    pub(crate) other: BTreeMap<String, Value>,
224}
225
226impl RoomEncryptedEventContent {
227    /// Get the algorithm of the event content.
228    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
229        self.scheme.algorithm()
230    }
231}
232
233impl EventType for RoomEncryptedEventContent {
234    const EVENT_TYPE: &'static str = "m.room.encrypted";
235}
236
237impl JsonCastable<ruma::events::AnyMessageLikeEventContent> for RoomEncryptedEventContent {}
238
239#[cfg(feature = "experimental-encrypted-state-events")]
240impl JsonCastable<ruma::events::AnyStateEventContent> for RoomEncryptedEventContent {}
241
242/// An enum for per encryption algorithm event contents.
243#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
244#[serde(try_from = "Helper")]
245pub enum RoomEventEncryptionScheme {
246    /// The event content for events encrypted with the m.megolm.v1.aes-sha2
247    /// algorithm.
248    MegolmV1AesSha2(MegolmV1AesSha2Content),
249    /// The event content for events encrypted with the m.megolm.v2.aes-sha2
250    /// algorithm.
251    #[cfg(feature = "experimental-algorithms")]
252    MegolmV2AesSha2(MegolmV2AesSha2Content),
253    /// An event content that was encrypted with an unknown encryption
254    /// algorithm.
255    Unknown(UnknownEncryptedContent),
256}
257
258impl RoomEventEncryptionScheme {
259    /// Get the algorithm of the event content.
260    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
261        match self {
262            RoomEventEncryptionScheme::MegolmV1AesSha2(_) => {
263                EventEncryptionAlgorithm::MegolmV1AesSha2
264            }
265            #[cfg(feature = "experimental-algorithms")]
266            RoomEventEncryptionScheme::MegolmV2AesSha2(_) => {
267                EventEncryptionAlgorithm::MegolmV2AesSha2
268            }
269            RoomEventEncryptionScheme::Unknown(c) => c.algorithm.to_owned(),
270        }
271    }
272}
273
274pub(crate) enum SupportedEventEncryptionSchemes<'a> {
275    MegolmV1AesSha2(&'a MegolmV1AesSha2Content),
276    #[cfg(feature = "experimental-algorithms")]
277    MegolmV2AesSha2(&'a MegolmV2AesSha2Content),
278}
279
280impl SupportedEventEncryptionSchemes<'_> {
281    /// The ID of the session used to encrypt the message.
282    pub fn session_id(&self) -> &str {
283        match self {
284            SupportedEventEncryptionSchemes::MegolmV1AesSha2(c) => &c.session_id,
285            #[cfg(feature = "experimental-algorithms")]
286            SupportedEventEncryptionSchemes::MegolmV2AesSha2(c) => &c.session_id,
287        }
288    }
289
290    /// The index of the Megolm ratchet that was used to encrypt the message.
291    pub fn message_index(&self) -> u32 {
292        match self {
293            SupportedEventEncryptionSchemes::MegolmV1AesSha2(c) => c.ciphertext.message_index(),
294            #[cfg(feature = "experimental-algorithms")]
295            SupportedEventEncryptionSchemes::MegolmV2AesSha2(c) => c.ciphertext.message_index(),
296        }
297    }
298}
299
300impl<'a> From<&'a MegolmV1AesSha2Content> for SupportedEventEncryptionSchemes<'a> {
301    fn from(c: &'a MegolmV1AesSha2Content) -> Self {
302        Self::MegolmV1AesSha2(c)
303    }
304}
305
306#[cfg(feature = "experimental-algorithms")]
307impl<'a> From<&'a MegolmV2AesSha2Content> for SupportedEventEncryptionSchemes<'a> {
308    fn from(c: &'a MegolmV2AesSha2Content) -> Self {
309        Self::MegolmV2AesSha2(c)
310    }
311}
312
313/// The event content for events encrypted with the m.megolm.v1.aes-sha2
314/// algorithm.
315#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
316pub struct MegolmV1AesSha2Content {
317    /// The encrypted content of the event.
318    pub ciphertext: MegolmMessage,
319
320    /// The Curve25519 key of the sender.
321    #[serde(default, with = "serde_curve_key_option", skip_serializing_if = "Option::is_none")]
322    pub sender_key: Option<Curve25519PublicKey>,
323
324    /// The ID of the sending device.
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub device_id: Option<OwnedDeviceId>,
327
328    /// The ID of the session used to encrypt the message.
329    pub session_id: String,
330}
331
332/// The event content for events encrypted with the m.megolm.v2.aes-sha2
333/// algorithm.
334#[cfg(feature = "experimental-algorithms")]
335#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
336pub struct MegolmV2AesSha2Content {
337    /// The encrypted content of the event.
338    pub ciphertext: MegolmMessage,
339
340    /// The ID of the session used to encrypt the message.
341    pub session_id: String,
342}
343
344/// An unknown and unsupported `m.room.encrypted` event content.
345#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
346pub struct UnknownEncryptedContent {
347    /// The algorithm that was used to encrypt the given event content.
348    pub algorithm: EventEncryptionAlgorithm,
349    /// The other data of the unknown encrypted content.
350    #[serde(flatten)]
351    other: BTreeMap<String, Value>,
352}
353
354#[derive(Debug, Deserialize, Serialize)]
355struct Helper {
356    algorithm: EventEncryptionAlgorithm,
357    #[serde(flatten)]
358    other: Value,
359}
360
361macro_rules! scheme_serialization {
362    ($something:ident, $($algorithm:ident => $content:ident),+ $(,)?) => {
363        $(
364            impl From<$content> for $something {
365                fn from(c: $content) -> Self {
366                    Self::$algorithm(c.into())
367                }
368            }
369        )+
370
371        impl TryFrom<Helper> for $something {
372            type Error = serde_json::Error;
373
374            fn try_from(value: Helper) -> Result<Self, Self::Error> {
375                Ok(match value.algorithm {
376                    $(
377                        EventEncryptionAlgorithm::$algorithm => {
378                            let content: $content = serde_json::from_value(value.other)?;
379                            content.into()
380                        }
381                    )+
382                    _ => Self::Unknown(UnknownEncryptedContent {
383                        algorithm: value.algorithm,
384                        other: serde_json::from_value(value.other)?,
385                    }),
386                })
387            }
388        }
389
390        impl Serialize for $something {
391            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
392            where
393                S: serde::Serializer,
394            {
395                let helper = match self {
396                    $(
397                        Self::$algorithm(r) => Helper {
398                            algorithm: self.algorithm(),
399                            other: serde_json::to_value(r).map_err(serde::ser::Error::custom)?,
400                        },
401                    )+
402                    Self::Unknown(r) => Helper {
403                        algorithm: r.algorithm.clone(),
404                        other: serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?,
405                    },
406                };
407
408                helper.serialize(serializer)
409            }
410        }
411    };
412}
413
414#[cfg(feature = "experimental-algorithms")]
415scheme_serialization!(
416    RoomEventEncryptionScheme,
417    MegolmV1AesSha2 => MegolmV1AesSha2Content,
418    MegolmV2AesSha2 => MegolmV2AesSha2Content
419);
420
421#[cfg(not(feature = "experimental-algorithms"))]
422scheme_serialization!(
423    RoomEventEncryptionScheme,
424    MegolmV1AesSha2 => MegolmV1AesSha2Content,
425);
426
427#[cfg(feature = "experimental-algorithms")]
428scheme_serialization!(
429    ToDeviceEncryptedEventContent,
430    OlmV1Curve25519AesSha2 => OlmV1Curve25519AesSha2Content,
431    OlmV2Curve25519AesSha2 => OlmV2Curve25519AesSha2Content,
432);
433
434#[cfg(not(feature = "experimental-algorithms"))]
435scheme_serialization!(
436    ToDeviceEncryptedEventContent,
437    OlmV1Curve25519AesSha2 => OlmV1Curve25519AesSha2Content,
438);
439
440#[cfg(test)]
441pub(crate) mod tests {
442    use assert_matches::assert_matches;
443    use assert_matches2::assert_let;
444    use serde_json::{Value, json};
445    use vodozemac::Curve25519PublicKey;
446
447    use super::{
448        EncryptedEvent, EncryptedToDeviceEvent, OlmV1Curve25519AesSha2Content,
449        RoomEventEncryptionScheme, ToDeviceEncryptedEventContent,
450    };
451
452    pub fn json() -> Value {
453        json!({
454            "sender": "@alice:example.org",
455            "event_id": "$Nhl3rsgHMjk-DjMJANawr9HHAhLg4GcoTYrSiYYGqEE",
456            "content": {
457                "m.custom": "something custom",
458                "algorithm": "m.megolm.v1.aes-sha2",
459                "device_id": "DEWRCMENGS",
460                "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
461                "sender_key": "WJ6Ce7U67a6jqkHYHd8o0+5H4bqdi9hInZdk0+swuXs",
462                "ciphertext":
463                    "AwgAEiBQs2LgBD2CcB+RLH2bsgp9VadFUJhBXOtCmcJuttBDOeDNjL21d9\
464                     z0AcVSfQFAh9huh4or7sWuNrHcvu9/sMbweTgc0UtdA5xFLheubHouXy4a\
465                     ewze+ShndWAaTbjWJMLsPSQDUMQHBA",
466                "m.relates_to": {
467                    "rel_type": "m.reference",
468                    "event_id": "$WUreEJERkFzO8i2dk6CmTex01cP1dZ4GWKhKCwkWHrQ"
469                },
470            },
471            "type": "m.room.encrypted",
472            "origin_server_ts": 1632491098485u64,
473            "m.custom.top": "something custom in the top",
474        })
475    }
476
477    pub fn olm_v1_json() -> Value {
478        json!({
479            "algorithm": "m.olm.v1.curve25519-aes-sha2",
480            "ciphertext": {
481                "Nn0L2hkcCMFKqynTjyGsJbth7QrVmX3lbrksMkrGOAw": {
482                    "body":
483                        "Awogv7Iysf062hV1gZNfG/SdO5TdLYtkRI12em6LxralPxoSICC/Av\
484                         nha6NfkaMWSC+5h+khS0wHiUzA2bPmAvVo/iYhGiAfDNh4F0eqPvOc\
485                         4Hw9wMgd+frzedZgmhUNfKT0UzHQZSJPAwogF8fTdTcPt1ppJ/KAEi\
486                         vFZ4dIyAlRUjzhlqzYsw9C1HoQACIgb9MK/a9TRLtwol9gfy7OeKdp\
487                         mSe39YhP+5OchhKvX6eO3/aED3X1oA",
488                    "type": 0
489                }
490            },
491            "sender_key": "mjkTX0I0Cp44ZfolOVbFe5WYPRmT6AX3J0ZbnGWnnWs"
492        })
493    }
494
495    pub fn to_device_json() -> Value {
496        json!({
497            "content": olm_v1_json(),
498            "sender": "@example:morpheus.localhost",
499            "type": "m.room.encrypted"
500        })
501    }
502
503    #[test]
504    fn deserialization() -> Result<(), serde_json::Error> {
505        let json = json();
506        let event: EncryptedEvent = serde_json::from_value(json.clone())?;
507
508        assert_matches!(event.content.scheme, RoomEventEncryptionScheme::MegolmV1AesSha2(_));
509        assert!(event.content.relates_to.is_some());
510        let serialized = serde_json::to_value(event)?;
511        assert_eq!(json, serialized);
512
513        let json = olm_v1_json();
514        let content: OlmV1Curve25519AesSha2Content = serde_json::from_value(json)?;
515
516        assert_eq!(
517            content.sender_key,
518            Curve25519PublicKey::from_base64("mjkTX0I0Cp44ZfolOVbFe5WYPRmT6AX3J0ZbnGWnnWs")
519                .unwrap()
520        );
521
522        assert_eq!(
523            content.recipient_key,
524            Curve25519PublicKey::from_base64("Nn0L2hkcCMFKqynTjyGsJbth7QrVmX3lbrksMkrGOAw")
525                .unwrap()
526        );
527
528        let json = to_device_json();
529        let event: EncryptedToDeviceEvent = serde_json::from_value(json.clone())?;
530
531        assert_let!(
532            ToDeviceEncryptedEventContent::OlmV1Curve25519AesSha2(content) = &event.content
533        );
534        assert!(content.message_id.is_none());
535
536        let serialized = serde_json::to_value(event)?;
537        assert_eq!(json, serialized);
538
539        Ok(())
540    }
541
542    #[test]
543    fn deserialization_missing_sender_key_device_id() -> Result<(), serde_json::Error> {
544        let json = json!({
545            "sender": "@alice:example.org",
546            "event_id": "$Nhl3rsgHMjk-DjMJANawr9HHAhLg4GcoTYrSiYYGqEE",
547            "content": {
548                "m.custom": "something custom",
549                "algorithm": "m.megolm.v1.aes-sha2",
550                "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
551                "ciphertext":
552                    "AwgAEiBQs2LgBD2CcB+RLH2bsgp9VadFUJhBXOtCmcJuttBDOeDNjL21d9\
553                     z0AcVSfQFAh9huh4or7sWuNrHcvu9/sMbweTgc0UtdA5xFLheubHouXy4a\
554                     ewze+ShndWAaTbjWJMLsPSQDUMQHBA",
555                "m.relates_to": {
556                    "rel_type": "m.reference",
557                    "event_id": "$WUreEJERkFzO8i2dk6CmTex01cP1dZ4GWKhKCwkWHrQ"
558                },
559            },
560            "type": "m.room.encrypted",
561            "origin_server_ts": 1632491098485u64,
562            "m.custom.top": "something custom in the top",
563        });
564
565        let event: EncryptedEvent = serde_json::from_value(json.clone())?;
566
567        assert_matches!(event.content.scheme, RoomEventEncryptionScheme::MegolmV1AesSha2(_));
568        assert!(event.content.relates_to.is_some());
569        let serialized = serde_json::to_value(event)?;
570        assert_eq!(json, serialized);
571
572        Ok(())
573    }
574}