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