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