mod builder;
pub mod compound_indexer;
mod errors;
mod indexer;
mod json_indexer;
mod jsonb_indexer;
mod match_indexer;
mod ore_indexer;
mod plaintext;
mod scoped_cipher;
mod text;
mod unique_indexer;
use crate::zerokms::{EncryptPayload, EncryptedRecord, IndexKey, ZeroKMSWithClientKey};
use compound_indexer::{
accumulator::Accumulator, composable_plaintext::ComposablePlaintext, ComposableIndex,
CompoundIndex,
};
use serde::Serialize;
use stack_auth::AuthStrategy;
use zerokms_protocol::cipherstash_config::{column::IndexType, operator::Operator, ColumnType};
pub use builder::{Encryptable, Encrypted, QueryBuilder, Queryable, StorageBuilder};
pub use errors::{EncryptionError, TypeParseError};
pub use indexer::IndexerInit;
pub use indexer::QueryOp;
pub use json_indexer::{
EncryptedEntry, EncryptedSteVecTerm, JsonIndexer, JsonIndexerOptions, SteQueryVec, SteVec,
TokenizedSelector,
};
pub use jsonb_indexer::*;
pub use match_indexer::MatchIndexer;
pub use ore_indexer::OreIndexer;
pub use plaintext::{
BytesWithDescriptor, Plaintext, PlaintextNullVariant, PlaintextTarget, TryFromPlaintext,
};
pub use scoped_cipher::{DecryptOptions, ScopedCipher};
pub use unique_indexer::UniqueIndexer;
pub struct Encryption<C = stack_auth::AutoStrategy> {
index_key: IndexKey,
client: ZeroKMSWithClientKey<C>,
}
impl<Creds> Encryption<Creds>
where
Creds: Send + Sync + 'static,
for<'a> &'a Creds: AuthStrategy,
{
pub fn new(root_key: IndexKey, client: ZeroKMSWithClientKey<Creds>) -> Self {
Self {
index_key: root_key,
client,
}
}
pub async fn encrypt<T: Into<BytesWithDescriptor>>(
&self,
items: impl IntoIterator<Item = T>,
) -> Result<Vec<EncryptedRecord>, EncryptionError> {
let payloads: Vec<BytesWithDescriptor> = items.into_iter().map(Into::into).collect();
Ok(self
.client
.encrypt(payloads.iter().map(EncryptPayload::from), None)
.await?)
}
pub async fn encrypt_single(
&self,
target: PlaintextTarget,
) -> Result<EncryptedRecord, EncryptionError> {
let payload = target.payload();
let ciphertext = self
.client
.encrypt_single(EncryptPayload::from(&payload), None)
.await?;
Ok(ciphertext)
}
pub async fn decrypt_single(
&self,
ciphertext: EncryptedRecord,
) -> Result<Plaintext, EncryptionError> {
let decrypted = self
.client
.decrypt_single(ciphertext, None, None, None)
.await?;
Ok(Plaintext::from_slice(&decrypted)?)
}
pub async fn maybe_decrypt_hex<I, C>(
&self,
ciphertexts: I,
) -> Result<Vec<Option<Plaintext>>, EncryptionError>
where
I: IntoIterator<Item = Option<C>>,
C: AsRef<[u8]>,
{
let records: (Vec<bool>, Vec<EncryptedRecord>) =
ciphertexts
.into_iter()
.fold(Default::default(), |(mut all, mut target), hex_str| {
if let Some(rec) = hex_str
.map(hex::decode)
.transpose()
.unwrap_or(None)
.and_then(|bytes| EncryptedRecord::from_cbor_bytes(&bytes).ok())
{
target.push(rec);
all.push(true);
} else {
all.push(false);
}
(all, target)
});
let decrypted = self.client.decrypt(records.1, None, None, None).await?;
let mut results = decrypted
.into_iter()
.map(|bytes| Plaintext::from_slice(&bytes));
Ok(records
.0
.iter()
.map(|valid| {
if *valid {
results.next().transpose()
} else {
Ok(None)
}
})
.collect::<Result<Vec<Option<Plaintext>>, _>>()?)
}
pub async fn decrypt(
&self,
ciphertexts: impl IntoIterator<Item = EncryptedRecord>,
) -> Result<Vec<Plaintext>, EncryptionError> {
let decrypted = self.client.decrypt(ciphertexts, None, None, None).await?;
Ok(decrypted
.iter()
.map(|bytes| Plaintext::from_slice(bytes))
.collect::<Result<Vec<Plaintext>, _>>()?)
}
pub fn index(
&self,
value: &Plaintext,
index_type: &IndexType,
) -> Result<IndexTerm, EncryptionError> {
match index_type {
IndexType::Ore => OreIndexer::try_init(index_type)?.encrypt(value, &self.index_key),
IndexType::Unique { .. } => {
UniqueIndexer::try_init(index_type)?.encrypt(value, &self.index_key)
}
IndexType::Match { .. } => {
MatchIndexer::try_init(index_type)?.encrypt(value, &self.index_key)
}
IndexType::SteVec { .. } => Err(EncryptionError::IndexingError(
"SteVec not supported via direct Encryption API: use Pipeline or Builder instead"
.to_string(),
)),
}
}
pub fn index_all(&self, target: &PlaintextTarget) -> Result<Vec<IndexTerm>, EncryptionError> {
let mut indexes = vec![];
for index in target.config().indexes.iter() {
indexes.push(self.index(&target.plaintext, &index.index_type)?);
}
Ok(indexes)
}
pub fn compound_index(
&self,
index: &CompoundIndex<impl ComposableIndex + Send>,
input: impl Into<ComposablePlaintext>,
salt: Option<impl AsRef<[u8]>>,
term_length: usize,
) -> Result<IndexTerm, EncryptionError> {
let accumulator = salt
.map(|s| Accumulator::from_salt(s.as_ref()))
.unwrap_or_else(Accumulator::empty);
let term = index
.compose_index(&self.index_key, input.into(), accumulator)?
.truncate(term_length)?;
Ok(term.into())
}
pub fn compound_query(
&self,
index: &CompoundIndex<impl ComposableIndex + Send>,
input: impl Into<ComposablePlaintext>,
salt: Option<impl AsRef<[u8]>>,
term_length: usize,
) -> Result<IndexTerm, EncryptionError> {
let accumulator = salt
.map(|s| Accumulator::from_salt(s.as_ref()))
.unwrap_or_else(Accumulator::empty);
let term = index
.compose_query(&self.index_key, input.into(), accumulator)?
.exactly_one()?
.truncate(term_length)?;
Ok(term.try_into()?)
}
pub fn index_for_operator(
&self,
value: &Plaintext,
index_type: &IndexType,
operator: &Operator,
cast_type: &ColumnType,
) -> Result<IndexTerm, EncryptionError> {
if !index_type.supports(operator, cast_type) {
return Err(EncryptionError::IndexingError(format!(
"Unsupported operator ({}) for Index {:?}",
operator.as_str(),
index_type
)));
}
match index_type {
IndexType::Ore => OreIndexer.encrypt_for_query(value, &self.index_key),
IndexType::Unique { .. } => self.index(value, index_type),
IndexType::Match { .. } => self.index(value, index_type),
IndexType::SteVec { .. } => self.index(value, index_type),
}
}
}
#[derive(Debug, Eq, PartialEq, Serialize)]
pub enum IndexTerm {
Binary(Vec<u8>),
BinaryVec(Vec<Vec<u8>>),
BitMap(Vec<u16>),
OreFull(Vec<u8>),
OreArray(Vec<Vec<u8>>),
OreLeft(Vec<u8>),
SteVecSelector(TokenizedSelector<16>),
SteVecTerm(EncryptedSteVecTerm),
SteQueryVec(SteQueryVec<16>),
Null,
}
impl IndexTerm {
pub fn as_binary(self) -> Option<Vec<u8>> {
if let Self::Binary(x) = self {
Some(x)
} else {
None
}
}
pub fn as_binary_vec(self) -> Option<Vec<Vec<u8>>> {
match self {
Self::BinaryVec(x) => Some(x),
Self::Binary(x) => Some(vec![x]),
_ => None,
}
}
}