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