matrix_sdk_crypto/types/events/
room_key.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 `m.room_key` to-device events.
16
17use std::collections::BTreeMap;
18
19use ruma::{serde::Raw, OwnedRoomId, RoomId};
20use serde::{Deserialize, Serialize};
21use serde_json::{value::to_raw_value, Value};
22use vodozemac::megolm::SessionKey;
23
24use super::{EventType, ToDeviceEvent};
25#[cfg(doc)]
26use crate::olm::InboundGroupSession;
27use crate::types::EventEncryptionAlgorithm;
28
29/// The `m.room_key` to-device event.
30pub type RoomKeyEvent = ToDeviceEvent<RoomKeyContent>;
31
32impl RoomKeyEvent {
33    /// Get the algorithm of the room key.
34    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
35        self.content.algorithm()
36    }
37}
38
39impl EventType for RoomKeyContent {
40    const EVENT_TYPE: &'static str = "m.room_key";
41}
42
43/// The `m.room_key` event content.
44///
45/// This is an enum over the different room key algorithms we support.  The
46/// currently-supported implementations are used to share
47/// [`InboundGroupSession`]s.
48///
49/// This event type is used to exchange keys for end-to-end encryption.
50/// Typically, it is encrypted as an m.room.encrypted event, then sent as a
51/// to-device event.
52///
53/// See <https://spec.matrix.org/v1.13/client-server-api/#mroom_key>.
54#[derive(Debug, Deserialize)]
55#[serde(try_from = "RoomKeyHelper")]
56pub enum RoomKeyContent {
57    /// The `m.megolm.v1.aes-sha2` variant of the `m.room_key` content.
58    MegolmV1AesSha2(Box<MegolmV1AesSha2Content>),
59    /// The `m.megolm.v2.aes-sha2` variant of the `m.room_key` content.
60    #[cfg(feature = "experimental-algorithms")]
61    MegolmV2AesSha2(Box<MegolmV1AesSha2Content>),
62    /// An unknown and unsupported variant of the `m.room_key` content.
63    Unknown(UnknownRoomKey),
64}
65
66impl RoomKeyContent {
67    /// Get the algorithm of the room key.
68    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
69        match &self {
70            RoomKeyContent::MegolmV1AesSha2(_) => EventEncryptionAlgorithm::MegolmV1AesSha2,
71            #[cfg(feature = "experimental-algorithms")]
72            RoomKeyContent::MegolmV2AesSha2(_) => EventEncryptionAlgorithm::MegolmV2AesSha2,
73            RoomKeyContent::Unknown(c) => c.algorithm.to_owned(),
74        }
75    }
76
77    pub(super) fn serialize_zeroized(&self) -> Result<Raw<RoomKeyContent>, serde_json::Error> {
78        #[derive(Serialize)]
79        struct Helper<'a> {
80            pub room_id: &'a RoomId,
81            pub session_id: &'a str,
82            pub session_key: &'a str,
83            #[serde(flatten)]
84            other: &'a BTreeMap<String, Value>,
85        }
86
87        let serialize_helper = |content: &MegolmV1AesSha2Content| {
88            let helper = Helper {
89                room_id: &content.room_id,
90                session_id: &content.session_id,
91                session_key: "",
92                other: &content.other,
93            };
94
95            let helper =
96                RoomKeyHelper { algorithm: self.algorithm(), other: serde_json::to_value(helper)? };
97
98            Ok(Raw::from_json(to_raw_value(&helper)?))
99        };
100
101        match self {
102            RoomKeyContent::MegolmV1AesSha2(c) => serialize_helper(c),
103            #[cfg(feature = "experimental-algorithms")]
104            RoomKeyContent::MegolmV2AesSha2(c) => serialize_helper(c),
105            RoomKeyContent::Unknown(c) => Ok(Raw::from_json(to_raw_value(&c)?)),
106        }
107    }
108}
109
110/// The `m.megolm.v1.aes-sha2` variant of the `m.room_key` content.
111#[derive(Deserialize, Serialize)]
112pub struct MegolmV1AesSha2Content {
113    /// The room where the key is used.
114    pub room_id: OwnedRoomId,
115    /// The ID of the session that the key is for.
116    pub session_id: String,
117    /// The key to be exchanged. Can be used to create a [`InboundGroupSession`]
118    /// that can be used to decrypt room events.
119    ///
120    /// [`InboundGroupSession`]: vodozemac::megolm::InboundGroupSession
121    pub session_key: SessionKey,
122    /// Whether this room key can be shared with users who are invited to the
123    /// room in the future, allowing access to history, as defined in
124    /// [MSC3061].
125    ///
126    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
127    #[serde(default, rename = "org.matrix.msc3061.shared_history")]
128    pub shared_history: bool,
129    /// Any other, custom and non-specced fields of the content.
130    #[serde(flatten)]
131    other: BTreeMap<String, Value>,
132}
133
134impl MegolmV1AesSha2Content {
135    /// Create a new `m.megolm.v1.aes-sha2` `m.room_key` content.
136    pub fn new(
137        room_id: OwnedRoomId,
138        session_id: String,
139        session_key: SessionKey,
140        shared_history: bool,
141    ) -> Self {
142        Self { room_id, session_id, session_key, other: Default::default(), shared_history }
143    }
144}
145
146#[cfg(not(tarpaulin_include))]
147impl std::fmt::Debug for MegolmV1AesSha2Content {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        f.debug_struct("MegolmV1AesSha2Content")
150            .field("room_id", &self.room_id)
151            .field("session_id", &self.session_id)
152            .finish_non_exhaustive()
153    }
154}
155
156/// The `m.megolm.v2.aes-sha2` variant of the `m.room_key` content.
157pub type MegolmV2AesSha2Content = MegolmV1AesSha2Content;
158
159/// An unknown and unsupported `m.room_key` algorithm.
160#[derive(Clone, Debug, Serialize, Deserialize)]
161pub struct UnknownRoomKey {
162    /// The algorithm of the unknown room key.
163    pub algorithm: EventEncryptionAlgorithm,
164    /// The other data of the unknown room key.
165    #[serde(flatten)]
166    other: BTreeMap<String, Value>,
167}
168
169#[derive(Deserialize, Serialize)]
170struct RoomKeyHelper {
171    algorithm: EventEncryptionAlgorithm,
172    #[serde(flatten)]
173    other: Value,
174}
175
176impl TryFrom<RoomKeyHelper> for RoomKeyContent {
177    type Error = serde_json::Error;
178
179    fn try_from(value: RoomKeyHelper) -> Result<Self, Self::Error> {
180        Ok(match value.algorithm {
181            EventEncryptionAlgorithm::MegolmV1AesSha2 => {
182                let content: MegolmV1AesSha2Content = serde_json::from_value(value.other)?;
183                Self::MegolmV1AesSha2(content.into())
184            }
185            #[cfg(feature = "experimental-algorithms")]
186            EventEncryptionAlgorithm::MegolmV2AesSha2 => {
187                let content: MegolmV2AesSha2Content = serde_json::from_value(value.other)?;
188                Self::MegolmV2AesSha2(content.into())
189            }
190            _ => Self::Unknown(UnknownRoomKey {
191                algorithm: value.algorithm,
192                other: serde_json::from_value(value.other)?,
193            }),
194        })
195    }
196}
197
198impl Serialize for RoomKeyContent {
199    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
200    where
201        S: serde::Serializer,
202    {
203        let helper = match self {
204            Self::MegolmV1AesSha2(r) => RoomKeyHelper {
205                algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
206                other: serde_json::to_value(r).map_err(serde::ser::Error::custom)?,
207            },
208            #[cfg(feature = "experimental-algorithms")]
209            Self::MegolmV2AesSha2(r) => RoomKeyHelper {
210                algorithm: EventEncryptionAlgorithm::MegolmV2AesSha2,
211                other: serde_json::to_value(r).map_err(serde::ser::Error::custom)?,
212            },
213            Self::Unknown(r) => RoomKeyHelper {
214                algorithm: r.algorithm.clone(),
215                other: serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?,
216            },
217        };
218
219        helper.serialize(serializer)
220    }
221}
222
223#[cfg(test)]
224pub(super) mod tests {
225    use assert_matches::assert_matches;
226    use serde_json::{json, Value};
227    use similar_asserts::assert_eq;
228
229    use super::RoomKeyEvent;
230    use crate::types::events::room_key::RoomKeyContent;
231
232    pub fn json() -> Value {
233        json!({
234            "sender": "@alice:example.org",
235            "content": {
236                "m.custom": "something custom",
237                "algorithm": "m.megolm.v1.aes-sha2",
238                "room_id": "!Cuyf34gef24t:localhost",
239                "org.matrix.msc3061.shared_history": false,
240                "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
241                "session_key": "AgAAAADNp1EbxXYOGmJtyX4AkD1bvJvAUyPkbIaKxtnGKjv\
242                                SQ3E/4mnuqdM4vsmNzpO1EeWzz1rDkUpYhYE9kP7sJhgLXi\
243                                jVv80fMPHfGc49hPdu8A+xnwD4SQiYdFmSWJOIqsxeo/fiH\
244                                tino//CDQENtcKuEt0I9s0+Kk4YSH310Szse2RQ+vjple31\
245                                QrCexmqfFJzkR/BJ5ogJHrPBQL0LgsPyglIbMTLg7qygIaY\
246                                U5Fe2QdKMH7nTZPNIRHh1RaMfHVETAUJBax88EWZBoifk80\
247                                gdHUwHSgMk77vCc2a5KHKLDA",
248            },
249            "type": "m.room_key",
250            "m.custom.top": "something custom in the top",
251        })
252    }
253
254    #[test]
255    fn deserialization() -> Result<(), serde_json::Error> {
256        let json = json();
257        let event: RoomKeyEvent = serde_json::from_value(json.clone())?;
258
259        assert_matches!(event.content, RoomKeyContent::MegolmV1AesSha2(_));
260        let serialized = serde_json::to_value(event)?;
261        assert_eq!(json, serialized);
262
263        Ok(())
264    }
265}