1#![allow(unused_variables, dead_code)]
2use crate::turso_assert;
3use crate::{LimboError, Result};
4use aegis::aegis128l::Aegis128L;
5use aegis::aegis128x2::Aegis128X2;
6use aegis::aegis128x4::Aegis128X4;
7use aegis::aegis256::Aegis256;
8use aegis::aegis256x2::Aegis256X2;
9use aegis::aegis256x4::Aegis256X4;
10use aes_gcm::{
11 aead::{Aead, AeadCore, AeadInPlace, KeyInit, OsRng},
12 Aes128Gcm, Aes256Gcm, Key, Nonce,
13};
14use turso_macros::{match_ignore_ascii_case, AtomicEnum};
15
16pub const TURSO_HEADER_PREFIX: &[u8] = b"Turso";
70pub const SQLITE_HEADER: &[u8] = b"SQLite format 3\0";
71const TURSO_VERSION: u8 = 0x00;
72const VERSION_OFFSET: usize = 5;
73const CIPHER_OFFSET: usize = 6;
74const TURSO_HEADER_SIZE: usize = 16;
75
76#[derive(Clone)]
77pub enum EncryptionKey {
78 Key128([u8; 16]),
79 Key256([u8; 32]),
80}
81
82impl EncryptionKey {
83 pub fn new_256(key: [u8; 32]) -> Self {
84 Self::Key256(key)
85 }
86
87 pub fn new_128(key: [u8; 16]) -> Self {
88 Self::Key128(key)
89 }
90
91 pub fn from_hex_string(s: &str) -> Result<Self> {
92 let hex_str = s.trim();
93 let bytes = hex::decode(hex_str)
94 .map_err(|e| LimboError::InvalidArgument(format!("Invalid hex string: {e}")))?;
95
96 match bytes.len() {
97 16 => {
98 let key: [u8; 16] = bytes.try_into().unwrap();
99 Ok(Self::Key128(key))
100 }
101 32 => {
102 let key: [u8; 32] = bytes.try_into().unwrap();
103 Ok(Self::Key256(key))
104 }
105 _ => Err(LimboError::InvalidArgument(format!(
106 "Hex string must decode to exactly 16 or 32 bytes, got {}",
107 bytes.len()
108 ))),
109 }
110 }
111
112 pub fn as_slice(&self) -> &[u8] {
113 match self {
114 Self::Key128(key) => key,
115 Self::Key256(key) => key,
116 }
117 }
118
119 #[allow(clippy::len_without_is_empty)]
120 pub fn len(&self) -> usize {
121 match self {
122 Self::Key128(_) => 16,
123 Self::Key256(_) => 32,
124 }
125 }
126
127 pub fn as_128(&self) -> Option<&[u8; 16]> {
128 match self {
129 Self::Key128(key) => Some(key),
130 _ => None,
131 }
132 }
133
134 pub fn as_256(&self) -> Option<&[u8; 32]> {
135 match self {
136 Self::Key256(key) => Some(key),
137 _ => None,
138 }
139 }
140}
141
142impl std::fmt::Debug for EncryptionKey {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 f.debug_struct("EncryptionKey")
145 .field("key", &"<encryption key redacted>")
146 .finish()
147 }
148}
149
150impl Drop for EncryptionKey {
151 fn drop(&mut self) {
152 match self {
154 Self::Key128(key) => {
155 for byte in key.iter_mut() {
156 unsafe {
157 std::ptr::write_volatile(byte, 0);
158 }
159 }
160 }
161 Self::Key256(key) => {
162 for byte in key.iter_mut() {
163 unsafe {
164 std::ptr::write_volatile(byte, 0);
165 }
166 }
167 }
168 }
169 }
170}
171
172macro_rules! define_aegis_cipher {
173 ($struct_name:ident, $cipher_type:ty, key128, $nonce_size:literal, $name:literal) => {
174 define_aegis_cipher!(@impl $struct_name, $cipher_type, $nonce_size, $name, 16, as_128);
175 };
176 ($struct_name:ident, $cipher_type:ty, key256, $nonce_size:literal, $name:literal) => {
177 define_aegis_cipher!(@impl $struct_name, $cipher_type, $nonce_size, $name, 32, as_256);
178 };
179 (@impl $struct_name:ident, $cipher_type:ty, $nonce_size:literal, $name:literal, $key_size:literal, $key_method:ident) => {
180 #[derive(Clone)]
181 pub struct $struct_name {
182 key: EncryptionKey,
183 }
184
185 impl $struct_name {
186 const TAG_SIZE: usize = 16;
187
188 fn new(key: &EncryptionKey) -> Self {
189 Self { key: key.clone() }
190 }
191
192 fn encrypt(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec<u8>, [u8; $nonce_size])> {
193 let nonce = generate_secure_nonce::<$nonce_size>();
194 let key_bytes = self.key.$key_method()
195 .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?;
196 let (ciphertext, tag) = <$cipher_type>::new(key_bytes, &nonce).encrypt(plaintext, ad);
197 let mut result = ciphertext;
198 result.extend_from_slice(&tag);
199 Ok((result, nonce))
200 }
201
202 fn decrypt(&self, ciphertext: &[u8], nonce: &[u8; $nonce_size], ad: &[u8]) -> Result<Vec<u8>> {
203 let mut out = Vec::with_capacity(ciphertext.len().saturating_sub(Self::TAG_SIZE));
204 self.decrypt_into(ciphertext, nonce, ad, &mut out)?;
205 Ok(out)
206 }
207
208 fn decrypt_into(
209 &self,
210 ciphertext: &[u8],
211 nonce: &[u8; $nonce_size],
212 ad: &[u8],
213 out: &mut Vec<u8>,
214 ) -> Result<()> {
215 if ciphertext.len() < Self::TAG_SIZE {
216 return Err(LimboError::from(CipherError::CiphertextTooShort { cipher: $name }));
217 }
218 let (ct, tag) = ciphertext.split_at(ciphertext.len() - Self::TAG_SIZE);
219 let tag_array: [u8; 16] = tag.try_into().map_err(|_| -> LimboError { CipherError::InvalidTagSize { cipher: $name }.into() })?;
220
221 let key_bytes = self.key.$key_method()
222 .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?;
223 out.clear();
224 out.extend_from_slice(ct);
225 <$cipher_type>::new(key_bytes, nonce)
226 .decrypt_in_place(out.as_mut_slice(), &tag_array, ad)
227 .map_err(|_| -> LimboError { CipherError::DecryptionFailed { cipher: $name }.into() })
228 }
229 }
230
231 impl std::fmt::Debug for $struct_name {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 f.debug_struct(stringify!($struct_name))
234 .field("key", &"<redacted>")
235 .finish()
236 }
237 }
238 };
239}
240
241macro_rules! define_aes_gcm_cipher {
242 ($struct_name:ident, $cipher_type:ty, key128, $name:literal) => {
243 define_aes_gcm_cipher!(@impl $struct_name, $cipher_type, $name, 16, as_128);
244 };
245 ($struct_name:ident, $cipher_type:ty, key256, $name:literal) => {
246 define_aes_gcm_cipher!(@impl $struct_name, $cipher_type, $name, 32, as_256);
247 };
248 (@impl $struct_name:ident, $cipher_type:ty, $name:literal, $key_size:literal, $key_method:ident) => {
249 #[derive(Clone)]
250 pub struct $struct_name {
251 cipher: $cipher_type,
252 }
253
254 impl $struct_name {
255 const TAG_SIZE: usize = 16;
256 const NONCE_SIZE: usize = 12;
257
258 fn new(key: &EncryptionKey) -> Result<Self> {
259 let key_bytes = key.$key_method()
260 .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?;
261 let cipher_key: &Key<$cipher_type> = key_bytes.into();
262 Ok(Self {
263 cipher: <$cipher_type>::new(cipher_key),
264 })
265 }
266
267 fn encrypt(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec<u8>, [u8; 12])> {
268 let nonce = <$cipher_type>::generate_nonce(&mut OsRng);
269 let ciphertext = self.cipher.encrypt(&nonce, aes_gcm::aead::Payload {
270 msg: plaintext,
271 aad: ad,
272 }).map_err(|e| {
273 LimboError::InternalError(format!("{} encryption failed: {e:?}", $name))
274 })?;
275 let mut nonce_array = [0u8; 12];
276 nonce_array.copy_from_slice(&nonce);
277 Ok((ciphertext, nonce_array))
278 }
279
280 fn decrypt(&self, ciphertext: &[u8], nonce: &[u8; 12], ad: &[u8]) -> Result<Vec<u8>> {
281 let mut out = Vec::with_capacity(ciphertext.len().saturating_sub(Self::TAG_SIZE));
282 self.decrypt_into(ciphertext, nonce, ad, &mut out)?;
283 Ok(out)
284 }
285
286 fn decrypt_into(
287 &self,
288 ciphertext: &[u8],
289 nonce: &[u8; 12],
290 ad: &[u8],
291 out: &mut Vec<u8>,
292 ) -> Result<()> {
293 let nonce = Nonce::from_slice(nonce);
294 out.clear();
295 out.extend_from_slice(ciphertext);
296 self.cipher
297 .decrypt_in_place(nonce, ad, out)
298 .map_err(|_| -> LimboError { CipherError::DecryptionFailed { cipher: $name }.into() })
299 }
300 }
301
302 impl std::fmt::Debug for $struct_name {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 f.debug_struct(stringify!($struct_name))
305 .field("key", &"<redacted>")
306 .finish()
307 }
308 }
309 };
310}
311
312define_aes_gcm_cipher!(Aes128GcmCipher, Aes128Gcm, key128, "AES-128-GCM");
314define_aes_gcm_cipher!(Aes256GcmCipher, Aes256Gcm, key256, "AES-256-GCM");
315
316define_aegis_cipher!(Aegis256Cipher, Aegis256::<16>, key256, 32, "AEGIS-256");
318define_aegis_cipher!(
319 Aegis256X2Cipher,
320 Aegis256X2::<16>,
321 key256,
322 32,
323 "AEGIS-256X2"
324);
325define_aegis_cipher!(
326 Aegis256X4Cipher,
327 Aegis256X4::<16>,
328 key256,
329 32,
330 "AEGIS-256X4"
331);
332define_aegis_cipher!(
333 Aegis128X2Cipher,
334 Aegis128X2::<16>,
335 key128,
336 16,
337 "AEGIS-128X2"
338);
339define_aegis_cipher!(Aegis128LCipher, Aegis128L::<16>, key128, 16, "AEGIS-128L");
340define_aegis_cipher!(
341 Aegis128X4Cipher,
342 Aegis128X4::<16>,
343 key128,
344 16,
345 "AEGIS-128X4"
346);
347
348#[derive(Debug, AtomicEnum, Clone, Copy, PartialEq, Eq)]
349pub enum CipherMode {
350 None,
351 Aes128Gcm,
352 Aes256Gcm,
353 Aegis256,
354 Aegis128L,
355 Aegis128X2,
356 Aegis128X4,
357 Aegis256X2,
358 Aegis256X4,
359}
360
361impl TryFrom<&str> for CipherMode {
362 type Error = LimboError;
363
364 fn try_from(s: &str) -> Result<Self, Self::Error> {
365 let s_bytes = s.as_bytes();
366 match_ignore_ascii_case!(match s_bytes {
367 b"aes128gcm" | b"aes-128-gcm" | b"aes_128_gcm" => Ok(CipherMode::Aes128Gcm),
368 b"aes256gcm" | b"aes-256-gcm" | b"aes_256_gcm" => Ok(CipherMode::Aes256Gcm),
369 b"aegis256" | b"aegis-256" | b"aegis_256" => Ok(CipherMode::Aegis256),
370 b"aegis128l" | b"aegis-128l" | b"aegis_128l" => Ok(CipherMode::Aegis128L),
371 b"aegis128x2" | b"aegis-128x2" | b"aegis_128x2" => Ok(CipherMode::Aegis128X2),
372 b"aegis128x4" | b"aegis-128x4" | b"aegis_128x4" => Ok(CipherMode::Aegis128X4),
373 b"aegis256x2" | b"aegis-256x2" | b"aegis_256x2" => Ok(CipherMode::Aegis256X2),
374 b"aegis256x4" | b"aegis-256x4" | b"aegis_256x4" => Ok(CipherMode::Aegis256X4),
375 _ => Err(LimboError::InvalidArgument(format!(
376 "Unknown cipher name: {s}"
377 ))),
378 })
379 }
380}
381
382impl std::fmt::Display for CipherMode {
383 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384 match self {
385 CipherMode::Aes128Gcm => write!(f, "aes128gcm"),
386 CipherMode::Aes256Gcm => write!(f, "aes256gcm"),
387 CipherMode::Aegis256 => write!(f, "aegis256"),
388 CipherMode::Aegis128L => write!(f, "aegis128l"),
389 CipherMode::Aegis128X2 => write!(f, "aegis128x2"),
390 CipherMode::Aegis128X4 => write!(f, "aegis128x4"),
391 CipherMode::Aegis256X2 => write!(f, "aegis256x2"),
392 CipherMode::Aegis256X4 => write!(f, "aegis256x4"),
393 CipherMode::None => write!(f, "None"),
394 }
395 }
396}
397
398impl CipherMode {
399 pub fn required_key_size(&self) -> usize {
402 match self {
403 CipherMode::Aes128Gcm => 16,
404 CipherMode::Aes256Gcm => 32,
405 CipherMode::Aegis256 => 32,
406 CipherMode::Aegis256X2 => 32,
407 CipherMode::Aegis256X4 => 32,
408 CipherMode::Aegis128L => 16,
409 CipherMode::Aegis128X2 => 16,
410 CipherMode::Aegis128X4 => 16,
411 CipherMode::None => 0,
412 }
413 }
414
415 pub fn nonce_size(&self) -> usize {
417 match self {
418 CipherMode::Aes128Gcm => 12,
419 CipherMode::Aes256Gcm => 12,
420 CipherMode::Aegis256 => 32,
421 CipherMode::Aegis256X2 => 32,
422 CipherMode::Aegis256X4 => 32,
423 CipherMode::Aegis128L => 16,
424 CipherMode::Aegis128X2 => 16,
425 CipherMode::Aegis128X4 => 16,
426 CipherMode::None => 0,
427 }
428 }
429
430 pub fn tag_size(&self) -> usize {
432 match self {
433 CipherMode::Aes128Gcm => 16,
434 CipherMode::Aes256Gcm => 16,
435 CipherMode::Aegis256 => 16,
436 CipherMode::Aegis256X2 => 16,
437 CipherMode::Aegis256X4 => 16,
438 CipherMode::Aegis128L => 16,
439 CipherMode::Aegis128X2 => 16,
440 CipherMode::Aegis128X4 => 16,
441 CipherMode::None => 0,
442 }
443 }
444
445 pub fn metadata_size(&self) -> usize {
447 self.nonce_size() + self.tag_size()
448 }
449
450 pub fn cipher_id(&self) -> u8 {
452 match self {
453 CipherMode::Aes128Gcm => 1,
454 CipherMode::Aes256Gcm => 2,
455 CipherMode::Aegis256 => 3,
456 CipherMode::Aegis256X2 => 4,
457 CipherMode::Aegis256X4 => 5,
458 CipherMode::Aegis128L => 6,
459 CipherMode::Aegis128X2 => 7,
460 CipherMode::Aegis128X4 => 8,
461 CipherMode::None => 0,
462 }
463 }
464
465 pub fn from_cipher_id(id: u8) -> Result<Self> {
467 match id {
468 1 => Ok(CipherMode::Aes128Gcm),
469 2 => Ok(CipherMode::Aes256Gcm),
470 3 => Ok(CipherMode::Aegis256),
471 4 => Ok(CipherMode::Aegis256X2),
472 5 => Ok(CipherMode::Aegis256X4),
473 6 => Ok(CipherMode::Aegis128L),
474 7 => Ok(CipherMode::Aegis128X2),
475 8 => Ok(CipherMode::Aegis128X4),
476 _ => Err(LimboError::InvalidArgument(format!(
477 "Unknown cipher ID: {id}"
478 ))),
479 }
480 }
481}
482
483#[derive(Clone)]
484pub enum Cipher {
485 Aes128Gcm(Box<Aes128GcmCipher>),
486 Aes256Gcm(Box<Aes256GcmCipher>),
487 Aegis256(Box<Aegis256Cipher>),
488 Aegis256X2(Box<Aegis256X2Cipher>),
489 Aegis256X4(Box<Aegis256X4Cipher>),
490 Aegis128L(Box<Aegis128LCipher>),
491 Aegis128X2(Box<Aegis128X2Cipher>),
492 Aegis128X4(Box<Aegis128X4Cipher>),
493}
494
495impl std::fmt::Debug for Cipher {
496 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
497 match self {
498 Cipher::Aes128Gcm(_) => write!(f, "Cipher::Aes128Gcm"),
499 Cipher::Aes256Gcm(_) => write!(f, "Cipher::Aes256Gcm"),
500 Cipher::Aegis256(_) => write!(f, "Cipher::Aegis256"),
501 Cipher::Aegis256X2(_) => write!(f, "Cipher::Aegis256X2"),
502 Cipher::Aegis256X4(_) => write!(f, "Cipher::Aegis256X4"),
503 Cipher::Aegis128L(_) => write!(f, "Cipher::Aegis128L"),
504 Cipher::Aegis128X2(_) => write!(f, "Cipher::Aegis128X2"),
505 Cipher::Aegis128X4(_) => write!(f, "Cipher::Aegis128X4"),
506 }
507 }
508}
509
510#[derive(Debug, Clone)]
511pub struct EncryptionContext {
512 cipher_mode: CipherMode,
513 cipher: Cipher,
514 page_size: usize,
515}
516
517impl EncryptionContext {
518 pub fn new(cipher_mode: CipherMode, key: &EncryptionKey, page_size: usize) -> Result<Self> {
519 let required_size = cipher_mode.required_key_size();
520 if key.len() != required_size {
521 return Err(crate::LimboError::InvalidArgument(format!(
522 "Invalid key size for {:?}: expected {} bytes, got {}",
523 cipher_mode,
524 required_size,
525 key.len()
526 )));
527 }
528
529 let cipher = match cipher_mode {
530 CipherMode::Aes128Gcm => Cipher::Aes128Gcm(Box::new(Aes128GcmCipher::new(key)?)),
531 CipherMode::Aes256Gcm => Cipher::Aes256Gcm(Box::new(Aes256GcmCipher::new(key)?)),
532 CipherMode::Aegis256 => Cipher::Aegis256(Box::new(Aegis256Cipher::new(key))),
533 CipherMode::Aegis256X2 => Cipher::Aegis256X2(Box::new(Aegis256X2Cipher::new(key))),
534 CipherMode::Aegis256X4 => Cipher::Aegis256X4(Box::new(Aegis256X4Cipher::new(key))),
535 CipherMode::Aegis128L => Cipher::Aegis128L(Box::new(Aegis128LCipher::new(key))),
536 CipherMode::Aegis128X2 => Cipher::Aegis128X2(Box::new(Aegis128X2Cipher::new(key))),
537 CipherMode::Aegis128X4 => Cipher::Aegis128X4(Box::new(Aegis128X4Cipher::new(key))),
538 CipherMode::None => {
539 return Err(LimboError::InvalidArgument(
540 "must select valid CipherMode".into(),
541 ))
542 }
543 };
544 Ok(Self {
545 cipher_mode,
546 cipher,
547 page_size,
548 })
549 }
550
551 pub fn cipher_mode(&self) -> CipherMode {
552 self.cipher_mode
553 }
554
555 pub fn required_reserved_bytes(&self) -> u8 {
557 self.cipher_mode.metadata_size() as u8
558 }
559
560 pub fn encrypt_chunk(&self, plaintext: &[u8], aad: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
561 self.encrypt_raw_with_ad(plaintext, aad)
562 }
563
564 pub fn decrypt_chunk(&self, ciphertext: &[u8], nonce: &[u8], aad: &[u8]) -> Result<Vec<u8>> {
565 self.decrypt_raw_with_ad(ciphertext, nonce, aad)
566 }
567
568 pub fn decrypt_chunk_into(
569 &self,
570 ciphertext: &[u8],
571 nonce: &[u8],
572 aad: &[u8],
573 out: &mut Vec<u8>,
574 ) -> Result<()> {
575 self.decrypt_raw_with_ad_into(ciphertext, nonce, aad, out)
576 }
577
578 pub fn nonce_size(&self) -> usize {
579 self.cipher_mode.nonce_size()
580 }
581
582 pub fn tag_size(&self) -> usize {
583 self.cipher_mode.tag_size()
584 }
585
586 fn create_turso_header(&self) -> [u8; TURSO_HEADER_SIZE] {
588 let mut header = [0u8; TURSO_HEADER_SIZE];
589
590 header[..TURSO_HEADER_PREFIX.len()].copy_from_slice(TURSO_HEADER_PREFIX);
592
593 header[VERSION_OFFSET] = TURSO_VERSION;
595
596 header[CIPHER_OFFSET] = self.cipher_mode.cipher_id();
598
599 header
601 }
602
603 fn validate_turso_header(&self, header: &[u8]) -> Result<()> {
605 if header.len() < TURSO_HEADER_SIZE {
606 return Err(LimboError::InternalError(
607 "Header too short for encrypted Turso db".into(),
608 ));
609 }
610
611 if &header[..TURSO_HEADER_PREFIX.len()] != TURSO_HEADER_PREFIX {
612 return Err(LimboError::InternalError(
613 "Invalid Turso header: prefix mismatch".into(),
614 ));
615 }
616
617 let version = header[VERSION_OFFSET];
618 if version != TURSO_VERSION {
619 return Err(LimboError::InternalError(format!(
620 "Unsupported Turso header version: expected {TURSO_VERSION}, got {version}"
621 )));
622 }
623
624 let cipher_id = header[CIPHER_OFFSET];
625 let header_cipher = CipherMode::from_cipher_id(cipher_id)?;
626 if header_cipher != self.cipher_mode {
627 return Err(LimboError::InternalError(format!(
628 "Cipher mode mismatch: expected {:?} (ID {}), got {:?} (ID {})",
629 self.cipher_mode,
630 self.cipher_mode.cipher_id(),
631 header_cipher,
632 cipher_id
633 )));
634 }
635
636 if header[CIPHER_OFFSET + 1..TURSO_HEADER_SIZE]
637 .iter()
638 .any(|&b| b != 0)
639 {
640 return Err(LimboError::InternalError(
641 "Invalid Turso header: unused bytes must be zero".into(),
642 ));
643 }
644 Ok(())
645 }
646
647 #[cfg(clt_turso_feature = "encryption")]
648 pub fn encrypt_page(&self, page: &[u8], page_id: usize) -> Result<Vec<u8>> {
649 use crate::storage::sqlite3_ondisk::DatabaseHeader;
650 if page_id == DatabaseHeader::PAGE_ID {
651 return self.encrypt_page_1(page);
652 }
653 tracing::debug!("encrypting page {}", page_id);
654 assert_eq!(
655 page.len(),
656 self.page_size,
657 "Page data must be exactly {} bytes",
658 self.page_size
659 );
660
661 let metadata_size = self.cipher_mode.metadata_size();
662 let reserved_bytes = &page[self.page_size - metadata_size..];
663
664 #[cfg(debug_assertions)]
665 {
666 let reserved_bytes_zeroed = reserved_bytes.iter().all(|&b| b == 0);
667 turso_assert!(
668 reserved_bytes_zeroed,
669 "last reserved bytes must be empty/zero, but found non-zero bytes"
670 );
671 }
672
673 let payload = &page[..self.page_size - metadata_size];
674 let (encrypted, nonce) = self.encrypt_raw(payload)?;
675
676 let nonce_size = self.cipher_mode.nonce_size();
677 assert_eq!(
678 encrypted.len(),
679 self.page_size - nonce_size,
680 "Encrypted page must be exactly {} bytes",
681 self.page_size - nonce_size
682 );
683
684 let mut result = Vec::with_capacity(self.page_size);
685 result.extend_from_slice(&encrypted);
686 result.extend_from_slice(&nonce);
687 assert_eq!(
688 result.len(),
689 self.page_size,
690 "Encrypted page must be exactly {} bytes",
691 self.page_size
692 );
693 Ok(result)
694 }
695
696 #[cfg(clt_turso_feature = "encryption")]
697 pub fn decrypt_page(&self, encrypted_page: &[u8], page_id: usize) -> Result<Vec<u8>> {
698 use crate::storage::sqlite3_ondisk::DatabaseHeader;
699 if page_id == DatabaseHeader::PAGE_ID {
700 return self.decrypt_page_1(encrypted_page);
701 }
702 tracing::debug!("decrypting page {}", page_id);
703 assert_eq!(
704 encrypted_page.len(),
705 self.page_size,
706 "Encrypted page data must be exactly {} bytes",
707 self.page_size
708 );
709
710 let nonce_size = self.cipher_mode.nonce_size();
711 let nonce_offset = encrypted_page.len() - nonce_size;
712 let payload = &encrypted_page[..nonce_offset];
713 let nonce = &encrypted_page[nonce_offset..];
714
715 let decrypted_data = self.decrypt_raw(payload, nonce)?;
716 let metadata_size = self.cipher_mode.metadata_size();
717 assert_eq!(
718 decrypted_data.len(),
719 self.page_size - metadata_size,
720 "Decrypted page data must be exactly {} bytes",
721 self.page_size - metadata_size
722 );
723
724 let mut result = Vec::with_capacity(self.page_size);
725 result.extend_from_slice(&decrypted_data);
726 result.resize(self.page_size, 0);
727
728 assert_eq!(
729 result.len(),
730 self.page_size,
731 "Decrypted page data must be exactly {} bytes",
732 self.page_size
733 );
734 Ok(result)
735 }
736
737 #[cfg(clt_turso_feature = "encryption")]
738 fn encrypt_page_1(&self, page: &[u8]) -> Result<Vec<u8>> {
739 use crate::storage::sqlite3_ondisk::DatabaseHeader;
740
741 tracing::debug!("encrypting page 1");
742 assert_eq!(
743 page.len(),
744 self.page_size,
745 "Page data must be exactly {} bytes",
746 self.page_size
747 );
748
749 turso_assert!(
751 page.starts_with(SQLITE_HEADER),
752 "Page 1 must start with SQLite header"
753 );
754
755 let metadata_size = self.cipher_mode.metadata_size();
756 let reserved_bytes = &page[self.page_size - metadata_size..];
757
758 #[cfg(debug_assertions)]
759 {
760 let reserved_bytes_zeroed = reserved_bytes.iter().all(|&b| b == 0);
765 turso_assert!(
766 reserved_bytes_zeroed,
767 "last reserved bytes must be empty/zero, but found non-zero bytes"
768 );
769 }
770
771 let turso_header = self.create_turso_header();
777 let mut new_header = Vec::with_capacity(DatabaseHeader::SIZE);
778 new_header.extend_from_slice(&turso_header);
779 new_header.extend_from_slice(&page[TURSO_HEADER_SIZE..DatabaseHeader::SIZE]);
780
781 let payload = &page[DatabaseHeader::SIZE..self.page_size - metadata_size];
782 let (encrypted, nonce) = self.encrypt_raw_with_ad(payload, &new_header)?;
783
784 let nonce_size = self.cipher_mode.nonce_size();
785 assert_eq!(
786 encrypted.len(),
787 self.page_size - nonce_size - DatabaseHeader::SIZE,
788 "Encrypted page must be exactly {} bytes",
789 self.page_size - nonce_size - DatabaseHeader::SIZE
790 );
791
792 let mut result = Vec::with_capacity(self.page_size);
793
794 result.append(&mut new_header);
796 result.extend_from_slice(&encrypted);
798 result.extend_from_slice(&nonce);
800
801 assert_eq!(
802 result.len(),
803 self.page_size,
804 "Encrypted page must be exactly {} bytes",
805 self.page_size
806 );
807 Ok(result)
808 }
809
810 #[cfg(clt_turso_feature = "encryption")]
811 fn decrypt_page_1(&self, encrypted_page: &[u8]) -> Result<Vec<u8>> {
812 use crate::storage::sqlite3_ondisk::DatabaseHeader;
813
814 tracing::debug!("decrypting page 1");
815 assert_eq!(
816 encrypted_page.len(),
817 self.page_size,
818 "Encrypted page data must be exactly {} bytes",
819 self.page_size
820 );
821
822 self.validate_turso_header(&encrypted_page[..TURSO_HEADER_SIZE])?;
823
824 let nonce_size = self.cipher_mode.nonce_size();
825 let nonce_offset = encrypted_page.len() - nonce_size;
826 let payload = &encrypted_page[DatabaseHeader::SIZE..nonce_offset];
827 let nonce = &encrypted_page[nonce_offset..];
828
829 let header = &encrypted_page[..DatabaseHeader::SIZE];
832 let decrypted_data = self.decrypt_raw_with_ad(payload, nonce, header)?;
833
834 let metadata_size = self.cipher_mode.metadata_size();
835 assert_eq!(
836 decrypted_data.len(),
837 self.page_size - metadata_size - DatabaseHeader::SIZE,
838 "Decrypted page data must be exactly {} bytes",
839 self.page_size - metadata_size - DatabaseHeader::SIZE
840 );
841
842 let mut result = Vec::with_capacity(self.page_size);
844 result.extend_from_slice(SQLITE_HEADER);
845 result.extend_from_slice(&encrypted_page[TURSO_HEADER_SIZE..DatabaseHeader::SIZE]);
846 result.extend_from_slice(&decrypted_data);
847 result.resize(self.page_size, 0);
848
849 assert_eq!(
850 result.len(),
851 self.page_size,
852 "Decrypted page data must be exactly {} bytes",
853 self.page_size
854 );
855 Ok(result)
856 }
857
858 fn encrypt_raw(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
860 const AD: &[u8] = b"";
861 self.encrypt_raw_with_ad(plaintext, AD)
862 }
863
864 fn encrypt_raw_with_ad(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
866 macro_rules! encrypt_cipher {
867 ($cipher:expr) => {{
868 let (ciphertext, nonce) = $cipher.encrypt(plaintext, ad)?;
869 Ok((ciphertext, nonce.to_vec()))
870 }};
871 }
872
873 match &self.cipher {
874 Cipher::Aes128Gcm(cipher) => encrypt_cipher!(cipher),
875 Cipher::Aes256Gcm(cipher) => encrypt_cipher!(cipher),
876 Cipher::Aegis256(cipher) => encrypt_cipher!(cipher),
877 Cipher::Aegis256X2(cipher) => encrypt_cipher!(cipher),
878 Cipher::Aegis256X4(cipher) => encrypt_cipher!(cipher),
879 Cipher::Aegis128L(cipher) => encrypt_cipher!(cipher),
880 Cipher::Aegis128X2(cipher) => encrypt_cipher!(cipher),
881 Cipher::Aegis128X4(cipher) => encrypt_cipher!(cipher),
882 }
883 }
884
885 fn decrypt_raw(&self, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>> {
886 const AD: &[u8] = b"";
887 self.decrypt_raw_with_ad(ciphertext, nonce, AD)
888 }
889
890 fn decrypt_raw_with_ad(&self, ciphertext: &[u8], nonce: &[u8], ad: &[u8]) -> Result<Vec<u8>> {
891 let mut out = Vec::with_capacity(ciphertext.len().saturating_sub(self.tag_size()));
892 self.decrypt_raw_with_ad_into(ciphertext, nonce, ad, &mut out)?;
893 Ok(out)
894 }
895
896 fn decrypt_raw_with_ad_into(
897 &self,
898 ciphertext: &[u8],
899 nonce: &[u8],
900 ad: &[u8],
901 out: &mut Vec<u8>,
902 ) -> Result<()> {
903 macro_rules! decrypt_with_nonce {
904 ($cipher:expr, $nonce_size:literal, $name:literal) => {{
905 let nonce_array: [u8; $nonce_size] = nonce.try_into().map_err(|_| {
906 LimboError::InternalError(format!(
907 "Invalid nonce size for {}: expected {}, got {}",
908 $name,
909 $nonce_size,
910 nonce.len()
911 ))
912 })?;
913 $cipher.decrypt_into(ciphertext, &nonce_array, ad, out)
914 }};
915 }
916
917 match &self.cipher {
918 Cipher::Aes128Gcm(cipher) => decrypt_with_nonce!(cipher, 12, "AES-128-GCM"),
919 Cipher::Aes256Gcm(cipher) => decrypt_with_nonce!(cipher, 12, "AES-256-GCM"),
920 Cipher::Aegis256(cipher) => decrypt_with_nonce!(cipher, 32, "AEGIS-256"),
921 Cipher::Aegis256X2(cipher) => decrypt_with_nonce!(cipher, 32, "AEGIS-256X2"),
922 Cipher::Aegis256X4(cipher) => decrypt_with_nonce!(cipher, 32, "AEGIS-256X4"),
923 Cipher::Aegis128L(cipher) => decrypt_with_nonce!(cipher, 16, "AEGIS-128L"),
924 Cipher::Aegis128X2(cipher) => decrypt_with_nonce!(cipher, 16, "AEGIS-128X2"),
925 Cipher::Aegis128X4(cipher) => decrypt_with_nonce!(cipher, 16, "AEGIS-128X4"),
926 }
927 }
928
929 #[cfg(not(clt_turso_feature = "encryption"))]
930 pub fn encrypt_page(&self, _page: &[u8], _page_id: usize) -> Result<Vec<u8>> {
931 Err(LimboError::InvalidArgument(
932 "encryption is not enabled, cannot encrypt page. enable via passing `--features encryption`".into(),
933 ))
934 }
935
936 #[cfg(not(clt_turso_feature = "encryption"))]
937 pub fn decrypt_page(&self, _encrypted_page: &[u8], _page_id: usize) -> Result<Vec<u8>> {
938 Err(LimboError::InvalidArgument(
939 "encryption is not enabled, cannot decrypt page. enable via passing `--features encryption`".into(),
940 ))
941 }
942}
943
944fn generate_secure_nonce<const N: usize>() -> [u8; N] {
945 use aes_gcm::aead::rand_core::RngCore;
947 let mut nonce = [0u8; N];
948 OsRng.fill_bytes(&mut nonce);
949 nonce
950}
951
952enum CipherError {
954 InvalidKeySize {
955 cipher: &'static str,
956 expected: usize,
957 },
958 InvalidTagSize {
959 cipher: &'static str,
960 },
961 DecryptionFailed {
962 cipher: &'static str,
963 },
964 CiphertextTooShort {
965 cipher: &'static str,
966 },
967}
968
969impl From<CipherError> for LimboError {
970 fn from(err: CipherError) -> Self {
971 let msg = match err {
972 CipherError::InvalidKeySize { cipher, expected } => {
973 format!("{cipher} requires {expected}-byte key")
974 }
975 CipherError::InvalidTagSize { cipher } => format!("Invalid tag size for {cipher}"),
976 CipherError::DecryptionFailed { cipher } => {
977 format!("{cipher} decryption failed: invalid tag")
978 }
979 CipherError::CiphertextTooShort { cipher } => {
980 format!("Ciphertext too short for {cipher}")
981 }
982 };
983 LimboError::InternalError(msg)
984 }
985}
986
987#[cfg(clt_turso_tests)]
988#[cfg(clt_turso_feature = "encryption")]
989mod tests {
990 use crate::storage::sqlite3_ondisk::DatabaseHeader;
991
992 use super::*;
993 use rand::Rng;
994 const DEFAULT_ENCRYPTED_PAGE_SIZE: usize = 4096;
995
996 macro_rules! test_cipher_wrapper {
997 ($test_name:ident, $cipher_type:ty, $key_gen:expr, $nonce_size:literal, $message:literal) => {
998 #[test]
999 fn $test_name() {
1000 let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap();
1001 let cipher = <$cipher_type>::new(&key);
1002
1003 let plaintext = $message.as_bytes();
1004 let ad = b"additional data";
1005
1006 let (ciphertext, nonce) = cipher.encrypt(plaintext, ad).unwrap();
1007 assert_eq!(nonce.len(), $nonce_size);
1008 assert_ne!(ciphertext[..plaintext.len()], plaintext[..]);
1009
1010 let decrypted = cipher.decrypt(&ciphertext, &nonce, ad).unwrap();
1011 assert_eq!(decrypted, plaintext);
1012 }
1013 };
1014 }
1015
1016 macro_rules! test_aes_cipher_wrapper {
1017 ($test_name:ident, $cipher_type:ty, $key_gen:expr, $nonce_size:literal, $message:literal) => {
1018 #[test]
1019 fn $test_name() {
1020 let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap();
1021 let cipher = <$cipher_type>::new(&key).unwrap();
1022
1023 let plaintext = $message.as_bytes();
1024 let ad = b"additional data";
1025
1026 let (ciphertext, nonce) = cipher.encrypt(plaintext, ad).unwrap();
1027 assert_eq!(nonce.len(), $nonce_size);
1028 assert_ne!(ciphertext[..plaintext.len()], plaintext[..]);
1029
1030 let decrypted = cipher.decrypt(&ciphertext, &nonce, ad).unwrap();
1031 assert_eq!(decrypted, plaintext);
1032 }
1033 };
1034 }
1035
1036 macro_rules! test_raw_encryption {
1037 ($test_name:ident, $cipher_mode:expr, $key_gen:expr, $nonce_size:literal, $message:literal) => {
1038 #[test]
1039 fn $test_name() {
1040 let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap();
1041 let ctx = EncryptionContext::new($cipher_mode, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1042 .unwrap();
1043
1044 let plaintext = $message.as_bytes();
1045 let (ciphertext, nonce) = ctx.encrypt_raw(plaintext).unwrap();
1046
1047 assert_eq!(nonce.len(), $nonce_size);
1048 assert_ne!(ciphertext[..plaintext.len()], plaintext[..]);
1049
1050 let decrypted = ctx.decrypt_raw(&ciphertext, &nonce).unwrap();
1051 assert_eq!(decrypted, plaintext);
1052 }
1053 };
1054 }
1055
1056 fn generate_random_hex_key() -> String {
1057 let mut rng = rand::rng();
1058 let mut bytes = [0u8; 32];
1059 rng.fill(&mut bytes);
1060 hex::encode(bytes)
1061 }
1062
1063 fn generate_random_hex_key_128() -> String {
1064 let mut rng = rand::rng();
1065 let mut bytes = [0u8; 16];
1066 rng.fill(&mut bytes);
1067 hex::encode(bytes)
1068 }
1069
1070 fn create_test_page_1() -> Vec<u8> {
1071 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1072 page[..SQLITE_HEADER.len()].copy_from_slice(SQLITE_HEADER);
1073 let mut rng = rand::rng();
1074 rng.fill(&mut page[SQLITE_HEADER.len()..DEFAULT_ENCRYPTED_PAGE_SIZE - 48]);
1076 page
1077 }
1078
1079 test_aes_cipher_wrapper!(
1080 test_aes128gcm_cipher_wrapper,
1081 Aes128GcmCipher,
1082 generate_random_hex_key_128,
1083 12,
1084 "Hello, AES-128-GCM!"
1085 );
1086
1087 test_raw_encryption!(
1088 test_aes128gcm_raw_encryption,
1089 CipherMode::Aes128Gcm,
1090 generate_random_hex_key_128,
1091 12,
1092 "Hello, AES-128-GCM!"
1093 );
1094
1095 #[test]
1096 fn test_page_1_encrypt_decrypt_round_trip_with_ad() {
1097 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1098 let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1099 .unwrap();
1100
1101 let page_data = create_test_page_1();
1102 let encrypted = ctx.encrypt_page(&page_data, 1).unwrap();
1103 assert_ne!(
1104 &page_data[0..DatabaseHeader::SIZE],
1105 &encrypted[0..DatabaseHeader::SIZE],
1106 "Encrypted data should be different from the page data"
1107 );
1108 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1109
1110 assert_eq!(&encrypted[..5], b"Turso");
1112 assert_eq!(encrypted[5], TURSO_VERSION);
1113 assert_eq!(encrypted[6], CipherMode::Aegis256.cipher_id());
1114
1115 assert_eq!(&encrypted[16..100], &page_data[16..100]); assert_ne!(&encrypted[100..200], &page_data[100..200]); let decrypted = ctx.decrypt_page(&encrypted, 1).unwrap();
1121 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1122
1123 assert_eq!(&decrypted[..SQLITE_HEADER.len()], SQLITE_HEADER);
1125 assert_eq!(decrypted, page_data);
1126 }
1127
1128 #[test]
1129 fn test_turso_header_validation() {
1130 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1131 let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1132 .unwrap();
1133
1134 assert_eq!(CipherMode::Aes128Gcm.cipher_id(), 1);
1136 assert_eq!(CipherMode::Aes256Gcm.cipher_id(), 2);
1137 assert_eq!(CipherMode::Aegis256.cipher_id(), 3);
1138 assert_eq!(CipherMode::Aegis128L.cipher_id(), 6);
1139
1140 assert_eq!(
1142 CipherMode::from_cipher_id(1).unwrap(),
1143 CipherMode::Aes128Gcm
1144 );
1145 assert_eq!(CipherMode::from_cipher_id(3).unwrap(), CipherMode::Aegis256);
1146 assert!(CipherMode::from_cipher_id(99).is_err());
1147
1148 let header = ctx.create_turso_header();
1150 assert_eq!(&header[..5], b"Turso");
1151 assert_eq!(header[5], TURSO_VERSION);
1152 assert_eq!(header[6], 3); assert_eq!(&header[7..], &[0u8; 9]); }
1155
1156 #[test]
1157 fn test_invalid_turso_header_fails_decrypt() {
1158 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1159 let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1160 .unwrap();
1161
1162 let page_data = create_test_page_1();
1163 let encrypted = ctx.encrypt_page(&page_data, 1).unwrap();
1164
1165 let mut corrupted = encrypted.clone();
1167 corrupted[0] = b'V'; assert!(ctx.decrypt_page(&corrupted, 1).is_err());
1169
1170 let mut wrong_cipher = encrypted;
1172 wrong_cipher[6] = 99; assert!(ctx.decrypt_page(&wrong_cipher, 1).is_err());
1174 }
1175
1176 #[test]
1177 fn test_associated_data_validation() {
1178 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1179 let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1180 .unwrap();
1181
1182 let page_data = create_test_page_1();
1183 let encrypted = ctx.encrypt_page(&page_data, 1).unwrap();
1184
1185 let mut corrupted_ad = encrypted;
1187 corrupted_ad[50] ^= 1; let decrypt_result = ctx.decrypt_page(&corrupted_ad, 1);
1191 assert!(
1192 decrypt_result.is_err(),
1193 "Decryption should fail with corrupted associated data"
1194 );
1195 }
1196
1197 #[test]
1198 fn test_turso_header_corruption_detection() {
1199 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1200 let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1201 .unwrap();
1202
1203 let page_data = create_test_page_1();
1204 let encrypted = ctx.encrypt_page(&page_data, 1).unwrap();
1205
1206 let mut corrupted_turso_header = encrypted;
1207 corrupted_turso_header[7] ^= 1;
1208
1209 let decrypt_result = ctx.decrypt_page(&corrupted_turso_header, 1);
1210 assert!(
1211 decrypt_result.is_err(),
1212 "Decryption should fail with corrupted Turso header"
1213 );
1214 }
1215
1216 #[test]
1217 fn test_aes128gcm_encrypt_decrypt_round_trip() {
1218 let mut rng = rand::rng();
1219 let cipher_mode = CipherMode::Aes128Gcm;
1220 let metadata_size = cipher_mode.metadata_size();
1221 let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;
1222
1223 let page_data = {
1224 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1225 page.iter_mut()
1226 .take(data_size)
1227 .for_each(|byte| *byte = rng.random());
1228 page
1229 };
1230
1231 let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap();
1232 let ctx = EncryptionContext::new(CipherMode::Aes128Gcm, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1233 .unwrap();
1234
1235 let page_id = 42;
1236 let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();
1237 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1238 assert_ne!(&encrypted[..data_size], &page_data[..data_size]);
1239 assert_ne!(&encrypted[..], &page_data[..]);
1240
1241 let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();
1242 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1243 assert_eq!(decrypted, page_data);
1244 }
1245
1246 #[test]
1247 fn test_aes_encrypt_decrypt_round_trip() {
1248 let mut rng = rand::rng();
1249 let cipher_mode = CipherMode::Aes256Gcm;
1250 let metadata_size = cipher_mode.metadata_size();
1251 let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;
1252
1253 let page_data = {
1254 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1255 page.iter_mut()
1256 .take(data_size)
1257 .for_each(|byte| *byte = rng.random());
1258 page
1259 };
1260
1261 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1262 let ctx = EncryptionContext::new(CipherMode::Aes256Gcm, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1263 .unwrap();
1264
1265 let page_id = 42;
1266 let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();
1267 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1268 assert_ne!(&encrypted[..data_size], &page_data[..data_size]);
1269 assert_ne!(&encrypted[..], &page_data[..]);
1270
1271 let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();
1272 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1273 assert_eq!(decrypted, page_data);
1274 }
1275
1276 test_cipher_wrapper!(
1277 test_aegis256_cipher_wrapper,
1278 Aegis256Cipher,
1279 generate_random_hex_key,
1280 32,
1281 "Hello, AEGIS-256!"
1282 );
1283
1284 test_raw_encryption!(
1285 test_aegis256_raw_encryption,
1286 CipherMode::Aegis256,
1287 generate_random_hex_key,
1288 32,
1289 "Hello, AEGIS-256!"
1290 );
1291
1292 #[test]
1293 fn test_aegis256_encrypt_decrypt_round_trip() {
1294 let mut rng = rand::rng();
1295 let cipher_mode = CipherMode::Aegis256;
1296 let metadata_size = cipher_mode.metadata_size();
1297 let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;
1298
1299 let page_data = {
1300 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1301 page.iter_mut()
1302 .take(data_size)
1303 .for_each(|byte| *byte = rng.random());
1304 page
1305 };
1306
1307 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1308 let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1309 .unwrap();
1310
1311 let page_id = 42;
1312 let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();
1313 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1314 assert_ne!(&encrypted[..data_size], &page_data[..data_size]);
1315
1316 let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();
1317 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1318 assert_eq!(decrypted, page_data);
1319 }
1320
1321 test_cipher_wrapper!(
1322 test_aegis128x2_cipher_wrapper,
1323 Aegis128X2Cipher,
1324 generate_random_hex_key_128,
1325 16,
1326 "Hello, AEGIS-128X2!"
1327 );
1328
1329 test_raw_encryption!(
1330 test_aegis128x2_raw_encryption,
1331 CipherMode::Aegis128X2,
1332 generate_random_hex_key_128,
1333 16,
1334 "Hello, AEGIS-128X2!"
1335 );
1336
1337 #[test]
1338 fn test_aegis128x2_encrypt_decrypt_round_trip() {
1339 let mut rng = rand::rng();
1340 let cipher_mode = CipherMode::Aegis128X2;
1341 let metadata_size = cipher_mode.metadata_size();
1342 let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;
1343
1344 let page_data = {
1345 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1346 page.iter_mut()
1347 .take(data_size)
1348 .for_each(|byte| *byte = rng.random());
1349 page
1350 };
1351
1352 let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap();
1353 let ctx = EncryptionContext::new(CipherMode::Aegis128X2, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1354 .unwrap();
1355
1356 let page_id = 42;
1357 let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();
1358 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1359 assert_ne!(&encrypted[..data_size], &page_data[..data_size]);
1360
1361 let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();
1362 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1363 assert_eq!(decrypted, page_data);
1364 }
1365
1366 test_cipher_wrapper!(
1367 test_aegis128l_cipher_wrapper,
1368 Aegis128LCipher,
1369 generate_random_hex_key_128,
1370 16,
1371 "Hello, AEGIS-128L!"
1372 );
1373
1374 test_raw_encryption!(
1375 test_aegis128l_raw_encryption,
1376 CipherMode::Aegis128L,
1377 generate_random_hex_key_128,
1378 16,
1379 "Hello, AEGIS-128L!"
1380 );
1381
1382 #[test]
1383 fn test_aegis128l_encrypt_decrypt_round_trip() {
1384 let mut rng = rand::rng();
1385 let cipher_mode = CipherMode::Aegis128L;
1386 let metadata_size = cipher_mode.metadata_size();
1387 let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;
1388
1389 let page_data = {
1390 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1391 page.iter_mut()
1392 .take(data_size)
1393 .for_each(|byte| *byte = rng.random());
1394 page
1395 };
1396
1397 let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap();
1398 let ctx = EncryptionContext::new(CipherMode::Aegis128L, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1399 .unwrap();
1400
1401 let page_id = 42;
1402 let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();
1403 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1404 assert_ne!(&encrypted[..data_size], &page_data[..data_size]);
1405
1406 let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();
1407 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1408 assert_eq!(decrypted, page_data);
1409 }
1410
1411 test_cipher_wrapper!(
1412 test_aegis128x4_cipher_wrapper,
1413 Aegis128X4Cipher,
1414 generate_random_hex_key_128,
1415 16,
1416 "Hello, AEGIS-128X4!"
1417 );
1418
1419 test_raw_encryption!(
1420 test_aegis128x4_raw_encryption,
1421 CipherMode::Aegis128X4,
1422 generate_random_hex_key_128,
1423 16,
1424 "Hello, AEGIS-128X4!"
1425 );
1426
1427 #[test]
1428 fn test_aegis128x4_encrypt_decrypt_round_trip() {
1429 let mut rng = rand::rng();
1430 let cipher_mode = CipherMode::Aegis128X4;
1431 let metadata_size = cipher_mode.metadata_size();
1432 let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;
1433
1434 let page_data = {
1435 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1436 page.iter_mut()
1437 .take(data_size)
1438 .for_each(|byte| *byte = rng.random());
1439 page
1440 };
1441
1442 let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap();
1443 let ctx = EncryptionContext::new(CipherMode::Aegis128X4, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1444 .unwrap();
1445
1446 let page_id = 42;
1447 let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();
1448 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1449 assert_ne!(&encrypted[..data_size], &page_data[..data_size]);
1450
1451 let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();
1452 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1453 assert_eq!(decrypted, page_data);
1454 }
1455
1456 test_cipher_wrapper!(
1457 test_aegis256x2_cipher_wrapper,
1458 Aegis256X2Cipher,
1459 generate_random_hex_key,
1460 32,
1461 "Hello, AEGIS-256X2!"
1462 );
1463
1464 test_raw_encryption!(
1465 test_aegis256x2_raw_encryption,
1466 CipherMode::Aegis256X2,
1467 generate_random_hex_key,
1468 32,
1469 "Hello, AEGIS-256X2!"
1470 );
1471
1472 #[test]
1473 fn test_aegis256x2_encrypt_decrypt_round_trip() {
1474 let mut rng = rand::rng();
1475 let cipher_mode = CipherMode::Aegis256X2;
1476 let metadata_size = cipher_mode.metadata_size();
1477 let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;
1478
1479 let page_data = {
1480 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1481 page.iter_mut()
1482 .take(data_size)
1483 .for_each(|byte| *byte = rng.random());
1484 page
1485 };
1486
1487 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1488 let ctx = EncryptionContext::new(CipherMode::Aegis256X2, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1489 .unwrap();
1490
1491 let page_id = 42;
1492 let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();
1493 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1494 assert_ne!(&encrypted[..data_size], &page_data[..data_size]);
1495
1496 let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();
1497 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1498 assert_eq!(decrypted, page_data);
1499 }
1500
1501 test_cipher_wrapper!(
1502 test_aegis256x4_cipher_wrapper,
1503 Aegis256X4Cipher,
1504 generate_random_hex_key,
1505 32,
1506 "Hello, AEGIS-256X4!"
1507 );
1508
1509 test_raw_encryption!(
1510 test_aegis256x4_raw_encryption,
1511 CipherMode::Aegis256X4,
1512 generate_random_hex_key,
1513 32,
1514 "Hello, AEGIS-256X4!"
1515 );
1516
1517 #[test]
1518 fn test_aegis256x4_encrypt_decrypt_round_trip() {
1519 let mut rng = rand::rng();
1520 let cipher_mode = CipherMode::Aegis256X4;
1521 let metadata_size = cipher_mode.metadata_size();
1522 let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size;
1523
1524 let page_data = {
1525 let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE];
1526 page.iter_mut()
1527 .take(data_size)
1528 .for_each(|byte| *byte = rng.random());
1529 page
1530 };
1531
1532 let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap();
1533 let ctx = EncryptionContext::new(CipherMode::Aegis256X4, &key, DEFAULT_ENCRYPTED_PAGE_SIZE)
1534 .unwrap();
1535
1536 let page_id = 42;
1537 let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap();
1538 assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1539 assert_ne!(&encrypted[..data_size], &page_data[..data_size]);
1540
1541 let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap();
1542 assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE);
1543 assert_eq!(decrypted, page_data);
1544 }
1545
1546 #[test]
1547 fn test_cipher_mode_string_parsing() {
1548 let mode = CipherMode::try_from("aes128gcm").unwrap();
1550 assert_eq!(mode, CipherMode::Aes128Gcm);
1551 assert_eq!(mode.to_string(), "aes128gcm");
1552 assert_eq!(mode.required_key_size(), 16);
1553 assert_eq!(mode.nonce_size(), 12);
1554 assert_eq!(mode.tag_size(), 16);
1555
1556 let mode = CipherMode::try_from("aes-128-gcm").unwrap();
1557 assert_eq!(mode, CipherMode::Aes128Gcm);
1558
1559 let mode = CipherMode::try_from("aes_128_gcm").unwrap();
1560 assert_eq!(mode, CipherMode::Aes128Gcm);
1561
1562 let mode = CipherMode::try_from("aes256gcm").unwrap();
1564 assert_eq!(mode, CipherMode::Aes256Gcm);
1565 assert_eq!(mode.to_string(), "aes256gcm");
1566 assert_eq!(mode.required_key_size(), 32);
1567 assert_eq!(mode.nonce_size(), 12);
1568
1569 let mode = CipherMode::try_from("aegis128x2").unwrap();
1571 assert_eq!(mode, CipherMode::Aegis128X2);
1572 assert_eq!(mode.to_string(), "aegis128x2");
1573 assert_eq!(mode.required_key_size(), 16);
1574 assert_eq!(mode.nonce_size(), 16);
1575 assert_eq!(mode.tag_size(), 16);
1576
1577 let mode = CipherMode::try_from("aegis-128x2").unwrap();
1578 assert_eq!(mode, CipherMode::Aegis128X2);
1579
1580 let mode = CipherMode::try_from("aegis_128x2").unwrap();
1581 assert_eq!(mode, CipherMode::Aegis128X2);
1582
1583 let mode = CipherMode::try_from("aegis128l").unwrap();
1585 assert_eq!(mode, CipherMode::Aegis128L);
1586 assert_eq!(mode.to_string(), "aegis128l");
1587 assert_eq!(mode.required_key_size(), 16);
1588 assert_eq!(mode.nonce_size(), 16);
1589
1590 let mode = CipherMode::try_from("aegis128x4").unwrap();
1592 assert_eq!(mode, CipherMode::Aegis128X4);
1593 assert_eq!(mode.to_string(), "aegis128x4");
1594 assert_eq!(mode.required_key_size(), 16);
1595 assert_eq!(mode.nonce_size(), 16);
1596
1597 let mode = CipherMode::try_from("aegis256x2").unwrap();
1599 assert_eq!(mode, CipherMode::Aegis256X2);
1600 assert_eq!(mode.to_string(), "aegis256x2");
1601 assert_eq!(mode.required_key_size(), 32);
1602 assert_eq!(mode.nonce_size(), 32);
1603
1604 let mode = CipherMode::try_from("aegis256x4").unwrap();
1606 assert_eq!(mode, CipherMode::Aegis256X4);
1607 assert_eq!(mode.to_string(), "aegis256x4");
1608 assert_eq!(mode.required_key_size(), 32);
1609 assert_eq!(mode.nonce_size(), 32);
1610 }
1611}