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