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