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}