Skip to main content

rivven_core/
encryption.rs

1//! Encryption at rest for Rivven data
2//!
3//! This module provides transparent encryption for WAL files, segments, and other
4//! persistent data. It uses AES-256-GCM for authenticated encryption with per-record
5//! nonces derived from LSN to ensure unique encryption contexts.
6//!
7//! # Features
8//!
9//! - **AES-256-GCM** authenticated encryption with 12-byte nonces
10//! - **Key derivation** using HKDF-SHA256 from master key
11//! - **Key rotation** support with key versioning
12//! - **Envelope encryption** for data keys encrypted by master key
13//! - **Zero-copy decryption** where possible
14//!
15//! # Security Properties
16//!
17//! - Confidentiality: AES-256 encryption
18//! - Integrity: GCM authentication tag (16 bytes)
19//! - Replay protection: LSN-derived nonces prevent nonce reuse
20//!
21//! # Example
22//!
23//! ```rust,ignore
24//! use rivven_core::encryption::{EncryptionConfig, KeyProvider, EncryptionManager};
25//!
26//! // Create encryption config
27//! let config = EncryptionConfig::new()
28//!     .with_algorithm(Algorithm::Aes256Gcm)
29//!     .with_key_provider(KeyProvider::File("/path/to/master.key".into()));
30//!
31//! // Initialize encryption manager
32//! let manager = EncryptionManager::new(config).await?;
33//!
34//! // Encrypt data
35//! let ciphertext = manager.encrypt(b"sensitive data", 12345)?;
36//!
37//! // Decrypt data  
38//! let plaintext = manager.decrypt(&ciphertext, 12345)?;
39//! ```
40
41use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM, CHACHA20_POLY1305};
42use ring::hkdf::{Salt, HKDF_SHA256};
43use ring::rand::{SecureRandom, SystemRandom};
44use secrecy::{ExposeSecret, SecretBox};
45use serde::{Deserialize, Serialize};
46use std::collections::HashMap;
47use std::fmt;
48use std::fs;
49use std::io;
50use std::path::PathBuf;
51use std::sync::atomic::{AtomicU32, Ordering};
52use std::sync::Arc;
53use thiserror::Error;
54
55/// AES-256-GCM nonce size in bytes
56const NONCE_SIZE: usize = 12;
57
58/// AES-256-GCM authentication tag size in bytes
59const TAG_SIZE: usize = 16;
60
61/// AES-256 key size in bytes
62const KEY_SIZE: usize = 32;
63
64/// Encryption header magic bytes
65const ENCRYPTION_MAGIC: [u8; 4] = [0x52, 0x56, 0x45, 0x4E]; // "RVEN"
66
67/// Current encryption format version
68const FORMAT_VERSION: u8 = 1;
69
70/// Encryption errors
71#[derive(Debug, Error)]
72pub enum EncryptionError {
73    #[error("key provider error: {0}")]
74    KeyProvider(String),
75
76    #[error("encryption failed: {0}")]
77    Encryption(String),
78
79    #[error("decryption failed: {0}")]
80    Decryption(String),
81
82    #[error("invalid key: {0}")]
83    InvalidKey(String),
84
85    #[error("key rotation error: {0}")]
86    KeyRotation(String),
87
88    #[error("io error: {0}")]
89    Io(#[from] io::Error),
90
91    #[error("invalid format: {0}")]
92    InvalidFormat(String),
93
94    #[error("unsupported version: {0}")]
95    UnsupportedVersion(u8),
96
97    #[error("key not found: version {0}")]
98    KeyNotFound(u32),
99}
100
101/// Result type for encryption operations
102pub type Result<T> = std::result::Result<T, EncryptionError>;
103
104/// Encryption algorithm
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
106#[serde(rename_all = "kebab-case")]
107pub enum Algorithm {
108    /// AES-256-GCM (recommended)
109    #[default]
110    Aes256Gcm,
111    /// ChaCha20-Poly1305 (alternative for systems without AES-NI)
112    ChaCha20Poly1305,
113}
114
115impl Algorithm {
116    /// Get the key size for this algorithm
117    pub fn key_size(&self) -> usize {
118        match self {
119            Algorithm::Aes256Gcm => 32,
120            Algorithm::ChaCha20Poly1305 => 32,
121        }
122    }
123
124    /// Get the nonce size for this algorithm
125    pub fn nonce_size(&self) -> usize {
126        match self {
127            Algorithm::Aes256Gcm => 12,
128            Algorithm::ChaCha20Poly1305 => 12,
129        }
130    }
131
132    /// Get the tag size for this algorithm
133    pub fn tag_size(&self) -> usize {
134        match self {
135            Algorithm::Aes256Gcm => 16,
136            Algorithm::ChaCha20Poly1305 => 16,
137        }
138    }
139}
140
141/// Key provider for encryption keys
142#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(tag = "type", rename_all = "kebab-case")]
144pub enum KeyProvider {
145    /// Read key from a file (hex-encoded or raw 32 bytes)
146    File { path: PathBuf },
147
148    /// Key from environment variable (hex-encoded)
149    Environment { variable: String },
150
151    // Future: AWS KMS key (feature = "aws-kms")
152    // Future: HashiCorp Vault (feature = "vault")
153    /// In-memory key (for testing only)
154    #[serde(skip)]
155    InMemory(#[serde(skip)] Vec<u8>),
156}
157
158impl Default for KeyProvider {
159    fn default() -> Self {
160        KeyProvider::Environment {
161            variable: "RIVVEN_ENCRYPTION_KEY".to_string(),
162        }
163    }
164}
165
166/// Encryption configuration
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct EncryptionConfig {
169    /// Whether encryption is enabled
170    #[serde(default)]
171    pub enabled: bool,
172
173    /// Encryption algorithm
174    #[serde(default)]
175    pub algorithm: Algorithm,
176
177    /// Key provider
178    #[serde(default)]
179    pub key_provider: KeyProvider,
180
181    /// Key rotation interval in days (0 = no rotation)
182    #[serde(default)]
183    pub key_rotation_days: u32,
184
185    /// Additional authenticated data scope
186    #[serde(default = "default_aad_scope")]
187    pub aad_scope: String,
188}
189
190fn default_aad_scope() -> String {
191    "rivven".to_string()
192}
193
194impl Default for EncryptionConfig {
195    fn default() -> Self {
196        Self {
197            enabled: false,
198            algorithm: Algorithm::default(),
199            key_provider: KeyProvider::default(),
200            key_rotation_days: 0,
201            aad_scope: default_aad_scope(),
202        }
203    }
204}
205
206impl EncryptionConfig {
207    /// Create a new encryption config with defaults
208    pub fn new() -> Self {
209        Self::default()
210    }
211
212    /// Enable encryption
213    pub fn enabled(mut self) -> Self {
214        self.enabled = true;
215        self
216    }
217
218    /// Set the encryption algorithm
219    pub fn with_algorithm(mut self, algorithm: Algorithm) -> Self {
220        self.algorithm = algorithm;
221        self
222    }
223
224    /// Set the key provider
225    pub fn with_key_provider(mut self, provider: KeyProvider) -> Self {
226        self.key_provider = provider;
227        self
228    }
229
230    /// Set key rotation interval
231    pub fn with_key_rotation_days(mut self, days: u32) -> Self {
232        self.key_rotation_days = days;
233        self
234    }
235}
236
237/// Encrypted data header
238///
239/// Format:
240/// - Magic (4 bytes): "RVEN"
241/// - Version (1 byte)
242/// - Algorithm (1 byte)
243/// - Key version (4 bytes)
244/// - Nonce (12 bytes)
245/// - Reserved (2 bytes)
246/// - Ciphertext + Tag (variable)
247#[derive(Debug, Clone)]
248pub struct EncryptedHeader {
249    pub version: u8,
250    pub algorithm: Algorithm,
251    pub key_version: u32,
252    pub nonce: [u8; NONCE_SIZE],
253}
254
255impl EncryptedHeader {
256    /// Header size in bytes
257    pub const SIZE: usize = 24;
258
259    /// Create a new header
260    pub fn new(algorithm: Algorithm, key_version: u32, nonce: [u8; NONCE_SIZE]) -> Self {
261        Self {
262            version: FORMAT_VERSION,
263            algorithm,
264            key_version,
265            nonce,
266        }
267    }
268
269    /// Serialize header to bytes
270    pub fn to_bytes(&self) -> [u8; Self::SIZE] {
271        let mut buf = [0u8; Self::SIZE];
272        buf[0..4].copy_from_slice(&ENCRYPTION_MAGIC);
273        buf[4] = self.version;
274        buf[5] = self.algorithm as u8;
275        buf[6..10].copy_from_slice(&self.key_version.to_be_bytes());
276        buf[10..22].copy_from_slice(&self.nonce);
277        // bytes 22-23 reserved
278        buf
279    }
280
281    /// Parse header from bytes
282    pub fn from_bytes(data: &[u8]) -> Result<Self> {
283        if data.len() < Self::SIZE {
284            return Err(EncryptionError::InvalidFormat(format!(
285                "header too short: {} < {}",
286                data.len(),
287                Self::SIZE
288            )));
289        }
290
291        // Check magic
292        if data[0..4] != ENCRYPTION_MAGIC {
293            return Err(EncryptionError::InvalidFormat("invalid magic bytes".into()));
294        }
295
296        let version = data[4];
297        if version != FORMAT_VERSION {
298            return Err(EncryptionError::UnsupportedVersion(version));
299        }
300
301        let algorithm = match data[5] {
302            0 => Algorithm::Aes256Gcm,
303            1 => Algorithm::ChaCha20Poly1305,
304            v => {
305                return Err(EncryptionError::InvalidFormat(format!(
306                    "unknown algorithm: {}",
307                    v
308                )))
309            }
310        };
311
312        let key_version = u32::from_be_bytes([data[6], data[7], data[8], data[9]]);
313        let mut nonce = [0u8; NONCE_SIZE];
314        nonce.copy_from_slice(&data[10..22]);
315
316        Ok(Self {
317            version,
318            algorithm,
319            key_version,
320            nonce,
321        })
322    }
323}
324
325/// Master key wrapper with secure memory handling
326pub struct MasterKey {
327    key: SecretBox<[u8; KEY_SIZE]>,
328    version: u32,
329}
330
331impl MasterKey {
332    /// Create a new master key from raw bytes
333    pub fn new(key: Vec<u8>, version: u32) -> Result<Self> {
334        if key.len() != KEY_SIZE {
335            return Err(EncryptionError::InvalidKey(format!(
336                "key must be {} bytes, got {}",
337                KEY_SIZE,
338                key.len()
339            )));
340        }
341        let mut key_array = [0u8; KEY_SIZE];
342        key_array.copy_from_slice(&key);
343        Ok(Self {
344            key: SecretBox::new(Box::new(key_array)),
345            version,
346        })
347    }
348
349    /// Generate a new random master key
350    pub fn generate(version: u32) -> Result<Self> {
351        let rng = SystemRandom::new();
352        let mut key = vec![0u8; KEY_SIZE];
353        rng.fill(&mut key)
354            .map_err(|_| EncryptionError::KeyProvider("failed to generate random key".into()))?;
355        Self::new(key, version)
356    }
357
358    /// Load master key from provider
359    pub fn from_provider(provider: &KeyProvider) -> Result<Self> {
360        match provider {
361            KeyProvider::File { path } => {
362                let data = fs::read(path)?;
363                let key = if data.len() == KEY_SIZE {
364                    // Raw binary key
365                    data
366                } else {
367                    // Try hex-encoded
368                    let hex_str = String::from_utf8(data)
369                        .map_err(|_| EncryptionError::InvalidKey("invalid key file format".into()))?
370                        .trim()
371                        .to_string();
372                    hex::decode(&hex_str).map_err(|e| {
373                        EncryptionError::InvalidKey(format!("invalid hex key: {}", e))
374                    })?
375                };
376                Self::new(key, 1)
377            }
378            KeyProvider::Environment { variable } => {
379                let hex_key = std::env::var(variable).map_err(|_| {
380                    EncryptionError::KeyProvider(format!(
381                        "environment variable '{}' not set",
382                        variable
383                    ))
384                })?;
385                let key = hex::decode(hex_key.trim()).map_err(|e| {
386                    EncryptionError::InvalidKey(format!("invalid hex key in env var: {}", e))
387                })?;
388                Self::new(key, 1)
389            }
390            KeyProvider::InMemory(key) => Self::new(key.clone(), 1),
391            #[allow(unreachable_patterns)]
392            _ => Err(EncryptionError::KeyProvider(
393                "unsupported key provider".into(),
394            )),
395        }
396    }
397
398    /// Get key version
399    pub fn version(&self) -> u32 {
400        self.version
401    }
402
403    /// Derive a data encryption key using HKDF
404    fn derive_data_key(&self, info: &[u8]) -> Result<[u8; KEY_SIZE]> {
405        let salt = Salt::new(HKDF_SHA256, b"rivven-encryption-v1");
406        let prk = salt.extract(self.key.expose_secret());
407        let info_refs = [info];
408        let okm = prk
409            .expand(&info_refs, DataKeyLen)
410            .map_err(|_| EncryptionError::Encryption("key derivation failed".into()))?;
411
412        let mut data_key = [0u8; KEY_SIZE];
413        okm.fill(&mut data_key)
414            .map_err(|_| EncryptionError::Encryption("key expansion failed".into()))?;
415        Ok(data_key)
416    }
417}
418
419impl fmt::Debug for MasterKey {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        f.debug_struct("MasterKey")
422            .field("version", &self.version)
423            .field("key", &"[REDACTED]")
424            .finish()
425    }
426}
427
428/// HKDF output length marker
429struct DataKeyLen;
430
431impl ring::hkdf::KeyType for DataKeyLen {
432    fn len(&self) -> usize {
433        KEY_SIZE
434    }
435}
436
437/// Encryption manager for transparent encryption at rest
438pub struct EncryptionManager {
439    config: EncryptionConfig,
440    master_key: MasterKey,
441    /// Current key for encryption (latest version)
442    data_key: LessSafeKey,
443    /// All known keys indexed by version (for decrypting old data)
444    key_store: parking_lot::RwLock<HashMap<u32, LessSafeKey>>,
445    rng: SystemRandom,
446    current_key_version: AtomicU32,
447}
448
449/// Select the ring AEAD algorithm based on our Algorithm enum
450fn ring_algorithm(algo: Algorithm) -> &'static ring::aead::Algorithm {
451    match algo {
452        Algorithm::Aes256Gcm => &AES_256_GCM,
453        Algorithm::ChaCha20Poly1305 => &CHACHA20_POLY1305,
454    }
455}
456
457impl EncryptionManager {
458    /// Create a new encryption manager
459    pub fn new(config: EncryptionConfig) -> Result<Arc<Self>> {
460        let master_key = MasterKey::from_provider(&config.key_provider)?;
461        let data_key_bytes = master_key.derive_data_key(config.aad_scope.as_bytes())?;
462
463        let algo = ring_algorithm(config.algorithm);
464        let unbound_key = UnboundKey::new(algo, &data_key_bytes)
465            .map_err(|_| EncryptionError::InvalidKey("failed to create encryption key".into()))?;
466        let data_key = LessSafeKey::new(unbound_key);
467
468        // Populate the key store with the initial key version
469        let version = master_key.version();
470        let store_unbound = UnboundKey::new(algo, &data_key_bytes)
471            .map_err(|_| EncryptionError::InvalidKey("failed to create store key".into()))?;
472        let mut key_store = HashMap::new();
473        key_store.insert(version, LessSafeKey::new(store_unbound));
474
475        Ok(Arc::new(Self {
476            config,
477            current_key_version: AtomicU32::new(version),
478            master_key,
479            data_key,
480            key_store: parking_lot::RwLock::new(key_store),
481            rng: SystemRandom::new(),
482        }))
483    }
484
485    /// Rotate the data encryption key, deriving a new key from a new master key.
486    /// Old keys are retained in the key store so existing data can still be decrypted.
487    pub fn rotate_key(&self, new_master: MasterKey) -> Result<()> {
488        let new_version = new_master.version();
489        if new_version <= self.current_key_version.load(Ordering::Relaxed) {
490            return Err(EncryptionError::KeyRotation(
491                "new key version must be greater than current".into(),
492            ));
493        }
494
495        let data_key_bytes = new_master.derive_data_key(self.config.aad_scope.as_bytes())?;
496        let algo = ring_algorithm(self.config.algorithm);
497
498        let new_key = UnboundKey::new(algo, &data_key_bytes)
499            .map_err(|_| EncryptionError::KeyRotation("failed to create new key".into()))?;
500
501        // Add to key store
502        {
503            let mut store = self.key_store.write();
504            store.insert(new_version, LessSafeKey::new(new_key));
505        }
506
507        // Atomically bump the current version — new encryptions will use this version
508        self.current_key_version
509            .store(new_version, Ordering::Release);
510
511        Ok(())
512    }
513
514    /// Get a key by version for decryption
515    fn get_key_for_version(&self, version: u32) -> Result<()> {
516        let store = self.key_store.read();
517        if store.contains_key(&version) {
518            Ok(())
519        } else {
520            Err(EncryptionError::KeyNotFound(version))
521        }
522    }
523
524    /// Create a no-op encryption manager for when encryption is disabled
525    pub fn disabled() -> Arc<DisabledEncryption> {
526        Arc::new(DisabledEncryption)
527    }
528
529    /// Check if encryption is enabled
530    pub fn is_enabled(&self) -> bool {
531        self.config.enabled
532    }
533
534    /// Get current key version
535    pub fn key_version(&self) -> u32 {
536        self.current_key_version.load(Ordering::Relaxed)
537    }
538
539    /// Generate a nonce from LSN (deterministic but unique per record)
540    fn generate_nonce(&self, lsn: u64) -> [u8; NONCE_SIZE] {
541        let mut nonce = [0u8; NONCE_SIZE];
542        // Use LSN as 8 bytes + 4 random bytes for uniqueness
543        nonce[0..8].copy_from_slice(&lsn.to_be_bytes());
544        self.rng.fill(&mut nonce[8..12]).ok();
545        nonce
546    }
547
548    /// Encrypt data with associated LSN for nonce derivation
549    pub fn encrypt(&self, plaintext: &[u8], lsn: u64) -> Result<Vec<u8>> {
550        let nonce_bytes = self.generate_nonce(lsn);
551        let nonce = Nonce::assume_unique_for_key(nonce_bytes);
552
553        let header = EncryptedHeader::new(
554            self.config.algorithm,
555            self.current_key_version.load(Ordering::Relaxed),
556            nonce_bytes,
557        );
558
559        // Allocate buffer: header + plaintext + tag
560        let mut output = Vec::with_capacity(EncryptedHeader::SIZE + plaintext.len() + TAG_SIZE);
561        output.extend_from_slice(&header.to_bytes());
562        output.extend_from_slice(plaintext);
563
564        // Encrypt in-place using separate tag method
565        let ciphertext_start = EncryptedHeader::SIZE;
566        let tag = self
567            .data_key
568            .seal_in_place_separate_tag(
569                nonce,
570                Aad::from(self.config.aad_scope.as_bytes()),
571                &mut output[ciphertext_start..],
572            )
573            .map_err(|_| EncryptionError::Encryption("seal failed".into()))?;
574
575        // Append the authentication tag
576        output.extend_from_slice(tag.as_ref());
577
578        Ok(output)
579    }
580
581    /// Decrypt data
582    pub fn decrypt(&self, ciphertext: &[u8], _lsn: u64) -> Result<Vec<u8>> {
583        if ciphertext.len() < EncryptedHeader::SIZE + TAG_SIZE {
584            return Err(EncryptionError::InvalidFormat(
585                "ciphertext too short".into(),
586            ));
587        }
588
589        let header = EncryptedHeader::from_bytes(ciphertext)?;
590
591        // Look up key by version from the key store (supports rotated keys)
592        self.get_key_for_version(header.key_version)?;
593
594        let nonce = Nonce::assume_unique_for_key(header.nonce);
595
596        // Select the correct algorithm from the header (supports cross-algorithm decrypt)
597        let algo = ring_algorithm(header.algorithm);
598
599        // Copy ciphertext + tag for in-place decryption
600        let mut buffer = ciphertext[EncryptedHeader::SIZE..].to_vec();
601
602        // We need to use the key for this specific version
603        let store = self.key_store.read();
604        let key = store
605            .get(&header.key_version)
606            .ok_or(EncryptionError::KeyNotFound(header.key_version))?;
607
608        // Verify the algorithm matches the key
609        if *key.algorithm() != *algo {
610            // Key was created with a different algorithm — re-derive
611            drop(store);
612            // For cross-algorithm decrypt: derive key bytes from master, create a temp key
613            let data_key_bytes = self
614                .master_key
615                .derive_data_key(self.config.aad_scope.as_bytes())?;
616            let unbound = UnboundKey::new(algo, &data_key_bytes)
617                .map_err(|_| EncryptionError::Decryption("key re-derive failed".into()))?;
618            let temp_key = LessSafeKey::new(unbound);
619            let plaintext = temp_key
620                .open_in_place(
621                    nonce,
622                    Aad::from(self.config.aad_scope.as_bytes()),
623                    &mut buffer,
624                )
625                .map_err(|_| EncryptionError::Decryption("authentication failed".into()))?;
626            return Ok(plaintext.to_vec());
627        }
628
629        let plaintext = key
630            .open_in_place(
631                nonce,
632                Aad::from(self.config.aad_scope.as_bytes()),
633                &mut buffer,
634            )
635            .map_err(|_| EncryptionError::Decryption("authentication failed".into()))?;
636
637        Ok(plaintext.to_vec())
638    }
639
640    /// Calculate encrypted size for given plaintext size
641    pub fn encrypted_size(&self, plaintext_len: usize) -> usize {
642        EncryptedHeader::SIZE + plaintext_len + TAG_SIZE
643    }
644}
645
646impl fmt::Debug for EncryptionManager {
647    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648        f.debug_struct("EncryptionManager")
649            .field("enabled", &self.config.enabled)
650            .field("algorithm", &self.config.algorithm)
651            .field("key_version", &self.key_version())
652            .finish()
653    }
654}
655
656/// Placeholder for disabled encryption
657#[derive(Debug)]
658pub struct DisabledEncryption;
659
660impl DisabledEncryption {
661    /// Pass-through "encrypt" (just returns the data as-is)
662    pub fn encrypt(&self, plaintext: &[u8], _lsn: u64) -> Result<Vec<u8>> {
663        Ok(plaintext.to_vec())
664    }
665
666    /// Pass-through "decrypt" (just returns the data as-is)
667    pub fn decrypt(&self, ciphertext: &[u8], _lsn: u64) -> Result<Vec<u8>> {
668        Ok(ciphertext.to_vec())
669    }
670
671    /// No overhead when disabled
672    pub fn encrypted_size(&self, plaintext_len: usize) -> usize {
673        plaintext_len
674    }
675
676    pub fn is_enabled(&self) -> bool {
677        false
678    }
679}
680
681/// Trait for encryption operations (allows dynamic dispatch)
682pub trait Encryptor: Send + Sync + std::fmt::Debug {
683    fn encrypt(&self, plaintext: &[u8], lsn: u64) -> Result<Vec<u8>>;
684    fn decrypt(&self, ciphertext: &[u8], lsn: u64) -> Result<Vec<u8>>;
685    fn encrypted_size(&self, plaintext_len: usize) -> usize;
686    fn is_enabled(&self) -> bool;
687}
688
689impl Encryptor for EncryptionManager {
690    fn encrypt(&self, plaintext: &[u8], lsn: u64) -> Result<Vec<u8>> {
691        self.encrypt(plaintext, lsn)
692    }
693
694    fn decrypt(&self, ciphertext: &[u8], lsn: u64) -> Result<Vec<u8>> {
695        self.decrypt(ciphertext, lsn)
696    }
697
698    fn encrypted_size(&self, plaintext_len: usize) -> usize {
699        self.encrypted_size(plaintext_len)
700    }
701
702    fn is_enabled(&self) -> bool {
703        self.is_enabled()
704    }
705}
706
707impl Encryptor for DisabledEncryption {
708    fn encrypt(&self, plaintext: &[u8], lsn: u64) -> Result<Vec<u8>> {
709        self.encrypt(plaintext, lsn)
710    }
711
712    fn decrypt(&self, ciphertext: &[u8], lsn: u64) -> Result<Vec<u8>> {
713        self.decrypt(ciphertext, lsn)
714    }
715
716    fn encrypted_size(&self, plaintext_len: usize) -> usize {
717        self.encrypted_size(plaintext_len)
718    }
719
720    fn is_enabled(&self) -> bool {
721        false
722    }
723}
724
725/// Generate a new encryption key and save to file
726pub fn generate_key_file(path: &std::path::Path) -> Result<()> {
727    let key = MasterKey::generate(1)?;
728    let hex_key = hex::encode(key.key.expose_secret());
729    fs::write(path, hex_key)?;
730
731    // Set restrictive permissions on Unix
732    #[cfg(unix)]
733    {
734        use std::os::unix::fs::PermissionsExt;
735        let mut perms = fs::metadata(path)?.permissions();
736        perms.set_mode(0o600);
737        fs::set_permissions(path, perms)?;
738    }
739
740    Ok(())
741}
742
743#[cfg(test)]
744mod tests {
745    use super::*;
746
747    fn test_config() -> EncryptionConfig {
748        let key = vec![0u8; 32]; // Test key
749        EncryptionConfig {
750            enabled: true,
751            algorithm: Algorithm::Aes256Gcm,
752            key_provider: KeyProvider::InMemory(key),
753            key_rotation_days: 0,
754            aad_scope: "test".to_string(),
755        }
756    }
757
758    #[test]
759    fn test_encrypt_decrypt() {
760        let manager = EncryptionManager::new(test_config()).unwrap();
761        let plaintext = b"Hello, World! This is sensitive data.";
762        let lsn = 12345u64;
763
764        let ciphertext = manager.encrypt(plaintext, lsn).unwrap();
765        assert_ne!(ciphertext.as_slice(), plaintext);
766        assert!(ciphertext.len() > plaintext.len());
767
768        let decrypted = manager.decrypt(&ciphertext, lsn).unwrap();
769        assert_eq!(decrypted, plaintext);
770    }
771
772    #[test]
773    fn test_encrypted_size() {
774        let manager = EncryptionManager::new(test_config()).unwrap();
775        let plaintext_len = 1000;
776
777        let expected = EncryptedHeader::SIZE + plaintext_len + TAG_SIZE;
778        assert_eq!(manager.encrypted_size(plaintext_len), expected);
779    }
780
781    #[test]
782    fn test_header_roundtrip() {
783        let nonce = [1u8; NONCE_SIZE];
784        let header = EncryptedHeader::new(Algorithm::Aes256Gcm, 42, nonce);
785
786        let bytes = header.to_bytes();
787        let parsed = EncryptedHeader::from_bytes(&bytes).unwrap();
788
789        assert_eq!(parsed.version, header.version);
790        assert_eq!(parsed.algorithm, header.algorithm);
791        assert_eq!(parsed.key_version, header.key_version);
792        assert_eq!(parsed.nonce, header.nonce);
793    }
794
795    #[test]
796    fn test_invalid_ciphertext() {
797        let manager = EncryptionManager::new(test_config()).unwrap();
798
799        // Too short
800        let result = manager.decrypt(&[0u8; 10], 1);
801        assert!(result.is_err());
802
803        // Invalid magic
804        let mut bad_magic = vec![0u8; 100];
805        let result = manager.decrypt(&bad_magic, 1);
806        assert!(result.is_err());
807
808        // Valid header but tampered ciphertext
809        bad_magic[0..4].copy_from_slice(&ENCRYPTION_MAGIC);
810        bad_magic[4] = FORMAT_VERSION;
811        let result = manager.decrypt(&bad_magic, 1);
812        assert!(result.is_err());
813    }
814
815    #[test]
816    fn test_tamper_detection() {
817        let manager = EncryptionManager::new(test_config()).unwrap();
818        let plaintext = b"Sensitive data that must not be tampered with";
819
820        let mut ciphertext = manager.encrypt(plaintext, 1).unwrap();
821
822        // Tamper with ciphertext (flip a bit in the data portion)
823        let tamper_pos = EncryptedHeader::SIZE + 10;
824        ciphertext[tamper_pos] ^= 0x01;
825
826        // Decryption should fail due to authentication
827        let result = manager.decrypt(&ciphertext, 1);
828        assert!(result.is_err());
829    }
830
831    #[test]
832    fn test_different_lsns_produce_different_ciphertexts() {
833        let manager = EncryptionManager::new(test_config()).unwrap();
834        let plaintext = b"Same plaintext";
835
836        let ct1 = manager.encrypt(plaintext, 1).unwrap();
837        let ct2 = manager.encrypt(plaintext, 2).unwrap();
838
839        // Same plaintext should produce different ciphertexts for different LSNs
840        assert_ne!(ct1, ct2);
841
842        // Both should decrypt correctly
843        assert_eq!(manager.decrypt(&ct1, 1).unwrap(), plaintext);
844        assert_eq!(manager.decrypt(&ct2, 2).unwrap(), plaintext);
845    }
846
847    #[test]
848    fn test_disabled_encryption_passthrough() {
849        let disabled = DisabledEncryption;
850        let plaintext = b"Not encrypted";
851
852        let encrypted = disabled.encrypt(plaintext, 1).unwrap();
853        assert_eq!(&encrypted[..], plaintext);
854
855        let decrypted = disabled.decrypt(plaintext, 1).unwrap();
856        assert_eq!(&decrypted[..], plaintext);
857
858        assert_eq!(disabled.encrypted_size(100), 100);
859        assert!(!disabled.is_enabled());
860    }
861
862    #[test]
863    fn test_master_key_validation() {
864        // Wrong key size
865        let result = MasterKey::new(vec![0u8; 16], 1);
866        assert!(result.is_err());
867
868        // Correct size
869        let result = MasterKey::new(vec![0u8; 32], 1);
870        assert!(result.is_ok());
871    }
872
873    #[test]
874    fn test_key_derivation_consistency() {
875        let key = MasterKey::new(vec![42u8; 32], 1).unwrap();
876
877        let dk1 = key.derive_data_key(b"scope1").unwrap();
878        let dk2 = key.derive_data_key(b"scope1").unwrap();
879        let dk3 = key.derive_data_key(b"scope2").unwrap();
880
881        // Same scope should derive same key
882        assert_eq!(dk1, dk2);
883
884        // Different scopes should derive different keys
885        assert_ne!(dk1, dk3);
886    }
887
888    #[test]
889    fn test_large_data_encryption() {
890        let manager = EncryptionManager::new(test_config()).unwrap();
891
892        // Test with 1MB of data
893        let plaintext: Vec<u8> = (0..1_000_000).map(|i| (i % 256) as u8).collect();
894
895        let ciphertext = manager.encrypt(&plaintext, 999999).unwrap();
896        let decrypted = manager.decrypt(&ciphertext, 999999).unwrap();
897
898        assert_eq!(decrypted, plaintext);
899    }
900
901    #[test]
902    fn test_generate_key() {
903        let key = MasterKey::generate(1).unwrap();
904        assert_eq!(key.version(), 1);
905    }
906
907    #[test]
908    fn test_chacha20_poly1305_encrypt_decrypt() {
909        let config = EncryptionConfig {
910            enabled: true,
911            algorithm: Algorithm::ChaCha20Poly1305,
912            key_provider: KeyProvider::InMemory(vec![0u8; 32]),
913            key_rotation_days: 0,
914            aad_scope: "test".to_string(),
915        };
916        let manager = EncryptionManager::new(config).unwrap();
917        let plaintext = b"ChaCha20-Poly1305 test payload";
918        let lsn = 42u64;
919
920        let ciphertext = manager.encrypt(plaintext, lsn).unwrap();
921        assert_ne!(ciphertext.as_slice(), plaintext.as_slice());
922
923        // Verify header encodes ChaCha20
924        let header = EncryptedHeader::from_bytes(&ciphertext).unwrap();
925        assert_eq!(header.algorithm, Algorithm::ChaCha20Poly1305);
926
927        let decrypted = manager.decrypt(&ciphertext, lsn).unwrap();
928        assert_eq!(decrypted, plaintext);
929    }
930
931    #[test]
932    fn test_key_rotation() {
933        let config = EncryptionConfig {
934            enabled: true,
935            algorithm: Algorithm::Aes256Gcm,
936            key_provider: KeyProvider::InMemory(vec![1u8; 32]),
937            key_rotation_days: 30,
938            aad_scope: "test".to_string(),
939        };
940        let manager = EncryptionManager::new(config).unwrap();
941
942        // Encrypt with version 1
943        let plaintext = b"data encrypted with key v1";
944        let ct_v1 = manager.encrypt(plaintext, 100).unwrap();
945        assert_eq!(manager.key_version(), 1);
946
947        // Rotate to version 2
948        let new_master = MasterKey::new(vec![2u8; 32], 2).unwrap();
949        manager.rotate_key(new_master).unwrap();
950        assert_eq!(manager.key_version(), 2);
951
952        // Old ciphertext (v1) still decryptable
953        let decrypted = manager.decrypt(&ct_v1, 100).unwrap();
954        assert_eq!(decrypted, plaintext);
955
956        // Rotation with same or lower version should fail
957        let bad_master = MasterKey::new(vec![3u8; 32], 1).unwrap();
958        assert!(manager.rotate_key(bad_master).is_err());
959    }
960}