Skip to main content

oxigdal_security/encryption/
at_rest.rs

1//! Data encryption at rest.
2
3use crate::encryption::{EncryptedData, EncryptionAlgorithm, EncryptionMetadata};
4use crate::error::{Result, SecurityError};
5use aes_gcm::{
6    Aes256Gcm, Nonce,
7    aead::{Aead, KeyInit},
8};
9use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
10use chacha20poly1305::ChaCha20Poly1305;
11
12/// Encryptor for data at rest.
13pub struct AtRestEncryptor {
14    algorithm: EncryptionAlgorithm,
15    key: Vec<u8>,
16    key_id: String,
17}
18
19impl AtRestEncryptor {
20    /// Create a new encryptor with the given algorithm and key.
21    pub fn new(algorithm: EncryptionAlgorithm, key: Vec<u8>, key_id: String) -> Result<Self> {
22        // Validate key length
23        let required_length = match algorithm {
24            EncryptionAlgorithm::Aes256Gcm => 32,
25            EncryptionAlgorithm::ChaCha20Poly1305 => 32,
26        };
27
28        if key.len() != required_length {
29            return Err(SecurityError::encryption(format!(
30                "Invalid key length: expected {}, got {}",
31                required_length,
32                key.len()
33            )));
34        }
35
36        Ok(Self {
37            algorithm,
38            key,
39            key_id,
40        })
41    }
42
43    /// Generate a random key for the given algorithm.
44    pub fn generate_key(algorithm: EncryptionAlgorithm) -> Vec<u8> {
45        let mut key = vec![
46            0u8;
47            match algorithm {
48                EncryptionAlgorithm::Aes256Gcm => 32,
49                EncryptionAlgorithm::ChaCha20Poly1305 => 32,
50            }
51        ];
52        getrandom::fill(&mut key).expect("getrandom failed");
53        key
54    }
55
56    /// Encrypt data.
57    pub fn encrypt(&self, plaintext: &[u8], aad: Option<&[u8]>) -> Result<EncryptedData> {
58        match self.algorithm {
59            EncryptionAlgorithm::Aes256Gcm => self.encrypt_aes_gcm(plaintext, aad),
60            EncryptionAlgorithm::ChaCha20Poly1305 => self.encrypt_chacha(plaintext, aad),
61        }
62    }
63
64    /// Decrypt data.
65    pub fn decrypt(&self, encrypted: &EncryptedData) -> Result<Vec<u8>> {
66        // Verify algorithm matches
67        if encrypted.metadata.algorithm != self.algorithm {
68            return Err(SecurityError::decryption(format!(
69                "Algorithm mismatch: expected {:?}, got {:?}",
70                self.algorithm, encrypted.metadata.algorithm
71            )));
72        }
73
74        // Verify key ID matches
75        if encrypted.metadata.key_id != self.key_id {
76            return Err(SecurityError::decryption(format!(
77                "Key ID mismatch: expected {}, got {}",
78                self.key_id, encrypted.metadata.key_id
79            )));
80        }
81
82        match self.algorithm {
83            EncryptionAlgorithm::Aes256Gcm => self.decrypt_aes_gcm(encrypted),
84            EncryptionAlgorithm::ChaCha20Poly1305 => self.decrypt_chacha(encrypted),
85        }
86    }
87
88    fn encrypt_aes_gcm(&self, plaintext: &[u8], aad: Option<&[u8]>) -> Result<EncryptedData> {
89        let cipher = Aes256Gcm::new_from_slice(&self.key)
90            .map_err(|e| SecurityError::encryption(format!("Failed to create cipher: {}", e)))?;
91
92        // Generate random nonce
93        let mut nonce_bytes = [0u8; 12];
94        getrandom::fill(&mut nonce_bytes).expect("getrandom failed");
95        let nonce = Nonce::from_slice(&nonce_bytes);
96
97        // Encrypt with optional AAD
98        let ciphertext = if let Some(aad_data) = aad {
99            cipher
100                .encrypt(
101                    nonce,
102                    aes_gcm::aead::Payload {
103                        msg: plaintext,
104                        aad: aad_data,
105                    },
106                )
107                .map_err(|e| SecurityError::encryption(format!("Encryption failed: {}", e)))?
108        } else {
109            cipher
110                .encrypt(nonce, plaintext)
111                .map_err(|e| SecurityError::encryption(format!("Encryption failed: {}", e)))?
112        };
113
114        let metadata = EncryptionMetadata::new(
115            self.algorithm,
116            self.key_id.clone(),
117            nonce_bytes.to_vec(),
118            aad.map(|a| a.to_vec()),
119        );
120
121        Ok(EncryptedData::new(ciphertext, metadata))
122    }
123
124    fn decrypt_aes_gcm(&self, encrypted: &EncryptedData) -> Result<Vec<u8>> {
125        let cipher = Aes256Gcm::new_from_slice(&self.key)
126            .map_err(|e| SecurityError::decryption(format!("Failed to create cipher: {}", e)))?;
127
128        if encrypted.metadata.iv.len() != 12 {
129            return Err(SecurityError::decryption(format!(
130                "Invalid nonce length: expected 12, got {}",
131                encrypted.metadata.iv.len()
132            )));
133        }
134
135        let nonce = Nonce::from_slice(&encrypted.metadata.iv);
136
137        let plaintext = if let Some(ref aad) = encrypted.metadata.aad {
138            cipher
139                .decrypt(
140                    nonce,
141                    aes_gcm::aead::Payload {
142                        msg: &encrypted.ciphertext,
143                        aad,
144                    },
145                )
146                .map_err(|e| SecurityError::decryption(format!("Decryption failed: {}", e)))?
147        } else {
148            cipher
149                .decrypt(nonce, encrypted.ciphertext.as_ref())
150                .map_err(|e| SecurityError::decryption(format!("Decryption failed: {}", e)))?
151        };
152
153        Ok(plaintext)
154    }
155
156    fn encrypt_chacha(&self, plaintext: &[u8], aad: Option<&[u8]>) -> Result<EncryptedData> {
157        let cipher = ChaCha20Poly1305::new_from_slice(&self.key)
158            .map_err(|e| SecurityError::encryption(format!("Failed to create cipher: {}", e)))?;
159
160        // Generate random nonce
161        let mut nonce_bytes = [0u8; 12];
162        getrandom::fill(&mut nonce_bytes).expect("getrandom failed");
163        let nonce = chacha20poly1305::Nonce::from_slice(&nonce_bytes);
164
165        // Encrypt with optional AAD
166        let ciphertext = if let Some(aad_data) = aad {
167            cipher
168                .encrypt(
169                    nonce,
170                    chacha20poly1305::aead::Payload {
171                        msg: plaintext,
172                        aad: aad_data,
173                    },
174                )
175                .map_err(|e| SecurityError::encryption(format!("Encryption failed: {}", e)))?
176        } else {
177            cipher
178                .encrypt(nonce, plaintext)
179                .map_err(|e| SecurityError::encryption(format!("Encryption failed: {}", e)))?
180        };
181
182        let metadata = EncryptionMetadata::new(
183            self.algorithm,
184            self.key_id.clone(),
185            nonce_bytes.to_vec(),
186            aad.map(|a| a.to_vec()),
187        );
188
189        Ok(EncryptedData::new(ciphertext, metadata))
190    }
191
192    fn decrypt_chacha(&self, encrypted: &EncryptedData) -> Result<Vec<u8>> {
193        let cipher = ChaCha20Poly1305::new_from_slice(&self.key)
194            .map_err(|e| SecurityError::decryption(format!("Failed to create cipher: {}", e)))?;
195
196        if encrypted.metadata.iv.len() != 12 {
197            return Err(SecurityError::decryption(format!(
198                "Invalid nonce length: expected 12, got {}",
199                encrypted.metadata.iv.len()
200            )));
201        }
202
203        let nonce = chacha20poly1305::Nonce::from_slice(&encrypted.metadata.iv);
204
205        let plaintext = if let Some(ref aad) = encrypted.metadata.aad {
206            cipher
207                .decrypt(
208                    nonce,
209                    chacha20poly1305::aead::Payload {
210                        msg: &encrypted.ciphertext,
211                        aad,
212                    },
213                )
214                .map_err(|e| SecurityError::decryption(format!("Decryption failed: {}", e)))?
215        } else {
216            cipher
217                .decrypt(nonce, encrypted.ciphertext.as_ref())
218                .map_err(|e| SecurityError::decryption(format!("Decryption failed: {}", e)))?
219        };
220
221        Ok(plaintext)
222    }
223
224    /// Encrypt data in place (overwrites input buffer).
225    pub fn encrypt_in_place(
226        &self,
227        buffer: &mut Vec<u8>,
228        aad: Option<&[u8]>,
229    ) -> Result<EncryptionMetadata> {
230        let encrypted = self.encrypt(buffer, aad)?;
231        buffer.clear();
232        buffer.extend_from_slice(&encrypted.ciphertext);
233        Ok(encrypted.metadata)
234    }
235
236    /// Get the algorithm used by this encryptor.
237    pub fn algorithm(&self) -> EncryptionAlgorithm {
238        self.algorithm
239    }
240
241    /// Get the key ID.
242    pub fn key_id(&self) -> &str {
243        &self.key_id
244    }
245}
246
247/// Field-level encryptor for encrypting specific fields in structured data.
248pub struct FieldEncryptor {
249    encryptor: AtRestEncryptor,
250}
251
252impl FieldEncryptor {
253    /// Create a new field encryptor.
254    pub fn new(encryptor: AtRestEncryptor) -> Self {
255        Self { encryptor }
256    }
257
258    /// Encrypt a string field.
259    pub fn encrypt_string(&self, value: &str) -> Result<String> {
260        let encrypted = self.encryptor.encrypt(value.as_bytes(), None)?;
261        let json = serde_json::to_string(&encrypted)?;
262        Ok(BASE64.encode(json))
263    }
264
265    /// Decrypt a string field.
266    pub fn decrypt_string(&self, encrypted: &str) -> Result<String> {
267        let json = BASE64
268            .decode(encrypted)
269            .map_err(|e| SecurityError::decryption(format!("Base64 decode failed: {}", e)))?;
270        let encrypted_data: EncryptedData = serde_json::from_slice(&json)?;
271        let plaintext = self.encryptor.decrypt(&encrypted_data)?;
272        String::from_utf8(plaintext)
273            .map_err(|e| SecurityError::decryption(format!("UTF-8 decode failed: {}", e)))
274    }
275
276    /// Encrypt a JSON-serializable value.
277    pub fn encrypt_json<T: serde::Serialize>(&self, value: &T) -> Result<String> {
278        let json = serde_json::to_vec(value)?;
279        let encrypted = self.encryptor.encrypt(&json, None)?;
280        let encrypted_json = serde_json::to_string(&encrypted)?;
281        Ok(BASE64.encode(encrypted_json))
282    }
283
284    /// Decrypt a JSON-serializable value.
285    pub fn decrypt_json<T: serde::de::DeserializeOwned>(&self, encrypted: &str) -> Result<T> {
286        let json = BASE64
287            .decode(encrypted)
288            .map_err(|e| SecurityError::decryption(format!("Base64 decode failed: {}", e)))?;
289        let encrypted_data: EncryptedData = serde_json::from_slice(&json)?;
290        let plaintext = self.encryptor.decrypt(&encrypted_data)?;
291        serde_json::from_slice(&plaintext).map_err(SecurityError::from)
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn test_aes_gcm_encryption() {
301        let key = AtRestEncryptor::generate_key(EncryptionAlgorithm::Aes256Gcm);
302        let encryptor =
303            AtRestEncryptor::new(EncryptionAlgorithm::Aes256Gcm, key, "test-key".to_string())
304                .expect("Failed to create encryptor");
305
306        let plaintext = b"Hello, World!";
307        let encrypted = encryptor
308            .encrypt(plaintext, None)
309            .expect("Encryption failed");
310
311        assert_ne!(encrypted.ciphertext, plaintext);
312        assert_eq!(encrypted.metadata.algorithm, EncryptionAlgorithm::Aes256Gcm);
313
314        let decrypted = encryptor.decrypt(&encrypted).expect("Decryption failed");
315        assert_eq!(decrypted, plaintext);
316    }
317
318    #[test]
319    fn test_aes_gcm_with_aad() {
320        let key = AtRestEncryptor::generate_key(EncryptionAlgorithm::Aes256Gcm);
321        let encryptor =
322            AtRestEncryptor::new(EncryptionAlgorithm::Aes256Gcm, key, "test-key".to_string())
323                .expect("Failed to create encryptor");
324
325        let plaintext = b"Hello, World!";
326        let aad = b"additional data";
327        let encrypted = encryptor
328            .encrypt(plaintext, Some(aad))
329            .expect("Encryption failed");
330
331        let decrypted = encryptor.decrypt(&encrypted).expect("Decryption failed");
332        assert_eq!(decrypted, plaintext);
333    }
334
335    #[test]
336    fn test_chacha_encryption() {
337        let key = AtRestEncryptor::generate_key(EncryptionAlgorithm::ChaCha20Poly1305);
338        let encryptor = AtRestEncryptor::new(
339            EncryptionAlgorithm::ChaCha20Poly1305,
340            key,
341            "test-key".to_string(),
342        )
343        .expect("Failed to create encryptor");
344
345        let plaintext = b"Hello, World!";
346        let encrypted = encryptor
347            .encrypt(plaintext, None)
348            .expect("Encryption failed");
349
350        assert_ne!(encrypted.ciphertext, plaintext);
351        assert_eq!(
352            encrypted.metadata.algorithm,
353            EncryptionAlgorithm::ChaCha20Poly1305
354        );
355
356        let decrypted = encryptor.decrypt(&encrypted).expect("Decryption failed");
357        assert_eq!(decrypted, plaintext);
358    }
359
360    #[test]
361    fn test_field_encryptor_string() {
362        let key = AtRestEncryptor::generate_key(EncryptionAlgorithm::Aes256Gcm);
363        let encryptor =
364            AtRestEncryptor::new(EncryptionAlgorithm::Aes256Gcm, key, "test-key".to_string())
365                .expect("Failed to create encryptor");
366        let field_encryptor = FieldEncryptor::new(encryptor);
367
368        let original = "sensitive data";
369        let encrypted = field_encryptor
370            .encrypt_string(original)
371            .expect("Encryption failed");
372
373        assert_ne!(encrypted, original);
374
375        let decrypted = field_encryptor
376            .decrypt_string(&encrypted)
377            .expect("Decryption failed");
378        assert_eq!(decrypted, original);
379    }
380
381    #[test]
382    fn test_encrypt_in_place() {
383        let key = AtRestEncryptor::generate_key(EncryptionAlgorithm::Aes256Gcm);
384        let encryptor =
385            AtRestEncryptor::new(EncryptionAlgorithm::Aes256Gcm, key, "test-key".to_string())
386                .expect("Failed to create encryptor");
387
388        let mut buffer = b"Hello, World!".to_vec();
389        let original = buffer.clone();
390
391        let metadata = encryptor
392            .encrypt_in_place(&mut buffer, None)
393            .expect("Encryption failed");
394
395        assert_ne!(buffer, original);
396
397        let encrypted = EncryptedData::new(buffer, metadata);
398        let decrypted = encryptor.decrypt(&encrypted).expect("Decryption failed");
399        assert_eq!(decrypted, original);
400    }
401}