cipherstash_dynamodb/crypto/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
mod b64_encode;
mod sealed;
mod sealer;
mod unsealed;

use std::borrow::Cow;

use crate::{
    traits::{PrimaryKeyError, PrimaryKeyParts, ReadConversionError, WriteConversionError},
    Identifiable, IndexType, PrimaryKey,
};
use cipherstash_client::{
    credentials::{service_credentials::ServiceToken, Credentials},
    encryption::{
        compound_indexer::{CompoundIndex, ExactIndex},
        Encryption, EncryptionError, Plaintext, TypeParseError,
    },
};
use thiserror::Error;

pub use b64_encode::*;
pub use sealed::{SealedTableEntry, UnsealSpec};
pub use sealer::Sealer;
pub use unsealed::Unsealed;

/// In order to stop indexes from exploding with indexes on large strings, cap the number of terms
/// generated per index. Since there is a fixed number of terms per index it is also possible to
/// delete all index terms for a particular record.
const MAX_TERMS_PER_INDEX: usize = 25;

#[derive(Debug, Error)]
pub enum SealError {
    #[error("Error when creating primary key: {0}")]
    PrimaryKeyError(#[from] PrimaryKeyError),
    #[error("Failed to encrypt partition key")]
    CryptoError(#[from] EncryptionError),
    #[error("Failed to convert attribute: {0} from internal representation")]
    ReadConversionError(#[from] ReadConversionError),
    #[error("Failed to convert attribute: {0} to internal representation")]
    WriteConversionError(#[from] WriteConversionError),
    // TODO: Does TypeParseError correctly redact the plaintext value?
    #[error("Failed to parse type for encryption: {0}")]
    TypeParseError(#[from] TypeParseError),
    #[error("Missing attribute: {0}")]
    MissingAttribute(String),
    #[error("Invalid ciphertext value: {0}")]
    InvalidCiphertext(String),
    #[error("Assertion failed: {0}")]
    AssertionFailed(String),
}

#[derive(Error, Debug)]
pub enum CryptoError {
    #[error("EncryptionError: {0}")]
    EncryptionError(#[from] EncryptionError),
    #[error("ReadConversionError: {0}")]
    ReadConversionError(#[from] ReadConversionError),
    #[error("{0}")]
    Other(String),
}

pub fn format_term_key(
    sort_key: &str,
    index_name: &str,
    index_type: IndexType,
    counter: usize,
) -> String {
    format!("{sort_key}#{index_name}#{index_type}#{counter}")
}

/// Get all the term index keys for a particular sort key and index definitions
///
/// This is used to delete any index items that shouldn't exist during either an update or
pub(crate) fn all_index_keys<'a>(
    sort_key: &str,
    protected_indexes: impl AsRef<[(Cow<'a, str>, IndexType)]>,
) -> Vec<String> {
    protected_indexes
        .as_ref()
        .iter()
        .flat_map(|(index_name, index_type)| {
            (0..)
                .take(MAX_TERMS_PER_INDEX)
                .map(|i| format_term_key(sort_key, index_name, *index_type, i))
                .collect::<Vec<String>>()
        })
        .collect()
}

/// Use a CipherStash [`ExactIndex`] to take the HMAC of a string with a provided salt
///
/// This value is used for term index keys and "encrypted" partition / sort keys
pub fn hmac(
    value: &str,
    salt: Option<&str>,
    cipher: &Encryption<impl Credentials<Token = ServiceToken>>,
) -> Result<Vec<u8>, EncryptionError> {
    let plaintext = Plaintext::Utf8Str(Some(value.to_string()));
    let index = CompoundIndex::new(ExactIndex::new(vec![]));

    cipher
        .compound_index(
            &index,
            plaintext,
            // passing None here results in no terms so pass an empty string
            Some(salt.unwrap_or("")),
            32,
        )?
        .as_binary()
        .ok_or(EncryptionError::IndexingError(
            "Invalid term type".to_string(),
        ))
}

// Contains all the necessary information to encrypt the primary key pair
#[derive(Clone)]
pub struct PreparedPrimaryKey {
    pub primary_key_parts: PrimaryKeyParts,
    pub is_pk_encrypted: bool,
    pub is_sk_encrypted: bool,
}

impl PreparedPrimaryKey {
    pub fn new<R>(k: impl Into<R::PrimaryKey>) -> Self
    where
        R: Identifiable,
    {
        let primary_key_parts = k
            .into()
            .into_parts(&R::type_name(), R::sort_key_prefix().as_deref());

        Self::new_from_parts::<R>(primary_key_parts)
    }

    pub fn new_from_parts<R>(primary_key_parts: PrimaryKeyParts) -> Self
    where
        R: Identifiable,
    {
        Self {
            primary_key_parts,
            is_pk_encrypted: R::is_pk_encrypted(),
            is_sk_encrypted: R::is_sk_encrypted(),
        }
    }
}