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