snapper-box 0.0.4

Cryptographic storage for snapper
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
//! Keys and other sensitive cryptographic material <span style="color:red">**HAZMAT**</span>.
//!
//! # <span style="color:red">**DANGER**</span>
//!
//! This module deals in low level cryptographic details. It is advisable to not deal with this module
//! directly, and instead use a higher level API.
use argon2::Argon2;
use blake3::Hasher;
use chacha20::{
    cipher::{generic_array::GenericArray, NewCipher, StreamCipher},
    XChaCha20, XNonce,
};
use rand::{Rng, RngCore};
use redacted::RedactedBytes;
use serde::{Deserialize, Serialize};
use snafu::{ensure, ResultExt};
use zeroize::{Zeroize, Zeroizing};

use crate::{
    crypto::types::{CipherText, ClearText},
    error::{Argon2Failure, BackendError, BadHMAC},
};

/// Allows access to the subkeys of a key-like structure
pub trait Key {
    /// Provides the encryption key as a chacha [`Key`](chacha20::Key)
    fn encryption_key(&self) -> &chacha20::Key;
    /// Provides the hmac key
    fn hmac_key(&self) -> &[u8; 32];
}

/// Nonce/Salt value used in encryption
///
/// This is stored as a 24-byte array, for serialization, but is viewable as an `XChaCha` nonce
///
/// Nonces are intended to be always randomly generated, and there is intentionally no API for
/// reconstructing a nonce.
#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Nonce(pub(crate) RedactedBytes<24>);

impl Nonce {
    /// Generates a new, random nonce
    pub fn random() -> Self {
        let mut data = [0_u8; 24];
        rand::thread_rng().fill(&mut data[..]);
        Self(data.into())
    }
    /// Gets a view of the nonce as an `XChaCha20` [`XNonce`]
    pub fn nonce(&self) -> &XNonce {
        GenericArray::from_slice(&self.0)
    }
}

/// Originating Key
///
/// This key consists of an independently, randomly generated encryption key, HMAC key, and an entropy
/// pool for generating derived keys.
///
/// While the components are generated independently from any user provided input, at rest encryption of
/// this key material is achieved by encrypting the `RootKey` using an argon2 derivation of a user
/// supplied password. This decoupling of key generation from user input allows several useful things,
/// such as allowing the password for a store to be changed without having to reencrypt all the data, as
/// well as making entire categories of attacks effectively impossible.
///
/// The included encryption and HMAC keys should only be used for encrypting top-level metadata, to
/// limit the amount of data encrypted with this key. Derived keys can be produced with a namespace
/// string via the `derive` method.
#[derive(Hash, Clone, Serialize, Deserialize, Zeroize)]
#[zeroize(drop)]
pub struct RootKey {
    /// The root cipher key, used for encrypting headers
    encryption: RedactedBytes<32>,
    /// The root HMAC key, used for validating headers
    hmac: RedactedBytes<32>,
    /// Random data used to derive encryption keys
    entropy: RedactedBytes<256>,
}

impl RootKey {
    /// Generates a new `RootKey`
    ///
    /// This method uses a cryptograpically secure random number generator to fill the encryption key, HMAC
    /// key, and entropy pools with random data.
    ///
    /// `RootKey`s should always be randomly generated, and there is intentionally no API for recreating a
    /// specific `RootKey`
    pub fn random() -> Self {
        let mut rand = rand::thread_rng();
        // Construct ourself first, and mutate in place, to limit the possibility of key material
        // getting leaked in a place that `zeroize` can't reach
        let mut ret = Self::null();
        // Fill keys
        rand.fill(&mut ret.encryption[..]);
        rand.fill(&mut ret.hmac[..]);
        rand.fill(&mut ret.entropy[..]);

        ret
    }
    /// Creates an all zero `RootKey`, also known as 'The null key'
    ///
    /// # <span style="color:red">**DANGER**</span>
    ///
    /// This method exists because this library does not support operating on plaintext at rest data, so the
    /// all-zero key is used as a known-ahead-of-time key for passwordless use.
    ///
    /// This is, hopefully obviously, incredibly insecure, and should only ever be called when storing data
    /// in plaintext would be appropriate.
    pub fn null() -> Self {
        RootKey {
            encryption: [0_u8; 32].into(),
            hmac: [0_u8; 32].into(),
            entropy: [0_u8; 256].into(),
        }
    }
    /// Encrypts this key, producing a [`EncryptedRootKey`], with the provided password.
    ///
    /// This uses a byte slice rather than a string to provide more flexibility.
    ///
    /// Argon2 and a random salt are used to generate 64 bytes of key material from the user supplied
    /// password, the first 32 bytes of which are used as the encryption key, and the last 32 bytes are used
    /// as an HMAC key. This allows us to use the password to provide both encryption and authentication.
    ///
    /// # Errors
    ///
    /// Will return:
    ///   * `Error::Argon2Failure` if the argon2 key derivation fails
    pub fn encrypt(&self, password: &[u8]) -> Result<EncryptedRootKey, BackendError> {
        // Generate a random salt
        let mut salt = [0_u8; 32];
        rand::thread_rng().fill(&mut salt[..]);
        // Prepare the argon2 instance
        let argon = Argon2::default();
        // Prepare the output buffer
        let mut argon_output = Zeroizing::new([0_u8; 64]);
        argon
            .hash_password_into(password, &salt, &mut argon_output[..])
            .context(Argon2Failure)?;
        // Use the first half of the argon output as the encryption key
        let encryption_key: &chacha20::Key = GenericArray::from_slice(&argon_output[0..32]);
        // Use the second half as the hmac key
        let mut hmac_key = Zeroizing::new([0_u8; 32]);
        hmac_key.copy_from_slice(&argon_output[32..]);

        // Get a random nonce
        let nonce = Nonce::random();
        // Serialize ourself
        let mut serial = serde_cbor::to_vec(self).expect("Infallible");
        // Encrypt ourself
        let mut chacha = XChaCha20::new(encryption_key, nonce.nonce());
        chacha.apply_keystream(&mut serial[..]);
        // Calculate the HMAC
        let hmac: [u8; 32] = blake3::keyed_hash(&hmac_key, &serial).into();
        Ok(EncryptedRootKey {
            nonce,
            hmac: hmac.into(),
            salt: salt.into(),
            payload: serial,
        })
    }
    /// Creates a [`DerivedKey`] from this [`RootKey`] using the provided namespace as part of the
    /// context string
    ///
    /// This method will generate, using a CSPRNG a random, 32 character, hexadecimal nonce (~128 bits of
    /// entropy) to include in the context string, which will then be combined with the provided namespace
    /// and fed into Blake3's key derivation mode. Blake3 is used to derive 64 bytes of key material, the
    /// first 32 bytes of which are used as an encryption key, and the last 32 bytes of which are used as an
    /// HMAC key.
    ///
    /// While [`DerivedKey`] can be used quite safely for a large number of encryptions, it is wise to limit
    /// the usage of an individual [`DerivedKey`] as much as possible, to limit the fallout of any
    /// accidental/unintentional nonce reuses.
    pub fn derive(&self, namespace: &str) -> DerivedKey {
        // Do this manually for speed.
        let mut nonce_array = [0_u8; 32];
        let mut nonce_bytes = [0_u8; 16];
        rand::thread_rng().fill_bytes(&mut nonce_bytes[..]);
        for (index, byte) in nonce_bytes.into_iter().enumerate() {
            // Do the upper nibble
            let upper_nibble = match byte & 0xF0_u8 {
                0x00 => 0x30_u8,
                0x10 => 0x31_u8,
                0x20 => 0x32_u8,
                0x30 => 0x33_u8,
                0x40 => 0x34_u8,
                0x50 => 0x35_u8,
                0x60 => 0x36_u8,
                0x70 => 0x37_u8,
                0x80 => 0x38_u8,
                0x90 => 0x39_u8,
                0xA0 => 0x41_u8,
                0xB0 => 0x42_u8,
                0xC0 => 0x43_u8,
                0xD0 => 0x44_u8,
                0xE0 => 0x45_u8,
                0xF0 => 0x46_u8,
                _ => unreachable!(),
            };
            // And the lower nibble
            let lower_nibble = match byte & 0x0F_u8 {
                0x00 => 0x30_u8,
                0x01 => 0x31_u8,
                0x02 => 0x32_u8,
                0x03 => 0x33_u8,
                0x04 => 0x34_u8,
                0x05 => 0x35_u8,
                0x06 => 0x36_u8,
                0x07 => 0x37_u8,
                0x08 => 0x38_u8,
                0x09 => 0x39_u8,
                0x0A => 0x41_u8,
                0x0B => 0x42_u8,
                0x0C => 0x43_u8,
                0x0D => 0x44_u8,
                0x0E => 0x45_u8,
                0x0F => 0x46_u8,
                _ => unreachable!(),
            };
            // Set the bytes
            nonce_array[index * 2] = upper_nibble;
            nonce_array[index * 2 + 1] = lower_nibble;
        }
        // We are only generating ascii chars, so we can mark the failure as unreachable
        let nonce = if let Ok(nonce) = std::str::from_utf8(&nonce_array[..]) {
            nonce
        } else {
            unreachable!()
        };

        // Setup the context string
        let context_string = format!("snapper-box nonce: {} namespace: {}", &*nonce, namespace);
        self.derive_with_context(context_string)
    }
    /// Creates a [`DerivedKey`] from this [`RootKey`] with a specified context string
    ///
    /// # <span style="color:red">**DANGER**</span>
    ///
    /// The `derive` method intentionally includes a random component to facilitate key rotation. Using this
    /// method for any other purpose then to rederive a lost key is dangerous, as it can lead to unintended
    /// key reuse.
    pub fn derive_with_context(&self, context_string: String) -> DerivedKey {
        // Setup the hasher
        let mut hasher = Hasher::new_derive_key(&context_string);
        // Load in the entropy
        hasher.update(&self.entropy[..]);
        // Make the final derived key
        let mut ret = DerivedKey {
            encryption: [0_u8; 32].into(),
            hmac: [0_u8; 32].into(),
            context_string,
        };
        // Load in the keys, and return
        let mut output = hasher.finalize_xof();
        output.fill(&mut ret.encryption[..]);
        output.fill(&mut ret.hmac[..]);
        ret
    }
}

impl Key for RootKey {
    /// Provides the encryption key as a chacha [`Key`](chacha20::Key)
    fn encryption_key(&self) -> &chacha20::Key {
        GenericArray::from_slice(&self.encryption)
    }
    /// Provides the hmac key as a reference to the underlying array
    fn hmac_key(&self) -> &[u8; 32] {
        self.hmac.as_ref()
    }
}

/// A [`RootKey`] that has been encrypted with an argon2 derivation of a users password
///
/// See the [`RootKey`] docs for a description of the method used for encryption
#[derive(Debug, Hash, Clone, Serialize, Deserialize)]
pub struct EncryptedRootKey {
    /// The nonce used for the encryption
    nonce: Nonce,
    /// The HMAC tag
    hmac: RedactedBytes<32>,
    /// The salt used for argon2
    salt: RedactedBytes<32>,
    /// The encrypted payload
    #[serde(with = "serde_bytes")]
    payload: Vec<u8>,
}

impl EncryptedRootKey {
    /// Attempts to decrypt the key with the provided password, and provide it as a [`RootKey`]
    ///
    /// # Errors
    ///
    /// Will return:
    ///   * `Error::Argon2Failure` if the argon2 key derivation fails
    ///   * `Error::BadHmac` if the hmac verification failed, either due to incorrect password or corruption
    ///   * `Error::KeyDeserialization` if the key fails to deserialize, which really shouldn't happen
    pub fn decrypt(&self, password: &[u8]) -> Result<RootKey, BackendError> {
        // Prepare the argon2 instance
        let argon = Argon2::default();
        // Prepare the output buffer
        let mut argon_output = Zeroizing::new([0_u8; 64]);
        argon
            .hash_password_into(password, &self.salt, &mut argon_output[..])
            .context(Argon2Failure)?;
        // Use the first half of the argon output as the encryption key
        let encryption_key: &chacha20::Key = GenericArray::from_slice(&argon_output[0..32]);
        // Use the second half as the hmac key
        let mut hmac_key = Zeroizing::new([0_u8; 32]);
        hmac_key.copy_from_slice(&argon_output[32..]);
        // Verify the HMAC
        let hmac = blake3::keyed_hash(&hmac_key, &self.payload);
        ensure!(hmac.eq(&*self.hmac), BadHMAC);
        // Decrypt the data
        let mut data = Zeroizing::new(self.payload.clone());
        let mut chacha = XChaCha20::new(encryption_key, self.nonce.nonce());
        chacha.apply_keystream(&mut data[..]);
        // Deserialize the data
        match serde_cbor::from_slice(&data) {
            Ok(x) => Ok(x),
            Err(_) => {
                // Do not preserve the serde error, this may leak secrets into logs
                Err(BackendError::KeyDeserialization)
            }
        }
    }
}

/// A key that has been derived from a [`RootKey`]
///
/// This will have an encryption key and an hmac key derived from the [`RootKey`]'s entropy pool, using
/// Blake3 in key-derivation mode, keyed with the [`RootKey`]'s hmac key.
///
/// This struct also contains the context string that was used to derive it, as a way to validate key
/// provenance, or possibly reconstruct a key after a corruption occurs.
#[derive(Hash, Clone, Serialize, Deserialize, Zeroize)]
#[zeroize(drop)]
pub struct DerivedKey {
    /// The encryption key
    encryption: RedactedBytes<32>,
    /// The root HMAC key, used for validating headers
    hmac: RedactedBytes<32>,
    /// The context string used to produce this key
    context_string: String,
}

impl Key for DerivedKey {
    /// Provides the encryption key as a chacha [`Key`](chacha20::Key)
    fn encryption_key(&self) -> &chacha20::Key {
        GenericArray::from_slice(&self.encryption)
    }
    /// Provides the hmac key
    fn hmac_key(&self) -> &[u8; 32] {
        self.hmac.as_ref()
    }
}

impl DerivedKey {
    /// Encrypts the given [`DerivedKey`] into an [`EncryptedDerivedKey`] using the given [`RootKey`].
    ///
    /// This method packs the derived key into a [`ClearText`] box, and then encrypts it to a [`CipherText`]
    /// box, without enabling compression.
    ///
    /// See the documentation for [`CipherText`] for a description of the encryption method.
    ///
    /// # Errors
    ///
    /// Will error if the encryption or serialization fail
    pub fn encrypt(
        &self,
        root_key: &RootKey,
    ) -> Result<EncryptedDerivedKey<'static>, BackendError> {
        // First get a cleartext containing ourself
        let cleartext = ClearText::new(self)?;
        // Then encrypt it with the root key, without compression
        let ciphertext = cleartext.encrypt(root_key, None)?;
        Ok(EncryptedDerivedKey {
            encrypted_key: ciphertext,
        })
    }
}

/// A [`DerivedKey`] that has been encrypted with a [`RootKey`]
///
/// See the documentation for [`CipherText`] for a description of the encryption method.
#[derive(Hash, Clone, Serialize, Deserialize)]
pub struct EncryptedDerivedKey<'a> {
    /// The encrypted key value
    encrypted_key: CipherText<'a>,
}

impl EncryptedDerivedKey<'_> {
    /// Decrypts the given [`EncryptedDerivedKey`] into a [`DerivedKey`], using the provided [`RootKey`].
    ///
    /// # Errors
    ///
    /// Will error if the decryption or deserialization fails
    pub fn decrypt(&self, root_key: &RootKey) -> Result<DerivedKey, BackendError> {
        // Attempt to get the cleartext
        let cleartext = self.encrypted_key.decrypt(root_key)?;
        // Attempt to deserialize it
        cleartext.deserialize()
    }
}

/// Unit tests
#[cfg(test)]
mod tests {
    use super::*;
    /// Unit tests for [`RootKey`]
    mod root_key {
        use super::*;
        /// Make sure the null key is all zeros
        #[test]
        fn null_is_zeros() {
            let key = RootKey::null();
            assert_eq!(key.encryption, [0_u8; 32].into());
            assert_eq!(key.hmac, [0_u8; 32].into());
            assert_eq!(key.entropy, [0_u8; 256].into());
        }
        /// Make sure randomly generated key has no zero segments
        #[test]
        fn random_is_not_zeros() {
            let key = RootKey::random();
            assert_ne!(key.encryption, [0_u8; 32].into());
            assert_ne!(key.hmac, [0_u8; 32].into());
            assert_ne!(key.entropy, [0_u8; 256].into());
        }
    }
    /// Unit tests for [`Nonce`]
    mod nonce {
        use super::*;
        /// Make sure the nonce is non-zero
        #[test]
        fn non_zero() {
            let nonce = Nonce::random();
            assert_ne!(nonce.0, [0_u8; 24].into());
        }
    }
    /// Unit tests for [`EncryptedRootKey`]
    mod encrypted_root_key {
        use super::*;
        /// Test round trip encryption/decryption of a [`RootKey`]
        #[test]
        fn round_trip() {
            let key = RootKey::random();
            let password = "password".as_bytes();
            let encrypted = key.encrypt(password).expect("Failed to encrypt key");
            let decrypted = encrypted.decrypt(password).expect("Failed to decrypt key");
            assert_eq!(decrypted.encryption, key.encryption);
            assert_eq!(decrypted.hmac, key.hmac);
            assert_eq!(decrypted.entropy, key.entropy);
        }
        /// Make sure the wrong password can't be used to decrypt a key
        #[test]
        fn bad_password_failure() {
            let key = RootKey::random();
            let password = "password".as_bytes();
            let wrong_password = "wrong password".as_bytes();
            let encrypted = key.encrypt(password).expect("Failed to encrypt key");
            let decrypted = encrypted.decrypt(wrong_password);
            assert!(decrypted.is_err());
        }
        /// Make sure corruption is detected
        #[test]
        fn corruption_failure() {
            let key = RootKey::random();
            let password = "password".as_bytes();
            let mut encrypted = key.encrypt(password).expect("Failed to encrypt key");
            // Corrupt the first byte of the payload
            encrypted.payload[0] = encrypted.payload[0].wrapping_add(1_u8);
            let decrypted = encrypted.decrypt(password);
            match decrypted {
                Ok(_) => panic!("Somehow decrypted corrupted data"),
                Err(e) => assert!(matches!(e, BackendError::BadHMAC)),
            }
        }
    }
    /// Unit tests for [`DerivedKey`]
    mod derived_key {
        use super::*;
        /// Ensure derived keys aren't zeros, and that the encryption key and hmac key aren't the
        /// same
        #[test]
        fn not_zero() {
            let root_key = RootKey::random();
            let derived_key = root_key.derive("namespace");
            assert_ne!(derived_key.encryption, [0_u8; 32].into());
            assert_ne!(derived_key.hmac, [0_u8; 32].into());
            assert_ne!(derived_key.encryption, derived_key.hmac);
        }
        /// Ensure that repeated calls to derive key are different
        #[test]
        fn non_repeatable() {
            let root_key = RootKey::random();
            let derived_key_1 = root_key.derive("namespace");
            let derived_key_2 = root_key.derive("namespace");

            assert_ne!(derived_key_1.encryption, derived_key_2.encryption);
            assert_ne!(derived_key_1.hmac, derived_key_2.hmac);
            assert_ne!(derived_key_1.context_string, derived_key_2.context_string);
        }
        /// Ensure that using the same context string twice gives the same key
        #[test]
        fn repeatable() {
            let root_key = RootKey::random();
            let derived_key_1 = root_key.derive_with_context("Some context goes here".to_string());
            let derived_key_2 = root_key.derive_with_context("Some context goes here".to_string());

            assert_eq!(derived_key_1.encryption, derived_key_2.encryption);
            assert_eq!(derived_key_1.hmac, derived_key_2.hmac);
            assert_eq!(derived_key_1.context_string, derived_key_2.context_string);
        }
    }
    /// Unit tests for [`EncryptedDerivedKey`]
    mod enc_derived_key {
        use super::*;
        /// Test round trip encryption/decryption of a [`DerivedKey`]
        #[test]
        fn round_trip() {
            let root_key = RootKey::random();
            let derived_key_orig = root_key.derive("testing");
            let enc_derived_key = derived_key_orig
                .encrypt(&root_key)
                .expect("Failed to encrypt key");
            let derived_key_deser = enc_derived_key
                .decrypt(&root_key)
                .expect("Failed to decrypt key");
            assert_eq!(derived_key_deser.encryption, derived_key_orig.encryption);
            assert_eq!(derived_key_deser.hmac, derived_key_orig.hmac);
            assert_eq!(
                derived_key_deser.context_string,
                derived_key_orig.context_string
            );
        }
    }
}