cipherstash-client 0.42.0

The official CipherStash SDK
Documentation
use super::{value_selector, EntryWithTokenizedSelector, OrderableTerm};
use crate::encryption::{
    json_indexer::{
        path_values::PathSegment,
        prefix_mac::{PrefixMac, UpdatePrefixMac},
        ste_vec::Selector,
    },
    Plaintext,
};
use serde_json::Value;

#[cfg_attr(test, derive(Debug))]
pub struct PlaintextEntry<'p> {
    path: Vec<PathSegment<'p>>,
    /// The orderable term, present only for number/string nodes — the ONLY
    /// term kind an entry can carry. Exact matching for every node kind is
    /// the value-inclusive selector's job (see [`value_selector`]).
    term: Option<OrderableTerm>,
    /// Retained so [`Self::build_selector`] can derive the value-inclusive
    /// selector from the same (post-`preprocess`) value the term and
    /// ciphertext are built from.
    value: &'p Value,
    plaintext: Plaintext,
    parent_is_array: bool,
}

impl<'p> PlaintextEntry<'p> {
    pub(crate) fn new(path: Vec<PathSegment<'p>>, value: &'p Value) -> Self {
        let parent_is_array = matches!(
            path.last().expect("path cannot be empty"),
            PathSegment::ArrayItem | PathSegment::ArrayPositionItem(_)
        );
        Self {
            path,
            term: OrderableTerm::for_json_value(value),
            value,
            plaintext: Plaintext::from(value.clone()),
            parent_is_array,
        }
    }

    /// Tokenize both selectors this node contributes: the **path selector**
    /// (`SEL(path)`, carried by the path entry alongside the node's ciphertext
    /// and optional orderable term) and the **value selector**
    /// (`SEL(tag ‖ path-token ‖ canonical value)`, carried by the
    /// presence-only value entry). Both come from the same `macca`, so they
    /// live in one keyed selector namespace; the value selector's fixed
    /// leading tag keeps the two input languages disjoint (see
    /// [`value_selector`]).
    pub(crate) fn build_selector<const N: usize, M>(
        self,
        macca: &mut M,
    ) -> EntryWithTokenizedSelector<N>
    where
        for<'u> M: PrefixMac
            + UpdatePrefixMac<String>
            + UpdatePrefixMac<&'u str>
            + UpdatePrefixMac<[u8; N]>,
    {
        let tokenized_selector = Selector::from_path(self.path).tokenize(macca);
        let value_selector =
            value_selector::tokenize_value_selector(&tokenized_selector, self.value, macca);
        EntryWithTokenizedSelector::new(
            tokenized_selector,
            value_selector,
            self.term,
            self.plaintext,
            self.parent_is_array,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        assert_was_finalized_with,
        encryption::json_indexer::{
            path_values::PathSegment,
            ste_vec::{priv_state::ste_plaintext_term::OrderableTerm, tests::TestPrefixMac},
        },
    };

    #[test]
    fn test_entry_build_selector_mac_build() {
        use PathSegment::*;

        // Each build_selector call now finalizes the macca TWICE: once for the
        // path selector and once for the value selector (tag, then the path
        // token — [0;16] under TestPrefixMac — then any canonical value
        // bytes). TestPrefixMac accumulates across finalizes, so the expected
        // update sequence covers both.
        fn build_selector(path_segments: Vec<PathSegment<'_>>) -> TestPrefixMac {
            let mut macca: TestPrefixMac = Default::default();
            let value = Value::Null;
            let _ = PlaintextEntry::new(path_segments, &value).build_selector::<16, _>(&mut macca);
            macca
        }

        assert_was_finalized_with!(build_selector(vec![Root]), "$", "NULL00", [0u8; 16]);
        assert_was_finalized_with!(
            build_selector(vec![Root, ObjectItem("name")]),
            "$['name']",
            "NULL00",
            [0u8; 16]
        );
        assert_was_finalized_with!(
            build_selector(vec![Root, ObjectItem("name"), ArrayWildcardItem]),
            "$['name'][*]",
            "NULL00",
            [0u8; 16]
        );
        assert_was_finalized_with!(
            build_selector(vec![Root, ArrayWildcardItem, ObjectItem("name")]),
            "$[*]['name']",
            "NULL00",
            [0u8; 16]
        );
    }

    #[test]
    fn test_value_selector_includes_canonical_value_bytes() {
        use PathSegment::*;

        let mut macca: TestPrefixMac = Default::default();
        let value = Value::String("secret".into());
        let _ = PlaintextEntry::new(vec![Root, ObjectItem("name")], &value)
            .build_selector::<16, _>(&mut macca);
        assert_was_finalized_with!(macca, "$['name']", "STRING", [0u8; 16], "secret");
    }

    #[test]
    fn test_entry_build_selector_term() {
        use PathSegment::*;

        fn build_selector(value: Value) -> Option<OrderableTerm> {
            let mut macca: TestPrefixMac = Default::default();
            PlaintextEntry::new(vec![Root, ObjectItem("name")], &value)
                .build_selector::<16, _>(&mut macca)
                .term
        }

        // Only numbers and strings carry a term (the orderable). Every other
        // kind is term-less: its value information lives in the value
        // selector alone.
        assert_eq!(build_selector(Value::Null), None);
        assert_eq!(build_selector(Value::Object(Default::default())), None);
        assert_eq!(build_selector(Value::Array(Default::default())), None);
        assert_eq!(build_selector(Value::Bool(true)), None);
        assert_eq!(build_selector(Value::Bool(false)), None);
        assert_eq!(
            build_selector(Value::String(String::from("secret"))),
            Some(OrderableTerm::String(String::from("secret")))
        );
    }
}