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