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