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