Skip to main content

matrix_sdk_crypto/types/
mod.rs

1// Copyright 2022-2024 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//! Module containing customized types modeling Matrix keys and events.
16//!
17//! These types were mostly taken from the Ruma project. The types differ in a
18//! couple of important ways to the Ruma types of the same name:
19//!
20//! 1. They are using vodozemac types so we directly deserialize into a
21//!    vodozemac Curve25519 or Ed25519 key.
22//! 2. They support lossless serialization cycles in a canonical JSON supported
23//!    way, meaning the white-space and field order won't be preserved but the
24//!    data will.
25//! 3. Types containing secrets implement the [`Zeroize`] and [`ZeroizeOnDrop`]
26//!    traits to clear out any memory containing secret key material.
27
28use std::{
29    borrow::Borrow,
30    collections::{
31        BTreeMap,
32        btree_map::{IntoIter, Iter},
33    },
34};
35
36use matrix_sdk_common::deserialized_responses::PrivOwnedStr;
37use ruma::{DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceKeyId, RoomId, serde::StringEnum};
38use serde::{Deserialize, Deserializer, Serialize, Serializer};
39use vodozemac::{Curve25519PublicKey, Ed25519PublicKey, KeyError};
40use zeroize::{Zeroize, ZeroizeOnDrop};
41
42mod backup;
43mod cross_signing;
44mod device_keys;
45pub mod events;
46mod one_time_keys;
47pub mod qr_login;
48pub mod requests;
49pub mod room_history;
50mod signatures;
51
52pub use self::{backup::*, cross_signing::*, device_keys::*, one_time_keys::*, signatures::*};
53use crate::store::types::BackupDecryptionKey;
54
55macro_rules! from_base64 {
56    ($foo:ident, $name:ident) => {
57        pub(crate) fn $name<'de, D>(deserializer: D) -> Result<$foo, D::Error>
58        where
59            D: Deserializer<'de>,
60        {
61            let mut string = String::deserialize(deserializer)?;
62
63            let result = $foo::from_base64(&string);
64            string.zeroize();
65
66            result.map_err(serde::de::Error::custom)
67        }
68    };
69}
70
71macro_rules! to_base64 {
72    ($foo:ident, $name:ident) => {
73        pub(crate) fn $name<S>(v: &$foo, serializer: S) -> Result<S::Ok, S::Error>
74        where
75            S: Serializer,
76        {
77            let mut string = v.to_base64();
78            let ret = string.serialize(serializer);
79
80            string.zeroize();
81
82            ret
83        }
84    };
85}
86
87/// Struct containing the bundle of secrets to fully activate a new device for
88/// end-to-end encryption.
89#[derive(Debug, Deserialize, Clone, Serialize, ZeroizeOnDrop)]
90#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
91pub struct SecretsBundle {
92    /// The cross-signing keys.
93    pub cross_signing: CrossSigningSecrets,
94    /// The backup key, if available.
95    pub backup: Option<BackupSecrets>,
96}
97
98/// Data for the secrets bundle containing the cross-signing keys.
99#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
100#[derive(Deserialize, Clone, Serialize, ZeroizeOnDrop)]
101pub struct CrossSigningSecrets {
102    /// The seed for the private part of the cross-signing master key, encoded
103    /// as base64.
104    pub master_key: String,
105    /// The seed for the private part of the cross-signing user-signing key,
106    /// encoded as base64.
107    pub user_signing_key: String,
108    /// The seed for the private part of the cross-signing self-signing key,
109    /// encoded as base64.
110    pub self_signing_key: String,
111}
112
113impl std::fmt::Debug for CrossSigningSecrets {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        f.debug_struct("CrossSigningSecrets")
116            .field("master_key", &"...")
117            .field("user_signing_key", &"...")
118            .field("self_signing_key", &"...")
119            .finish()
120    }
121}
122
123/// Data for the secrets bundle containing the secret and version for a
124/// `m.megolm_backup.v1.curve25519-aes-sha2` backup.
125#[derive(Debug, Deserialize, Clone, Serialize, ZeroizeOnDrop)]
126pub struct MegolmBackupV1Curve25519AesSha2Secrets {
127    /// The private half of the backup key, can be used to access and decrypt
128    /// room keys in the backup. Also called the recovery key in the
129    /// [spec](https://spec.matrix.org/v1.10/client-server-api/#recovery-key).
130    #[serde(serialize_with = "backup_key_to_base64", deserialize_with = "backup_key_from_base64")]
131    pub key: BackupDecryptionKey,
132    /// The backup version that is tied to the above backup key.
133    pub backup_version: String,
134}
135
136from_base64!(BackupDecryptionKey, backup_key_from_base64);
137to_base64!(BackupDecryptionKey, backup_key_to_base64);
138
139/// Enum for the algorithm-specific secrets for the room key backup.
140#[derive(Debug, Clone, ZeroizeOnDrop, Serialize, Deserialize)]
141#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
142#[serde(tag = "algorithm")]
143pub enum BackupSecrets {
144    /// Backup secrets for the `m.megolm_backup.v1.curve25519-aes-sha2` backup
145    /// algorithm.
146    #[serde(rename = "m.megolm_backup.v1.curve25519-aes-sha2")]
147    MegolmBackupV1Curve25519AesSha2(MegolmBackupV1Curve25519AesSha2Secrets),
148}
149
150impl BackupSecrets {
151    /// Get the algorithm of the secrets contained in the [`BackupSecrets`].
152    pub fn algorithm(&self) -> &str {
153        match &self {
154            BackupSecrets::MegolmBackupV1Curve25519AesSha2(_) => {
155                "m.megolm_backup.v1.curve25519-aes-sha2"
156            }
157        }
158    }
159}
160
161/// A collection of signing keys, a map from the key id to the signing key.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct SigningKeys<T: Ord>(BTreeMap<T, SigningKey>);
164
165impl<T: Ord> SigningKeys<T> {
166    /// Create a new, empty, `SigningKeys` collection.
167    pub fn new() -> Self {
168        Self(BTreeMap::new())
169    }
170
171    /// Insert a `SigningKey` into the collection.
172    pub fn insert(&mut self, key_id: T, key: SigningKey) -> Option<SigningKey> {
173        self.0.insert(key_id, key)
174    }
175
176    /// Get a `SigningKey` with the given `DeviceKeyId`.
177    pub fn get<Q>(&self, key_id: &Q) -> Option<&SigningKey>
178    where
179        T: Borrow<Q>,
180        Q: Ord + ?Sized,
181    {
182        self.0.get(key_id)
183    }
184
185    /// Create an iterator over the `SigningKey`s in this collection.
186    pub fn iter(&self) -> Iter<'_, T, SigningKey> {
187        self.0.iter()
188    }
189
190    /// Do we hold any keys in or is our collection completely empty.
191    pub fn is_empty(&self) -> bool {
192        self.0.is_empty()
193    }
194}
195
196impl<T: Ord> Default for SigningKeys<T> {
197    fn default() -> Self {
198        Self::new()
199    }
200}
201
202impl<T: Ord> IntoIterator for SigningKeys<T> {
203    type Item = (T, SigningKey);
204
205    type IntoIter = IntoIter<T, SigningKey>;
206
207    fn into_iter(self) -> Self::IntoIter {
208        self.0.into_iter()
209    }
210}
211
212impl<K: Ord> FromIterator<(K, SigningKey)> for SigningKeys<K> {
213    fn from_iter<T: IntoIterator<Item = (K, SigningKey)>>(iter: T) -> Self {
214        let map = BTreeMap::from_iter(iter);
215
216        Self(map)
217    }
218}
219
220impl<K: Ord, const N: usize> From<[(K, SigningKey); N]> for SigningKeys<K> {
221    fn from(v: [(K, SigningKey); N]) -> Self {
222        let map = BTreeMap::from(v);
223
224        Self(map)
225    }
226}
227
228// Helper trait to generalize between a `OwnedDeviceKeyId` and a
229// `DeviceKeyAlgorithm` so that we can support Deserialize for
230// `SigningKeys<T>`
231trait Algorithm {
232    fn algorithm(&self) -> DeviceKeyAlgorithm;
233}
234
235impl Algorithm for OwnedDeviceKeyId {
236    fn algorithm(&self) -> DeviceKeyAlgorithm {
237        DeviceKeyId::algorithm(self)
238    }
239}
240
241impl Algorithm for DeviceKeyAlgorithm {
242    fn algorithm(&self) -> DeviceKeyAlgorithm {
243        self.to_owned()
244    }
245}
246
247/// An encryption algorithm to be used to encrypt messages sent to a room.
248#[derive(Clone, StringEnum)]
249#[non_exhaustive]
250pub enum EventEncryptionAlgorithm {
251    /// Olm version 1 using Curve25519, AES-256, and SHA-256.
252    #[ruma_enum(rename = "m.olm.v1.curve25519-aes-sha2")]
253    OlmV1Curve25519AesSha2,
254
255    /// Olm version 2 using Curve25519, AES-256, and SHA-256.
256    #[cfg(feature = "experimental-algorithms")]
257    #[ruma_enum(rename = "m.olm.v2.curve25519-aes-sha2")]
258    OlmV2Curve25519AesSha2,
259
260    /// Megolm version 1 using AES-256 and SHA-256.
261    #[ruma_enum(rename = "m.megolm.v1.aes-sha2")]
262    MegolmV1AesSha2,
263
264    /// Megolm version 2 using AES-256 and SHA-256.
265    #[cfg(feature = "experimental-algorithms")]
266    #[ruma_enum(rename = "m.megolm.v2.aes-sha2")]
267    MegolmV2AesSha2,
268
269    #[doc(hidden)]
270    _Custom(PrivOwnedStr),
271}
272
273impl<T: Ord + Serialize> Serialize for SigningKeys<T> {
274    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
275    where
276        S: Serializer,
277    {
278        let keys: BTreeMap<&T, String> =
279            self.0.iter().map(|(key_id, key)| (key_id, key.to_base64())).collect();
280
281        keys.serialize(serializer)
282    }
283}
284
285impl<'de, T: Algorithm + Ord + Deserialize<'de>> Deserialize<'de> for SigningKeys<T> {
286    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
287    where
288        D: Deserializer<'de>,
289    {
290        let map: BTreeMap<T, String> = Deserialize::deserialize(deserializer)?;
291
292        let map: Result<_, _> = map
293            .into_iter()
294            .map(|(key_id, key)| {
295                let key = SigningKey::from_parts(&key_id.algorithm(), key)
296                    .map_err(serde::de::Error::custom)?;
297
298                Ok((key_id, key))
299            })
300            .collect();
301
302        Ok(SigningKeys(map?))
303    }
304}
305
306// Vodozemac serializes Curve25519 keys directly as a byteslice, while Matrix
307// likes to base64 encode all byte slices.
308//
309// This ensures that we serialize/deserialize in a Matrix-compatible way.
310from_base64!(Curve25519PublicKey, deserialize_curve_key);
311to_base64!(Curve25519PublicKey, serialize_curve_key);
312
313from_base64!(Ed25519PublicKey, deserialize_ed25519_key);
314to_base64!(Ed25519PublicKey, serialize_ed25519_key);
315
316pub(crate) fn deserialize_curve_key_vec<'de, D>(de: D) -> Result<Vec<Curve25519PublicKey>, D::Error>
317where
318    D: Deserializer<'de>,
319{
320    let keys: Vec<String> = Deserialize::deserialize(de)?;
321    let keys: Result<Vec<Curve25519PublicKey>, KeyError> =
322        keys.iter().map(|k| Curve25519PublicKey::from_base64(k)).collect();
323
324    keys.map_err(serde::de::Error::custom)
325}
326
327pub(crate) fn serialize_curve_key_vec<S>(
328    keys: &[Curve25519PublicKey],
329    s: S,
330) -> Result<S::Ok, S::Error>
331where
332    S: Serializer,
333{
334    let keys: Vec<String> = keys.iter().map(|k| k.to_base64()).collect();
335    keys.serialize(s)
336}
337
338mod serde_curve_key_option {
339    use super::{Curve25519PublicKey, Deserialize, Deserializer, Serialize, Serializer};
340
341    pub(crate) fn deserialize<'de, D>(de: D) -> Result<Option<Curve25519PublicKey>, D::Error>
342    where
343        D: Deserializer<'de>,
344    {
345        let key: Option<String> = Deserialize::deserialize(de)?;
346        key.map(|k| Curve25519PublicKey::from_base64(&k))
347            .transpose()
348            .map_err(serde::de::Error::custom)
349    }
350
351    pub(crate) fn serialize<S>(key: &Option<Curve25519PublicKey>, s: S) -> Result<S::Ok, S::Error>
352    where
353        S: Serializer,
354    {
355        let key = key.as_ref().map(|k| k.to_base64());
356        key.serialize(s)
357    }
358}
359
360/// Trait to express the various room key export formats we have in a unified
361/// manner.
362pub trait RoomKeyExport {
363    /// The ID of the room where the exported room key was used.
364    fn room_id(&self) -> &RoomId;
365    /// The unique ID of the exported room key.
366    fn session_id(&self) -> &str;
367    /// The [Curve25519PublicKey] long-term identity key of the sender of this
368    /// room key.
369    fn sender_key(&self) -> Curve25519PublicKey;
370}
371
372#[cfg(test)]
373mod test {
374    use insta::{assert_debug_snapshot, assert_json_snapshot};
375    use serde_json::json;
376    use similar_asserts::assert_eq;
377
378    use super::*;
379
380    #[test]
381    fn serialize_secrets_bundle() {
382        let json = json!({
383            "cross_signing": {
384                "master_key": "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw",
385                "self_signing_key": "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk",
386                "user_signing_key": "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM",
387            },
388            "backup": {
389                "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
390                "backup_version": "2",
391                "key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
392            },
393        });
394
395        let deserialized: SecretsBundle = serde_json::from_value(json.clone())
396            .expect("We should be able to deserialize the secrets bundle");
397
398        let serialized = serde_json::to_value(&deserialized)
399            .expect("We should be able to serialize a secrets bundle");
400
401        assert_eq!(json, serialized, "A serialization cycle should yield the same result");
402    }
403
404    #[test]
405    fn snapshot_backup_decryption_key() {
406        let decryption_key = BackupDecryptionKey { inner: Box::new([1u8; 32]) };
407        assert_json_snapshot!(decryption_key);
408
409        // should not log the key !
410        assert_debug_snapshot!(decryption_key);
411    }
412
413    #[test]
414    fn snapshot_secret_bundle() {
415        let secret_bundle = SecretsBundle {
416            cross_signing: CrossSigningSecrets {
417                master_key: "MSKMSKMSKMSKMSKMSKMSKMSKMSKMSKMSKMSK".to_owned(),
418                user_signing_key: "USKUSKUSKUSKUSKUSKUSKUSKUSKUSKUSKUSK".to_owned(),
419                self_signing_key: "SSKSSKSSKSSKSSKSSKSSKSSKSSKSSKSSK".to_owned(),
420            },
421            backup: Some(BackupSecrets::MegolmBackupV1Curve25519AesSha2(
422                MegolmBackupV1Curve25519AesSha2Secrets {
423                    key: BackupDecryptionKey::from_bytes(&[0u8; 32]),
424                    backup_version: "v1.1".to_owned(),
425                },
426            )),
427        };
428
429        assert_json_snapshot!(secret_bundle);
430
431        let secret_bundle = SecretsBundle {
432            cross_signing: CrossSigningSecrets {
433                master_key: "MSKMSKMSKMSKMSKMSKMSKMSKMSKMSKMSKMSK".to_owned(),
434                user_signing_key: "USKUSKUSKUSKUSKUSKUSKUSKUSKUSKUSKUSK".to_owned(),
435                self_signing_key: "SSKSSKSSKSSKSSKSSKSSKSSKSSKSSKSSK".to_owned(),
436            },
437            backup: None,
438        };
439
440        assert_json_snapshot!(secret_bundle);
441    }
442}