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