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