diff --git a/src/util/key_obfuscator.rs b/src/util/key_obfuscator.rs index 056e050..b7431d7 100644 --- a/src/util/key_obfuscator.rs +++ b/src/util/key_obfuscator.rs @@ -11,8 +11,8 @@ use crate::crypto::chacha20poly1305::ChaCha20Poly1305; /// /// It provides client-side deterministic encryption of given keys using ChaCha20-Poly1305. pub struct KeyObfuscator { - obfuscation_key: [u8; 32], - hashing_key: [u8; 32], + obfuscation_key: [u8; KEY_LENGTH], + hashing_key: [u8; KEY_LENGTH], } impl KeyObfuscator { @@ -24,6 +24,7 @@ impl KeyObfuscator { } } +const KEY_LENGTH: usize = 32; const TAG_LENGTH: usize = 16; const NONCE_LENGTH: usize = 12; @@ -137,20 +138,28 @@ impl KeyObfuscator { /// Derives the obfuscation and hashing keys from the master key. fn derive_obfuscation_and_hashing_keys( - obfuscation_master_key: &[u8; 32], - ) -> ([u8; 32], [u8; 32]) { + obfuscation_master_key: &[u8; KEY_LENGTH], + ) -> ([u8; KEY_LENGTH], [u8; KEY_LENGTH]) { let prk = Self::hkdf(obfuscation_master_key, "pseudo_random_key".as_bytes()); let k1 = Self::hkdf(&prk, "obfuscation_key".as_bytes()); let k2 = Self::hkdf(&prk, &[&k1[..], "hashing_key".as_bytes()].concat()); (k1, k2) } - fn hkdf(initial_key_material: &[u8], salt: &[u8]) -> [u8; 32] { + fn hkdf(initial_key_material: &[u8], salt: &[u8]) -> [u8; KEY_LENGTH] { let mut engine = HmacEngine::::new(salt); engine.input(initial_key_material); Hmac::from_engine(engine).to_byte_array() } } +impl Drop for KeyObfuscator { + fn drop(&mut self) { + // Zeroize the owned keys + self.obfuscation_key.copy_from_slice(&[0u8; KEY_LENGTH]); + self.hashing_key.copy_from_slice(&[0u8; KEY_LENGTH]); + } +} + #[cfg(test)] mod tests { use crate::util::key_obfuscator::KeyObfuscator; diff --git a/src/util/storable_builder.rs b/src/util/storable_builder.rs index a5857d6..41d254b 100644 --- a/src/util/storable_builder.rs +++ b/src/util/storable_builder.rs @@ -6,16 +6,16 @@ use std::io; use std::io::{Error, ErrorKind}; /// [`StorableBuilder`] is a utility to build and deconstruct [`Storable`] objects. +/// /// It provides client-side Encrypt-then-MAC using ChaCha20-Poly1305. pub struct StorableBuilder { - data_encryption_key: [u8; 32], entropy_source: T, } impl StorableBuilder { /// Constructs a new instance. - pub fn new(data_encryption_key: [u8; 32], entropy_source: T) -> StorableBuilder { - Self { data_encryption_key, entropy_source } + pub fn new(entropy_source: T) -> StorableBuilder { + Self { entropy_source } } } @@ -34,18 +34,21 @@ const CHACHA20_CIPHER_NAME: &'static str = "ChaCha20Poly1305"; impl StorableBuilder { /// Creates a [`Storable`] that can be serialized and stored as `value` in [`PutObjectRequest`]. /// - /// Uses ChaCha20 for encrypting `input` and Poly1305 for generating a mac/tag. + /// Uses ChaCha20 for encrypting `input` and Poly1305 for generating a mac/tag with associated + /// data `aad` (usually the storage key). /// /// Refer to docs on [`Storable`] for more information. /// /// [`PutObjectRequest`]: crate::types::PutObjectRequest - pub fn build(&self, input: Vec, version: i64) -> Storable { + pub fn build( + &self, input: Vec, version: i64, data_encryption_key: &[u8; 32], aad: &[u8], + ) -> Storable { let mut nonce = vec![0u8; 12]; self.entropy_source.fill_bytes(&mut nonce[4..]); let mut data_blob = PlaintextBlob { value: input, version }.encode_to_vec(); - let mut cipher = ChaCha20Poly1305::new(&self.data_encryption_key, &nonce, &[]); + let mut cipher = ChaCha20Poly1305::new(data_encryption_key, &nonce, aad); let mut tag = vec![0u8; 16]; cipher.encrypt_inplace(&mut data_blob, &mut tag); Storable { @@ -62,10 +65,14 @@ impl StorableBuilder { /// corresponding version as stored at the time of [`PutObjectRequest`]. /// /// [`PutObjectRequest`]: crate::types::PutObjectRequest - pub fn deconstruct(&self, mut storable: Storable) -> io::Result<(Vec, i64)> { - let encryption_metadata = storable.encryption_metadata.unwrap(); + pub fn deconstruct( + &self, mut storable: Storable, data_encryption_key: &[u8; 32], aad: &[u8], + ) -> io::Result<(Vec, i64)> { + let encryption_metadata = storable + .encryption_metadata + .ok_or_else(|| Error::new(ErrorKind::InvalidData, "Invalid Metadata"))?; let mut cipher = - ChaCha20Poly1305::new(&self.data_encryption_key, &encryption_metadata.nonce, &[]); + ChaCha20Poly1305::new(data_encryption_key, &encryption_metadata.nonce, aad); cipher .decrypt_inplace(&mut storable.data, encryption_metadata.tag.borrow()) @@ -97,16 +104,42 @@ mod tests { let test_entropy_provider = TestEntropyProvider; let mut data_key = [0u8; 32]; test_entropy_provider.fill_bytes(&mut data_key); - let storable_builder = StorableBuilder { - data_encryption_key: data_key, - entropy_source: test_entropy_provider, - }; + let storable_builder = StorableBuilder::new(test_entropy_provider); let expected_data = b"secret".to_vec(); let expected_version = 8; - let storable = storable_builder.build(expected_data.clone(), expected_version); + let aad = b"A"; + let storable = + storable_builder.build(expected_data.clone(), expected_version, &data_key, aad); - let (actual_data, actual_version) = storable_builder.deconstruct(storable).unwrap(); + let (actual_data, actual_version) = + storable_builder.deconstruct(storable, &data_key, aad).unwrap(); assert_eq!(actual_data, expected_data); assert_eq!(actual_version, expected_version); } + + #[test] + fn decrypt_key_mismatch_fails() { + let test_entropy_provider = TestEntropyProvider; + let mut data_key = [0u8; 32]; + test_entropy_provider.fill_bytes(&mut data_key); + let storable_builder = StorableBuilder::new(test_entropy_provider); + + let expected_data_a = b"secret_a".to_vec(); + let expected_version_a = 8; + let aad_a = b"A"; + let storable_a = + storable_builder.build(expected_data_a.clone(), expected_version_a, &data_key, aad_a); + + let expected_data_b = b"secret_b".to_vec(); + let expected_version_b = 8; + let aad_b = b"B"; + let storable_b = + storable_builder.build(expected_data_b.clone(), expected_version_b, &data_key, aad_b); + + let (actual_data, actual_version) = + storable_builder.deconstruct(storable_a, &data_key, aad_a).unwrap(); + assert_eq!(actual_data, expected_data_a); + assert_eq!(actual_version, expected_version_a); + assert!(storable_builder.deconstruct(storable_b, &data_key, aad_a).is_err()); + } }