Skip to main content

kimberlite_crypto/
encryption.rs

1//! AES-256-GCM authenticated encryption for tenant data isolation.
2//!
3//! Provides envelope encryption where each tenant's data is encrypted with
4//! a unique key. AES-256-GCM is a FIPS 197 approved AEAD cipher that provides
5//! both confidentiality and integrity.
6//!
7//! # Example
8//!
9//! ```
10//! use kimberlite_crypto::encryption::{EncryptionKey, Nonce, encrypt, decrypt};
11//!
12//! let key = EncryptionKey::generate();
13//! let position: u64 = 42;  // Log position of the record
14//! let nonce = Nonce::from_position(position);
15//!
16//! let plaintext = b"sensitive tenant data";
17//! let ciphertext = encrypt(&key, &nonce, plaintext);
18//!
19//! let decrypted = decrypt(&key, &nonce, &ciphertext).unwrap();
20//! assert_eq!(decrypted, plaintext.as_slice());
21//! ```
22//!
23//! # Security
24//!
25//! - **Never reuse a nonce** with the same key. For `Kimberlite`, nonces are
26//!   derived from log position to guarantee uniqueness.
27//! - Store keys encrypted at rest using [`WrappedKey`] for the key hierarchy.
28//! - The authentication tag prevents tampering — decryption fails if the
29//!   ciphertext or tag is modified.
30
31use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
32use zeroize::{Zeroize, ZeroizeOnDrop};
33
34use crate::CryptoError;
35
36// ============================================================================
37// Constants
38// ============================================================================
39
40/// Length of an AES-256-GCM encryption key in bytes (256 bits).
41///
42/// AES-256-GCM is chosen as a FIPS 197 approved algorithm, providing
43/// compliance for regulated industries (healthcare, finance, government).
44pub const KEY_LENGTH: usize = 32;
45
46/// Length of an AES-256-GCM nonce in bytes (96 bits).
47///
48/// In `Kimberlite`, nonces are derived from the log position to guarantee
49/// uniqueness without requiring random generation.
50pub const NONCE_LENGTH: usize = 12;
51
52/// Length of the AES-GCM authentication tag in bytes (128 bits).
53///
54/// The authentication tag ensures integrity — decryption fails if the
55/// ciphertext or tag has been tampered with.
56pub const TAG_LENGTH: usize = 16;
57
58pub const WRAPPED_KEY_LENGTH: usize = NONCE_LENGTH + KEY_LENGTH + TAG_LENGTH;
59
60// ============================================================================
61// EncryptionKey
62// ============================================================================
63
64/// An AES-256-GCM encryption key (256 bits).
65///
66/// This is secret key material that must be protected. Use [`EncryptionKey::generate`]
67/// to create a new random key, or [`EncryptionKey::from_bytes`] to restore from
68/// secure storage.
69///
70/// Key material is securely zeroed from memory when dropped via [`ZeroizeOnDrop`].
71///
72/// # Security
73///
74/// - Never log or expose the key bytes
75/// - Store encrypted at rest (wrap with a KEK from the key hierarchy)
76/// - Use one key per tenant/segment for isolation
77#[derive(Zeroize, ZeroizeOnDrop)]
78pub struct EncryptionKey([u8; KEY_LENGTH]);
79
80impl EncryptionKey {
81    // ========================================================================
82    // Functional Core (pure, testable)
83    // ========================================================================
84
85    /// Creates an encryption key from random bytes (pure, no IO).
86    ///
87    /// This is the functional core - it performs no IO and is fully testable.
88    /// Use [`Self::generate`] for the public API that handles randomness.
89    ///
90    /// # Security
91    ///
92    /// Only use bytes from a CSPRNG. This is `pub(crate)` to prevent misuse.
93    pub(crate) fn from_random_bytes(bytes: [u8; KEY_LENGTH]) -> Self {
94        // Precondition: caller provided non-degenerate random bytes
95        assert!(
96            bytes.iter().any(|&b| b != 0),
97            "EncryptionKey random bytes are all zeros - RNG failure or bug"
98        );
99
100        Self(bytes)
101    }
102
103    /// Restores an encryption key from its 32-byte representation.
104    ///
105    /// # Security
106    ///
107    /// Only use bytes from a previously generated key or a secure KDF.
108    pub fn from_bytes(bytes: &[u8; KEY_LENGTH]) -> Self {
109        // Precondition: caller didn't pass degenerate key material
110        assert!(
111            bytes.iter().any(|&b| b != 0),
112            "EncryptionKey bytes are all zeros - corrupted or uninitialized key material"
113        );
114
115        Self(*bytes)
116    }
117
118    /// Returns the raw 32-byte key material.
119    ///
120    /// # Security
121    ///
122    /// Handle with care — this is secret key material.
123    pub fn to_bytes(&self) -> [u8; KEY_LENGTH] {
124        self.0
125    }
126
127    // ========================================================================
128    // Imperative Shell (IO boundary)
129    // ========================================================================
130
131    /// Generates a new random encryption key using the OS CSPRNG.
132    ///
133    /// This is the imperative shell - it handles IO (randomness) and delegates
134    /// to a pure internal constructor for the actual construction.
135    ///
136    /// # Panics
137    ///
138    /// Panics if the OS CSPRNG fails (catastrophic system error).
139    // The `let` binding is load-bearing: it holds the key while the
140    // property check runs. Rewriting to a bare return would move the
141    // temporary into the `always!` call and back out again, which
142    // clippy can't see past.
143    #[allow(clippy::let_and_return)]
144    pub fn generate() -> Self {
145        let random_bytes: [u8; KEY_LENGTH] = generate_random();
146        let key = Self::from_random_bytes(random_bytes);
147
148        // Property: generated key must never be all zeros (RNG health check)
149        kimberlite_properties::always!(
150            key.0.iter().any(|&b| b != 0),
151            "crypto.encryption_key_not_all_zeros",
152            "EncryptionKey must never be all-zeros after generation"
153        );
154
155        key
156    }
157}
158
159// ============================================================================
160// Nonce
161// ============================================================================
162
163/// An AES-256-GCM nonce (96 bits) derived from log position.
164///
165/// Nonces in `Kimberlite` are deterministically derived from the record's log
166/// position, guaranteeing uniqueness without requiring random generation.
167/// The 8-byte position is placed in the first 8 bytes; the remaining 4 bytes
168/// are reserved (currently zero).
169///
170/// ```text
171/// Nonce layout (12 bytes):
172/// ┌────────────────────────────┬──────────────┐
173/// │  position (8 bytes, LE)    │  reserved    │
174/// │  [0..8]                    │  [8..12]     │
175/// └────────────────────────────┴──────────────┘
176/// ```
177///
178/// # Security
179///
180/// **Never reuse a nonce with the same key.** Nonce reuse completely breaks
181/// the confidentiality of AES-GCM. Position-derived nonces guarantee uniqueness
182/// as long as each position is encrypted at most once per key.
183#[derive(Clone, Copy, PartialEq, Eq)]
184pub struct Nonce([u8; NONCE_LENGTH]);
185
186impl Nonce {
187    // ========================================================================
188    // Functional Core (pure, testable)
189    // ========================================================================
190
191    /// Creates a nonce from random bytes (pure, no IO).
192    ///
193    /// This is the functional core - it performs no IO and is fully testable.
194    /// Use [`Self::generate_random`] for the public API that handles randomness.
195    ///
196    /// # Security
197    ///
198    /// Only use bytes from a CSPRNG. This is `pub(crate)` to prevent misuse.
199    pub(crate) fn from_random_bytes(bytes: [u8; NONCE_LENGTH]) -> Self {
200        // Precondition: caller provided non-degenerate random bytes
201        assert!(
202            bytes.iter().any(|&b| b != 0),
203            "Nonce random bytes are all zeros - RNG failure or bug"
204        );
205
206        Self(bytes)
207    }
208
209    /// Creates a nonce from a log position (pure, deterministic).
210    ///
211    /// The position's little-endian bytes occupy the first 8 bytes of the
212    /// nonce. The remaining 4 bytes are reserved for future use (e.g., key
213    /// rotation counter) and are currently set to zero.
214    ///
215    /// # Arguments
216    ///
217    /// * `position` - The log position of the record being encrypted
218    pub fn from_position(position: u64) -> Self {
219        let mut nonce = [0u8; NONCE_LENGTH];
220        nonce[..8].copy_from_slice(&position.to_le_bytes());
221
222        Self(nonce)
223    }
224
225    /// Creates a nonce from raw bytes.
226    ///
227    /// Used when deserializing a nonce from storage (e.g., from a [`WrappedKey`]).
228    ///
229    /// # Arguments
230    ///
231    /// * `bytes` - The 12-byte nonce
232    pub fn from_bytes(bytes: [u8; NONCE_LENGTH]) -> Self {
233        Self(bytes)
234    }
235
236    /// Returns the raw 12-byte nonce.
237    ///
238    /// Use this for serialization or when interfacing with external systems.
239    pub fn to_bytes(&self) -> [u8; NONCE_LENGTH] {
240        self.0
241    }
242
243    // ========================================================================
244    // Imperative Shell (IO boundary)
245    // ========================================================================
246
247    /// Generates a random nonce using the OS CSPRNG.
248    ///
249    /// Used for key wrapping where there's no log position to derive from.
250    /// The random nonce must be stored alongside the ciphertext.
251    ///
252    /// This is the imperative shell - it handles IO (randomness) and delegates
253    /// to a pure internal constructor for the actual construction.
254    ///
255    /// # Panics
256    ///
257    /// Panics if the OS CSPRNG fails (catastrophic system error).
258    pub fn generate_random() -> Self {
259        let random_bytes: [u8; NONCE_LENGTH] = generate_random();
260        Self::from_random_bytes(random_bytes)
261    }
262}
263
264// ============================================================================
265// Ciphertext
266// ============================================================================
267
268/// Encrypted data with its authentication tag.
269///
270/// Contains the ciphertext followed by a 16-byte AES-GCM authentication tag.
271/// The total length is `plaintext.len() + TAG_LENGTH`. Decryption will fail
272/// if the ciphertext or tag has been modified.
273///
274/// ```text
275/// Ciphertext layout:
276/// ┌────────────────────────────┬──────────────────┐
277/// │  encrypted data            │  auth tag        │
278/// │  [0..plaintext.len()]      │  [last 16 bytes] │
279/// └────────────────────────────┴──────────────────┘
280/// ```
281#[derive(Clone, PartialEq, Eq)]
282pub struct Ciphertext(Vec<u8>);
283
284impl Ciphertext {
285    /// Creates a ciphertext from raw bytes.
286    ///
287    /// # Arguments
288    ///
289    /// * `bytes` - The encrypted data with authentication tag appended
290    ///
291    /// # Panics
292    ///
293    /// Debug builds panic if `bytes.len() < TAG_LENGTH` (no room for auth tag).
294    pub fn from_bytes(bytes: Vec<u8>) -> Self {
295        assert!(
296            bytes.len() >= TAG_LENGTH,
297            "ciphertext too short: must be at least {} bytes for auth tag, got {}",
298            TAG_LENGTH,
299            bytes.len()
300        );
301
302        Self(bytes)
303    }
304
305    /// Returns the raw ciphertext bytes (including authentication tag).
306    ///
307    /// Use this for serialization or storage.
308    pub fn to_bytes(&self) -> &[u8] {
309        &self.0
310    }
311
312    /// Returns the length of the ciphertext (including authentication tag).
313    pub fn len(&self) -> usize {
314        self.0.len()
315    }
316
317    /// Returns true if the ciphertext is empty (which would be invalid).
318    pub fn is_empty(&self) -> bool {
319        self.0.is_empty()
320    }
321}
322
323// ============================================================================
324// WrappedKey
325// ============================================================================
326
327/// An encryption key wrapped (encrypted) by another key.
328///
329/// Used in the key hierarchy to protect DEKs with KEKs, and KEKs with the
330/// master key. The wrapped key can be safely stored on disk — it cannot be
331/// unwrapped without the wrapping key.
332///
333/// ```text
334/// WrappedKey layout (60 bytes):
335/// ┌────────────────────────────┬────────────────────────────┬──────────────┐
336/// │  nonce (12 bytes)          │  encrypted key (32 bytes)  │  tag (16)    │
337/// └────────────────────────────┴────────────────────────────┴──────────────┘
338/// ```
339///
340/// # Security
341///
342/// - The wrapped ciphertext is encrypted, not raw key material
343/// - The nonce is not secret (it's stored alongside the ciphertext)
344/// - `ZeroizeOnDrop` is not needed since this contains no plaintext secrets
345/// - The wrapping key (KEK/MK) is what must be protected
346///
347/// # Example
348///
349/// ```
350/// use kimberlite_crypto::encryption::{EncryptionKey, WrappedKey, KEY_LENGTH};
351///
352/// // KEK wraps a DEK
353/// let kek = EncryptionKey::generate();
354/// let dek_bytes: [u8; KEY_LENGTH] = [0x42; KEY_LENGTH]; // In practice, use generate()
355///
356/// let wrapped = WrappedKey::new(&kek, &dek_bytes);
357///
358/// // Store wrapped.to_bytes() on disk...
359///
360/// // Later, unwrap with the same KEK
361/// let unwrapped = wrapped.unwrap_key(&kek).unwrap();
362/// assert_eq!(dek_bytes, unwrapped);
363/// ```
364pub struct WrappedKey {
365    nonce: Nonce,
366    ciphertext: Ciphertext,
367}
368
369impl WrappedKey {
370    /// Wraps (encrypts) a key using the provided wrapping key.
371    ///
372    /// Generates a random nonce and encrypts the key material using AES-256-GCM.
373    /// The nonce is stored alongside the ciphertext for later unwrapping.
374    ///
375    /// # Arguments
376    ///
377    /// * `wrapping_key` - The key used to encrypt (KEK or master key)
378    /// * `key_to_wrap` - The 32-byte key material to protect (DEK or KEK)
379    ///
380    /// # Returns
381    ///
382    /// A [`WrappedKey`] that can be serialized and stored safely.
383    pub fn new(wrapping_key: &EncryptionKey, key_to_wrap: &[u8; KEY_LENGTH]) -> Self {
384        // Precondition: key material isn't degenerate
385        assert!(
386            key_to_wrap.iter().any(|&b| b != 0),
387            "key_to_wrap is all zeros - corrupted or uninitialized key material"
388        );
389
390        let nonce = Nonce::generate_random();
391        let ciphertext = encrypt(wrapping_key, &nonce, key_to_wrap);
392
393        // Postcondition: ciphertext is correct size (key + tag)
394        assert_eq!(
395            ciphertext.len(),
396            KEY_LENGTH + TAG_LENGTH,
397            "wrapped ciphertext has unexpected length: expected {}, got {}",
398            KEY_LENGTH + TAG_LENGTH,
399            ciphertext.len()
400        );
401
402        Self { nonce, ciphertext }
403    }
404
405    /// Unwraps (decrypts) the key using the provided wrapping key.
406    ///
407    /// Verifies the authentication tag and returns the original key material
408    /// if the wrapping key is correct.
409    ///
410    /// # Arguments
411    ///
412    /// * `wrapping_key` - The key used during wrapping (must match)
413    ///
414    /// # Errors
415    ///
416    /// Returns [`CryptoError::DecryptionError`] if:
417    /// - The wrapping key is incorrect
418    /// - The wrapped data has been tampered with
419    ///
420    /// # Returns
421    ///
422    /// The original 32-byte key material.
423    pub fn unwrap_key(
424        &self,
425        wrapping_key: &EncryptionKey,
426    ) -> Result<[u8; KEY_LENGTH], CryptoError> {
427        let decrypted = decrypt(wrapping_key, &self.nonce, &self.ciphertext)?;
428
429        // Postcondition: decrypted key has correct length
430        assert_eq!(
431            decrypted.len(),
432            KEY_LENGTH,
433            "unwrapped key has unexpected length: expected {}, got {}",
434            KEY_LENGTH,
435            decrypted.len()
436        );
437
438        decrypted
439            .try_into()
440            .map_err(|_| CryptoError::DecryptionError)
441    }
442
443    /// Serializes the wrapped key to bytes for storage.
444    ///
445    /// The format is: `nonce (12 bytes) || ciphertext (48 bytes)`.
446    ///
447    /// # Returns
448    ///
449    /// A 60-byte array suitable for storing on disk or in a database.
450    pub fn to_bytes(&self) -> [u8; WRAPPED_KEY_LENGTH] {
451        let mut bytes = [0u8; WRAPPED_KEY_LENGTH];
452        bytes[..NONCE_LENGTH].copy_from_slice(&self.nonce.to_bytes());
453        bytes[NONCE_LENGTH..].copy_from_slice(self.ciphertext.to_bytes());
454
455        // Postcondition: we produced non-degenerate output
456        assert!(
457            bytes.iter().any(|&b| b != 0),
458            "serialized wrapped key is all zeros - encryption bug"
459        );
460
461        bytes
462    }
463
464    /// Deserializes a wrapped key from bytes.
465    ///
466    /// # Arguments
467    ///
468    /// * `bytes` - A 60-byte array from [`WrappedKey::to_bytes`]
469    ///
470    /// # Returns
471    ///
472    /// A [`WrappedKey`] that can be unwrapped with the original wrapping key.
473    pub fn from_bytes(bytes: &[u8; WRAPPED_KEY_LENGTH]) -> Self {
474        // Precondition: bytes aren't all zeros (likely corrupted or uninitialized)
475        assert!(
476            bytes.iter().any(|&b| b != 0),
477            "wrapped key bytes are all zeros - corrupted or uninitialized storage"
478        );
479
480        let mut nonce_bytes = [0u8; NONCE_LENGTH];
481        nonce_bytes.copy_from_slice(&bytes[..NONCE_LENGTH]);
482        let ciphertext = Ciphertext::from_bytes(bytes[NONCE_LENGTH..].to_vec());
483
484        Self {
485            nonce: Nonce::from_bytes(nonce_bytes),
486            ciphertext,
487        }
488    }
489}
490
491// ============================================================================
492// Key Hierarchy
493// ============================================================================
494
495/// Provider for master key operations.
496///
497/// The master key is the root of the key hierarchy. It wraps Key Encryption
498/// Keys (KEKs), which in turn wrap Data Encryption Keys (DEKs).
499///
500/// ```text
501/// MasterKeyProvider
502///     │
503///     └── wraps ──► KeyEncryptionKey (per tenant)
504///                       │
505///                       └── wraps ──► DataEncryptionKey (per segment)
506///                                         │
507///                                         └── encrypts ──► Record data
508/// ```
509///
510/// This trait abstracts the master key storage, enabling:
511/// - [`InMemoryMasterKey`]: Development, testing, single-node deployments
512/// - Future: HSM-backed implementation where the key never leaves the hardware
513///
514/// # Security
515///
516/// The master key is the most sensitive secret in the system. If compromised,
517/// all tenant data can be decrypted. In production, use an HSM.
518pub trait MasterKeyProvider {
519    /// Wraps a Key Encryption Key for secure storage.
520    ///
521    /// The wrapped KEK can be stored on disk and later unwrapped with
522    /// [`MasterKeyProvider::unwrap_kek`].
523    fn wrap_kek(&self, kek_bytes: &[u8; KEY_LENGTH]) -> WrappedKey;
524
525    /// Unwraps a Key Encryption Key from storage.
526    ///
527    /// # Errors
528    ///
529    /// Returns [`CryptoError::DecryptionError`] if the wrapped key is
530    /// corrupted or was wrapped by a different master key.
531    fn unwrap_kek(&self, wrapped: &WrappedKey) -> Result<[u8; KEY_LENGTH], CryptoError>;
532}
533
534/// In-memory master key for development and testing.
535///
536/// Stores the master key material directly in memory. Suitable for:
537/// - Development and testing
538/// - Single-node deployments with disk encryption
539/// - Environments without HSM access
540///
541/// # Security
542///
543/// The key material is zeroed on drop via [`ZeroizeOnDrop`]. However, for
544/// production deployments handling sensitive data, prefer an HSM-backed
545/// implementation.
546///
547/// # Example
548///
549/// ```
550/// use kimberlite_crypto::encryption::{InMemoryMasterKey, MasterKeyProvider, KeyEncryptionKey};
551///
552/// let master = InMemoryMasterKey::generate();
553/// let (kek, wrapped_kek) = KeyEncryptionKey::generate_and_wrap(&master);
554///
555/// // Store wrapped_kek.to_bytes() on disk...
556/// ```
557#[derive(Zeroize, ZeroizeOnDrop)]
558pub struct InMemoryMasterKey(EncryptionKey);
559
560impl InMemoryMasterKey {
561    // ========================================================================
562    // Functional Core (pure, testable)
563    // ========================================================================
564
565    /// Creates a master key from random bytes (pure, no IO).
566    ///
567    /// This is the functional core - it performs no IO and is fully testable.
568    /// Use [`Self::generate`] for the public API that handles randomness.
569    ///
570    /// # Security
571    ///
572    /// Only use bytes from a CSPRNG. This is `pub(crate)` to prevent misuse.
573    pub(crate) fn from_random_bytes(bytes: [u8; KEY_LENGTH]) -> Self {
574        Self(EncryptionKey::from_random_bytes(bytes))
575    }
576
577    /// Restores a master key from its 32-byte representation.
578    ///
579    /// # Security
580    ///
581    /// Only use bytes from a previously generated key or secure backup.
582    /// The bytes should come from encrypted-at-rest storage.
583    pub fn from_bytes(bytes: &[u8; KEY_LENGTH]) -> Self {
584        // Precondition: caller didn't pass degenerate key material
585        assert!(
586            bytes.iter().any(|&b| b != 0),
587            "master key bytes are all zeros - corrupted or uninitialized key material"
588        );
589
590        Self(EncryptionKey::from_bytes(bytes))
591    }
592
593    /// Returns the raw 32-byte key material for backup.
594    ///
595    /// # Security
596    ///
597    /// **Handle with extreme care.** This is the root secret of the entire
598    /// key hierarchy. Only use this for:
599    /// - Secure backup to encrypted storage
600    /// - Key escrow with proper controls
601    ///
602    /// Never log, transmit unencrypted, or store in plaintext.
603    pub fn to_bytes(&self) -> [u8; KEY_LENGTH] {
604        self.0.to_bytes()
605    }
606
607    // ========================================================================
608    // Imperative Shell (IO boundary)
609    // ========================================================================
610
611    /// Generates a new random master key using the OS CSPRNG.
612    ///
613    /// This is the imperative shell - it handles IO (randomness) and delegates
614    /// to a pure internal constructor for the actual construction.
615    ///
616    /// # Panics
617    ///
618    /// Panics if the OS CSPRNG fails (catastrophic system error).
619    pub fn generate() -> Self {
620        let random_bytes: [u8; KEY_LENGTH] = generate_random();
621        Self::from_random_bytes(random_bytes)
622    }
623}
624
625impl MasterKeyProvider for InMemoryMasterKey {
626    fn wrap_kek(&self, kek_bytes: &[u8; KEY_LENGTH]) -> WrappedKey {
627        // Precondition: KEK bytes aren't degenerate
628        assert!(
629            kek_bytes.iter().any(|&b| b != 0),
630            "KEK bytes are all zeros - corrupted or uninitialized key material"
631        );
632
633        WrappedKey::new(&self.0, kek_bytes)
634    }
635
636    fn unwrap_kek(&self, wrapped: &WrappedKey) -> Result<[u8; KEY_LENGTH], CryptoError> {
637        let kek_bytes = wrapped.unwrap_key(&self.0)?;
638
639        // Postcondition: unwrapped KEK isn't degenerate
640        assert!(
641            kek_bytes.iter().any(|&b| b != 0),
642            "unwrapped KEK is all zeros - decryption produced invalid key"
643        );
644
645        Ok(kek_bytes)
646    }
647}
648
649/// Key Encryption Key (KEK) for wrapping Data Encryption Keys.
650///
651/// Each tenant has one KEK. The KEK is wrapped by the master key and stored
652/// alongside tenant metadata. Deleting a tenant's wrapped KEK renders all
653/// their data cryptographically inaccessible (GDPR "right to erasure").
654///
655/// # Key Hierarchy Position
656///
657/// ```text
658/// MasterKeyProvider
659///     │
660///     └── wraps ──► KeyEncryptionKey (this type)
661///                       │
662///                       └── wraps ──► DataEncryptionKey
663/// ```
664///
665/// # Example
666///
667/// ```
668/// use kimberlite_crypto::encryption::{
669///     InMemoryMasterKey, MasterKeyProvider, KeyEncryptionKey, DataEncryptionKey,
670/// };
671///
672/// let master = InMemoryMasterKey::generate();
673///
674/// // Create KEK for a new tenant
675/// let (kek, wrapped_kek) = KeyEncryptionKey::generate_and_wrap(&master);
676///
677/// // Store wrapped_kek.to_bytes() in tenant metadata...
678///
679/// // Later: restore KEK when tenant accesses data
680/// let kek = KeyEncryptionKey::restore(&master, &wrapped_kek).unwrap();
681/// ```
682#[derive(Zeroize, ZeroizeOnDrop)]
683pub struct KeyEncryptionKey(EncryptionKey);
684
685impl KeyEncryptionKey {
686    // ========================================================================
687    // Functional Core (pure, testable)
688    // ========================================================================
689
690    /// Constructs a KEK from raw bytes. Used by the KMS integration
691    /// path ([`crate::kms`]) where the KEK round-trips through the
692    /// cloud KMS as a `SealedKey` rather than the local `WrappedKey`
693    /// envelope. Mirrors [`EncryptionKey::from_bytes`].
694    ///
695    /// # Security
696    ///
697    /// Only use bytes from a CSPRNG or from a previously-sealed KEK
698    /// that was just decrypted by a trusted KMS.
699    pub fn from_bytes(bytes: &[u8; KEY_LENGTH]) -> Self {
700        assert!(
701            bytes.iter().any(|&b| b != 0),
702            "KEK bytes are all zeros - corrupted or uninitialized key material"
703        );
704        Self(EncryptionKey::from_bytes(bytes))
705    }
706
707    /// Generates a fresh KEK from the OS CSPRNG. Used by the KMS
708    /// integration when the operator wants a new tenant key that
709    /// will be sealed under their cloud KMS rather than wrapped
710    /// under a local master.
711    pub fn generate() -> Self {
712        Self(EncryptionKey::generate())
713    }
714
715    /// Exposes the 32-byte KEK material. Used by the KMS integration
716    /// to feed the bytes into a cloud `seal` call. Mirrors
717    /// [`EncryptionKey::to_bytes`]; same security caveats apply —
718    /// handle with care, never log, zero after use.
719    pub fn to_bytes(&self) -> [u8; KEY_LENGTH] {
720        self.0.to_bytes()
721    }
722
723    /// Creates a KEK from random bytes and wraps it (pure, no IO).
724    ///
725    /// This is the functional core - it performs no IO and is fully testable.
726    /// Use [`Self::generate_and_wrap`] for the public API that handles randomness.
727    ///
728    /// # Security
729    ///
730    /// Only use bytes from a CSPRNG. This is `pub(crate)` to prevent misuse.
731    pub(crate) fn from_random_bytes_and_wrap(
732        random_bytes: [u8; KEY_LENGTH],
733        master: &impl MasterKeyProvider,
734    ) -> (Self, WrappedKey) {
735        let key = EncryptionKey::from_random_bytes(random_bytes);
736        let wrapped = master.wrap_kek(&key.to_bytes());
737
738        (Self(key), wrapped)
739    }
740
741    /// Restores a KEK from its wrapped form (pure, no IO).
742    ///
743    /// Use this when loading a tenant's KEK from storage.
744    ///
745    /// # Arguments
746    ///
747    /// * `master` - The master key provider that originally wrapped this KEK
748    /// * `wrapped` - The wrapped KEK from storage
749    ///
750    /// # Errors
751    ///
752    /// Returns [`CryptoError::DecryptionError`] if:
753    /// - The wrapped key is corrupted
754    /// - The wrong master key is used
755    pub fn restore(
756        master: &impl MasterKeyProvider,
757        wrapped: &WrappedKey,
758    ) -> Result<Self, CryptoError> {
759        let key_bytes = master.unwrap_kek(wrapped)?;
760
761        // Postcondition: restored key isn't degenerate
762        assert!(
763            key_bytes.iter().any(|&b| b != 0),
764            "restored KEK is all zeros - decryption produced invalid key"
765        );
766
767        Ok(Self(EncryptionKey::from_bytes(&key_bytes)))
768    }
769
770    /// Wraps a Data Encryption Key for secure storage.
771    ///
772    /// The wrapped DEK should be stored in the segment header.
773    pub fn wrap_dek(&self, dek_bytes: &[u8; KEY_LENGTH]) -> WrappedKey {
774        // Precondition: DEK bytes aren't degenerate
775        assert!(
776            dek_bytes.iter().any(|&b| b != 0),
777            "DEK bytes are all zeros - corrupted or uninitialized key material"
778        );
779
780        WrappedKey::new(&self.0, dek_bytes)
781    }
782
783    /// Unwraps a Data Encryption Key from storage.
784    ///
785    /// # Errors
786    ///
787    /// Returns [`CryptoError::DecryptionError`] if:
788    /// - The wrapped key is corrupted
789    /// - The wrong KEK is used
790    pub fn unwrap_dek(&self, wrapped: &WrappedKey) -> Result<[u8; KEY_LENGTH], CryptoError> {
791        let dek_bytes = wrapped.unwrap_key(&self.0)?;
792
793        // Postcondition: unwrapped DEK isn't degenerate
794        assert!(
795            dek_bytes.iter().any(|&b| b != 0),
796            "unwrapped DEK is all zeros - decryption produced invalid key"
797        );
798
799        Ok(dek_bytes)
800    }
801
802    // ========================================================================
803    // Imperative Shell (IO boundary)
804    // ========================================================================
805
806    /// Generates a new KEK and wraps it with the master key.
807    ///
808    /// Returns both the usable KEK and its wrapped form for storage.
809    /// The wrapped form should be persisted alongside tenant metadata.
810    ///
811    /// This is the imperative shell - it handles IO (randomness) and delegates
812    /// to a pure internal constructor for the actual construction.
813    ///
814    /// # Arguments
815    ///
816    /// * `master` - The master key provider to wrap the KEK
817    ///
818    /// # Returns
819    ///
820    /// A tuple of `(usable_kek, wrapped_kek_for_storage)`.
821    ///
822    /// # Panics
823    ///
824    /// Panics if the OS CSPRNG fails (catastrophic system error).
825    pub fn generate_and_wrap(master: &impl MasterKeyProvider) -> (Self, WrappedKey) {
826        let random_bytes: [u8; KEY_LENGTH] = generate_random();
827        Self::from_random_bytes_and_wrap(random_bytes, master)
828    }
829}
830
831/// Data Encryption Key (DEK) for encrypting actual record data.
832///
833/// Each segment (chunk of the log) has its own DEK. The DEK is wrapped by
834/// the tenant's KEK and stored in the segment header.
835///
836/// # Key Hierarchy Position
837///
838/// ```text
839/// MasterKeyProvider
840///     │
841///     └── wraps ──► KeyEncryptionKey
842///                       │
843///                       └── wraps ──► DataEncryptionKey (this type)
844///                                         │
845///                                         └── encrypts ──► Record data
846/// ```
847///
848/// # Example
849///
850/// ```
851/// use kimberlite_crypto::encryption::{
852///     InMemoryMasterKey, KeyEncryptionKey, DataEncryptionKey,
853///     Nonce, encrypt, decrypt,
854/// };
855///
856/// let master = InMemoryMasterKey::generate();
857/// let (kek, _) = KeyEncryptionKey::generate_and_wrap(&master);
858///
859/// // Create DEK for a new segment
860/// let (dek, wrapped_dek) = DataEncryptionKey::generate_and_wrap(&kek);
861///
862/// // Encrypt data
863/// let nonce = Nonce::from_position(0);
864/// let ciphertext = encrypt(dek.encryption_key(), &nonce, b"secret data");
865///
866/// // Decrypt data
867/// let plaintext = decrypt(dek.encryption_key(), &nonce, &ciphertext).unwrap();
868/// assert_eq!(plaintext, b"secret data");
869/// ```
870#[derive(Zeroize, ZeroizeOnDrop)]
871pub struct DataEncryptionKey(EncryptionKey);
872
873impl DataEncryptionKey {
874    // ========================================================================
875    // Functional Core (pure, testable)
876    // ========================================================================
877
878    /// Creates a DEK from random bytes and wraps it (pure, no IO).
879    ///
880    /// This is the functional core - it performs no IO and is fully testable.
881    /// Use [`Self::generate_and_wrap`] for the public API that handles randomness.
882    ///
883    /// # Security
884    ///
885    /// Only use bytes from a CSPRNG. This is `pub(crate)` to prevent misuse.
886    pub(crate) fn from_random_bytes_and_wrap(
887        random_bytes: [u8; KEY_LENGTH],
888        kek: &KeyEncryptionKey,
889    ) -> (Self, WrappedKey) {
890        let encryption_key = EncryptionKey::from_random_bytes(random_bytes);
891        let wrapped = kek.wrap_dek(&encryption_key.to_bytes());
892
893        (Self(encryption_key), wrapped)
894    }
895
896    /// Restores a DEK from its wrapped form (pure, no IO).
897    ///
898    /// Use this when loading a segment's DEK from its header.
899    ///
900    /// # Arguments
901    ///
902    /// * `kek` - The KEK that originally wrapped this DEK
903    /// * `wrapped` - The wrapped DEK from the segment header
904    ///
905    /// # Errors
906    ///
907    /// Returns [`CryptoError::DecryptionError`] if:
908    /// - The wrapped key is corrupted
909    /// - The wrong KEK is used
910    pub fn restore(kek: &KeyEncryptionKey, wrapped: &WrappedKey) -> Result<Self, CryptoError> {
911        let key_bytes = kek.unwrap_dek(wrapped)?;
912
913        // Postcondition: restored key isn't degenerate
914        assert!(
915            key_bytes.iter().any(|&b| b != 0),
916            "restored DEK is all zeros - decryption produced invalid key"
917        );
918
919        Ok(Self(EncryptionKey::from_bytes(&key_bytes)))
920    }
921
922    /// Returns a reference to the underlying encryption key.
923    ///
924    /// Use this with [`encrypt`] and [`decrypt`] to encrypt/decrypt record data.
925    ///
926    /// # Example
927    ///
928    /// ```
929    /// # use kimberlite_crypto::encryption::{InMemoryMasterKey, KeyEncryptionKey, DataEncryptionKey, Nonce, encrypt};
930    /// # let master = InMemoryMasterKey::generate();
931    /// # let (kek, _) = KeyEncryptionKey::generate_and_wrap(&master);
932    /// # let (dek, _) = DataEncryptionKey::generate_and_wrap(&kek);
933    /// let nonce = Nonce::from_position(42);
934    /// let ciphertext = encrypt(dek.encryption_key(), &nonce, b"data");
935    /// ```
936    pub fn encryption_key(&self) -> &EncryptionKey {
937        &self.0
938    }
939
940    // ========================================================================
941    // Imperative Shell (IO boundary)
942    // ========================================================================
943
944    /// Generates a new DEK and wraps it with the KEK.
945    ///
946    /// Returns both the usable DEK and its wrapped form for storage.
947    /// The wrapped form should be stored in the segment header.
948    ///
949    /// This is the imperative shell - it handles IO (randomness) and delegates
950    /// to a pure internal constructor for the actual construction.
951    ///
952    /// # Arguments
953    ///
954    /// * `kek` - The Key Encryption Key to wrap this DEK
955    ///
956    /// # Returns
957    ///
958    /// A tuple of `(usable_dek, wrapped_dek_for_storage)`.
959    ///
960    /// # Panics
961    ///
962    /// Panics if the OS CSPRNG fails (catastrophic system error).
963    pub fn generate_and_wrap(kek: &KeyEncryptionKey) -> (Self, WrappedKey) {
964        let random_bytes: [u8; KEY_LENGTH] = generate_random();
965        Self::from_random_bytes_and_wrap(random_bytes, kek)
966    }
967
968    /// Irreversibly shred this DEK and produce a commitment to the act.
969    ///
970    /// **AUDIT-2026-04 C-1 / H-4 primitive.** Consumes the DEK (forcing
971    /// `Drop` → `Zeroize`) and returns a digest committing to the
972    /// shredding event: `SHA-256(pre_shred_key_bytes || shred_nonce)`.
973    /// The nonce is caller-provided so attempts at proof-forgery
974    /// without access to the key cannot be constructed after the fact.
975    ///
976    /// Once this returns, the key's ciphertext is cryptographically
977    /// unrecoverable — the underlying memory is zeroed via
978    /// [`ZeroizeOnDrop`], and the digest binds the act to the specific
979    /// key material that was destroyed.
980    ///
981    /// Callers typically pair this with an
982    /// `ErasureProof { key_shred_digest, .. }` struct in
983    /// `kimberlite-compliance::erasure`.
984    pub fn shred(self, shred_nonce: &[u8; 32]) -> [u8; 32] {
985        // Capture a digest of the about-to-be-destroyed key material
986        // bound to the caller's nonce. `to_bytes()` is a 32-byte copy;
987        // the copy is dropped after the hasher consumes it.
988        use sha2::{Digest, Sha256};
989        let mut hasher = <Sha256 as Digest>::new();
990        let key_bytes = self.0.to_bytes();
991        hasher.update(key_bytes);
992        hasher.update(shred_nonce);
993        let digest: [u8; 32] = hasher.finalize().into();
994        // Drop consumes self and zeroizes via ZeroizeOnDrop.
995        drop(self);
996        digest
997    }
998}
999
1000// ============================================================================
1001// Encrypt / Decrypt
1002// ============================================================================
1003
1004/// A pre-computed AES-256-GCM cipher instance for repeated encrypt/decrypt operations.
1005///
1006/// Creating an `Aes256Gcm` instance involves computing the AES key schedule (~1us).
1007/// For hot paths that encrypt/decrypt many records with the same key, caching the
1008/// cipher instance avoids repeating this computation.
1009///
1010/// # Example
1011///
1012/// ```
1013/// use kimberlite_crypto::encryption::{EncryptionKey, CachedCipher, Nonce};
1014///
1015/// let key = EncryptionKey::generate();
1016/// let cipher = CachedCipher::new(&key);
1017///
1018/// let nonce = Nonce::from_position(0);
1019/// let ciphertext = cipher.encrypt(&nonce, b"data");
1020/// let plaintext = cipher.decrypt(&nonce, &ciphertext).unwrap();
1021/// assert_eq!(plaintext, b"data");
1022/// ```
1023pub struct CachedCipher {
1024    cipher: Aes256Gcm,
1025}
1026
1027impl CachedCipher {
1028    /// Creates a cached cipher from an encryption key.
1029    ///
1030    /// The AES key schedule is computed once during construction.
1031    pub fn new(key: &EncryptionKey) -> Self {
1032        Self {
1033            cipher: Aes256Gcm::new_from_slice(&key.0).expect("KEY_LENGTH is always valid"),
1034        }
1035    }
1036
1037    /// Encrypts plaintext using the cached cipher instance.
1038    pub fn encrypt(&self, nonce: &Nonce, plaintext: &[u8]) -> Ciphertext {
1039        assert!(
1040            plaintext.len() <= MAX_PLAINTEXT_LENGTH,
1041            "plaintext exceeds {} byte sanity limit, got {} bytes",
1042            MAX_PLAINTEXT_LENGTH,
1043            plaintext.len()
1044        );
1045
1046        let nonce_array = nonce.0.into();
1047        let data = self
1048            .cipher
1049            .encrypt(&nonce_array, plaintext)
1050            .expect("AES-GCM encryption cannot fail with valid inputs");
1051
1052        assert_eq!(
1053            data.len(),
1054            plaintext.len() + TAG_LENGTH,
1055            "ciphertext length mismatch"
1056        );
1057
1058        Ciphertext(data)
1059    }
1060
1061    /// Decrypts ciphertext using the cached cipher instance.
1062    pub fn decrypt(&self, nonce: &Nonce, ciphertext: &Ciphertext) -> Result<Vec<u8>, CryptoError> {
1063        let ciphertext_len = ciphertext.0.len();
1064        assert!(
1065            ciphertext_len >= TAG_LENGTH,
1066            "ciphertext too short: {ciphertext_len} bytes, need at least {TAG_LENGTH}"
1067        );
1068
1069        let nonce_array = nonce.0.into();
1070        let plaintext = self
1071            .cipher
1072            .decrypt(&nonce_array, ciphertext.0.as_slice())
1073            .map_err(|_| CryptoError::DecryptionError)?;
1074
1075        assert_eq!(
1076            plaintext.len(),
1077            ciphertext.0.len() - TAG_LENGTH,
1078            "plaintext length mismatch"
1079        );
1080
1081        Ok(plaintext)
1082    }
1083}
1084
1085/// Maximum plaintext size for encryption (64 MiB).
1086///
1087/// This is a sanity limit to catch accidental misuse. AES-GCM can encrypt
1088/// larger messages, but individual records should never approach this size.
1089#[allow(dead_code)]
1090const MAX_PLAINTEXT_LENGTH: usize = 64 * 1024 * 1024;
1091
1092/// Encrypts plaintext using AES-256-GCM.
1093///
1094/// Returns a [`Ciphertext`] containing the encrypted data with a 16-byte
1095/// authentication tag appended. The ciphertext length is `plaintext.len() + 16`.
1096///
1097/// # Arguments
1098///
1099/// * `key` - The encryption key
1100/// * `nonce` - A unique nonce derived from log position (never reuse with the same key!)
1101/// * `plaintext` - The data to encrypt
1102///
1103/// # Returns
1104///
1105/// A [`Ciphertext`] containing the encrypted data and authentication tag.
1106///
1107/// # Panics
1108///
1109/// Debug builds panic if `plaintext` exceeds 64 MiB (sanity limit).
1110pub fn encrypt(key: &EncryptionKey, nonce: &Nonce, plaintext: &[u8]) -> Ciphertext {
1111    // Precondition: plaintext length is reasonable
1112    assert!(
1113        plaintext.len() <= MAX_PLAINTEXT_LENGTH,
1114        "plaintext exceeds {} byte sanity limit, got {} bytes",
1115        MAX_PLAINTEXT_LENGTH,
1116        plaintext.len()
1117    );
1118
1119    let cipher = Aes256Gcm::new_from_slice(&key.0).expect("KEY_LENGTH is always valid");
1120    let nonce_array = nonce.0.into();
1121
1122    let data = cipher
1123        .encrypt(&nonce_array, plaintext)
1124        .expect("AES-GCM encryption cannot fail with valid inputs");
1125
1126    // Postcondition: ciphertext is plaintext + tag
1127    assert_eq!(
1128        data.len(),
1129        plaintext.len() + TAG_LENGTH,
1130        "ciphertext length mismatch: expected {}, got {}",
1131        plaintext.len() + TAG_LENGTH,
1132        data.len()
1133    );
1134
1135    Ciphertext(data)
1136}
1137
1138/// Decrypts ciphertext using AES-256-GCM.
1139///
1140/// Verifies the authentication tag and returns the original plaintext if valid.
1141///
1142/// # Arguments
1143///
1144/// * `key` - The encryption key (must match the key used for encryption)
1145/// * `nonce` - The nonce used during encryption (same log position)
1146/// * `ciphertext` - The encrypted data with authentication tag
1147///
1148/// # Errors
1149///
1150/// Returns [`CryptoError::DecryptionError`] if:
1151/// - The key is incorrect
1152/// - The nonce is incorrect
1153/// - The ciphertext has been tampered with
1154/// - The authentication tag is invalid
1155///
1156/// # Panics
1157///
1158/// Debug builds panic if `ciphertext` is shorter than [`TAG_LENGTH`] bytes.
1159pub fn decrypt(
1160    key: &EncryptionKey,
1161    nonce: &Nonce,
1162    ciphertext: &Ciphertext,
1163) -> Result<Vec<u8>, CryptoError> {
1164    // Precondition: ciphertext has at least the auth tag
1165    let ciphertext_len = ciphertext.0.len();
1166    assert!(
1167        ciphertext_len >= TAG_LENGTH,
1168        "ciphertext too short: {ciphertext_len} bytes, need at least {TAG_LENGTH}"
1169    );
1170
1171    let cipher = Aes256Gcm::new_from_slice(&key.0).expect("KEY_LENGTH is always valid");
1172    let nonce_array = nonce.0.into();
1173
1174    let plaintext = cipher
1175        .decrypt(&nonce_array, ciphertext.0.as_slice())
1176        .map_err(|_| CryptoError::DecryptionError)?;
1177
1178    // Postcondition: plaintext is ciphertext minus tag
1179    assert_eq!(
1180        plaintext.len(),
1181        ciphertext.0.len() - TAG_LENGTH,
1182        "plaintext length mismatch: expected {}, got {}",
1183        ciphertext.0.len() - TAG_LENGTH,
1184        plaintext.len()
1185    );
1186
1187    // Property: encrypt-then-decrypt roundtrip produces original plaintext
1188    // Re-encrypt the decrypted plaintext and verify it matches the original ciphertext.
1189    // This validates the AES-GCM roundtrip invariant.
1190    kimberlite_properties::always!(
1191        {
1192            let re_encrypted = encrypt(key, nonce, &plaintext);
1193            re_encrypted.to_bytes() == ciphertext.to_bytes()
1194        },
1195        "crypto.encrypt_decrypt_roundtrip",
1196        "re-encrypting decrypted plaintext must produce identical ciphertext"
1197    );
1198
1199    Ok(plaintext)
1200}
1201
1202// ============================================================================
1203// Internal Helpers
1204// ============================================================================
1205
1206/// Fills a buffer with cryptographically secure random bytes.
1207///
1208/// # Panics
1209///
1210/// Panics if the OS CSPRNG fails. This indicates a catastrophic system error
1211/// (e.g., /dev/urandom unavailable) and cannot be meaningfully recovered from.
1212fn generate_random<const N: usize>() -> [u8; N] {
1213    let mut bytes = [0u8; N];
1214    getrandom::fill(&mut bytes).expect("CSPRNG failure");
1215    bytes
1216}
1217
1218/// Fill a caller-owned buffer with CSPRNG bytes. Exposed for the
1219/// [`crate::kms`] module which needs to construct fresh key material
1220/// in bytes-first form (so it can be both sealed against an external
1221/// KMS *and* passed to `KeyEncryptionKey::from_bytes`).
1222pub(crate) fn fill_random(buf: &mut [u8]) {
1223    getrandom::fill(buf).expect("CSPRNG failure");
1224}
1225
1226// ============================================================================
1227// Tests
1228// ============================================================================
1229
1230#[cfg(test)]
1231mod tests {
1232    use super::*;
1233
1234    #[test]
1235    fn encrypt_decrypt_roundtrip() {
1236        let key = EncryptionKey::generate();
1237        let nonce = Nonce::from_position(42);
1238        let plaintext = b"sensitive tenant data";
1239
1240        let ciphertext = encrypt(&key, &nonce, plaintext);
1241        let decrypted = decrypt(&key, &nonce, &ciphertext).unwrap();
1242
1243        assert_eq!(decrypted, plaintext);
1244    }
1245
1246    #[test]
1247    fn encrypt_decrypt_empty_plaintext() {
1248        let key = EncryptionKey::generate();
1249        let nonce = Nonce::from_position(0);
1250        let plaintext = b"";
1251
1252        let ciphertext = encrypt(&key, &nonce, plaintext);
1253        let decrypted = decrypt(&key, &nonce, &ciphertext).unwrap();
1254
1255        assert_eq!(decrypted, plaintext);
1256        assert_eq!(ciphertext.len(), TAG_LENGTH); // Just the tag
1257    }
1258
1259    #[test]
1260    fn ciphertext_length_is_plaintext_plus_tag() {
1261        let key = EncryptionKey::generate();
1262        let nonce = Nonce::from_position(100);
1263        let plaintext = b"hello world";
1264
1265        let ciphertext = encrypt(&key, &nonce, plaintext);
1266
1267        assert_eq!(ciphertext.len(), plaintext.len() + TAG_LENGTH);
1268    }
1269
1270    #[test]
1271    fn wrong_key_fails_decryption() {
1272        let key1 = EncryptionKey::generate();
1273        let key2 = EncryptionKey::generate();
1274        let nonce = Nonce::from_position(42);
1275        let plaintext = b"secret message";
1276
1277        let ciphertext = encrypt(&key1, &nonce, plaintext);
1278        let result = decrypt(&key2, &nonce, &ciphertext);
1279
1280        assert!(result.is_err());
1281    }
1282
1283    #[test]
1284    fn wrong_nonce_fails_decryption() {
1285        let key = EncryptionKey::generate();
1286        let nonce1 = Nonce::from_position(42);
1287        let nonce2 = Nonce::from_position(43);
1288        let plaintext = b"secret message";
1289
1290        let ciphertext = encrypt(&key, &nonce1, plaintext);
1291        let result = decrypt(&key, &nonce2, &ciphertext);
1292
1293        assert!(result.is_err());
1294    }
1295
1296    #[test]
1297    fn tampered_ciphertext_fails_decryption() {
1298        let key = EncryptionKey::generate();
1299        let nonce = Nonce::from_position(42);
1300        let plaintext = b"secret message";
1301
1302        let ciphertext = encrypt(&key, &nonce, plaintext);
1303
1304        // Tamper with the ciphertext
1305        let mut tampered_bytes = ciphertext.to_bytes().to_vec();
1306        tampered_bytes[0] ^= 0x01; // Flip a bit
1307        let tampered = Ciphertext::from_bytes(tampered_bytes);
1308
1309        let result = decrypt(&key, &nonce, &tampered);
1310
1311        assert!(result.is_err());
1312    }
1313
1314    #[test]
1315    fn tampered_tag_fails_decryption() {
1316        let key = EncryptionKey::generate();
1317        let nonce = Nonce::from_position(42);
1318        let plaintext = b"secret message";
1319
1320        let ciphertext = encrypt(&key, &nonce, plaintext);
1321
1322        // Tamper with the auth tag (last 16 bytes)
1323        let mut tampered_bytes = ciphertext.to_bytes().to_vec();
1324        let len = tampered_bytes.len();
1325        tampered_bytes[len - 1] ^= 0x01; // Flip a bit in the tag
1326        let tampered = Ciphertext::from_bytes(tampered_bytes);
1327
1328        let result = decrypt(&key, &nonce, &tampered);
1329
1330        assert!(result.is_err());
1331    }
1332
1333    #[test]
1334    fn nonce_from_position_layout() {
1335        let nonce = Nonce::from_position(0x0102_0304_0506_0708);
1336        let bytes = nonce.to_bytes();
1337
1338        // Little-endian: least significant byte first
1339        assert_eq!(bytes[0], 0x08);
1340        assert_eq!(bytes[1], 0x07);
1341        assert_eq!(bytes[2], 0x06);
1342        assert_eq!(bytes[3], 0x05);
1343        assert_eq!(bytes[4], 0x04);
1344        assert_eq!(bytes[5], 0x03);
1345        assert_eq!(bytes[6], 0x02);
1346        assert_eq!(bytes[7], 0x01);
1347
1348        // Reserved bytes are zero
1349        assert_eq!(bytes[8], 0x00);
1350        assert_eq!(bytes[9], 0x00);
1351        assert_eq!(bytes[10], 0x00);
1352        assert_eq!(bytes[11], 0x00);
1353    }
1354
1355    #[test]
1356    fn nonce_position_zero_is_valid() {
1357        let key = EncryptionKey::generate();
1358        let nonce = Nonce::from_position(0);
1359        let plaintext = b"first record";
1360
1361        let ciphertext = encrypt(&key, &nonce, plaintext);
1362        let decrypted = decrypt(&key, &nonce, &ciphertext).unwrap();
1363
1364        assert_eq!(decrypted, plaintext);
1365    }
1366
1367    #[test]
1368    fn encryption_key_roundtrip() {
1369        let original = EncryptionKey::generate();
1370        let bytes = original.to_bytes();
1371        let restored = EncryptionKey::from_bytes(&bytes);
1372
1373        // Same key should produce same ciphertext
1374        let nonce = Nonce::from_position(1);
1375        let plaintext = b"test";
1376
1377        let ct1 = encrypt(&original, &nonce, plaintext);
1378        let ct2 = encrypt(&restored, &nonce, plaintext);
1379
1380        assert_eq!(ct1.to_bytes(), ct2.to_bytes());
1381    }
1382
1383    #[test]
1384    fn ciphertext_roundtrip() {
1385        let key = EncryptionKey::generate();
1386        let nonce = Nonce::from_position(999);
1387        let plaintext = b"data to serialize";
1388
1389        let ciphertext = encrypt(&key, &nonce, plaintext);
1390
1391        // Simulate serialization/deserialization
1392        let bytes = ciphertext.to_bytes().to_vec();
1393        let restored = Ciphertext::from_bytes(bytes);
1394
1395        let decrypted = decrypt(&key, &nonce, &restored).unwrap();
1396        assert_eq!(decrypted, plaintext);
1397    }
1398
1399    #[test]
1400    fn different_positions_produce_different_ciphertexts() {
1401        let key = EncryptionKey::generate();
1402        let plaintext = b"same plaintext";
1403
1404        let ct1 = encrypt(&key, &Nonce::from_position(1), plaintext);
1405        let ct2 = encrypt(&key, &Nonce::from_position(2), plaintext);
1406
1407        // Same plaintext, different nonces = different ciphertexts
1408        assert_ne!(ct1.to_bytes(), ct2.to_bytes());
1409    }
1410
1411    #[test]
1412    fn encryption_is_deterministic() {
1413        let key = EncryptionKey::generate();
1414        let nonce = Nonce::from_position(42);
1415        let plaintext = b"deterministic test";
1416
1417        let ct1 = encrypt(&key, &nonce, plaintext);
1418        let ct2 = encrypt(&key, &nonce, plaintext);
1419
1420        // Same key + nonce + plaintext = same ciphertext
1421        assert_eq!(ct1.to_bytes(), ct2.to_bytes());
1422    }
1423
1424    // ========================================================================
1425    // WrappedKey Tests
1426    // ========================================================================
1427
1428    #[test]
1429    fn wrap_unwrap_roundtrip() {
1430        let wrapping_key = EncryptionKey::generate();
1431        let original_key: [u8; KEY_LENGTH] = generate_random();
1432
1433        let wrapped = WrappedKey::new(&wrapping_key, &original_key);
1434        let unwrapped = wrapped.unwrap_key(&wrapping_key).unwrap();
1435
1436        assert_eq!(original_key, unwrapped);
1437    }
1438
1439    #[test]
1440    fn wrapped_key_serialization_roundtrip() {
1441        let wrapping_key = EncryptionKey::generate();
1442        let original_key: [u8; KEY_LENGTH] = generate_random();
1443
1444        let wrapped = WrappedKey::new(&wrapping_key, &original_key);
1445        let bytes = wrapped.to_bytes();
1446
1447        // Simulate storing to disk and loading back
1448        let restored = WrappedKey::from_bytes(&bytes);
1449        let unwrapped = restored.unwrap_key(&wrapping_key).unwrap();
1450
1451        assert_eq!(original_key, unwrapped);
1452    }
1453
1454    #[test]
1455    fn wrapped_key_has_correct_length() {
1456        let wrapping_key = EncryptionKey::generate();
1457        let key_to_wrap: [u8; KEY_LENGTH] = generate_random();
1458
1459        let wrapped = WrappedKey::new(&wrapping_key, &key_to_wrap);
1460        let bytes = wrapped.to_bytes();
1461
1462        assert_eq!(bytes.len(), WRAPPED_KEY_LENGTH);
1463        assert_eq!(bytes.len(), NONCE_LENGTH + KEY_LENGTH + TAG_LENGTH);
1464    }
1465
1466    #[test]
1467    fn wrong_wrapping_key_fails_unwrap() {
1468        let key1 = EncryptionKey::generate();
1469        let key2 = EncryptionKey::generate();
1470        let original: [u8; KEY_LENGTH] = generate_random();
1471
1472        let wrapped = WrappedKey::new(&key1, &original);
1473        let result = wrapped.unwrap_key(&key2);
1474
1475        assert!(result.is_err());
1476    }
1477
1478    #[test]
1479    fn tampered_wrapped_key_fails_unwrap() {
1480        let wrapping_key = EncryptionKey::generate();
1481        let original: [u8; KEY_LENGTH] = generate_random();
1482
1483        let wrapped = WrappedKey::new(&wrapping_key, &original);
1484        let mut bytes = wrapped.to_bytes();
1485
1486        // Tamper with the ciphertext portion
1487        bytes[NONCE_LENGTH] ^= 0x01;
1488
1489        let tampered = WrappedKey::from_bytes(&bytes);
1490        let result = tampered.unwrap_key(&wrapping_key);
1491
1492        assert!(result.is_err());
1493    }
1494
1495    #[test]
1496    fn different_keys_produce_different_wrapped_output() {
1497        let wrapping_key = EncryptionKey::generate();
1498        let key1: [u8; KEY_LENGTH] = generate_random();
1499        let key2: [u8; KEY_LENGTH] = generate_random();
1500
1501        let wrapped1 = WrappedKey::new(&wrapping_key, &key1);
1502        let wrapped2 = WrappedKey::new(&wrapping_key, &key2);
1503
1504        // Different plaintext keys = different ciphertexts
1505        assert_ne!(wrapped1.to_bytes(), wrapped2.to_bytes());
1506    }
1507
1508    #[test]
1509    fn same_key_wrapped_twice_differs_due_to_random_nonce() {
1510        let wrapping_key = EncryptionKey::generate();
1511        let key_to_wrap: [u8; KEY_LENGTH] = generate_random();
1512
1513        let wrapped1 = WrappedKey::new(&wrapping_key, &key_to_wrap);
1514        let wrapped2 = WrappedKey::new(&wrapping_key, &key_to_wrap);
1515
1516        // Same key wrapped twice uses different random nonces
1517        assert_ne!(wrapped1.to_bytes(), wrapped2.to_bytes());
1518
1519        // But both unwrap to the same key
1520        let unwrapped1 = wrapped1.unwrap_key(&wrapping_key).unwrap();
1521        let unwrapped2 = wrapped2.unwrap_key(&wrapping_key).unwrap();
1522        assert_eq!(unwrapped1, unwrapped2);
1523        assert_eq!(unwrapped1, key_to_wrap);
1524    }
1525
1526    // ========================================================================
1527    // Key Hierarchy Tests
1528    // ========================================================================
1529
1530    #[test]
1531    fn master_key_generate_and_restore() {
1532        let master = InMemoryMasterKey::generate();
1533        let bytes = master.to_bytes();
1534
1535        let restored = InMemoryMasterKey::from_bytes(&bytes);
1536
1537        // Both should wrap the same KEK identically (modulo random nonce)
1538        let kek_bytes: [u8; KEY_LENGTH] = generate_random();
1539        let wrapped1 = master.wrap_kek(&kek_bytes);
1540        let wrapped2 = restored.wrap_kek(&kek_bytes);
1541
1542        // Different nonces, but both unwrap to same key
1543        let unwrapped1 = master.unwrap_kek(&wrapped1).unwrap();
1544        let unwrapped2 = restored.unwrap_kek(&wrapped2).unwrap();
1545
1546        assert_eq!(unwrapped1, kek_bytes);
1547        assert_eq!(unwrapped2, kek_bytes);
1548    }
1549
1550    #[test]
1551    fn kek_generate_and_restore() {
1552        let master = InMemoryMasterKey::generate();
1553
1554        // Generate a new KEK
1555        let (kek, wrapped_kek) = KeyEncryptionKey::generate_and_wrap(&master);
1556
1557        // Restore it from the wrapped form
1558        let restored_kek = KeyEncryptionKey::restore(&master, &wrapped_kek).unwrap();
1559
1560        // Both should wrap the same DEK bytes
1561        let dek_bytes: [u8; KEY_LENGTH] = generate_random();
1562        let wrapped1 = kek.wrap_dek(&dek_bytes);
1563        let wrapped2 = restored_kek.wrap_dek(&dek_bytes);
1564
1565        // Verify both can unwrap each other's wrapped keys
1566        let unwrapped1 = kek.unwrap_dek(&wrapped2).unwrap();
1567        let unwrapped2 = restored_kek.unwrap_dek(&wrapped1).unwrap();
1568
1569        assert_eq!(unwrapped1, dek_bytes);
1570        assert_eq!(unwrapped2, dek_bytes);
1571    }
1572
1573    #[test]
1574    fn dek_generate_and_restore() {
1575        let master = InMemoryMasterKey::generate();
1576        let (kek, _) = KeyEncryptionKey::generate_and_wrap(&master);
1577
1578        // Generate a new DEK
1579        let (dek, wrapped_dek) = DataEncryptionKey::generate_and_wrap(&kek);
1580
1581        // Restore it from the wrapped form
1582        let restored_dek = DataEncryptionKey::restore(&kek, &wrapped_dek).unwrap();
1583
1584        // Both should encrypt/decrypt identically
1585        let nonce = Nonce::from_position(42);
1586        let plaintext = b"secret tenant data";
1587
1588        let ciphertext = encrypt(dek.encryption_key(), &nonce, plaintext);
1589        let decrypted = decrypt(restored_dek.encryption_key(), &nonce, &ciphertext).unwrap();
1590
1591        assert_eq!(decrypted, plaintext);
1592    }
1593
1594    #[test]
1595    fn full_key_hierarchy_roundtrip() {
1596        // Master key (root of trust)
1597        let master = InMemoryMasterKey::generate();
1598
1599        // KEK for tenant "acme"
1600        let (kek_acme, wrapped_kek_acme) = KeyEncryptionKey::generate_and_wrap(&master);
1601
1602        // DEK for segment 0 of tenant "acme"
1603        let (dek_seg0, wrapped_dek_seg0) = DataEncryptionKey::generate_and_wrap(&kek_acme);
1604
1605        // Encrypt some data
1606        let nonce = Nonce::from_position(0);
1607        let plaintext = b"acme's sensitive record";
1608        let ciphertext = encrypt(dek_seg0.encryption_key(), &nonce, plaintext);
1609
1610        // --- Simulate restart: reload everything from wrapped forms ---
1611
1612        // Restore KEK from wrapped form
1613        let kek = KeyEncryptionKey::restore(&master, &wrapped_kek_acme).unwrap();
1614
1615        // Restore DEK from wrapped form
1616        let dek = DataEncryptionKey::restore(&kek, &wrapped_dek_seg0).unwrap();
1617
1618        // Decrypt the data
1619        let decrypted = decrypt(dek.encryption_key(), &nonce, &ciphertext).unwrap();
1620
1621        assert_eq!(decrypted, plaintext);
1622    }
1623
1624    #[test]
1625    fn wrong_master_key_fails_kek_restore() {
1626        let master1 = InMemoryMasterKey::generate();
1627        let master2 = InMemoryMasterKey::generate();
1628
1629        let (_, wrapped_kek) = KeyEncryptionKey::generate_and_wrap(&master1);
1630
1631        // Try to restore with wrong master key
1632        let result = KeyEncryptionKey::restore(&master2, &wrapped_kek);
1633
1634        assert!(result.is_err());
1635    }
1636
1637    #[test]
1638    fn wrong_kek_fails_dek_restore() {
1639        let master = InMemoryMasterKey::generate();
1640        let (kek1, _) = KeyEncryptionKey::generate_and_wrap(&master);
1641        let (kek2, _) = KeyEncryptionKey::generate_and_wrap(&master);
1642
1643        let (_, wrapped_dek) = DataEncryptionKey::generate_and_wrap(&kek1);
1644
1645        // Try to restore with wrong KEK
1646        let result = DataEncryptionKey::restore(&kek2, &wrapped_dek);
1647
1648        assert!(result.is_err());
1649    }
1650
1651    #[test]
1652    fn tenant_isolation_via_kek() {
1653        let master = InMemoryMasterKey::generate();
1654
1655        // Two tenants with different KEKs
1656        let (kek_tenant_a, _) = KeyEncryptionKey::generate_and_wrap(&master);
1657        let (kek_tenant_b, _) = KeyEncryptionKey::generate_and_wrap(&master);
1658
1659        // Tenant A encrypts data
1660        let (dek_a, wrapped_dek_a) = DataEncryptionKey::generate_and_wrap(&kek_tenant_a);
1661        let nonce = Nonce::from_position(0);
1662        let _ciphertext_a = encrypt(dek_a.encryption_key(), &nonce, b"tenant A secret");
1663
1664        // Tenant B cannot restore tenant A's DEK
1665        let result = DataEncryptionKey::restore(&kek_tenant_b, &wrapped_dek_a);
1666        assert!(result.is_err());
1667
1668        // Even if somehow they got the wrapped DEK bytes, they can't decrypt
1669        // (This is guaranteed by the above test, but demonstrates the isolation)
1670    }
1671
1672    #[test]
1673    fn wrapped_kek_serialization_roundtrip() {
1674        let master = InMemoryMasterKey::generate();
1675        let (_, wrapped_kek) = KeyEncryptionKey::generate_and_wrap(&master);
1676
1677        // Serialize to bytes (for storage)
1678        let bytes = wrapped_kek.to_bytes();
1679
1680        // Deserialize back
1681        let restored_wrapped = WrappedKey::from_bytes(&bytes);
1682
1683        // Should still be unwrappable
1684        let kek = KeyEncryptionKey::restore(&master, &restored_wrapped).unwrap();
1685
1686        // And should work for wrapping DEKs
1687        let dek_bytes: [u8; KEY_LENGTH] = generate_random();
1688        let dek_wrapped = kek.wrap_dek(&dek_bytes);
1689        let unwrapped = kek.unwrap_dek(&dek_wrapped).unwrap();
1690
1691        assert_eq!(unwrapped, dek_bytes);
1692    }
1693
1694    #[test]
1695    fn dek_encryption_key_reference() {
1696        let master = InMemoryMasterKey::generate();
1697        let (kek, _) = KeyEncryptionKey::generate_and_wrap(&master);
1698        let (dek, _) = DataEncryptionKey::generate_and_wrap(&kek);
1699
1700        // Get reference to inner key
1701        let key_ref = dek.encryption_key();
1702
1703        // Use it for encryption
1704        let nonce = Nonce::from_position(1);
1705        let ciphertext = encrypt(key_ref, &nonce, b"test");
1706
1707        // And decryption
1708        let plaintext = decrypt(key_ref, &nonce, &ciphertext).unwrap();
1709
1710        assert_eq!(plaintext, b"test");
1711    }
1712
1713    // ========================================================================
1714    // Property-Based Tests (for comprehensive coverage)
1715    // ========================================================================
1716
1717    use proptest::prelude::*;
1718
1719    proptest! {
1720        /// Property: encrypt + decrypt is the identity for any plaintext
1721        #[test]
1722        fn prop_encrypt_decrypt_roundtrip(plaintext in prop::collection::vec(any::<u8>(), 0..10000)) {
1723            let key = EncryptionKey::generate();
1724            let nonce = Nonce::from_position(42);
1725
1726            let ciphertext = encrypt(&key, &nonce, &plaintext);
1727            let decrypted = decrypt(&key, &nonce, &ciphertext)
1728                .expect("decryption should succeed for valid ciphertext");
1729
1730            prop_assert_eq!(decrypted, plaintext);
1731        }
1732
1733        /// Property: different nonces produce different ciphertexts for same plaintext
1734        #[test]
1735        fn prop_different_nonces_different_ciphertext(
1736            positions in (any::<u64>(), any::<u64>()).prop_filter("positions must differ", |(p1, p2)| p1 != p2),
1737            plaintext in prop::collection::vec(any::<u8>(), 1..1000),
1738        ) {
1739            let (position1, position2) = positions;
1740            let key = EncryptionKey::generate();
1741            let nonce1 = Nonce::from_position(position1);
1742            let nonce2 = Nonce::from_position(position2);
1743
1744            let ct1 = encrypt(&key, &nonce1, &plaintext);
1745            let ct2 = encrypt(&key, &nonce2, &plaintext);
1746
1747            // Different nonces must produce different ciphertexts
1748            prop_assert_ne!(ct1.to_bytes(), ct2.to_bytes());
1749        }
1750
1751        /// Property: ciphertext is always plaintext length + TAG_LENGTH
1752        #[test]
1753        fn prop_ciphertext_length(plaintext in prop::collection::vec(any::<u8>(), 0..10000)) {
1754            let key = EncryptionKey::generate();
1755            let nonce = Nonce::from_position(100);
1756
1757            let ciphertext = encrypt(&key, &nonce, &plaintext);
1758
1759            prop_assert_eq!(ciphertext.to_bytes().len(), plaintext.len() + TAG_LENGTH);
1760        }
1761
1762        /// Property: decryption with wrong key always fails
1763        #[test]
1764        fn prop_wrong_key_fails(plaintext in prop::collection::vec(any::<u8>(), 1..1000)) {
1765            let key1 = EncryptionKey::generate();
1766            let key2 = EncryptionKey::generate();
1767            let nonce = Nonce::from_position(42);
1768
1769            let ciphertext = encrypt(&key1, &nonce, &plaintext);
1770            let result = decrypt(&key2, &nonce, &ciphertext);
1771
1772            prop_assert!(result.is_err(), "decryption with wrong key must fail");
1773        }
1774
1775        /// Property: decryption with wrong nonce always fails
1776        #[test]
1777        fn prop_wrong_nonce_fails(
1778            positions in (any::<u64>(), any::<u64>()).prop_filter("positions must differ", |(p1, p2)| p1 != p2),
1779            plaintext in prop::collection::vec(any::<u8>(), 1..1000),
1780        ) {
1781            let (position1, position2) = positions;
1782            let key = EncryptionKey::generate();
1783            let nonce1 = Nonce::from_position(position1);
1784            let nonce2 = Nonce::from_position(position2);
1785
1786            let ciphertext = encrypt(&key, &nonce1, &plaintext);
1787            let result = decrypt(&key, &nonce2, &ciphertext);
1788
1789            prop_assert!(result.is_err(), "decryption with wrong nonce must fail");
1790        }
1791
1792        /// Property: any bit flip in ciphertext causes decryption failure
1793        #[test]
1794        fn prop_tampered_ciphertext_fails(
1795            plaintext in prop::collection::vec(any::<u8>(), 1..1000),
1796            bit_position in 0usize..8000, // Up to 1000 bytes * 8 bits
1797        ) {
1798            let key = EncryptionKey::generate();
1799            let nonce = Nonce::from_position(42);
1800
1801            let ciphertext = encrypt(&key, &nonce, &plaintext);
1802            let ct_bytes = ciphertext.to_bytes();
1803
1804            // Only tamper if bit_position is within range
1805            if bit_position / 8 < ct_bytes.len() {
1806                let mut tampered = ct_bytes.to_vec();
1807                let byte_idx = bit_position / 8;
1808                let bit_idx = bit_position % 8;
1809                tampered[byte_idx] ^= 1 << bit_idx;
1810
1811                let tampered_ct = Ciphertext::from_bytes(tampered);
1812                let result = decrypt(&key, &nonce, &tampered_ct);
1813
1814                prop_assert!(result.is_err(), "tampered ciphertext must fail authentication");
1815            }
1816        }
1817
1818        /// Property: wrapped key unwraps to original key
1819        #[test]
1820        fn prop_wrap_unwrap_roundtrip(key_bytes in prop::array::uniform32(any::<u8>())) {
1821            let wrapping_key = EncryptionKey::generate();
1822
1823            let wrapped = WrappedKey::new(&wrapping_key, &key_bytes);
1824            let unwrapped = wrapped.unwrap_key(&wrapping_key)
1825                .expect("unwrapping with correct key should succeed");
1826
1827            prop_assert_eq!(unwrapped, key_bytes);
1828        }
1829
1830        /// Property: encryption is deterministic (same key+nonce+plaintext = same ciphertext)
1831        #[test]
1832        fn prop_encryption_deterministic(
1833            position in any::<u64>(),
1834            plaintext in prop::collection::vec(any::<u8>(), 0..1000),
1835        ) {
1836            let key = EncryptionKey::generate();
1837            let nonce = Nonce::from_position(position);
1838
1839            let ct1 = encrypt(&key, &nonce, &plaintext);
1840            let ct2 = encrypt(&key, &nonce, &plaintext);
1841
1842            prop_assert_eq!(ct1.to_bytes(), ct2.to_bytes());
1843        }
1844
1845        /// Property: key serialization roundtrip preserves functionality
1846        #[test]
1847        fn prop_key_serialization_roundtrip(plaintext in prop::collection::vec(any::<u8>(), 1..1000)) {
1848            let original = EncryptionKey::generate();
1849            let bytes = original.to_bytes();
1850            let restored = EncryptionKey::from_bytes(&bytes);
1851
1852            let nonce = Nonce::from_position(1);
1853            let ct1 = encrypt(&original, &nonce, &plaintext);
1854            let ct2 = encrypt(&restored, &nonce, &plaintext);
1855
1856            // Same key produces same ciphertext
1857            prop_assert_eq!(ct1.to_bytes(), ct2.to_bytes());
1858
1859            // Both can decrypt
1860            let decrypted1 = decrypt(&original, &nonce, &ct1).unwrap();
1861            let decrypted2 = decrypt(&restored, &nonce, &ct2).unwrap();
1862            prop_assert_eq!(&decrypted1[..], &plaintext[..]);
1863            prop_assert_eq!(&decrypted2[..], &plaintext[..]);
1864        }
1865
1866        /// Property: nonce from position is injective (different positions = different nonces)
1867        #[test]
1868        fn prop_nonce_position_injective(
1869            positions in (any::<u64>(), any::<u64>()).prop_filter("positions must differ", |(p1, p2)| p1 != p2),
1870        ) {
1871            let (pos1, pos2) = positions;
1872            let nonce1 = Nonce::from_position(pos1);
1873            let nonce2 = Nonce::from_position(pos2);
1874
1875            prop_assert_ne!(nonce1.to_bytes(), nonce2.to_bytes());
1876        }
1877    }
1878
1879    // ========================================================================
1880    // Additional Edge Case Tests
1881    // ========================================================================
1882
1883    #[test]
1884    fn truncated_ciphertext_fails() {
1885        let key = EncryptionKey::generate();
1886        let nonce = Nonce::from_position(42);
1887        let plaintext = b"hello world";
1888
1889        let ciphertext = encrypt(&key, &nonce, plaintext);
1890        let ct_bytes = ciphertext.to_bytes();
1891
1892        // Truncate to less than TAG_LENGTH
1893        if ct_bytes.len() > TAG_LENGTH {
1894            let truncated = Ciphertext::from_bytes(ct_bytes[..TAG_LENGTH].to_vec());
1895            let result = decrypt(&key, &nonce, &truncated);
1896            assert!(result.is_err(), "truncated ciphertext must fail");
1897        }
1898    }
1899
1900    #[test]
1901    fn corrupted_tag_fails() {
1902        let key = EncryptionKey::generate();
1903        let nonce = Nonce::from_position(42);
1904        let plaintext = b"authenticated encryption test";
1905
1906        let ciphertext = encrypt(&key, &nonce, plaintext);
1907        let mut ct_bytes = ciphertext.to_bytes().to_vec();
1908
1909        // Corrupt the last byte of the tag
1910        let last_idx = ct_bytes.len() - 1;
1911        ct_bytes[last_idx] = ct_bytes[last_idx].wrapping_add(1);
1912
1913        let corrupted = Ciphertext::from_bytes(ct_bytes);
1914        let result = decrypt(&key, &nonce, &corrupted);
1915
1916        assert!(
1917            result.is_err(),
1918            "corrupted tag must cause authentication failure"
1919        );
1920    }
1921
1922    #[test]
1923    fn maximum_position_nonce() {
1924        let key = EncryptionKey::generate();
1925        let max_position = u64::MAX;
1926        let nonce = Nonce::from_position(max_position);
1927        let plaintext = b"test at max position";
1928
1929        let ciphertext = encrypt(&key, &nonce, plaintext);
1930        let decrypted = decrypt(&key, &nonce, &ciphertext).unwrap();
1931
1932        assert_eq!(decrypted, plaintext);
1933    }
1934
1935    #[test]
1936    #[cfg_attr(
1937        miri,
1938        ignore = "AES-GCM 1 MiB roundtrip — AUDIT-2026-05 H-6. \
1939                  MIRI's value is UB detection on the unsafe / FFI \
1940                  surface; arithmetic correctness on AES-GCM internals \
1941                  is upstream `aes-gcm` crate's responsibility, exercised \
1942                  here via property tests + fuzz under cargo test. \
1943                  Annotation closes the v0.7.0 ROADMAP MIRI-timeout item."
1944    )]
1945    fn large_plaintext_encryption() {
1946        let key = EncryptionKey::generate();
1947        let nonce = Nonce::from_position(1);
1948        // 1MB plaintext
1949        let plaintext = vec![0xAB; 1_024 * 1_024];
1950
1951        let ciphertext = encrypt(&key, &nonce, &plaintext);
1952        let decrypted = decrypt(&key, &nonce, &ciphertext).unwrap();
1953
1954        assert_eq!(decrypted, plaintext);
1955        assert_eq!(ciphertext.to_bytes().len(), plaintext.len() + TAG_LENGTH);
1956    }
1957
1958    #[test]
1959    fn wrapped_key_wrong_length_from_bytes() {
1960        // Test that from_bytes handles wrong length gracefully
1961        let mut too_short = [0u8; WRAPPED_KEY_LENGTH];
1962        too_short[WRAPPED_KEY_LENGTH - 1] = 0xFF; // Make it non-zero to ensure it's different
1963
1964        let wrapped = WrappedKey::from_bytes(&too_short);
1965        let wrapping_key = EncryptionKey::generate();
1966        let result = wrapped.unwrap_key(&wrapping_key);
1967
1968        // Should fail to unwrap (authentication will fail)
1969        assert!(result.is_err(), "corrupted wrapped key should fail unwrap");
1970    }
1971
1972    #[test]
1973    #[should_panic(expected = "EncryptionKey bytes are all zeros")]
1974    fn encryption_key_all_zeros_panics() {
1975        // Production assertion rejects all-zero keys
1976        let _key = EncryptionKey::from_bytes(&[0u8; KEY_LENGTH]);
1977    }
1978
1979    #[test]
1980    #[should_panic(expected = "EncryptionKey random bytes are all zeros")]
1981    fn encryption_key_from_random_bytes_all_zeros_panics() {
1982        // Production assertion checks for non-degenerate keys
1983        let key_bytes = [0u8; KEY_LENGTH];
1984        let key = EncryptionKey::from_random_bytes(key_bytes);
1985        drop(key);
1986    }
1987
1988    #[test]
1989    fn ciphertext_serialization_preserves_authentication() {
1990        let key = EncryptionKey::generate();
1991        let nonce = Nonce::from_position(123);
1992        let plaintext = b"serialization test";
1993
1994        let ciphertext = encrypt(&key, &nonce, plaintext);
1995
1996        // Serialize and deserialize
1997        let bytes = ciphertext.to_bytes().to_vec();
1998        let restored = Ciphertext::from_bytes(bytes);
1999
2000        // Should still authenticate correctly
2001        let decrypted = decrypt(&key, &nonce, &restored).unwrap();
2002        assert_eq!(decrypted, plaintext);
2003
2004        // Tamper after deserialization
2005        let mut tampered_bytes = restored.to_bytes().to_vec();
2006        tampered_bytes[0] ^= 0x01;
2007        let tampered = Ciphertext::from_bytes(tampered_bytes);
2008
2009        let result = decrypt(&key, &nonce, &tampered);
2010        assert!(
2011            result.is_err(),
2012            "tampered deserialized ciphertext must fail"
2013        );
2014    }
2015
2016    #[test]
2017    fn multiple_wrapped_keys_independent() {
2018        let wrapping_key = EncryptionKey::generate();
2019        let key1: [u8; KEY_LENGTH] = generate_random();
2020        let key2: [u8; KEY_LENGTH] = generate_random();
2021
2022        let wrapped1 = WrappedKey::new(&wrapping_key, &key1);
2023        let wrapped2 = WrappedKey::new(&wrapping_key, &key2);
2024
2025        // Each wrapped key unwraps to its original
2026        assert_eq!(wrapped1.unwrap_key(&wrapping_key).unwrap(), key1);
2027        assert_eq!(wrapped2.unwrap_key(&wrapping_key).unwrap(), key2);
2028
2029        // Wrapped keys are different (due to random nonces)
2030        assert_ne!(wrapped1.to_bytes(), wrapped2.to_bytes());
2031    }
2032
2033    #[test]
2034    fn nonce_reserves_upper_bytes() {
2035        // Verify that upper 4 bytes are always zero (reserved for future use)
2036        for position in [0u64, 1, 42, u64::MAX / 2, u64::MAX] {
2037            let nonce = Nonce::from_position(position);
2038            let bytes = nonce.to_bytes();
2039
2040            // Bytes 8-11 must be zero (reserved)
2041            assert_eq!(bytes[8], 0, "byte 8 must be reserved (zero)");
2042            assert_eq!(bytes[9], 0, "byte 9 must be reserved (zero)");
2043            assert_eq!(bytes[10], 0, "byte 10 must be reserved (zero)");
2044            assert_eq!(bytes[11], 0, "byte 11 must be reserved (zero)");
2045        }
2046    }
2047
2048    #[test]
2049    fn kek_dek_hierarchy_isolation() {
2050        let master = InMemoryMasterKey::generate();
2051
2052        // Create two separate KEK/DEK hierarchies
2053        let (kek1, _) = KeyEncryptionKey::generate_and_wrap(&master);
2054        let (kek2, _) = KeyEncryptionKey::generate_and_wrap(&master);
2055
2056        let (_dek1, wrapped_dek1) = DataEncryptionKey::generate_and_wrap(&kek1);
2057        let (_dek2, wrapped_dek2) = DataEncryptionKey::generate_and_wrap(&kek2);
2058
2059        // kek1 can unwrap dek1 but not dek2
2060        assert!(DataEncryptionKey::restore(&kek1, &wrapped_dek1).is_ok());
2061        assert!(DataEncryptionKey::restore(&kek1, &wrapped_dek2).is_err());
2062
2063        // kek2 can unwrap dek2 but not dek1
2064        assert!(DataEncryptionKey::restore(&kek2, &wrapped_dek2).is_ok());
2065        assert!(DataEncryptionKey::restore(&kek2, &wrapped_dek1).is_err());
2066    }
2067
2068    #[test]
2069    fn encryption_key_zeroize_on_drop() {
2070        // This test verifies that ZeroizeOnDrop is working
2071        // We can't directly observe the memory being zeroed, but we can verify
2072        // the trait is properly applied (compilation would fail otherwise)
2073        let key = EncryptionKey::generate();
2074        let _bytes = key.to_bytes();
2075        // When key goes out of scope, ZeroizeOnDrop should zero the memory
2076        drop(key);
2077        // If ZeroizeOnDrop wasn't working, this would be a security issue
2078    }
2079
2080    // ========================================================================
2081    // AUDIT-2026-04 C-1 / H-4 — DataEncryptionKey::shred
2082    // ========================================================================
2083
2084    /// Shred yields a 32-byte digest that is deterministic in (key,
2085    /// nonce) and consumes the key.
2086    #[test]
2087    fn dek_shred_is_deterministic_in_key_and_nonce() {
2088        let master = InMemoryMasterKey::generate();
2089        let (kek, _) = KeyEncryptionKey::generate_and_wrap(&master);
2090        let (dek1, wrapped) = DataEncryptionKey::generate_and_wrap(&kek);
2091        let dek2 = DataEncryptionKey::restore(&kek, &wrapped).unwrap();
2092
2093        let nonce = [0xAB; 32];
2094        let d1 = dek1.shred(&nonce);
2095        let d2 = dek2.shred(&nonce);
2096        assert_eq!(d1, d2, "same key + same nonce → same shred digest");
2097    }
2098
2099    /// Different keys produce different shred digests, even with the
2100    /// same nonce. This is the property that makes the erasure proof
2101    /// bind to the specific key material destroyed.
2102    #[test]
2103    fn dek_shred_differs_on_different_keys() {
2104        let master = InMemoryMasterKey::generate();
2105        let (kek, _) = KeyEncryptionKey::generate_and_wrap(&master);
2106        let (dek_a, _) = DataEncryptionKey::generate_and_wrap(&kek);
2107        let (dek_b, _) = DataEncryptionKey::generate_and_wrap(&kek);
2108        let nonce = [0xCD; 32];
2109        let da = dek_a.shred(&nonce);
2110        let db = dek_b.shred(&nonce);
2111        assert_ne!(da, db, "different keys must produce different digests");
2112    }
2113
2114    /// Different nonces on the same key produce different digests —
2115    /// prevents replay of an earlier shred digest as proof of a new
2116    /// erasure.
2117    #[test]
2118    fn dek_shred_differs_on_different_nonces() {
2119        let master = InMemoryMasterKey::generate();
2120        let (kek, wrapped) = (
2121            KeyEncryptionKey::generate_and_wrap(&master).0,
2122            None::<WrappedKey>,
2123        );
2124        let _ = wrapped;
2125        let (dek_a, wrapped_a) = DataEncryptionKey::generate_and_wrap(&kek);
2126        // Restore a second instance of the same key so we can shred it
2127        // twice with different nonces.
2128        let dek_b = DataEncryptionKey::restore(&kek, &wrapped_a).unwrap();
2129        let d1 = dek_a.shred(&[1u8; 32]);
2130        let d2 = dek_b.shred(&[2u8; 32]);
2131        assert_ne!(d1, d2, "same key + different nonces → different digests");
2132    }
2133
2134    /// Shred digest is NOT the key bytes — verifies the hash function
2135    /// is actually being applied. A trivial implementation that
2136    /// returned `key_bytes || nonce[..some]` would pass deterministic/
2137    /// differing tests but fail this one.
2138    #[test]
2139    fn dek_shred_digest_differs_from_key_bytes() {
2140        let master = InMemoryMasterKey::generate();
2141        let (kek, _) = KeyEncryptionKey::generate_and_wrap(&master);
2142        let (dek, wrapped) = DataEncryptionKey::generate_and_wrap(&kek);
2143        let key_bytes = dek.encryption_key().to_bytes();
2144        let digest = DataEncryptionKey::restore(&kek, &wrapped)
2145            .unwrap()
2146            .shred(&[0u8; 32]);
2147        assert_ne!(digest, key_bytes, "digest must not leak key bytes");
2148    }
2149}