vss_client/util/
storable_builder.rs

1use crate::crypto::chacha20poly1305::ChaCha20Poly1305;
2use crate::types::{EncryptionMetadata, PlaintextBlob, Storable};
3use ::prost::Message;
4use std::borrow::Borrow;
5use std::io;
6use std::io::{Error, ErrorKind};
7
8/// [`StorableBuilder`] is a utility to build and deconstruct [`Storable`] objects.
9/// It provides client-side Encrypt-then-MAC using ChaCha20-Poly1305.
10pub struct StorableBuilder<T: EntropySource> {
11	data_encryption_key: [u8; 32],
12	entropy_source: T,
13}
14
15impl<T: EntropySource> StorableBuilder<T> {
16	/// Constructs a new instance.
17	pub fn new(data_encryption_key: [u8; 32], entropy_source: T) -> StorableBuilder<T> {
18		Self { data_encryption_key, entropy_source }
19	}
20}
21
22/// A trait representing a source for generating entropy/randomness.
23pub trait EntropySource {
24	/// Fills a buffer with random bytes.
25	///
26	/// This method must generate the specified number of random bytes and write them into the given
27	/// buffer. It is expected that this method will be cryptographically secure and suitable for use
28	/// cases requiring strong randomness, such as generating nonces or secret keys.
29	fn fill_bytes(&self, buffer: &mut [u8]);
30}
31
32const CHACHA20_CIPHER_NAME: &'static str = "ChaCha20Poly1305";
33
34impl<T: EntropySource> StorableBuilder<T> {
35	/// Creates a [`Storable`] that can be serialized and stored as `value` in [`PutObjectRequest`].
36	///
37	/// Uses ChaCha20 for encrypting `input` and Poly1305 for generating a mac/tag.
38	///
39	/// Refer to docs on [`Storable`] for more information.
40	///
41	/// [`PutObjectRequest`]: crate::types::PutObjectRequest
42	pub fn build(&self, input: Vec<u8>, version: i64) -> Storable {
43		let mut nonce = vec![0u8; 12];
44		self.entropy_source.fill_bytes(&mut nonce[4..]);
45
46		let mut data_blob = PlaintextBlob { value: input, version }.encode_to_vec();
47
48		let mut cipher = ChaCha20Poly1305::new(&self.data_encryption_key, &nonce, &[]);
49		let mut tag = vec![0u8; 16];
50		cipher.encrypt_inplace(&mut data_blob, &mut tag);
51		Storable {
52			data: data_blob,
53			encryption_metadata: Some(EncryptionMetadata {
54				nonce,
55				tag,
56				cipher_format: CHACHA20_CIPHER_NAME.to_string(),
57			}),
58		}
59	}
60
61	/// Deconstructs the provided [`Storable`] and returns constituent decrypted data and its
62	/// corresponding version as stored at the time of [`PutObjectRequest`].
63	///
64	/// [`PutObjectRequest`]: crate::types::PutObjectRequest
65	pub fn deconstruct(&self, mut storable: Storable) -> io::Result<(Vec<u8>, i64)> {
66		let encryption_metadata = storable.encryption_metadata.unwrap();
67		let mut cipher =
68			ChaCha20Poly1305::new(&self.data_encryption_key, &encryption_metadata.nonce, &[]);
69
70		cipher
71			.decrypt_inplace(&mut storable.data, encryption_metadata.tag.borrow())
72			.map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid Tag"))?;
73
74		let data_blob = PlaintextBlob::decode(&storable.data[..])
75			.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
76		Ok((data_blob.value, data_blob.version))
77	}
78}
79
80#[cfg(test)]
81mod tests {
82	use super::*;
83
84	pub struct TestEntropyProvider;
85	impl EntropySource for TestEntropyProvider {
86		/// A terrible implementation which fills a buffer with bytes from a simple counter for testing
87		/// purposes.
88		fn fill_bytes(&self, buffer: &mut [u8]) {
89			for (i, byte) in buffer.iter_mut().enumerate() {
90				*byte = (i % 256) as u8;
91			}
92		}
93	}
94
95	#[test]
96	fn encrypt_decrypt() {
97		let test_entropy_provider = TestEntropyProvider;
98		let mut data_key = [0u8; 32];
99		test_entropy_provider.fill_bytes(&mut data_key);
100		let storable_builder = StorableBuilder {
101			data_encryption_key: data_key,
102			entropy_source: test_entropy_provider,
103		};
104		let expected_data = b"secret".to_vec();
105		let expected_version = 8;
106		let storable = storable_builder.build(expected_data.clone(), expected_version);
107
108		let (actual_data, actual_version) = storable_builder.deconstruct(storable).unwrap();
109		assert_eq!(actual_data, expected_data);
110		assert_eq!(actual_version, expected_version);
111	}
112}