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