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, SigningKey,
39    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#[derive(Deserialize, Serialize)]
71#[allow(missing_debug_implementations)]
72pub struct ExportedRoomKey {
73    /// The encryption algorithm that the session uses.
74    pub algorithm: EventEncryptionAlgorithm,
75
76    /// The room where the session is used.
77    pub room_id: OwnedRoomId,
78
79    /// The Curve25519 key of the device which initiated the session originally.
80    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
81    pub sender_key: Curve25519PublicKey,
82
83    /// The ID of the session that the key is for.
84    pub session_id: String,
85
86    /// The key for the session.
87    pub session_key: ExportedSessionKey,
88
89    /// The Ed25519 key of the device which initiated the session originally.
90    #[serde(default)]
91    pub sender_claimed_keys: SigningKeys<DeviceKeyAlgorithm>,
92
93    /// Chain of Curve25519 keys through which this session was forwarded, via
94    /// m.forwarded_room_key events.
95    #[serde(
96        default,
97        deserialize_with = "deserialize_curve_key_vec",
98        serialize_with = "serialize_curve_key_vec"
99    )]
100    pub forwarding_curve25519_key_chain: Vec<Curve25519PublicKey>,
101}
102
103impl ExportedRoomKey {
104    /// Create an `ExportedRoomKey` from a `BackedUpRoomKey`.
105    ///
106    /// This can be used when importing the keys from a backup into the store.
107    pub fn from_backed_up_room_key(
108        room_id: OwnedRoomId,
109        session_id: String,
110        room_key: BackedUpRoomKey,
111    ) -> Self {
112        Self {
113            algorithm: room_key.algorithm,
114            room_id,
115            sender_key: room_key.sender_key,
116            session_id,
117            session_key: room_key.session_key,
118            sender_claimed_keys: room_key.sender_claimed_keys,
119            forwarding_curve25519_key_chain: room_key.forwarding_curve25519_key_chain,
120        }
121    }
122}
123
124/// A backed up version of an `InboundGroupSession`
125///
126/// This can be used to backup the `InboundGroupSession` to the server.
127#[derive(Deserialize, Serialize)]
128#[allow(missing_debug_implementations)]
129pub struct BackedUpRoomKey {
130    /// The encryption algorithm that the session uses.
131    pub algorithm: EventEncryptionAlgorithm,
132
133    /// The Curve25519 key of the device which initiated the session originally.
134    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
135    pub sender_key: Curve25519PublicKey,
136
137    /// The key for the session.
138    pub session_key: ExportedSessionKey,
139
140    /// The Ed25519 key of the device which initiated the session originally.
141    pub sender_claimed_keys: SigningKeys<DeviceKeyAlgorithm>,
142
143    /// Chain of Curve25519 keys through which this session was forwarded, via
144    /// m.forwarded_room_key events.
145    #[serde(
146        default,
147        deserialize_with = "deserialize_curve_key_vec",
148        serialize_with = "serialize_curve_key_vec"
149    )]
150    pub forwarding_curve25519_key_chain: Vec<Curve25519PublicKey>,
151}
152
153impl TryFrom<ExportedRoomKey> for ForwardedRoomKeyContent {
154    type Error = SessionExportError;
155
156    /// Convert an exported room key into a content for a forwarded room key
157    /// event.
158    ///
159    /// This will fail if the exported room key doesn't contain an Ed25519
160    /// claimed sender key.
161    fn try_from(room_key: ExportedRoomKey) -> Result<ForwardedRoomKeyContent, Self::Error> {
162        match room_key.algorithm {
163            EventEncryptionAlgorithm::MegolmV1AesSha2 => {
164                // The forwarded room key content only supports a single claimed sender
165                // key and it requires it to be a Ed25519 key. This here will be lossy
166                // conversion since we're dropping all other key types.
167                //
168                // This was fixed by the megolm v2 content. Hopefully we'll deprecate megolm v1
169                // before we have multiple signing keys.
170                if let Some(SigningKey::Ed25519(claimed_ed25519_key)) =
171                    room_key.sender_claimed_keys.get(&DeviceKeyAlgorithm::Ed25519)
172                {
173                    Ok(ForwardedRoomKeyContent::MegolmV1AesSha2(
174                        ForwardedMegolmV1AesSha2Content {
175                            room_id: room_key.room_id,
176                            session_id: room_key.session_id,
177                            session_key: room_key.session_key,
178                            claimed_sender_key: room_key.sender_key,
179                            claimed_ed25519_key: *claimed_ed25519_key,
180                            forwarding_curve25519_key_chain: room_key
181                                .forwarding_curve25519_key_chain
182                                .clone(),
183                            other: Default::default(),
184                        }
185                        .into(),
186                    ))
187                } else {
188                    Err(SessionExportError::MissingEd25519Key)
189                }
190            }
191            #[cfg(feature = "experimental-algorithms")]
192            EventEncryptionAlgorithm::MegolmV2AesSha2 => {
193                Ok(ForwardedRoomKeyContent::MegolmV2AesSha2(
194                    ForwardedMegolmV2AesSha2Content {
195                        room_id: room_key.room_id,
196                        session_id: room_key.session_id,
197                        session_key: room_key.session_key,
198                        claimed_sender_key: room_key.sender_key,
199                        claimed_signing_keys: room_key.sender_claimed_keys,
200                        other: Default::default(),
201                    }
202                    .into(),
203                ))
204            }
205            _ => Err(SessionExportError::Algorithm(room_key.algorithm)),
206        }
207    }
208}
209
210impl From<ExportedRoomKey> for BackedUpRoomKey {
211    fn from(k: ExportedRoomKey) -> Self {
212        Self {
213            algorithm: k.algorithm,
214            sender_key: k.sender_key,
215            session_key: k.session_key,
216            sender_claimed_keys: k.sender_claimed_keys,
217            forwarding_curve25519_key_chain: k.forwarding_curve25519_key_chain,
218        }
219    }
220}
221
222impl TryFrom<ForwardedRoomKeyContent> for ExportedRoomKey {
223    type Error = SessionExportError;
224
225    /// Convert the content of a forwarded room key into a exported room key.
226    fn try_from(forwarded_key: ForwardedRoomKeyContent) -> Result<Self, Self::Error> {
227        let algorithm = forwarded_key.algorithm();
228
229        match forwarded_key {
230            ForwardedRoomKeyContent::MegolmV1AesSha2(content) => {
231                let mut sender_claimed_keys = SigningKeys::new();
232                sender_claimed_keys
233                    .insert(DeviceKeyAlgorithm::Ed25519, content.claimed_ed25519_key.into());
234
235                Ok(Self {
236                    algorithm,
237                    room_id: content.room_id,
238                    session_id: content.session_id,
239                    forwarding_curve25519_key_chain: content.forwarding_curve25519_key_chain,
240                    sender_claimed_keys,
241                    sender_key: content.claimed_sender_key,
242                    session_key: content.session_key,
243                })
244            }
245            #[cfg(feature = "experimental-algorithms")]
246            ForwardedRoomKeyContent::MegolmV2AesSha2(content) => Ok(Self {
247                algorithm,
248                room_id: content.room_id,
249                session_id: content.session_id,
250                forwarding_curve25519_key_chain: Default::default(),
251                sender_claimed_keys: content.claimed_signing_keys,
252                sender_key: content.claimed_sender_key,
253                session_key: content.session_key,
254            }),
255            ForwardedRoomKeyContent::Unknown(c) => Err(SessionExportError::Algorithm(c.algorithm)),
256        }
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use serde_json::json;
263
264    use super::BackedUpRoomKey;
265
266    #[test]
267    fn test_deserialize_backed_up_key() {
268        let data = json!({
269                "algorithm": "m.megolm.v1.aes-sha2",
270                "room_id": "!room:id",
271                "sender_key": "FOvlmz18LLI3k/llCpqRoKT90+gFF8YhuL+v1YBXHlw",
272                "session_id": "/2K+V777vipCxPZ0gpY9qcpz1DYaXwuMRIu0UEP0Wa0",
273                "session_key": "AQAAAAAclzWVMeWBKH+B/WMowa3rb4ma3jEl6n5W4GCs9ue65CruzD3ihX+85pZ9hsV9Bf6fvhjp76WNRajoJYX0UIt7aosjmu0i+H+07hEQ0zqTKpVoSH0ykJ6stAMhdr6Q4uW5crBmdTTBIsqmoWsNJZKKoE2+ldYrZ1lrFeaJbjBIY/9ivle++74qQsT2dIKWPanKc9Q2Gl8LjESLtFBD9Fmt",
274                "sender_claimed_keys": {
275                    "ed25519": "F4P7f1Z0RjbiZMgHk1xBCG3KC4/Ng9PmxLJ4hQ13sHA"
276                },
277                "forwarding_curve25519_key_chain": ["DBPC2zr6c9qimo9YRFK3RVr0Two/I6ODb9mbsToZN3Q", "bBc/qzZFOOKshMMT+i4gjS/gWPDoKfGmETs9yfw9430"]
278        });
279
280        let backed_up_room_key: BackedUpRoomKey = serde_json::from_value(data)
281            .expect("We should be able to deserialize the backed up room key.");
282        assert_eq!(
283            backed_up_room_key.forwarding_curve25519_key_chain.len(),
284            2,
285            "The number of forwarding Curve25519 chains should be two."
286        );
287    }
288}