Skip to main content

fraiseql_server/encryption/
database_adapter.rs

1// Phase 12.3 Cycle 2: Database Adapter Integration (REFACTOR)
2//! Database adapter for transparent field-level encryption/decryption
3//!
4//! Provides query-layer integration for automatic encryption on write
5//! and decryption on read operations.
6//!
7//! # Features
8//!
9//! - Automatic encryption on INSERT/UPDATE operations
10//! - Automatic decryption on SELECT operations
11//! - Multi-field encryption with independent keys
12//! - Cipher caching for performance
13//! - Context-based authenticated encryption for audit trails
14//! - Key rotation support via cache invalidation
15
16use std::{collections::HashMap, sync::Arc};
17
18use tokio::sync::RwLock;
19
20use super::FieldEncryption;
21use crate::secrets_manager::{SecretsError, SecretsManager};
22
23/// Trait for managing encrypted fields in database adapters
24///
25/// Enables automatic encryption/decryption at the query layer without
26/// requiring manual encryption/decryption in application code.
27#[allow(async_fn_in_trait)]
28pub trait EncryptedFieldAdapter: Send + Sync {
29    /// Get list of encrypted field names
30    fn get_encrypted_fields(&self) -> Vec<String>;
31
32    /// Check if a field is encrypted
33    fn is_encrypted(&self, field_name: &str) -> bool {
34        self.get_encrypted_fields().contains(&field_name.to_string())
35    }
36
37    /// Encrypt a plaintext value for the given field
38    async fn encrypt_value(
39        &self,
40        field_name: &str,
41        plaintext: &str,
42    ) -> Result<Vec<u8>, SecretsError>;
43
44    /// Decrypt an encrypted value for the given field
45    async fn decrypt_value(
46        &self,
47        field_name: &str,
48        ciphertext: &[u8],
49    ) -> Result<String, SecretsError>;
50
51    /// Encrypt with additional context for audit trail
52    async fn encrypt_with_context(
53        &self,
54        field_name: &str,
55        plaintext: &str,
56        context: &str,
57    ) -> Result<Vec<u8>, SecretsError>;
58
59    /// Decrypt with context verification
60    async fn decrypt_with_context(
61        &self,
62        field_name: &str,
63        ciphertext: &[u8],
64        context: &str,
65    ) -> Result<String, SecretsError>;
66}
67
68/// Encryption context for audit trail inclusion
69///
70/// Format: "user:{user_id}:field:{field_name}:timestamp:{timestamp}"
71#[derive(Debug, Clone)]
72pub struct EncryptionContext {
73    /// User ID performing the operation
74    pub user_id:    String,
75    /// Field name being encrypted
76    pub field_name: String,
77    /// Operation type (insert, update, select)
78    pub operation:  String,
79    /// Timestamp of operation
80    pub timestamp:  String,
81}
82
83impl EncryptionContext {
84    /// Create new encryption context
85    pub fn new(
86        user_id: impl Into<String>,
87        field_name: impl Into<String>,
88        operation: impl Into<String>,
89        timestamp: impl Into<String>,
90    ) -> Self {
91        Self {
92            user_id:    user_id.into(),
93            field_name: field_name.into(),
94            operation:  operation.into(),
95            timestamp:  timestamp.into(),
96        }
97    }
98
99    /// Convert context to string for authenticated data
100    pub fn to_aad_string(&self) -> String {
101        format!(
102            "user:{}:field:{}:op:{}:ts:{}",
103            self.user_id, self.field_name, self.operation, self.timestamp
104        )
105    }
106}
107
108/// Cached encryption cipher for a field
109#[derive(Clone)]
110struct CachedEncryption {
111    cipher:   FieldEncryption,
112    /// Key name from Vault - kept for debugging and audit trail (used in Phase 12.3+ cycles)
113    #[allow(dead_code)]
114    key_name: String,
115}
116
117/// Basic implementation of EncryptedFieldAdapter
118///
119/// Uses SecretsManager to fetch encryption keys from Vault
120/// and caches ciphers for performance.
121pub struct DatabaseFieldAdapter {
122    /// Secrets manager for fetching encryption keys
123    secrets_manager: Arc<SecretsManager>,
124    /// Mapping of field names to encryption key names in Vault
125    field_keys:      HashMap<String, String>,
126    /// Cached cipher instances per field
127    ciphers:         Arc<RwLock<HashMap<String, CachedEncryption>>>,
128}
129
130impl DatabaseFieldAdapter {
131    /// Create new database field adapter
132    ///
133    /// # Arguments
134    ///
135    /// * `secrets_manager` - SecretsManager for fetching encryption keys from Vault
136    /// * `field_keys` - Mapping of database field names to Vault key names
137    ///
138    /// # Example
139    ///
140    /// ```ignore
141    /// let mut field_keys = HashMap::new();
142    /// field_keys.insert("email".to_string(), "db/email_key".to_string());
143    /// field_keys.insert("phone".to_string(), "db/phone_key".to_string());
144    ///
145    /// let adapter = DatabaseFieldAdapter::new(secrets_manager, field_keys);
146    /// ```
147    pub fn new(secrets_manager: Arc<SecretsManager>, field_keys: HashMap<String, String>) -> Self {
148        Self {
149            secrets_manager,
150            field_keys,
151            ciphers: Arc::new(RwLock::new(HashMap::new())),
152        }
153    }
154
155    /// Get or create cached cipher for field
156    async fn get_cipher(&self, field_name: &str) -> Result<FieldEncryption, SecretsError> {
157        // Check cache first
158        let cache = self.ciphers.read().await;
159        if let Some(cached) = cache.get(field_name) {
160            return Ok(cached.cipher.clone());
161        }
162        drop(cache);
163
164        // Fetch key from SecretsManager
165        let key_name = self.field_keys.get(field_name).ok_or_else(|| {
166            SecretsError::NotFound(format!(
167                "Encryption key for field '{}' not configured",
168                field_name
169            ))
170        })?;
171
172        let key_str = self.secrets_manager.get_secret(key_name).await?;
173        let key_bytes = key_str.as_bytes().to_vec();
174
175        if key_bytes.len() != 32 {
176            return Err(SecretsError::ValidationError(format!(
177                "Encryption key for field '{}' must be 32 bytes, got {}",
178                field_name,
179                key_bytes.len()
180            )));
181        }
182
183        let cipher = FieldEncryption::new(&key_bytes);
184
185        // Cache for future use
186        let mut cache = self.ciphers.write().await;
187        cache.insert(
188            field_name.to_string(),
189            CachedEncryption {
190                cipher:   cipher.clone(),
191                key_name: key_name.clone(),
192            },
193        );
194
195        Ok(cipher)
196    }
197
198    /// Register new encrypted field with its encryption key
199    ///
200    /// # Arguments
201    ///
202    /// * `field_name` - Database field name to be encrypted
203    /// * `key_name` - Vault secret name for the encryption key
204    pub fn register_field(&mut self, field_name: impl Into<String>, key_name: impl Into<String>) {
205        self.field_keys.insert(field_name.into(), key_name.into());
206    }
207
208    /// Invalidate cipher cache, forcing fresh key retrieval from SecretsManager
209    ///
210    /// Useful after key rotation in Vault. Next encryption/decryption
211    /// will fetch the new key and create a new cipher.
212    pub async fn invalidate_cache(&self) {
213        let mut cache = self.ciphers.write().await;
214        cache.clear();
215    }
216
217    /// Invalidate cache for specific field
218    ///
219    /// # Arguments
220    ///
221    /// * `field_name` - Field to invalidate cache for
222    pub async fn invalidate_field_cache(&self, field_name: &str) {
223        let mut cache = self.ciphers.write().await;
224        cache.remove(field_name);
225    }
226
227    /// Get current cache size
228    ///
229    /// Returns number of cached ciphers (one per encrypted field being used).
230    pub async fn cache_size(&self) -> usize {
231        self.ciphers.read().await.len()
232    }
233}
234
235impl EncryptedFieldAdapter for DatabaseFieldAdapter {
236    fn get_encrypted_fields(&self) -> Vec<String> {
237        self.field_keys.keys().cloned().collect()
238    }
239
240    async fn encrypt_value(
241        &self,
242        field_name: &str,
243        plaintext: &str,
244    ) -> Result<Vec<u8>, SecretsError> {
245        let cipher = self.get_cipher(field_name).await.map_err(|e| {
246            SecretsError::EncryptionError(format!(
247                "Failed to get encryption cipher for field '{}': {}",
248                field_name, e
249            ))
250        })?;
251
252        cipher.encrypt(plaintext).map_err(|e| {
253            SecretsError::EncryptionError(format!(
254                "Failed to encrypt value for field '{}': {}",
255                field_name, e
256            ))
257        })
258    }
259
260    async fn decrypt_value(
261        &self,
262        field_name: &str,
263        ciphertext: &[u8],
264    ) -> Result<String, SecretsError> {
265        let cipher = self.get_cipher(field_name).await.map_err(|e| {
266            SecretsError::EncryptionError(format!(
267                "Failed to get decryption cipher for field '{}': {}",
268                field_name, e
269            ))
270        })?;
271
272        cipher.decrypt(ciphertext).map_err(|e| {
273            SecretsError::EncryptionError(format!(
274                "Failed to decrypt value for field '{}': {}",
275                field_name, e
276            ))
277        })
278    }
279
280    async fn encrypt_with_context(
281        &self,
282        field_name: &str,
283        plaintext: &str,
284        context: &str,
285    ) -> Result<Vec<u8>, SecretsError> {
286        let cipher = self.get_cipher(field_name).await.map_err(|e| {
287            SecretsError::EncryptionError(format!(
288                "Failed to get encryption cipher for field '{}': {}",
289                field_name, e
290            ))
291        })?;
292
293        cipher.encrypt_with_context(plaintext, context).map_err(|e| {
294            SecretsError::EncryptionError(format!(
295                "Failed to encrypt value with context for field '{}': {}",
296                field_name, e
297            ))
298        })
299    }
300
301    async fn decrypt_with_context(
302        &self,
303        field_name: &str,
304        ciphertext: &[u8],
305        context: &str,
306    ) -> Result<String, SecretsError> {
307        let cipher = self.get_cipher(field_name).await.map_err(|e| {
308            SecretsError::EncryptionError(format!(
309                "Failed to get decryption cipher for field '{}': {}",
310                field_name, e
311            ))
312        })?;
313
314        cipher.decrypt_with_context(ciphertext, context).map_err(|e| {
315            SecretsError::EncryptionError(format!(
316                "Failed to decrypt value with context for field '{}': {}",
317                field_name, e
318            ))
319        })
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_encryption_context_creation() {
329        let ctx = EncryptionContext::new("user123", "email", "insert", "2024-01-01T00:00:00Z");
330        assert_eq!(ctx.user_id, "user123");
331        assert_eq!(ctx.field_name, "email");
332        assert_eq!(ctx.operation, "insert");
333    }
334
335    #[test]
336    fn test_encryption_context_aad_string() {
337        let ctx = EncryptionContext::new("user456", "phone", "update", "2024-01-02T12:00:00Z");
338        let aad = ctx.to_aad_string();
339        assert!(aad.contains("user:user456"));
340        assert!(aad.contains("field:phone"));
341        assert!(aad.contains("op:update"));
342        assert!(aad.contains("ts:2024-01-02T12:00:00Z"));
343    }
344
345    #[tokio::test]
346    #[ignore] // Requires SecretsManager setup
347    async fn test_adapter_get_cipher_caching() {
348        // When cipher accessed multiple times for same field
349        // Should return cached instance on subsequent calls
350        assert!(true);
351    }
352
353    #[tokio::test]
354    #[ignore]
355    async fn test_adapter_multiple_keys() {
356        // When adapter configured with multiple fields and keys
357        // Each field should use its own encryption key
358        // Keys sourced from SecretsManager
359        assert!(true);
360    }
361
362    #[tokio::test]
363    #[ignore]
364    async fn test_adapter_cache_invalidation() {
365        // When cache invalidated (e.g., after key rotation)
366        // Next access should fetch fresh key from SecretsManager
367        // Old cached ciphers discarded
368        assert!(true);
369    }
370
371    #[tokio::test]
372    #[ignore]
373    async fn test_adapter_missing_key_error() {
374        // When field not registered in adapter
375        // encrypt_value should return NotFound error
376        // Should indicate which key missing
377        assert!(true);
378    }
379
380    #[tokio::test]
381    #[ignore]
382    async fn test_adapter_is_encrypted_check() {
383        // When checking if field is encrypted
384        // Should return true for registered fields
385        // Should return false for unregistered fields
386        assert!(true);
387    }
388}