Skip to main content

matrix_sdk_crypto/olm/group_sessions/
mod.rs

1// Copyright 2020 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
15use ruma::{DeviceKeyAlgorithm, OwnedRoomId};
16use serde::{Deserialize, Serialize};
17
18mod forwarder_data;
19mod inbound;
20mod outbound;
21mod sender_data;
22pub(crate) mod sender_data_finder;
23
24pub use forwarder_data::ForwarderData;
25pub use inbound::{InboundGroupSession, PickledInboundGroupSession};
26pub(crate) use outbound::ShareState;
27pub use outbound::{
28    EncryptionSettings, OutboundGroupSession, OutboundGroupSessionEncryptionResult,
29    PickledOutboundGroupSession, ShareInfo,
30};
31pub use sender_data::{KnownSenderData, SenderData, SenderDataType};
32use thiserror::Error;
33pub use vodozemac::megolm::{ExportedSessionKey, SessionKey};
34use vodozemac::{Curve25519PublicKey, megolm::SessionKeyDecodeError};
35
36#[cfg(feature = "experimental-algorithms")]
37use crate::types::events::forwarded_room_key::ForwardedMegolmV2AesSha2Content;
38use crate::types::{
39    EventEncryptionAlgorithm, RoomKeyExport, SigningKey, SigningKeys, deserialize_curve_key,
40    deserialize_curve_key_vec,
41    events::forwarded_room_key::{ForwardedMegolmV1AesSha2Content, ForwardedRoomKeyContent},
42    serialize_curve_key, serialize_curve_key_vec,
43};
44
45/// An error type for the creation of group sessions.
46#[derive(Debug, Error)]
47pub enum SessionCreationError {
48    /// The provided algorithm is not supported.
49    #[error("The provided algorithm is not supported: {0}")]
50    Algorithm(EventEncryptionAlgorithm),
51    /// The room key key couldn't be decoded.
52    #[error(transparent)]
53    Decode(#[from] SessionKeyDecodeError),
54}
55
56/// An error type for the export of inbound group sessions.
57///
58/// Exported inbound group sessions will be either uploaded as backups, sent as
59/// `m.forwarded_room_key`s, or exported into a file backup.
60#[derive(Debug, Error)]
61pub enum SessionExportError {
62    /// The provided algorithm is not supported.
63    #[error("The provided algorithm is not supported: {0}")]
64    Algorithm(EventEncryptionAlgorithm),
65    /// The session export is missing a claimed Ed25519 sender key.
66    #[error("The provided room key export is missing a claimed Ed25519 sender key")]
67    MissingEd25519Key,
68}
69
70/// An exported version of an [`InboundGroupSession`].
71///
72/// This can be used to share the `InboundGroupSession` in an exported file.
73///
74/// See <https://spec.matrix.org/v1.13/client-server-api/#key-export-format>.
75#[derive(Deserialize, Serialize)]
76#[allow(missing_debug_implementations)]
77pub struct ExportedRoomKey {
78    /// The encryption algorithm that the session uses.
79    pub algorithm: EventEncryptionAlgorithm,
80
81    /// The room where the session is used.
82    pub room_id: OwnedRoomId,
83
84    /// The Curve25519 key of the device which initiated the session originally.
85    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
86    pub sender_key: Curve25519PublicKey,
87
88    /// The ID of the session that the key is for.
89    pub session_id: String,
90
91    /// The key for the session.
92    pub session_key: ExportedSessionKey,
93
94    /// The Ed25519 key of the device which initiated the session originally.
95    #[serde(default)]
96    pub sender_claimed_keys: SigningKeys<DeviceKeyAlgorithm>,
97
98    /// Chain of Curve25519 keys through which this session was forwarded, via
99    /// m.forwarded_room_key events.
100    #[serde(
101        default,
102        deserialize_with = "deserialize_curve_key_vec",
103        serialize_with = "serialize_curve_key_vec"
104    )]
105    pub forwarding_curve25519_key_chain: Vec<Curve25519PublicKey>,
106
107    /// Whether this [`ExportedRoomKey`] can be shared with users who are
108    /// invited to the room in the future, allowing access to history, as
109    /// defined in [MSC3061].
110    ///
111    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
112    #[serde(
113        default,
114        rename = "shared_history",
115        alias = "m.shared_history",
116        alias = "org.matrix.msc3061.shared_history"
117    )]
118    pub shared_history: bool,
119}
120
121impl ExportedRoomKey {
122    /// Create an `ExportedRoomKey` from a `BackedUpRoomKey`.
123    ///
124    /// This can be used when importing the keys from a backup into the store.
125    pub fn from_backed_up_room_key(
126        room_id: OwnedRoomId,
127        session_id: String,
128        room_key: BackedUpRoomKey,
129    ) -> Self {
130        let BackedUpRoomKey {
131            algorithm,
132            sender_key,
133            session_key,
134            sender_claimed_keys,
135            forwarding_curve25519_key_chain,
136            shared_history,
137        } = room_key;
138
139        Self {
140            algorithm,
141            room_id,
142            sender_key,
143            session_id,
144            session_key,
145            sender_claimed_keys,
146            forwarding_curve25519_key_chain,
147            shared_history,
148        }
149    }
150}
151
152impl RoomKeyExport for &ExportedRoomKey {
153    fn room_id(&self) -> &ruma::RoomId {
154        &self.room_id
155    }
156
157    fn session_id(&self) -> &str {
158        &self.session_id
159    }
160
161    fn sender_key(&self) -> Curve25519PublicKey {
162        self.sender_key
163    }
164}
165
166/// A backed up version of an [`InboundGroupSession`].
167///
168/// This can be used to back up the [`InboundGroupSession`] to the server using
169/// [server-side key backups].
170///
171/// See
172/// [https://spec.matrix.org/v1.13/client-server-api/#definition-backedupsessiondata][https-spec-matrix-org-v1-13-client-server-api-definition-backedupsessiondata].
173///
174/// [server-side key backups]: https://spec.matrix.org/v1.13/client-server-api/#server-side-key-backups
175///
176/// [https-spec-matrix-org-v1-13-client-server-api-definition-backedupsessiondata]: https://spec.matrix.org/v1.13/client-server-api/#definition-backedupsessiondata
177#[derive(Deserialize, Serialize)]
178#[allow(missing_debug_implementations)]
179pub struct BackedUpRoomKey {
180    /// The encryption algorithm that the session uses.
181    pub algorithm: EventEncryptionAlgorithm,
182
183    /// The Curve25519 key of the device which initiated the session originally.
184    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
185    pub sender_key: Curve25519PublicKey,
186
187    /// The key for the session.
188    pub session_key: ExportedSessionKey,
189
190    /// The Ed25519 key of the device which initiated the session originally.
191    pub sender_claimed_keys: SigningKeys<DeviceKeyAlgorithm>,
192
193    /// Chain of Curve25519 keys through which this session was forwarded, via
194    /// `m.forwarded_room_key` events.
195    #[serde(
196        default,
197        deserialize_with = "deserialize_curve_key_vec",
198        serialize_with = "serialize_curve_key_vec"
199    )]
200    pub forwarding_curve25519_key_chain: Vec<Curve25519PublicKey>,
201
202    /// Whether this [`BackedUpRoomKey`] can be shared with users who are
203    /// invited to the room in the future, allowing access to history, as
204    /// defined in [MSC3061].
205    ///
206    /// [MSC3061]: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
207    #[serde(
208        default,
209        rename = "shared_history",
210        alias = "m.shared_history",
211        alias = "org.matrix.msc3061.shared_history"
212    )]
213    pub shared_history: bool,
214}
215
216impl TryFrom<ExportedRoomKey> for ForwardedRoomKeyContent {
217    type Error = SessionExportError;
218
219    /// Convert an exported room key into a content for a forwarded room key
220    /// event.
221    ///
222    /// This will fail if the exported room key doesn't contain an Ed25519
223    /// claimed sender key.
224    fn try_from(room_key: ExportedRoomKey) -> Result<ForwardedRoomKeyContent, Self::Error> {
225        match room_key.algorithm {
226            EventEncryptionAlgorithm::MegolmV1AesSha2 => {
227                // The forwarded room key content only supports a single claimed
228                // sender key and it requires it to be a Ed25519 key. This here
229                // will be lossy conversion since we're dropping all other key
230                // types.
231                //
232                // This was fixed by the megolm v2 content. Hopefully we'll
233                // deprecate megolm v1 before we have multiple signing keys.
234                if let Some(SigningKey::Ed25519(claimed_ed25519_key)) =
235                    room_key.sender_claimed_keys.get(&DeviceKeyAlgorithm::Ed25519)
236                {
237                    Ok(ForwardedRoomKeyContent::MegolmV1AesSha2(
238                        ForwardedMegolmV1AesSha2Content {
239                            room_id: room_key.room_id,
240                            session_id: room_key.session_id,
241                            session_key: room_key.session_key,
242                            claimed_sender_key: room_key.sender_key,
243                            claimed_ed25519_key: *claimed_ed25519_key,
244                            forwarding_curve25519_key_chain: room_key
245                                .forwarding_curve25519_key_chain
246                                .clone(),
247                            other: Default::default(),
248                        }
249                        .into(),
250                    ))
251                } else {
252                    Err(SessionExportError::MissingEd25519Key)
253                }
254            }
255            #[cfg(feature = "experimental-algorithms")]
256            EventEncryptionAlgorithm::MegolmV2AesSha2 => {
257                Ok(ForwardedRoomKeyContent::MegolmV2AesSha2(
258                    ForwardedMegolmV2AesSha2Content {
259                        room_id: room_key.room_id,
260                        session_id: room_key.session_id,
261                        session_key: room_key.session_key,
262                        claimed_sender_key: room_key.sender_key,
263                        claimed_signing_keys: room_key.sender_claimed_keys,
264                        other: Default::default(),
265                    }
266                    .into(),
267                ))
268            }
269            _ => Err(SessionExportError::Algorithm(room_key.algorithm)),
270        }
271    }
272}
273
274impl From<ExportedRoomKey> for BackedUpRoomKey {
275    fn from(value: ExportedRoomKey) -> Self {
276        let ExportedRoomKey {
277            algorithm,
278            room_id: _,
279            sender_key,
280            session_id: _,
281            session_key,
282            sender_claimed_keys,
283            forwarding_curve25519_key_chain,
284            shared_history,
285        } = value;
286
287        Self {
288            algorithm,
289            sender_key,
290            session_key,
291            sender_claimed_keys,
292            forwarding_curve25519_key_chain,
293            shared_history,
294        }
295    }
296}
297
298impl TryFrom<ForwardedRoomKeyContent> for ExportedRoomKey {
299    type Error = SessionExportError;
300
301    /// Convert the content of a forwarded room key into a exported room key.
302    fn try_from(forwarded_key: ForwardedRoomKeyContent) -> Result<Self, Self::Error> {
303        let algorithm = forwarded_key.algorithm();
304
305        match forwarded_key {
306            ForwardedRoomKeyContent::MegolmV1AesSha2(content) => {
307                let mut sender_claimed_keys = SigningKeys::new();
308                sender_claimed_keys
309                    .insert(DeviceKeyAlgorithm::Ed25519, content.claimed_ed25519_key.into());
310
311                Ok(Self {
312                    algorithm,
313                    room_id: content.room_id,
314                    session_id: content.session_id,
315                    forwarding_curve25519_key_chain: content.forwarding_curve25519_key_chain,
316                    sender_claimed_keys,
317                    sender_key: content.claimed_sender_key,
318                    session_key: content.session_key,
319                    shared_history: false,
320                })
321            }
322            #[cfg(feature = "experimental-algorithms")]
323            ForwardedRoomKeyContent::MegolmV2AesSha2(content) => Ok(Self {
324                algorithm,
325                room_id: content.room_id,
326                session_id: content.session_id,
327                forwarding_curve25519_key_chain: Default::default(),
328                sender_claimed_keys: content.claimed_signing_keys,
329                sender_key: content.claimed_sender_key,
330                session_key: content.session_key,
331                shared_history: false,
332            }),
333            ForwardedRoomKeyContent::Unknown(c) => Err(SessionExportError::Algorithm(c.algorithm)),
334        }
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use serde_json::json;
341
342    use super::BackedUpRoomKey;
343
344    #[test]
345    fn test_deserialize_backed_up_key() {
346        let data = json!({
347                "algorithm": "m.megolm.v1.aes-sha2",
348                "room_id": "!room:id",
349                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
350                "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
351                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
352                "sender_claimed_keys": {
353                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
354                },
355                "forwarding_curve25519_key_chain": ["DBPC2zr6c9qimo9YRFK3RVr0Two/I6ODb9mbsToZN3Q", "bBc/qzZFOOKshMMT+i4gjS/gWPDoKfGmETs9yfw9430"]
356        });
357
358        let backed_up_room_key: BackedUpRoomKey = serde_json::from_value(data)
359            .expect("We should be able to deserialize the backed up room key.");
360        assert_eq!(
361            backed_up_room_key.forwarding_curve25519_key_chain.len(),
362            2,
363            "The number of forwarding Curve25519 chains should be two."
364        );
365    }
366}