Skip to content

Commit 10630c1

Browse files
committed
Add storable_builder helper
1 parent 232415b commit 10630c1

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
1010
#![deny(rustdoc::broken_intra_doc_links)]
1111
#![deny(rustdoc::private_intra_doc_links)]
12+
#![deny(missing_docs)]
1213

1314
/// Implements a thin-client ([`client::VssClient`]) to access a hosted instance of Versioned Storage Service (VSS).
1415
pub mod client;
@@ -19,5 +20,8 @@ pub mod error;
1920
/// Contains request/response types generated from the API definition of VSS.
2021
pub mod types;
2122

23+
/// Contains utils for encryption, requests-retries etc.
24+
pub mod util;
25+
2226
// Encryption-Decryption related crate-only helpers.
2327
pub(crate) mod crypto;

src/util/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/// Contains [`StorableBuilder`] utility.
2+
///
3+
/// [`StorableBuilder`]: storable_builder::StorableBuilder
4+
pub mod storable_builder;

src/util/storable_builder.rs

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

0 commit comments

Comments
 (0)