1use crate::{
2 AlloyMetadata, DerivationPath, EncryptedBytes, FieldId, PlaintextBytes, Secret, SecretPath,
3 TenantId, create_batch_result_struct, create_batch_result_struct_using_newtype,
4 errors::AlloyError, util,
5};
6use aes_gcm::KeyInit;
7use aes_siv::siv::Aes256Siv;
8use bytes::Bytes;
9use ironcore_documents::{impl_secret_debug, v5::key_id_header::KeyIdHeader};
10use std::collections::HashMap;
11use uniffi::custom_newtype;
12
13#[derive(Debug, Clone, uniffi::Record)]
14pub struct EncryptedField {
15 pub encrypted_field: EncryptedBytes,
16 pub secret_path: SecretPath,
17 pub derivation_path: DerivationPath,
18}
19
20#[derive(Debug, Clone, uniffi::Record)]
21pub struct PlaintextField {
22 pub plaintext_field: PlaintextBytes,
23 pub secret_path: SecretPath,
24 pub derivation_path: DerivationPath,
25}
26#[derive(Debug, Clone)]
27pub struct PlaintextFields(pub HashMap<FieldId, PlaintextField>);
28custom_newtype!(PlaintextFields, HashMap<FieldId, PlaintextField>);
29#[derive(Debug, Clone)]
30pub struct EncryptedFields(pub HashMap<FieldId, EncryptedField>);
31custom_newtype!(EncryptedFields, HashMap<FieldId, EncryptedField>);
32pub struct GenerateFieldQueryResult(pub HashMap<FieldId, Vec<EncryptedField>>);
33custom_newtype!(GenerateFieldQueryResult, HashMap<FieldId, Vec<EncryptedField>>);
34create_batch_result_struct!(DeterministicRotateResult, EncryptedField, FieldId);
35create_batch_result_struct_using_newtype!(
36 DeterministicEncryptBatchResult,
37 EncryptedField,
38 FieldId,
39 EncryptedFields
40);
41create_batch_result_struct_using_newtype!(
42 DeterministicDecryptBatchResult,
43 PlaintextField,
44 FieldId,
45 PlaintextFields
46);
47
48#[derive(Clone)]
50pub(crate) struct DeterministicEncryptionKey(pub Vec<u8>);
51impl_secret_debug!(DeterministicEncryptionKey);
52
53impl DeterministicEncryptionKey {
54 pub(crate) fn derive_from_secret(
57 secret: &Secret,
58 tenant_id: &TenantId,
59 derivation_path: &DerivationPath,
60 ) -> Self {
61 let hash_result = util::hash512(
62 &secret.secret[..],
63 format!("{}-{}", tenant_id.0, derivation_path.0),
64 );
65 Self(hash_result.to_vec())
66 }
67}
68
69#[uniffi::export]
70#[async_trait::async_trait]
71pub trait DeterministicFieldOps: Send + Sync {
72 async fn encrypt(
77 &self,
78 plaintext_field: PlaintextField,
79 metadata: &AlloyMetadata,
80 ) -> Result<EncryptedField, AlloyError>;
81 async fn encrypt_batch(
86 &self,
87 fields: PlaintextFields,
88 metadata: &AlloyMetadata,
89 ) -> Result<DeterministicEncryptBatchResult, AlloyError>;
90 async fn decrypt(
92 &self,
93 encrypted_field: EncryptedField,
94 metadata: &AlloyMetadata,
95 ) -> Result<PlaintextField, AlloyError>;
96 async fn decrypt_batch(
100 &self,
101 encrypted_fields: EncryptedFields,
102 metadata: &AlloyMetadata,
103 ) -> Result<DeterministicDecryptBatchResult, AlloyError>;
104 async fn generate_query_field_values(
107 &self,
108 fields_to_query: PlaintextFields,
109 metadata: &AlloyMetadata,
110 ) -> Result<GenerateFieldQueryResult, AlloyError>;
111 async fn rotate_fields(
115 &self,
116 encrypted_fields: EncryptedFields,
117 metadata: &AlloyMetadata,
118 new_tenant_id: Option<TenantId>,
119 ) -> Result<DeterministicRotateResult, AlloyError>;
120 async fn get_in_rotation_prefix(
126 &self,
127 secret_path: SecretPath,
128 derivation_path: DerivationPath,
129 metadata: &AlloyMetadata,
130 ) -> Result<Vec<u8>, AlloyError>;
131}
132
133pub(crate) fn encrypt_internal(
134 key: DeterministicEncryptionKey,
135 key_id_header: KeyIdHeader,
136 plaintext_field: PlaintextField,
137) -> Result<EncryptedField, AlloyError> {
138 let current_derived_key_sized: [u8; 64] =
139 key.0.try_into().map_err(|_| AlloyError::InvalidKey {
140 msg: "The derived key was not 64 bytes.".to_string(),
141 })?;
142 let encrypted_bytes = deterministic_encrypt(
143 current_derived_key_sized,
144 plaintext_field.plaintext_field.0.as_slice(),
145 )?;
146 let encrypted_field = key_id_header.put_header_on_document(encrypted_bytes);
147 Ok(EncryptedField {
148 encrypted_field: EncryptedBytes(encrypted_field.into()),
149 secret_path: plaintext_field.secret_path,
150 derivation_path: plaintext_field.derivation_path,
151 })
152}
153
154pub(crate) fn decrypt_internal(
155 key: DeterministicEncryptionKey,
156 ciphertext: Bytes,
157 secret_path: SecretPath,
158 derivation_path: DerivationPath,
159) -> Result<PlaintextField, AlloyError> {
160 let sized_key: [u8; 64] = key.0.try_into().map_err(|_| AlloyError::InvalidKey {
161 msg: "The derived key was not 64 bytes.".to_string(),
162 })?;
163 deterministic_decrypt(sized_key, &ciphertext).map(|res| PlaintextField {
164 plaintext_field: res,
165 secret_path,
166 derivation_path,
167 })
168}
169
170fn deterministic_encrypt(key: [u8; 64], plaintext: &[u8]) -> Result<Vec<u8>, AlloyError> {
171 deterministic_encrypt_core(key, plaintext, &[])
172}
173
174fn deterministic_encrypt_core(
175 key: [u8; 64],
176 plaintext: &[u8],
177 associated_data: &[u8],
178) -> Result<Vec<u8>, AlloyError> {
179 let mut cipher = Aes256Siv::new(&key.into());
180 cipher
181 .encrypt([associated_data], plaintext)
182 .map_err(|e| AlloyError::EncryptError { msg: e.to_string() })
183}
184
185fn deterministic_decrypt(key: [u8; 64], ciphertext: &[u8]) -> Result<PlaintextBytes, AlloyError> {
186 deterministic_decrypt_core(key, ciphertext, &[])
187}
188
189fn deterministic_decrypt_core(
190 key: [u8; 64],
191 ciphertext: &[u8],
192 associated_data: &[u8],
193) -> Result<PlaintextBytes, AlloyError> {
194 let mut cipher = Aes256Siv::new(&key.into());
195 cipher
196 .decrypt([associated_data], ciphertext)
197 .map(PlaintextBytes)
198 .map_err(|_| AlloyError::DecryptError {
199 msg: "Failed deterministic decryption. Ensure the data and tenant ID are correct"
200 .to_string(),
201 })
202}
203
204#[cfg(test)]
205mod test {
206 use super::*;
207 use hex_literal::hex;
208
209 #[test]
213 fn test_known_deterministic() {
214 let key = hex!(
215 "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f06f6e6d6c6b6a69686766656463626160f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f"
216 );
217 let ad = hex!("101112131415161718191a1b1c1d1e1f2021222324252627");
218 let plaintext = hex!("112233445566778899aabbccddee");
219 let encrypt_result = deterministic_encrypt_core(key, &plaintext, &ad).unwrap();
220 let expected_encrypt = hex!("f125274c598065cfc26b0e71575029088b035217e380cac8919ee800c126");
221 assert_eq!(encrypt_result.clone(), expected_encrypt);
222 let decrypt_result = deterministic_decrypt_core(key, &encrypt_result, &ad).unwrap();
223 assert_eq!(decrypt_result.0, plaintext);
224 }
225}