cipherstash_dynamodb/crypto/
unsealed.rs

1use super::{
2    attrs::{FlattenedProtectedAttributes, NormalizedProtectedAttributes},
3    SealError,
4};
5use crate::{
6    encrypted_table::{AttributeName, TableAttribute, TableAttributes},
7    Decryptable,
8};
9use cipherstash_client::encryption::Plaintext;
10use std::collections::HashMap;
11
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.
15pub struct Unsealed {
16    /// Protected plaintexts with their descriptors
17    protected: NormalizedProtectedAttributes,
18    unprotected: TableAttributes,
19}
20
21impl Default for Unsealed {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl Unsealed {
28    pub fn new() -> Self {
29        Self {
30            protected: NormalizedProtectedAttributes::new(),
31            unprotected: Default::default(),
32        }
33    }
34
35    /// Create a new Unsealed with a descriptor prefix.
36    pub fn new_with_descriptor(descriptor: impl Into<String>) -> Self {
37        Self {
38            protected: NormalizedProtectedAttributes::new_with_prefix(descriptor),
39            unprotected: Default::default(),
40        }
41    }
42
43    /// Create a new Unsealed from the protected and unprotected attributes.
44    pub(crate) fn new_from_parts(
45        protected: NormalizedProtectedAttributes,
46        unprotected: TableAttributes,
47    ) -> Self {
48        let mut unsealed = Self::new();
49        unsealed.protected = protected;
50        unsealed.unprotected = unprotected;
51        unsealed
52    }
53
54    /// Create a new Unsealed from the protected and unprotected attributes.
55    pub(crate) fn new_from_unprotected(unprotected: TableAttributes) -> Self {
56        let mut unsealed = Self::new();
57        unsealed.unprotected = unprotected;
58        unsealed
59    }
60
61    #[deprecated(since = "0.7.3", note = "Use `Unsealed::take_unprotected` instead")]
62    pub fn get_plaintext(&self, name: impl Into<AttributeName>) -> TableAttribute {
63        self.unprotected
64            .get(name)
65            .cloned()
66            .unwrap_or(TableAttribute::Null)
67    }
68
69    /// Add a new protected attribute, `name`, with the given plaintext.
70    pub fn add_protected(&mut self, name: impl Into<String>, plaintext: impl Into<Plaintext>) {
71        self.protected.insert(name, plaintext.into());
72    }
73
74    /// Add a new protected map, `name`, with the given map of plaintexts.
75    pub fn add_protected_map(&mut self, name: impl Into<String>, map: HashMap<String, Plaintext>) {
76        self.protected.insert_map(name, map);
77    }
78
79    /// Insert a new key-value pair into a map stored in the protected attributes, `name`.
80    /// If the map does not exist, it will be created.
81    /// If the map exists, the key-value pair will be updated.
82    /// If an attribute called `name` already exists but is not a map, this will panic.
83    pub fn add_protected_map_field(
84        &mut self,
85        name: impl Into<String>,
86        subkey: impl Into<String>,
87        value: impl Into<Plaintext>,
88    ) {
89        self.protected
90            .insert_and_update_map(name, subkey, value.into());
91    }
92
93    /// Add a new unprotected attribute, `name`, with the given plaintext.
94    pub fn add_unprotected(
95        &mut self,
96        name: impl Into<AttributeName>,
97        attribute: impl Into<TableAttribute>,
98    ) {
99        self.unprotected.insert(name, attribute);
100    }
101
102    /// Removes and returns the unprotected attribute, `name`.
103    /// See also [TableAttribute].
104    ///
105    /// If the attribute does not exist, `TableAttribute::Null` is returned.
106    pub fn take_unprotected(&mut self, name: impl Into<AttributeName>) -> TableAttribute {
107        self.unprotected
108            .remove(name)
109            .unwrap_or(TableAttribute::Null)
110    }
111
112    /// Removes and returns the protected attribute, `name`.
113    pub fn take_protected(&mut self, name: &str) -> Option<Plaintext> {
114        self.protected.take(name)
115    }
116
117    /// Removes and returns the map stored in the protected attributes, `name`.
118    /// The caller can convert to whatever type they need.
119    pub fn take_protected_map(&mut self, name: &str) -> Option<HashMap<String, Plaintext>> {
120        self.protected.take_map(name)
121    }
122
123    /// Flatten the protected attributes and returns them along with the unprotected attributes.
124    pub(crate) fn flatten_into_parts(self) -> (FlattenedProtectedAttributes, TableAttributes) {
125        (self.protected.flatten(), self.unprotected)
126    }
127
128    /// Convert `self` into `T` using the attributes stored in `self`.
129    /// The [Decryptable] trait must be implemented for `T` and this method calls [Decryptable::from_unsealed].
130    pub fn into_value<T: Decryptable>(self) -> Result<T, SealError> {
131        T::from_unsealed(self)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use std::collections::BTreeMap;
139
140    #[test]
141    fn test_protected_field() {
142        let mut unsealed = Unsealed::new_with_descriptor("test");
143        unsealed.add_protected("test", "value");
144
145        let plaintext = unsealed.take_protected("test").unwrap();
146        assert_eq!(plaintext, Plaintext::from("value"));
147    }
148
149    #[test]
150    fn test_protected_map() {
151        let mut unsealed = Unsealed::new_with_descriptor("test");
152        let mut map = HashMap::new();
153        map.insert("a".to_string(), Plaintext::from("value-a"));
154        map.insert("b".to_string(), Plaintext::from("value-b"));
155        map.insert("c".to_string(), Plaintext::from("value-c"));
156        unsealed.add_protected_map("test", map);
157
158        let nested: BTreeMap<String, Plaintext> = unsealed
159            .take_protected_map("test")
160            .unwrap()
161            .into_iter()
162            .collect();
163
164        assert_eq!(nested.len(), 3);
165        assert_eq!(nested["a"], Plaintext::from("value-a"));
166        assert_eq!(nested["b"], Plaintext::from("value-b"));
167        assert_eq!(nested["c"], Plaintext::from("value-c"));
168    }
169
170    #[test]
171    fn test_protected_map_field() {
172        let mut unsealed = Unsealed::new_with_descriptor("test");
173        unsealed.add_protected_map_field("test", "a", "value-a");
174        unsealed.add_protected_map_field("test", "b", "value-b");
175        unsealed.add_protected_map_field("test", "c", "value-c");
176
177        let nested: BTreeMap<String, Plaintext> = unsealed
178            .take_protected_map("test")
179            .unwrap()
180            .into_iter()
181            .collect();
182
183        assert_eq!(nested.len(), 3);
184        assert_eq!(nested["a"], Plaintext::from("value-a"));
185        assert_eq!(nested["b"], Plaintext::from("value-b"));
186        assert_eq!(nested["c"], Plaintext::from("value-c"));
187    }
188
189    #[test]
190    fn test_protected_mixed() {
191        let mut unsealed = Unsealed::new_with_descriptor("test");
192        unsealed.add_protected("test", "value");
193        unsealed.add_protected_map_field("attrs", "a", "value-a");
194        unsealed.add_protected_map_field("attrs", "b", "value-b");
195        unsealed.add_protected_map_field("attrs", "c", "value-c");
196
197        let plaintext = unsealed.take_protected("test").unwrap();
198        assert_eq!(plaintext, Plaintext::from("value"));
199
200        let nested: BTreeMap<String, Plaintext> = unsealed
201            .take_protected_map("attrs")
202            .unwrap()
203            .into_iter()
204            .collect();
205
206        assert_eq!(nested.len(), 3);
207        assert_eq!(nested["a"], Plaintext::from("value-a"));
208        assert_eq!(nested["b"], Plaintext::from("value-b"));
209        assert_eq!(nested["c"], Plaintext::from("value-c"));
210    }
211
212    #[test]
213    #[should_panic]
214    fn test_protected_map_override() {
215        let mut unsealed = Unsealed::new_with_descriptor("test");
216        unsealed.add_protected("test", "value");
217        // Panics because "test" is already a protected scalar
218        unsealed.add_protected_map_field("test", "a", "value-a");
219    }
220
221    #[test]
222    fn test_unprotected() {
223        let mut unsealed = Unsealed::new_with_descriptor("test");
224        unsealed.add_unprotected("test", "value");
225
226        let attribute = unsealed.take_unprotected("test");
227        assert!(attribute == "value".into(), "values do not match");
228    }
229}