Skip to content

Commit 74239ed

Browse files
committed
Using ZeroKMS and ScopedCipher directly, rough working version - tests pass
1 parent b231c5d commit 74239ed

File tree

14 files changed

+831
-557
lines changed

14 files changed

+831
-557
lines changed

Cargo.lock

Lines changed: 514 additions & 322 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ keywords = ["cryptography", "security", "databases", "encryption", "dynamodb"]
1313
categories = ["cryptography", "database"]
1414

1515
[dependencies]
16-
cipherstash-client = { version = ">=0.12.4" }
16+
cipherstash-client = { version = ">=0.12.4", path = "../cipherstash-suite/packages/cipherstash-client/" }
1717
cipherstash-dynamodb-derive = { version = "0.8", path = "cipherstash-dynamodb-derive" }
1818

1919
aws-sdk-dynamodb = "1.3.0"
@@ -26,6 +26,7 @@ hex = "0.4.3"
2626
tracing = { version = "0.1", features = ["log"] }
2727
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"]}
2828
miette = "7.2.0"
29+
uuid = "1.10.0"
2930

3031
[dev-dependencies]
3132
tokio = { version = "1", features = ["full"] }

src/crypto/attrs/flattened_encrypted_attributes.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
use crate::{
22
crypto::{attrs::flattened_protected_attributes::FlattenedAttrName, SealError},
3-
encrypted_table::TableAttributes,
3+
encrypted_table::{ScopedCipherWithCreds, TableAttributes},
44
traits::TableAttribute,
55
};
66
use cipherstash_client::{
7-
credentials::{service_credentials::ServiceToken, Credentials},
8-
encryption::Encryption,
9-
zerokms::EncryptedRecord,
7+
encryption::Plaintext, zerokms::EncryptedRecord
108
};
119
use itertools::Itertools;
1210

@@ -36,7 +34,7 @@ impl FlattenedEncryptedAttributes {
3634
/// Decrypt self, returning a [FlattenedProtectedAttributes].
3735
pub(crate) async fn decrypt_all(
3836
self,
39-
cipher: &Encryption<impl Credentials<Token = ServiceToken>>,
37+
cipher: &ScopedCipherWithCreds,
4038
) -> Result<FlattenedProtectedAttributes, SealError> {
4139
let descriptors = self
4240
.attrs
@@ -47,8 +45,13 @@ impl FlattenedEncryptedAttributes {
4745
cipher
4846
.decrypt(self.attrs.into_iter())
4947
.await
50-
.map(|records| records.into_iter().zip(descriptors.into_iter()).collect())
51-
.map_err(SealError::from)
48+
.map(|records| records
49+
.into_iter()
50+
// FIXME: We should change the decrypt method to return a plaintext and/or make a Plaintext::from_bytes method which consumes the bytes
51+
.map(|bytes| Plaintext::from_slice(&bytes).unwrap())
52+
.zip(descriptors.into_iter()).collect())
53+
// FIXME: EncryptedRecord should return an error exposed in cipherstash_client
54+
.map_err(|_| SealError::AssertionFailed("FIXME".to_string()))
5255
}
5356

5457
/// Denormalize the encrypted records into a TableAttributes.

src/crypto/attrs/flattened_protected_attributes.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ use super::{
22
flattened_encrypted_attributes::FlattenedEncryptedAttributes,
33
normalized_protected_attributes::NormalizedKey,
44
};
5-
use crate::{crypto::SealError, encrypted_table::AttributeName};
5+
use crate::{crypto::SealError, encrypted_table::{AttributeName, ScopedCipherWithCreds}};
66
use cipherstash_client::{
7-
credentials::{service_credentials::ServiceToken, Credentials},
8-
encryption::{BytesWithDescriptor, Encryption, Plaintext},
7+
encryption::{BytesWithDescriptor, Plaintext}, zerokms::EncryptPayload,
98
};
109
use itertools::Itertools;
1110

@@ -32,13 +31,16 @@ impl FlattenedProtectedAttributes {
3231
/// The output is a vec of `chunk_into` [FlattenedEncryptedAttributes] objects.
3332
pub(crate) async fn encrypt_all(
3433
self,
35-
cipher: &Encryption<impl Credentials<Token = ServiceToken>>,
34+
cipher: &ScopedCipherWithCreds,
3635
chunk_into: usize,
3736
) -> Result<Vec<FlattenedEncryptedAttributes>, SealError> {
3837
let chunk_size = self.0.len() / chunk_into;
38+
let payloads: Vec<BytesWithDescriptor> = self.0.into_iter().map(Into::into).collect();
3939

4040
cipher
41-
.encrypt(self.0.into_iter())
41+
.encrypt(
42+
payloads.iter().map(EncryptPayload::from),
43+
)
4244
.await?
4345
.into_iter()
4446
.chunks(chunk_size)

src/crypto/b64_encode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
22

33
/// Base64 encode the provided buffer using a URL safe scheme with no padding
4+
// TODO: Make this consume and zeroize
45
pub fn b64_encode(x: impl AsRef<[u8]>) -> String {
56
URL_SAFE_NO_PAD.encode(x)
67
}

src/crypto/mod.rs

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,12 @@ mod sealed;
44
mod sealer;
55
mod unsealed;
66
use crate::{
7-
traits::{PrimaryKeyError, PrimaryKeyParts, ReadConversionError, WriteConversionError},
8-
Identifiable, IndexType, PrimaryKey,
7+
traits::{PrimaryKeyError, PrimaryKeyParts, ReadConversionError, WriteConversionError}, Identifiable, IndexType, PrimaryKey
98
};
109
use cipherstash_client::{
11-
credentials::{service_credentials::ServiceToken, Credentials},
1210
encryption::{
13-
compound_indexer::{CompoundIndex, ExactIndex},
14-
Encryption, EncryptionError, Plaintext, TypeParseError,
15-
},
16-
zerokms::Error as ZeroKmsError,
11+
EncryptionError, TypeParseError
12+
},zerokms
1713
};
1814
use miette::Diagnostic;
1915
use std::borrow::Cow;
@@ -47,15 +43,16 @@ pub enum SealError {
4743
#[error("Assertion failed: {0}")]
4844
AssertionFailed(String),
4945

50-
// Note that we don't expose the specific error type here
51-
// so as to avoid leaking any information
5246
#[error(transparent)]
53-
EncryptionError(#[from] EncryptionError),
47+
//#[diagnostic(transparent)] // TODO
48+
CryptoError(#[from] zerokms::Error),
5449

50+
/// Error resulting from Indexing in `cipherstash_client::encryption::compound_indexer`
5551
#[error(transparent)]
56-
ZeroKmsError(#[from] ZeroKmsError),
52+
IndexError(#[from] EncryptionError),
5753
}
5854

55+
// TODO: Possibly remove this
5956
#[derive(Error, Debug)]
6057
pub enum CryptoError {
6158
#[error("EncryptionError: {0}")]
@@ -94,31 +91,64 @@ pub(crate) fn all_index_keys<'a>(
9491
.collect()
9592
}
9693

97-
/// Use a CipherStash [`ExactIndex`] to take the HMAC of a string with a provided salt
94+
/* /// Use a CipherStash [`ExactIndex`] to take the HMAC of a string with a provided salt
9895
///
9996
/// This value is used for term index keys and "encrypted" partition / sort keys
100-
pub fn hmac(
97+
pub fn prf(
10198
value: &str,
10299
salt: Option<&str>,
103-
cipher: &Encryption<impl Credentials<Token = ServiceToken>>,
100+
cipher: &Cipher,
101+
// TODO: Pass a DatasetWithRootKey (use a Protected)
102+
root_key: [u8; 32],
104103
) -> Result<Vec<u8>, EncryptionError> {
105104
let plaintext = Plaintext::Utf8Str(Some(value.to_string()));
106105
let index = CompoundIndex::new(ExactIndex::new(vec![]));
107106
108-
cipher
109-
.compound_index(
110-
&index,
111-
plaintext,
112-
// passing None here results in no terms so pass an empty string
113-
Some(salt.unwrap_or("")),
114-
32,
115-
)?
107+
// passing None here results in no terms so pass an empty string
108+
let salt = salt.unwrap_or("");
109+
let accumulator = Accumulator::from_salt(salt);
110+
111+
index
112+
.compose_index(root_key, plaintext.into(), accumulator)?
113+
// TODO: Use a constant for the 32
114+
.truncate(32)
115+
.map(IndexTerm::from)?
116116
.as_binary()
117117
.ok_or(EncryptionError::IndexingError(
118118
"Invalid term type".to_string(),
119119
))
120+
} */
121+
122+
/*// FIXME: Don't use the root key here
123+
pub fn query_compound_prf<I>(index: I, plaintext: ComposablePlaintext, info: String, root_key: [u8; 32]) -> Result<IndexTerm, SealError> where I: ComposableIndex + Send {
124+
let index = CompoundIndex::new(index);
125+
let accumulator = Accumulator::from_salt(info);
126+
127+
index
128+
.compose_query(root_key, plaintext, accumulator)?
129+
.exactly_one()
130+
// FIXME: Don't use a magic number
131+
.and_then(|term| term.truncate(12))
132+
.and_then(|term| IndexTerm::try_from(term))
133+
.map_err(EncryptionError::from)
134+
.map_err(SealError::from)
120135
}
121136
137+
// FIXME: Don't use the root key here
138+
pub fn compound_prf<I>(index: I, plaintext: ComposablePlaintext, info: String, root_key: [u8; 32]) -> Result<IndexTerm, SealError> where I: ComposableIndex + Send {
139+
let index = CompoundIndex::new(index);
140+
let accumulator = Accumulator::from_salt(info);
141+
142+
let term = index
143+
.compose_index(root_key, plaintext, accumulator)?
144+
// FIXME: Don't use a magic number
145+
.truncate(12)
146+
.map_err(EncryptionError::from)?;
147+
148+
// Saftey: This conversion is Infallible
149+
Ok(IndexTerm::try_from(term).unwrap())
150+
} */
151+
122152
// Contains all the necessary information to encrypt the primary key pair
123153
#[derive(Clone)]
124154
pub struct PreparedPrimaryKey {

src/crypto/sealed.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
use crate::{
22
crypto::attrs::FlattenedEncryptedAttributes,
3-
encrypted_table::TableEntry,
3+
encrypted_table::{ScopedCipherWithCreds, TableEntry},
44
traits::{ReadConversionError, WriteConversionError},
55
Decryptable, Identifiable,
66
};
77
use aws_sdk_dynamodb::{primitives::Blob, types::AttributeValue};
8-
use cipherstash_client::{
9-
credentials::{service_credentials::ServiceToken, Credentials},
10-
encryption::Encryption,
11-
};
128
use itertools::Itertools;
139
use std::{borrow::Cow, collections::HashMap};
1410

@@ -72,7 +68,7 @@ impl SealedTableEntry {
7268
pub(crate) async fn unseal_all(
7369
items: Vec<Self>,
7470
spec: UnsealSpec<'_>,
75-
cipher: &Encryption<impl Credentials<Token = ServiceToken>>,
71+
cipher: &ScopedCipherWithCreds,
7672
) -> Result<Vec<Unsealed>, SealError> {
7773
let UnsealSpec {
7874
protected_attributes,
@@ -134,7 +130,7 @@ impl SealedTableEntry {
134130
pub(crate) async fn unseal(
135131
self,
136132
spec: UnsealSpec<'_>,
137-
cipher: &Encryption<impl Credentials<Token = ServiceToken>>,
133+
cipher: &ScopedCipherWithCreds,
138134
) -> Result<Unsealed, SealError> {
139135
let mut vec = Self::unseal_all(vec![self], spec, cipher).await?;
140136

@@ -207,35 +203,34 @@ impl TryFrom<SealedTableEntry> for HashMap<String, AttributeValue> {
207203

208204
#[cfg(test)]
209205
mod tests {
206+
use crate::encrypted_table::{Cipher, ScopedCipherWithCreds};
207+
210208
use super::SealedTableEntry;
211209
use cipherstash_client::{
212-
credentials::{auto_refresh::AutoRefresh, service_credentials::ServiceCredentials},
213-
encryption::Encryption,
214-
ConsoleConfig, ZeroKMS, ZeroKMSConfig,
210+
credentials::auto_refresh::AutoRefresh, ConsoleConfig, ZeroKMS, ZeroKMSConfig
215211
};
216212
use miette::IntoDiagnostic;
217-
use std::borrow::Cow;
218-
219-
type Cipher = Encryption<AutoRefresh<ServiceCredentials>>;
213+
use uuid::Uuid;
214+
use std::{borrow::Cow, sync::Arc};
220215

221216
// FIXME: Use the test cipher from CipherStash Client when that's ready
222-
async fn get_cipher() -> Result<Cipher, Box<dyn std::error::Error>> {
223-
let console_config = ConsoleConfig::builder().with_env().build()?;
217+
async fn get_cipher() -> Result<Arc<Cipher>, Box<dyn std::error::Error>> {
218+
let console_config = ConsoleConfig::builder().with_env().build().into_diagnostic()?;
224219
let zero_kms_config = ZeroKMSConfig::builder()
225220
.decryption_log(true)
226221
.with_env()
227222
.console_config(&console_config)
228-
.build_with_client_key()?;
223+
.build_with_client_key()
224+
.into_diagnostic()?;
229225

230-
let zero_kms_client = ZeroKMS::new_with_client_key(
226+
let cipher = ZeroKMS::new_with_client_key(
231227
&zero_kms_config.base_url(),
232228
AutoRefresh::new(zero_kms_config.credentials()),
233229
zero_kms_config.decryption_log_path().as_deref(),
234230
zero_kms_config.client_key(),
235231
);
236232

237-
let config = zero_kms_client.load_dataset_config().await?;
238-
Ok(Encryption::new(config.index_root_key, zero_kms_client))
233+
Ok(Arc::new(cipher))
239234
}
240235

241236
#[tokio::test]
@@ -245,9 +240,14 @@ mod tests {
245240
sort_key_prefix: "test".to_string(),
246241
};
247242
let cipher = get_cipher().await?;
248-
let results = SealedTableEntry::unseal_all(vec![], spec, &cipher)
243+
// TODO: Temporary obvs
244+
let dataset_id = Uuid::parse_str("93e10481-2692-4d65-a619-37e36a496e64").unwrap();
245+
let scoped_cipher = ScopedCipherWithCreds::init(cipher, dataset_id).await;
246+
247+
let results = SealedTableEntry::unseal_all(vec![], spec, &scoped_cipher)
249248
.await
250249
.into_diagnostic()?;
250+
251251
assert!(results.is_empty());
252252

253253
Ok(())

0 commit comments

Comments
 (0)