Skip to main content

matrix_sdk_crypto/
secret_storage.rs

1// Copyright 2023 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//! Helpers for implementing the Secrets Storage mechanism from the Matrix
16//! [spec].
17//!
18//! [spec]: https://spec.matrix.org/v1.8/client-server-api/#storage
19
20// This is here because we have a zeroize(skip) further below, which incorrectly
21// triggers a unused_assignments warning due to the macro not using a variable.
22//
23// This will be fixed once we bump Zeroize.
24#![allow(unused_assignments)]
25
26use std::fmt;
27
28use hmac::Hmac;
29pub use hmac::digest::MacError;
30use pbkdf2::pbkdf2;
31use rand::{
32    Rng,
33    distr::{Alphanumeric, SampleString},
34    rng,
35};
36use ruma::{
37    UInt,
38    events::{
39        GlobalAccountDataEventContent, GlobalAccountDataEventType,
40        secret::request::SecretName,
41        secret_storage::{
42            key::{
43                PassPhrase, SecretStorageEncryptionAlgorithm, SecretStorageKeyEventContent,
44                SecretStorageV1AesHmacSha2Properties,
45            },
46            secret::{
47                AesHmacSha2EncryptedData as RumaAesHmacSha2EncryptedData, SecretEncryptedData,
48            },
49        },
50    },
51    serde::{Base64, JsonCastable},
52};
53use serde::{Deserialize, Serialize, de::Error};
54use sha2::Sha512;
55use subtle::ConstantTimeEq;
56use thiserror::Error;
57use zeroize::{Zeroize, ZeroizeOnDrop};
58
59use crate::ciphers::{AesHmacSha2Key, HmacSha256Mac, IV_SIZE, KEY_SIZE, MAC_SIZE};
60
61/// Error type for the decoding of a [`SecretStorageKey`].
62///
63/// The [`SecretStorageKey`] can be restored from a Base58 encoded string or
64/// from a string containing a passphrase.
65///
66/// This error type is used to report errors when trying to restore from either
67/// of those strings.
68#[derive(Debug, Error)]
69pub enum DecodeError {
70    /// The decoded secret storage key has an invalid prefix.
71    #[error("The decoded secret storage key has an invalid prefix: expected {0:?}, got {1:?}")]
72    Prefix([u8; 2], [u8; 2]),
73    /// The parity byte of the secret storage key didn't match.
74    #[error("The parity byte of the secret storage key doesn't match: expected {0:?}, got {1:?}")]
75    Parity(u8, u8),
76    /// The secret storage key isn't valid Base58.
77    #[error(transparent)]
78    Base58(#[from] bs58::decode::Error),
79    /// The secret storage key isn't valid Base64.
80    #[error(transparent)]
81    Base64(#[from] vodozemac::Base64DecodeError),
82    /// The secret storage key is too short, we couldn't read enough data.
83    #[error("The Base58 decoded key has an invalid length, expected {0}, got {1}")]
84    KeyLength(usize, usize),
85    /// The typed in secret storage was incorrect, the MAC check failed.
86    #[error("The MAC check for the secret storage key failed")]
87    Mac(#[from] MacError),
88    /// The MAC of the secret storage key for the MAC check has an incorrect
89    /// length.
90    #[error(
91        "The MAC of for the secret storage MAC check has an incorrect length, \
92         expected: {0}, got: {1}"
93    )]
94    MacLength(usize, usize),
95    /// The IV of the secret storage key for the MAC check has an incorrect
96    /// length.
97    #[error(
98        "The IV of for the secret storage key MAC check has an incorrect length, \
99         expected: {0}, got: {1}"
100    )]
101    IvLength(usize, usize),
102    /// The secret storage key is using an unsupported secret encryption
103    /// algorithm. Currently only the [`m.secret_storage.v1.aes-hmac-sha2`]
104    /// algorithm is supported.
105    ///
106    /// [`m.secret_storage.v1.aes-hmac-sha2`]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
107    #[error("The secret storage key is using an unsupported secret encryption algorithm: {0}")]
108    UnsupportedAlgorithm(String),
109    /// The passphrase-based secret storage key has an excessively high KDF
110    /// iteration count.
111    #[error(
112        "The passphrase-based secret storage key has an excessively high KDF iteration count: {0}"
113    )]
114    KdfIterationCount(UInt),
115}
116
117/// A secret storage key which can be used to store encrypted data in the user's
118/// account data as defined in the [spec].
119///
120/// The secret storage key can be initialized from a passphrase or from a
121/// base58-encoded string.
122///
123/// To bootstrap a new [`SecretStorageKey`], use the [`SecretStorageKey::new()`]
124/// or [`SecretStorageKey::new_from_passphrase()`] method.
125///
126/// After a new [`SecretStorageKey`] has been created, the info about the key
127/// needs to be uploaded to the homeserver as a global account data event. The
128/// event and event type for this can be retrieved using the
129/// [`SecretStorageKey::event_content()`] and [`SecretStorageKey::event_type()`]
130/// methods, respectively.
131///
132/// # Examples
133///
134/// ```no_run
135/// use matrix_sdk_crypto::secret_storage::SecretStorageKey;
136///
137/// // Create a new secret storage key.
138/// let key =
139///     SecretStorageKey::new_from_passphrase("It's a secret to everybody");
140/// // Retrieve the content.
141/// let content = key.event_content();
142/// // Now upload the content to the server and mark the new key as the default one.
143///
144/// // If we want to restore the secret key, we'll need to retrieve the previously uploaded global
145/// // account data event.
146/// let restored_key = SecretStorageKey::from_account_data(
147///     "It's a secret to everybody",
148///     content.to_owned()
149/// );
150/// ```
151///
152/// [spec]: https://spec.matrix.org/v1.8/client-server-api/#secret-storage
153#[derive(Zeroize, ZeroizeOnDrop)]
154pub struct SecretStorageKey {
155    /// Information about the secret storage key.
156    ///
157    /// This is uploaded to the homeserver in a global account data event.
158    #[zeroize(skip)]
159    storage_key_info: SecretStorageKeyEventContent,
160    /// The private key material.
161    secret_key: Box<[u8; 32]>,
162}
163
164#[cfg(not(tarpaulin_include))]
165impl fmt::Debug for SecretStorageKey {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        f.debug_struct("SecretStorageKey")
168            .field("storage_key_info", &self.storage_key_info)
169            .finish_non_exhaustive()
170    }
171}
172
173/// Encrypted data for the AES-CTR/HMAC-SHA-256 secret storage algorithm.
174#[derive(Clone, Debug, Deserialize, Serialize)]
175#[serde(into = "RumaAesHmacSha2EncryptedData", try_from = "RumaAesHmacSha2EncryptedData")]
176pub struct AesHmacSha2EncryptedData {
177    /// The initialization vector that was used to encrypt the ciphertext.
178    pub iv: [u8; IV_SIZE],
179    /// The ciphertext of the message.
180    pub ciphertext: Base64,
181    /// The message authentication code ensuring that the message was not
182    /// forged.
183    pub mac: [u8; MAC_SIZE],
184}
185
186impl TryFrom<RumaAesHmacSha2EncryptedData> for AesHmacSha2EncryptedData {
187    type Error = serde_json::Error;
188
189    fn try_from(value: RumaAesHmacSha2EncryptedData) -> Result<Self, Self::Error> {
190        let RumaAesHmacSha2EncryptedData { iv, ciphertext, mac, .. } = value;
191        let iv_length = iv.as_bytes().len();
192        let mac_length = mac.as_bytes().len();
193
194        if iv_length != IV_SIZE {
195            Err(serde_json::Error::custom(format!(
196                "Invalid initialization vector length, expected length {IV_SIZE}, got: {iv_length}",
197            )))
198        } else if mac_length != MAC_SIZE {
199            Err(serde_json::Error::custom(format!(
200                "Invalid message authentication tag length, expected length {MAC_SIZE}, got: {mac_length}",
201            )))
202        } else {
203            let mut mac_array = [0u8; MAC_SIZE];
204            let mut iv_array = [0u8; IV_SIZE];
205
206            mac_array.copy_from_slice(mac.as_bytes());
207            iv_array.copy_from_slice(iv.as_bytes());
208
209            Ok(Self { iv: iv_array, ciphertext, mac: mac_array })
210        }
211    }
212}
213
214impl From<AesHmacSha2EncryptedData> for RumaAesHmacSha2EncryptedData {
215    fn from(value: AesHmacSha2EncryptedData) -> Self {
216        Self::new(Base64::new(value.iv.to_vec()), value.ciphertext, Base64::new(value.mac.to_vec()))
217    }
218}
219
220impl JsonCastable<SecretEncryptedData> for AesHmacSha2EncryptedData {}
221impl JsonCastable<AesHmacSha2EncryptedData> for SecretEncryptedData {}
222
223impl SecretStorageKey {
224    const ZERO_MESSAGE: &'static [u8; 32] = &[0u8; 32];
225    const PREFIX: [u8; 2] = [0x8b, 0x01];
226    const PREFIX_PARITY: u8 = Self::PREFIX[0] ^ Self::PREFIX[1];
227    const DEFAULT_KEY_ID_LEN: usize = 32;
228    #[cfg(not(test))]
229    const DEFAULT_PBKDF_ITERATIONS: u32 = 500_000;
230    #[cfg(test)]
231    const DEFAULT_PBKDF_ITERATIONS: u32 = 10;
232
233    // 35 bytes in total: a 2-byte prefix, 32 bytes for the key material and one
234    // parity byte
235    const DECODED_BASE58_KEY_LEN: usize = 2 + 32 + 1;
236
237    /// Calculate a parity byte for the base58-encoded variant of the
238    /// [`SecretStorageKey`]. Described in the [spec].
239    ///
240    /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#key-representation
241    fn parity_byte(bytes: &[u8]) -> u8 {
242        bytes.iter().fold(Self::PREFIX_PARITY, |acc, x| acc ^ x)
243    }
244
245    /// Check that the [`SecretStorageKey`] is the one described in the given
246    /// [`SecretEncryptionAlgorithm`].
247    ///
248    /// This is done by encrypting a message containing zero bytes and comparing
249    /// the MAC of this encrypted message to the MAC given in the
250    /// [`SecretEncryptionAlgorithm`]. The exact steps are described in the
251    /// [spec].
252    ///
253    /// This check needs to be done every time we restore a [`SecretStorageKey`]
254    /// from a passphrase or from the base58-encoded variant of it.
255    ///
256    /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
257    fn check_zero_message(&self) -> Result<(), DecodeError> {
258        match &self.storage_key_info.algorithm {
259            SecretStorageEncryptionAlgorithm::V1AesHmacSha2(properties) => {
260                let (Some(iv), Some(mac)) = (&properties.iv, &properties.mac) else {
261                    // The IV and/or MAC are missing from the account data
262                    // content. As the [spec] says, we have to assume that the
263                    // key is valid.
264                    //
265                    // [spec]: https://spec.matrix.org/unstable/client-server-api/#msecret_storagev1aes-hmac-sha2
266                    return Ok(());
267                };
268
269                let iv = iv.as_bytes();
270                let iv_length = iv.len();
271
272                if iv_length != IV_SIZE {
273                    return Err(DecodeError::IvLength(IV_SIZE, iv_length));
274                }
275
276                let mut iv_array = [0u8; 16];
277                iv_array.copy_from_slice(iv);
278
279                // I'm not particularly convinced that this couldn't have been
280                // done simpler. Why do we need to reproduce the ciphertext?
281                // Couldn't we just generate the MAC tag using the
282                // `ZERO_MESSAGE`?
283                //
284                // If someone is reading this and is designing a new secret
285                // encryption algorithm, please consider the above suggestion.
286                let key = AesHmacSha2Key::from_secret_storage_key(&self.secret_key, "");
287                let ciphertext = key.apply_keystream(Self::ZERO_MESSAGE.to_vec(), &iv_array);
288                let expected_mac = HmacSha256Mac::from_slice(mac.as_bytes())
289                    .ok_or_else(|| DecodeError::MacLength(MAC_SIZE, mac.as_bytes().len()))?;
290
291                key.verify_mac(&ciphertext, expected_mac.as_bytes())?;
292
293                Ok(())
294            }
295            custom => Err(DecodeError::UnsupportedAlgorithm(custom.algorithm().to_owned())),
296        }
297    }
298
299    fn create_event_content(key_id: String, key: &[u8; KEY_SIZE]) -> SecretStorageKeyEventContent {
300        let key = AesHmacSha2Key::from_secret_storage_key(key, "");
301
302        let (ciphertext, iv) = key.encrypt(Self::ZERO_MESSAGE.to_vec());
303        let iv = Base64::new(iv.to_vec());
304        let mac = Base64::new(key.create_mac_tag(&ciphertext).as_bytes().to_vec());
305
306        SecretStorageKeyEventContent::new(
307            key_id,
308            SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
309                SecretStorageV1AesHmacSha2Properties::new(Some(iv), Some(mac)),
310            ),
311        )
312    }
313
314    /// Create a new random [`SecretStorageKey`].
315    pub fn new() -> Self {
316        let mut key = Box::new([0u8; KEY_SIZE]);
317        let mut rng = rng();
318        rng.fill_bytes(key.as_mut_slice());
319
320        let key_id = Alphanumeric.sample_string(&mut rng, Self::DEFAULT_KEY_ID_LEN);
321
322        Self::from_bytes(key_id, key)
323    }
324
325    /// Create a new passphrase-based [`SecretStorageKey`].
326    ///
327    /// The passphrase will be expanded into a 32-byte key using the `m.pbkdf2`
328    /// algorithm described in the [spec].
329    ///
330    /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#deriving-keys-from-passphrases
331    pub fn new_from_passphrase(passphrase: &str) -> Self {
332        let mut key = Box::new([0u8; 32]);
333        let mut rng = rng();
334        let salt = Alphanumeric.sample_string(&mut rng, Self::DEFAULT_KEY_ID_LEN);
335
336        pbkdf2::<Hmac<Sha512>>(
337            passphrase.as_bytes(),
338            salt.as_bytes(),
339            Self::DEFAULT_PBKDF_ITERATIONS,
340            key.as_mut_slice(),
341        )
342        .expect(
343            "We should be able to expand a passphrase of any length due to \
344             HMAC being able to be initialized with any input size",
345        );
346
347        let key_id = Alphanumeric.sample_string(&mut rng, Self::DEFAULT_KEY_ID_LEN);
348        let mut key = Self::from_bytes(key_id, key);
349
350        key.storage_key_info.passphrase =
351            Some(PassPhrase::new(salt, Self::DEFAULT_PBKDF_ITERATIONS.into()));
352
353        key
354    }
355
356    pub(crate) fn from_bytes(key_id: String, key: Box<[u8; KEY_SIZE]>) -> Self {
357        let storage_key_info = Self::create_event_content(key_id, &key);
358
359        Self { storage_key_info, secret_key: key }
360    }
361
362    /// Restore a [`SecretStorageKey`] from the given input and the description
363    /// of the key.
364    ///
365    /// The [`SecretStorageKeyEventContent`] will contain the description of the
366    /// [`SecretStorageKey`]. The constructor will check if the provided input
367    /// string matches to the description.
368    ///
369    /// The input can be a passphrase or a Base58 export of the
370    /// [`SecretStorageKey`].
371    pub fn from_account_data(
372        input: &str,
373        content: SecretStorageKeyEventContent,
374    ) -> Result<Self, DecodeError> {
375        let key = if let Some(passphrase_info) = &content.passphrase {
376            // If the content defines a passphrase, first try treating the input
377            // as a passphrase.
378            match Self::from_passphrase(input, &content, passphrase_info) {
379                Ok(key) => key,
380                // Let us fallback to Base58 now. If that fails as well, return
381                // the original, passphrase-based error.
382                Err(e) => Self::from_base58(input, &content).map_err(|_| e)?,
383            }
384        } else {
385            // No passphrase info, so it must be base58-encoded.
386            Self::from_base58(input, &content)?
387        };
388
389        Ok(key)
390    }
391
392    fn from_passphrase(
393        passphrase: &str,
394        key_info: &SecretStorageKeyEventContent,
395        passphrase_info: &PassPhrase,
396    ) -> Result<Self, DecodeError> {
397        let mut key = Box::new([0u8; 32]);
398        pbkdf2::<Hmac<Sha512>>(
399            passphrase.as_bytes(),
400            passphrase_info.salt.as_bytes(),
401            passphrase_info
402                .iterations
403                .try_into()
404                .map_err(|_| DecodeError::KdfIterationCount(passphrase_info.iterations))?,
405            key.as_mut_slice(),
406        )
407        .expect(
408            "We should be able to expand a passphrase of any length due to \
409             HMAC being able to be initialized with any input size",
410        );
411
412        let key = Self { storage_key_info: key_info.to_owned(), secret_key: key };
413        key.check_zero_message()?;
414
415        Ok(key)
416    }
417
418    // Parse a secret storage key represented as a base58-encoded string.
419    //
420    // This method reverses the process in the [`SecretStorageKey::to_base58()`]
421    // method.
422    fn parse_base58_key(value: &str) -> Result<Box<[u8; 32]>, DecodeError> {
423        // The spec tells us to remove any whitespace:
424        // > When decoding a raw key, the process should be reversed, with the
425        // > exception that whitespace is insignificant in the user’s input.
426        //
427        // Spec link:
428        // https://spec.matrix.org/unstable/client-server-api/#key-representation
429        let value: String = value.chars().filter(|c| !c.is_whitespace()).collect();
430
431        let mut decoded = bs58::decode(value).with_alphabet(bs58::Alphabet::BITCOIN).into_vec()?;
432
433        let mut prefix = [0u8; 2];
434        let mut key = Box::new([0u8; 32]);
435
436        let decoded_len = decoded.len();
437
438        if decoded_len != Self::DECODED_BASE58_KEY_LEN {
439            Err(DecodeError::KeyLength(Self::DECODED_BASE58_KEY_LEN, decoded_len))
440        } else {
441            prefix.copy_from_slice(&decoded[0..2]);
442            key.copy_from_slice(&decoded[2..34]);
443            let expected_parity = decoded[34];
444
445            decoded.zeroize();
446
447            let parity = Self::parity_byte(key.as_ref());
448
449            let unexpected_choice = prefix.ct_ne(&Self::PREFIX);
450            let unexpected_parity = expected_parity.ct_ne(&parity);
451
452            if unexpected_choice.into() {
453                Err(DecodeError::Prefix(Self::PREFIX, prefix))
454            } else if unexpected_parity.into() {
455                Err(DecodeError::Parity(expected_parity, parity))
456            } else {
457                Ok(key)
458            }
459        }
460    }
461
462    /// Try to create a [`SecretStorageKey`] from a Base58 export.
463    fn from_base58(
464        value: &str,
465        key_info: &SecretStorageKeyEventContent,
466    ) -> Result<Self, DecodeError> {
467        let secret_key = Self::parse_base58_key(value)?;
468        let key = Self { storage_key_info: key_info.to_owned(), secret_key };
469        key.check_zero_message()?;
470
471        Ok(key)
472    }
473
474    /// Export the [`SecretStorageKey`] as a base58-encoded string as defined in
475    /// the [spec].
476    ///
477    /// _Note_: This returns a copy of the private key material of the
478    /// [`SecretStorageKey`] as a string. The caller needs to ensure that this
479    /// string is zeroized.
480    ///
481    /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#key-representation
482    pub fn to_base58(&self) -> String {
483        const DISPLAY_CHUNK_SIZE: usize = 4;
484
485        let mut bytes = Box::new([0u8; Self::DECODED_BASE58_KEY_LEN]);
486
487        // The key is prepended by the two prefix bytes, 0x8b and 0x01.
488        bytes[0..2].copy_from_slice(Self::PREFIX.as_slice());
489        bytes[2..34].copy_from_slice(self.secret_key.as_slice());
490
491        // All the bytes in the string above, including the two header bytes,
492        // are XORed together to form a parity byte. This parity byte is
493        // appended to the byte string.
494        bytes[34] = Self::parity_byte(self.secret_key.as_slice());
495
496        // The byte string is encoded using Base58, using the same mapping as is
497        // used for Bitcoin addresses.
498        let base_58 =
499            bs58::encode(bytes.as_slice()).with_alphabet(bs58::Alphabet::BITCOIN).into_string();
500
501        bytes.zeroize();
502
503        // The string is formatted into groups of four characters separated by
504        // spaces.
505        base_58
506            .chars()
507            .collect::<Vec<char>>()
508            .chunks(DISPLAY_CHUNK_SIZE)
509            .map(|c| c.iter().collect::<String>())
510            .collect::<Vec<_>>()
511            .join(" ")
512    }
513
514    /// Encrypt a given secret string as a Secrets Storage secret with the given
515    /// secret name.
516    ///
517    /// # Examples
518    ///
519    /// ```
520    /// use matrix_sdk_crypto::secret_storage::SecretStorageKey;
521    /// use ruma::events::secret::request::SecretName;
522    ///
523    /// let key = SecretStorageKey::new();
524    /// let secret = "It's a secret to everybody";
525    /// let secret_name = SecretName::from("my-secret");
526    ///
527    /// let encrypted_data = key.encrypt(secret.as_bytes().to_vec(), &secret_name);
528    ///
529    /// let decrypted = key.decrypt(&encrypted_data, &secret_name)?;
530    ///
531    /// assert_eq!(secret.as_bytes(), decrypted);
532    /// # anyhow::Ok(())
533    /// ```
534    pub fn encrypt(
535        &self,
536        plaintext: Vec<u8>,
537        secret_name: &SecretName,
538    ) -> AesHmacSha2EncryptedData {
539        let key = AesHmacSha2Key::from_secret_storage_key(&self.secret_key, secret_name.as_str());
540
541        let (ciphertext, iv) = key.encrypt(plaintext);
542        let mac = key.create_mac_tag(&ciphertext).into_bytes();
543        let ciphertext = Base64::new(ciphertext);
544
545        AesHmacSha2EncryptedData { iv, ciphertext, mac }
546    }
547
548    /// Decrypt the given [`AesHmacSha2EncryptedData`] containing a secret with
549    /// the given secret name.
550    pub fn decrypt(
551        &self,
552        data: &AesHmacSha2EncryptedData,
553        secret_name: &SecretName,
554    ) -> Result<Vec<u8>, MacError> {
555        let key = AesHmacSha2Key::from_secret_storage_key(&self.secret_key, secret_name.as_str());
556        let ciphertext = data.ciphertext.to_owned().into_inner();
557
558        key.verify_mac(&ciphertext, &data.mac)?;
559
560        let plaintext = key.decrypt(ciphertext, &data.iv);
561
562        Ok(plaintext)
563    }
564
565    /// The info about the [`SecretStorageKey`] formatted as a
566    /// [`SecretStorageKeyEventContent`].
567    ///
568    /// The [`SecretStorageKeyEventContent`] contains information about the
569    /// secret storage key. This information can be used to determine whether
570    /// the secret the user has entered is a valid secret for unlocking the
571    /// Secrets Storage (i.e. a valid [`SecretStorageKey`]).
572    pub fn event_content(&self) -> &SecretStorageKeyEventContent {
573        &self.storage_key_info
574    }
575
576    /// The unique ID of this [`SecretStorageKey`].
577    pub fn key_id(&self) -> &str {
578        &self.storage_key_info.key_id
579    }
580
581    /// The event type of this [`SecretStorageKey`].
582    ///
583    /// Can be used when uploading the key info as a
584    /// [`SecretStorageKeyEventContent`] to the homeserver.
585    ///
586    /// The type is equal to the concatenation of the string
587    /// `"m.secret_storage.key."` and the key ID from the
588    /// [`SecretStorageKey::key_id()`] method.
589    pub fn event_type(&self) -> GlobalAccountDataEventType {
590        self.event_content().event_type()
591    }
592}
593
594impl Default for SecretStorageKey {
595    fn default() -> Self {
596        Self::new()
597    }
598}
599
600#[cfg(test)]
601mod test {
602    use assert_matches::assert_matches;
603    use ruma::events::EventContentFromType;
604    use serde_json::{json, value::to_raw_value};
605    use strass::assert_let;
606
607    use super::*;
608
609    const SECRET_STORAGE_KEY: &[u8; 32] = &[0u8; 32];
610
611    #[test]
612    fn encrypting() {
613        let secret = "It's a secret to everybody";
614        let secret_name = SecretName::from("secret_message");
615
616        let key = SecretStorageKey::from_bytes(
617            "key_id".to_owned(),
618            Box::new(SECRET_STORAGE_KEY.to_owned()),
619        );
620
621        let encrypted = key.encrypt(secret.as_bytes().to_vec(), &secret_name);
622        let decrypted = key
623            .decrypt(&encrypted, &secret_name)
624            .expect("We should be able to decrypt the message we just encrypted");
625
626        assert_eq!(
627            secret.as_bytes(),
628            decrypted,
629            "Encryption roundtrip should result in the same plaintext"
630        );
631    }
632
633    #[test]
634    fn from_passphrase_roundtrip() {
635        let passphrase = "It's a secret to everybody";
636        let secret = "Foobar";
637        let secret_name = SecretName::from("secret_message");
638
639        let key = SecretStorageKey::new_from_passphrase("It's a secret to everybody");
640
641        let encrypted = key.encrypt(secret.as_bytes().to_vec(), &secret_name);
642        let content = to_raw_value(key.event_content())
643            .expect("We should be able to serialize the secret storage key event content");
644
645        let content = SecretStorageKeyEventContent::from_parts(
646            &key.event_type().to_string(),
647            &content,
648        )
649        .expect(
650            "We should be able to parse our, just serialized, secret storage key event content",
651        );
652
653        let key = SecretStorageKey::from_account_data(passphrase, content)
654            .expect("We should be able to restore our secret storage key");
655
656        let decrypted = key.decrypt(&encrypted, &secret_name).expect(
657            "We should be able to decrypt the message using the restored secret storage key",
658        );
659
660        assert_eq!(
661            secret.as_bytes(),
662            decrypted,
663            "The encryption roundtrip should produce the same plaintext"
664        );
665    }
666
667    #[test]
668    fn from_base58_roundtrip() {
669        let secret = "Foobar";
670        let secret_name = SecretName::from("secret_message");
671
672        let key = SecretStorageKey::new();
673
674        let encrypted = key.encrypt(secret.as_bytes().to_vec(), &secret_name);
675        let content = to_raw_value(key.event_content())
676            .expect("We should be able to serialize the secret storage key event content");
677
678        let content = SecretStorageKeyEventContent::from_parts(
679            &key.event_type().to_string(),
680            &content,
681        )
682        .expect(
683            "We should be able to parse our, just serialized, secret storage key event content",
684        );
685
686        let base58_key = key.to_base58();
687
688        let key = SecretStorageKey::from_account_data(&base58_key, content)
689            .expect("We should be able to restore our secret storage key");
690
691        let decrypted = key.decrypt(&encrypted, &secret_name).expect(
692            "We should be able to decrypt the message using the restored secret storage key",
693        );
694
695        assert_eq!(
696            secret.as_bytes(),
697            decrypted,
698            "The encryption roundtrip should produce the same plaintext"
699        );
700    }
701
702    #[test]
703    fn from_account_data_and_passphrase() {
704        let json = to_raw_value(&json!({
705            "algorithm":"m.secret_storage.v1.aes-hmac-sha2",
706            "iv":"gH2iNpiETFhApvW6/FFEJQ",
707            "mac":"9Lw12m5SKDipNghdQXKjgpfdj1/K7HFI2brO+UWAGoM",
708            "passphrase":{
709                "algorithm":"m.pbkdf2",
710                "salt":"IuLnH7S85YtZmkkBJKwNUKxWF42g9O1H",
711                "iterations":10
712            }
713        }))
714        .unwrap();
715
716        let content = SecretStorageKeyEventContent::from_parts(
717            "m.secret_storage.key.DZkbKc0RtKSq0z8V61w6KBmJCK6OCiIu",
718            &json,
719        )
720        .expect("We should be able to deserialize our static secret storage key");
721
722        SecretStorageKey::from_account_data("It's a secret to everybody", content)
723            .expect("We should be able to restore the secret storage key");
724    }
725
726    #[test]
727    fn from_account_data_and_base58() {
728        let base58_key = "EsTj 3yST y93F SLpB jJsz eAXc 2XzA ygD3 w69H fGaN TKBj jXEd";
729        let key_id = "bmur2d9ypPUH1msSwCxQOJkuKRmJI55e";
730
731        let json = to_raw_value(&json!({
732            "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
733            "iv": "xv5b6/p3ExEw++wTyfSHEg==",
734            "mac": "ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8="
735        }))
736        .unwrap();
737
738        let content = SecretStorageKeyEventContent::from_parts(
739            &format!("m.secret_storage.key.{key_id}"),
740            &json,
741        )
742        .expect("We should be able to deserialize our static secret storage key");
743
744        let key = SecretStorageKey::from_account_data(base58_key, content)
745            .expect("We should be able to restore the secret storage key");
746
747        assert_eq!(key_id, key.key_id(), "The key should correctly remember the key ID");
748    }
749
750    #[test]
751    fn invalid_key() {
752        let key = SecretStorageKey::new_from_passphrase("It's a secret to everybody");
753
754        let content = to_raw_value(key.event_content())
755            .expect("We should be able to serialize the secret storage key event content");
756
757        let content = SecretStorageKeyEventContent::from_parts(
758            &key.event_type().to_string(),
759            &content,
760        )
761        .expect(
762            "We should be able to parse our, just serialized, secret storage key event content",
763        );
764
765        assert_matches!(
766            SecretStorageKey::from_account_data("It's a secret to nobody", content.to_owned()),
767            Err(DecodeError::Mac(_)),
768            "Using the wrong passphrase should throw a MAC error"
769        );
770
771        let key = SecretStorageKey::new();
772        let base58_key = key.to_base58();
773
774        assert_matches!(
775            SecretStorageKey::from_account_data(&base58_key, content),
776            Err(DecodeError::Mac(_)),
777            "Using the wrong base58 key should throw a MAC error"
778        );
779    }
780
781    /// The `iv` and `mac` properties within the `m.secret_storage.key.*`
782    /// content are optional, and the spec says we must assume the passphrase is
783    /// correct in that case.
784    #[test]
785    fn accepts_any_passphrase_if_mac_and_iv_are_missing() {
786        let mut content = SecretStorageKeyEventContent::new(
787            "my_new_key_id".to_owned(),
788            SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
789                SecretStorageV1AesHmacSha2Properties::new(None, None),
790            ),
791        );
792        content.passphrase =
793            Some(PassPhrase::new("salty goodness".to_owned(), UInt::new_saturating(100)));
794
795        SecretStorageKey::from_account_data("It's a secret to nobody", content)
796            .expect("Should accept any passphrase");
797    }
798
799    #[test]
800    fn base58_parsing() {
801        const DECODED_KEY: [u8; 32] = [
802            159, 189, 70, 187, 52, 81, 113, 198, 246, 2, 44, 154, 37, 213, 104, 27, 165, 78, 236,
803            106, 108, 73, 83, 243, 173, 192, 185, 110, 157, 145, 173, 163,
804        ];
805
806        let key = "EsT           pRvZTnjck8    YrhRAtw XLS84Nr2r9S9LGAWDaExVAPBvLRK   ";
807        let parsed_key = SecretStorageKey::parse_base58_key(key)
808            .expect("Whitespace in the Base58 encoded key should not matter");
809
810        assert_eq!(
811            parsed_key.as_slice(),
812            DECODED_KEY,
813            "Decoding the key should produce the correct bytes"
814        );
815
816        let key = "EsTpRvZTnjck8YrhRAtwXLS84Nr2r9S9LGAWDaExVAPBvLRk";
817        assert_matches!(
818            SecretStorageKey::parse_base58_key(key),
819            Err(DecodeError::Parity(..)),
820            "We should detect an invalid parity byte"
821        );
822
823        let key = "AATpRvZTnjck8YrhRAtwXLS84Nr2r9S9LGAWDaExVAPBvLRk";
824        assert_matches!(
825            SecretStorageKey::parse_base58_key(key),
826            Err(DecodeError::Prefix(..)),
827            "We should detect an invalid prefix"
828        );
829
830        let key = "AATpRvZTnjck8YrhRAtwXLS84Nr2r9S9";
831        assert_matches!(
832            SecretStorageKey::parse_base58_key(key),
833            Err(DecodeError::KeyLength(..)),
834            "We should detect if the key isn't of the correct length"
835        );
836
837        let key = "AATpRvZTnjck8YrhRAtwXLS84Nr0OIl";
838        assert_matches!(
839            SecretStorageKey::parse_base58_key(key),
840            Err(DecodeError::Base58(..)),
841            "We should detect if the key isn't Base58"
842        );
843    }
844
845    #[test]
846    fn encrypted_data_decoding() {
847        let json = json!({
848              "iv": "bdfCwu+ECYgZ/jWTkGrQ/A==",
849              "ciphertext": "lCRSSA1lChONEXj/8RyogsgAa8ouQwYDnLr4XBCheRikrZykLRzPCx3doCE=",
850              "mac": "NXeV1dZaOe2JLvQ6Hh6tFto7AgFFdaQnY0l9pruwdtE="
851        });
852
853        let content: RumaAesHmacSha2EncryptedData = serde_json::from_value(json)
854            .expect("We should be able to deserialize our static JSON content");
855
856        let encrypted_data: AesHmacSha2EncryptedData = content.try_into()
857            .expect("We should be able to convert a valid SecretEncryptedData to a AesHmacSha2EncryptedData struct");
858
859        assert_eq!(
860            encrypted_data.mac,
861            [
862                53, 119, 149, 213, 214, 90, 57, 237, 137, 46, 244, 58, 30, 30, 173, 22, 218, 59, 2,
863                1, 69, 117, 164, 39, 99, 73, 125, 166, 187, 176, 118, 209
864            ]
865        );
866        assert_eq!(
867            encrypted_data.iv,
868            [109, 215, 194, 194, 239, 132, 9, 136, 25, 254, 53, 147, 144, 106, 208, 252]
869        );
870
871        let secret_encrypted_data: RumaAesHmacSha2EncryptedData = encrypted_data.to_owned().into();
872
873        assert_let!(
874            RumaAesHmacSha2EncryptedData { iv, ciphertext, mac, .. } = secret_encrypted_data
875        );
876        assert_eq!(mac.as_bytes(), encrypted_data.mac.as_slice());
877        assert_eq!(iv.as_bytes(), encrypted_data.iv.as_slice());
878        assert_eq!(ciphertext, encrypted_data.ciphertext);
879
880        let invalid_mac_json = json!({
881              "iv": "bdfCwu+ECYgZ/jWTkGrQ/A==",
882              "ciphertext": "lCRSSA1lChONEXj/8RyogsgAa8ouQwYDnLr4XBCheRikrZykLRzPCx3doCE=",
883              "mac": "NXeV1dZaOe2JLvQ6Hh6tFtgFFdaQnY0l9pruwdtE"
884        });
885
886        let content: RumaAesHmacSha2EncryptedData = serde_json::from_value(invalid_mac_json)
887            .expect("We should be able to deserialize our static JSON content");
888
889        let encrypted_data: Result<AesHmacSha2EncryptedData, _> = content.try_into();
890        encrypted_data.expect_err(
891            "We should be able to detect if a SecretEncryptedData content has an invalid MAC",
892        );
893
894        let invalid_iv_json = json!({
895              "iv": "bdfCwu+gZ/jWTkGrQ/A",
896              "ciphertext": "lCRSSA1lChONEXj/8RyogsgAa8ouQwYDnLr4XBCheRikrZykLRzPCx3doCE=",
897              "mac": "NXeV1dZaOe2JLvQ6Hh6tFto7AgFFdaQnY0l9pruwdtE="
898        });
899
900        let content: RumaAesHmacSha2EncryptedData = serde_json::from_value(invalid_iv_json)
901            .expect("We should be able to deserialize our static JSON content");
902
903        let encrypted_data: Result<AesHmacSha2EncryptedData, _> = content.try_into();
904        encrypted_data.expect_err(
905            "We should be able to detect if a SecretEncryptedData content has an invalid IV",
906        );
907    }
908
909    #[test]
910    fn invalid_key_info() {
911        let base58_key = "EsTj 3yST y93F SLpB jJsz eAXc 2XzA ygD3 w69H fGaN TKBj jXEd";
912
913        let content = SecretStorageKeyEventContent::new(
914            "bmur2d9ypPUH1msSwCxQOJkuKRmJI55e".to_owned(),
915            SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
916                SecretStorageV1AesHmacSha2Properties::new(
917                    Some(Base64::new(vec![0u8; 14])),
918                    Some(Base64::new(vec![0u8; 32])),
919                ),
920            ),
921        );
922
923        assert_matches!(
924            SecretStorageKey::from_account_data(base58_key, content),
925            Err(DecodeError::IvLength(..)),
926            "We should correctly detect an invalid IV"
927        );
928
929        let content = SecretStorageKeyEventContent::new(
930            "bmur2d9ypPUH1msSwCxQOJkuKRmJI55e".to_owned(),
931            SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
932                SecretStorageV1AesHmacSha2Properties::new(
933                    Some(Base64::new(vec![0u8; 16])),
934                    Some(Base64::new(vec![0u8; 10])),
935                ),
936            ),
937        );
938
939        assert_matches!(
940            SecretStorageKey::from_account_data(base58_key, content),
941            Err(DecodeError::MacLength(..)),
942            "We should correctly detect an invalid MAC"
943        );
944
945        let json = to_raw_value(&json!({
946            "algorithm": "m.secret_storage.custom",
947            "iv": "xv5b6/p3ExEw++wTyfSHEg==",
948            "mac": "ujBBbXahnTAMkmPUX2/0+VTfUh63pGyVRuBcDMgmJC8="
949        }))
950        .unwrap();
951
952        let content = SecretStorageKeyEventContent::from_parts(
953            "m.secret_storage.key.bmur2d9ypPUH1msSwCxQOJkuKRmJI55e",
954            &json,
955        )
956        .expect("We should be able to deserialize our static secret storage key");
957
958        assert_matches!(
959            SecretStorageKey::from_account_data(base58_key, content),
960            Err(DecodeError::UnsupportedAlgorithm(..)),
961            "We should correctly detect a unsupported algorithm"
962        );
963    }
964}