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