1use crate::error::{MongrelError, Result};
14use zeroize::Zeroizing;
15
16pub const DEK_LEN: usize = 32;
18
19pub fn fill_random(buf: &mut [u8]) -> Result<()> {
21 getrandom::getrandom(buf).map_err(|error| MongrelError::EntropyUnavailable(error.to_string()))
22}
23
24pub trait Cipher: Send + Sync {
26 fn encrypt_page(&self, nonce: &[u8; 12], plaintext: &[u8]) -> Result<Vec<u8>>;
28
29 fn decrypt_page(&self, nonce: &[u8; 12], ciphertext: &[u8]) -> Result<Vec<u8>>;
31
32 fn encrypt_page_with_aad(
33 &self,
34 nonce: &[u8; 12],
35 plaintext: &[u8],
36 aad: &[u8],
37 ) -> Result<Vec<u8>> {
38 if aad.is_empty() {
39 self.encrypt_page(nonce, plaintext)
40 } else {
41 Err(MongrelError::Encryption(
42 "cipher does not support associated data".into(),
43 ))
44 }
45 }
46
47 fn decrypt_page_with_aad(
48 &self,
49 nonce: &[u8; 12],
50 ciphertext: &[u8],
51 aad: &[u8],
52 ) -> Result<Vec<u8>> {
53 if aad.is_empty() {
54 self.decrypt_page(nonce, ciphertext)
55 } else {
56 Err(MongrelError::Decryption(
57 "cipher does not support associated data".into(),
58 ))
59 }
60 }
61}
62
63#[derive(Debug, Default, Clone, Copy)]
65pub struct PlaintextCipher;
66
67impl Cipher for PlaintextCipher {
68 fn encrypt_page(&self, _nonce: &[u8; 12], plaintext: &[u8]) -> Result<Vec<u8>> {
69 Ok(plaintext.to_vec())
70 }
71
72 fn decrypt_page(&self, _nonce: &[u8; 12], ciphertext: &[u8]) -> Result<Vec<u8>> {
73 Ok(ciphertext.to_vec())
74 }
75}
76
77mod aes {
78 use super::{Cipher, MongrelError, Result};
79 use aes_gcm::aead::{Aead, Payload};
80 use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
81
82 pub struct AesCipher {
85 cipher: Aes256Gcm,
86 }
87
88 impl AesCipher {
89 pub fn new(key: &[u8]) -> Result<Self> {
91 if key.len() != 32 {
92 return Err(MongrelError::InvalidArgument(format!(
93 "aes-256 key must be 32 bytes, got {}",
94 key.len()
95 )));
96 }
97 Ok(Self {
98 cipher: Aes256Gcm::new_from_slice(key)
99 .map_err(|e| MongrelError::Encryption(format!("aes key init: {e}")))?,
100 })
101 }
102 }
103
104 impl Cipher for AesCipher {
105 fn encrypt_page(&self, nonce: &[u8; 12], plaintext: &[u8]) -> Result<Vec<u8>> {
106 let nonce = Nonce::from_slice(nonce);
107 self.cipher
108 .encrypt(nonce, plaintext)
109 .map_err(|e| MongrelError::Encryption(format!("aes encrypt: {e}")))
110 }
111
112 fn decrypt_page(&self, nonce: &[u8; 12], ciphertext: &[u8]) -> Result<Vec<u8>> {
113 let nonce = Nonce::from_slice(nonce);
114 self.cipher
115 .decrypt(nonce, ciphertext)
116 .map_err(|e| MongrelError::Decryption(format!("aes decrypt: {e}")))
117 }
118
119 fn encrypt_page_with_aad(
120 &self,
121 nonce: &[u8; 12],
122 plaintext: &[u8],
123 aad: &[u8],
124 ) -> Result<Vec<u8>> {
125 self.cipher
126 .encrypt(
127 Nonce::from_slice(nonce),
128 Payload {
129 msg: plaintext,
130 aad,
131 },
132 )
133 .map_err(|e| MongrelError::Encryption(format!("aes encrypt: {e}")))
134 }
135
136 fn decrypt_page_with_aad(
137 &self,
138 nonce: &[u8; 12],
139 ciphertext: &[u8],
140 aad: &[u8],
141 ) -> Result<Vec<u8>> {
142 self.cipher
143 .decrypt(
144 Nonce::from_slice(nonce),
145 Payload {
146 msg: ciphertext,
147 aad,
148 },
149 )
150 .map_err(|e| MongrelError::Decryption(format!("aes decrypt: {e}")))
151 }
152 }
153}
154
155pub use aes::AesCipher;
156
157mod key {
158 use super::{fill_random, Cipher, MongrelError, Result, DEK_LEN};
159 use aes_gcm::aead::{Aead, KeyInit};
160 use aes_gcm::{Aes256Gcm, Nonce};
161 use serde::{Deserialize, Serialize};
162 use zeroize::Zeroizing;
163
164 pub const SALT_LEN: usize = 16;
166 pub const ALGO_AES_GCM: u8 = 1;
168 const KEK_INFO: &[u8] = b"mongreldb/kek/v1";
170 const KEK_RAW_INFO: &[u8] = b"mongreldb/kek-raw/v1";
172 const ARGON2_M_COST: u32 = 19_456;
174 const ARGON2_T_COST: u32 = 2;
176 const ARGON2_P_COST: u32 = 1;
178
179 pub struct Kek(Zeroizing<[u8; DEK_LEN]>);
184
185 impl Kek {
186 pub fn derive(passphrase: &str, salt: &[u8; SALT_LEN]) -> Result<Self> {
189 let params =
190 argon2::Params::new(ARGON2_M_COST, ARGON2_T_COST, ARGON2_P_COST, Some(DEK_LEN))
191 .map_err(|e| MongrelError::Encryption(format!("argon2 params: {e}")))?;
192 let argon =
193 argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params);
194 let mut prk = Zeroizing::new([0u8; DEK_LEN]);
196 argon
197 .hash_password_into(passphrase.as_bytes(), salt, prk.as_mut())
198 .map_err(|e| MongrelError::Encryption(format!("argon2 derive: {e}")))?;
199 let hk = hkdf::Hkdf::<sha2::Sha256>::from_prk(prk.as_ref())
201 .map_err(|e| MongrelError::Encryption(format!("hkdf from_prk: {e}")))?;
202 let mut kek = Zeroizing::new([0u8; DEK_LEN]);
203 hk.expand(KEK_INFO, kek.as_mut())
204 .map_err(|e| MongrelError::Encryption(format!("hkdf expand: {e}")))?;
205 Ok(Kek(kek))
206 }
207
208 pub fn from_raw_key(raw: &[u8], salt: &[u8; SALT_LEN]) -> Result<Self> {
213 if raw.len() < DEK_LEN {
214 return Err(MongrelError::InvalidArgument(format!(
215 "raw key must be >= {DEK_LEN} bytes, got {}",
216 raw.len()
217 )));
218 }
219 let hk = hkdf::Hkdf::<sha2::Sha256>::new(Some(salt), raw);
221 let mut kek = Zeroizing::new([0u8; DEK_LEN]);
222 hk.expand(KEK_RAW_INFO, kek.as_mut())
223 .map_err(|e| MongrelError::Encryption(format!("hkdf expand: {e}")))?;
224 Ok(Kek(kek))
225 }
226
227 pub fn derive_wal_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
229 self.derive_subkey(b"mongreldb/wal/v1")
230 }
231
232 pub fn derive_shared_wal_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
236 self.derive_subkey(b"mongreldb/swal/v1")
237 }
238
239 pub fn derive_table_wal_key(&self, table_id: u64) -> Zeroizing<[u8; DEK_LEN]> {
242 let mut info = b"mongreldb/twal/".to_vec();
243 info.extend_from_slice(&table_id.to_be_bytes());
244 info.extend_from_slice(b"/v1");
245 self.derive_subkey(&info)
246 }
247
248 pub fn derive_cache_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
250 self.derive_subkey(b"mongreldb/rcache/v1")
251 }
252
253 pub fn derive_idx_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
255 self.derive_subkey(b"mongreldb/idx/v1")
256 }
257
258 pub fn derive_run_mac_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
260 self.derive_subkey(b"mongreldb/run-mac/v1")
261 }
262
263 pub fn derive_meta_key(&self) -> Zeroizing<[u8; DEK_LEN]> {
266 self.derive_subkey(b"mongreldb/meta/v1")
267 }
268
269 pub fn wrap_dek(&self, dek: &[u8; DEK_LEN], wrap_nonce: &[u8; 12]) -> Result<Vec<u8>> {
273 let cipher = Aes256Gcm::new_from_slice(&self.0[..])
274 .map_err(|e| MongrelError::Encryption(format!("kek aes init: {e}")))?;
275 cipher
276 .encrypt(Nonce::from_slice(wrap_nonce), dek.as_slice())
277 .map_err(|e| MongrelError::Encryption(format!("dek wrap: {e}")))
278 }
279
280 pub fn unwrap_dek(
282 &self,
283 wrapped: &[u8],
284 wrap_nonce: &[u8; 12],
285 ) -> Result<Zeroizing<[u8; DEK_LEN]>> {
286 let cipher = Aes256Gcm::new_from_slice(&self.0[..])
287 .map_err(|e| MongrelError::Encryption(format!("kek aes init: {e}")))?;
288 let pt = Zeroizing::new(
289 cipher
290 .decrypt(Nonce::from_slice(wrap_nonce), wrapped)
291 .map_err(|e| MongrelError::Decryption(format!("dek unwrap: {e}")))?,
292 );
293 if pt.len() != DEK_LEN {
294 return Err(MongrelError::Decryption(format!(
295 "unwrapped dek is {} bytes, expected {DEK_LEN}",
296 pt.len()
297 )));
298 }
299 let mut dek = Zeroizing::new([0u8; DEK_LEN]);
300 dek.copy_from_slice(&pt[..]);
301 Ok(dek)
302 }
303 }
304
305 impl std::fmt::Debug for Kek {
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307 f.write_str("Kek(**redacted**)")
308 }
309 }
310
311 pub const SCHEME_HMAC_EQ: u8 = 1;
313 pub const SCHEME_OPE_RANGE: u8 = 2;
314
315 impl Kek {
316 pub fn derive_subkey(&self, info: &[u8]) -> Zeroizing<[u8; DEK_LEN]> {
322 let hk = hkdf::Hkdf::<sha2::Sha256>::from_prk(&self.0[..])
323 .expect("KEK is 32 bytes >= HashLen");
324 let mut k = Zeroizing::new([0u8; DEK_LEN]);
325 hk.expand(info, k.as_mut())
326 .expect("32-byte output <= 255*HashLen");
327 k
328 }
329
330 pub fn derive_column_key(&self, column_id: u16) -> Zeroizing<[u8; DEK_LEN]> {
332 let mut info = b"mongreldb/colkey/".to_vec();
333 info.extend_from_slice(&column_id.to_be_bytes());
334 self.derive_subkey(&info)
335 }
336
337 pub fn wrap_column_key(
339 &self,
340 col_key: &[u8; DEK_LEN],
341 wrap_nonce: &[u8; 12],
342 ) -> Result<Vec<u8>> {
343 self.wrap_dek(col_key, wrap_nonce)
344 }
345 }
346
347 pub fn hmac_token(col_key: &[u8; DEK_LEN], msg: &[u8]) -> [u8; 32] {
351 use hmac::Mac;
352 let mut mac = <hmac::Hmac<sha2::Sha256> as Mac>::new_from_slice(col_key)
353 .expect("HMAC accepts any key size");
354 mac.update(msg);
355 mac.finalize().into_bytes().into()
356 }
357
358 pub fn ope_token_i64(col_key: &[u8; DEK_LEN], x: i64) -> [u8; 16] {
374 let m = (x as u64) ^ (1u64 << 63); monotone_ope(col_key, m)
376 }
377
378 pub fn ope_token_f64(col_key: &[u8; DEK_LEN], x: f64) -> [u8; 16] {
381 let bits = x.to_bits();
382 let m = if bits & (1u64 << 63) != 0 {
383 !bits
384 } else {
385 bits ^ (1u64 << 63)
386 };
387 monotone_ope(col_key, m)
388 }
389
390 const OPE_INFO: &[u8] = b"mongreldb/ope-blk/v2";
392
393 fn monotone_ope(col_key: &[u8; DEK_LEN], m: u64) -> [u8; 16] {
412 let mb = m.to_be_bytes();
413 let mut out = [0u8; 16];
414 for i in 0..8 {
415 let v = mb[i] as usize;
416 let mut o: u32 = v as u32;
417 if v > 0 {
418 let mut info = Vec::with_capacity(OPE_INFO.len() + 1 + i);
421 info.extend_from_slice(OPE_INFO);
422 info.push(i as u8);
423 info.extend_from_slice(&mb[..i]);
424 let hk = hkdf::Hkdf::<sha2::Sha256>::from_prk(&col_key[..])
425 .expect("col_key is 32 bytes >= HashLen");
426 let mut gaps = [0u8; 255];
427 hk.expand(&info, &mut gaps[..v])
428 .expect("v <= 255 <= 255*HashLen");
429 for &g in &gaps[..v] {
430 o += g as u32;
431 }
432 }
433 let chunk = o as u16; out[2 * i..2 * i + 2].copy_from_slice(&chunk.to_be_bytes());
435 }
436 out
437 }
438
439 pub fn encrypt_blob(dek: &[u8; DEK_LEN], plaintext: &[u8]) -> Result<Vec<u8>> {
444 let cipher = crate::encryption::AesCipher::new(&dek[..])?;
445 let mut nonce = [0u8; 12];
446 fill_random(&mut nonce)?;
447 let ct = cipher.encrypt_page(&nonce, plaintext)?;
448 let mut out = Vec::with_capacity(12 + ct.len());
449 out.extend_from_slice(&nonce);
450 out.extend_from_slice(&ct);
451 Ok(out)
452 }
453
454 pub fn decrypt_blob(dek: &[u8; DEK_LEN], bytes: &[u8]) -> Result<Vec<u8>> {
457 if bytes.len() < 12 + 16 {
458 return Err(MongrelError::Decryption("blob too short".into()));
459 }
460 let cipher = crate::encryption::AesCipher::new(&dek[..])?;
461 let nonce: [u8; 12] = bytes[..12].try_into().unwrap();
462 cipher.decrypt_page(&nonce, &bytes[12..])
463 }
464
465 pub fn run_metadata_mac(
473 mac_key: &[u8; DEK_LEN],
474 header: &[u8],
475 dir: &[u8],
476 descriptor: &[u8],
477 ) -> [u8; 32] {
478 use hmac::Mac;
479 let mut mac = <hmac::Hmac<sha2::Sha256> as Mac>::new_from_slice(mac_key)
480 .expect("HMAC accepts any key size");
481 mac.update(b"mongreldb/run-meta-mac/v1");
482 for part in [header, dir, descriptor] {
483 mac.update(&(part.len() as u64).to_le_bytes());
484 mac.update(part);
485 }
486 mac.finalize().into_bytes().into()
487 }
488
489 pub(super) fn wrap_nonce(nonce_prefix: [u8; 12], kind: u8, column_id: u16) -> [u8; 12] {
496 let mut n = nonce_prefix;
497 n[8] = kind;
498 n[9..11].copy_from_slice(&column_id.to_le_bytes());
499 n[11] = 0;
500 n
501 }
502
503 pub(super) const WRAP_KIND_DEK: u8 = 0;
505 pub(super) const WRAP_KIND_COLUMN: u8 = 1;
507
508 #[derive(Clone, Serialize, Deserialize)]
512 pub struct ColumnKeyDescriptor {
513 pub column_id: u16,
514 pub scheme: u8,
516 pub wrapped_column_key: Vec<u8>,
517 }
518
519 #[derive(Clone, Serialize, Deserialize)]
523 pub struct EncryptionDescriptor {
524 pub algo: u8,
526 pub nonce_prefix: [u8; 12],
530 pub wrapped_dek: Vec<u8>,
532 pub column_descriptors: Vec<ColumnKeyDescriptor>,
534 }
535
536 pub fn generate_dek() -> Result<Zeroizing<[u8; DEK_LEN]>> {
538 let mut k = Zeroizing::new([0u8; DEK_LEN]);
539 fill_random(k.as_mut())?;
540 Ok(k)
541 }
542
543 pub fn random_salt() -> Result<[u8; SALT_LEN]> {
545 let mut s = [0u8; SALT_LEN];
546 fill_random(&mut s)?;
547 Ok(s)
548 }
549
550 pub fn random_nonce_prefix() -> Result<[u8; 12]> {
553 let mut n = [0u8; 12];
554 fill_random(&mut n[..8])?;
555 Ok(n)
556 }
557
558 pub fn build_page_nonce(nonce_prefix: [u8; 12], column_id: u16, page_seq: u32) -> [u8; 12] {
563 let mut n = nonce_prefix;
564 n[8..10].copy_from_slice(&column_id.to_le_bytes());
565 n[10..12].copy_from_slice(&(page_seq as u16).to_le_bytes());
568 n
569 }
570
571 pub fn setup_run_encryption(
577 kek: &Kek,
578 indexable_columns: &[(u16, u8)],
579 ) -> Result<crate::encryption::RunEncryption> {
580 let dek = generate_dek()?;
581 let nonce_prefix = random_nonce_prefix()?;
582 let cipher: Box<dyn Cipher> = Box::new(crate::encryption::AesCipher::new(&dek[..])?);
583 let dek_nonce = wrap_nonce(nonce_prefix, WRAP_KIND_DEK, 0);
584 let wrapped = kek.wrap_dek(&dek, &dek_nonce)?;
585 let mut column_descriptors = Vec::with_capacity(indexable_columns.len());
586 for &(column_id, scheme) in indexable_columns {
587 let col_key = kek.derive_column_key(column_id);
588 let col_nonce = wrap_nonce(nonce_prefix, WRAP_KIND_COLUMN, column_id);
589 let wrapped_col = kek.wrap_column_key(&col_key, &col_nonce)?;
590 column_descriptors.push(ColumnKeyDescriptor {
591 column_id,
592 scheme,
593 wrapped_column_key: wrapped_col,
594 });
595 }
596 let desc = EncryptionDescriptor {
597 algo: ALGO_AES_GCM,
598 nonce_prefix,
599 wrapped_dek: wrapped,
600 column_descriptors,
601 };
602 let descriptor_bytes = bincode::serialize(&desc)?;
603 Ok(crate::encryption::RunEncryption {
604 cipher,
605 nonce_prefix,
606 descriptor_bytes,
607 mac_key: Some(*kek.derive_run_mac_key()),
608 })
609 }
610
611 pub fn build_run_cipher(
614 kek: &Kek,
615 descriptor_bytes: &[u8],
616 ) -> Result<crate::encryption::RunEncryption> {
617 let desc: EncryptionDescriptor = bincode::deserialize(descriptor_bytes)
618 .map_err(|e| MongrelError::Decryption(format!("bad encryption descriptor: {e}")))?;
619 if desc.algo != ALGO_AES_GCM {
620 return Err(MongrelError::Decryption(format!(
621 "unsupported encryption algo {}",
622 desc.algo
623 )));
624 }
625 let dek_nonce = wrap_nonce(desc.nonce_prefix, WRAP_KIND_DEK, 0);
626 let dek = kek.unwrap_dek(&desc.wrapped_dek, &dek_nonce)?;
627 let cipher: Box<dyn Cipher> = Box::new(crate::encryption::AesCipher::new(&dek[..])?);
628 Ok(crate::encryption::RunEncryption {
629 cipher,
630 nonce_prefix: desc.nonce_prefix,
631 descriptor_bytes: Vec::new(),
632 mac_key: None,
633 })
634 }
635}
636
637pub use key::{
638 build_page_nonce, build_run_cipher, decrypt_blob, encrypt_blob, generate_dek, hmac_token,
639 ope_token_f64, ope_token_i64, random_nonce_prefix, random_salt, run_metadata_mac,
640 setup_run_encryption, ColumnKeyDescriptor, EncryptionDescriptor, Kek, ALGO_AES_GCM, SALT_LEN,
641 SCHEME_HMAC_EQ, SCHEME_OPE_RANGE,
642};
643
644pub struct RunEncryption {
648 pub cipher: Box<dyn Cipher>,
649 pub nonce_prefix: [u8; 12],
650 pub descriptor_bytes: Vec<u8>,
651 pub mac_key: Option<[u8; 32]>,
654}
655
656pub fn meta_dek_for(kek: Option<&Kek>) -> Option<[u8; DEK_LEN]> {
659 kek.map(|k| *k.derive_meta_key())
660}
661
662pub fn wal_dek_for(kek: Option<&Kek>) -> Option<Zeroizing<[u8; DEK_LEN]>> {
665 kek.map(|k| k.derive_shared_wal_key())
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[test]
673 fn plaintext_is_identity() {
674 let c = PlaintextCipher;
675 let ct = c.encrypt_page(&[0; 12], b"hello").unwrap();
676 assert_eq!(ct, b"hello");
677 let pt = c.decrypt_page(&[0; 12], &ct).unwrap();
678 assert_eq!(pt, b"hello");
679 }
680
681 #[test]
682 fn aes_round_trip() {
683 let c = AesCipher::new(&[7u8; 32]).unwrap();
684 let nonce = [1u8; 12];
685 let ct = c.encrypt_page(&nonce, b"secret page").unwrap();
686 assert_ne!(ct, b"secret page");
687 let pt = c.decrypt_page(&nonce, &ct).unwrap();
688 assert_eq!(pt, b"secret page");
689 }
690
691 #[test]
692 fn kek_derive_is_deterministic_for_same_passphrase_and_salt() {
693 let salt = random_salt().unwrap();
694 let k1 = Kek::derive("correct horse battery staple", &salt).unwrap();
695 let k2 = Kek::derive("correct horse battery staple", &salt).unwrap();
696 let dek = generate_dek().unwrap();
697 let np = random_nonce_prefix().unwrap();
698 let w1 = k1.wrap_dek(&dek, &np).unwrap();
699 let w2 = k2.wrap_dek(&dek, &np).unwrap();
700 assert_eq!(w1, w2, "same passphrase+salt must yield same KEK");
701 }
702
703 #[test]
704 fn kek_differs_for_different_salt() {
705 let s1 = random_salt().unwrap();
706 let s2 = random_salt().unwrap();
707 let k1 = Kek::derive("passphrase", &s1).unwrap();
708 let k2 = Kek::derive("passphrase", &s2).unwrap();
709 let dek = generate_dek().unwrap();
710 let np = random_nonce_prefix().unwrap();
711 let w1 = k1.wrap_dek(&dek, &np).unwrap();
712 let w2 = k2.wrap_dek(&dek, &np).unwrap();
713 assert_ne!(w1, w2, "different salts must yield different KEKs");
714 }
715
716 #[test]
717 fn dek_wrap_unwrap_round_trip() {
718 let salt = random_salt().unwrap();
719 let kek = Kek::derive("hunter2", &salt).unwrap();
720 let dek = generate_dek().unwrap();
721 let np = random_nonce_prefix().unwrap();
722 let wrapped = kek.wrap_dek(&dek, &np).unwrap();
723 assert_eq!(wrapped.len(), DEK_LEN + 16);
724 let unwrapped = kek.unwrap_dek(&wrapped, &np).unwrap();
725 assert_eq!(unwrapped.as_ref(), dek.as_ref());
726 }
727
728 #[test]
729 fn unwrap_rejects_wrong_passphrase() {
730 let salt = random_salt().unwrap();
731 let enc_kek = Kek::derive("right-pass", &salt).unwrap();
732 let dec_kek = Kek::derive("wrong-pass", &salt).unwrap();
733 let dek = generate_dek().unwrap();
734 let np = random_nonce_prefix().unwrap();
735 let wrapped = enc_kek.wrap_dek(&dek, &np).unwrap();
736 assert!(dec_kek.unwrap_dek(&wrapped, &np).is_err());
737 }
738
739 #[test]
740 fn page_nonce_overlays_column_and_page() {
741 let np = random_nonce_prefix().unwrap();
742 let n = build_page_nonce(np, 0x0304, 0x0506);
743 assert_eq!(&n[..8], &np[..8]);
745 assert_eq!(n[8..10], [0x04, 0x03]);
746 assert_eq!(n[10..12], [0x06, 0x05]);
747 }
748
749 #[test]
750 fn page_nonce_unique_per_column_and_page() {
751 let np = random_nonce_prefix().unwrap();
752 let a = build_page_nonce(np, 1, 0);
753 let b = build_page_nonce(np, 1, 1);
754 let c = build_page_nonce(np, 2, 0);
755 assert_ne!(a, b);
756 assert_ne!(a, c);
757 assert_ne!(b, c);
758 }
759
760 #[test]
761 fn column_key_is_deterministic_from_kek() {
762 let salt = random_salt().unwrap();
763 let k1 = Kek::derive("pass", &salt).unwrap();
764 let k2 = Kek::derive("pass", &salt).unwrap();
765 let c1 = k1.derive_column_key(7);
766 let c2 = k2.derive_column_key(7);
767 assert_eq!(c1.as_ref(), c2.as_ref(), "same KEK + column => same key");
768 let c3 = k1.derive_column_key(8);
770 assert_ne!(c1.as_ref(), c3.as_ref());
771 }
772
773 #[test]
774 fn hmac_token_collides_only_for_equal_values() {
775 let salt = random_salt().unwrap();
776 let k = Kek::derive("pass", &salt).unwrap();
777 let ck = k.derive_column_key(1);
778 let a = hmac_token(&ck, b"hello");
779 let b = hmac_token(&ck, b"hello");
780 let c = hmac_token(&ck, b"world");
781 assert_eq!(a, b, "equal plaintexts => equal tokens");
782 assert_ne!(a, c, "unequal plaintexts => distinct tokens");
783 let ck2 = k.derive_column_key(2);
785 assert_ne!(a, hmac_token(&ck2, b"hello"));
786 }
787
788 #[test]
789 fn ope_token_i64_preserves_order() {
790 let salt = random_salt().unwrap();
791 let k = Kek::derive("pass", &salt).unwrap();
792 let ck = k.derive_column_key(3);
793 let vals = [i64::MIN, -1_000_000, -1, 0, 1, 42, 1_000_000, i64::MAX];
794 let tokens: Vec<_> = vals.iter().map(|&x| ope_token_i64(&ck, x)).collect();
795 for w in tokens.windows(2) {
797 assert!(w[0] < w[1], "OPE must preserve order");
798 }
799 assert_eq!(ope_token_i64(&ck, 0), ope_token_i64(&ck, 0));
801 }
802
803 #[test]
808 fn ope_token_is_non_linear() {
809 let salt = random_salt().unwrap();
810 let k = Kek::derive("pass", &salt).unwrap();
811 let ck = k.derive_column_key(9);
812 let t = |x: i64| u128::from_be_bytes(ope_token_i64(&ck, x));
813 let g1 = t(101).wrapping_sub(t(100));
815 let g2 = t(102).wrapping_sub(t(101));
816 let g3 = t(103).wrapping_sub(t(102));
817 assert!(
818 !(g1 == g2 && g2 == g3),
819 "constant token gaps => OPE is still affine/linear"
820 );
821 assert!(t(100) < t(101) && t(101) < t(102) && t(102) < t(103));
823 assert_eq!(ope_token_i64(&ck, 100), ope_token_i64(&ck, 100));
824 }
825
826 #[test]
827 fn ope_token_f64_preserves_order() {
828 let salt = random_salt().unwrap();
829 let k = Kek::derive("pass", &salt).unwrap();
830 let ck = k.derive_column_key(4);
831 let vals = [
832 f64::NEG_INFINITY,
833 -1.5,
834 0.0,
835 std::f64::consts::PI,
836 1e9,
837 f64::INFINITY,
838 ];
839 let tokens: Vec<_> = vals.iter().map(|&x| ope_token_f64(&ck, x)).collect();
840 for w in tokens.windows(2) {
841 assert!(w[0] < w[1], "OPE over f64 must preserve total order");
842 }
843 assert!(ope_token_f64(&ck, -1.0) < ope_token_f64(&ck, 1.0));
845 }
846
847 #[test]
851 fn wrap_nonces_are_distinct_within_a_run() {
852 use super::key::{wrap_nonce, WRAP_KIND_COLUMN, WRAP_KIND_DEK};
853 let salt = random_salt().unwrap();
854 let kek = Kek::derive("pass", &salt).unwrap();
855 let np = random_nonce_prefix().unwrap();
856
857 let dek_n = wrap_nonce(np, WRAP_KIND_DEK, 0);
859 let col1 = wrap_nonce(np, WRAP_KIND_COLUMN, 1);
860 let col2 = wrap_nonce(np, WRAP_KIND_COLUMN, 2);
861 assert_ne!(dek_n, col1);
862 assert_ne!(dek_n, col2);
863 assert_ne!(col1, col2);
864
865 let k = generate_dek().unwrap();
868 let w_dek = kek.wrap_dek(&k, &dek_n).unwrap();
869 let w_col = kek.wrap_column_key(&k, &col1).unwrap();
870 assert_ne!(
871 w_dek, w_col,
872 "DEK and column-key wraps must not share a nonce"
873 );
874
875 let enc =
877 setup_run_encryption(&kek, &[(1, SCHEME_HMAC_EQ), (2, SCHEME_OPE_RANGE)]).unwrap();
878 let built = build_run_cipher(&kek, &enc.descriptor_bytes).unwrap();
879 assert_eq!(built.nonce_prefix, enc.nonce_prefix);
880 }
881
882 #[test]
887 fn wal_deks_are_domain_separated() {
888 let salt = random_salt().unwrap();
889 let k = Kek::derive("pass", &salt).unwrap();
890 let shared = k.derive_shared_wal_key();
891 let tbl1 = k.derive_table_wal_key(1);
892 let tbl2 = k.derive_table_wal_key(2);
893 let legacy = k.derive_wal_key();
894 assert_ne!(shared.as_ref(), tbl1.as_ref(), "shared != table1");
895 assert_ne!(shared.as_ref(), tbl2.as_ref(), "shared != table2");
896 assert_ne!(shared.as_ref(), legacy.as_ref(), "shared != legacy");
897 assert_ne!(tbl1.as_ref(), tbl2.as_ref(), "table1 != table2");
898 assert_ne!(tbl1.as_ref(), legacy.as_ref(), "table1 != legacy");
899 }
900}