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}