adaptive_pipeline_domain/services/
encryption_service.rs

1// /////////////////////////////////////////////////////////////////////////////
2// Adaptive Pipeline
3// Copyright (c) 2025 Michael Gardner, A Bit of Help, Inc.
4// SPDX-License-Identifier: BSD-3-Clause
5// See LICENSE file in the project root.
6// /////////////////////////////////////////////////////////////////////////////
7
8//! # Encryption Service
9//!
10//! Domain service trait for authenticated encryption (AEAD) with algorithms
11//! (AES-256-GCM, ChaCha20-Poly1305), secure key derivation (Argon2, Scrypt,
12//! PBKDF2), and memory zeroization. Provides chunk-by-chunk streaming,
13//! tampering detection, and security context integration. Thread-safe,
14//! stateless operations. See mdBook for algorithm comparison and security
15//! features.
16//! concurrently across multiple threads. The service maintains no mutable state
17//! and all operations are stateless.
18//!
19//! ## Integration
20//!
21//! The encryption service integrates with:
22//!
23//! - **Security Context**: Access control and security policies
24//! - **Pipeline Processing**: Core pipeline stage processing
25//! - **Key Management**: Secure key storage and retrieval
26//! - **Audit Logging**: Security event tracking and compliance
27
28use serde::{Deserialize, Serialize};
29
30use crate::services::datetime_serde;
31use crate::value_objects::EncryptionBenchmark;
32use crate::{FileChunk, PipelineError, ProcessingContext, SecurityContext};
33use zeroize::{Zeroize, ZeroizeOnDrop};
34
35// NOTE: Domain traits are synchronous. Async execution is an infrastructure
36// concern. Infrastructure can provide async adapters that wrap sync
37// implementations.
38
39/// Encryption algorithms supported by the adaptive pipeline system
40///
41/// This enum provides type-safe selection of encryption algorithms with
42/// different performance characteristics and security properties. All
43/// algorithms provide authenticated encryption with associated data (AEAD) for
44/// both confidentiality and integrity protection.
45///
46/// # Algorithm Characteristics
47///
48/// - **AES-256-GCM**: Industry standard with 256-bit keys, excellent
49///   performance
50/// - **ChaCha20-Poly1305**: Modern stream cipher, constant-time implementation
51/// - **AES-128-GCM**: Faster variant with 128-bit keys, still highly secure
52/// - **AES-192-GCM**: Middle ground with 192-bit keys
53/// - **Custom**: User-defined algorithms for specialized requirements
54///
55/// # Security Properties
56///
57/// All algorithms provide:
58/// - **Confidentiality**: Data is encrypted and unreadable without the key
59/// - **Integrity**: Tampering is detected through authentication tags
60/// - **Authentication**: Verifies data origin and prevents forgery
61/// - **Semantic Security**: Identical plaintexts produce different ciphertexts
62///
63/// # Examples
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub enum EncryptionAlgorithm {
66    Aes256Gcm,
67    ChaCha20Poly1305,
68    Aes128Gcm,
69    Aes192Gcm,
70    Custom(String),
71}
72
73/// Key derivation functions for secure key generation from passwords or key
74/// material
75///
76/// This enum provides type-safe selection of key derivation functions (KDFs)
77/// with different security properties and performance characteristics. All
78/// functions are designed to be computationally expensive to resist brute-force
79/// attacks.
80///
81/// # Function Characteristics
82///
83/// - **Argon2**: Memory-hard function, winner of Password Hashing Competition
84/// - **Scrypt**: Memory-hard function with tunable parameters
85/// - **PBKDF2**: Standard function with configurable iterations
86/// - **Custom**: User-defined functions for specialized requirements
87///
88/// # Security Properties
89///
90/// - **Argon2**: Resistant to GPU and ASIC attacks, configurable memory and
91///   time costs
92/// - **Scrypt**: Good resistance to hardware attacks, balanced memory/time
93///   trade-offs
94/// - **PBKDF2**: Widely supported, but more vulnerable to specialized hardware
95///   attacks
96///
97/// # Performance Considerations
98///
99/// | Function | Speed | Memory Usage | GPU Resistance | ASIC Resistance |
100/// |----------|-------|--------------|----------------|------------------|
101/// | Argon2   | Slow  | High         | Excellent      | Excellent        |
102/// | Scrypt   | Medium| Medium       | Good           | Good             |
103/// | PBKDF2   | Fast  | Low          | Poor           | Poor             |
104///
105/// # Examples
106#[derive(Debug, Clone, PartialEq)]
107pub enum KeyDerivationFunction {
108    /// Argon2 - Memory-hard function resistant to GPU and ASIC attacks
109    /// Winner of the Password Hashing Competition, provides excellent security
110    Argon2,
111
112    /// Scrypt - Memory-hard function with tunable parameters
113    /// Good balance of security and performance
114    Scrypt,
115
116    /// PBKDF2 - Standard key derivation function
117    /// Widely supported but less resistant to specialized attacks
118    Pbkdf2,
119
120    /// Custom key derivation function for specialized requirements
121    Custom(String),
122}
123
124/// Encryption configuration that encapsulates all parameters for encryption
125/// operations
126///
127/// This configuration struct provides comprehensive control over encryption
128/// behavior, including algorithm selection, key derivation parameters, and
129/// security settings. The configuration is immutable and thread-safe.
130///
131/// # Configuration Parameters
132///
133/// - **Algorithm**: The encryption algorithm to use
134/// - **Key Derivation**: Function for deriving keys from passwords
135/// - **Key Size**: Size of encryption keys in bytes
136/// - **Nonce Size**: Size of nonces/initialization vectors in bytes
137/// - **Salt Size**: Size of salt for key derivation in bytes
138/// - **Iterations**: Number of iterations for key derivation
139/// - **Memory Cost**: Memory usage for memory-hard functions (optional)
140/// - **Parallel Cost**: Parallelism level for key derivation (optional)
141/// - **Associated Data**: Additional authenticated data (optional)
142///
143/// # Examples
144///
145///
146/// # Security Considerations
147///
148/// - **Key Size**: Larger keys provide better security but may impact
149///   performance
150/// - **Iterations**: Higher iteration counts increase security but slow key
151///   derivation
152/// - **Memory Cost**: Higher memory usage improves resistance to attacks
153/// - **Salt Size**: Larger salts prevent rainbow table attacks
154/// - **Associated Data**: Additional data authenticated but not encrypted
155#[derive(Debug, Clone)]
156pub struct EncryptionConfig {
157    /// The encryption algorithm to use for processing
158    pub algorithm: EncryptionAlgorithm,
159
160    /// Key derivation function for generating keys from passwords
161    pub key_derivation: KeyDerivationFunction,
162
163    /// Size of encryption keys in bytes
164    pub key_size: u32,
165
166    /// Size of nonces/initialization vectors in bytes
167    pub nonce_size: u32,
168
169    /// Size of salt for key derivation in bytes
170    pub salt_size: u32,
171
172    /// Number of iterations for key derivation functions
173    pub iterations: u32,
174
175    /// Memory cost for memory-hard functions (bytes)
176    pub memory_cost: Option<u32>,
177
178    /// Parallelism level for key derivation functions
179    pub parallel_cost: Option<u32>,
180
181    /// Additional authenticated data (not encrypted)
182    pub associated_data: Option<Vec<u8>>,
183}
184
185/// Key material for encryption/decryption operations with secure memory
186/// management
187///
188/// This struct contains all cryptographic material needed for encryption and
189/// decryption operations. It implements secure memory management through the
190/// `Zeroize` trait to ensure sensitive data is properly cleared from memory
191/// when no longer needed.
192///
193/// # Security Features
194///
195/// - **Automatic Zeroization**: Keys are securely wiped from memory on drop
196/// - **Expiration Support**: Keys can have expiration times for security
197///   policies
198/// - **Algorithm Binding**: Keys are bound to specific algorithms
199/// - **Timestamp Tracking**: Creation time tracking for audit and compliance
200///
201/// # Key Material Components
202///
203/// - **Key**: The actual encryption/decryption key
204/// - **Nonce**: Unique number used once per encryption operation
205/// - **Salt**: Random data used in key derivation
206/// - **Algorithm**: The encryption algorithm this key is for
207/// - **Created At**: When the key material was generated
208/// - **Expires At**: Optional expiration time for key rotation
209///
210/// # Examples
211///
212///
213/// # Memory Security
214///
215/// The key material implements `Zeroize` to ensure sensitive data is securely
216/// cleared from memory:
217///
218///
219/// # Serialization
220///
221/// Key material can be serialized for storage, but care must be taken to:
222/// - Encrypt serialized key material
223/// - Use secure storage mechanisms
224/// - Implement proper access controls
225/// - Follow key management best practices
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct KeyMaterial {
228    /// The encryption/decryption key (sensitive data)
229    pub key: Vec<u8>,
230
231    /// Nonce/initialization vector for encryption operations
232    pub nonce: Vec<u8>,
233
234    /// Salt used in key derivation (if applicable)
235    pub salt: Vec<u8>,
236
237    /// The encryption algorithm this key material is for
238    pub algorithm: EncryptionAlgorithm,
239
240    /// When this key material was created (RFC3339 format)
241    #[serde(with = "datetime_serde")]
242    pub created_at: chrono::DateTime<chrono::Utc>,
243
244    /// Optional expiration time for key rotation (RFC3339 format)
245    #[serde(with = "datetime_serde::optional")]
246    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
247}
248
249impl Zeroize for KeyMaterial {
250    fn zeroize(&mut self) {
251        self.key.zeroize();
252        self.nonce.zeroize();
253        self.salt.zeroize();
254    }
255}
256
257impl ZeroizeOnDrop for KeyMaterial {}
258
259impl KeyMaterial {
260    pub fn len(&self) -> usize {
261        self.key.len()
262    }
263
264    pub fn is_empty(&self) -> bool {
265        self.key.is_empty()
266    }
267
268    pub fn new(key: Vec<u8>, nonce: Vec<u8>, salt: Vec<u8>, algorithm: EncryptionAlgorithm) -> Self {
269        Self {
270            key,
271            nonce,
272            salt,
273            algorithm,
274            created_at: chrono::Utc::now(),
275            expires_at: None,
276        }
277    }
278}
279
280/// Domain service interface for encryption operations
281///
282/// This trait is **synchronous** following DDD principles. The domain layer
283/// defines *what* operations exist, not *how* they execute. Async execution
284/// is an infrastructure concern. Infrastructure adapters can wrap this trait
285/// to provide async interfaces when needed.
286///
287/// # Note on Async
288///
289/// For async contexts, use `AsyncEncryptionAdapter` from the infrastructure
290/// layer.
291///
292/// # Note on Parallel Processing
293///
294/// Parallel processing of chunks (encrypt_chunks_parallel,
295/// decrypt_chunks_parallel) is an infrastructure concern and has been removed
296/// from the domain trait. Use infrastructure adapters for batch/parallel
297/// operations.
298///
299/// # Unified Stage Interface
300///
301/// This trait extends `StageService`, providing the unified `process_chunk()`
302/// method that all stages implement. The specialized `encrypt_chunk()` and
303/// `decrypt_chunk()` methods are maintained for backward compatibility and
304/// internal use, but `process_chunk()` is the primary interface used by the
305/// pipeline system.
306pub trait EncryptionService: super::stage_service::StageService {
307    /// Encrypts a file chunk using the specified configuration and key material
308    ///
309    /// # Note on Async
310    ///
311    /// This method is synchronous in the domain. For async contexts,
312    /// use `AsyncEncryptionAdapter` from the infrastructure layer.
313    fn encrypt_chunk(
314        &self,
315        chunk: FileChunk,
316        config: &EncryptionConfig,
317        key_material: &KeyMaterial,
318        context: &mut ProcessingContext,
319    ) -> Result<FileChunk, PipelineError>;
320
321    /// Decrypts a file chunk using the specified configuration and key material
322    ///
323    /// # Note on Async
324    ///
325    /// This method is synchronous in the domain. For async contexts,
326    /// use `AsyncEncryptionAdapter` from the infrastructure layer.
327    fn decrypt_chunk(
328        &self,
329        chunk: FileChunk,
330        config: &EncryptionConfig,
331        key_material: &KeyMaterial,
332        context: &mut ProcessingContext,
333    ) -> Result<FileChunk, PipelineError>;
334
335    /// Derives key material from password using the specified KDF
336    ///
337    /// # Note
338    ///
339    /// This is a CPU-intensive operation. Use infrastructure adapters
340    /// to execute in blocking thread pool when called from async contexts.
341    fn derive_key_material(
342        &self,
343        password: &str,
344        config: &EncryptionConfig,
345        security_context: &SecurityContext,
346    ) -> Result<KeyMaterial, PipelineError>;
347
348    /// Generates random key material for encryption operations
349    ///
350    /// # Note
351    ///
352    /// This operation uses cryptographically secure random number generation.
353    /// Execution is synchronous in domain, wrap with adapter for async
354    /// contexts.
355    fn generate_key_material(
356        &self,
357        config: &EncryptionConfig,
358        security_context: &SecurityContext,
359    ) -> Result<KeyMaterial, PipelineError>;
360
361    /// Validates encryption configuration parameters
362    ///
363    /// Checks if the configuration is valid and supported by this
364    /// implementation.
365    fn validate_config(&self, config: &EncryptionConfig) -> Result<(), PipelineError>;
366
367    /// Gets list of supported encryption algorithms
368    ///
369    /// Returns the algorithms that this implementation can handle.
370    fn supported_algorithms(&self) -> Vec<EncryptionAlgorithm>;
371
372    /// Benchmarks encryption performance with sample data
373    ///
374    /// # Note
375    ///
376    /// This is a CPU-intensive operation. Use infrastructure adapters
377    /// for async execution in blocking thread pool.
378    fn benchmark_algorithm(
379        &self,
380        algorithm: &EncryptionAlgorithm,
381        test_data: &[u8],
382    ) -> Result<EncryptionBenchmark, PipelineError>;
383
384    /// Securely wipes key material from memory
385    ///
386    /// Ensures sensitive key data is properly zeroized before deallocation.
387    fn wipe_key_material(&self, key_material: &mut KeyMaterial) -> Result<(), PipelineError>;
388
389    /// Stores key material securely (e.g., HSM integration)
390    ///
391    /// # Note
392    ///
393    /// This may involve I/O operations. Infrastructure implementations
394    /// should use appropriate async adapters when needed.
395    fn store_key_material(
396        &self,
397        key_material: &KeyMaterial,
398        key_id: &str,
399        security_context: &SecurityContext,
400    ) -> Result<(), PipelineError>;
401
402    /// Retrieves key material securely (e.g., from HSM)
403    ///
404    /// # Note
405    ///
406    /// This may involve I/O operations. Infrastructure implementations
407    /// should use appropriate async adapters when needed.
408    fn retrieve_key_material(
409        &self,
410        key_id: &str,
411        security_context: &SecurityContext,
412    ) -> Result<KeyMaterial, PipelineError>;
413
414    /// Rotates encryption keys to new configuration
415    ///
416    /// Returns the new key ID for the rotated keys.
417    ///
418    /// # Note
419    ///
420    /// This may involve I/O operations. Infrastructure implementations
421    /// should use appropriate async adapters when needed.
422    fn rotate_keys(
423        &self,
424        old_key_id: &str,
425        new_config: &EncryptionConfig,
426        security_context: &SecurityContext,
427    ) -> Result<String, PipelineError>;
428}
429
430impl Default for EncryptionConfig {
431    fn default() -> Self {
432        Self {
433            algorithm: EncryptionAlgorithm::Aes256Gcm,
434            key_derivation: KeyDerivationFunction::Argon2,
435            key_size: 32,   // 256 bits
436            nonce_size: 12, // 96 bits for GCM
437            salt_size: 16,  // 128 bits
438            iterations: 100_000,
439            memory_cost: Some(65536), // 64MB for Argon2
440            parallel_cost: Some(1),
441            associated_data: None,
442        }
443    }
444}
445
446impl EncryptionConfig {
447    /// Creates a new encryption configuration
448    pub fn new(algorithm: EncryptionAlgorithm) -> Self {
449        Self {
450            algorithm,
451            ..Default::default()
452        }
453    }
454
455    /// Sets key derivation function
456    pub fn with_key_derivation(mut self, kdf: KeyDerivationFunction) -> Self {
457        self.key_derivation = kdf;
458        self
459    }
460
461    /// Sets key size
462    pub fn with_key_size(mut self, size: u32) -> Self {
463        self.key_size = size;
464        self
465    }
466
467    /// Sets iterations
468    pub fn with_iterations(mut self, iterations: u32) -> Self {
469        self.iterations = iterations;
470        self
471    }
472
473    /// Sets memory cost (for Argon2)
474    pub fn with_memory_cost(mut self, cost: u32) -> Self {
475        self.memory_cost = Some(cost);
476        self
477    }
478
479    /// Sets parallel cost (for Argon2)
480    pub fn with_parallel_cost(mut self, cost: u32) -> Self {
481        self.parallel_cost = Some(cost);
482        self
483    }
484
485    /// Sets associated data
486    pub fn with_associated_data(mut self, data: Vec<u8>) -> Self {
487        self.associated_data = Some(data);
488        self
489    }
490
491    /// Creates a high-security configuration
492    pub fn high_security() -> Self {
493        Self {
494            algorithm: EncryptionAlgorithm::Aes256Gcm,
495            key_derivation: KeyDerivationFunction::Argon2,
496            key_size: 32,
497            nonce_size: 12,
498            salt_size: 32,              // Larger salt
499            iterations: 1_000_000,      // More iterations
500            memory_cost: Some(1048576), // 1GB for Argon2
501            parallel_cost: Some(4),
502            associated_data: None,
503        }
504    }
505
506    /// Creates a performance-optimized configuration
507    pub fn performance_optimized() -> Self {
508        Self {
509            algorithm: EncryptionAlgorithm::ChaCha20Poly1305,
510            key_derivation: KeyDerivationFunction::Argon2,
511            key_size: 32,
512            nonce_size: 12,
513            salt_size: 16,
514            iterations: 10_000,      // Fewer iterations
515            memory_cost: Some(8192), // 8MB for Argon2
516            parallel_cost: Some(1),
517            associated_data: None,
518        }
519    }
520}
521
522/// Implementation of `FromParameters` for type-safe config extraction.
523///
524/// This implementation converts `StageConfiguration.parameters` HashMap
525/// into a typed `EncryptionConfig` object.
526///
527/// ## Expected Parameters
528///
529/// - **algorithm** (required): Encryption algorithm name
530///   - Valid values: "aes256gcm", "aes128gcm", "chacha20poly1305",
531///     "xchacha20poly1305"
532///   - Example: `"algorithm" => "aes256gcm"`
533///
534/// - **key_size** (optional): Key size in bytes
535///   - Default: 32
536///   - Example: `"key_size" => "32"`
537///
538/// - **iterations** (optional): KDF iterations
539///   - Default: 3
540///   - Example: `"iterations" => "10000"`
541///
542/// ## Usage Example
543///
544/// ```rust
545/// use adaptive_pipeline_domain::services::{EncryptionConfig, FromParameters};
546/// use std::collections::HashMap;
547///
548/// let mut params = HashMap::new();
549/// params.insert("algorithm".to_string(), "aes256gcm".to_string());
550///
551/// let config = EncryptionConfig::from_parameters(&params).unwrap();
552/// ```
553impl super::stage_service::FromParameters for EncryptionConfig {
554    fn from_parameters(params: &std::collections::HashMap<String, String>) -> Result<Self, PipelineError> {
555        // Required: algorithm
556        let algorithm_str = params
557            .get("algorithm")
558            .ok_or_else(|| PipelineError::MissingParameter("algorithm".into()))?;
559
560        let algorithm = match algorithm_str.to_lowercase().as_str() {
561            "aes256gcm" | "aes-256-gcm" => EncryptionAlgorithm::Aes256Gcm,
562            "aes128gcm" | "aes-128-gcm" => EncryptionAlgorithm::Aes128Gcm,
563            "chacha20poly1305" | "chacha20-poly1305" => EncryptionAlgorithm::ChaCha20Poly1305,
564            other => {
565                return Err(PipelineError::InvalidParameter(format!(
566                    "Unknown encryption algorithm: {}",
567                    other
568                )));
569            }
570        };
571
572        // Optional parameters with defaults
573        let key_size = params.get("key_size").and_then(|s| s.parse::<u32>().ok()).unwrap_or(32);
574
575        let iterations = params
576            .get("iterations")
577            .and_then(|s| s.parse::<u32>().ok())
578            .unwrap_or(3);
579
580        Ok(Self {
581            algorithm,
582            key_derivation: KeyDerivationFunction::Argon2,
583            key_size,
584            nonce_size: 12,
585            salt_size: 16,
586            iterations,
587            memory_cost: Some(65536), // 64MB default
588            parallel_cost: Some(4),
589            associated_data: None,
590        })
591    }
592}
593
594impl KeyMaterial {
595    /// Sets expiration time
596    pub fn with_expiration(mut self, expires_at: chrono::DateTime<chrono::Utc>) -> Self {
597        self.expires_at = Some(expires_at);
598        self
599    }
600
601    /// Checks if key material is expired
602    pub fn is_expired(&self) -> bool {
603        if let Some(expires_at) = self.expires_at {
604            chrono::Utc::now() > expires_at
605        } else {
606            false
607        }
608    }
609
610    /// Securely clears key material
611    pub fn clear(&mut self) {
612        // Zero out sensitive data
613        self.key.fill(0);
614        self.nonce.fill(0);
615        self.salt.fill(0);
616
617        // Clear vectors
618        self.key.clear();
619        self.nonce.clear();
620        self.salt.clear();
621
622        // Shrink to free memory
623        self.key.shrink_to_fit();
624        self.nonce.shrink_to_fit();
625        self.salt.shrink_to_fit();
626    }
627
628    /// Gets key size in bytes
629    pub fn key_size(&self) -> usize {
630        self.key.len()
631    }
632
633    /// Gets nonce size in bytes
634    pub fn nonce_size(&self) -> usize {
635        self.nonce.len()
636    }
637
638    /// Gets salt size in bytes
639    pub fn salt_size(&self) -> usize {
640        self.salt.len()
641    }
642}
643
644impl Drop for KeyMaterial {
645    fn drop(&mut self) {
646        self.clear();
647    }
648}
649
650impl std::fmt::Display for EncryptionAlgorithm {
651    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
652        match self {
653            EncryptionAlgorithm::Aes256Gcm => write!(f, "AES-256-GCM"),
654            EncryptionAlgorithm::ChaCha20Poly1305 => write!(f, "ChaCha20-Poly1305"),
655            EncryptionAlgorithm::Aes128Gcm => write!(f, "AES-128-GCM"),
656            EncryptionAlgorithm::Aes192Gcm => write!(f, "AES-192-GCM"),
657            EncryptionAlgorithm::Custom(name) => write!(f, "Custom({})", name),
658        }
659    }
660}
661
662impl std::fmt::Display for KeyDerivationFunction {
663    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
664        match self {
665            KeyDerivationFunction::Argon2 => write!(f, "Argon2"),
666            KeyDerivationFunction::Scrypt => write!(f, "scrypt"),
667            KeyDerivationFunction::Pbkdf2 => write!(f, "PBKDF2"),
668            KeyDerivationFunction::Custom(name) => write!(f, "Custom({})", name),
669        }
670    }
671}