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