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        btree_map::{IntoIter, Iter},
32        BTreeMap,
33    },
34};
35
36use as_variant::as_variant;
37use matrix_sdk_common::deserialized_responses::PrivOwnedStr;
38use ruma::{
39    serde::StringEnum, DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceKeyId, OwnedUserId, RoomId,
40    UserId,
41};
42use serde::{Deserialize, Deserializer, Serialize, Serializer};
43use vodozemac::{Curve25519PublicKey, Ed25519PublicKey, Ed25519Signature, KeyError};
44use zeroize::{Zeroize, ZeroizeOnDrop};
45
46mod backup;
47mod cross_signing;
48mod device_keys;
49pub mod events;
50mod one_time_keys;
51pub mod qr_login;
52pub mod requests;
53pub mod room_history;
54
55pub use self::{backup::*, cross_signing::*, device_keys::*, one_time_keys::*};
56use crate::store::types::BackupDecryptionKey;
57
58macro_rules! from_base64 {
59    ($foo:ident, $name:ident) => {
60        pub(crate) fn $name<'de, D>(deserializer: D) -> Result<$foo, D::Error>
61        where
62            D: Deserializer<'de>,
63        {
64            let mut string = String::deserialize(deserializer)?;
65
66            let result = $foo::from_base64(&string);
67            string.zeroize();
68
69            result.map_err(serde::de::Error::custom)
70        }
71    };
72}
73
74macro_rules! to_base64 {
75    ($foo:ident, $name:ident) => {
76        pub(crate) fn $name<S>(v: &$foo, serializer: S) -> Result<S::Ok, S::Error>
77        where
78            S: Serializer,
79        {
80            let mut string = v.to_base64();
81            let ret = string.serialize(serializer);
82
83            string.zeroize();
84
85            ret
86        }
87    };
88}
89
90/// Struct containing the bundle of secrets to fully activate a new devices for
91/// end-to-end encryption.
92#[derive(Debug, Deserialize, Clone, Serialize, ZeroizeOnDrop)]
93pub struct SecretsBundle {
94    /// The cross-signing keys.
95    pub cross_signing: CrossSigningSecrets,
96    /// The backup key, if available.
97    pub backup: Option<BackupSecrets>,
98}
99
100/// Data for the secrets bundle containing the cross-signing keys.
101#[derive(Deserialize, Clone, Serialize, ZeroizeOnDrop)]
102pub struct CrossSigningSecrets {
103    /// The seed for the private part of the cross-signing master key, encoded
104    /// as base64.
105    pub master_key: String,
106    /// The seed for the private part of the cross-signing user-signing key,
107    /// encoded as base64.
108    pub user_signing_key: String,
109    /// The seed for the private part of the cross-signing self-signing key,
110    /// encoded as base64.
111    pub self_signing_key: String,
112}
113
114impl std::fmt::Debug for CrossSigningSecrets {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("CrossSigningSecrets")
117            .field("master_key", &"...")
118            .field("user_signing_key", &"...")
119            .field("self_signing_key", &"...")
120            .finish()
121    }
122}
123
124/// Data for the secrets bundle containing the secret and version for a
125/// `m.megolm_backup.v1.curve25519-aes-sha2` backup.
126#[derive(Debug, Deserialize, Clone, Serialize, ZeroizeOnDrop)]
127pub struct MegolmBackupV1Curve25519AesSha2Secrets {
128    /// The private half of the backup key, can be used to access and decrypt
129    /// room keys in the backup. Also called the recovery key in the
130    /// [spec](https://spec.matrix.org/v1.10/client-server-api/#recovery-key).
131    #[serde(serialize_with = "backup_key_to_base64", deserialize_with = "backup_key_from_base64")]
132    pub key: BackupDecryptionKey,
133    /// The backup version that is tied to the above backup key.
134    pub backup_version: String,
135}
136
137from_base64!(BackupDecryptionKey, backup_key_from_base64);
138to_base64!(BackupDecryptionKey, backup_key_to_base64);
139
140/// Enum for the algorithm-specific secrets for the room key backup.
141#[derive(Debug, Clone, ZeroizeOnDrop, Serialize, Deserialize)]
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/// Represents a potentially decoded signature (but *not* a validated one).
162///
163/// There are two important cases here:
164///
165/// 1. If the claimed algorithm is supported *and* the payload has an expected
166///    format, the signature will be represent by the enum variant corresponding
167///    to that algorithm. For example, decodable Ed25519 signatures are
168///    represented as `Ed25519(...)`.
169/// 2. If the claimed algorithm is unsupported, the signature is represented as
170///    `Other(...)`.
171#[derive(Clone, Debug, PartialEq, Eq)]
172pub enum Signature {
173    /// A Ed25519 digital signature.
174    Ed25519(Ed25519Signature),
175    /// A digital signature in an unsupported algorithm. The raw signature bytes
176    /// are represented as a base64-encoded string.
177    Other(String),
178}
179
180/// Represents a signature that could not be decoded.
181///
182/// This will currently only hold invalid Ed25519 signatures.
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct InvalidSignature {
185    /// The base64 encoded string that is claimed to contain a signature but
186    /// could not be decoded.
187    pub source: String,
188}
189
190impl Signature {
191    /// Get the Ed25519 signature, if this is one.
192    pub fn ed25519(&self) -> Option<Ed25519Signature> {
193        as_variant!(self, Self::Ed25519).copied()
194    }
195
196    /// Convert the signature to a base64 encoded string.
197    pub fn to_base64(&self) -> String {
198        match self {
199            Signature::Ed25519(s) => s.to_base64(),
200            Signature::Other(s) => s.to_owned(),
201        }
202    }
203}
204
205impl From<Ed25519Signature> for Signature {
206    fn from(signature: Ed25519Signature) -> Self {
207        Self::Ed25519(signature)
208    }
209}
210
211/// Signatures for a signed object.
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct Signatures(
214    BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>>,
215);
216
217impl Signatures {
218    /// Create a new, empty, signatures collection.
219    pub fn new() -> Self {
220        Signatures(Default::default())
221    }
222
223    /// Add the given signature from the given signer and the given key_id to
224    /// the collection.
225    pub fn add_signature(
226        &mut self,
227        signer: OwnedUserId,
228        key_id: OwnedDeviceKeyId,
229        signature: Ed25519Signature,
230    ) -> Option<Result<Signature, InvalidSignature>> {
231        self.0.entry(signer).or_default().insert(key_id, Ok(signature.into()))
232    }
233
234    /// Try to find an Ed25519 signature from the given signer with the given
235    /// key id.
236    pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
237        self.get(signer)?.get(key_id)?.as_ref().ok()?.ed25519()
238    }
239
240    /// Get the map of signatures that belong to the given user.
241    pub fn get(
242        &self,
243        signer: &UserId,
244    ) -> Option<&BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>> {
245        self.0.get(signer)
246    }
247
248    /// Remove all the signatures we currently hold.
249    pub fn clear(&mut self) {
250        self.0.clear()
251    }
252
253    /// Do we hold any signatures or is our collection completely empty.
254    pub fn is_empty(&self) -> bool {
255        self.0.is_empty()
256    }
257
258    /// How many signatures do we currently hold.
259    pub fn signature_count(&self) -> usize {
260        self.0.values().map(|u| u.len()).sum()
261    }
262}
263
264impl Default for Signatures {
265    fn default() -> Self {
266        Self::new()
267    }
268}
269
270impl IntoIterator for Signatures {
271    type Item = (OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>);
272
273    type IntoIter =
274        IntoIter<OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>>;
275
276    fn into_iter(self) -> Self::IntoIter {
277        self.0.into_iter()
278    }
279}
280
281impl<'de> Deserialize<'de> for Signatures {
282    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
283    where
284        D: Deserializer<'de>,
285    {
286        let map: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>> =
287            Deserialize::deserialize(deserializer)?;
288
289        let map = map
290            .into_iter()
291            .map(|(user, signatures)| {
292                let signatures = signatures
293                    .into_iter()
294                    .map(|(key_id, s)| {
295                        let algorithm = key_id.algorithm();
296                        let signature = match algorithm {
297                            DeviceKeyAlgorithm::Ed25519 => Ed25519Signature::from_base64(&s)
298                                .map(|s| s.into())
299                                .map_err(|_| InvalidSignature { source: s }),
300                            _ => Ok(Signature::Other(s)),
301                        };
302
303                        Ok((key_id, signature))
304                    })
305                    .collect::<Result<BTreeMap<_, _>, _>>()?;
306
307                Ok((user, signatures))
308            })
309            .collect::<Result<_, _>>()?;
310
311        Ok(Signatures(map))
312    }
313}
314
315impl Serialize for Signatures {
316    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
317    where
318        S: Serializer,
319    {
320        let signatures: BTreeMap<&OwnedUserId, BTreeMap<&OwnedDeviceKeyId, String>> = self
321            .0
322            .iter()
323            .map(|(u, m)| {
324                (
325                    u,
326                    m.iter()
327                        .map(|(d, s)| {
328                            (
329                                d,
330                                match s {
331                                    Ok(s) => s.to_base64(),
332                                    Err(i) => i.source.to_owned(),
333                                },
334                            )
335                        })
336                        .collect(),
337                )
338            })
339            .collect();
340
341        Serialize::serialize(&signatures, serializer)
342    }
343}
344
345/// A collection of signing keys, a map from the key id to the signing key.
346#[derive(Debug, Clone, PartialEq, Eq)]
347pub struct SigningKeys<T: Ord>(BTreeMap<T, SigningKey>);
348
349impl<T: Ord> SigningKeys<T> {
350    /// Create a new, empty, `SigningKeys` collection.
351    pub fn new() -> Self {
352        Self(BTreeMap::new())
353    }
354
355    /// Insert a `SigningKey` into the collection.
356    pub fn insert(&mut self, key_id: T, key: SigningKey) -> Option<SigningKey> {
357        self.0.insert(key_id, key)
358    }
359
360    /// Get a `SigningKey` with the given `DeviceKeyId`.
361    pub fn get<Q>(&self, key_id: &Q) -> Option<&SigningKey>
362    where
363        T: Borrow<Q>,
364        Q: Ord + ?Sized,
365    {
366        self.0.get(key_id)
367    }
368
369    /// Create an iterator over the `SigningKey`s in this collection.
370    pub fn iter(&self) -> Iter<'_, T, SigningKey> {
371        self.0.iter()
372    }
373
374    /// Do we hold any keys in or is our collection completely empty.
375    pub fn is_empty(&self) -> bool {
376        self.0.is_empty()
377    }
378}
379
380impl<T: Ord> Default for SigningKeys<T> {
381    fn default() -> Self {
382        Self::new()
383    }
384}
385
386impl<T: Ord> IntoIterator for SigningKeys<T> {
387    type Item = (T, SigningKey);
388
389    type IntoIter = IntoIter<T, SigningKey>;
390
391    fn into_iter(self) -> Self::IntoIter {
392        self.0.into_iter()
393    }
394}
395
396impl<K: Ord> FromIterator<(K, SigningKey)> for SigningKeys<K> {
397    fn from_iter<T: IntoIterator<Item = (K, SigningKey)>>(iter: T) -> Self {
398        let map = BTreeMap::from_iter(iter);
399
400        Self(map)
401    }
402}
403
404impl<K: Ord, const N: usize> From<[(K, SigningKey); N]> for SigningKeys<K> {
405    fn from(v: [(K, SigningKey); N]) -> Self {
406        let map = BTreeMap::from(v);
407
408        Self(map)
409    }
410}
411
412// Helper trait to generalize between a `OwnedDeviceKeyId` and a
413// `DeviceKeyAlgorithm` so that we can support Deserialize for
414// `SigningKeys<T>`
415trait Algorithm {
416    fn algorithm(&self) -> DeviceKeyAlgorithm;
417}
418
419impl Algorithm for OwnedDeviceKeyId {
420    fn algorithm(&self) -> DeviceKeyAlgorithm {
421        DeviceKeyId::algorithm(self)
422    }
423}
424
425impl Algorithm for DeviceKeyAlgorithm {
426    fn algorithm(&self) -> DeviceKeyAlgorithm {
427        self.to_owned()
428    }
429}
430
431/// An encryption algorithm to be used to encrypt messages sent to a room.
432#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
433#[non_exhaustive]
434pub enum EventEncryptionAlgorithm {
435    /// Olm version 1 using Curve25519, AES-256, and SHA-256.
436    #[ruma_enum(rename = "m.olm.v1.curve25519-aes-sha2")]
437    OlmV1Curve25519AesSha2,
438
439    /// Olm version 2 using Curve25519, AES-256, and SHA-256.
440    #[cfg(feature = "experimental-algorithms")]
441    #[ruma_enum(rename = "m.olm.v2.curve25519-aes-sha2")]
442    OlmV2Curve25519AesSha2,
443
444    /// Megolm version 1 using AES-256 and SHA-256.
445    #[ruma_enum(rename = "m.megolm.v1.aes-sha2")]
446    MegolmV1AesSha2,
447
448    /// Megolm version 2 using AES-256 and SHA-256.
449    #[cfg(feature = "experimental-algorithms")]
450    #[ruma_enum(rename = "m.megolm.v2.aes-sha2")]
451    MegolmV2AesSha2,
452
453    #[doc(hidden)]
454    _Custom(PrivOwnedStr),
455}
456
457impl<T: Ord + Serialize> Serialize for SigningKeys<T> {
458    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
459    where
460        S: Serializer,
461    {
462        let keys: BTreeMap<&T, String> =
463            self.0.iter().map(|(key_id, key)| (key_id, key.to_base64())).collect();
464
465        keys.serialize(serializer)
466    }
467}
468
469impl<'de, T: Algorithm + Ord + Deserialize<'de>> Deserialize<'de> for SigningKeys<T> {
470    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
471    where
472        D: Deserializer<'de>,
473    {
474        let map: BTreeMap<T, String> = Deserialize::deserialize(deserializer)?;
475
476        let map: Result<_, _> = map
477            .into_iter()
478            .map(|(key_id, key)| {
479                let key = SigningKey::from_parts(&key_id.algorithm(), key)
480                    .map_err(serde::de::Error::custom)?;
481
482                Ok((key_id, key))
483            })
484            .collect();
485
486        Ok(SigningKeys(map?))
487    }
488}
489
490// Vodozemac serializes Curve25519 keys directly as a byteslice, while Matrix
491// likes to base64 encode all byte slices.
492//
493// This ensures that we serialize/deserialize in a Matrix-compatible way.
494from_base64!(Curve25519PublicKey, deserialize_curve_key);
495to_base64!(Curve25519PublicKey, serialize_curve_key);
496
497from_base64!(Ed25519PublicKey, deserialize_ed25519_key);
498to_base64!(Ed25519PublicKey, serialize_ed25519_key);
499
500pub(crate) fn deserialize_curve_key_vec<'de, D>(de: D) -> Result<Vec<Curve25519PublicKey>, D::Error>
501where
502    D: Deserializer<'de>,
503{
504    let keys: Vec<String> = Deserialize::deserialize(de)?;
505    let keys: Result<Vec<Curve25519PublicKey>, KeyError> =
506        keys.iter().map(|k| Curve25519PublicKey::from_base64(k)).collect();
507
508    keys.map_err(serde::de::Error::custom)
509}
510
511pub(crate) fn serialize_curve_key_vec<S>(
512    keys: &[Curve25519PublicKey],
513    s: S,
514) -> Result<S::Ok, S::Error>
515where
516    S: Serializer,
517{
518    let keys: Vec<String> = keys.iter().map(|k| k.to_base64()).collect();
519    keys.serialize(s)
520}
521
522mod serde_curve_key_option {
523    use super::{Curve25519PublicKey, Deserialize, Deserializer, Serialize, Serializer};
524
525    pub(crate) fn deserialize<'de, D>(de: D) -> Result<Option<Curve25519PublicKey>, D::Error>
526    where
527        D: Deserializer<'de>,
528    {
529        let key: Option<String> = Deserialize::deserialize(de)?;
530        key.map(|k| Curve25519PublicKey::from_base64(&k))
531            .transpose()
532            .map_err(serde::de::Error::custom)
533    }
534
535    pub(crate) fn serialize<S>(key: &Option<Curve25519PublicKey>, s: S) -> Result<S::Ok, S::Error>
536    where
537        S: Serializer,
538    {
539        let key = key.as_ref().map(|k| k.to_base64());
540        key.serialize(s)
541    }
542}
543
544/// Trait to express the various room key export formats we have in a unified
545/// manner.
546pub trait RoomKeyExport {
547    /// The ID of the room where the exported room key was used.
548    fn room_id(&self) -> &RoomId;
549    /// The unique ID of the exported room key.
550    fn session_id(&self) -> &str;
551    /// The [Curve25519PublicKey] long-term identity key of the sender of this
552    /// room key.
553    fn sender_key(&self) -> Curve25519PublicKey;
554}
555
556#[cfg(test)]
557mod test {
558    use insta::{assert_debug_snapshot, assert_json_snapshot, with_settings};
559    use ruma::{device_id, user_id};
560    use serde_json::json;
561    use similar_asserts::assert_eq;
562
563    use super::*;
564
565    #[test]
566    fn serialize_secrets_bundle() {
567        let json = json!({
568            "cross_signing": {
569                "master_key": "rTtSv67XGS6k/rg6/yTG/m573cyFTPFRqluFhQY+hSw",
570                "self_signing_key": "4jbPt7jh5D2iyM4U+3IDa+WthgJB87IQN1ATdkau+xk",
571                "user_signing_key": "YkFKtkjcsTxF6UAzIIG/l6Nog/G2RigCRfWj3cjNWeM",
572            },
573            "backup": {
574                "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
575                "backup_version": "2",
576                "key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
577            },
578        });
579
580        let deserialized: SecretsBundle = serde_json::from_value(json.clone())
581            .expect("We should be able to deserialize the secrets bundle");
582
583        let serialized = serde_json::to_value(&deserialized)
584            .expect("We should be able to serialize a secrets bundle");
585
586        assert_eq!(json, serialized, "A serialization cycle should yield the same result");
587    }
588
589    #[test]
590    fn snapshot_backup_decryption_key() {
591        let decryption_key = BackupDecryptionKey { inner: Box::new([1u8; 32]) };
592        assert_json_snapshot!(decryption_key);
593
594        // should not log the key !
595        assert_debug_snapshot!(decryption_key);
596    }
597
598    #[test]
599    fn snapshot_signatures() {
600        let signatures = Signatures(BTreeMap::from([
601            (
602                user_id!("@alice:localhost").to_owned(),
603                BTreeMap::from([
604                    (
605                        DeviceKeyId::from_parts(
606                            DeviceKeyAlgorithm::Ed25519,
607                            device_id!("ABCDEFGH"),
608                        ),
609                        Ok(Signature::from(Ed25519Signature::from_slice(&[0u8; 64]).unwrap())),
610                    ),
611                    (
612                        DeviceKeyId::from_parts(
613                            DeviceKeyAlgorithm::Curve25519,
614                            device_id!("IJKLMNOP"),
615                        ),
616                        Ok(Signature::from(Ed25519Signature::from_slice(&[1u8; 64]).unwrap())),
617                    ),
618                ]),
619            ),
620            (
621                user_id!("@bob:localhost").to_owned(),
622                BTreeMap::from([(
623                    DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, device_id!("ABCDEFGH")),
624                    Err(InvalidSignature { source: "SOME+B64+SOME+B64+SOME+B64+==".to_owned() }),
625                )]),
626            ),
627        ]));
628
629        with_settings!({sort_maps =>true}, {
630            assert_json_snapshot!(signatures)
631        });
632    }
633
634    #[test]
635    fn snapshot_secret_bundle() {
636        let secret_bundle = SecretsBundle {
637            cross_signing: CrossSigningSecrets {
638                master_key: "MSKMSKMSKMSKMSKMSKMSKMSKMSKMSKMSKMSK".to_owned(),
639                user_signing_key: "USKUSKUSKUSKUSKUSKUSKUSKUSKUSKUSKUSK".to_owned(),
640                self_signing_key: "SSKSSKSSKSSKSSKSSKSSKSSKSSKSSKSSK".to_owned(),
641            },
642            backup: Some(BackupSecrets::MegolmBackupV1Curve25519AesSha2(
643                MegolmBackupV1Curve25519AesSha2Secrets {
644                    key: BackupDecryptionKey::from_bytes(&[0u8; 32]),
645                    backup_version: "v1.1".to_owned(),
646                },
647            )),
648        };
649
650        assert_json_snapshot!(secret_bundle);
651
652        let secret_bundle = SecretsBundle {
653            cross_signing: CrossSigningSecrets {
654                master_key: "MSKMSKMSKMSKMSKMSKMSKMSKMSKMSKMSKMSK".to_owned(),
655                user_signing_key: "USKUSKUSKUSKUSKUSKUSKUSKUSKUSKUSKUSK".to_owned(),
656                self_signing_key: "SSKSSKSSKSSKSSKSSKSSKSSKSSKSSKSSK".to_owned(),
657            },
658            backup: None,
659        };
660
661        assert_json_snapshot!(secret_bundle);
662    }
663}