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