snapper_box/crypto/
key.rs

1//! Keys and other sensitive cryptographic material <span style="color:red">**HAZMAT**</span>.
2//!
3//! # <span style="color:red">**DANGER**</span>
4//!
5//! This module deals in low level cryptographic details. It is advisable to not deal with this module
6//! directly, and instead use a higher level API.
7use argon2::Argon2;
8use blake3::Hasher;
9use chacha20::{
10    cipher::{generic_array::GenericArray, NewCipher, StreamCipher},
11    XChaCha20, XNonce,
12};
13use rand::{Rng, RngCore};
14use redacted::RedactedBytes;
15use serde::{Deserialize, Serialize};
16use snafu::{ensure, ResultExt};
17use zeroize::{Zeroize, Zeroizing};
18
19use crate::{
20    crypto::types::{CipherText, ClearText},
21    error::{Argon2Failure, BackendError, BadHMAC},
22};
23
24/// Allows access to the subkeys of a key-like structure
25pub trait Key {
26    /// Provides the encryption key as a chacha [`Key`](chacha20::Key)
27    fn encryption_key(&self) -> &chacha20::Key;
28    /// Provides the hmac key
29    fn hmac_key(&self) -> &[u8; 32];
30}
31
32/// Nonce/Salt value used in encryption
33///
34/// This is stored as a 24-byte array, for serialization, but is viewable as an `XChaCha` nonce
35///
36/// Nonces are intended to be always randomly generated, and there is intentionally no API for
37/// reconstructing a nonce.
38#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
39pub struct Nonce(pub(crate) RedactedBytes<24>);
40
41impl Nonce {
42    /// Generates a new, random nonce
43    pub fn random() -> Self {
44        let mut data = [0_u8; 24];
45        rand::thread_rng().fill(&mut data[..]);
46        Self(data.into())
47    }
48    /// Gets a view of the nonce as an `XChaCha20` [`XNonce`]
49    pub fn nonce(&self) -> &XNonce {
50        GenericArray::from_slice(&self.0)
51    }
52}
53
54/// Originating Key
55///
56/// This key consists of an independently, randomly generated encryption key, HMAC key, and an entropy
57/// pool for generating derived keys.
58///
59/// While the components are generated independently from any user provided input, at rest encryption of
60/// this key material is achieved by encrypting the `RootKey` using an argon2 derivation of a user
61/// supplied password. This decoupling of key generation from user input allows several useful things,
62/// such as allowing the password for a store to be changed without having to reencrypt all the data, as
63/// well as making entire categories of attacks effectively impossible.
64///
65/// The included encryption and HMAC keys should only be used for encrypting top-level metadata, to
66/// limit the amount of data encrypted with this key. Derived keys can be produced with a namespace
67/// string via the `derive` method.
68#[derive(Hash, Clone, Serialize, Deserialize, Zeroize)]
69#[zeroize(drop)]
70pub struct RootKey {
71    /// The root cipher key, used for encrypting headers
72    encryption: RedactedBytes<32>,
73    /// The root HMAC key, used for validating headers
74    hmac: RedactedBytes<32>,
75    /// Random data used to derive encryption keys
76    entropy: RedactedBytes<256>,
77}
78
79impl RootKey {
80    /// Generates a new `RootKey`
81    ///
82    /// This method uses a cryptograpically secure random number generator to fill the encryption key, HMAC
83    /// key, and entropy pools with random data.
84    ///
85    /// `RootKey`s should always be randomly generated, and there is intentionally no API for recreating a
86    /// specific `RootKey`
87    pub fn random() -> Self {
88        let mut rand = rand::thread_rng();
89        // Construct ourself first, and mutate in place, to limit the possibility of key material
90        // getting leaked in a place that `zeroize` can't reach
91        let mut ret = Self::null();
92        // Fill keys
93        rand.fill(&mut ret.encryption[..]);
94        rand.fill(&mut ret.hmac[..]);
95        rand.fill(&mut ret.entropy[..]);
96
97        ret
98    }
99    /// Creates an all zero `RootKey`, also known as 'The null key'
100    ///
101    /// # <span style="color:red">**DANGER**</span>
102    ///
103    /// This method exists because this library does not support operating on plaintext at rest data, so the
104    /// all-zero key is used as a known-ahead-of-time key for passwordless use.
105    ///
106    /// This is, hopefully obviously, incredibly insecure, and should only ever be called when storing data
107    /// in plaintext would be appropriate.
108    pub fn null() -> Self {
109        RootKey {
110            encryption: [0_u8; 32].into(),
111            hmac: [0_u8; 32].into(),
112            entropy: [0_u8; 256].into(),
113        }
114    }
115    /// Encrypts this key, producing a [`EncryptedRootKey`], with the provided password.
116    ///
117    /// This uses a byte slice rather than a string to provide more flexibility.
118    ///
119    /// Argon2 and a random salt are used to generate 64 bytes of key material from the user supplied
120    /// password, the first 32 bytes of which are used as the encryption key, and the last 32 bytes are used
121    /// as an HMAC key. This allows us to use the password to provide both encryption and authentication.
122    ///
123    /// # Errors
124    ///
125    /// Will return:
126    ///   * `Error::Argon2Failure` if the argon2 key derivation fails
127    pub fn encrypt(&self, password: &[u8]) -> Result<EncryptedRootKey, BackendError> {
128        // Generate a random salt
129        let mut salt = [0_u8; 32];
130        rand::thread_rng().fill(&mut salt[..]);
131        // Prepare the argon2 instance
132        let argon = Argon2::default();
133        // Prepare the output buffer
134        let mut argon_output = Zeroizing::new([0_u8; 64]);
135        argon
136            .hash_password_into(password, &salt, &mut argon_output[..])
137            .context(Argon2Failure)?;
138        // Use the first half of the argon output as the encryption key
139        let encryption_key: &chacha20::Key = GenericArray::from_slice(&argon_output[0..32]);
140        // Use the second half as the hmac key
141        let mut hmac_key = Zeroizing::new([0_u8; 32]);
142        hmac_key.copy_from_slice(&argon_output[32..]);
143
144        // Get a random nonce
145        let nonce = Nonce::random();
146        // Serialize ourself
147        let mut serial = serde_cbor::to_vec(self).expect("Infallible");
148        // Encrypt ourself
149        let mut chacha = XChaCha20::new(encryption_key, nonce.nonce());
150        chacha.apply_keystream(&mut serial[..]);
151        // Calculate the HMAC
152        let hmac: [u8; 32] = blake3::keyed_hash(&hmac_key, &serial).into();
153        Ok(EncryptedRootKey {
154            nonce,
155            hmac: hmac.into(),
156            salt: salt.into(),
157            payload: serial,
158        })
159    }
160    /// Creates a [`DerivedKey`] from this [`RootKey`] using the provided namespace as part of the
161    /// context string
162    ///
163    /// This method will generate, using a CSPRNG a random, 32 character, hexadecimal nonce (~128 bits of
164    /// entropy) to include in the context string, which will then be combined with the provided namespace
165    /// and fed into Blake3's key derivation mode. Blake3 is used to derive 64 bytes of key material, the
166    /// first 32 bytes of which are used as an encryption key, and the last 32 bytes of which are used as an
167    /// HMAC key.
168    ///
169    /// While [`DerivedKey`] can be used quite safely for a large number of encryptions, it is wise to limit
170    /// the usage of an individual [`DerivedKey`] as much as possible, to limit the fallout of any
171    /// accidental/unintentional nonce reuses.
172    pub fn derive(&self, namespace: &str) -> DerivedKey {
173        // Do this manually for speed.
174        let mut nonce_array = [0_u8; 32];
175        let mut nonce_bytes = [0_u8; 16];
176        rand::thread_rng().fill_bytes(&mut nonce_bytes[..]);
177        for (index, byte) in nonce_bytes.into_iter().enumerate() {
178            // Do the upper nibble
179            let upper_nibble = match byte & 0xF0_u8 {
180                0x00 => 0x30_u8,
181                0x10 => 0x31_u8,
182                0x20 => 0x32_u8,
183                0x30 => 0x33_u8,
184                0x40 => 0x34_u8,
185                0x50 => 0x35_u8,
186                0x60 => 0x36_u8,
187                0x70 => 0x37_u8,
188                0x80 => 0x38_u8,
189                0x90 => 0x39_u8,
190                0xA0 => 0x41_u8,
191                0xB0 => 0x42_u8,
192                0xC0 => 0x43_u8,
193                0xD0 => 0x44_u8,
194                0xE0 => 0x45_u8,
195                0xF0 => 0x46_u8,
196                _ => unreachable!(),
197            };
198            // And the lower nibble
199            let lower_nibble = match byte & 0x0F_u8 {
200                0x00 => 0x30_u8,
201                0x01 => 0x31_u8,
202                0x02 => 0x32_u8,
203                0x03 => 0x33_u8,
204                0x04 => 0x34_u8,
205                0x05 => 0x35_u8,
206                0x06 => 0x36_u8,
207                0x07 => 0x37_u8,
208                0x08 => 0x38_u8,
209                0x09 => 0x39_u8,
210                0x0A => 0x41_u8,
211                0x0B => 0x42_u8,
212                0x0C => 0x43_u8,
213                0x0D => 0x44_u8,
214                0x0E => 0x45_u8,
215                0x0F => 0x46_u8,
216                _ => unreachable!(),
217            };
218            // Set the bytes
219            nonce_array[index * 2] = upper_nibble;
220            nonce_array[index * 2 + 1] = lower_nibble;
221        }
222        // We are only generating ascii chars, so we can mark the failure as unreachable
223        let nonce = if let Ok(nonce) = std::str::from_utf8(&nonce_array[..]) {
224            nonce
225        } else {
226            unreachable!()
227        };
228
229        // Setup the context string
230        let context_string = format!("snapper-box nonce: {} namespace: {}", &*nonce, namespace);
231        self.derive_with_context(context_string)
232    }
233    /// Creates a [`DerivedKey`] from this [`RootKey`] with a specified context string
234    ///
235    /// # <span style="color:red">**DANGER**</span>
236    ///
237    /// The `derive` method intentionally includes a random component to facilitate key rotation. Using this
238    /// method for any other purpose then to rederive a lost key is dangerous, as it can lead to unintended
239    /// key reuse.
240    pub fn derive_with_context(&self, context_string: String) -> DerivedKey {
241        // Setup the hasher
242        let mut hasher = Hasher::new_derive_key(&context_string);
243        // Load in the entropy
244        hasher.update(&self.entropy[..]);
245        // Make the final derived key
246        let mut ret = DerivedKey {
247            encryption: [0_u8; 32].into(),
248            hmac: [0_u8; 32].into(),
249            context_string,
250        };
251        // Load in the keys, and return
252        let mut output = hasher.finalize_xof();
253        output.fill(&mut ret.encryption[..]);
254        output.fill(&mut ret.hmac[..]);
255        ret
256    }
257}
258
259impl Key for RootKey {
260    /// Provides the encryption key as a chacha [`Key`](chacha20::Key)
261    fn encryption_key(&self) -> &chacha20::Key {
262        GenericArray::from_slice(&self.encryption)
263    }
264    /// Provides the hmac key as a reference to the underlying array
265    fn hmac_key(&self) -> &[u8; 32] {
266        self.hmac.as_ref()
267    }
268}
269
270/// A [`RootKey`] that has been encrypted with an argon2 derivation of a users password
271///
272/// See the [`RootKey`] docs for a description of the method used for encryption
273#[derive(Debug, Hash, Clone, Serialize, Deserialize)]
274pub struct EncryptedRootKey {
275    /// The nonce used for the encryption
276    nonce: Nonce,
277    /// The HMAC tag
278    hmac: RedactedBytes<32>,
279    /// The salt used for argon2
280    salt: RedactedBytes<32>,
281    /// The encrypted payload
282    #[serde(with = "serde_bytes")]
283    payload: Vec<u8>,
284}
285
286impl EncryptedRootKey {
287    /// Attempts to decrypt the key with the provided password, and provide it as a [`RootKey`]
288    ///
289    /// # Errors
290    ///
291    /// Will return:
292    ///   * `Error::Argon2Failure` if the argon2 key derivation fails
293    ///   * `Error::BadHmac` if the hmac verification failed, either due to incorrect password or corruption
294    ///   * `Error::KeyDeserialization` if the key fails to deserialize, which really shouldn't happen
295    pub fn decrypt(&self, password: &[u8]) -> Result<RootKey, BackendError> {
296        // Prepare the argon2 instance
297        let argon = Argon2::default();
298        // Prepare the output buffer
299        let mut argon_output = Zeroizing::new([0_u8; 64]);
300        argon
301            .hash_password_into(password, &self.salt, &mut argon_output[..])
302            .context(Argon2Failure)?;
303        // Use the first half of the argon output as the encryption key
304        let encryption_key: &chacha20::Key = GenericArray::from_slice(&argon_output[0..32]);
305        // Use the second half as the hmac key
306        let mut hmac_key = Zeroizing::new([0_u8; 32]);
307        hmac_key.copy_from_slice(&argon_output[32..]);
308        // Verify the HMAC
309        let hmac = blake3::keyed_hash(&hmac_key, &self.payload);
310        ensure!(hmac.eq(&*self.hmac), BadHMAC);
311        // Decrypt the data
312        let mut data = Zeroizing::new(self.payload.clone());
313        let mut chacha = XChaCha20::new(encryption_key, self.nonce.nonce());
314        chacha.apply_keystream(&mut data[..]);
315        // Deserialize the data
316        match serde_cbor::from_slice(&data) {
317            Ok(x) => Ok(x),
318            Err(_) => {
319                // Do not preserve the serde error, this may leak secrets into logs
320                Err(BackendError::KeyDeserialization)
321            }
322        }
323    }
324}
325
326/// A key that has been derived from a [`RootKey`]
327///
328/// This will have an encryption key and an hmac key derived from the [`RootKey`]'s entropy pool, using
329/// Blake3 in key-derivation mode, keyed with the [`RootKey`]'s hmac key.
330///
331/// This struct also contains the context string that was used to derive it, as a way to validate key
332/// provenance, or possibly reconstruct a key after a corruption occurs.
333#[derive(Hash, Clone, Serialize, Deserialize, Zeroize)]
334#[zeroize(drop)]
335pub struct DerivedKey {
336    /// The encryption key
337    encryption: RedactedBytes<32>,
338    /// The root HMAC key, used for validating headers
339    hmac: RedactedBytes<32>,
340    /// The context string used to produce this key
341    context_string: String,
342}
343
344impl Key for DerivedKey {
345    /// Provides the encryption key as a chacha [`Key`](chacha20::Key)
346    fn encryption_key(&self) -> &chacha20::Key {
347        GenericArray::from_slice(&self.encryption)
348    }
349    /// Provides the hmac key
350    fn hmac_key(&self) -> &[u8; 32] {
351        self.hmac.as_ref()
352    }
353}
354
355impl DerivedKey {
356    /// Encrypts the given [`DerivedKey`] into an [`EncryptedDerivedKey`] using the given [`RootKey`].
357    ///
358    /// This method packs the derived key into a [`ClearText`] box, and then encrypts it to a [`CipherText`]
359    /// box, without enabling compression.
360    ///
361    /// See the documentation for [`CipherText`] for a description of the encryption method.
362    ///
363    /// # Errors
364    ///
365    /// Will error if the encryption or serialization fail
366    pub fn encrypt(
367        &self,
368        root_key: &RootKey,
369    ) -> Result<EncryptedDerivedKey<'static>, BackendError> {
370        // First get a cleartext containing ourself
371        let cleartext = ClearText::new(self)?;
372        // Then encrypt it with the root key, without compression
373        let ciphertext = cleartext.encrypt(root_key, None)?;
374        Ok(EncryptedDerivedKey {
375            encrypted_key: ciphertext,
376        })
377    }
378}
379
380/// A [`DerivedKey`] that has been encrypted with a [`RootKey`]
381///
382/// See the documentation for [`CipherText`] for a description of the encryption method.
383#[derive(Hash, Clone, Serialize, Deserialize)]
384pub struct EncryptedDerivedKey<'a> {
385    /// The encrypted key value
386    encrypted_key: CipherText<'a>,
387}
388
389impl EncryptedDerivedKey<'_> {
390    /// Decrypts the given [`EncryptedDerivedKey`] into a [`DerivedKey`], using the provided [`RootKey`].
391    ///
392    /// # Errors
393    ///
394    /// Will error if the decryption or deserialization fails
395    pub fn decrypt(&self, root_key: &RootKey) -> Result<DerivedKey, BackendError> {
396        // Attempt to get the cleartext
397        let cleartext = self.encrypted_key.decrypt(root_key)?;
398        // Attempt to deserialize it
399        cleartext.deserialize()
400    }
401}
402
403/// Unit tests
404#[cfg(test)]
405mod tests {
406    use super::*;
407    /// Unit tests for [`RootKey`]
408    mod root_key {
409        use super::*;
410        /// Make sure the null key is all zeros
411        #[test]
412        fn null_is_zeros() {
413            let key = RootKey::null();
414            assert_eq!(key.encryption, [0_u8; 32].into());
415            assert_eq!(key.hmac, [0_u8; 32].into());
416            assert_eq!(key.entropy, [0_u8; 256].into());
417        }
418        /// Make sure randomly generated key has no zero segments
419        #[test]
420        fn random_is_not_zeros() {
421            let key = RootKey::random();
422            assert_ne!(key.encryption, [0_u8; 32].into());
423            assert_ne!(key.hmac, [0_u8; 32].into());
424            assert_ne!(key.entropy, [0_u8; 256].into());
425        }
426    }
427    /// Unit tests for [`Nonce`]
428    mod nonce {
429        use super::*;
430        /// Make sure the nonce is non-zero
431        #[test]
432        fn non_zero() {
433            let nonce = Nonce::random();
434            assert_ne!(nonce.0, [0_u8; 24].into());
435        }
436    }
437    /// Unit tests for [`EncryptedRootKey`]
438    mod encrypted_root_key {
439        use super::*;
440        /// Test round trip encryption/decryption of a [`RootKey`]
441        #[test]
442        fn round_trip() {
443            let key = RootKey::random();
444            let password = "password".as_bytes();
445            let encrypted = key.encrypt(password).expect("Failed to encrypt key");
446            let decrypted = encrypted.decrypt(password).expect("Failed to decrypt key");
447            assert_eq!(decrypted.encryption, key.encryption);
448            assert_eq!(decrypted.hmac, key.hmac);
449            assert_eq!(decrypted.entropy, key.entropy);
450        }
451        /// Make sure the wrong password can't be used to decrypt a key
452        #[test]
453        fn bad_password_failure() {
454            let key = RootKey::random();
455            let password = "password".as_bytes();
456            let wrong_password = "wrong password".as_bytes();
457            let encrypted = key.encrypt(password).expect("Failed to encrypt key");
458            let decrypted = encrypted.decrypt(wrong_password);
459            assert!(decrypted.is_err());
460        }
461        /// Make sure corruption is detected
462        #[test]
463        fn corruption_failure() {
464            let key = RootKey::random();
465            let password = "password".as_bytes();
466            let mut encrypted = key.encrypt(password).expect("Failed to encrypt key");
467            // Corrupt the first byte of the payload
468            encrypted.payload[0] = encrypted.payload[0].wrapping_add(1_u8);
469            let decrypted = encrypted.decrypt(password);
470            match decrypted {
471                Ok(_) => panic!("Somehow decrypted corrupted data"),
472                Err(e) => assert!(matches!(e, BackendError::BadHMAC)),
473            }
474        }
475    }
476    /// Unit tests for [`DerivedKey`]
477    mod derived_key {
478        use super::*;
479        /// Ensure derived keys aren't zeros, and that the encryption key and hmac key aren't the
480        /// same
481        #[test]
482        fn not_zero() {
483            let root_key = RootKey::random();
484            let derived_key = root_key.derive("namespace");
485            assert_ne!(derived_key.encryption, [0_u8; 32].into());
486            assert_ne!(derived_key.hmac, [0_u8; 32].into());
487            assert_ne!(derived_key.encryption, derived_key.hmac);
488        }
489        /// Ensure that repeated calls to derive key are different
490        #[test]
491        fn non_repeatable() {
492            let root_key = RootKey::random();
493            let derived_key_1 = root_key.derive("namespace");
494            let derived_key_2 = root_key.derive("namespace");
495
496            assert_ne!(derived_key_1.encryption, derived_key_2.encryption);
497            assert_ne!(derived_key_1.hmac, derived_key_2.hmac);
498            assert_ne!(derived_key_1.context_string, derived_key_2.context_string);
499        }
500        /// Ensure that using the same context string twice gives the same key
501        #[test]
502        fn repeatable() {
503            let root_key = RootKey::random();
504            let derived_key_1 = root_key.derive_with_context("Some context goes here".to_string());
505            let derived_key_2 = root_key.derive_with_context("Some context goes here".to_string());
506
507            assert_eq!(derived_key_1.encryption, derived_key_2.encryption);
508            assert_eq!(derived_key_1.hmac, derived_key_2.hmac);
509            assert_eq!(derived_key_1.context_string, derived_key_2.context_string);
510        }
511    }
512    /// Unit tests for [`EncryptedDerivedKey`]
513    mod enc_derived_key {
514        use super::*;
515        /// Test round trip encryption/decryption of a [`DerivedKey`]
516        #[test]
517        fn round_trip() {
518            let root_key = RootKey::random();
519            let derived_key_orig = root_key.derive("testing");
520            let enc_derived_key = derived_key_orig
521                .encrypt(&root_key)
522                .expect("Failed to encrypt key");
523            let derived_key_deser = enc_derived_key
524                .decrypt(&root_key)
525                .expect("Failed to decrypt key");
526            assert_eq!(derived_key_deser.encryption, derived_key_orig.encryption);
527            assert_eq!(derived_key_deser.hmac, derived_key_orig.hmac);
528            assert_eq!(
529                derived_key_deser.context_string,
530                derived_key_orig.context_string
531            );
532        }
533    }
534}