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