Skip to main content

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