-
Notifications
You must be signed in to change notification settings - Fork 8
Allow to set aad in StorableBuilder::{build,desconstruct}
#40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<T: EntropySource> { | ||
| data_encryption_key: [u8; 32], | ||
| entropy_source: T, | ||
| } | ||
|
|
||
| impl<T: EntropySource> StorableBuilder<T> { | ||
| /// Constructs a new instance. | ||
| pub fn new(data_encryption_key: [u8; 32], entropy_source: T) -> StorableBuilder<T> { | ||
| Self { data_encryption_key, entropy_source } | ||
| pub fn new(entropy_source: T) -> StorableBuilder<T> { | ||
| Self { entropy_source } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -34,18 +34,21 @@ const CHACHA20_CIPHER_NAME: &'static str = "ChaCha20Poly1305"; | |
| impl<T: EntropySource> StorableBuilder<T> { | ||
| /// 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<u8>, version: i64) -> Storable { | ||
| pub fn build( | ||
| &self, input: Vec<u8>, version: i64, data_encryption_key: &[u8; 32], aad: &[u8], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC the aad is required to have a specific (set of?) lengths or we panic, we shouldn't expose a generic any-length API publicly as a result, IMO.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, AFAICT that's only the assert!(key.len() == 16 || key.len() == 32);
assert!(nonce.len() == 12);For the self.mac.input(&self.aad_len.to_le_bytes());
self.mac.input(&(self.data_len as u64).to_le_bytes());Or maybe I'm missing something?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That said, it might still be debatable if we simply want to take the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops sorry no you're right, I was thinking of the nonce. I think this API is fine. |
||
| ) -> 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<T: EntropySource> StorableBuilder<T> { | |
| /// corresponding version as stored at the time of [`PutObjectRequest`]. | ||
| /// | ||
| /// [`PutObjectRequest`]: crate::types::PutObjectRequest | ||
| pub fn deconstruct(&self, mut storable: Storable) -> io::Result<(Vec<u8>, 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<u8>, 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()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i wonder if we could be opinionated here for future writes and have the AAD commit to:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re: 2.:
Hmm, that might be an option, but (especially for backwards compat) the current approach where the 'user' determines the aad (given that they also build the actual storable) might be more flexible?
I guess we also could consider committing to the store id, but it would mean it can never change, e.g., that services can't ever move data between stores for the user.
Also, as noted below, I doubt the described replay attacks can be fully mitigated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On whether we should commit to
store_id:Since a VSS store is:
We already have the notion that each
store_idshould have a unique DEK.So as long as the user uses a different DEK for each
store_id, the server won't be able to move data to a differentstore_idwithout being transparent with the user.If I understand things correctly here, I would rather not commit to the
store_idin theaad, and suggest users use a different DEK for eachstore_id- which seems to fit well with the currentVssStore.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for committing to the key-value key, yea I think letting the user set the
aadso that we have an easier time with backwards compat makes the most sense.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, why? Users currently can change the
store_idwhen setting up LDK with VSS, without changing their entropy, and the store_id doesn't influence how the DEK is derived?Right, we could change the DEK derivation scheme to include the
store_id, but a) we'll need to figure out how to maintain backwards compatibility for the 'legacy' derivation and b) this would mean that we can't easily migrate between stores/store ids. Might be debatable if that's what we want, but we'd def. block off that road.Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes you are right.
Should migrations between
store_idof this kind be handled by the user, not by the server ? Ie list all keys, get all the values from the old store, and put them in the new store.In that case I would rather just commit to the
store_idin theaadinstead of including it in the DEK.1- How important is it to maintain backwards compat at this point ? This feature is alpha after all.
2- If we want to maintain backwards compat, could we create a migration routine on startup that triggers for old VSS stores, and transitions them to the new store + scheme ? We'd have to support both schemes at once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that sounds reasonable. Just saying we're blocking off the other way and need to take that step consciously. Committing to the store id also manifests that it needs to remain the users' choice I believe?
'Alpha' or not, we have users in production with it. So at the very least we need to offer some migration path for them.
I think this discussion is better had over on the LDK Node PR, but I was thinking to introduce a
vss_version(or similar) key that is read first thing and default to 0 if it's absent.