1use crate::error::{MongrelError, Result};
14#[cfg(feature = "encryption")]
15use zeroize::Zeroizing;
16
17pub const DEK_LEN: usize = 32;
19
20pub fn fill_random(buf: &mut [u8]) -> Result<()> {
22 getrandom::getrandom(buf).map_err(|error| MongrelError::EntropyUnavailable(error.to_string()))
23}
24
25pub trait Cipher: Send + Sync {
27 fn encrypt_page(&self, nonce: &[u8; 12], plaintext: &[u8]) -> Result<Vec<u8>>;
29
30 fn decrypt_page(&self, nonce: &[u8; 12], ciphertext: &[u8]) -> Result<Vec<u8>>;
32
33 fn encrypt_page_with_aad(
34 &self,
35 nonce: &[u8; 12],
36 plaintext: &[u8],
37 aad: &[u8],
38 ) -> Result<Vec<u8>> {
39 if aad.is_empty() {
40 self.encrypt_page(nonce, plaintext)
41 } else {
42 Err(MongrelError::Encryption(
43 "cipher does not support associated data".into(),
44 ))
45 }
46 }
47
48 fn decrypt_page_with_aad(
49 &self,
50 nonce: &[u8; 12],
51 ciphertext: &[u8],
52 aad: &[u8],
53 ) -> Result<Vec<u8>> {
54 if aad.is_empty() {
55 self.decrypt_page(nonce, ciphertext)
56 } else {
57 Err(MongrelError::Decryption(
58 "cipher does not support associated data".into(),
59 ))
60 }
61 }
62}
63
64#[derive(Debug, Default, Clone, Copy)]
66pub struct PlaintextCipher;
67
68impl Cipher for PlaintextCipher {
69 fn encrypt_page(&self, _nonce: &[u8; 12], plaintext: &[u8]) -> Result<Vec<u8>> {
70 Ok(plaintext.to_vec())
71 }
72
73 fn decrypt_page(&self, _nonce: &[u8; 12], ciphertext: &[u8]) -> Result<Vec<u8>> {
74 Ok(ciphertext.to_vec())
75 }
76}
77
78#[cfg(feature = "encryption")]
79mod aes {
80 use super::{Cipher, MongrelError, Result};
81 use aes_gcm::aead::{Aead, Payload};
82 use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
83
84 pub struct AesCipher {
87 cipher: Aes256Gcm,
88 }
89
90 impl AesCipher {
91 pub fn new(key: &[u8]) -> Result<Self> {
93 if key.len() != 32 {
94 return Err(MongrelError::InvalidArgument(format!(
95 "aes-256 key must be 32 bytes, got {}",
96 key.len()
97 )));
98 }
99 Ok(Self {
100 cipher: Aes256Gcm::new_from_slice(key)
101 .map_err(|e| MongrelError::Encryption(format!("aes key init: {e}")))?,
102 })
103 }
104 }
105
106 impl Cipher for AesCipher {
107 fn encrypt_page(&self, nonce: &[u8; 12], plaintext: &[u8]) -> Result<Vec<u8>> {
108 let nonce = Nonce::from_slice(nonce);
109 self.cipher
110 .encrypt(nonce, plaintext)
111 .map_err(|e| MongrelError::Encryption(format!("aes encrypt: {e}")))
112 }
113
114 fn decrypt_page(&self, nonce: &[u8; 12], ciphertext: &[u8]) -> Result<Vec<u8>> {
115 let nonce = Nonce::from_slice(nonce);
116 self.cipher
117 .decrypt(nonce, ciphertext)
118 .map_err(|e| MongrelError::Decryption(format!("aes decrypt: {e}")))
119 }
120
121 fn encrypt_page_with_aad(
122 &self,
123 nonce: &[u8; 12],
124 plaintext: &[u8],
125 aad: &[u8],
126 ) -> Result<Vec<u8>> {
127 self.cipher
128 .encrypt(
129 Nonce::from_slice(nonce),
130 Payload {
131 msg: plaintext,
132 aad,
133 },
134 )
135 .map_err(|e| MongrelError::Encryption(format!("aes encrypt: {e}")))
136 }
137
138 fn decrypt_page_with_aad(
139 &self,
140 nonce: &[u8; 12],
141 ciphertext: &[u8],
142 aad: &[u8],
143 ) -> Result<Vec<u8>> {
144 self.cipher
145 .decrypt(
146 Nonce::from_slice(nonce),
147 Payload {
148 msg: ciphertext,
149 aad,
150 },
151 )
152 .map_err(|e| MongrelError::Decryption(format!("aes decrypt: {e}")))
153 }
154 }
155}
156
157#[cfg(feature = "encryption")]
158pub use aes::AesCipher;
159
160#[cfg(feature = "encryption")]
161mod key {
162 use super::{fill_random, Cipher, MongrelError, Result, DEK_LEN};
163 use aes_gcm::aead::{Aead, KeyInit};
164 use aes_gcm::{Aes256Gcm, Nonce};
165 use serde::{Deserialize, Serialize};
166 use zeroize::Zeroizing;
167
168 pub const SALT_LEN: usize = 16;
170 pub const ALGO_AES_GCM: u8 = 1;
172 const KEK_INFO: &[u8] = b"mongreldb/kek/v1";
174 const KEK_RAW_INFO: &[u8] = b"mongreldb/kek-raw/v1";
176 const ARGON2_M_COST: u32 = 19_456;
178 const ARGON2_T_COST: u32 = 2;
180 const ARGON2_P_COST: u32 = 1;
182
183 pub struct Kek(Zeroizing<[u8; DEK_LEN]>);
188
189 impl Kek {
190 pub fn derive(passphrase: &str, salt: &[u8; SALT_LEN]) -> Result<Self> {
193 let params =
194 argon2::Params::new(ARGON2_M_COST, ARGON2_T_COST, ARGON2_P_COST, Some(DEK_LEN))
195 .map_err(|e| MongrelError::Encryption(format!("argon2 params: {e}")))?;
196 let argon =
197 argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params);
198 let mut prk = Zeroizing::new([0u8; DEK_LEN]);
200 argon
201 .hash_password_into(passphrase.as_bytes(), salt, prk.as_mut())
202 .map_err(|e| MongrelError::Encryption(format!("argon2 derive: {e}")))?;
203 let hk = hkdf::Hkdf::<sha2::Sha256>::from_prk(prk.as_ref())
205 .map_err(|e| MongrelError::Encryption(format!("hkdf from_prk: {e}")))?;
206 let mut kek = Zeroizing::new([0u8; DEK_LEN]);
207 hk.expand(KEK_INFO, kek.as_mut())
208 .map_err(|e| MongrelError::Encryption(format!("hkdf expand: {e}")))?;
209 Ok(Kek(kek))
210 }
211
212 pub fn from_raw_key(raw: &[u8], salt: &[u8; SALT_LEN]) -> Result<Self> {
217 if raw.len() < DEK_LEN {
218 return Err(MongrelError::InvalidArgument(format!(
219 "raw key must be >= {DEK_LEN} bytes, got {}",
220 raw.len()
221 )));
222 }
223 let hk = hkdf::Hkdf::<sha2::Sha256>::new(Some(salt), raw);
225 let mut kek = Zeroizing::new([0u8; DEK_LEN]);
226 hk.expand(KEK_RAW_INFO, kek.as_mut())
227 .map_err(|e| MongrelError::Encryption(format!("hkdf expand: {e}")))?;
228 Ok(Kek(kek))
229 }
230
231 pub fn derive_wal_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
233 self.derive_subkey(b"mongreldb/wal/v1")
234 }
235
236 pub fn derive_shared_wal_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
240 self.derive_subkey(b"mongreldb/swal/v1")
241 }
242
243 pub fn derive_table_wal_key(&self, table_id: u64) -> Zeroizing<[u8; DEK_LEN]> {
246 let mut info = b"mongreldb/twal/".to_vec();
247 info.extend_from_slice(&table_id.to_be_bytes());
248 info.extend_from_slice(b"/v1");
249 self.derive_subkey(&info)
250 }
251
252 pub fn derive_cache_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
254 self.derive_subkey(b"mongreldb/rcache/v1")
255 }
256
257 pub fn derive_idx_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
259 self.derive_subkey(b"mongreldb/idx/v1")
260 }
261
262 pub fn derive_run_mac_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
264 self.derive_subkey(b"mongreldb/run-mac/v1")
265 }
266
267 pub fn derive_meta_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
270 self.derive_subkey(b"mongreldb/meta/v1")
271 }
272
273 pub fn wrap_dek(&self, dek: &[u8; DEK_LEN], wrap_nonce: &[u8; 12]) -> Result<Vec<u8>> {
277 let cipher = Aes256Gcm::new_from_slice(&self.0[..])
278 .map_err(|e| MongrelError::Encryption(format!("kek aes init: {e}")))?;
279 cipher
280 .encrypt(Nonce::from_slice(wrap_nonce), dek.as_slice())
281 .map_err(|e| MongrelError::Encryption(format!("dek wrap: {e}")))
282 }
283
284 pub fn unwrap_dek(
286 &self,
287 wrapped: &[u8],
288 wrap_nonce: &[u8; 12],
289 ) -> Result<Zeroizing<[u8; DEK_LEN]>> {
290 let cipher = Aes256Gcm::new_from_slice(&self.0[..])
291 .map_err(|e| MongrelError::Encryption(format!("kek aes init: {e}")))?;
292 let pt = Zeroizing::new(
293 cipher
294 .decrypt(Nonce::from_slice(wrap_nonce), wrapped)
295 .map_err(|e| MongrelError::Decryption(format!("dek unwrap: {e}")))?,
296 );
297 if pt.len() != DEK_LEN {
298 return Err(MongrelError::Decryption(format!(
299 "unwrapped dek is {} bytes, expected {DEK_LEN}",
300 pt.len()
301 )));
302 }
303 let mut dek = Zeroizing::new([0u8; DEK_LEN]);
304 dek.copy_from_slice(&pt[..]);
305 Ok(dek)
306 }
307 }
308
309 impl std::fmt::Debug for Kek {
310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311 f.write_str("Kek(**redacted**)")
312 }
313 }
314
315 pub const SCHEME_HMAC_EQ: u8 = 1;
317 pub const SCHEME_OPE_RANGE: u8 = 2;
318
319 impl Kek {
320 pub fn derive_subkey(&self, info: &[u8]) -> Zeroizing<[u8; DEK_LEN]> {
326 let hk = hkdf::Hkdf::<sha2::Sha256>::from_prk(&self.0[..])
327 .expect("KEK is 32 bytes >= HashLen");
328 let mut k = Zeroizing::new([0u8; DEK_LEN]);
329 hk.expand(info, k.as_mut())
330 .expect("32-byte output <= 255*HashLen");
331 k
332 }
333
334 pub fn derive_column_key(&self, column_id: u16) -> Zeroizing<[u8; DEK_LEN]> {
336 let mut info = b"mongreldb/colkey/".to_vec();
337 info.extend_from_slice(&column_id.to_be_bytes());
338 self.derive_subkey(&info)
339 }
340
341 pub fn wrap_column_key(
343 &self,
344 col_key: &[u8; DEK_LEN],
345 wrap_nonce: &[u8; 12],
346 ) -> Result<Vec<u8>> {
347 self.wrap_dek(col_key, wrap_nonce)
348 }
349 }
350
351 pub fn hmac_token(col_key: &[u8; DEK_LEN], msg: &[u8]) -> [u8; 32] {
355 use hmac::Mac;
356 let mut mac = <hmac::Hmac<sha2::Sha256> as Mac>::new_from_slice(col_key)
357 .expect("HMAC accepts any key size");
358 mac.update(msg);
359 mac.finalize().into_bytes().into()
360 }
361
362 pub fn ope_token_i64(col_key: &[u8; DEK_LEN], x: i64) -> [u8; 16] {
378 let m = (x as u64) ^ (1u64 << 63); monotone_ope(col_key, m)
380 }
381
382 pub fn ope_token_f64(col_key: &[u8; DEK_LEN], x: f64) -> [u8; 16] {
385 let bits = x.to_bits();
386 let m = if bits & (1u64 << 63) != 0 {
387 !bits
388 } else {
389 bits ^ (1u64 << 63)
390 };
391 monotone_ope(col_key, m)
392 }
393
394 const OPE_INFO: &[u8] = b"mongreldb/ope-blk/v2";
396
397 fn monotone_ope(col_key: &[u8; DEK_LEN], m: u64) -> [u8; 16] {
416 let mb = m.to_be_bytes();
417 let mut out = [0u8; 16];
418 for i in 0..8 {
419 let v = mb[i] as usize;
420 let mut o: u32 = v as u32;
421 if v > 0 {
422 let mut info = Vec::with_capacity(OPE_INFO.len() + 1 + i);
425 info.extend_from_slice(OPE_INFO);
426 info.push(i as u8);
427 info.extend_from_slice(&mb[..i]);
428 let hk = hkdf::Hkdf::<sha2::Sha256>::from_prk(&col_key[..])
429 .expect("col_key is 32 bytes >= HashLen");
430 let mut gaps = [0u8; 255];
431 hk.expand(&info, &mut gaps[..v])
432 .expect("v <= 255 <= 255*HashLen");
433 for &g in &gaps[..v] {
434 o += g as u32;
435 }
436 }
437 let chunk = o as u16; out[2 * i..2 * i + 2].copy_from_slice(&chunk.to_be_bytes());
439 }
440 out
441 }
442
443 pub fn encrypt_blob(dek: &[u8; DEK_LEN], plaintext: &[u8]) -> Result<Vec<u8>> {
448 let cipher = crate::encryption::AesCipher::new(&dek[..])?;
449 let mut nonce = [0u8; 12];
450 fill_random(&mut nonce)?;
451 let ct = cipher.encrypt_page(&nonce, plaintext)?;
452 let mut out = Vec::with_capacity(12 + ct.len());
453 out.extend_from_slice(&nonce);
454 out.extend_from_slice(&ct);
455 Ok(out)
456 }
457
458 pub fn decrypt_blob(dek: &[u8; DEK_LEN], bytes: &[u8]) -> Result<Vec<u8>> {
461 if bytes.len() < 12 + 16 {
462 return Err(MongrelError::Decryption("blob too short".into()));
463 }
464 let cipher = crate::encryption::AesCipher::new(&dek[..])?;
465 let nonce: [u8; 12] = bytes[..12].try_into().unwrap();
466 cipher.decrypt_page(&nonce, &bytes[12..])
467 }
468
469 pub fn run_metadata_mac(
477 mac_key: &[u8; DEK_LEN],
478 header: &[u8],
479 dir: &[u8],
480 descriptor: &[u8],
481 ) -> [u8; 32] {
482 use hmac::Mac;
483 let mut mac = <hmac::Hmac<sha2::Sha256> as Mac>::new_from_slice(mac_key)
484 .expect("HMAC accepts any key size");
485 mac.update(b"mongreldb/run-meta-mac/v1");
486 for part in [header, dir, descriptor] {
487 mac.update(&(part.len() as u64).to_le_bytes());
488 mac.update(part);
489 }
490 mac.finalize().into_bytes().into()
491 }
492
493 pub(super) fn wrap_nonce(nonce_prefix: [u8; 12], kind: u8, column_id: u16) -> [u8; 12] {
500 let mut n = nonce_prefix;
501 n[8] = kind;
502 n[9..11].copy_from_slice(&column_id.to_le_bytes());
503 n[11] = 0;
504 n
505 }
506
507 pub(super) const WRAP_KIND_DEK: u8 = 0;
509 pub(super) const WRAP_KIND_COLUMN: u8 = 1;
511
512 #[derive(Clone, Serialize, Deserialize)]
516 pub struct ColumnKeyDescriptor {
517 pub column_id: u16,
518 pub scheme: u8,
520 pub wrapped_column_key: Vec<u8>,
521 }
522
523 #[derive(Clone, Serialize, Deserialize)]
527 pub struct EncryptionDescriptor {
528 pub algo: u8,
530 pub nonce_prefix: [u8; 12],
534 pub wrapped_dek: Vec<u8>,
536 pub column_descriptors: Vec<ColumnKeyDescriptor>,
538 }
539
540 pub fn generate_dek() -> Result<Zeroizing<[u8; DEK_LEN]>> {
542 let mut k = Zeroizing::new([0u8; DEK_LEN]);
543 fill_random(k.as_mut())?;
544 Ok(k)
545 }
546
547 pub fn random_salt() -> Result<[u8; SALT_LEN]> {
549 let mut s = [0u8; SALT_LEN];
550 fill_random(&mut s)?;
551 Ok(s)
552 }
553
554 pub fn random_nonce_prefix() -> Result<[u8; 12]> {
557 let mut n = [0u8; 12];
558 fill_random(&mut n[..8])?;
559 Ok(n)
560 }
561
562 pub fn build_page_nonce(nonce_prefix: [u8; 12], column_id: u16, page_seq: u32) -> [u8; 12] {
567 let mut n = nonce_prefix;
568 n[8..10].copy_from_slice(&column_id.to_le_bytes());
569 n[10..12].copy_from_slice(&(page_seq as u16).to_le_bytes());
572 n
573 }
574
575 pub fn setup_run_encryption(
581 kek: &Kek,
582 indexable_columns: &[(u16, u8)],
583 ) -> Result<crate::encryption::RunEncryption> {
584 let dek = generate_dek()?;
585 let nonce_prefix = random_nonce_prefix()?;
586 let cipher: Box<dyn Cipher> = Box::new(crate::encryption::AesCipher::new(&dek[..])?);
587 let dek_nonce = wrap_nonce(nonce_prefix, WRAP_KIND_DEK, 0);
588 let wrapped = kek.wrap_dek(&dek, &dek_nonce)?;
589 let mut column_descriptors = Vec::with_capacity(indexable_columns.len());
590 for &(column_id, scheme) in indexable_columns {
591 let col_key = kek.derive_column_key(column_id);
592 let col_nonce = wrap_nonce(nonce_prefix, WRAP_KIND_COLUMN, column_id);
593 let wrapped_col = kek.wrap_column_key(&col_key, &col_nonce)?;
594 column_descriptors.push(ColumnKeyDescriptor {
595 column_id,
596 scheme,
597 wrapped_column_key: wrapped_col,
598 });
599 }
600 let desc = EncryptionDescriptor {
601 algo: ALGO_AES_GCM,
602 nonce_prefix,
603 wrapped_dek: wrapped,
604 column_descriptors,
605 };
606 let descriptor_bytes = bincode::serialize(&desc)?;
607 Ok(crate::encryption::RunEncryption {
608 cipher,
609 nonce_prefix,
610 descriptor_bytes,
611 mac_key: Some(*kek.derive_run_mac_key()),
612 })
613 }
614
615 pub fn build_run_cipher(
618 kek: &Kek,
619 descriptor_bytes: &[u8],
620 ) -> Result<crate::encryption::RunEncryption> {
621 let desc: EncryptionDescriptor = bincode::deserialize(descriptor_bytes)
622 .map_err(|e| MongrelError::Decryption(format!("bad encryption descriptor: {e}")))?;
623 if desc.algo != ALGO_AES_GCM {
624 return Err(MongrelError::Decryption(format!(
625 "unsupported encryption algo {}",
626 desc.algo
627 )));
628 }
629 let dek_nonce = wrap_nonce(desc.nonce_prefix, WRAP_KIND_DEK, 0);
630 let dek = kek.unwrap_dek(&desc.wrapped_dek, &dek_nonce)?;
631 let cipher: Box<dyn Cipher> = Box::new(crate::encryption::AesCipher::new(&dek[..])?);
632 Ok(crate::encryption::RunEncryption {
633 cipher,
634 nonce_prefix: desc.nonce_prefix,
635 descriptor_bytes: Vec::new(),
636 mac_key: None,
637 })
638 }
639}
640
641#[cfg(feature = "encryption")]
642pub use key::{
643 build_page_nonce, build_run_cipher, decrypt_blob, encrypt_blob, generate_dek, hmac_token,
644 ope_token_f64, ope_token_i64, random_nonce_prefix, random_salt, run_metadata_mac,
645 setup_run_encryption, ColumnKeyDescriptor, EncryptionDescriptor, Kek, ALGO_AES_GCM, SALT_LEN,
646 SCHEME_HMAC_EQ, SCHEME_OPE_RANGE,
647};
648
649pub struct RunEncryption {
655 pub cipher: Box<dyn Cipher>,
656 pub nonce_prefix: [u8; 12],
657 pub descriptor_bytes: Vec<u8>,
658 pub mac_key: Option<[u8; 32]>,
661}
662
663#[cfg(not(feature = "encryption"))]
668pub struct Kek {
669 _private: (),
670}
671
672#[cfg(not(feature = "encryption"))]
675#[doc(hidden)]
676pub fn setup_run_encryption(
677 _kek: &Kek,
678 _indexable_columns: &[(u16, u8)],
679) -> crate::Result<RunEncryption> {
680 unreachable!("Kek is unconstructable without the encryption feature")
681}
682
683#[cfg(not(feature = "encryption"))]
685#[doc(hidden)]
686pub fn build_run_cipher(_kek: &Kek, _descriptor_bytes: &[u8]) -> crate::Result<RunEncryption> {
687 unreachable!("Kek is unconstructable without the encryption feature")
688}
689
690#[cfg(feature = "encryption")]
693pub fn meta_dek_for(kek: Option<&Kek>) -> Option<[u8; DEK_LEN]> {
694 kek.map(|k| *k.derive_meta_key())
695}
696
697#[cfg(not(feature = "encryption"))]
698pub fn meta_dek_for(_kek: Option<&Kek>) -> Option<[u8; DEK_LEN]> {
699 None
700}
701
702#[cfg(feature = "encryption")]
705pub fn wal_dek_for(kek: Option<&Kek>) -> Option<Zeroizing<[u8; DEK_LEN]>> {
706 kek.map(|k| k.derive_shared_wal_key())
707}
708
709#[cfg(not(feature = "encryption"))]
710pub fn wal_dek_for(_kek: Option<&Kek>) -> Option<zeroize::Zeroizing<[u8; DEK_LEN]>> {
711 None
712}
713
714#[cfg(test)]
715mod tests {
716 use super::*;
717
718 #[test]
719 fn plaintext_is_identity() {
720 let c = PlaintextCipher;
721 let ct = c.encrypt_page(&[0; 12], b"hello").unwrap();
722 assert_eq!(ct, b"hello");
723 let pt = c.decrypt_page(&[0; 12], &ct).unwrap();
724 assert_eq!(pt, b"hello");
725 }
726
727 #[cfg(feature = "encryption")]
728 #[test]
729 fn aes_round_trip() {
730 let c = AesCipher::new(&[7u8; 32]).unwrap();
731 let nonce = [1u8; 12];
732 let ct = c.encrypt_page(&nonce, b"secret page").unwrap();
733 assert_ne!(ct, b"secret page");
734 let pt = c.decrypt_page(&nonce, &ct).unwrap();
735 assert_eq!(pt, b"secret page");
736 }
737
738 #[cfg(feature = "encryption")]
739 #[test]
740 fn kek_derive_is_deterministic_for_same_passphrase_and_salt() {
741 let salt = random_salt().unwrap();
742 let k1 = Kek::derive("correct horse battery staple", &salt).unwrap();
743 let k2 = Kek::derive("correct horse battery staple", &salt).unwrap();
744 let dek = generate_dek().unwrap();
745 let np = random_nonce_prefix().unwrap();
746 let w1 = k1.wrap_dek(&dek, &np).unwrap();
747 let w2 = k2.wrap_dek(&dek, &np).unwrap();
748 assert_eq!(w1, w2, "same passphrase+salt must yield same KEK");
749 }
750
751 #[cfg(feature = "encryption")]
752 #[test]
753 fn kek_differs_for_different_salt() {
754 let s1 = random_salt().unwrap();
755 let s2 = random_salt().unwrap();
756 let k1 = Kek::derive("passphrase", &s1).unwrap();
757 let k2 = Kek::derive("passphrase", &s2).unwrap();
758 let dek = generate_dek().unwrap();
759 let np = random_nonce_prefix().unwrap();
760 let w1 = k1.wrap_dek(&dek, &np).unwrap();
761 let w2 = k2.wrap_dek(&dek, &np).unwrap();
762 assert_ne!(w1, w2, "different salts must yield different KEKs");
763 }
764
765 #[cfg(feature = "encryption")]
766 #[test]
767 fn dek_wrap_unwrap_round_trip() {
768 let salt = random_salt().unwrap();
769 let kek = Kek::derive("hunter2", &salt).unwrap();
770 let dek = generate_dek().unwrap();
771 let np = random_nonce_prefix().unwrap();
772 let wrapped = kek.wrap_dek(&dek, &np).unwrap();
773 assert_eq!(wrapped.len(), DEK_LEN + 16);
774 let unwrapped = kek.unwrap_dek(&wrapped, &np).unwrap();
775 assert_eq!(unwrapped.as_ref(), dek.as_ref());
776 }
777
778 #[cfg(feature = "encryption")]
779 #[test]
780 fn unwrap_rejects_wrong_passphrase() {
781 let salt = random_salt().unwrap();
782 let enc_kek = Kek::derive("right-pass", &salt).unwrap();
783 let dec_kek = Kek::derive("wrong-pass", &salt).unwrap();
784 let dek = generate_dek().unwrap();
785 let np = random_nonce_prefix().unwrap();
786 let wrapped = enc_kek.wrap_dek(&dek, &np).unwrap();
787 assert!(dec_kek.unwrap_dek(&wrapped, &np).is_err());
788 }
789
790 #[cfg(feature = "encryption")]
791 #[test]
792 fn page_nonce_overlays_column_and_page() {
793 let np = random_nonce_prefix().unwrap();
794 let n = build_page_nonce(np, 0x0304, 0x0506);
795 assert_eq!(&n[..8], &np[..8]);
797 assert_eq!(n[8..10], [0x04, 0x03]);
798 assert_eq!(n[10..12], [0x06, 0x05]);
799 }
800
801 #[cfg(feature = "encryption")]
802 #[test]
803 fn page_nonce_unique_per_column_and_page() {
804 let np = random_nonce_prefix().unwrap();
805 let a = build_page_nonce(np, 1, 0);
806 let b = build_page_nonce(np, 1, 1);
807 let c = build_page_nonce(np, 2, 0);
808 assert_ne!(a, b);
809 assert_ne!(a, c);
810 assert_ne!(b, c);
811 }
812
813 #[cfg(feature = "encryption")]
814 #[test]
815 fn column_key_is_deterministic_from_kek() {
816 let salt = random_salt().unwrap();
817 let k1 = Kek::derive("pass", &salt).unwrap();
818 let k2 = Kek::derive("pass", &salt).unwrap();
819 let c1 = k1.derive_column_key(7);
820 let c2 = k2.derive_column_key(7);
821 assert_eq!(c1.as_ref(), c2.as_ref(), "same KEK + column => same key");
822 let c3 = k1.derive_column_key(8);
824 assert_ne!(c1.as_ref(), c3.as_ref());
825 }
826
827 #[cfg(feature = "encryption")]
828 #[test]
829 fn hmac_token_collides_only_for_equal_values() {
830 let salt = random_salt().unwrap();
831 let k = Kek::derive("pass", &salt).unwrap();
832 let ck = k.derive_column_key(1);
833 let a = hmac_token(&ck, b"hello");
834 let b = hmac_token(&ck, b"hello");
835 let c = hmac_token(&ck, b"world");
836 assert_eq!(a, b, "equal plaintexts => equal tokens");
837 assert_ne!(a, c, "unequal plaintexts => distinct tokens");
838 let ck2 = k.derive_column_key(2);
840 assert_ne!(a, hmac_token(&ck2, b"hello"));
841 }
842
843 #[cfg(feature = "encryption")]
844 #[test]
845 fn ope_token_i64_preserves_order() {
846 let salt = random_salt().unwrap();
847 let k = Kek::derive("pass", &salt).unwrap();
848 let ck = k.derive_column_key(3);
849 let vals = [i64::MIN, -1_000_000, -1, 0, 1, 42, 1_000_000, i64::MAX];
850 let tokens: Vec<_> = vals.iter().map(|&x| ope_token_i64(&ck, x)).collect();
851 for w in tokens.windows(2) {
853 assert!(w[0] < w[1], "OPE must preserve order");
854 }
855 assert_eq!(ope_token_i64(&ck, 0), ope_token_i64(&ck, 0));
857 }
858
859 #[cfg(feature = "encryption")]
864 #[test]
865 fn ope_token_is_non_linear() {
866 let salt = random_salt().unwrap();
867 let k = Kek::derive("pass", &salt).unwrap();
868 let ck = k.derive_column_key(9);
869 let t = |x: i64| u128::from_be_bytes(ope_token_i64(&ck, x));
870 let g1 = t(101).wrapping_sub(t(100));
872 let g2 = t(102).wrapping_sub(t(101));
873 let g3 = t(103).wrapping_sub(t(102));
874 assert!(
875 !(g1 == g2 && g2 == g3),
876 "constant token gaps => OPE is still affine/linear"
877 );
878 assert!(t(100) < t(101) && t(101) < t(102) && t(102) < t(103));
880 assert_eq!(ope_token_i64(&ck, 100), ope_token_i64(&ck, 100));
881 }
882
883 #[cfg(feature = "encryption")]
884 #[test]
885 fn ope_token_f64_preserves_order() {
886 let salt = random_salt().unwrap();
887 let k = Kek::derive("pass", &salt).unwrap();
888 let ck = k.derive_column_key(4);
889 let vals = [
890 f64::NEG_INFINITY,
891 -1.5,
892 0.0,
893 std::f64::consts::PI,
894 1e9,
895 f64::INFINITY,
896 ];
897 let tokens: Vec<_> = vals.iter().map(|&x| ope_token_f64(&ck, x)).collect();
898 for w in tokens.windows(2) {
899 assert!(w[0] < w[1], "OPE over f64 must preserve total order");
900 }
901 assert!(ope_token_f64(&ck, -1.0) < ope_token_f64(&ck, 1.0));
903 }
904
905 #[cfg(feature = "encryption")]
909 #[test]
910 fn wrap_nonces_are_distinct_within_a_run() {
911 use super::key::{wrap_nonce, WRAP_KIND_COLUMN, WRAP_KIND_DEK};
912 let salt = random_salt().unwrap();
913 let kek = Kek::derive("pass", &salt).unwrap();
914 let np = random_nonce_prefix().unwrap();
915
916 let dek_n = wrap_nonce(np, WRAP_KIND_DEK, 0);
918 let col1 = wrap_nonce(np, WRAP_KIND_COLUMN, 1);
919 let col2 = wrap_nonce(np, WRAP_KIND_COLUMN, 2);
920 assert_ne!(dek_n, col1);
921 assert_ne!(dek_n, col2);
922 assert_ne!(col1, col2);
923
924 let k = generate_dek().unwrap();
927 let w_dek = kek.wrap_dek(&k, &dek_n).unwrap();
928 let w_col = kek.wrap_column_key(&k, &col1).unwrap();
929 assert_ne!(
930 w_dek, w_col,
931 "DEK and column-key wraps must not share a nonce"
932 );
933
934 let enc =
936 setup_run_encryption(&kek, &[(1, SCHEME_HMAC_EQ), (2, SCHEME_OPE_RANGE)]).unwrap();
937 let built = build_run_cipher(&kek, &enc.descriptor_bytes).unwrap();
938 assert_eq!(built.nonce_prefix, enc.nonce_prefix);
939 }
940
941 #[cfg(feature = "encryption")]
946 #[test]
947 fn wal_deks_are_domain_separated() {
948 let salt = random_salt().unwrap();
949 let k = Kek::derive("pass", &salt).unwrap();
950 let shared = k.derive_shared_wal_key();
951 let tbl1 = k.derive_table_wal_key(1);
952 let tbl2 = k.derive_table_wal_key(2);
953 let legacy = k.derive_wal_key();
954 assert_ne!(shared.as_ref(), tbl1.as_ref(), "shared != table1");
955 assert_ne!(shared.as_ref(), tbl2.as_ref(), "shared != table2");
956 assert_ne!(shared.as_ref(), legacy.as_ref(), "shared != legacy");
957 assert_ne!(tbl1.as_ref(), tbl2.as_ref(), "table1 != table2");
958 assert_ne!(tbl1.as_ref(), legacy.as_ref(), "table1 != legacy");
959 }
960}