mod encrypted_entry;
mod encrypted_term;
pub(super) mod priv_state;
mod selector;
mod ste_plaintext_vec;
use crate::{ejsonpath::Selector, encryption::EncryptionError};
use crate::{
encryption::Plaintext,
zerokms::{self, DataKeyWithTag, KeyHeader, RecordWithNonce},
};
use aes_gcm_siv::{aead::Payload, Nonce};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub use encrypted_entry::EncryptedEntry;
pub use ste_plaintext_vec::StePlaintextVec;
pub use encrypted_term::EncryptedSteVecTerm;
pub use selector::TokenizedSelector;
pub const VALUE_ENTRY_SENTINEL: [u8; 3] = [1, 0x7f, 1];
pub fn is_value_entry_sentinel(bytes: &[u8]) -> bool {
bytes == VALUE_ENTRY_SENTINEL
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SteVec<const N: usize> {
pub(super) header: KeyHeader,
pub(super) entries: Vec<EncryptedEntry<N>>,
}
impl<const N: usize> SteVec<N> {
pub fn header(&self) -> &KeyHeader {
&self.header
}
pub fn entries(&self) -> &[EncryptedEntry<N>] {
&self.entries
}
}
impl SteVec<16> {
pub fn into_root_record_with_nonce(self) -> Result<RecordWithNonce, EncryptionError> {
let root = self.entries.into_iter().next().ok_or_else(|| {
EncryptionError::InvalidValue("SteVec missing root record".to_string())
})?;
Ok(self
.header
.record_with_selector(root.ciphertext, root.tokenized_selector.as_bytes()))
}
pub fn root_nonce(&self) -> Result<[u8; 12], EncryptionError> {
self.entries
.first()
.map(|entry| entry.tokenized_selector.aead_nonce())
.ok_or_else(|| EncryptionError::InvalidValue("SteVec missing root record".to_string()))
}
pub fn root_selector(&self) -> Result<[u8; 16], EncryptionError> {
self.entries
.first()
.map(|entry| entry.tokenized_selector.as_bytes())
.ok_or_else(|| EncryptionError::InvalidValue("SteVec missing root record".to_string()))
}
}
impl IntoIterator for SteVec<16> {
type Item = EncryptedEntry<16>;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.entries.into_iter()
}
}
pub struct SteVecPendingEncryption<const N: usize>(Vec<EntryWithEncryptedTerm<N>>);
impl SteVecPendingEncryption<16> {
pub fn encrypt(
self,
key: DataKeyWithTag,
descriptor: &str,
keyset_id: Option<Uuid>,
) -> Result<SteVec<16>, zerokms::Error> {
let header = KeyHeader::new(&key, descriptor, keyset_id);
let entries = self
.0
.into_iter()
.map(|entry| entry.build_final(&key, descriptor))
.try_collect()?;
Ok(SteVec { header, entries })
}
}
impl<const N: usize> SteVecPendingEncryption<N> {
pub fn into_query(self) -> SteQueryVec<N> {
SteQueryVec::from(self)
}
}
enum EntryPlaintext {
Node(Plaintext),
ValueSentinel,
}
struct EntryWithEncryptedTerm<const N: usize> {
tokenized_selector: TokenizedSelector<N>,
term: Option<EncryptedSteVecTerm>,
plaintext: EntryPlaintext,
parent_is_array: bool,
}
impl EntryWithEncryptedTerm<16> {
fn build_final(
self,
key: &DataKeyWithTag,
descriptor: &str,
) -> Result<EncryptedEntry<16>, zerokms::Error> {
let bytes = match &self.plaintext {
EntryPlaintext::Node(plaintext) => plaintext.to_vec(),
EntryPlaintext::ValueSentinel => VALUE_ENTRY_SENTINEL.to_vec(),
};
let selector = self.tokenized_selector.as_bytes();
let mut aad = zerokms::build_aad(descriptor, &key.tag);
aad.extend_from_slice(&selector);
let nonce = Nonce::from_slice(&selector[..12]);
let ciphertext = zerokms::encrypt_aead(
key.key(),
nonce,
Payload {
msg: &bytes,
aad: &aad,
},
)?;
Ok(EncryptedEntry::new(
self.tokenized_selector,
self.term,
ciphertext,
self.parent_is_array,
))
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct QueryEntry<const N: usize>(
pub(super) TokenizedSelector<N>,
pub(super) Option<EncryptedSteVecTerm>,
);
impl<const N: usize> From<EntryWithEncryptedTerm<N>> for QueryEntry<N> {
fn from(entry: EntryWithEncryptedTerm<N>) -> Self {
Self(entry.tokenized_selector, entry.term)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SteQueryVec<const N: usize>(pub(super) Vec<QueryEntry<N>>);
impl<const N: usize> From<SteVecPendingEncryption<N>> for SteQueryVec<N> {
fn from(pending: SteVecPendingEncryption<N>) -> Self {
Self(pending.0.into_iter().map(QueryEntry::from).collect())
}
}
#[cfg(feature = "tokio")]
impl<const N: usize> SteQueryVec<N> {
pub(crate) fn from_value_selector(selector: TokenizedSelector<N>) -> Self {
Self(vec![QueryEntry(selector, None)])
}
}
impl<const N: usize> QueryEntry<N> {
pub fn into_parts(self) -> (TokenizedSelector<N>, Option<EncryptedSteVecTerm>) {
(self.0, self.1)
}
}
impl<const N: usize> IntoIterator for SteQueryVec<N> {
type Item = QueryEntry<N>;
type IntoIter = std::vec::IntoIter<QueryEntry<N>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[cfg(test)]
mod tests {
use crate::encryption::json_indexer::{
prefix_mac::{PrefixMac, UpdatePrefixMac},
ste_vec::{encrypted_term::EncryptedSteVecTerm, EncryptedEntry, TokenizedSelector},
};
use std::fmt::Debug;
#[macro_export]
macro_rules! boxed {
($($literal:expr),*) => {
&[$(Box::new($literal)),*]
};
}
#[macro_export]
macro_rules! assert_was_finalized_with {
($macca:expr, $($term:expr),*) => {
$macca.assert_was_finalized_with($crate::boxed!($($term),*))
};
}
#[derive(Default)]
pub struct TestPrefixMac {
pub pending_terms: Vec<Box<dyn Debug>>,
pub finalized_terms: Vec<Box<dyn Debug>>,
}
impl TestPrefixMac {
pub fn assert_was_finalized_with(&self, terms: &[Box<dyn Debug>]) {
let x = self
.finalized_terms
.iter()
.map(|term| format!("{term:?}"))
.collect::<Vec<_>>();
let y = terms
.iter()
.map(|term| format!("{term:?}"))
.collect::<Vec<_>>();
assert_eq!(x, y, "Expected MAC updates to be {y:#?}, got {x:#?}");
}
}
impl PrefixMac for TestPrefixMac {
fn finalize_reset<const N: usize>(&mut self) -> [u8; N] {
self.finalized_terms.append(&mut self.pending_terms);
[0; N]
}
}
impl UpdatePrefixMac<String> for TestPrefixMac {
fn update(&mut self, value: String) {
self.pending_terms.push(Box::new(value));
}
}
impl UpdatePrefixMac<&str> for TestPrefixMac {
fn update(&mut self, value: &str) {
self.update(value.to_string());
}
}
impl UpdatePrefixMac<[u8; 16]> for TestPrefixMac {
fn update(&mut self, value: [u8; 16]) {
self.pending_terms.push(Box::new(value));
}
}
fn ciphertext() -> Vec<u8> {
vec![1; 32]
}
#[test]
fn test_serde_encrypted_entry() {
use cllw_ore::OpeCllw8VariableV1;
let entry = EncryptedEntry::new(
TokenizedSelector([1; 16]),
Some(EncryptedSteVecTerm::Compat {
op: OpeCllw8VariableV1::from_bytes(vec![2; 16]),
}),
ciphertext(),
false,
);
let serialized = serde_json::to_string(&entry).unwrap();
assert_eq!(
serde_json::from_str::<EncryptedEntry<16>>(&serialized).unwrap(),
entry
);
}
#[test]
fn test_serde_encrypted_entry_term_less() {
let entry = EncryptedEntry::new(TokenizedSelector([3; 16]), None, ciphertext(), false);
let serialized = serde_json::to_string(&entry).unwrap();
assert!(
!serialized.contains("\"hm\""),
"no MAC term key: {serialized}"
);
assert!(
!serialized.contains("\"op\""),
"no orderable key: {serialized}"
);
assert!(
!serialized.contains("null"),
"term must be absent, not null: {serialized}"
);
assert_eq!(
serde_json::from_str::<EncryptedEntry<16>>(&serialized).unwrap(),
entry
);
}
}