matrix_sdk_store_encryption/
lib.rs

1// Copyright 2022 The Matrix.org Foundation C.I.C.
2// Copyright 2021 Damir Jelić
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![doc = include_str!("../README.md")]
17#![warn(missing_debug_implementations, missing_docs)]
18
19use std::ops::DerefMut;
20
21use base64::{
22    alphabet,
23    engine::{general_purpose, GeneralPurpose},
24    Engine,
25};
26use blake3::{derive_key, Hash};
27use chacha20poly1305::{
28    aead::{Aead, Error as EncryptionError},
29    Key as ChachaKey, KeyInit, XChaCha20Poly1305, XNonce,
30};
31use hmac::Hmac;
32use pbkdf2::pbkdf2;
33use rand::{thread_rng, Error as RandomError, Fill};
34use serde::{de::DeserializeOwned, Deserialize, Serialize};
35use sha2::Sha256;
36use zeroize::{Zeroize, ZeroizeOnDrop};
37
38const VERSION: u8 = 1;
39const KDF_SALT_SIZE: usize = 32;
40const XNONCE_SIZE: usize = 24;
41const KDF_ROUNDS: u32 = 200_000;
42
43const BASE64: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
44
45type MacKeySeed = [u8; 32];
46
47/// Error type for the `StoreCipher` operations.
48#[derive(Debug, thiserror::Error)]
49pub enum Error {
50    /// Failed to serialize a value.
51    #[error("Failed to serialize a value: `{0}`")]
52    Serialization(#[from] rmp_serde::encode::Error),
53
54    /// Failed to deserialize a value.
55    #[error("Failed to deserialize a value: `{0}`")]
56    Deserialization(#[from] rmp_serde::decode::Error),
57
58    /// Failed to deserialize or serialize a JSON value.
59    #[error("Failed to deserialize or serialize a JSON value: `{0}`")]
60    Json(#[from] serde_json::Error),
61
62    /// Error encrypting or decrypting a value.
63    #[error("Error encrypting or decrypting a value: `{0}`")]
64    Encryption(#[from] EncryptionError),
65
66    /// Could not generate enough randomness for a cryptographic operation: {0}
67    #[error("Could not generate enough randomness for a cryptographic operation: `{0}`")]
68    Random(#[from] RandomError),
69
70    /// Unsupported ciphertext version.
71    #[error("Unsupported ciphertext version, expected `{0}`, got `{1}`")]
72    Version(u8, u8),
73
74    /// The ciphertext had an invalid length.
75    #[error("The ciphertext had an invalid length, expected `{0}`, got `{1}`")]
76    Length(usize, usize),
77
78    /// Failed to import a store cipher, the export used a passphrase while
79    /// we are trying to import it using a key or vice-versa.
80    #[error(
81        "Failed to import a store cipher, the export used a passphrase while we are trying to \
82         import it using a key or vice-versa"
83    )]
84    KdfMismatch,
85}
86
87/// An encryption key that can be used to encrypt data for key/value stores.
88///
89/// # Examples
90///
91/// ```
92/// # let example = || {
93/// use matrix_sdk_store_encryption::StoreCipher;
94/// use serde_json::{json, value::Value};
95///
96/// let store_cipher = StoreCipher::new()?;
97///
98/// // Export the store cipher and persist it in your key/value store
99/// let export = store_cipher.export("secret-passphrase")?;
100///
101/// let value = json!({
102///     "some": "data",
103/// });
104///
105/// let encrypted = store_cipher.encrypt_value(&value)?;
106/// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?;
107///
108/// assert_eq!(value, decrypted);
109/// # anyhow::Ok(()) };
110/// ```
111#[allow(missing_debug_implementations)]
112pub struct StoreCipher {
113    inner: Keys,
114}
115
116impl StoreCipher {
117    /// Generate a new random store cipher.
118    pub fn new() -> Result<Self, Error> {
119        Ok(Self { inner: Keys::new()? })
120    }
121
122    /// Encrypt the store cipher using the given passphrase and export it.
123    ///
124    /// This method can be used to persist the `StoreCipher` in an unencrypted
125    /// key/value store in a safe manner.
126    ///
127    /// The `StoreCipher` can later on be restored using
128    /// [`StoreCipher::import`].
129    ///
130    /// # Arguments
131    ///
132    /// * `passphrase` - The passphrase that should be used to encrypt the store
133    ///   cipher.
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// # let example = || {
139    /// use matrix_sdk_store_encryption::StoreCipher;
140    /// use serde_json::json;
141    ///
142    /// let store_cipher = StoreCipher::new()?;
143    ///
144    /// // Export the store cipher and persist it in your key/value store
145    /// let export = store_cipher.export("secret-passphrase");
146    ///
147    /// // Save the export in your key/value store.
148    /// # anyhow::Ok(()) };
149    /// ```
150    pub fn export(&self, passphrase: &str) -> Result<Vec<u8>, Error> {
151        self.export_kdf(passphrase, KDF_ROUNDS)
152    }
153
154    /// Encrypt the store cipher using the given key and export it.
155    ///
156    /// This method can be used to persist the `StoreCipher` in an unencrypted
157    /// key/value store in a safe manner.
158    ///
159    /// The `StoreCipher` can later on be restored using
160    /// [`StoreCipher::import_with_key`].
161    ///
162    /// # Arguments
163    ///
164    /// * `key` - The 32-byte key to be used to encrypt the store cipher. It's
165    ///   recommended to use a freshly and securely generated random key.
166    ///
167    /// # Examples
168    ///
169    /// ```rust,no_run
170    /// # let example = || {
171    /// use matrix_sdk_store_encryption::StoreCipher;
172    /// use serde_json::json;
173    ///
174    /// let store_cipher = StoreCipher::new()?;
175    ///
176    /// // Export the store cipher and persist it in your key/value store
177    /// let export = store_cipher.export_with_key(&[0u8; 32]);
178    ///
179    /// // Save the export in your key/value store.
180    /// # anyhow::Ok(()) };
181    /// ```
182    pub fn export_with_key(&self, key: &[u8; 32]) -> Result<Vec<u8>, Error> {
183        let store_cipher = self.export_helper(key, KdfInfo::None)?;
184        Ok(rmp_serde::to_vec_named(&store_cipher).expect("Can't serialize the store cipher"))
185    }
186
187    fn export_helper(
188        &self,
189        key: &[u8; 32],
190        kdf_info: KdfInfo,
191    ) -> Result<EncryptedStoreCipher, Error> {
192        let key = ChachaKey::from_slice(key.as_ref());
193        let cipher = XChaCha20Poly1305::new(key);
194
195        let nonce = Keys::get_nonce()?;
196
197        let mut keys = [0u8; 64];
198
199        keys[0..32].copy_from_slice(self.inner.encryption_key.as_ref());
200        keys[32..64].copy_from_slice(self.inner.mac_key_seed.as_ref());
201
202        let ciphertext = cipher.encrypt(XNonce::from_slice(&nonce), keys.as_ref())?;
203
204        keys.zeroize();
205
206        Ok(EncryptedStoreCipher {
207            kdf_info,
208            ciphertext_info: CipherTextInfo::ChaCha20Poly1305 { nonce, ciphertext },
209        })
210    }
211
212    #[doc(hidden)]
213    pub fn _insecure_export_fast_for_testing(&self, passphrase: &str) -> Result<Vec<u8>, Error> {
214        self.export_kdf(passphrase, 1000)
215    }
216
217    fn export_kdf(&self, passphrase: &str, kdf_rounds: u32) -> Result<Vec<u8>, Error> {
218        let mut rng = thread_rng();
219
220        let mut salt = [0u8; KDF_SALT_SIZE];
221        salt.try_fill(&mut rng)?;
222
223        let key = StoreCipher::expand_key(passphrase, &salt, kdf_rounds);
224
225        let store_cipher = self.export_helper(
226            &key,
227            KdfInfo::Pbkdf2ToChaCha20Poly1305 { rounds: kdf_rounds, kdf_salt: salt },
228        )?;
229
230        Ok(rmp_serde::to_vec_named(&store_cipher).expect("Can't serialize the store cipher"))
231    }
232
233    fn import_helper(key: &ChachaKey, encrypted: EncryptedStoreCipher) -> Result<Self, Error> {
234        let mut decrypted = match encrypted.ciphertext_info {
235            CipherTextInfo::ChaCha20Poly1305 { nonce, ciphertext } => {
236                let cipher = XChaCha20Poly1305::new(key);
237                let nonce = XNonce::from_slice(&nonce);
238                cipher.decrypt(nonce, ciphertext.as_ref())?
239            }
240        };
241
242        if decrypted.len() != 64 {
243            decrypted.zeroize();
244
245            Err(Error::Length(64, decrypted.len()))
246        } else {
247            let mut encryption_key = Box::new([0u8; 32]);
248            let mut mac_key_seed = Box::new([0u8; 32]);
249
250            encryption_key.copy_from_slice(&decrypted[0..32]);
251            mac_key_seed.copy_from_slice(&decrypted[32..64]);
252
253            let keys = Keys { encryption_key, mac_key_seed };
254
255            decrypted.zeroize();
256
257            Ok(Self { inner: keys })
258        }
259    }
260
261    /// Restore a store cipher from an export encrypted with a passphrase.
262    ///
263    /// # Arguments
264    ///
265    /// * `passphrase` - The passphrase that was used to encrypt the store
266    ///   cipher.
267    ///
268    /// * `encrypted` - The exported and encrypted version of the store cipher.
269    ///
270    /// # Examples
271    ///
272    /// ```rust,no_run
273    /// # let example = || {
274    /// use matrix_sdk_store_encryption::StoreCipher;
275    /// use serde_json::json;
276    ///
277    /// let store_cipher = StoreCipher::new()?;
278    ///
279    /// // Export the store cipher and persist it in your key/value store
280    /// let export = store_cipher.export("secret-passphrase")?;
281    ///
282    /// // This is now the same as `store_cipher`.
283    /// let imported = StoreCipher::import("secret-passphrase", &export)?;
284    ///
285    /// // Save the export in your key/value store.
286    /// # anyhow::Ok(()) };
287    /// ```
288    pub fn import(passphrase: &str, encrypted: &[u8]) -> Result<Self, Error> {
289        // Our old export format used serde_json for the serialization format. Let's
290        // first try the new format and if that fails, try the old one.
291        let encrypted: EncryptedStoreCipher =
292            if let Ok(deserialized) = rmp_serde::from_slice(encrypted) {
293                deserialized
294            } else {
295                serde_json::from_slice(encrypted)?
296            };
297
298        let key = match encrypted.kdf_info {
299            KdfInfo::Pbkdf2ToChaCha20Poly1305 { rounds, kdf_salt } => {
300                Self::expand_key(passphrase, &kdf_salt, rounds)
301            }
302            KdfInfo::None => {
303                return Err(Error::KdfMismatch);
304            }
305        };
306
307        let key = ChachaKey::from_slice(key.as_ref());
308
309        Self::import_helper(key, encrypted)
310    }
311
312    /// Restore a store cipher from an export encrypted with a random key.
313    ///
314    /// # Arguments
315    ///
316    /// * `key` - The 32-byte decryption key that was previously used to encrypt
317    ///   the store cipher.
318    ///
319    /// * `encrypted` - The exported and encrypted version of the store cipher.
320    ///
321    /// # Examples
322    ///
323    /// ```rust,no_run
324    /// # let example = || {
325    /// use matrix_sdk_store_encryption::StoreCipher;
326    /// use serde_json::json;
327    ///
328    /// let store_cipher = StoreCipher::new()?;
329    ///
330    /// // Export the store cipher and persist it in your key/value store
331    /// let export = store_cipher.export_with_key(&[0u8; 32])?;
332    ///
333    /// // This is now the same as `store_cipher`.
334    /// let imported = StoreCipher::import_with_key(&[0u8; 32], &export)?;
335    ///
336    /// // Save the export in your key/value store.
337    /// # anyhow::Ok(()) };
338    /// ```
339    pub fn import_with_key(key: &[u8; 32], encrypted: &[u8]) -> Result<Self, Error> {
340        let encrypted: EncryptedStoreCipher = rmp_serde::from_slice(encrypted)?;
341
342        if let KdfInfo::Pbkdf2ToChaCha20Poly1305 { .. } = encrypted.kdf_info {
343            return Err(Error::KdfMismatch);
344        }
345
346        let key = ChachaKey::from_slice(key.as_ref());
347
348        Self::import_helper(key, encrypted)
349    }
350
351    /// Hash a key before it is inserted into the key/value store.
352    ///
353    /// This prevents the key names from leaking to parties which do not have
354    /// the ability to decrypt the key/value store.
355    ///
356    /// # Arguments
357    ///
358    /// * `table_name` - The name of the key/value table this key will be
359    ///   inserted into. This can also contain additional unique data. It will
360    ///   be used to derive a table-specific cryptographic key which will be
361    ///   used in a keyed hash function. This ensures data independence between
362    ///   the different tables of the key/value store.
363    ///
364    /// * `key` - The key to be hashed, prior to insertion into the key/value
365    ///   store.
366    ///
367    /// **Note**: This is a one-way transformation; you cannot obtain the
368    /// original key from its hash.
369    ///
370    /// # Examples
371    ///
372    /// ```rust,no_run
373    /// # let example = || {
374    /// use matrix_sdk_store_encryption::StoreCipher;
375    /// use serde_json::json;
376    ///
377    /// let store_cipher = StoreCipher::new()?;
378    ///
379    /// let key = "bulbasaur";
380    ///
381    /// // Hash the key so people don't know which pokemon we have collected.
382    /// let hashed_key = store_cipher.hash_key("list-of-pokemon", key.as_ref());
383    ///
384    /// // It's now safe to insert the key into our key/value store.
385    /// # anyhow::Ok(()) };
386    /// ```
387    pub fn hash_key(&self, table_name: &str, key: &[u8]) -> [u8; 32] {
388        let mac_key = self.inner.get_mac_key_for_table(table_name);
389
390        mac_key.mac(key).into()
391    }
392
393    /// Encrypt a value before it is inserted into the key/value store.
394    ///
395    /// A value can be decrypted using the [`StoreCipher::decrypt_value()`]
396    /// method.
397    ///
398    /// # Arguments
399    ///
400    /// * `value` - A value that should be encrypted, any value that implements
401    ///   `Serialize` can be given to this method. The value will be serialized
402    ///   as json before it is encrypted.
403    ///
404    /// # Examples
405    ///
406    /// ```rust,no_run
407    /// # let example = || {
408    /// use matrix_sdk_store_encryption::StoreCipher;
409    /// use serde_json::{json, value::Value};
410    ///
411    /// let store_cipher = StoreCipher::new()?;
412    ///
413    /// let value = json!({
414    ///     "some": "data",
415    /// });
416    ///
417    /// let encrypted = store_cipher.encrypt_value(&value)?;
418    /// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?;
419    ///
420    /// assert_eq!(value, decrypted);
421    /// # anyhow::Ok(()) };
422    /// ```
423    pub fn encrypt_value(&self, value: &impl Serialize) -> Result<Vec<u8>, Error> {
424        let data = serde_json::to_vec(value)?;
425        Ok(serde_json::to_vec(&self.encrypt_value_data(data)?)?)
426    }
427
428    /// Encrypt some data before it is inserted into the key/value store.
429    ///
430    /// A value can be decrypted using the [`StoreCipher::decrypt_value_data()`]
431    /// method. This is the lower level function to `encrypt_value`
432    ///
433    /// # Arguments
434    ///
435    /// * `data` - A value that should be encrypted, encoded as a `Vec<u8>`
436    ///
437    /// # Examples
438    ///
439    /// ```
440    /// # let example = || {
441    /// use matrix_sdk_store_encryption::StoreCipher;
442    /// use serde_json::{json, value::Value};
443    ///
444    /// let store_cipher = StoreCipher::new()?;
445    ///
446    /// let value = serde_json::to_vec(&json!({
447    ///     "some": "data",
448    /// }))?;
449    ///
450    /// let encrypted = store_cipher.encrypt_value_data(value.clone())?;
451    /// let decrypted = store_cipher.decrypt_value_data(encrypted)?;
452    ///
453    /// assert_eq!(value, decrypted);
454    /// # anyhow::Ok(()) };
455    /// ```
456    pub fn encrypt_value_data(&self, mut data: Vec<u8>) -> Result<EncryptedValue, Error> {
457        let nonce = Keys::get_nonce()?;
458        let cipher = XChaCha20Poly1305::new(self.inner.encryption_key());
459
460        let ciphertext = cipher.encrypt(XNonce::from_slice(&nonce), data.as_ref())?;
461
462        data.zeroize();
463        Ok(EncryptedValue { version: VERSION, ciphertext, nonce })
464    }
465
466    /// Encrypt some data before it is inserted into the key/value store,
467    /// using base64 for arrays of integers.
468    ///
469    /// A value can be decrypted using the
470    /// [`StoreCipher::decrypt_value_base64_data()`] method.
471    ///
472    /// # Arguments
473    ///
474    /// * `data` - A value that should be encrypted, encoded as a `Vec<u8>`
475    ///
476    /// # Examples
477    ///
478    /// ```
479    /// # let example = || {
480    /// use matrix_sdk_store_encryption::StoreCipher;
481    /// use serde_json::{json, value::Value};
482    ///
483    /// let store_cipher = StoreCipher::new()?;
484    ///
485    /// let value = serde_json::to_vec(&json!({
486    ///     "some": "data",
487    /// }))?;
488    ///
489    /// let encrypted = store_cipher.encrypt_value_base64_data(value.clone())?;
490    /// let decrypted = store_cipher.decrypt_value_base64_data(encrypted)?;
491    ///
492    /// assert_eq!(value, decrypted);
493    /// # anyhow::Ok(()) };
494    /// ```
495    pub fn encrypt_value_base64_data(&self, data: Vec<u8>) -> Result<EncryptedValueBase64, Error> {
496        self.encrypt_value_data(data).map(EncryptedValueBase64::from)
497    }
498
499    /// Decrypt a value after it was fetched from the key/value store.
500    ///
501    /// A value can be encrypted using the [`StoreCipher::encrypt_value()`]
502    /// method.
503    ///
504    /// # Arguments
505    ///
506    /// * `value` - The ciphertext of a value that should be decrypted.
507    ///
508    /// The method will deserialize the decrypted value into the expected type.
509    ///
510    /// # Examples
511    ///
512    /// ```
513    /// # let example = || {
514    /// use matrix_sdk_store_encryption::StoreCipher;
515    /// use serde_json::{json, value::Value};
516    ///
517    /// let store_cipher = StoreCipher::new()?;
518    ///
519    /// let value = json!({
520    ///     "some": "data",
521    /// });
522    ///
523    /// let encrypted = store_cipher.encrypt_value(&value)?;
524    /// let decrypted: Value = store_cipher.decrypt_value(&encrypted)?;
525    ///
526    /// assert_eq!(value, decrypted);
527    /// # anyhow::Ok(()) };
528    /// ```
529    pub fn decrypt_value<T: DeserializeOwned>(&self, value: &[u8]) -> Result<T, Error> {
530        let value: EncryptedValue = serde_json::from_slice(value)?;
531        let mut plaintext = self.decrypt_value_data(value)?;
532        let ret = serde_json::from_slice(&plaintext);
533        plaintext.zeroize();
534        Ok(ret?)
535    }
536
537    /// Decrypt a base64-encoded value after it was fetched from the key/value
538    /// store.
539    ///
540    /// A value can be encrypted using the
541    /// [`StoreCipher::encrypt_value_base64_data()`] method.
542    ///
543    /// # Arguments
544    ///
545    /// * `value` - The EncryptedValueBase64 of a value that should be
546    ///   decrypted.
547    ///
548    /// The method will return the raw decrypted value
549    ///
550    /// # Examples
551    ///
552    /// ```
553    /// # let example = || {
554    /// use matrix_sdk_store_encryption::StoreCipher;
555    /// use serde_json::{json, value::Value};
556    ///
557    /// let store_cipher = StoreCipher::new()?;
558    ///
559    /// let value = serde_json::to_vec(&json!({
560    ///     "some": "data",
561    /// }))?;
562    ///
563    /// let encrypted = store_cipher.encrypt_value_base64_data(value.clone())?;
564    /// let decrypted = store_cipher.decrypt_value_base64_data(encrypted)?;
565    ///
566    /// assert_eq!(value, decrypted);
567    /// # anyhow::Ok(()) };
568    /// ```
569    pub fn decrypt_value_base64_data(&self, value: EncryptedValueBase64) -> Result<Vec<u8>, Error> {
570        self.decrypt_value_data(value.try_into()?)
571    }
572
573    /// Decrypt a value after it was fetched from the key/value store.
574    ///
575    /// A value can be encrypted using the [`StoreCipher::encrypt_value_data()`]
576    /// method. Lower level method to [`StoreCipher::decrypt_value()`].
577    ///
578    /// # Arguments
579    ///
580    /// * `value` - The EncryptedValue of a value that should be decrypted.
581    ///
582    /// The method will return the raw decrypted value
583    ///
584    /// # Examples
585    ///
586    /// ```
587    /// # let example = || {
588    /// use matrix_sdk_store_encryption::StoreCipher;
589    /// use serde_json::{json, value::Value};
590    ///
591    /// let store_cipher = StoreCipher::new()?;
592    ///
593    /// let value = serde_json::to_vec(&json!({
594    ///     "some": "data",
595    /// }))?;
596    ///
597    /// let encrypted = store_cipher.encrypt_value_data(value.clone())?;
598    /// let decrypted = store_cipher.decrypt_value_data(encrypted)?;
599    ///
600    /// assert_eq!(value, decrypted);
601    /// # anyhow::Ok(()) };
602    /// ```
603    pub fn decrypt_value_data(&self, value: EncryptedValue) -> Result<Vec<u8>, Error> {
604        if value.version != VERSION {
605            return Err(Error::Version(VERSION, value.version));
606        }
607
608        let cipher = XChaCha20Poly1305::new(self.inner.encryption_key());
609        let nonce = XNonce::from_slice(&value.nonce);
610        Ok(cipher.decrypt(nonce, value.ciphertext.as_ref())?)
611    }
612
613    /// Expand the given passphrase into a KEY_SIZE long key.
614    fn expand_key(passphrase: &str, salt: &[u8], rounds: u32) -> Box<[u8; 32]> {
615        let mut key = Box::new([0u8; 32]);
616        pbkdf2::<Hmac<Sha256>>(passphrase.as_bytes(), salt, rounds, key.deref_mut()).expect(
617            "We should be able to expand a passphrase of any length due to \
618             HMAC being able to be initialized with any input size",
619        );
620
621        key
622    }
623}
624
625#[derive(ZeroizeOnDrop)]
626struct MacKey(Box<[u8; 32]>);
627
628impl MacKey {
629    fn mac(&self, input: &[u8]) -> Hash {
630        blake3::keyed_hash(&self.0, input)
631    }
632}
633
634/// Encrypted value, ready for storage, as created by the
635/// [`StoreCipher::encrypt_value_data()`]
636#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
637pub struct EncryptedValue {
638    version: u8,
639    ciphertext: Vec<u8>,
640    nonce: [u8; XNONCE_SIZE],
641}
642
643/// An error representing a failure to decode and encrypted value from base64
644/// back into a `Vec<u8>`.
645#[derive(Debug)]
646pub enum EncryptedValueBase64DecodeError {
647    /// Base64 decoding failed because the string was not valid base64
648    DecodeError(base64::DecodeSliceError),
649
650    /// Decoding the nonce failed because it was not the expected length
651    IncorrectNonceLength(usize),
652}
653
654impl std::fmt::Display for EncryptedValueBase64DecodeError {
655    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
656        let msg = match self {
657            EncryptedValueBase64DecodeError::DecodeError(e) => e.to_string(),
658            EncryptedValueBase64DecodeError::IncorrectNonceLength(length) => {
659                format!("Incorrect nonce length {length}. Expected length: {XNONCE_SIZE}.")
660            }
661        };
662
663        f.write_str(&msg)
664    }
665}
666
667impl From<base64::DecodeSliceError> for EncryptedValueBase64DecodeError {
668    fn from(value: base64::DecodeSliceError) -> Self {
669        Self::DecodeError(value)
670    }
671}
672
673impl From<base64::DecodeError> for EncryptedValueBase64DecodeError {
674    fn from(value: base64::DecodeError) -> Self {
675        Self::DecodeError(value.into())
676    }
677}
678
679impl From<Vec<u8>> for EncryptedValueBase64DecodeError {
680    fn from(value: Vec<u8>) -> Self {
681        Self::IncorrectNonceLength(value.len())
682    }
683}
684
685impl From<EncryptedValueBase64DecodeError> for Error {
686    fn from(value: EncryptedValueBase64DecodeError) -> Self {
687        Error::Deserialization(rmp_serde::decode::Error::Uncategorized(value.to_string()))
688    }
689}
690
691impl TryFrom<EncryptedValueBase64> for EncryptedValue {
692    type Error = EncryptedValueBase64DecodeError;
693
694    fn try_from(value: EncryptedValueBase64) -> Result<Self, Self::Error> {
695        let mut nonce = [0; XNONCE_SIZE];
696        BASE64.decode_slice(value.nonce, &mut nonce)?;
697
698        Ok(Self { version: value.version, ciphertext: BASE64.decode(value.ciphertext)?, nonce })
699    }
700}
701
702/// Encrypted value, ready for storage, as created by the
703/// [`StoreCipher::encrypt_value_base64_data()`]
704#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
705pub struct EncryptedValueBase64 {
706    version: u8,
707    ciphertext: String,
708    nonce: String,
709}
710
711impl EncryptedValueBase64 {
712    /// Create a new EncryptedValueBase64
713    pub fn new(version: u8, ciphertext: &str, nonce: &str) -> Self {
714        Self { version, ciphertext: ciphertext.to_owned(), nonce: nonce.to_owned() }
715    }
716}
717
718impl From<EncryptedValue> for EncryptedValueBase64 {
719    fn from(value: EncryptedValue) -> Self {
720        Self {
721            version: value.version,
722            ciphertext: BASE64.encode(value.ciphertext),
723            nonce: BASE64.encode(value.nonce),
724        }
725    }
726}
727
728#[derive(ZeroizeOnDrop)]
729struct Keys {
730    encryption_key: Box<[u8; 32]>,
731    mac_key_seed: Box<MacKeySeed>,
732}
733
734impl Keys {
735    fn new() -> Result<Self, Error> {
736        let mut encryption_key = Box::new([0u8; 32]);
737        let mut mac_key_seed = Box::new([0u8; 32]);
738
739        let mut rng = thread_rng();
740
741        encryption_key.try_fill(&mut rng)?;
742        mac_key_seed.try_fill(&mut rng)?;
743
744        Ok(Self { encryption_key, mac_key_seed })
745    }
746
747    fn encryption_key(&self) -> &ChachaKey {
748        ChachaKey::from_slice(self.encryption_key.as_slice())
749    }
750
751    fn mac_key_seed(&self) -> &MacKeySeed {
752        &self.mac_key_seed
753    }
754
755    fn get_mac_key_for_table(&self, table_name: &str) -> MacKey {
756        let mut key = MacKey(Box::new([0u8; 32]));
757        let mut output = derive_key(table_name, self.mac_key_seed());
758
759        key.0.copy_from_slice(&output);
760
761        output.zeroize();
762
763        key
764    }
765
766    fn get_nonce() -> Result<[u8; XNONCE_SIZE], RandomError> {
767        let mut nonce = [0u8; XNONCE_SIZE];
768        let mut rng = thread_rng();
769
770        nonce.try_fill(&mut rng)?;
771
772        Ok(nonce)
773    }
774}
775
776/// Version specific info for the key derivation method that is used.
777#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
778enum KdfInfo {
779    None,
780    /// The PBKDF2 to Chacha key derivation variant.
781    Pbkdf2ToChaCha20Poly1305 {
782        /// The number of PBKDF rounds that were used when deriving the store
783        /// key.
784        rounds: u32,
785        /// The salt that was used when the passphrase was expanded into a store
786        /// key.
787        kdf_salt: [u8; KDF_SALT_SIZE],
788    },
789}
790
791/// Version specific info for encryption method that is used to encrypt our
792/// store cipher.
793#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
794enum CipherTextInfo {
795    /// A store cipher encrypted using the ChaCha20Poly1305 AEAD.
796    ChaCha20Poly1305 {
797        /// The nonce that was used to encrypt the ciphertext.
798        nonce: [u8; XNONCE_SIZE],
799        /// The encrypted store cipher.
800        ciphertext: Vec<u8>,
801    },
802}
803
804/// An encrypted version of our store cipher, this can be safely stored in a
805/// database.
806#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
807struct EncryptedStoreCipher {
808    /// Info about the key derivation method that was used to expand the
809    /// passphrase into an encryption key.
810    pub kdf_info: KdfInfo,
811    /// The ciphertext with it's accompanying additional data that is needed to
812    /// decrypt the store cipher.
813    pub ciphertext_info: CipherTextInfo,
814}
815
816#[cfg(test)]
817mod tests {
818    use serde_json::{json, Value};
819
820    use super::{Error, StoreCipher};
821    use crate::{EncryptedValue, EncryptedValueBase64, EncryptedValueBase64DecodeError};
822
823    #[test]
824    fn generating() {
825        StoreCipher::new().unwrap();
826    }
827
828    #[test]
829    fn exporting_store_cipher() -> Result<(), Error> {
830        let passphrase = "it's a secret to everybody";
831        let store_cipher = StoreCipher::new()?;
832
833        let value = json!({
834            "some": "data"
835        });
836
837        let encrypted_value = store_cipher.encrypt_value(&value)?;
838
839        let encrypted = store_cipher._insecure_export_fast_for_testing(passphrase)?;
840        let decrypted = StoreCipher::import(passphrase, &encrypted)?;
841
842        assert_eq!(store_cipher.inner.encryption_key, decrypted.inner.encryption_key);
843        assert_eq!(store_cipher.inner.mac_key_seed, decrypted.inner.mac_key_seed);
844
845        let decrypted_value: Value = decrypted.decrypt_value(&encrypted_value)?;
846
847        assert_eq!(value, decrypted_value);
848
849        // Can't use assert matches here since we don't have a Debug implementation for
850        // StoreCipher.
851        match StoreCipher::import_with_key(&[0u8; 32], &encrypted) {
852            Err(Error::KdfMismatch) => {}
853            _ => panic!(
854                "Invalid error when importing a passphrase-encrypted store cipher with a key"
855            ),
856        }
857
858        let store_cipher = StoreCipher::new()?;
859        let encrypted_value = store_cipher.encrypt_value(&value)?;
860
861        let export = store_cipher.export_with_key(&[0u8; 32])?;
862        let decrypted = StoreCipher::import_with_key(&[0u8; 32], &export)?;
863
864        let decrypted_value: Value = decrypted.decrypt_value(&encrypted_value)?;
865        assert_eq!(value, decrypted_value);
866
867        // Same as above, can't use assert_matches.
868        match StoreCipher::import_with_key(&[0u8; 32], &encrypted) {
869            Err(Error::KdfMismatch) => {}
870            _ => panic!(
871                "Invalid error when importing a key-encrypted store cipher with a passphrase"
872            ),
873        }
874
875        let old_export = json!({
876            "ciphertext_info": {
877                "ChaCha20Poly1305":{
878                    "ciphertext":[
879                        136,202,212,194,9,223,171,109,152,84,140,183,14,55,198,22,150,130,80,135,
880                        161,202,79,205,151,202,120,91,108,154,252,94,56,178,108,216,186,179,167,128,
881                        154,107,243,195,14,138,86,78,140,159,245,170,204,227,27,84,255,161,196,69,
882                        60,150,69,123,67,134,28,50,10,179,250,141,221,19,202,132,28,122,92,116
883                    ],
884                    "nonce":[
885                        108,3,115,54,65,135,250,188,212,204,93,223,78,11,52,46,
886                        124,140,218,73,88,167,50,230
887                    ]
888                }
889            },
890            "kdf_info":{
891                "Pbkdf2ToChaCha20Poly1305":{
892                    "kdf_salt":[
893                        221,133,149,116,199,122,172,189,236,42,26,204,53,164,245,158,137,113,
894                        31,220,239,66,64,51,242,164,185,166,176,218,209,245
895                    ],
896                    "rounds":1000
897                }
898            }
899        });
900
901        let old_export = serde_json::to_vec(&old_export)?;
902
903        StoreCipher::import(passphrase, &old_export)
904            .expect("We can import the old store-cipher export");
905
906        Ok(())
907    }
908
909    #[test]
910    fn test_importing_invalid_store_cipher_does_not_panic() {
911        // This used to panic, we're testing that we're getting a real error.
912        assert!(StoreCipher::import_with_key(&[0; 32], &[0; 64]).is_err())
913    }
914
915    #[test]
916    fn encrypting_values() -> Result<(), Error> {
917        let event = json!({
918                "content": {
919                "body": "Bee Gees - Stayin' Alive",
920                "info": {
921                    "duration": 2140786u32,
922                    "mimetype": "audio/mpeg",
923                    "size": 1563685u32
924                },
925                "msgtype": "m.audio",
926                "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd"
927            },
928        });
929
930        let store_cipher = StoreCipher::new()?;
931
932        let encrypted = store_cipher.encrypt_value(&event)?;
933        let decrypted: Value = store_cipher.decrypt_value(&encrypted)?;
934
935        assert_eq!(event, decrypted);
936
937        Ok(())
938    }
939
940    #[test]
941    fn encrypting_values_base64() -> Result<(), Error> {
942        let event = json!({
943                "content": {
944                "body": "Bee Gees - Stayin' Alive",
945                "info": {
946                    "duration": 2140786u32,
947                    "mimetype": "audio/mpeg",
948                    "size": 1563685u32
949                },
950                "msgtype": "m.audio",
951                "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd"
952            },
953        });
954
955        let store_cipher = StoreCipher::new()?;
956
957        let data = serde_json::to_vec(&event)?;
958        let encrypted = store_cipher.encrypt_value_base64_data(data)?;
959
960        let plaintext = store_cipher.decrypt_value_base64_data(encrypted)?;
961        let decrypted: Value = serde_json::from_slice(&plaintext)?;
962
963        assert_eq!(event, decrypted);
964
965        Ok(())
966    }
967
968    #[test]
969    fn encrypting_keys() -> Result<(), Error> {
970        let store_cipher = StoreCipher::new()?;
971
972        let first = store_cipher.hash_key("some_table", b"It's dangerous to go alone");
973        let second = store_cipher.hash_key("some_table", b"It's dangerous to go alone");
974        let third = store_cipher.hash_key("another_table", b"It's dangerous to go alone");
975        let fourth = store_cipher.hash_key("another_table", b"It's dangerous to go alone");
976        let fifth = store_cipher.hash_key("another_table", b"It's not dangerous to go alone");
977
978        assert_eq!(first, second);
979        assert_ne!(first, third);
980        assert_eq!(third, fourth);
981        assert_ne!(fourth, fifth);
982
983        Ok(())
984    }
985
986    #[test]
987    fn can_round_trip_normal_to_base64_encrypted_values() {
988        let normal1 = EncryptedValue { version: 2, ciphertext: vec![1, 2, 4], nonce: make_nonce() };
989        let normal2 = EncryptedValue { version: 2, ciphertext: vec![1, 2, 4], nonce: make_nonce() };
990
991        // We can convert to base 64 and the result looks as expected
992        let base64: EncryptedValueBase64 = normal1.into();
993        assert_eq!(base64.ciphertext, "AQIE");
994
995        // The round trip leaves it unchanged
996        let new_normal: EncryptedValue = base64.try_into().unwrap();
997        assert_eq!(normal2, new_normal);
998    }
999
1000    #[test]
1001    fn can_round_trip_base64_to_normal_encrypted_values() {
1002        let base64_1 = EncryptedValueBase64 {
1003            version: 2,
1004            ciphertext: "abc".to_owned(),
1005            nonce: make_nonce_base64(),
1006        };
1007        let base64_2 = EncryptedValueBase64 {
1008            version: 2,
1009            ciphertext: "abc".to_owned(),
1010            nonce: make_nonce_base64(),
1011        };
1012
1013        // We can convert to normal and the result looks as expected
1014        let normal: EncryptedValue = base64_1.try_into().unwrap();
1015        assert_eq!(normal.ciphertext, &[105, 183]);
1016
1017        // The round trip leaves it unchanged
1018        let new_base64: EncryptedValueBase64 = normal.into();
1019        assert_eq!(base64_2, new_base64);
1020    }
1021
1022    #[test]
1023    fn decoding_invalid_base64_returns_an_error() {
1024        let base64 =
1025            EncryptedValueBase64 { version: 2, ciphertext: "a".to_owned(), nonce: "b".to_owned() };
1026
1027        let result: Result<EncryptedValue, EncryptedValueBase64DecodeError> = base64.try_into();
1028
1029        let Err(err) = result else {
1030            panic!("Should be an error!");
1031        };
1032
1033        assert_eq!(err.to_string(), "DecodeError: Invalid input length: 1");
1034    }
1035
1036    fn make_nonce() -> [u8; 24] {
1037        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
1038    }
1039
1040    fn make_nonce_base64() -> String {
1041        "AAECAwQFBgcICQoLDA0ODxAREhMUFRYX".to_owned()
1042    }
1043}