matrix_sdk_crypto/types/events/
room_key_request.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_key_request` events.
16
17use std::collections::BTreeMap;
18
19use ruma::{
20    events::AnyToDeviceEventContent, serde::JsonCastable, OwnedDeviceId, OwnedRoomId,
21    OwnedTransactionId, RoomId,
22};
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25use vodozemac::Curve25519PublicKey;
26
27use super::{EventType, ToDeviceEvent};
28use crate::types::{serde_curve_key_option, EventEncryptionAlgorithm};
29
30/// The `m.room_key_request` to-device event.
31pub type RoomKeyRequestEvent = ToDeviceEvent<RoomKeyRequestContent>;
32
33impl Clone for RoomKeyRequestEvent {
34    fn clone(&self) -> Self {
35        Self {
36            sender: self.sender.clone(),
37            content: self.content.clone(),
38            other: self.other.clone(),
39        }
40    }
41}
42
43/// The content for a `m.room_key_request` event.
44#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
45pub struct RoomKeyRequestContent {
46    /// The action that this room key request is carrying.
47    #[serde(flatten)]
48    pub action: Action,
49    /// The ID of the device that is requesting the room key.
50    pub requesting_device_id: OwnedDeviceId,
51    /// A random string uniquely identifying the request for a key. If the key
52    /// is requested multiple times, it should be reused. It should also reused
53    /// in order to cancel a request.
54    pub request_id: OwnedTransactionId,
55}
56
57impl RoomKeyRequestContent {
58    /// Create a new content for a `m.room_key_request` event with the action
59    /// set to request a room key with the given `RequestedKeyInfo`.
60    pub fn new_request(
61        info: RequestedKeyInfo,
62        requesting_device_id: OwnedDeviceId,
63        request_id: OwnedTransactionId,
64    ) -> Self {
65        Self { action: Action::Request(info), requesting_device_id, request_id }
66    }
67}
68
69impl EventType for RoomKeyRequestContent {
70    const EVENT_TYPE: &'static str = "m.room_key_request";
71}
72
73impl JsonCastable<AnyToDeviceEventContent> for RoomKeyRequestContent {}
74
75/// Enum describing different actions a room key request event can be carrying.
76#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
77#[serde(tag = "action", content = "body")]
78pub enum Action {
79    /// An action describing a cancellation of a previous room key request.
80    #[serde(rename = "request_cancellation")]
81    Cancellation,
82    /// An action describing a room key request.
83    #[serde(rename = "request")]
84    Request(RequestedKeyInfo),
85}
86
87/// Info about the room key that is being requested.
88#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
89#[serde(try_from = "RequestedKeyInfoHelper")]
90pub enum RequestedKeyInfo {
91    /// The `m.megolm.v1.aes-sha2` variant of the `m.room_key_request` content.
92    MegolmV1AesSha2(MegolmV1AesSha2Content),
93    /// The `m.megolm.v2.aes-sha2` variant of the `m.room_key_request` content.
94    #[cfg(feature = "experimental-algorithms")]
95    MegolmV2AesSha2(MegolmV2AesSha2Content),
96    /// An unknown and unsupported variant of the `m.room_key_request`
97    /// content.
98    Unknown(UnknownRoomKeyRequest),
99}
100
101impl RequestedKeyInfo {
102    /// Get the algorithm of the room key request content.
103    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
104        match self {
105            RequestedKeyInfo::MegolmV1AesSha2(_) => EventEncryptionAlgorithm::MegolmV1AesSha2,
106            #[cfg(feature = "experimental-algorithms")]
107            RequestedKeyInfo::MegolmV2AesSha2(_) => EventEncryptionAlgorithm::MegolmV2AesSha2,
108            RequestedKeyInfo::Unknown(c) => c.algorithm.to_owned(),
109        }
110    }
111}
112
113impl From<SupportedKeyInfo> for RequestedKeyInfo {
114    fn from(s: SupportedKeyInfo) -> Self {
115        match s {
116            SupportedKeyInfo::MegolmV1AesSha2(c) => Self::MegolmV1AesSha2(c),
117            #[cfg(feature = "experimental-algorithms")]
118            SupportedKeyInfo::MegolmV2AesSha2(c) => Self::MegolmV2AesSha2(c),
119        }
120    }
121}
122
123impl From<MegolmV1AesSha2Content> for RequestedKeyInfo {
124    fn from(c: MegolmV1AesSha2Content) -> Self {
125        Self::MegolmV1AesSha2(c)
126    }
127}
128
129#[cfg(feature = "experimental-algorithms")]
130impl From<MegolmV2AesSha2Content> for RequestedKeyInfo {
131    fn from(c: MegolmV2AesSha2Content) -> Self {
132        Self::MegolmV2AesSha2(c)
133    }
134}
135
136/// Info about the room key that is being requested.
137///
138/// This is similar to [`RequestedKeyInfo`] but it contains only supported
139/// algorithms.
140#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
141#[serde(try_from = "RequestedKeyInfo", into = "RequestedKeyInfo")]
142pub enum SupportedKeyInfo {
143    /// The `m.megolm.v1.aes-sha2` variant of the `m.room_key_request` content.
144    MegolmV1AesSha2(MegolmV1AesSha2Content),
145    /// The `m.megolm.v2.aes-sha2` variant of the `m.room_key_request` content.
146    #[cfg(feature = "experimental-algorithms")]
147    MegolmV2AesSha2(MegolmV2AesSha2Content),
148}
149
150impl SupportedKeyInfo {
151    /// Get the algorithm of the room key request content.
152    pub fn algorithm(&self) -> EventEncryptionAlgorithm {
153        match self {
154            Self::MegolmV1AesSha2(_) => EventEncryptionAlgorithm::MegolmV1AesSha2,
155            #[cfg(feature = "experimental-algorithms")]
156            Self::MegolmV2AesSha2(_) => EventEncryptionAlgorithm::MegolmV2AesSha2,
157        }
158    }
159
160    /// Get the room ID where the room key is used.
161    pub fn room_id(&self) -> &RoomId {
162        match self {
163            Self::MegolmV1AesSha2(c) => &c.room_id,
164            #[cfg(feature = "experimental-algorithms")]
165            Self::MegolmV2AesSha2(c) => &c.room_id,
166        }
167    }
168
169    /// Get the unique ID of the room key.
170    pub fn session_id(&self) -> &str {
171        match self {
172            Self::MegolmV1AesSha2(c) => &c.session_id,
173            #[cfg(feature = "experimental-algorithms")]
174            Self::MegolmV2AesSha2(c) => &c.session_id,
175        }
176    }
177}
178
179impl TryFrom<RequestedKeyInfo> for SupportedKeyInfo {
180    type Error = &'static str;
181
182    fn try_from(value: RequestedKeyInfo) -> Result<Self, Self::Error> {
183        match value {
184            RequestedKeyInfo::MegolmV1AesSha2(c) => Ok(Self::MegolmV1AesSha2(c)),
185            #[cfg(feature = "experimental-algorithms")]
186            RequestedKeyInfo::MegolmV2AesSha2(c) => Ok(Self::MegolmV2AesSha2(c)),
187            RequestedKeyInfo::Unknown(_) => Err("Unsupported algorithm for a room key request"),
188        }
189    }
190}
191
192impl From<MegolmV1AesSha2Content> for SupportedKeyInfo {
193    fn from(c: MegolmV1AesSha2Content) -> Self {
194        Self::MegolmV1AesSha2(c)
195    }
196}
197
198#[cfg(feature = "experimental-algorithms")]
199impl From<MegolmV2AesSha2Content> for SupportedKeyInfo {
200    fn from(c: MegolmV2AesSha2Content) -> Self {
201        Self::MegolmV2AesSha2(c)
202    }
203}
204
205/// The content for a `m.megolm.v2.aes-sha2` `m.room_key_request` event.
206#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
207pub struct MegolmV1AesSha2Content {
208    /// The room where the key is used.
209    pub room_id: OwnedRoomId,
210
211    /// The Curve25519 key of the device which initiated the session originally.
212    #[serde(default, with = "serde_curve_key_option", skip_serializing_if = "Option::is_none")]
213    pub sender_key: Option<Curve25519PublicKey>,
214
215    /// The ID of the session that the key is for.
216    pub session_id: String,
217}
218
219/// The content for a `m.megolm.v1.aes-sha2` `m.room_key_request` event.
220#[cfg(feature = "experimental-algorithms")]
221#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
222pub struct MegolmV2AesSha2Content {
223    /// The room where the key is used.
224    pub room_id: OwnedRoomId,
225
226    /// The ID of the session that the key is for.
227    pub session_id: String,
228}
229
230/// An unknown and unsupported `m.room_key_request` algorithm.
231#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
232pub struct UnknownRoomKeyRequest {
233    /// The algorithm of the unknown room key request.
234    pub algorithm: EventEncryptionAlgorithm,
235
236    /// The other data of the unknown room key request.
237    #[serde(flatten)]
238    other: BTreeMap<String, Value>,
239}
240
241#[derive(Clone, Debug, Serialize, Deserialize)]
242struct RequestedKeyInfoHelper {
243    pub algorithm: EventEncryptionAlgorithm,
244    #[serde(flatten)]
245    other: Value,
246}
247
248impl Serialize for RequestedKeyInfo {
249    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
250    where
251        S: serde::Serializer,
252    {
253        let other = match self {
254            Self::MegolmV1AesSha2(r) => {
255                serde_json::to_value(r).map_err(serde::ser::Error::custom)?
256            }
257            #[cfg(feature = "experimental-algorithms")]
258            Self::MegolmV2AesSha2(r) => {
259                serde_json::to_value(r).map_err(serde::ser::Error::custom)?
260            }
261            Self::Unknown(r) => {
262                serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?
263            }
264        };
265
266        let helper = RequestedKeyInfoHelper { algorithm: self.algorithm(), other };
267
268        helper.serialize(serializer)
269    }
270}
271
272impl TryFrom<RequestedKeyInfoHelper> for RequestedKeyInfo {
273    type Error = serde_json::Error;
274
275    fn try_from(value: RequestedKeyInfoHelper) -> Result<Self, Self::Error> {
276        Ok(match value.algorithm {
277            EventEncryptionAlgorithm::MegolmV1AesSha2 => {
278                Self::MegolmV1AesSha2(serde_json::from_value(value.other)?)
279            }
280            #[cfg(feature = "experimental-algorithms")]
281            EventEncryptionAlgorithm::MegolmV2AesSha2 => {
282                Self::MegolmV2AesSha2(serde_json::from_value(value.other)?)
283            }
284            _ => Self::Unknown(UnknownRoomKeyRequest {
285                algorithm: value.algorithm,
286                other: serde_json::from_value(value.other)?,
287            }),
288        })
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use assert_matches::assert_matches;
295    use serde_json::{json, Value};
296
297    use super::{Action, RequestedKeyInfo, RoomKeyRequestEvent};
298
299    pub fn json() -> Value {
300        json!({
301            "sender": "@alice:example.org",
302            "content": {
303                "action": "request",
304                "body": {
305                    "algorithm": "m.megolm.v1.aes-sha2",
306                    "room_id": "!Cuyf34gef24t:localhost",
307                    "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
308                    "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
309                },
310                "request_id": "1495474790150.19",
311                "requesting_device_id": "RJYKSTBOIE"
312            },
313            "type": "m.room_key_request"
314        })
315    }
316
317    pub fn json_cancellation() -> Value {
318        json!({
319            "sender": "@alice:example.org",
320            "content": {
321                "action": "request_cancellation",
322                "request_id": "1495474790150.19",
323                "requesting_device_id": "RJYKSTBOIE"
324            },
325            "type": "m.room_key_request"
326        })
327    }
328
329    #[cfg(feature = "experimental-algorithms")]
330    pub fn json_megolm_v2() -> Value {
331        json!({
332            "sender": "@alice:example.org",
333            "content": {
334                "action": "request",
335                "body": {
336                    "algorithm": "m.megolm.v2.aes-sha2",
337                    "room_id": "!Cuyf34gef24t:localhost",
338                    "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
339                },
340                "request_id": "1495474790150.19",
341                "requesting_device_id": "RJYKSTBOIE"
342            },
343            "type": "m.room_key_request"
344        })
345    }
346
347    #[test]
348    fn deserialization() -> Result<(), serde_json::Error> {
349        let json = json();
350        let event: RoomKeyRequestEvent = serde_json::from_value(json.clone())?;
351
352        assert_matches!(
353            event.content.action,
354            Action::Request(RequestedKeyInfo::MegolmV1AesSha2(_))
355        );
356        let serialized = serde_json::to_value(event)?;
357        assert_eq!(json, serialized);
358
359        let json = json_cancellation();
360        let event: RoomKeyRequestEvent = serde_json::from_value(json.clone())?;
361
362        assert_matches!(event.content.action, Action::Cancellation);
363        let serialized = serde_json::to_value(event)?;
364        assert_eq!(json, serialized);
365
366        Ok(())
367    }
368
369    #[test]
370    #[cfg(feature = "experimental-algorithms")]
371    fn deserialization_megolm_v2() -> Result<(), serde_json::Error> {
372        let json = json_megolm_v2();
373        let event: RoomKeyRequestEvent = serde_json::from_value(json.clone())?;
374
375        assert_matches!(
376            event.content.action,
377            Action::Request(RequestedKeyInfo::MegolmV2AesSha2(_))
378        );
379
380        let serialized = serde_json::to_value(event)?;
381        assert_eq!(json, serialized);
382
383        Ok(())
384    }
385
386    #[test]
387    fn deserialization_missing_sender_key() -> Result<(), serde_json::Error> {
388        let json = json!({
389            "sender": "@alice:example.org",
390            "content": {
391                "action": "request",
392                "body": {
393                    "algorithm": "m.megolm.v1.aes-sha2",
394                    "room_id": "!Cuyf34gef24t:localhost",
395                    "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
396                },
397                "request_id": "1495474790150.19",
398                "requesting_device_id": "RJYKSTBOIE"
399            },
400            "type": "m.room_key_request"
401        });
402
403        let event: RoomKeyRequestEvent = serde_json::from_value(json.clone())?;
404
405        assert_matches!(
406            event.content.action,
407            Action::Request(RequestedKeyInfo::MegolmV1AesSha2(_))
408        );
409        let serialized = serde_json::to_value(event)?;
410        assert_eq!(json, serialized);
411
412        Ok(())
413    }
414}