Skip to content

Commit 53d6cd0

Browse files
committed
Updates to Unsealed, additional tests
1 parent 269cd7d commit 53d6cd0

File tree

6 files changed

+161
-76
lines changed

6 files changed

+161
-76
lines changed

cipherstash-dynamodb-derive/src/decryptable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub(crate) fn derive_decryptable(input: DeriveInput) -> Result<TokenStream, syn:
3838
let attr_ident = format_ident!("{attr}");
3939

4040
quote! {
41-
#attr_ident: ::cipherstash_dynamodb::traits::TryFromTableAttr::try_from_table_attr(unsealed.get_plaintext(#attr))?
41+
#attr_ident: ::cipherstash_dynamodb::traits::TryFromTableAttr::try_from_table_attr(unsealed.take_unprotected(#attr))?
4242
}
4343
}))
4444
.chain(skipped_attributes.iter().map(|attr| {

src/crypto/attrs/flattened_protected_attributes.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use itertools::Itertools;
1111

1212
// TODO: This thing is confusingly named - it holds unencrypted attributes that are intended for encryption
1313
/// Describes a set of flattened protected attributes intended for encryption.
14-
#[derive(PartialEq, Debug)]
14+
#[derive(PartialEq)]
1515
pub(crate) struct FlattenedProtectedAttributes(pub(super) Vec<FlattenedProtectedAttribute>);
1616

1717
impl FlattenedProtectedAttributes {
@@ -68,8 +68,7 @@ impl FromIterator<(Plaintext, String)> for FlattenedProtectedAttributes {
6868
/// Describes a flattened protected attribute intended for encryption.
6969
/// It is composed of a [Plaintext] and a [FlattenedKey].
7070
///
71-
// TODO: Only implement Debug in tests
72-
#[derive(PartialEq, Debug)]
71+
#[derive(PartialEq)]
7372
pub(crate) struct FlattenedProtectedAttribute {
7473
plaintext: Plaintext,
7574
key: FlattenedAttrName,
@@ -110,8 +109,7 @@ impl From<FlattenedProtectedAttribute> for BytesWithDescriptor {
110109
///
111110
/// The key is composed of a prefix, a key, and an optional subkey.
112111
/// A Map would have a key and a subkey, while a scalar would only have a key.
113-
// TODO: Only implement Debug in tests
114-
#[derive(PartialEq, Hash, Eq, Clone, Debug)]
112+
#[derive(PartialEq, Hash, Eq, Clone)]
115113
pub(super) struct FlattenedAttrName {
116114
// TODO: Use a Cow to avoid copies during decryption
117115
// We may also never set this to None in which can we can remove the Option
@@ -212,6 +210,26 @@ impl From<(&str, &str)> for FlattenedAttrName {
212210
#[cfg(test)]
213211
mod tests {
214212
use super::*;
213+
use std::fmt::Debug;
214+
215+
impl Debug for FlattenedAttrName {
216+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217+
f.debug_struct("FlattenedAttrName")
218+
.field("prefix", &self.prefix)
219+
.field("name", &self.name)
220+
.field("subkey", &self.subkey)
221+
.finish()
222+
}
223+
}
224+
225+
impl Debug for FlattenedProtectedAttribute {
226+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227+
f.debug_struct("FlattenedProtectedAttribute")
228+
.field("plaintext", &self.plaintext)
229+
.field("key", &self.key)
230+
.finish()
231+
}
232+
}
215233

216234
#[test]
217235
fn test_flattened_key_from_string() {

src/crypto/attrs/normalized_protected_attributes.rs

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ use super::flattened_protected_attributes::{
22
FlattenedAttrName, FlattenedProtectedAttribute, FlattenedProtectedAttributes,
33
};
44
use cipherstash_client::encryption::Plaintext;
5-
use std::collections::HashMap;
5+
use std::{
6+
collections::HashMap,
7+
hash::{Hash, Hasher},
8+
};
69

710
pub(crate) struct NormalizedProtectedAttributes {
811
values: HashMap<NormalizedKey, NormalizedValue>,
@@ -110,12 +113,29 @@ impl FromIterator<FlattenedProtectedAttribute> for NormalizedProtectedAttributes
110113

111114
/// Normalized keys are effectively just strings but wrapping them in an enum allows us to
112115
/// differentiate between scalar and map keys without checking the value.
113-
#[derive(PartialEq, Debug, Hash, Eq)]
114116
pub(crate) enum NormalizedKey {
115117
Scalar(String),
116118
Map(String),
117119
}
118120

121+
impl PartialEq for NormalizedKey {
122+
fn eq(&self, other: &Self) -> bool {
123+
match (self, other) {
124+
(Self::Scalar(a) | Self::Map(a), Self::Scalar(b) | Self::Map(b)) => a == b,
125+
}
126+
}
127+
}
128+
129+
impl Eq for NormalizedKey {}
130+
131+
impl Hash for NormalizedKey {
132+
fn hash<H: Hasher>(&self, state: &mut H) {
133+
match self {
134+
NormalizedKey::Scalar(s) | NormalizedKey::Map(s) => s.hash(state),
135+
}
136+
}
137+
}
138+
119139
impl NormalizedKey {
120140
pub(super) fn new_scalar(key: impl Into<String>) -> Self {
121141
Self::Scalar(key.into())
@@ -140,7 +160,6 @@ impl From<NormalizedKey> for String {
140160
}
141161
}
142162

143-
// TODO: Don't debug or only derive in tests
144163
#[derive(PartialEq, Debug)]
145164
pub(crate) enum NormalizedValue {
146165
Scalar(Plaintext),
@@ -198,6 +217,17 @@ impl NormalizedValue {
198217
#[cfg(test)]
199218
mod tests {
200219
use super::*;
220+
use std::fmt::Debug;
221+
222+
// Only debug in tests
223+
impl Debug for NormalizedKey {
224+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225+
match self {
226+
NormalizedKey::Scalar(s) => write!(f, "Scalar({})", s),
227+
NormalizedKey::Map(s) => write!(f, "Map({})", s),
228+
}
229+
}
230+
}
201231

202232
#[test]
203233
fn test_normalized_key() {
@@ -208,30 +238,6 @@ mod tests {
208238
assert_eq!(String::from(map), "map");
209239
}
210240

211-
#[test]
212-
fn test_normalized_key_partial_eq() {
213-
assert_eq!(
214-
NormalizedKey::Scalar("a".to_string()),
215-
NormalizedKey::Scalar("a".to_string())
216-
);
217-
assert_ne!(
218-
NormalizedKey::Scalar("a".to_string()),
219-
NormalizedKey::Scalar("b".to_string())
220-
);
221-
assert_eq!(
222-
NormalizedKey::Map("a".to_string()),
223-
NormalizedKey::Map("a".to_string())
224-
);
225-
assert_ne!(
226-
NormalizedKey::Map("a".to_string()),
227-
NormalizedKey::Map("b".to_string())
228-
);
229-
assert_ne!(
230-
NormalizedKey::Scalar("a".to_string()),
231-
NormalizedKey::Map("a".to_string())
232-
);
233-
}
234-
235241
#[test]
236242
fn test_normalized_value_into_scalar() {
237243
let scalar = NormalizedValue::Scalar(Plaintext::from("scalar"));

src/crypto/unsealed.rs

Lines changed: 99 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use crate::{
99
use cipherstash_client::encryption::Plaintext;
1010
use std::collections::HashMap;
1111

12-
/// Wrapper to indicate that a value is NOT encrypted
12+
/// Wrapper to which values are added prior to being encrypted.
13+
/// Values added as "protected" (e.g. via [Unsealed::add_protected]) will be encrypted.
14+
/// Values added as "unprotected" (e.g. via [Unsealed::add_unprotected]) will not be encrypted.
1315
pub struct Unsealed {
1416
/// Protected plaintexts with their descriptors
1517
protected: NormalizedProtectedAttributes,
@@ -38,18 +40,20 @@ impl Unsealed {
3840
}
3941
}
4042

41-
// TODO: Change this to take_unprotected
43+
#[deprecated(since = "0.7.3", note = "Use `Unsealed::take_unprotected` instead")]
4244
pub fn get_plaintext(&self, name: impl Into<AttributeName>) -> TableAttribute {
4345
self.unprotected
4446
.get(name)
4547
.cloned()
4648
.unwrap_or(TableAttribute::Null)
4749
}
4850

51+
/// Add a new protected attribute, `name`, with the given plaintext.
4952
pub fn add_protected(&mut self, name: impl Into<String>, plaintext: impl Into<Plaintext>) {
5053
self.protected.insert(name, plaintext.into());
5154
}
5255

56+
/// Add a new protected map, `name`, with the given map of plaintexts.
5357
pub fn add_protected_map(&mut self, name: impl Into<String>, map: HashMap<String, Plaintext>) {
5458
self.protected.insert_map(name, map);
5559
}
@@ -68,6 +72,7 @@ impl Unsealed {
6872
.insert_and_update_map(name, subkey, value.into());
6973
}
7074

75+
/// Add a new unprotected attribute, `name`, with the given plaintext.
7176
pub fn add_unprotected(
7277
&mut self,
7378
name: impl Into<AttributeName>,
@@ -76,6 +81,16 @@ impl Unsealed {
7681
self.unprotected.insert(name, attribute);
7782
}
7883

84+
/// Removes and returns the unprotected attribute, `name`.
85+
/// See also [TableAttribute].
86+
///
87+
/// If the attribute does not exist, `TableAttribute::Null` is returned.
88+
pub fn take_unprotected(&mut self, name: impl Into<AttributeName>) -> TableAttribute {
89+
self.unprotected
90+
.remove(name)
91+
.unwrap_or(TableAttribute::Null)
92+
}
93+
7994
/// Removes and returns the protected attribute, `name`.
8095
pub fn take_protected(&mut self, name: &str) -> Option<Plaintext> {
8196
self.protected.take(name)
@@ -103,63 +118,105 @@ impl Unsealed {
103118
unsealed
104119
}
105120

121+
/// Convert `self` into `T` using the attributes stored in `self`.
122+
/// The [Decryptable] trait must be implemented for `T` and this method calls [Decryptable::from_unsealed].
106123
pub fn into_value<T: Decryptable>(self) -> Result<T, SealError> {
107124
T::from_unsealed(self)
108125
}
109126
}
110127

111128
#[cfg(test)]
112129
mod tests {
113-
/*use std::collections::BTreeMap;
114-
115130
use super::*;
131+
use std::collections::BTreeMap;
116132

117133
#[test]
118-
fn test_nested_protected() {
134+
fn test_protected_field() {
119135
let mut unsealed = Unsealed::new_with_descriptor("test");
120-
unsealed.add_protected("test.a", Plaintext::from("a"));
121-
unsealed.add_protected("test.b", Plaintext::from("b"));
122-
unsealed.add_protected("test.c", Plaintext::from("c"));
123-
unsealed.add_protected("test.d", Plaintext::from("d"));
124-
125-
let nested = unsealed
126-
.nested_protected("test")
127-
.collect::<BTreeMap<_, _>>();
136+
unsealed.add_protected("test", "value");
128137

129-
assert_eq!(nested.len(), 4);
130-
assert_eq!(nested["a"], Plaintext::from("a"));
131-
assert_eq!(nested["b"], Plaintext::from("b"));
132-
assert_eq!(nested["c"], Plaintext::from("c"));
133-
assert_eq!(nested["d"], Plaintext::from("d"));
138+
let plaintext = unsealed.take_protected("test").unwrap();
139+
assert_eq!(plaintext, Plaintext::from("value"));
134140
}
135141

136142
#[test]
137-
fn test_flatted_protected_value() {
143+
fn test_protected_map() {
144+
let mut unsealed = Unsealed::new_with_descriptor("test");
138145
let mut map = HashMap::new();
139146
map.insert("a".to_string(), Plaintext::from("value-a"));
140147
map.insert("b".to_string(), Plaintext::from("value-b"));
141148
map.insert("c".to_string(), Plaintext::from("value-c"));
142-
map.insert("d".to_string(), Plaintext::from("value-d"));
143-
144-
let protected = NormalizedValue::Map(map, "test".to_string());
145-
let flattened = protected.flatten();
146-
147-
assert_eq!(flattened.len(), 4);
148-
assert!(flattened.contains(&NormalizedValue::Scalar(
149-
Plaintext::from("value-a"),
150-
"test.a".to_string()
151-
)));
152-
assert!(flattened.contains(&NormalizedValue::Scalar(
153-
Plaintext::from("value-b"),
154-
"test.b".to_string()
155-
)));
156-
assert!(flattened.contains(&NormalizedValue::Scalar(
157-
Plaintext::from("value-c"),
158-
"test.c".to_string()
159-
)));
160-
assert!(flattened.contains(&NormalizedValue::Scalar(
161-
Plaintext::from("value-d"),
162-
"test.d".to_string()
163-
)));
164-
}*/
149+
unsealed.add_protected_map("test", map);
150+
151+
let nested: BTreeMap<String, Plaintext> = unsealed
152+
.take_protected_map("test")
153+
.unwrap()
154+
.into_iter()
155+
.collect();
156+
157+
assert_eq!(nested.len(), 3);
158+
assert_eq!(nested["a"], Plaintext::from("value-a"));
159+
assert_eq!(nested["b"], Plaintext::from("value-b"));
160+
assert_eq!(nested["c"], Plaintext::from("value-c"));
161+
}
162+
163+
#[test]
164+
fn test_protected_map_field() {
165+
let mut unsealed = Unsealed::new_with_descriptor("test");
166+
unsealed.add_protected_map_field("test", "a", "value-a");
167+
unsealed.add_protected_map_field("test", "b", "value-b");
168+
unsealed.add_protected_map_field("test", "c", "value-c");
169+
170+
let nested: BTreeMap<String, Plaintext> = unsealed
171+
.take_protected_map("test")
172+
.unwrap()
173+
.into_iter()
174+
.collect();
175+
176+
assert_eq!(nested.len(), 3);
177+
assert_eq!(nested["a"], Plaintext::from("value-a"));
178+
assert_eq!(nested["b"], Plaintext::from("value-b"));
179+
assert_eq!(nested["c"], Plaintext::from("value-c"));
180+
}
181+
182+
#[test]
183+
fn test_protected_mixed() {
184+
let mut unsealed = Unsealed::new_with_descriptor("test");
185+
unsealed.add_protected("test", "value");
186+
unsealed.add_protected_map_field("attrs", "a", "value-a");
187+
unsealed.add_protected_map_field("attrs", "b", "value-b");
188+
unsealed.add_protected_map_field("attrs", "c", "value-c");
189+
190+
let plaintext = unsealed.take_protected("test").unwrap();
191+
assert_eq!(plaintext, Plaintext::from("value"));
192+
193+
let nested: BTreeMap<String, Plaintext> = unsealed
194+
.take_protected_map("attrs")
195+
.unwrap()
196+
.into_iter()
197+
.collect();
198+
199+
assert_eq!(nested.len(), 3);
200+
assert_eq!(nested["a"], Plaintext::from("value-a"));
201+
assert_eq!(nested["b"], Plaintext::from("value-b"));
202+
assert_eq!(nested["c"], Plaintext::from("value-c"));
203+
}
204+
205+
#[test]
206+
#[should_panic]
207+
fn test_protected_map_override() {
208+
let mut unsealed = Unsealed::new_with_descriptor("test");
209+
unsealed.add_protected("test", "value");
210+
// Panics because "test" is already a protected scalar
211+
unsealed.add_protected_map_field("test", "a", "value-a");
212+
}
213+
214+
#[test]
215+
fn test_unprotected() {
216+
let mut unsealed = Unsealed::new_with_descriptor("test");
217+
unsealed.add_unprotected("test", "value");
218+
219+
let attribute = unsealed.take_unprotected("test");
220+
assert!(attribute == "value".into(), "values do not match");
221+
}
165222
}

src/encrypted_table/table_attributes.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ impl TableAttributes {
4646
.try_insert_map(subkey.into(), value.into())
4747
}
4848

49+
pub(crate) fn remove(&mut self, name: impl Into<AttributeName>) -> Option<TableAttribute> {
50+
self.0.remove(&name.into())
51+
}
52+
4953
// TODO: Add unit tests for this
5054
/// Partition the attributes into protected and unprotected attributes
5155
/// given the list of protected keys.

src/traits/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ mod tests {
246246
unsealed.take_protected("name"),
247247
)?,
248248
age: TryFromPlaintext::try_from_optional_plaintext(unsealed.take_protected("age"))?,
249-
tag: TryFromTableAttr::try_from_table_attr(unsealed.get_plaintext("tag"))?,
249+
tag: TryFromTableAttr::try_from_table_attr(unsealed.take_unprotected("tag"))?,
250250
attrs: get_attrs(&mut unsealed)?,
251251
})
252252
}

0 commit comments

Comments
 (0)