cipherstash-client 0.42.0

The official CipherStash SDK
Documentation
use serde::{Deserialize, Serialize};

use crate::{
    ejsonpath::{IndexArg, Selector},
    encryption::json_indexer::{
        path_values::PathSegment,
        prefix_mac::{PrefixMac, UpdatePrefixMac},
    },
};

impl Selector {
    pub(crate) fn from_path(path: Vec<PathSegment>) -> Self {
        path.into_iter()
            .fold(Selector::Root, |acc, segment| match segment {
                PathSegment::Root => acc,
                PathSegment::ArrayWildcardItem => {
                    Selector::Index(Box::new(acc), IndexArg::Wildcard)
                }
                PathSegment::ArrayItem => Selector::Index(Box::new(acc), IndexArg::Item),
                PathSegment::ArrayPositionItem(n) => {
                    Selector::Index(Box::new(acc), IndexArg::Number(n))
                }
                PathSegment::ObjectItem(key) => {
                    Selector::Index(Box::new(acc), IndexArg::Field(key.to_owned()))
                }
            })
    }

    pub(crate) fn tokenize<const N: usize, M>(self, macca: &mut M) -> TokenizedSelector<N>
    where
        M: PrefixMac + UpdatePrefixMac<String>,
    {
        macca.update(self.canonicalise().to_string());
        TokenizedSelector(macca.finalize_reset())
    }
}

/// Represents a tokenized selector for a value in a JSON object.
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
#[serde(transparent)]
pub struct TokenizedSelector<const N: usize>(
    #[serde(
        serialize_with = "hex::serde::serialize",
        deserialize_with = "hex_decode_array"
    )]
    pub(crate) [u8; N],
);

impl<const N: usize> TokenizedSelector<N> {
    pub fn as_bytes(&self) -> [u8; N] {
        self.0
    }

    /// The AEAD nonce for this selector's entry under the SteVec envelope
    /// wire format: the first 12 bytes of the tokenized selector.
    ///
    /// Every entry of a document encrypts under the document's single data
    /// key, so nonce distinctness is what prevents equal plaintexts at
    /// different (path, value) positions from producing equal ciphertexts —
    /// and because the nonce is re-derivable from the entry's stored `s`,
    /// nothing extra is stored. Ciphertext equality can then only occur where
    /// selector equality already reveals the same fact, which is public by
    /// design (containment matches on selector presence).
    ///
    /// A 12-byte nonce needs at least 12 selector bytes; `N < 12` is rejected
    /// at compile time rather than panicking at runtime. Production selectors
    /// are 16 bytes.
    pub fn aead_nonce(&self) -> [u8; 12] {
        const {
            assert!(
                N >= 12,
                "TokenizedSelector<N> needs N >= 12 for a 12-byte AEAD nonce"
            )
        };
        let mut nonce = [0u8; 12];
        nonce.copy_from_slice(&self.0[..12]);
        nonce
    }
}

fn hex_decode_array<'de, const N: usize, D>(deserializer: D) -> Result<[u8; N], D::Error>
where
    D: serde::Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    let mut out: [u8; N] = [0; N];
    hex::decode_to_slice(s, &mut out).map_err(serde::de::Error::custom)?;
    Ok(out)
}

#[cfg(test)]
mod test {
    use super::super::tests::TestPrefixMac;
    use crate::{assert_was_finalized_with, encryption::json_indexer::ste_vec::selector::Selector};

    #[test]
    fn test_tokenize() {
        let mut macca: TestPrefixMac = Default::default();
        let selector = Selector::parse("$.foo.bar[*]").unwrap();
        let _ = selector.tokenize::<16, _>(&mut macca);
        assert_was_finalized_with!(macca, "$['foo']['bar'][*]");

        let mut macca: TestPrefixMac = Default::default();
        let selector = Selector::parse("$.foo.bar").unwrap();
        let _ = selector.tokenize::<16, _>(&mut macca);
        assert_was_finalized_with!(macca, "$['foo']['bar']");
    }
}