cipherstash_dynamodb/crypto/
mod.rs

1mod attrs;
2mod b64_encode;
3mod sealed;
4mod sealer;
5mod unsealed;
6use crate::{
7    traits::{PrimaryKeyError, PrimaryKeyParts, ReadConversionError, WriteConversionError},
8    Identifiable, IndexType, PrimaryKey,
9};
10use cipherstash_client::{
11    credentials::{service_credentials::ServiceToken, Credentials},
12    encryption::{
13        compound_indexer::{CompoundIndex, ExactIndex},
14        Encryption, EncryptionError, Plaintext, TypeParseError,
15    },
16    zerokms::Error as ZeroKmsError,
17};
18use miette::Diagnostic;
19use std::borrow::Cow;
20use thiserror::Error;
21
22// Re-exports
23pub use b64_encode::*;
24pub use sealed::{SealedTableEntry, UnsealSpec};
25pub use sealer::{Sealer, UnsealedIndex};
26pub use unsealed::Unsealed;
27
28/// In order to stop indexes from exploding with indexes on large strings, cap the number of terms
29/// generated per index. Since there is a fixed number of terms per index it is also possible to
30/// delete all index terms for a particular record.
31const MAX_TERMS_PER_INDEX: usize = 25;
32
33#[derive(Debug, Error, Diagnostic)]
34pub enum SealError {
35    #[error("Error when creating primary key: {0}")]
36    PrimaryKeyError(#[from] PrimaryKeyError),
37    #[error("ReadConversionError: {0}")]
38    ReadConversionError(#[from] ReadConversionError),
39    #[error("WriteConversionError: {0}")]
40    WriteConversionError(#[from] WriteConversionError),
41    #[error("TypeParseError: {0}")]
42    TypeParseError(#[from] TypeParseError),
43    #[error("Missing attribute: {0}")]
44    MissingAttribute(String),
45    #[error("Invalid ciphertext value: {0}")]
46    InvalidCiphertext(String),
47    #[error("Assertion failed: {0}")]
48    AssertionFailed(String),
49
50    // Note that we don't expose the specific error type here
51    // so as to avoid leaking any information
52    #[error(transparent)]
53    EncryptionError(#[from] EncryptionError),
54
55    #[error(transparent)]
56    ZeroKmsError(#[from] ZeroKmsError),
57}
58
59#[derive(Error, Debug)]
60pub enum CryptoError {
61    #[error("EncryptionError: {0}")]
62    EncryptionError(#[from] EncryptionError),
63    #[error("ReadConversionError: {0}")]
64    ReadConversionError(#[from] ReadConversionError),
65    #[error("{0}")]
66    Other(String),
67}
68
69pub fn format_term_key(
70    sort_key: &str,
71    index_name: &str,
72    index_type: IndexType,
73    counter: usize,
74) -> String {
75    format!("{sort_key}#{index_name}#{index_type}#{counter}")
76}
77
78/// Get all the term index keys for a particular sort key and index definitions
79///
80/// This is used to delete any index items that shouldn't exist during either an update or
81pub(crate) fn all_index_keys<'a>(
82    sort_key: &str,
83    protected_indexes: impl AsRef<[(Cow<'a, str>, IndexType)]>,
84) -> Vec<String> {
85    protected_indexes
86        .as_ref()
87        .iter()
88        .flat_map(|(index_name, index_type)| {
89            (0..)
90                .take(MAX_TERMS_PER_INDEX)
91                .map(|i| format_term_key(sort_key, index_name, *index_type, i))
92                .collect::<Vec<String>>()
93        })
94        .collect()
95}
96
97/// Use a CipherStash [`ExactIndex`] to take the HMAC of a string with a provided salt
98///
99/// This value is used for term index keys and "encrypted" partition / sort keys
100pub fn hmac(
101    value: &str,
102    salt: Option<&str>,
103    cipher: &Encryption<impl Credentials<Token = ServiceToken>>,
104) -> Result<Vec<u8>, EncryptionError> {
105    let plaintext = Plaintext::Utf8Str(Some(value.to_string()));
106    let index = CompoundIndex::new(ExactIndex::new(vec![]));
107
108    cipher
109        .compound_index(
110            &index,
111            plaintext,
112            // passing None here results in no terms so pass an empty string
113            Some(salt.unwrap_or("")),
114            32,
115        )?
116        .as_binary()
117        .ok_or(EncryptionError::IndexingError(
118            "Invalid term type".to_string(),
119        ))
120}
121
122// Contains all the necessary information to encrypt the primary key pair
123#[derive(Clone)]
124pub struct PreparedPrimaryKey {
125    pub primary_key_parts: PrimaryKeyParts,
126    pub is_pk_encrypted: bool,
127    pub is_sk_encrypted: bool,
128}
129
130impl PreparedPrimaryKey {
131    pub fn new<R>(k: impl Into<R::PrimaryKey>) -> Self
132    where
133        R: Identifiable,
134    {
135        let primary_key_parts = k
136            .into()
137            .into_parts(&R::type_name(), R::sort_key_prefix().as_deref());
138
139        Self::new_from_parts::<R>(primary_key_parts)
140    }
141
142    pub fn new_from_parts<R>(primary_key_parts: PrimaryKeyParts) -> Self
143    where
144        R: Identifiable,
145    {
146        Self {
147            primary_key_parts,
148            is_pk_encrypted: R::is_pk_encrypted(),
149            is_sk_encrypted: R::is_sk_encrypted(),
150        }
151    }
152}