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