Skip to main content

egide_transit/
lib.rs

1//! # Egide Transit Engine
2//!
3//! Encryption as a Service - applications encrypt/decrypt without seeing keys.
4//!
5//! ## Features
6//!
7//! - Encrypt/Decrypt data via API without exposing keys
8//! - Key versioning with rotation support
9//! - Rewrap (re-encrypt with latest key version)
10//! - Datakey generation for envelope encryption
11//!
12//! ## Ciphertext Format
13//!
14//! Ciphertexts are encoded as: `egide:v{version}:{base64_ciphertext}`
15//!
16//! This allows the engine to determine which key version to use for decryption.
17
18#![forbid(unsafe_code)]
19
20pub mod error;
21
22pub use error::TransitError;
23
24use std::path::Path;
25use std::str::FromStr;
26
27use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
28use serde::{Deserialize, Serialize};
29use tracing::{debug, info, warn};
30
31use egide_crypto::{aead, kdf, random, MasterKey};
32use egide_storage_sqlite::SqliteBackend;
33
34// ============================================================================
35// SQL Schema
36// ============================================================================
37
38const SCHEMA: &str = r"
39CREATE TABLE IF NOT EXISTS transit_keys (
40    name            TEXT PRIMARY KEY,
41    key_type        TEXT NOT NULL,
42    latest_version  INTEGER NOT NULL DEFAULT 1,
43    min_encryption_version INTEGER NOT NULL DEFAULT 1,
44    min_decryption_version INTEGER NOT NULL DEFAULT 1,
45    supports_encryption INTEGER NOT NULL DEFAULT 1,
46    supports_decryption INTEGER NOT NULL DEFAULT 1,
47    supports_derivation INTEGER NOT NULL DEFAULT 0,
48    exportable      INTEGER NOT NULL DEFAULT 0,
49    deletion_allowed INTEGER NOT NULL DEFAULT 0,
50    created_at      INTEGER NOT NULL,
51    updated_at      INTEGER NOT NULL
52);
53
54CREATE TABLE IF NOT EXISTS transit_key_versions (
55    name            TEXT NOT NULL,
56    version         INTEGER NOT NULL,
57    key_material    TEXT NOT NULL,
58    nonce           TEXT NOT NULL,
59    created_at      INTEGER NOT NULL,
60    PRIMARY KEY (name, version),
61    FOREIGN KEY (name) REFERENCES transit_keys(name) ON DELETE CASCADE
62);
63
64CREATE INDEX IF NOT EXISTS idx_transit_key_versions_name ON transit_key_versions(name);
65";
66
67// ============================================================================
68// Types
69// ============================================================================
70
71/// Supported key types for transit encryption.
72#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(rename_all = "kebab-case")]
74pub enum KeyType {
75    /// AES-256-GCM (default, widely compatible).
76    #[default]
77    Aes256Gcm,
78    /// ChaCha20-Poly1305 (fast on systems without AES-NI).
79    ChaCha20Poly1305,
80}
81
82impl std::fmt::Display for KeyType {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        match self {
85            Self::Aes256Gcm => write!(f, "aes256-gcm"),
86            Self::ChaCha20Poly1305 => write!(f, "chacha20-poly1305"),
87        }
88    }
89}
90
91impl FromStr for KeyType {
92    type Err = TransitError;
93
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        match s {
96            "aes256-gcm" => Ok(Self::Aes256Gcm),
97            "chacha20-poly1305" => Ok(Self::ChaCha20Poly1305),
98            _ => Err(TransitError::InvalidKeyType(s.to_string())),
99        }
100    }
101}
102
103/// Configuration for creating a new transit key.
104// Each bool maps to a distinct, independently togglable capability flag; a state machine would
105// add indirection without clarifying intent here.
106#[allow(clippy::struct_excessive_bools)]
107#[derive(Debug, Clone, Default)]
108pub struct KeyConfig {
109    /// Key type (default: AES-256-GCM).
110    pub key_type: KeyType,
111    /// Allow encryption operations (default: true).
112    pub supports_encryption: bool,
113    /// Allow decryption operations (default: true).
114    pub supports_decryption: bool,
115    /// Allow key derivation (default: false).
116    pub supports_derivation: bool,
117    /// Allow key export (default: false).
118    pub exportable: bool,
119    /// Allow key deletion (default: false).
120    pub deletion_allowed: bool,
121}
122
123impl KeyConfig {
124    /// Creates a new `KeyConfig` with sensible defaults.
125    #[must_use]
126    pub fn new() -> Self {
127        Self {
128            key_type: KeyType::default(),
129            supports_encryption: true,
130            supports_decryption: true,
131            supports_derivation: false,
132            exportable: false,
133            deletion_allowed: false,
134        }
135    }
136}
137
138/// Metadata about a transit key.
139// Each bool maps to a distinct, independently togglable capability; refactoring into enums
140// would mirror the same information with more boilerplate and no semantic gain.
141#[allow(clippy::struct_excessive_bools)]
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct TransitKey {
144    /// Key name.
145    pub name: String,
146    /// Key type.
147    pub key_type: KeyType,
148    /// Latest (current) version number.
149    pub latest_version: u32,
150    /// Minimum version allowed for encryption.
151    pub min_encryption_version: u32,
152    /// Minimum version allowed for decryption.
153    pub min_decryption_version: u32,
154    /// Whether encryption is supported.
155    pub supports_encryption: bool,
156    /// Whether decryption is supported.
157    pub supports_decryption: bool,
158    /// Whether key derivation is supported.
159    pub supports_derivation: bool,
160    /// Whether the key can be exported.
161    pub exportable: bool,
162    /// Whether the key can be deleted.
163    pub deletion_allowed: bool,
164    /// Creation timestamp (Unix seconds).
165    pub created_at: u64,
166    /// Last update timestamp (Unix seconds).
167    pub updated_at: u64,
168}
169
170/// Information about a specific key version.
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct KeyVersionInfo {
173    /// Version number.
174    pub version: u32,
175    /// Creation timestamp (Unix seconds).
176    pub created_at: u64,
177}
178
179/// Result of a datakey generation.
180#[derive(Debug, Clone)]
181pub struct DataKey {
182    /// Plaintext key (32 bytes for use by the client).
183    pub plaintext: Vec<u8>,
184    /// Wrapped (encrypted) key for storage.
185    pub ciphertext: String,
186}
187
188// ============================================================================
189// Hex Encoding Helpers
190// ============================================================================
191
192fn hex_encode(data: &[u8]) -> String {
193    use std::fmt::Write as _;
194    data.iter()
195        .fold(String::with_capacity(data.len() * 2), |mut s, b| {
196            write!(s, "{b:02x}").expect("writing to String is infallible");
197            s
198        })
199}
200
201fn hex_decode(s: &str) -> Result<Vec<u8>, TransitError> {
202    if !s.len().is_multiple_of(2) {
203        return Err(TransitError::Storage("invalid hex length".into()));
204    }
205    (0..s.len())
206        .step_by(2)
207        .map(|i| {
208            u8::from_str_radix(&s[i..i + 2], 16)
209                .map_err(|_| TransitError::Storage("invalid hex".into()))
210        })
211        .collect()
212}
213
214// ============================================================================
215// Transit Engine
216// ============================================================================
217
218/// The Transit Engine provides encryption-as-a-service.
219///
220/// Applications can encrypt and decrypt data without ever seeing the keys.
221pub struct TransitEngine {
222    storage: SqliteBackend,
223    master_key: MasterKey,
224}
225
226impl TransitEngine {
227    /// Creates a new `TransitEngine` with the given storage path and master key.
228    pub async fn new(
229        data_path: impl AsRef<Path>,
230        master_key: MasterKey,
231    ) -> Result<Self, TransitError> {
232        let storage = SqliteBackend::open(data_path.as_ref(), "transit")
233            .await
234            .map_err(|e| TransitError::Storage(e.to_string()))?;
235
236        // Initialize schema
237        storage
238            .execute_raw(SCHEMA)
239            .await
240            .map_err(|e| TransitError::Storage(e.to_string()))?;
241
242        info!("Transit engine initialized");
243
244        Ok(Self {
245            storage,
246            master_key,
247        })
248    }
249
250    // ========================================================================
251    // Key Derivation & Encryption Helpers
252    // ========================================================================
253
254    /// Derives a unique encryption key for a transit key version.
255    fn derive_version_key(&self, name: &str, version: u32) -> Result<[u8; 32], TransitError> {
256        let info = format!("egide-transit-v1:{name}:{version}");
257        let key = kdf::derive_encryption_key(self.master_key.as_bytes(), info.as_bytes())?;
258        Ok(*key)
259    }
260
261    /// Encrypts raw key material for storage.
262    fn encrypt_key_material(
263        &self,
264        name: &str,
265        version: u32,
266        key: &[u8],
267    ) -> Result<(Vec<u8>, Vec<u8>), TransitError> {
268        let wrapping_key = self.derive_version_key(name, version)?;
269        let aad = format!("transit-key:{name}:{version}");
270        let ciphertext = aead::encrypt(&wrapping_key, key, Some(aad.as_bytes()))?;
271
272        // Split nonce (first 12 bytes) from ciphertext
273        let nonce = ciphertext[..12].to_vec();
274        let encrypted = ciphertext[12..].to_vec();
275
276        Ok((encrypted, nonce))
277    }
278
279    /// Decrypts stored key material.
280    fn decrypt_key_material(
281        &self,
282        name: &str,
283        version: u32,
284        encrypted: &[u8],
285        nonce: &[u8],
286    ) -> Result<Vec<u8>, TransitError> {
287        let wrapping_key = self.derive_version_key(name, version)?;
288        let aad = format!("transit-key:{name}:{version}");
289
290        // Reconstruct ciphertext with nonce prefix
291        let mut ciphertext = Vec::with_capacity(nonce.len() + encrypted.len());
292        ciphertext.extend_from_slice(nonce);
293        ciphertext.extend_from_slice(encrypted);
294
295        let decrypted = aead::decrypt(&wrapping_key, &ciphertext, Some(aad.as_bytes()))?;
296        Ok(decrypted.to_vec())
297    }
298
299    /// Gets the raw key material for a specific version.
300    async fn get_key_material(&self, name: &str, version: u32) -> Result<Vec<u8>, TransitError> {
301        let row = self
302            .storage
303            .query_one::<(String, String)>(
304                "SELECT key_material, nonce FROM transit_key_versions WHERE name = ? AND version = ?",
305                &[name, &version.to_string()],
306            )
307            .await
308            .map_err(|e| TransitError::Storage(e.to_string()))?
309            .ok_or_else(|| TransitError::VersionNotFound {
310                name: name.to_string(),
311                version,
312            })?;
313
314        let (key_material_hex, nonce_hex) = row;
315        let key_material = hex_decode(&key_material_hex)?;
316        let nonce = hex_decode(&nonce_hex)?;
317
318        self.decrypt_key_material(name, version, &key_material, &nonce)
319    }
320
321    // ========================================================================
322    // Timestamp Helper
323    // ========================================================================
324
325    fn now() -> u64 {
326        std::time::SystemTime::now()
327            .duration_since(std::time::UNIX_EPOCH)
328            .expect("system time before UNIX epoch")
329            .as_secs()
330    }
331
332    // ========================================================================
333    // Key Name Validation
334    // ========================================================================
335
336    fn validate_name(name: &str) -> Result<(), TransitError> {
337        if name.is_empty() {
338            return Err(TransitError::InvalidKeyName("name cannot be empty".into()));
339        }
340        if name.len() > 128 {
341            return Err(TransitError::InvalidKeyName(
342                "name too long (max 128 chars)".into(),
343            ));
344        }
345        if !name
346            .chars()
347            .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
348        {
349            return Err(TransitError::InvalidKeyName(
350                "name can only contain alphanumeric, dash, underscore".into(),
351            ));
352        }
353        Ok(())
354    }
355
356    // ========================================================================
357    // Key Management Operations
358    // ========================================================================
359
360    /// Creates a new transit key.
361    pub async fn create_key(
362        &self,
363        name: &str,
364        config: KeyConfig,
365    ) -> Result<TransitKey, TransitError> {
366        Self::validate_name(name)?;
367
368        // Check if key already exists
369        let existing = self
370            .storage
371            .query_one::<(String,)>("SELECT name FROM transit_keys WHERE name = ?", &[name])
372            .await
373            .map_err(|e| TransitError::Storage(e.to_string()))?;
374
375        if existing.is_some() {
376            return Err(TransitError::KeyExists(name.to_string()));
377        }
378
379        let now = Self::now();
380
381        // Generate initial key material (32 bytes for AES-256 or ChaCha20)
382        let raw_key = random::generate_key()?;
383        let (encrypted_key, nonce) = self.encrypt_key_material(name, 1, raw_key.as_ref())?;
384
385        // Insert key metadata
386        self.storage
387            .execute(
388                "INSERT INTO transit_keys (name, key_type, latest_version, min_encryption_version, min_decryption_version, supports_encryption, supports_decryption, supports_derivation, exportable, deletion_allowed, created_at, updated_at) VALUES (?, ?, 1, 1, 1, ?, ?, ?, ?, ?, ?, ?)",
389                &[
390                    name,
391                    &config.key_type.to_string(),
392                    &i32::from(config.supports_encryption).to_string(),
393                    &i32::from(config.supports_decryption).to_string(),
394                    &i32::from(config.supports_derivation).to_string(),
395                    &i32::from(config.exportable).to_string(),
396                    &i32::from(config.deletion_allowed).to_string(),
397                    &now.to_string(),
398                    &now.to_string(),
399                ],
400            )
401            .await
402            .map_err(|e| TransitError::Storage(e.to_string()))?;
403
404        // Insert initial key version
405        self.storage
406            .execute(
407                "INSERT INTO transit_key_versions (name, version, key_material, nonce, created_at) VALUES (?, 1, ?, ?, ?)",
408                &[
409                    name,
410                    &hex_encode(&encrypted_key),
411                    &hex_encode(&nonce),
412                    &now.to_string(),
413                ],
414            )
415            .await
416            .map_err(|e| TransitError::Storage(e.to_string()))?;
417
418        info!(name = name, key_type = %config.key_type, "Transit key created");
419
420        Ok(TransitKey {
421            name: name.to_string(),
422            key_type: config.key_type,
423            latest_version: 1,
424            min_encryption_version: 1,
425            min_decryption_version: 1,
426            supports_encryption: config.supports_encryption,
427            supports_decryption: config.supports_decryption,
428            supports_derivation: config.supports_derivation,
429            exportable: config.exportable,
430            deletion_allowed: config.deletion_allowed,
431            created_at: now,
432            updated_at: now,
433        })
434    }
435
436    /// Gets metadata for a transit key.
437    pub async fn get_key(&self, name: &str) -> Result<TransitKey, TransitError> {
438        Self::validate_name(name)?;
439
440        let row = self
441            .storage
442            .query_one::<(String, String, String, String, String, String, String, String, String, String, String, String)>(
443                "SELECT name, key_type, CAST(latest_version AS TEXT), CAST(min_encryption_version AS TEXT), CAST(min_decryption_version AS TEXT), CAST(supports_encryption AS TEXT), CAST(supports_decryption AS TEXT), CAST(supports_derivation AS TEXT), CAST(exportable AS TEXT), CAST(deletion_allowed AS TEXT), CAST(created_at AS TEXT), CAST(updated_at AS TEXT) FROM transit_keys WHERE name = ?",
444                &[name],
445            )
446            .await
447            .map_err(|e| TransitError::Storage(e.to_string()))?
448            .ok_or_else(|| TransitError::KeyNotFound(name.to_string()))?;
449
450        let (
451            name,
452            key_type,
453            latest_version,
454            min_enc,
455            min_dec,
456            enc,
457            dec,
458            deriv,
459            export,
460            del,
461            created,
462            updated,
463        ) = row;
464
465        Ok(TransitKey {
466            name,
467            key_type: key_type.parse()?,
468            latest_version: latest_version.parse().unwrap_or(1),
469            min_encryption_version: min_enc.parse().unwrap_or(1),
470            min_decryption_version: min_dec.parse().unwrap_or(1),
471            supports_encryption: enc.parse::<i32>().unwrap_or(1) != 0,
472            supports_decryption: dec.parse::<i32>().unwrap_or(1) != 0,
473            supports_derivation: deriv.parse::<i32>().unwrap_or(0) != 0,
474            exportable: export.parse::<i32>().unwrap_or(0) != 0,
475            deletion_allowed: del.parse::<i32>().unwrap_or(0) != 0,
476            created_at: created.parse().unwrap_or(0),
477            updated_at: updated.parse().unwrap_or(0),
478        })
479    }
480
481    /// Lists all transit key names.
482    pub async fn list_keys(&self) -> Result<Vec<String>, TransitError> {
483        let rows = self
484            .storage
485            .query_all::<(String,)>("SELECT name FROM transit_keys ORDER BY name", &[])
486            .await
487            .map_err(|e| TransitError::Storage(e.to_string()))?;
488
489        Ok(rows.into_iter().map(|(name,)| name).collect())
490    }
491
492    /// Lists all versions of a key.
493    pub async fn list_versions(&self, name: &str) -> Result<Vec<KeyVersionInfo>, TransitError> {
494        Self::validate_name(name)?;
495
496        // Verify key exists
497        let _ = self.get_key(name).await?;
498
499        let rows = self
500            .storage
501            .query_all::<(String, String)>(
502                "SELECT CAST(version AS TEXT), CAST(created_at AS TEXT) FROM transit_key_versions WHERE name = ? ORDER BY version DESC",
503                &[name],
504            )
505            .await
506            .map_err(|e| TransitError::Storage(e.to_string()))?;
507
508        Ok(rows
509            .into_iter()
510            .map(|(version, created_at)| KeyVersionInfo {
511                version: version.parse().unwrap_or(0),
512                created_at: created_at.parse().unwrap_or(0),
513            })
514            .collect())
515    }
516
517    /// Rotates a key to a new version.
518    pub async fn rotate_key(&self, name: &str) -> Result<u32, TransitError> {
519        Self::validate_name(name)?;
520
521        let key = self.get_key(name).await?;
522        let new_version = key.latest_version + 1;
523        let now = Self::now();
524
525        // Generate new key material
526        let raw_key = random::generate_key()?;
527        let (encrypted_key, nonce) =
528            self.encrypt_key_material(name, new_version, raw_key.as_ref())?;
529
530        // Insert new version
531        self.storage
532            .execute(
533                "INSERT INTO transit_key_versions (name, version, key_material, nonce, created_at) VALUES (?, ?, ?, ?, ?)",
534                &[
535                    name,
536                    &new_version.to_string(),
537                    &hex_encode(&encrypted_key),
538                    &hex_encode(&nonce),
539                    &now.to_string(),
540                ],
541            )
542            .await
543            .map_err(|e| TransitError::Storage(e.to_string()))?;
544
545        // Update latest version
546        self.storage
547            .execute(
548                "UPDATE transit_keys SET latest_version = ?, updated_at = ? WHERE name = ?",
549                &[&new_version.to_string(), &now.to_string(), name],
550            )
551            .await
552            .map_err(|e| TransitError::Storage(e.to_string()))?;
553
554        info!(name = name, version = new_version, "Transit key rotated");
555
556        Ok(new_version)
557    }
558
559    /// Deletes a transit key (if deletion is allowed).
560    pub async fn delete_key(&self, name: &str) -> Result<(), TransitError> {
561        Self::validate_name(name)?;
562
563        let key = self.get_key(name).await?;
564
565        if !key.deletion_allowed {
566            return Err(TransitError::DeletionNotAllowed(name.to_string()));
567        }
568
569        // Delete versions first (foreign key)
570        self.storage
571            .execute("DELETE FROM transit_key_versions WHERE name = ?", &[name])
572            .await
573            .map_err(|e| TransitError::Storage(e.to_string()))?;
574
575        // Delete key
576        self.storage
577            .execute("DELETE FROM transit_keys WHERE name = ?", &[name])
578            .await
579            .map_err(|e| TransitError::Storage(e.to_string()))?;
580
581        warn!(name = name, "Transit key deleted");
582
583        Ok(())
584    }
585
586    /// Updates key configuration (min versions, etc.).
587    pub async fn update_key_config(
588        &self,
589        name: &str,
590        min_encryption_version: Option<u32>,
591        min_decryption_version: Option<u32>,
592        deletion_allowed: Option<bool>,
593    ) -> Result<(), TransitError> {
594        Self::validate_name(name)?;
595
596        let key = self.get_key(name).await?;
597        let now = Self::now();
598
599        let min_enc = min_encryption_version.unwrap_or(key.min_encryption_version);
600        let min_dec = min_decryption_version.unwrap_or(key.min_decryption_version);
601        let del = deletion_allowed.unwrap_or(key.deletion_allowed);
602
603        // Validate: min versions cannot exceed latest version
604        if min_enc > key.latest_version {
605            return Err(TransitError::VersionNotFound {
606                name: name.to_string(),
607                version: min_enc,
608            });
609        }
610        if min_dec > key.latest_version {
611            return Err(TransitError::VersionNotFound {
612                name: name.to_string(),
613                version: min_dec,
614            });
615        }
616
617        self.storage
618            .execute(
619                "UPDATE transit_keys SET min_encryption_version = ?, min_decryption_version = ?, deletion_allowed = ?, updated_at = ? WHERE name = ?",
620                &[
621                    &min_enc.to_string(),
622                    &min_dec.to_string(),
623                    &i32::from(del).to_string(),
624                    &now.to_string(),
625                    name,
626                ],
627            )
628            .await
629            .map_err(|e| TransitError::Storage(e.to_string()))?;
630
631        debug!(name = name, "Transit key config updated");
632
633        Ok(())
634    }
635
636    // ========================================================================
637    // Encryption/Decryption Operations
638    // ========================================================================
639
640    /// Encrypts plaintext using the latest version of a key.
641    ///
642    /// Returns ciphertext in format: `egide:v{version}:{base64}`
643    pub async fn encrypt(&self, name: &str, plaintext: &[u8]) -> Result<String, TransitError> {
644        let key = self.get_key(name).await?;
645
646        if !key.supports_encryption {
647            return Err(TransitError::OperationNotAllowed(
648                "encryption not allowed for this key".into(),
649            ));
650        }
651
652        self.encrypt_with_version(name, plaintext, key.latest_version)
653            .await
654    }
655
656    /// Encrypts plaintext using a specific key version.
657    pub async fn encrypt_with_version(
658        &self,
659        name: &str,
660        plaintext: &[u8],
661        version: u32,
662    ) -> Result<String, TransitError> {
663        let key = self.get_key(name).await?;
664
665        if !key.supports_encryption {
666            return Err(TransitError::OperationNotAllowed(
667                "encryption not allowed for this key".into(),
668            ));
669        }
670
671        if version < key.min_encryption_version {
672            return Err(TransitError::VersionBelowMinEncryption {
673                version,
674                min: key.min_encryption_version,
675            });
676        }
677
678        if version > key.latest_version {
679            return Err(TransitError::VersionNotFound {
680                name: name.to_string(),
681                version,
682            });
683        }
684
685        // Get the raw key material
686        let raw_key = self.get_key_material(name, version).await?;
687
688        // Encrypt with AAD containing key name for domain separation
689        let aad = format!("egide-transit:{name}:{version}");
690        let ciphertext = aead::encrypt(&raw_key, plaintext, Some(aad.as_bytes()))?;
691
692        // Format: egide:v{version}:{base64}
693        let encoded = BASE64.encode(&ciphertext);
694        Ok(format!("egide:v{version}:{encoded}"))
695    }
696
697    /// Decrypts ciphertext.
698    ///
699    /// Automatically determines the key version from the ciphertext format.
700    pub async fn decrypt(&self, name: &str, ciphertext: &str) -> Result<Vec<u8>, TransitError> {
701        let key = self.get_key(name).await?;
702
703        if !key.supports_decryption {
704            return Err(TransitError::OperationNotAllowed(
705                "decryption not allowed for this key".into(),
706            ));
707        }
708
709        // Parse ciphertext format: egide:v{version}:{base64}
710        let (version, data) = Self::parse_ciphertext(ciphertext)?;
711
712        if version < key.min_decryption_version {
713            return Err(TransitError::VersionBelowMinDecryption {
714                version,
715                min: key.min_decryption_version,
716            });
717        }
718
719        // Get the raw key material for this version
720        let raw_key = self.get_key_material(name, version).await?;
721
722        // Decrypt with AAD
723        let aad = format!("egide-transit:{name}:{version}");
724        let decrypted = aead::decrypt(&raw_key, &data, Some(aad.as_bytes()))
725            .map_err(|_| TransitError::DecryptionFailed)?;
726        Ok(decrypted.to_vec())
727    }
728
729    /// Parses the ciphertext format and extracts version and raw data.
730    fn parse_ciphertext(ciphertext: &str) -> Result<(u32, Vec<u8>), TransitError> {
731        let parts: Vec<&str> = ciphertext.splitn(3, ':').collect();
732
733        if parts.len() != 3 || parts[0] != "egide" {
734            return Err(TransitError::InvalidCiphertext);
735        }
736
737        let version_str = parts[1]
738            .strip_prefix('v')
739            .ok_or(TransitError::InvalidCiphertext)?;
740        let version: u32 = version_str
741            .parse()
742            .map_err(|_| TransitError::InvalidCiphertext)?;
743
744        let data = BASE64
745            .decode(parts[2])
746            .map_err(|_| TransitError::InvalidCiphertext)?;
747
748        Ok((version, data))
749    }
750
751    /// Rewraps ciphertext with the latest key version.
752    ///
753    /// This decrypts and re-encrypts without exposing plaintext to the caller.
754    pub async fn rewrap(&self, name: &str, ciphertext: &str) -> Result<String, TransitError> {
755        let key = self.get_key(name).await?;
756
757        // Parse to get current version
758        let (current_version, _) = Self::parse_ciphertext(ciphertext)?;
759
760        // If already at latest version, return as-is
761        if current_version == key.latest_version {
762            return Ok(ciphertext.to_string());
763        }
764
765        // Decrypt with old version, encrypt with new
766        let plaintext = self.decrypt(name, ciphertext).await?;
767        self.encrypt(name, &plaintext).await
768    }
769
770    // ========================================================================
771    // Datakey Generation
772    // ========================================================================
773
774    /// Generates a new data encryption key (DEK).
775    ///
776    /// Returns both the plaintext key (for immediate use) and the wrapped key
777    /// (for storage). The plaintext key should be used and then discarded.
778    pub async fn generate_datakey(&self, name: &str) -> Result<DataKey, TransitError> {
779        let key = self.get_key(name).await?;
780
781        if !key.supports_encryption {
782            return Err(TransitError::OperationNotAllowed(
783                "datakey generation requires encryption capability".into(),
784            ));
785        }
786
787        // Generate a random 32-byte key
788        let plaintext_key = random::generate_key()?;
789
790        // Wrap it with the transit key
791        let wrapped = self.encrypt(name, plaintext_key.as_ref()).await?;
792
793        Ok(DataKey {
794            plaintext: plaintext_key.to_vec(),
795            ciphertext: wrapped,
796        })
797    }
798
799    /// Decrypts a wrapped data key.
800    pub async fn decrypt_datakey(
801        &self,
802        name: &str,
803        wrapped: &str,
804    ) -> Result<Vec<u8>, TransitError> {
805        self.decrypt(name, wrapped).await
806    }
807}
808
809// ============================================================================
810// Tests
811// ============================================================================
812
813#[cfg(test)]
814#[allow(clippy::disallowed_methods)]
815mod tests {
816    use super::*;
817    use tempfile::TempDir;
818
819    async fn setup() -> (TempDir, TransitEngine) {
820        let tmp = TempDir::new().unwrap();
821        let master_key = MasterKey::generate().unwrap();
822        let engine = TransitEngine::new(tmp.path(), master_key).await.unwrap();
823        (tmp, engine)
824    }
825
826    #[tokio::test]
827    async fn test_create_and_get_key() {
828        let (_tmp, engine) = setup().await;
829
830        let key = engine.create_key("my-key", KeyConfig::new()).await.unwrap();
831        assert_eq!(key.name, "my-key");
832        assert_eq!(key.key_type, KeyType::Aes256Gcm);
833        assert_eq!(key.latest_version, 1);
834        assert!(key.supports_encryption);
835        assert!(key.supports_decryption);
836
837        let retrieved = engine.get_key("my-key").await.unwrap();
838        assert_eq!(retrieved.name, "my-key");
839    }
840
841    #[tokio::test]
842    async fn test_key_already_exists() {
843        let (_tmp, engine) = setup().await;
844
845        engine
846            .create_key("dup-key", KeyConfig::new())
847            .await
848            .unwrap();
849        let result = engine.create_key("dup-key", KeyConfig::new()).await;
850        assert!(matches!(result, Err(TransitError::KeyExists(_))));
851    }
852
853    #[tokio::test]
854    async fn test_key_not_found() {
855        let (_tmp, engine) = setup().await;
856
857        let result = engine.get_key("nonexistent").await;
858        assert!(matches!(result, Err(TransitError::KeyNotFound(_))));
859    }
860
861    #[tokio::test]
862    async fn test_invalid_key_name() {
863        let (_tmp, engine) = setup().await;
864
865        let result = engine.create_key("", KeyConfig::new()).await;
866        assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
867
868        let result = engine.create_key("key with spaces", KeyConfig::new()).await;
869        assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
870    }
871
872    #[tokio::test]
873    async fn test_list_keys() {
874        let (_tmp, engine) = setup().await;
875
876        engine.create_key("alpha", KeyConfig::new()).await.unwrap();
877        engine.create_key("beta", KeyConfig::new()).await.unwrap();
878        engine.create_key("gamma", KeyConfig::new()).await.unwrap();
879
880        let keys = engine.list_keys().await.unwrap();
881        assert_eq!(keys, vec!["alpha", "beta", "gamma"]);
882    }
883
884    #[tokio::test]
885    async fn test_encrypt_decrypt() {
886        let (_tmp, engine) = setup().await;
887
888        engine
889            .create_key("enc-key", KeyConfig::new())
890            .await
891            .unwrap();
892
893        let plaintext = b"Hello, World!";
894        let ciphertext = engine.encrypt("enc-key", plaintext).await.unwrap();
895
896        assert!(ciphertext.starts_with("egide:v1:"));
897
898        let decrypted = engine.decrypt("enc-key", &ciphertext).await.unwrap();
899        assert_eq!(decrypted, plaintext);
900    }
901
902    #[tokio::test]
903    async fn test_encrypt_decrypt_large_data() {
904        let (_tmp, engine) = setup().await;
905
906        engine
907            .create_key("large-key", KeyConfig::new())
908            .await
909            .unwrap();
910
911        // i % 256 is always in [0, 255]; the cast is intentionally safe.
912        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
913        let plaintext: Vec<u8> = (0..10000).map(|i| (i % 256) as u8).collect();
914        let ciphertext = engine.encrypt("large-key", &plaintext).await.unwrap();
915        let decrypted = engine.decrypt("large-key", &ciphertext).await.unwrap();
916
917        assert_eq!(decrypted, plaintext);
918    }
919
920    #[tokio::test]
921    async fn test_key_rotation() {
922        let (_tmp, engine) = setup().await;
923
924        engine
925            .create_key("rotate-key", KeyConfig::new())
926            .await
927            .unwrap();
928
929        // Encrypt with v1
930        let ciphertext_v1 = engine.encrypt("rotate-key", b"secret").await.unwrap();
931        assert!(ciphertext_v1.starts_with("egide:v1:"));
932
933        // Rotate
934        let new_version = engine.rotate_key("rotate-key").await.unwrap();
935        assert_eq!(new_version, 2);
936
937        // Encrypt with v2
938        let ciphertext_v2 = engine.encrypt("rotate-key", b"secret").await.unwrap();
939        assert!(ciphertext_v2.starts_with("egide:v2:"));
940
941        // Both should still decrypt
942        let decrypted_v1 = engine.decrypt("rotate-key", &ciphertext_v1).await.unwrap();
943        let decrypted_v2 = engine.decrypt("rotate-key", &ciphertext_v2).await.unwrap();
944        assert_eq!(decrypted_v1, b"secret");
945        assert_eq!(decrypted_v2, b"secret");
946    }
947
948    #[tokio::test]
949    async fn test_rewrap() {
950        let (_tmp, engine) = setup().await;
951
952        engine
953            .create_key("rewrap-key", KeyConfig::new())
954            .await
955            .unwrap();
956
957        let ciphertext_v1 = engine.encrypt("rewrap-key", b"data").await.unwrap();
958        assert!(ciphertext_v1.starts_with("egide:v1:"));
959
960        // Rotate key
961        engine.rotate_key("rewrap-key").await.unwrap();
962
963        // Rewrap
964        let ciphertext_v2 = engine.rewrap("rewrap-key", &ciphertext_v1).await.unwrap();
965        assert!(ciphertext_v2.starts_with("egide:v2:"));
966
967        // Verify data unchanged
968        let decrypted = engine.decrypt("rewrap-key", &ciphertext_v2).await.unwrap();
969        assert_eq!(decrypted, b"data");
970    }
971
972    #[tokio::test]
973    async fn test_min_decryption_version() {
974        let (_tmp, engine) = setup().await;
975
976        engine
977            .create_key("min-dec", KeyConfig::new())
978            .await
979            .unwrap();
980
981        let ciphertext_v1 = engine.encrypt("min-dec", b"old").await.unwrap();
982
983        // Rotate and update min_decryption_version
984        engine.rotate_key("min-dec").await.unwrap();
985        engine
986            .update_key_config("min-dec", None, Some(2), None)
987            .await
988            .unwrap();
989
990        // v1 ciphertext should fail
991        let result = engine.decrypt("min-dec", &ciphertext_v1).await;
992        assert!(matches!(
993            result,
994            Err(TransitError::VersionBelowMinDecryption { .. })
995        ));
996    }
997
998    #[tokio::test]
999    async fn test_delete_key() {
1000        let (_tmp, engine) = setup().await;
1001
1002        // Create with deletion_allowed = false (default)
1003        engine
1004            .create_key("no-delete", KeyConfig::new())
1005            .await
1006            .unwrap();
1007        let result = engine.delete_key("no-delete").await;
1008        assert!(matches!(result, Err(TransitError::DeletionNotAllowed(_))));
1009
1010        // Create with deletion_allowed = true
1011        let mut config = KeyConfig::new();
1012        config.deletion_allowed = true;
1013        engine.create_key("can-delete", config).await.unwrap();
1014        engine.delete_key("can-delete").await.unwrap();
1015
1016        let result = engine.get_key("can-delete").await;
1017        assert!(matches!(result, Err(TransitError::KeyNotFound(_))));
1018    }
1019
1020    #[tokio::test]
1021    async fn test_generate_datakey() {
1022        let (_tmp, engine) = setup().await;
1023
1024        engine
1025            .create_key("dek-key", KeyConfig::new())
1026            .await
1027            .unwrap();
1028
1029        let datakey = engine.generate_datakey("dek-key").await.unwrap();
1030        assert_eq!(datakey.plaintext.len(), 32);
1031        assert!(datakey.ciphertext.starts_with("egide:v1:"));
1032
1033        // Verify we can decrypt the wrapped key
1034        let decrypted = engine
1035            .decrypt_datakey("dek-key", &datakey.ciphertext)
1036            .await
1037            .unwrap();
1038        assert_eq!(decrypted, datakey.plaintext);
1039    }
1040
1041    #[tokio::test]
1042    async fn test_encryption_disabled() {
1043        let (_tmp, engine) = setup().await;
1044
1045        let mut config = KeyConfig::new();
1046        config.supports_encryption = false;
1047
1048        engine.create_key("no-enc", config).await.unwrap();
1049
1050        let result = engine.encrypt("no-enc", b"data").await;
1051        assert!(matches!(result, Err(TransitError::OperationNotAllowed(_))));
1052    }
1053
1054    #[tokio::test]
1055    async fn test_decryption_disabled() {
1056        let (_tmp, engine) = setup().await;
1057
1058        // Create key, encrypt something, then test with a decrypt-disabled key
1059        engine
1060            .create_key("enc-only", KeyConfig::new())
1061            .await
1062            .unwrap();
1063        let ciphertext = engine.encrypt("enc-only", b"data").await.unwrap();
1064
1065        // Create a new engine to test with a decrypt-disabled key
1066        let (_tmp2, engine2) = setup().await;
1067        let mut config = KeyConfig::new();
1068        config.supports_decryption = false;
1069        engine2.create_key("no-dec", config).await.unwrap();
1070
1071        let result = engine2.decrypt("no-dec", &ciphertext).await;
1072        assert!(matches!(result, Err(TransitError::OperationNotAllowed(_))));
1073    }
1074
1075    #[tokio::test]
1076    async fn test_invalid_ciphertext() {
1077        let (_tmp, engine) = setup().await;
1078
1079        engine
1080            .create_key("test-key", KeyConfig::new())
1081            .await
1082            .unwrap();
1083
1084        let result = engine.decrypt("test-key", "invalid").await;
1085        assert!(matches!(result, Err(TransitError::InvalidCiphertext)));
1086
1087        let result = engine.decrypt("test-key", "not:a:valid:format").await;
1088        assert!(matches!(result, Err(TransitError::InvalidCiphertext)));
1089
1090        let result = engine
1091            .decrypt("test-key", "egide:v1:!!!invalid-base64!!!")
1092            .await;
1093        assert!(matches!(result, Err(TransitError::InvalidCiphertext)));
1094    }
1095
1096    #[tokio::test]
1097    async fn test_key_isolation() {
1098        let (_tmp, engine) = setup().await;
1099
1100        engine.create_key("key-a", KeyConfig::new()).await.unwrap();
1101        engine.create_key("key-b", KeyConfig::new()).await.unwrap();
1102
1103        let ciphertext = engine.encrypt("key-a", b"secret").await.unwrap();
1104
1105        // Should fail when trying to decrypt with different key
1106        let result = engine.decrypt("key-b", &ciphertext).await;
1107        assert!(matches!(result, Err(TransitError::DecryptionFailed)));
1108    }
1109
1110    #[tokio::test]
1111    async fn test_list_versions() {
1112        let (_tmp, engine) = setup().await;
1113
1114        engine
1115            .create_key("ver-key", KeyConfig::new())
1116            .await
1117            .unwrap();
1118        engine.rotate_key("ver-key").await.unwrap();
1119        engine.rotate_key("ver-key").await.unwrap();
1120
1121        let versions = engine.list_versions("ver-key").await.unwrap();
1122        assert_eq!(versions.len(), 3);
1123        assert_eq!(versions[0].version, 3); // Most recent first
1124        assert_eq!(versions[1].version, 2);
1125        assert_eq!(versions[2].version, 1);
1126    }
1127
1128    // ========================================================================
1129    // Edge Case Tests
1130    // ========================================================================
1131
1132    #[tokio::test]
1133    async fn test_encrypt_empty_data() {
1134        let (_tmp, engine) = setup().await;
1135        engine
1136            .create_key("empty-key", KeyConfig::new())
1137            .await
1138            .unwrap();
1139
1140        let ciphertext = engine.encrypt("empty-key", b"").await.unwrap();
1141        let decrypted = engine.decrypt("empty-key", &ciphertext).await.unwrap();
1142        assert_eq!(decrypted, b"");
1143    }
1144
1145    #[tokio::test]
1146    async fn test_encrypt_binary_data() {
1147        let (_tmp, engine) = setup().await;
1148        engine
1149            .create_key("bin-key", KeyConfig::new())
1150            .await
1151            .unwrap();
1152
1153        // All possible byte values
1154        let binary_data: Vec<u8> = (0..=255).collect();
1155        let ciphertext = engine.encrypt("bin-key", &binary_data).await.unwrap();
1156        let decrypted = engine.decrypt("bin-key", &ciphertext).await.unwrap();
1157        assert_eq!(decrypted, binary_data);
1158    }
1159
1160    #[tokio::test]
1161    async fn test_encrypt_unicode_data() {
1162        let (_tmp, engine) = setup().await;
1163        engine
1164            .create_key("unicode-key", KeyConfig::new())
1165            .await
1166            .unwrap();
1167
1168        let unicode_data = "Hello 世界! 🔐 Ægide résiste aux attaques! 日本語テスト";
1169        let ciphertext = engine
1170            .encrypt("unicode-key", unicode_data.as_bytes())
1171            .await
1172            .unwrap();
1173        let decrypted = engine.decrypt("unicode-key", &ciphertext).await.unwrap();
1174        assert_eq!(String::from_utf8(decrypted).unwrap(), unicode_data);
1175    }
1176
1177    #[tokio::test]
1178    async fn test_key_name_max_length() {
1179        let (_tmp, engine) = setup().await;
1180
1181        // 128 chars should work
1182        let max_name: String = "a".repeat(128);
1183        engine
1184            .create_key(&max_name, KeyConfig::new())
1185            .await
1186            .unwrap();
1187
1188        // 129 chars should fail
1189        let too_long: String = "a".repeat(129);
1190        let result = engine.create_key(&too_long, KeyConfig::new()).await;
1191        assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
1192    }
1193
1194    #[tokio::test]
1195    async fn test_key_name_allowed_chars() {
1196        let (_tmp, engine) = setup().await;
1197
1198        // These should all work
1199        engine.create_key("my-key", KeyConfig::new()).await.unwrap();
1200        engine.create_key("my_key", KeyConfig::new()).await.unwrap();
1201        engine
1202            .create_key("MyKey123", KeyConfig::new())
1203            .await
1204            .unwrap();
1205        engine
1206            .create_key("KEY-2024_test", KeyConfig::new())
1207            .await
1208            .unwrap();
1209
1210        // These should fail
1211        let result = engine.create_key("key/path", KeyConfig::new()).await;
1212        assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
1213
1214        let result = engine.create_key("key.name", KeyConfig::new()).await;
1215        assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
1216
1217        let result = engine.create_key("key name", KeyConfig::new()).await;
1218        assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
1219    }
1220
1221    // ========================================================================
1222    // Version Boundary Tests
1223    // ========================================================================
1224
1225    #[tokio::test]
1226    async fn test_encrypt_with_specific_version() {
1227        let (_tmp, engine) = setup().await;
1228        engine
1229            .create_key("ver-enc", KeyConfig::new())
1230            .await
1231            .unwrap();
1232        engine.rotate_key("ver-enc").await.unwrap();
1233        engine.rotate_key("ver-enc").await.unwrap();
1234
1235        // Encrypt with v2 (not latest)
1236        let ciphertext = engine
1237            .encrypt_with_version("ver-enc", b"data", 2)
1238            .await
1239            .unwrap();
1240        assert!(ciphertext.starts_with("egide:v2:"));
1241
1242        // Should decrypt correctly
1243        let decrypted = engine.decrypt("ver-enc", &ciphertext).await.unwrap();
1244        assert_eq!(decrypted, b"data");
1245    }
1246
1247    #[tokio::test]
1248    async fn test_min_encryption_version() {
1249        let (_tmp, engine) = setup().await;
1250        engine
1251            .create_key("min-enc", KeyConfig::new())
1252            .await
1253            .unwrap();
1254        engine.rotate_key("min-enc").await.unwrap();
1255
1256        // Set min_encryption_version to 2
1257        engine
1258            .update_key_config("min-enc", Some(2), None, None)
1259            .await
1260            .unwrap();
1261
1262        // Encrypting with v1 should fail
1263        let result = engine.encrypt_with_version("min-enc", b"data", 1).await;
1264        assert!(matches!(
1265            result,
1266            Err(TransitError::VersionBelowMinEncryption { .. })
1267        ));
1268
1269        // Encrypting with v2 should work
1270        let ciphertext = engine
1271            .encrypt_with_version("min-enc", b"data", 2)
1272            .await
1273            .unwrap();
1274        assert!(ciphertext.starts_with("egide:v2:"));
1275    }
1276
1277    #[tokio::test]
1278    async fn test_encrypt_with_nonexistent_version() {
1279        let (_tmp, engine) = setup().await;
1280        engine
1281            .create_key("noversion", KeyConfig::new())
1282            .await
1283            .unwrap();
1284
1285        // Try to encrypt with version 99
1286        let result = engine.encrypt_with_version("noversion", b"data", 99).await;
1287        assert!(matches!(result, Err(TransitError::VersionNotFound { .. })));
1288    }
1289
1290    #[tokio::test]
1291    async fn test_rewrap_already_latest() {
1292        let (_tmp, engine) = setup().await;
1293        engine
1294            .create_key("rewrap-latest", KeyConfig::new())
1295            .await
1296            .unwrap();
1297
1298        let ciphertext = engine.encrypt("rewrap-latest", b"data").await.unwrap();
1299
1300        // Rewrap should return same ciphertext (already at latest)
1301        let rewrapped = engine.rewrap("rewrap-latest", &ciphertext).await.unwrap();
1302        assert_eq!(rewrapped, ciphertext);
1303    }
1304
1305    #[tokio::test]
1306    async fn test_multiple_rotations() {
1307        let (_tmp, engine) = setup().await;
1308        engine
1309            .create_key("multi-rot", KeyConfig::new())
1310            .await
1311            .unwrap();
1312
1313        // Encrypt with each version
1314        let ct1 = engine.encrypt("multi-rot", b"v1-data").await.unwrap();
1315
1316        engine.rotate_key("multi-rot").await.unwrap();
1317        let ct2 = engine.encrypt("multi-rot", b"v2-data").await.unwrap();
1318
1319        engine.rotate_key("multi-rot").await.unwrap();
1320        let ct3 = engine.encrypt("multi-rot", b"v3-data").await.unwrap();
1321
1322        engine.rotate_key("multi-rot").await.unwrap();
1323        let ct4 = engine.encrypt("multi-rot", b"v4-data").await.unwrap();
1324
1325        engine.rotate_key("multi-rot").await.unwrap();
1326        let ct5 = engine.encrypt("multi-rot", b"v5-data").await.unwrap();
1327
1328        // All should decrypt correctly
1329        assert_eq!(engine.decrypt("multi-rot", &ct1).await.unwrap(), b"v1-data");
1330        assert_eq!(engine.decrypt("multi-rot", &ct2).await.unwrap(), b"v2-data");
1331        assert_eq!(engine.decrypt("multi-rot", &ct3).await.unwrap(), b"v3-data");
1332        assert_eq!(engine.decrypt("multi-rot", &ct4).await.unwrap(), b"v4-data");
1333        assert_eq!(engine.decrypt("multi-rot", &ct5).await.unwrap(), b"v5-data");
1334
1335        // Verify version numbers
1336        assert!(ct1.starts_with("egide:v1:"));
1337        assert!(ct5.starts_with("egide:v5:"));
1338    }
1339
1340    // ========================================================================
1341    // Error Condition Tests
1342    // ========================================================================
1343
1344    #[tokio::test]
1345    async fn test_tampered_ciphertext_base64() {
1346        let (_tmp, engine) = setup().await;
1347        engine
1348            .create_key("tamper-key", KeyConfig::new())
1349            .await
1350            .unwrap();
1351
1352        let ciphertext = engine.encrypt("tamper-key", b"secret").await.unwrap();
1353
1354        // Tamper with the base64 payload
1355        let parts: Vec<&str> = ciphertext.splitn(3, ':').collect();
1356        let tampered = format!("{}:{}:{}TAMPERED", parts[0], parts[1], parts[2]);
1357
1358        let result = engine.decrypt("tamper-key", &tampered).await;
1359        // Could be InvalidCiphertext (bad base64) or DecryptionFailed (bad auth tag)
1360        assert!(result.is_err());
1361    }
1362
1363    #[tokio::test]
1364    async fn test_tampered_ciphertext_bytes() {
1365        let (_tmp, engine) = setup().await;
1366        engine
1367            .create_key("tamper-bytes", KeyConfig::new())
1368            .await
1369            .unwrap();
1370
1371        let ciphertext = engine.encrypt("tamper-bytes", b"secret").await.unwrap();
1372
1373        // Decode, flip a bit, re-encode
1374        let parts: Vec<&str> = ciphertext.splitn(3, ':').collect();
1375        let mut bytes = BASE64.decode(parts[2]).unwrap();
1376        if !bytes.is_empty() {
1377            bytes[0] ^= 0xFF; // Flip bits
1378        }
1379        let tampered = format!("{}:{}:{}", parts[0], parts[1], BASE64.encode(&bytes));
1380
1381        let result = engine.decrypt("tamper-bytes", &tampered).await;
1382        assert!(matches!(result, Err(TransitError::DecryptionFailed)));
1383    }
1384
1385    #[tokio::test]
1386    async fn test_wrong_version_in_ciphertext() {
1387        let (_tmp, engine) = setup().await;
1388        engine
1389            .create_key("wrong-ver", KeyConfig::new())
1390            .await
1391            .unwrap();
1392
1393        let ciphertext = engine.encrypt("wrong-ver", b"data").await.unwrap();
1394
1395        // Change version number in ciphertext from v1 to v2
1396        let fake_v2 = ciphertext.replace("egide:v1:", "egide:v2:");
1397
1398        // Should fail - v2 doesn't exist
1399        let result = engine.decrypt("wrong-ver", &fake_v2).await;
1400        assert!(matches!(result, Err(TransitError::VersionNotFound { .. })));
1401    }
1402
1403    #[tokio::test]
1404    async fn test_update_config_invalid_version() {
1405        let (_tmp, engine) = setup().await;
1406        engine
1407            .create_key("cfg-ver", KeyConfig::new())
1408            .await
1409            .unwrap();
1410
1411        // Try to set min_encryption_version higher than latest
1412        let result = engine
1413            .update_key_config("cfg-ver", Some(99), None, None)
1414            .await;
1415        assert!(matches!(result, Err(TransitError::VersionNotFound { .. })));
1416
1417        // Try to set min_decryption_version higher than latest
1418        let result = engine
1419            .update_key_config("cfg-ver", None, Some(99), None)
1420            .await;
1421        assert!(matches!(result, Err(TransitError::VersionNotFound { .. })));
1422    }
1423
1424    #[tokio::test]
1425    async fn test_list_versions_nonexistent_key() {
1426        let (_tmp, engine) = setup().await;
1427
1428        let result = engine.list_versions("nonexistent").await;
1429        assert!(matches!(result, Err(TransitError::KeyNotFound(_))));
1430    }
1431
1432    // ========================================================================
1433    // Integration Tests - Full Workflows
1434    // ========================================================================
1435
1436    #[tokio::test]
1437    async fn test_full_lifecycle() {
1438        let (_tmp, engine) = setup().await;
1439
1440        // 1. Create key
1441        let key = engine
1442            .create_key("lifecycle", KeyConfig::new())
1443            .await
1444            .unwrap();
1445        assert_eq!(key.latest_version, 1);
1446
1447        // 2. Encrypt some data
1448        let ct1 = engine
1449            .encrypt("lifecycle", b"initial-secret")
1450            .await
1451            .unwrap();
1452
1453        // 3. Rotate key
1454        let v2 = engine.rotate_key("lifecycle").await.unwrap();
1455        assert_eq!(v2, 2);
1456
1457        // 4. Encrypt more data with new version
1458        let ct2 = engine.encrypt("lifecycle", b"new-secret").await.unwrap();
1459
1460        // 5. Both should decrypt
1461        assert_eq!(
1462            engine.decrypt("lifecycle", &ct1).await.unwrap(),
1463            b"initial-secret"
1464        );
1465        assert_eq!(
1466            engine.decrypt("lifecycle", &ct2).await.unwrap(),
1467            b"new-secret"
1468        );
1469
1470        // 6. Rewrap old ciphertext
1471        let ct1_rewrapped = engine.rewrap("lifecycle", &ct1).await.unwrap();
1472        assert!(ct1_rewrapped.starts_with("egide:v2:"));
1473        assert_eq!(
1474            engine.decrypt("lifecycle", &ct1_rewrapped).await.unwrap(),
1475            b"initial-secret"
1476        );
1477
1478        // 7. Update min_decryption_version to deprecate v1
1479        engine
1480            .update_key_config("lifecycle", None, Some(2), None)
1481            .await
1482            .unwrap();
1483
1484        // 8. Old ct1 should now fail
1485        let result = engine.decrypt("lifecycle", &ct1).await;
1486        assert!(matches!(
1487            result,
1488            Err(TransitError::VersionBelowMinDecryption { .. })
1489        ));
1490
1491        // 9. Rewrapped version should still work
1492        assert_eq!(
1493            engine.decrypt("lifecycle", &ct1_rewrapped).await.unwrap(),
1494            b"initial-secret"
1495        );
1496    }
1497
1498    #[tokio::test]
1499    async fn test_envelope_encryption_workflow() {
1500        let (_tmp, engine) = setup().await;
1501        engine
1502            .create_key("envelope-kek", KeyConfig::new())
1503            .await
1504            .unwrap();
1505
1506        // Simulate envelope encryption workflow
1507        // 1. Generate a data key
1508        let datakey = engine.generate_datakey("envelope-kek").await.unwrap();
1509
1510        // 2. Client uses plaintext key to encrypt their data (simulated)
1511        let client_data = b"sensitive application data";
1512        let client_encrypted =
1513            egide_crypto::aead::encrypt(&datakey.plaintext, client_data, Some(b"app-context"))
1514                .unwrap();
1515
1516        // 3. Client stores wrapped key alongside their encrypted data
1517        let stored_wrapped_key = datakey.ciphertext.clone();
1518
1519        // 4. Later, client needs to decrypt
1520        // 4a. Unwrap the data key
1521        let recovered_dek = engine
1522            .decrypt_datakey("envelope-kek", &stored_wrapped_key)
1523            .await
1524            .unwrap();
1525        assert_eq!(recovered_dek, datakey.plaintext);
1526
1527        // 4b. Client decrypts their data with recovered key
1528        let decrypted =
1529            egide_crypto::aead::decrypt(&recovered_dek, &client_encrypted, Some(b"app-context"))
1530                .unwrap();
1531        assert_eq!(&decrypted[..], client_data);
1532    }
1533
1534    #[tokio::test]
1535    async fn test_multi_key_workflow() {
1536        let (_tmp, engine) = setup().await;
1537
1538        // Create keys for different purposes
1539        engine
1540            .create_key("users-key", KeyConfig::new())
1541            .await
1542            .unwrap();
1543        engine
1544            .create_key("payments-key", KeyConfig::new())
1545            .await
1546            .unwrap();
1547        engine
1548            .create_key("logs-key", KeyConfig::new())
1549            .await
1550            .unwrap();
1551
1552        // Encrypt different data types
1553        let user_ct = engine
1554            .encrypt("users-key", b"user@email.com")
1555            .await
1556            .unwrap();
1557        let payment_ct = engine
1558            .encrypt("payments-key", b"4111111111111111")
1559            .await
1560            .unwrap();
1561        let log_ct = engine
1562            .encrypt("logs-key", b"debug log entry")
1563            .await
1564            .unwrap();
1565
1566        // Verify isolation - can't cross-decrypt
1567        assert!(engine.decrypt("payments-key", &user_ct).await.is_err());
1568        assert!(engine.decrypt("logs-key", &payment_ct).await.is_err());
1569        assert!(engine.decrypt("users-key", &log_ct).await.is_err());
1570
1571        // Correct decryption works
1572        assert_eq!(
1573            engine.decrypt("users-key", &user_ct).await.unwrap(),
1574            b"user@email.com"
1575        );
1576        assert_eq!(
1577            engine.decrypt("payments-key", &payment_ct).await.unwrap(),
1578            b"4111111111111111"
1579        );
1580        assert_eq!(
1581            engine.decrypt("logs-key", &log_ct).await.unwrap(),
1582            b"debug log entry"
1583        );
1584    }
1585
1586    // ========================================================================
1587    // Persistence Tests
1588    // ========================================================================
1589
1590    #[tokio::test]
1591    async fn test_persistence_across_restart() {
1592        let tmp = TempDir::new().unwrap();
1593        let master_key = MasterKey::generate().unwrap();
1594        let master_key_bytes = master_key.as_bytes().to_vec();
1595
1596        // First session: create key and encrypt
1597        let ciphertext = {
1598            let engine = TransitEngine::new(tmp.path(), master_key).await.unwrap();
1599            engine
1600                .create_key("persist-key", KeyConfig::new())
1601                .await
1602                .unwrap();
1603            engine.rotate_key("persist-key").await.unwrap();
1604            engine
1605                .encrypt("persist-key", b"persisted-data")
1606                .await
1607                .unwrap()
1608        };
1609        // Engine dropped here
1610
1611        // Second session: recover with same master key
1612        {
1613            let master_key2 = MasterKey::from_bytes(&master_key_bytes).unwrap();
1614            let engine2 = TransitEngine::new(tmp.path(), master_key2).await.unwrap();
1615
1616            // Key should exist
1617            let key = engine2.get_key("persist-key").await.unwrap();
1618            assert_eq!(key.latest_version, 2);
1619
1620            // Should decrypt data from previous session
1621            let decrypted = engine2.decrypt("persist-key", &ciphertext).await.unwrap();
1622            assert_eq!(decrypted, b"persisted-data");
1623
1624            // Should be able to continue rotating
1625            let v3 = engine2.rotate_key("persist-key").await.unwrap();
1626            assert_eq!(v3, 3);
1627        }
1628    }
1629
1630    #[tokio::test]
1631    async fn test_wrong_master_key_fails() {
1632        let tmp = TempDir::new().unwrap();
1633
1634        // First session: create and encrypt
1635        let ciphertext = {
1636            let master_key1 = MasterKey::generate().unwrap();
1637            let engine = TransitEngine::new(tmp.path(), master_key1).await.unwrap();
1638            engine
1639                .create_key("wrong-mk", KeyConfig::new())
1640                .await
1641                .unwrap();
1642            engine.encrypt("wrong-mk", b"data").await.unwrap()
1643        };
1644
1645        // Second session: different master key
1646        {
1647            let master_key2 = MasterKey::generate().unwrap(); // Different key!
1648            let engine2 = TransitEngine::new(tmp.path(), master_key2).await.unwrap();
1649
1650            // Key metadata exists but decryption should fail
1651            let key = engine2.get_key("wrong-mk").await.unwrap();
1652            assert_eq!(key.name, "wrong-mk");
1653
1654            // Decryption fails because key material was encrypted with different master
1655            let result = engine2.decrypt("wrong-mk", &ciphertext).await;
1656            assert!(result.is_err());
1657        }
1658    }
1659
1660    #[tokio::test]
1661    async fn test_concurrent_encryptions() {
1662        let (_tmp, engine) = setup().await;
1663        engine
1664            .create_key("concurrent", KeyConfig::new())
1665            .await
1666            .unwrap();
1667
1668        // Encrypt many items "concurrently" (sequential in test but simulates load)
1669        let mut ciphertexts = Vec::new();
1670        for i in 0..100 {
1671            let data = format!("message-{i}");
1672            let ct = engine.encrypt("concurrent", data.as_bytes()).await.unwrap();
1673            ciphertexts.push((data, ct));
1674        }
1675
1676        // All should decrypt correctly
1677        for (original, ct) in ciphertexts {
1678            let decrypted = engine.decrypt("concurrent", &ct).await.unwrap();
1679            assert_eq!(String::from_utf8(decrypted).unwrap(), original);
1680        }
1681    }
1682}