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(¶ms).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}