mod pipeline;
mod query;
use std::borrow::Cow;
use super::{
indexer::Indexes,
json_indexer::{SteVec, SteVecPendingEncryption},
EncryptionError, IndexTerm,
};
use crate::zerokms::{Decryptable, EncryptedRecord, IndexKey, RetrieveKeyPayload};
use uuid::Uuid;
#[cfg(feature = "tokio")]
use super::BytesWithDescriptor;
#[cfg(feature = "tokio")]
use crate::zerokms::DataKeyWithTag;
pub use pipeline::Encryptable;
pub use query::{QueryBuilder, Queryable};
use zerokms_protocol::Context;
pub struct StorageBuilder<'k, T> {
source: T,
key: &'k IndexKey,
index_terms: Vec<IndexTerm>,
descriptor: Option<String>,
context: Cow<'k, [Context]>,
keyset_id: Uuid,
ste_vec: Option<SteVecPendingEncryption<16>>,
}
impl<'k, T> StorageBuilder<'k, T> {
pub fn new(source: T, key: &'k IndexKey, keyset_id: Uuid) -> Self {
Self {
source,
key,
keyset_id,
index_terms: Default::default(),
descriptor: None,
context: Default::default(),
ste_vec: None,
}
}
pub fn index_with<I>(self, index: I) -> Result<Self, EncryptionError>
where
for<'t> I: Indexes<'t, T>,
{
index.index(self)
}
pub fn with_descriptor(mut self, descriptor: impl Into<String>) -> Self {
self.descriptor = Some(descriptor.into());
self
}
pub fn with_context(mut self, context: Context) -> Self {
self.context.to_mut().push(context);
self
}
pub fn set_context(mut self, context: Cow<'k, [Context]>) -> Self {
self.context = context;
self
}
pub(crate) fn plaintext(&self) -> &T {
&self.source
}
pub(crate) fn index_key(&self) -> &IndexKey {
self.key
}
pub(crate) fn set_ste_vec(&mut self, ste_vec: SteVecPendingEncryption<16>) {
self.ste_vec = Some(ste_vec);
}
pub(crate) fn add_index_term(&mut self, index_term: IndexTerm) {
self.index_terms.push(index_term);
}
pub fn keyset_id(&self) -> Uuid {
self.keyset_id
}
pub fn descriptor(&self) -> &str {
if self.ste_vec.is_none() {
self.descriptor.as_deref().unwrap_or("")
} else {
""
}
}
#[cfg(feature = "tokio")]
pub(crate) fn build_for_encryption(self) -> PendingEncryption<'k>
where
BytesWithDescriptor: From<(T, String)>,
{
let encryptable = if let Some(ste_vec) = self.ste_vec {
EncryptableInner::SteVec(ste_vec)
} else {
let bytes =
BytesWithDescriptor::from((self.source, self.descriptor.unwrap_or_default()));
EncryptableInner::Plaintext(bytes, self.index_terms, self.context)
};
PendingEncryption {
encryptable,
keyset_id: self.keyset_id,
}
}
pub fn to_index_terms(self) -> Vec<IndexTerm> {
self.index_terms
}
}
#[cfg(feature = "tokio")]
enum EncryptableInner<'a> {
Plaintext(BytesWithDescriptor, Vec<IndexTerm>, Cow<'a, [Context]>),
SteVec(SteVecPendingEncryption<16>), }
#[cfg(feature = "tokio")]
pub struct PendingEncryption<'a> {
encryptable: EncryptableInner<'a>,
keyset_id: Uuid,
}
#[cfg(feature = "tokio")]
impl<'a> PendingEncryption<'a> {
pub fn encrypt(self, datakey_with_tag: DataKeyWithTag) -> Result<Encrypted, EncryptionError> {
match self.encryptable {
EncryptableInner::Plaintext(bytes, index_terms, context) => {
use crate::zerokms::{self, EncryptPayload};
let payload = EncryptPayload::from(&bytes).set_context(context.clone());
let keyset_id = Some(self.keyset_id);
zerokms::encrypt(payload, datakey_with_tag, keyset_id)
.map(|encrypted| Encrypted::Record(encrypted, index_terms))
.map_err(EncryptionError::from)
}
EncryptableInner::SteVec(ste_vec_pending_encryption) => ste_vec_pending_encryption
.encrypt(datakey_with_tag)
.map(Encrypted::SteVec)
.map_err(EncryptionError::from),
}
}
}
#[derive(Debug)]
pub enum Encrypted {
SteVec(SteVec<16>),
Record(EncryptedRecord, Vec<IndexTerm>),
}
impl Decryptable for Encrypted {
type Error = EncryptionError;
fn into_encrypted_record(self) -> Result<EncryptedRecord, Self::Error> {
match self {
Encrypted::Record(record, _) => Ok(record),
Encrypted::SteVec(stevec) => stevec.into_root_ciphertext(),
}
}
fn keyset_id(&self) -> Option<Uuid> {
match self {
Encrypted::Record(encrypted_record, ..) => encrypted_record.keyset_id,
Encrypted::SteVec(..) => None,
}
}
fn retrieve_key_payload(&self) -> Result<RetrieveKeyPayload<'_>, Self::Error> {
match self {
Encrypted::Record(encrypted_record, ..) => encrypted_record
.retrieve_key_payload()
.map_err(|_| EncryptionError::Infallible),
Encrypted::SteVec(stevec) => stevec.root_ciphertext().and_then(|root| {
root.retrieve_key_payload()
.map_err(|_| EncryptionError::Infallible)
}),
}
}
}
#[cfg(all(test, feature = "tokio"))]
mod tests {
use super::*;
use crate::encryption::{JsonIndexer, MatchIndexer, Plaintext, UniqueIndexer};
use crate::zerokms::{self, DataKeyWithTag, IndexKey};
use serde_json::json;
impl EncryptableInner<'_> {
fn bytes_with_descriptor(&self) -> Option<&BytesWithDescriptor> {
match self {
EncryptableInner::Plaintext(bytes, ..) => Some(bytes),
_ => None,
}
}
fn index_terms(&self) -> Option<&[IndexTerm]> {
match self {
EncryptableInner::Plaintext(_, terms, ..) => Some(terms),
_ => None,
}
}
}
#[test]
fn test_single_standard_index() -> Result<(), EncryptionError> {
let plaintext = Plaintext::from("hello world");
let index_key = IndexKey::from([0; 32]);
let keyset_id = Uuid::new_v4();
let for_encryption = StorageBuilder::new(plaintext, &index_key, keyset_id)
.index_with(UniqueIndexer::default())?
.with_descriptor("descriptor")
.build_for_encryption();
assert!(matches!(
for_encryption.encryptable,
EncryptableInner::Plaintext(..)
));
assert_eq!(for_encryption.encryptable.index_terms().unwrap().len(), 1);
assert!(for_encryption.encryptable.bytes_with_descriptor().is_some());
assert_eq!(for_encryption.keyset_id, keyset_id);
Ok(())
}
#[test]
fn test_multiple_standard_indexes() -> Result<(), EncryptionError> {
let plaintext = Plaintext::from("hello world");
let index_key = IndexKey::from([0; 32]);
let keyset_id = Uuid::new_v4();
let for_encryption = StorageBuilder::new(plaintext, &index_key, keyset_id)
.index_with(UniqueIndexer::default())?
.index_with(MatchIndexer::default())?
.with_descriptor("descriptor")
.build_for_encryption();
assert!(matches!(
for_encryption.encryptable,
EncryptableInner::Plaintext(..)
));
assert_eq!(for_encryption.encryptable.index_terms().unwrap().len(), 2);
assert_eq!(for_encryption.keyset_id, keyset_id);
Ok(())
}
#[test]
fn test_ste_vec_index() -> Result<(), EncryptionError> {
let plaintext = Plaintext::from(json!({"hello": {}}));
let index_key = IndexKey::from([0; 32]);
let keyset_id = Uuid::new_v4();
let for_encryption = StorageBuilder::new(plaintext, &index_key, keyset_id)
.index_with(JsonIndexer::default())?
.with_descriptor("descriptor")
.build_for_encryption();
assert!(matches!(
for_encryption.encryptable,
EncryptableInner::SteVec(_)
));
assert_eq!(for_encryption.encryptable.index_terms(), None);
assert!(for_encryption.encryptable.bytes_with_descriptor().is_none());
assert_eq!(for_encryption.keyset_id, keyset_id);
Ok(())
}
#[test]
fn test_standard_encrypt() -> Result<(), EncryptionError> {
let plaintext = Plaintext::from("hello world");
let index_key = IndexKey::from([0; 32]);
let datakey_with_tag = DataKeyWithTag::default();
let keyset_id = Uuid::new_v4();
let encrypted = StorageBuilder::new(plaintext, &index_key, keyset_id)
.index_with(UniqueIndexer::default())?
.with_descriptor("descriptor")
.build_for_encryption()
.encrypt(datakey_with_tag)?;
assert!(matches!(encrypted, Encrypted::Record(_, _)));
let result = zerokms::decrypt(encrypted, DataKeyWithTag::default().key).unwrap();
let result = Plaintext::from_slice(&result)?;
assert_eq!(result, *"hello world");
Ok(())
}
#[test]
fn test_standard_encrypt_with_context() -> Result<(), EncryptionError> {
let plaintext = Plaintext::from("hello world");
let index_key = IndexKey::from([0; 32]);
let datakey_with_tag = DataKeyWithTag::default();
let keyset_id = Uuid::new_v4();
let encrypted = StorageBuilder::new(plaintext, &index_key, keyset_id)
.index_with(UniqueIndexer::default())?
.with_descriptor("descriptor")
.with_context(Context::new_tag("sensitive"))
.with_context(Context::new_identity_claim("sub"))
.build_for_encryption()
.encrypt(datakey_with_tag)?;
assert!(matches!(encrypted, Encrypted::Record(_, _)));
Ok(())
}
#[test]
fn test_ste_vec_encrypt() -> Result<(), EncryptionError> {
let plaintext = Plaintext::from(json!({"hello": {}}));
let index_key = IndexKey::from([0; 32]);
let datakey_with_tag = DataKeyWithTag::default();
let keyset_id = Uuid::new_v4();
let encrypted = StorageBuilder::new(plaintext, &index_key, keyset_id)
.index_with(JsonIndexer::default())?
.with_descriptor("descriptor")
.build_for_encryption()
.encrypt(datakey_with_tag)?;
assert!(matches!(encrypted, Encrypted::SteVec(_)));
Ok(())
}
}