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