Skip to content

Commit ef6c2aa

Browse files
author
Bennett Hardwick
authored
Merge pull request #15 from cipherstash/feat/new-sort-primary
Change to dynamic sort key, new attrs, primary key abstraction
2 parents c3a280c + 2a9650d commit ef6c2aa

File tree

18 files changed

+513
-36
lines changed

18 files changed

+513
-36
lines changed

cryptonamo-derive/src/encryptable.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,32 @@ pub(crate) fn derive_encryptable(input: DeriveInput) -> Result<TokenStream, syn:
3333
}
3434
}));
3535

36+
let sort_key_impl = if let Some(sort_key_field) = &settings.sort_key_field {
37+
let sort_key_attr = format_ident!("{sort_key_field}");
38+
quote! { format!("{}#{}", Self::type_name(), self.#sort_key_attr) }
39+
} else {
40+
quote! { Self::type_name().into() }
41+
};
42+
43+
let primary_key_impl = if settings.sort_key_field.is_some() {
44+
quote! { type PrimaryKey = cryptonamo::PkSk; }
45+
} else {
46+
quote! { type PrimaryKey = cryptonamo::Pk; }
47+
};
48+
3649
let expanded = quote! {
3750
#[automatically_derived]
3851
impl cryptonamo::traits::Encryptable for #ident {
52+
#primary_key_impl
53+
3954
fn type_name() -> &'static str {
4055
#type_name
4156
}
4257

58+
fn sort_key(&self) -> String {
59+
#sort_key_impl
60+
}
61+
4362
fn partition_key(&self) -> String {
4463
self.#partition_key.to_string()
4564
}

cryptonamo-derive/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ mod settings;
1010
use proc_macro::TokenStream;
1111
use syn::{parse_macro_input, DeriveInput};
1212

13-
#[proc_macro_derive(Encryptable, attributes(cryptonamo))]
13+
#[proc_macro_derive(Encryptable, attributes(cryptonamo, sort_key, partition_key))]
1414
pub fn derive_encryptable(input: TokenStream) -> TokenStream {
1515
encryptable::derive_encryptable(parse_macro_input!(input as DeriveInput))
1616
.unwrap_or_else(syn::Error::into_compile_error)
1717
.into()
1818
}
1919

20-
#[proc_macro_derive(Decryptable, attributes(cryptonamo))]
20+
#[proc_macro_derive(Decryptable, attributes(cryptonamo, sort_key, partition_key))]
2121
pub fn derive_decryptable(input: TokenStream) -> TokenStream {
2222
decryptable::derive_decryptable(parse_macro_input!(input as DeriveInput))
2323
.unwrap_or_else(syn::Error::into_compile_error)
2424
.into()
2525
}
2626

27-
#[proc_macro_derive(Searchable, attributes(cryptonamo))]
27+
#[proc_macro_derive(Searchable, attributes(cryptonamo, sort_key, partition_key))]
2828
pub fn derive_searchable(input: TokenStream) -> TokenStream {
2929
searchable::derive_searchable(parse_macro_input!(input as DeriveInput))
3030
.unwrap_or_else(syn::Error::into_compile_error)

cryptonamo-derive/src/settings/builder.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ pub(crate) struct SettingsBuilder {
77
ident: Ident,
88
type_name: String,
99
sort_key_prefix: Option<String>,
10-
partition_key: Option<String>,
10+
sort_key_field: Option<String>,
11+
partition_key_field: Option<String>,
1112
protected_attributes: Vec<String>,
1213
unprotected_attributes: Vec<String>,
1314
skipped_attributes: Vec<String>,
@@ -33,7 +34,8 @@ impl SettingsBuilder {
3334
ident: input.ident.clone(),
3435
type_name,
3536
sort_key_prefix: None,
36-
partition_key: None,
37+
sort_key_field: None,
38+
partition_key_field: None,
3739
protected_attributes: Vec::new(),
3840
unprotected_attributes: Vec::new(),
3941
skipped_attributes: Vec::new(),
@@ -92,6 +94,48 @@ impl SettingsBuilder {
9294

9395
// Parse the meta for the field
9496
for attr in &field.attrs {
97+
if attr.path().is_ident("sort_key") {
98+
let field_name = ident
99+
.as_ref()
100+
.ok_or_else(|| {
101+
syn::Error::new_spanned(
102+
field,
103+
"internal error: identifier was not Some",
104+
)
105+
})?
106+
.to_string();
107+
108+
if let Some(f) = &self.sort_key_field {
109+
return Err(syn::Error::new_spanned(
110+
field,
111+
format!("sort key was already specified to be '{f}'"),
112+
));
113+
}
114+
115+
self.sort_key_field = Some(field_name);
116+
}
117+
118+
if attr.path().is_ident("partition_key") {
119+
let field_name = ident
120+
.as_ref()
121+
.ok_or_else(|| {
122+
syn::Error::new_spanned(
123+
field,
124+
"internal error: identifier was not Some",
125+
)
126+
})?
127+
.to_string();
128+
129+
if let Some(f) = &self.partition_key_field {
130+
return Err(syn::Error::new_spanned(
131+
field,
132+
format!("partition key was already specified to be '{f}'"),
133+
));
134+
}
135+
136+
self.partition_key_field = Some(field_name);
137+
}
138+
95139
if attr.path().is_ident("cryptonamo") {
96140
let mut query: Option<(String, String, Span)> = None;
97141
let mut compound_index_name: Option<(String, Span)> = None;
@@ -203,7 +247,8 @@ impl SettingsBuilder {
203247
ident,
204248
type_name,
205249
sort_key_prefix,
206-
partition_key,
250+
sort_key_field,
251+
partition_key_field: partition_key,
207252
protected_attributes,
208253
unprotected_attributes,
209254
skipped_attributes,
@@ -222,7 +267,8 @@ impl SettingsBuilder {
222267
Ok(Settings {
223268
ident,
224269
sort_key_prefix,
225-
partition_key: Some(partition_key), // TODO: Remove the Some
270+
sort_key_field,
271+
partition_key_field: Some(partition_key), // TODO: Remove the Some
226272
protected_attributes,
227273
unprotected_attributes,
228274
skipped_attributes,
@@ -236,7 +282,7 @@ impl SettingsBuilder {
236282
}
237283

238284
pub(crate) fn set_partition_key(&mut self, value: String) -> Result<(), syn::Error> {
239-
self.partition_key = Some(value);
285+
self.partition_key_field = Some(value);
240286
Ok(())
241287
}
242288

cryptonamo-derive/src/settings/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ pub(crate) enum AttributeMode {
1616
pub(crate) struct Settings {
1717
ident: Ident,
1818
pub(crate) sort_key_prefix: String,
19-
pub(crate) partition_key: Option<String>,
19+
pub(crate) sort_key_field: Option<String>,
20+
pub(crate) partition_key_field: Option<String>,
2021
protected_attributes: Vec<String>,
2122
unprotected_attributes: Vec<String>,
2223

@@ -70,7 +71,7 @@ impl Settings {
7071
}
7172

7273
pub(crate) fn get_partition_key(&self) -> Result<String, syn::Error> {
73-
self.partition_key.clone().ok_or_else(|| {
74+
self.partition_key_field.clone().ok_or_else(|| {
7475
syn::Error::new_spanned(
7576
&self.sort_key_prefix,
7677
"No partition key defined for this struct",

dynamo.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ aws dynamodb create-table \
88
AttributeName=pk,KeyType=HASH \
99
AttributeName=sk,KeyType=RANGE \
1010
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
11-
--global-secondary-indexes "IndexName=TermIndex,KeySchema=[{AttributeName=term,KeyType=HASH},{AttributeName=pk,KeyType=RANGE}],Projection={ProjectionType=ALL},ProvisionedThroughput={ReadCapacityUnits=5,WriteCapacityUnits=5}" \
11+
--global-secondary-indexes "IndexName=TermIndex,KeySchema=[{AttributeName=term,KeyType=HASH}],Projection={ProjectionType=ALL},ProvisionedThroughput={ReadCapacityUnits=5,WriteCapacityUnits=5}" \
1212
--endpoint-url http://localhost:8000

src/crypto/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ pub enum CryptoError {
4444
Other(String),
4545
}
4646

47-
pub(crate) fn all_index_keys<E: Searchable + Encryptable>() -> Vec<String> {
47+
pub(crate) fn all_index_keys<E: Searchable + Encryptable>(sort_key: &str) -> Vec<String> {
4848
E::protected_indexes()
4949
.iter()
5050
.flat_map(|index_name| {
5151
(0..)
5252
.take(MAX_TERMS_PER_INDEX)
53-
.map(|i| format!("{}#{}#{}", E::type_name(), index_name, i))
53+
.map(|i| format!("{}#{}#{}", sort_key, index_name, i))
5454
.collect::<Vec<String>>()
5555
})
5656
.collect()

src/crypto/sealer.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,10 @@ impl<T> Sealer<T> {
6060
T: Searchable,
6161
{
6262
let pk = encrypt_partition_key(&self.inner.partition_key(), cipher).unwrap(); // FIXME
63+
let sk = self.inner.sort_key();
6364

64-
let mut table_entry = TableEntry::new_with_attributes(
65-
pk.clone(),
66-
T::type_name().to_string(),
67-
None,
68-
self.unsealed.unprotected(),
69-
);
65+
let mut table_entry =
66+
TableEntry::new_with_attributes(pk.clone(), sk, None, self.unsealed.unprotected());
7067

7168
let protected = T::protected_attributes()
7269
.iter()
@@ -84,6 +81,8 @@ impl<T> Sealer<T> {
8481
}
8582
});
8683

84+
let sort_key = self.inner.sort_key();
85+
8786
let protected_indexes = T::protected_indexes();
8887
let terms: Vec<(&&str, Vec<u8>)> = protected_indexes
8988
.iter()
@@ -126,7 +125,7 @@ impl<T> Sealer<T> {
126125
.clone()
127126
.set_term(hex::encode(term))
128127
// TODO: HMAC the sort key, too (users#index_name#pk)
129-
.set_sk(format!("{}#{}#{}", T::type_name(), index_name, i)),
128+
.set_sk(format!("{}#{}#{}", &sort_key, index_name, i)),
130129
)
131130
})
132131
.chain(once(Sealed(table_entry.clone())))

src/encrypted_table/mod.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ pub use self::{
66
};
77
use crate::{
88
crypto::*,
9-
traits::{Decryptable, ReadConversionError, Searchable, WriteConversionError},
9+
traits::{
10+
Decryptable, PrimaryKey, PrimaryKeyParts, ReadConversionError, Searchable,
11+
WriteConversionError,
12+
},
1013
};
1114
use aws_sdk_dynamodb::{
1215
types::{AttributeValue, Delete, Put, TransactWriteItem},
@@ -112,12 +115,13 @@ impl EncryptedTable {
112115
QueryBuilder::new(self)
113116
}
114117

115-
pub async fn get<T>(&self, pk: &str) -> Result<Option<T>, GetError>
118+
pub async fn get<T>(&self, k: impl Into<T::PrimaryKey>) -> Result<Option<T>, GetError>
116119
where
117120
T: Decryptable,
118121
{
119-
let pk = encrypt_partition_key(pk, &self.cipher)?;
120-
let sk = T::type_name().to_string();
122+
let PrimaryKeyParts { pk, sk } = k.into().into_parts(T::type_name());
123+
124+
let pk = encrypt_partition_key(&pk, &self.cipher)?;
121125

122126
let result = self
123127
.db
@@ -138,12 +142,13 @@ impl EncryptedTable {
138142
}
139143
}
140144

141-
pub async fn delete<E: Searchable>(&self, pk: &str) -> Result<(), DeleteError> {
142-
let pk = AttributeValue::S(encrypt_partition_key(pk, &self.cipher)?);
145+
// TODO: create PrimaryKey abstraction
146+
pub async fn delete<E: Searchable>(&self, k: impl Into<E::PrimaryKey>) -> Result<(), DeleteError> {
147+
let PrimaryKeyParts { pk, sk } = k.into().into_parts(E::type_name());
148+
149+
let pk = AttributeValue::S(encrypt_partition_key(&pk, &self.cipher)?);
143150

144-
let sk_to_delete = [E::type_name().to_string()]
145-
.into_iter()
146-
.chain(all_index_keys::<E>().into_iter());
151+
let sk_to_delete = all_index_keys::<E>(&sk).into_iter().into_iter().chain([sk]);
147152

148153
let transact_items = sk_to_delete.map(|sk| {
149154
TransactWriteItem::builder()
@@ -177,6 +182,7 @@ impl EncryptedTable {
177182
{
178183
let mut seen_sk = HashSet::new();
179184

185+
let sk = record.sort_key();
180186
let sealer: Sealer<T> = record.into_sealer()?;
181187
let (pk, sealed) = sealer.seal(&self.cipher, 12).await?;
182188

@@ -198,7 +204,7 @@ impl EncryptedTable {
198204
);
199205
}
200206

201-
for index_sk in all_index_keys::<T>() {
207+
for index_sk in all_index_keys::<T>(&sk) {
202208
if seen_sk.contains(&index_sk) {
203209
continue;
204210
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ mod error;
403403
pub mod traits;
404404
pub use encrypted_table::{EncryptedTable, QueryBuilder};
405405
pub use error::Error;
406-
pub use traits::{Decryptable, Encryptable, Searchable};
406+
pub use traits::{Decryptable, Encryptable, Searchable, PrimaryKey, PkSk, Pk};
407407

408408
#[doc(hidden)]
409409
pub use cryptonamo_derive::{Decryptable, Encryptable, Searchable};

src/traits/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ pub use cipherstash_client::encryption::{
66
},
77
Plaintext,
88
};
9+
10+
mod primary_key;
11+
pub use primary_key::*;
12+
913
use std::fmt::Debug;
1014
use thiserror::Error;
1115

@@ -24,8 +28,15 @@ pub enum WriteConversionError {
2428
}
2529

2630
pub trait Encryptable: Debug + Sized {
31+
type PrimaryKey: PrimaryKey;
32+
2733
// TODO: Add a function indicating that the root should be stored
2834
fn type_name() -> &'static str;
35+
36+
fn sort_key(&self) -> String {
37+
Self::type_name().into()
38+
}
39+
2940
fn partition_key(&self) -> String;
3041

3142
fn protected_attributes() -> Vec<&'static str>;
@@ -56,6 +67,7 @@ pub trait Searchable: Encryptable {
5667

5768
pub trait Decryptable: Encryptable {
5869
/// Convert an `Unsealed` into a `Self`.
70+
5971
fn from_unsealed(unsealed: Unsealed) -> Result<Self, SealError>;
6072

6173
/// Defines which attributes are decryptable for this type.

0 commit comments

Comments
 (0)