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