diff --git a/core/lib.rs b/core/lib.rs index 373e89c81..4802c0d1c 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -2038,16 +2038,16 @@ impl Connection { self.syms.borrow().vtab_modules.keys().cloned().collect() } - pub fn set_encryption_key(&self, key: EncryptionKey) { + pub fn set_encryption_key(&self, key: EncryptionKey) -> Result<()> { tracing::trace!("setting encryption key for connection"); *self.encryption_key.borrow_mut() = Some(key.clone()); - self.set_encryption_context(); + self.set_encryption_context() } - pub fn set_encryption_cipher(&self, cipher_mode: CipherMode) { + pub fn set_encryption_cipher(&self, cipher_mode: CipherMode) -> Result<()> { tracing::trace!("setting encryption cipher for connection"); self.encryption_cipher_mode.replace(Some(cipher_mode)); - self.set_encryption_context(); + self.set_encryption_context() } pub fn get_encryption_cipher_mode(&self) -> Option { @@ -2055,17 +2055,17 @@ impl Connection { } // if both key and cipher are set, set encryption context on pager - fn set_encryption_context(&self) { + fn set_encryption_context(&self) -> Result<()> { let key_ref = self.encryption_key.borrow(); let Some(key) = key_ref.as_ref() else { - return; + return Ok(()); }; let Some(cipher_mode) = self.encryption_cipher_mode.get() else { - return; + return Ok(()); }; tracing::trace!("setting encryption ctx for connection"); let pager = self.pager.borrow(); - pager.set_encryption_context(cipher_mode, key); + pager.set_encryption_context(cipher_mode, key) } } diff --git a/core/storage/encryption.rs b/core/storage/encryption.rs index 9bebd0f56..7980daf42 100644 --- a/core/storage/encryption.rs +++ b/core/storage/encryption.rs @@ -1,56 +1,80 @@ #![allow(unused_variables, dead_code)] use crate::{LimboError, Result}; +use aegis::aegis128l::Aegis128L; +use aegis::aegis128x2::Aegis128X2; +use aegis::aegis128x4::Aegis128X4; use aegis::aegis256::Aegis256; -use aes_gcm::aead::{AeadCore, OsRng}; -use std::ops::Deref; +use aegis::aegis256x2::Aegis256X2; +use aegis::aegis256x4::Aegis256X4; +use aes_gcm::{ + aead::{Aead, AeadCore, KeyInit, OsRng}, + Aes128Gcm, Aes256Gcm, Key, Nonce, +}; use turso_macros::match_ignore_ascii_case; -// AEGIS-256 supports both 16 and 32 byte tags, we use the 16 byte variant, it is faster -// and provides sufficient security for our use case. -const AEGIS_TAG_SIZE: usize = 16; -const AES256GCM_TAG_SIZE: usize = 16; -#[repr(transparent)] #[derive(Clone)] -pub struct EncryptionKey([u8; 32]); +pub enum EncryptionKey { + Key128([u8; 16]), + Key256([u8; 32]), +} impl EncryptionKey { - pub fn new(key: [u8; 32]) -> Self { - Self(key) + pub fn new_256(key: [u8; 32]) -> Self { + Self::Key256(key) + } + + pub fn new_128(key: [u8; 16]) -> Self { + Self::Key128(key) } pub fn from_hex_string(s: &str) -> Result { let hex_str = s.trim(); let bytes = hex::decode(hex_str) .map_err(|e| LimboError::InvalidArgument(format!("Invalid hex string: {e}")))?; - let key: [u8; 32] = bytes.try_into().map_err(|v: Vec| { - LimboError::InvalidArgument(format!( - "Hex string must decode to exactly 32 bytes, got {}", - v.len() - )) - })?; - Ok(Self(key)) - } - pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 + match bytes.len() { + 16 => { + let key: [u8; 16] = bytes.try_into().unwrap(); + Ok(Self::Key128(key)) + } + 32 => { + let key: [u8; 32] = bytes.try_into().unwrap(); + Ok(Self::Key256(key)) + } + _ => Err(LimboError::InvalidArgument(format!( + "Hex string must decode to exactly 16 or 32 bytes, got {}", + bytes.len() + ))), + } } pub fn as_slice(&self) -> &[u8] { - &self.0 + match self { + Self::Key128(key) => key, + Self::Key256(key) => key, + } } -} -impl Deref for EncryptionKey { - type Target = [u8; 32]; - - fn deref(&self) -> &Self::Target { - &self.0 + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + match self { + Self::Key128(_) => 16, + Self::Key256(_) => 32, + } } -} -impl AsRef<[u8; 32]> for EncryptionKey { - fn as_ref(&self) -> &[u8; 32] { - &self.0 + pub fn as_128(&self) -> Option<&[u8; 16]> { + match self { + Self::Key128(key) => Some(key), + _ => None, + } + } + + pub fn as_256(&self) -> Option<&[u8; 32]> { + match self { + Self::Key256(key) => Some(key), + _ => None, + } } } @@ -65,203 +89,180 @@ impl std::fmt::Debug for EncryptionKey { impl Drop for EncryptionKey { fn drop(&mut self) { // securely zero out the key bytes before dropping - for byte in self.0.iter_mut() { - unsafe { - std::ptr::write_volatile(byte, 0); + match self { + Self::Key128(key) => { + for byte in key.iter_mut() { + unsafe { + std::ptr::write_volatile(byte, 0); + } + } + } + Self::Key256(key) => { + for byte in key.iter_mut() { + unsafe { + std::ptr::write_volatile(byte, 0); + } + } } } } } -pub trait AeadCipher { - fn encrypt(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec, Vec)>; - fn decrypt(&self, ciphertext: &[u8], nonce: &[u8], ad: &[u8]) -> Result>; - - fn encrypt_detached(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec, Vec, Vec)>; - - fn decrypt_detached( - &self, - ciphertext: &[u8], - nonce: &[u8], - tag: &[u8], - ad: &[u8], - ) -> Result>; -} - -// wrapper struct for AEGIS-256 cipher, because the crate we use is a bit low-level and we add -// some nice abstractions here -// note, the AEGIS has many variants and support for hardware acceleration. Here we just use the -// vanilla version, which is still order of magnitudes faster than AES-GCM in software. Hardware -// based compilation is left for future work. -#[derive(Clone)] -pub struct Aegis256Cipher { - key: EncryptionKey, -} - -impl Aegis256Cipher { - fn new(key: &EncryptionKey) -> Self { - Self { key: key.clone() } - } -} - -impl AeadCipher for Aegis256Cipher { - fn encrypt(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec, Vec)> { - let nonce = generate_secure_nonce(); - let (ciphertext, tag) = - Aegis256::::new(self.key.as_bytes(), &nonce).encrypt(plaintext, ad); - - let mut result = ciphertext; - result.extend_from_slice(&tag); - Ok((result, nonce.to_vec())) - } - - fn decrypt(&self, ciphertext: &[u8], nonce: &[u8], ad: &[u8]) -> Result> { - if ciphertext.len() < AEGIS_TAG_SIZE { - return Err(LimboError::InternalError("Ciphertext too short".into())); +macro_rules! define_aegis_cipher { + ($struct_name:ident, $cipher_type:ty, key128, $nonce_size:literal, $name:literal) => { + define_aegis_cipher!(@impl $struct_name, $cipher_type, $nonce_size, $name, 16, as_128); + }; + ($struct_name:ident, $cipher_type:ty, key256, $nonce_size:literal, $name:literal) => { + define_aegis_cipher!(@impl $struct_name, $cipher_type, $nonce_size, $name, 32, as_256); + }; + (@impl $struct_name:ident, $cipher_type:ty, $nonce_size:literal, $name:literal, $key_size:literal, $key_method:ident) => { + #[derive(Clone)] + pub struct $struct_name { + key: EncryptionKey, } - let (ct, tag) = ciphertext.split_at(ciphertext.len() - AEGIS_TAG_SIZE); - let tag_array: [u8; AEGIS_TAG_SIZE] = tag.try_into().map_err(|_| { - LimboError::InternalError(format!("Invalid tag size for AEGIS-256 {AEGIS_TAG_SIZE}")) - })?; - let nonce_array: [u8; 32] = nonce - .try_into() - .map_err(|_| LimboError::InternalError("Invalid nonce size for AEGIS-256".into()))?; + impl $struct_name { + const TAG_SIZE: usize = 16; - Aegis256::::new(self.key.as_bytes(), &nonce_array) - .decrypt(ct, &tag_array, ad) - .map_err(|_| LimboError::InternalError("AEGIS-256 decryption failed".into())) - } + fn new(key: &EncryptionKey) -> Self { + Self { key: key.clone() } + } - fn encrypt_detached(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec, Vec, Vec)> { - let nonce = generate_secure_nonce(); - let (ciphertext, tag) = - Aegis256::::new(self.key.as_bytes(), &nonce).encrypt(plaintext, ad); + fn encrypt(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec, [u8; $nonce_size])> { + let nonce = generate_secure_nonce::<$nonce_size>(); + let key_bytes = self.key.$key_method() + .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?; + let (ciphertext, tag) = <$cipher_type>::new(key_bytes, &nonce).encrypt(plaintext, ad); + let mut result = ciphertext; + result.extend_from_slice(&tag); + Ok((result, nonce)) + } - Ok((ciphertext, tag.to_vec(), nonce.to_vec())) - } + fn decrypt(&self, ciphertext: &[u8], nonce: &[u8; $nonce_size], ad: &[u8]) -> Result> { + if ciphertext.len() < Self::TAG_SIZE { + return Err(LimboError::from(CipherError::CiphertextTooShort { cipher: $name })); + } + let (ct, tag) = ciphertext.split_at(ciphertext.len() - Self::TAG_SIZE); + let tag_array: [u8; 16] = tag.try_into().map_err(|_| -> LimboError { CipherError::InvalidTagSize { cipher: $name }.into() })?; - fn decrypt_detached( - &self, - ciphertext: &[u8], - nonce: &[u8], - tag: &[u8], - ad: &[u8], - ) -> Result> { - let tag_array: [u8; AEGIS_TAG_SIZE] = tag.try_into().map_err(|_| { - LimboError::InternalError(format!("Invalid tag size for AEGIS-256 {AEGIS_TAG_SIZE}")) - })?; - let nonce_array: [u8; 32] = nonce - .try_into() - .map_err(|_| LimboError::InternalError("Invalid nonce size for AEGIS-256".into()))?; - - Aegis256::::new(self.key.as_bytes(), &nonce_array) - .decrypt(ciphertext, &tag_array, ad) - .map_err(|_| LimboError::InternalError("AEGIS-256 decrypt_detached failed".into())) - } -} - -#[derive(Clone)] -pub struct Aes256GcmCipher { - key: EncryptionKey, -} - -impl Aes256GcmCipher { - fn new(key: &EncryptionKey) -> Self { - Self { key: key.clone() } - } -} - -impl AeadCipher for Aes256GcmCipher { - fn encrypt(&self, plaintext: &[u8], _ad: &[u8]) -> Result<(Vec, Vec)> { - use aes_gcm::aead::{AeadInPlace, KeyInit}; - use aes_gcm::Aes256Gcm; - - let cipher = Aes256Gcm::new_from_slice(self.key.as_bytes()) - .map_err(|_| LimboError::InternalError("Bad AES key".into()))?; - let nonce = Aes256Gcm::generate_nonce(&mut rand::thread_rng()); - let mut buffer = plaintext.to_vec(); - - let tag = cipher - .encrypt_in_place_detached(&nonce, b"", &mut buffer) - .map_err(|_| LimboError::InternalError("AES-GCM encrypt failed".into()))?; - - buffer.extend_from_slice(&tag[..AES256GCM_TAG_SIZE]); - Ok((buffer, nonce.to_vec())) - } - - fn decrypt(&self, ciphertext: &[u8], nonce: &[u8], ad: &[u8]) -> Result> { - use aes_gcm::aead::{AeadInPlace, KeyInit}; - use aes_gcm::{Aes256Gcm, Nonce}; - - if ciphertext.len() < AES256GCM_TAG_SIZE { - return Err(LimboError::InternalError("Ciphertext too short".into())); + let key_bytes = self.key.$key_method() + .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?; + <$cipher_type>::new(key_bytes, nonce) + .decrypt(ct, &tag_array, ad) + .map_err(|_| -> LimboError { CipherError::DecryptionFailed { cipher: $name }.into() }) + } } - let (ct, tag) = ciphertext.split_at(ciphertext.len() - AES256GCM_TAG_SIZE); - let cipher = Aes256Gcm::new_from_slice(self.key.as_bytes()) - .map_err(|_| LimboError::InternalError("Bad AES key".into()))?; - let nonce = Nonce::from_slice(nonce); - - let mut buffer = ct.to_vec(); - cipher - .decrypt_in_place_detached(nonce, ad, &mut buffer, tag.into()) - .map_err(|_| LimboError::InternalError("AES-GCM decrypt failed".into()))?; - - Ok(buffer) - } - - fn encrypt_detached(&self, plaintext: &[u8], ad: &[u8]) -> Result<(Vec, Vec, Vec)> { - use aes_gcm::aead::{AeadInPlace, KeyInit}; - use aes_gcm::Aes256Gcm; - - let cipher = Aes256Gcm::new_from_slice(self.key.as_bytes()) - .map_err(|_| LimboError::InternalError("Bad AES key".into()))?; - let nonce = Aes256Gcm::generate_nonce(&mut rand::thread_rng()); - - let mut buffer = plaintext.to_vec(); - let tag = cipher - .encrypt_in_place_detached(&nonce, ad, &mut buffer) - .map_err(|_| LimboError::InternalError("AES-GCM encrypt_detached failed".into()))?; - - Ok((buffer, nonce.to_vec(), tag.to_vec())) - } - - fn decrypt_detached( - &self, - ciphertext: &[u8], - nonce: &[u8], - tag: &[u8], - ad: &[u8], - ) -> Result> { - use aes_gcm::aead::{AeadInPlace, KeyInit}; - use aes_gcm::{Aes256Gcm, Nonce}; - - let cipher = Aes256Gcm::new_from_slice(self.key.as_bytes()) - .map_err(|_| LimboError::InternalError("Bad AES key".into()))?; - let nonce = Nonce::from_slice(nonce); - - let mut buffer = ciphertext.to_vec(); - cipher - .decrypt_in_place_detached(nonce, ad, &mut buffer, tag.into()) - .map_err(|_| LimboError::InternalError("AES-GCM decrypt_detached failed".into()))?; - - Ok(buffer) - } + impl std::fmt::Debug for $struct_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(stringify!($struct_name)) + .field("key", &"") + .finish() + } + } + }; } -impl std::fmt::Debug for Aegis256Cipher { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Aegis256Cipher") - .field("key", &"") - .finish() - } +macro_rules! define_aes_gcm_cipher { + ($struct_name:ident, $cipher_type:ty, key128, $name:literal) => { + define_aes_gcm_cipher!(@impl $struct_name, $cipher_type, $name, 16, as_128); + }; + ($struct_name:ident, $cipher_type:ty, key256, $name:literal) => { + define_aes_gcm_cipher!(@impl $struct_name, $cipher_type, $name, 32, as_256); + }; + (@impl $struct_name:ident, $cipher_type:ty, $name:literal, $key_size:literal, $key_method:ident) => { + #[derive(Clone)] + pub struct $struct_name { + cipher: $cipher_type, + } + + impl $struct_name { + const TAG_SIZE: usize = 16; + const NONCE_SIZE: usize = 12; + + fn new(key: &EncryptionKey) -> Result { + let key_bytes = key.$key_method() + .ok_or_else(|| -> LimboError { CipherError::InvalidKeySize { cipher: $name, expected: $key_size }.into() })?; + let cipher_key: &Key<$cipher_type> = key_bytes.into(); + Ok(Self { + cipher: <$cipher_type>::new(cipher_key), + }) + } + + fn encrypt(&self, plaintext: &[u8], _ad: &[u8]) -> Result<(Vec, [u8; 12])> { + let nonce = <$cipher_type>::generate_nonce(&mut OsRng); + let ciphertext = self.cipher.encrypt(&nonce, plaintext).map_err(|e| { + LimboError::InternalError(format!("{} encryption failed: {e:?}", $name)) + })?; + let mut nonce_array = [0u8; 12]; + nonce_array.copy_from_slice(&nonce); + Ok((ciphertext, nonce_array)) + } + + fn decrypt(&self, ciphertext: &[u8], nonce: &[u8; 12], _ad: &[u8]) -> Result> { + let nonce = Nonce::from_slice(nonce); + self.cipher + .decrypt(nonce, ciphertext) + .map_err(|_| -> LimboError { CipherError::DecryptionFailed { cipher: $name }.into() }) + } + } + + impl std::fmt::Debug for $struct_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(stringify!($struct_name)) + .field("key", &"") + .finish() + } + } + }; } +// AES-GCM ciphers +define_aes_gcm_cipher!(Aes128GcmCipher, Aes128Gcm, key128, "AES-128-GCM"); +define_aes_gcm_cipher!(Aes256GcmCipher, Aes256Gcm, key256, "AES-256-GCM"); + +// AEGIS ciphers +define_aegis_cipher!(Aegis256Cipher, Aegis256::<16>, key256, 32, "AEGIS-256"); +define_aegis_cipher!( + Aegis256X2Cipher, + Aegis256X2::<16>, + key256, + 32, + "AEGIS-256X2" +); +define_aegis_cipher!( + Aegis256X4Cipher, + Aegis256X4::<16>, + key256, + 32, + "AEGIS-256X4" +); +define_aegis_cipher!( + Aegis128X2Cipher, + Aegis128X2::<16>, + key128, + 16, + "AEGIS-128X2" +); +define_aegis_cipher!(Aegis128LCipher, Aegis128L::<16>, key128, 16, "AEGIS-128L"); +define_aegis_cipher!( + Aegis128X4Cipher, + Aegis128X4::<16>, + key128, + 16, + "AEGIS-128X4" +); + #[derive(Debug, Clone, Copy, PartialEq)] pub enum CipherMode { + Aes128Gcm, Aes256Gcm, Aegis256, + Aegis128L, + Aegis128X2, + Aegis128X4, + Aegis256X2, + Aegis256X4, } impl TryFrom<&str> for CipherMode { @@ -270,8 +271,14 @@ impl TryFrom<&str> for CipherMode { fn try_from(s: &str) -> Result { let s_bytes = s.as_bytes(); match_ignore_ascii_case!(match s_bytes { + b"aes128gcm" | b"aes-128-gcm" | b"aes_128_gcm" => Ok(CipherMode::Aes128Gcm), b"aes256gcm" | b"aes-256-gcm" | b"aes_256_gcm" => Ok(CipherMode::Aes256Gcm), b"aegis256" | b"aegis-256" | b"aegis_256" => Ok(CipherMode::Aegis256), + b"aegis128l" | b"aegis-128l" | b"aegis_128l" => Ok(CipherMode::Aegis128L), + b"aegis128x2" | b"aegis-128x2" | b"aegis_128x2" => Ok(CipherMode::Aegis128X2), + b"aegis128x4" | b"aegis-128x4" | b"aegis_128x4" => Ok(CipherMode::Aegis128X4), + b"aegis256x2" | b"aegis-256x2" | b"aegis_256x2" => Ok(CipherMode::Aegis256X2), + b"aegis256x4" | b"aegis-256x4" | b"aegis_256x4" => Ok(CipherMode::Aegis256X4), _ => Err(LimboError::InvalidArgument(format!( "Unknown cipher name: {s}" ))), @@ -282,8 +289,14 @@ impl TryFrom<&str> for CipherMode { impl std::fmt::Display for CipherMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + CipherMode::Aes128Gcm => write!(f, "aes128gcm"), CipherMode::Aes256Gcm => write!(f, "aes256gcm"), CipherMode::Aegis256 => write!(f, "aegis256"), + CipherMode::Aegis128L => write!(f, "aegis128l"), + CipherMode::Aegis128X2 => write!(f, "aegis128x2"), + CipherMode::Aegis128X4 => write!(f, "aegis128x4"), + CipherMode::Aegis256X2 => write!(f, "aegis256x2"), + CipherMode::Aegis256X4 => write!(f, "aegis256x4"), } } } @@ -293,24 +306,42 @@ impl CipherMode { /// For 128-bit algorithms, it would be 16 bytes, etc. pub fn required_key_size(&self) -> usize { match self { + CipherMode::Aes128Gcm => 16, CipherMode::Aes256Gcm => 32, CipherMode::Aegis256 => 32, + CipherMode::Aegis256X2 => 32, + CipherMode::Aegis256X4 => 32, + CipherMode::Aegis128L => 16, + CipherMode::Aegis128X2 => 16, + CipherMode::Aegis128X4 => 16, } } /// Returns the nonce size for this cipher mode. pub fn nonce_size(&self) -> usize { match self { + CipherMode::Aes128Gcm => 12, CipherMode::Aes256Gcm => 12, CipherMode::Aegis256 => 32, + CipherMode::Aegis256X2 => 32, + CipherMode::Aegis256X4 => 32, + CipherMode::Aegis128L => 16, + CipherMode::Aegis128X2 => 16, + CipherMode::Aegis128X4 => 16, } } /// Returns the authentication tag size for this cipher mode. pub fn tag_size(&self) -> usize { match self { - CipherMode::Aes256Gcm => AES256GCM_TAG_SIZE, - CipherMode::Aegis256 => AEGIS_TAG_SIZE, + CipherMode::Aes128Gcm => 16, + CipherMode::Aes256Gcm => 16, + CipherMode::Aegis256 => 16, + CipherMode::Aegis256X2 => 16, + CipherMode::Aegis256X4 => 16, + CipherMode::Aegis128L => 16, + CipherMode::Aegis128X2 => 16, + CipherMode::Aegis128X4 => 16, } } @@ -322,24 +353,27 @@ impl CipherMode { #[derive(Clone)] pub enum Cipher { - Aes256Gcm(Aes256GcmCipher), - Aegis256(Aegis256Cipher), -} - -impl Cipher { - fn as_aead(&self) -> &dyn AeadCipher { - match self { - Cipher::Aes256Gcm(c) => c, - Cipher::Aegis256(c) => c, - } - } + Aes128Gcm(Box), + Aes256Gcm(Box), + Aegis256(Box), + Aegis256X2(Box), + Aegis256X4(Box), + Aegis128L(Box), + Aegis128X2(Box), + Aegis128X4(Box), } impl std::fmt::Debug for Cipher { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Cipher::Aes128Gcm(_) => write!(f, "Cipher::Aes128Gcm"), Cipher::Aes256Gcm(_) => write!(f, "Cipher::Aes256Gcm"), Cipher::Aegis256(_) => write!(f, "Cipher::Aegis256"), + Cipher::Aegis256X2(_) => write!(f, "Cipher::Aegis256X2"), + Cipher::Aegis256X4(_) => write!(f, "Cipher::Aegis256X4"), + Cipher::Aegis128L(_) => write!(f, "Cipher::Aegis128L"), + Cipher::Aegis128X2(_) => write!(f, "Cipher::Aegis128X2"), + Cipher::Aegis128X4(_) => write!(f, "Cipher::Aegis128X4"), } } } @@ -354,18 +388,24 @@ pub struct EncryptionContext { impl EncryptionContext { pub fn new(cipher_mode: CipherMode, key: &EncryptionKey, page_size: usize) -> Result { let required_size = cipher_mode.required_key_size(); - if key.as_slice().len() != required_size { + if key.len() != required_size { return Err(crate::LimboError::InvalidArgument(format!( "Invalid key size for {:?}: expected {} bytes, got {}", cipher_mode, required_size, - key.as_slice().len() + key.len() ))); } let cipher = match cipher_mode { - CipherMode::Aes256Gcm => Cipher::Aes256Gcm(Aes256GcmCipher::new(key)), - CipherMode::Aegis256 => Cipher::Aegis256(Aegis256Cipher::new(key)), + CipherMode::Aes128Gcm => Cipher::Aes128Gcm(Box::new(Aes128GcmCipher::new(key)?)), + CipherMode::Aes256Gcm => Cipher::Aes256Gcm(Box::new(Aes256GcmCipher::new(key)?)), + CipherMode::Aegis256 => Cipher::Aegis256(Box::new(Aegis256Cipher::new(key))), + CipherMode::Aegis256X2 => Cipher::Aegis256X2(Box::new(Aegis256X2Cipher::new(key))), + CipherMode::Aegis256X4 => Cipher::Aegis256X4(Box::new(Aegis256X4Cipher::new(key))), + CipherMode::Aegis128L => Cipher::Aegis128L(Box::new(Aegis128LCipher::new(key))), + CipherMode::Aegis128X2 => Cipher::Aegis128X2(Box::new(Aegis128X2Cipher::new(key))), + CipherMode::Aegis128X4 => Cipher::Aegis128X4(Box::new(Aegis128X4Cipher::new(key))), }; Ok(Self { cipher_mode, @@ -471,21 +511,54 @@ impl EncryptionContext { /// encrypts raw data using the configured cipher, returns ciphertext and nonce fn encrypt_raw(&self, plaintext: &[u8]) -> Result<(Vec, Vec)> { - self.cipher.as_aead().encrypt(plaintext, b"") + const AD: &[u8] = b""; + + macro_rules! encrypt_cipher { + ($cipher:expr) => {{ + let (ciphertext, nonce) = $cipher.encrypt(plaintext, AD)?; + Ok((ciphertext, nonce.to_vec())) + }}; + } + + match &self.cipher { + Cipher::Aes128Gcm(cipher) => encrypt_cipher!(cipher), + Cipher::Aes256Gcm(cipher) => encrypt_cipher!(cipher), + Cipher::Aegis256(cipher) => encrypt_cipher!(cipher), + Cipher::Aegis256X2(cipher) => encrypt_cipher!(cipher), + Cipher::Aegis256X4(cipher) => encrypt_cipher!(cipher), + Cipher::Aegis128L(cipher) => encrypt_cipher!(cipher), + Cipher::Aegis128X2(cipher) => encrypt_cipher!(cipher), + Cipher::Aegis128X4(cipher) => encrypt_cipher!(cipher), + } } fn decrypt_raw(&self, ciphertext: &[u8], nonce: &[u8]) -> Result> { - self.cipher.as_aead().decrypt(ciphertext, nonce, b"") - } + const AD: &[u8] = b""; - fn encrypt_raw_detached(&self, plaintext: &[u8]) -> Result<(Vec, Vec, Vec)> { - self.cipher.as_aead().encrypt_detached(plaintext, b"") - } + macro_rules! decrypt_with_nonce { + ($cipher:expr, $nonce_size:literal, $name:literal) => {{ + let nonce_array: [u8; $nonce_size] = nonce.try_into().map_err(|_| { + LimboError::InternalError(format!( + "Invalid nonce size for {}: expected {}, got {}", + $name, + $nonce_size, + nonce.len() + )) + })?; + $cipher.decrypt(ciphertext, &nonce_array, AD) + }}; + } - fn decrypt_raw_detached(&self, ciphertext: &[u8], nonce: &[u8], tag: &[u8]) -> Result> { - self.cipher - .as_aead() - .decrypt_detached(ciphertext, nonce, tag, b"") + match &self.cipher { + Cipher::Aes128Gcm(cipher) => decrypt_with_nonce!(cipher, 12, "AES-128-GCM"), + Cipher::Aes256Gcm(cipher) => decrypt_with_nonce!(cipher, 12, "AES-256-GCM"), + Cipher::Aegis256(cipher) => decrypt_with_nonce!(cipher, 32, "AEGIS-256"), + Cipher::Aegis256X2(cipher) => decrypt_with_nonce!(cipher, 32, "AEGIS-256X2"), + Cipher::Aegis256X4(cipher) => decrypt_with_nonce!(cipher, 32, "AEGIS-256X4"), + Cipher::Aegis128L(cipher) => decrypt_with_nonce!(cipher, 16, "AEGIS-128L"), + Cipher::Aegis128X2(cipher) => decrypt_with_nonce!(cipher, 16, "AEGIS-128X2"), + Cipher::Aegis128X4(cipher) => decrypt_with_nonce!(cipher, 16, "AEGIS-128X4"), + } } #[cfg(not(feature = "encryption"))] @@ -503,21 +576,118 @@ impl EncryptionContext { } } -fn generate_secure_nonce() -> [u8; 32] { - // use OsRng directly to fill bytes, similar to how AeadCore does it +fn generate_secure_nonce() -> [u8; N] { + // use OsRng directly to fill bytes, generic over nonce size use aes_gcm::aead::rand_core::RngCore; - let mut nonce = [0u8; 32]; + let mut nonce = [0u8; N]; OsRng.fill_bytes(&mut nonce); nonce } -#[cfg(feature = "encryption")] +// Helper functions for consistent error messages +enum CipherError { + InvalidKeySize { + cipher: &'static str, + expected: usize, + }, + InvalidTagSize { + cipher: &'static str, + }, + DecryptionFailed { + cipher: &'static str, + }, + CiphertextTooShort { + cipher: &'static str, + }, +} + +impl From for LimboError { + fn from(err: CipherError) -> Self { + let msg = match err { + CipherError::InvalidKeySize { cipher, expected } => { + format!("{cipher} requires {expected}-byte key") + } + CipherError::InvalidTagSize { cipher } => format!("Invalid tag size for {cipher}"), + CipherError::DecryptionFailed { cipher } => { + format!("{cipher} decryption failed: invalid tag") + } + CipherError::CiphertextTooShort { cipher } => { + format!("Ciphertext too short for {cipher}") + } + }; + LimboError::InternalError(msg) + } +} + #[cfg(test)] mod tests { use super::*; use rand::Rng; const DEFAULT_ENCRYPTED_PAGE_SIZE: usize = 4096; + macro_rules! test_cipher_wrapper { + ($test_name:ident, $cipher_type:ty, $key_gen:expr, $nonce_size:literal, $message:literal) => { + #[test] + #[cfg(feature = "encryption")] + fn $test_name() { + let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap(); + let cipher = <$cipher_type>::new(&key); + + let plaintext = $message.as_bytes(); + let ad = b"additional data"; + + let (ciphertext, nonce) = cipher.encrypt(plaintext, ad).unwrap(); + assert_eq!(nonce.len(), $nonce_size); + assert_ne!(ciphertext[..plaintext.len()], plaintext[..]); + + let decrypted = cipher.decrypt(&ciphertext, &nonce, ad).unwrap(); + assert_eq!(decrypted, plaintext); + } + }; + } + + macro_rules! test_aes_cipher_wrapper { + ($test_name:ident, $cipher_type:ty, $key_gen:expr, $nonce_size:literal, $message:literal) => { + #[test] + #[cfg(feature = "encryption")] + fn $test_name() { + let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap(); + let cipher = <$cipher_type>::new(&key).unwrap(); + + let plaintext = $message.as_bytes(); + let ad = b"additional data"; + + let (ciphertext, nonce) = cipher.encrypt(plaintext, ad).unwrap(); + assert_eq!(nonce.len(), $nonce_size); + assert_ne!(ciphertext[..plaintext.len()], plaintext[..]); + + let decrypted = cipher.decrypt(&ciphertext, &nonce, ad).unwrap(); + assert_eq!(decrypted, plaintext); + } + }; + } + + macro_rules! test_raw_encryption { + ($test_name:ident, $cipher_mode:expr, $key_gen:expr, $nonce_size:literal, $message:literal) => { + #[test] + #[cfg(feature = "encryption")] + fn $test_name() { + let key = EncryptionKey::from_hex_string(&$key_gen()).unwrap(); + let ctx = EncryptionContext::new($cipher_mode, &key, DEFAULT_ENCRYPTED_PAGE_SIZE) + .unwrap(); + + let plaintext = $message.as_bytes(); + let (ciphertext, nonce) = ctx.encrypt_raw(plaintext).unwrap(); + + assert_eq!(nonce.len(), $nonce_size); + assert_ne!(ciphertext[..plaintext.len()], plaintext[..]); + + let decrypted = ctx.decrypt_raw(&ciphertext, &nonce).unwrap(); + assert_eq!(decrypted, plaintext); + } + }; + } + fn generate_random_hex_key() -> String { let mut rng = rand::thread_rng(); let mut bytes = [0u8; 32]; @@ -525,7 +695,62 @@ mod tests { hex::encode(bytes) } + fn generate_random_hex_key_128() -> String { + let mut rng = rand::thread_rng(); + let mut bytes = [0u8; 16]; + rng.fill(&mut bytes); + hex::encode(bytes) + } + + test_aes_cipher_wrapper!( + test_aes128gcm_cipher_wrapper, + Aes128GcmCipher, + generate_random_hex_key_128, + 12, + "Hello, AES-128-GCM!" + ); + + test_raw_encryption!( + test_aes128gcm_raw_encryption, + CipherMode::Aes128Gcm, + generate_random_hex_key_128, + 12, + "Hello, AES-128-GCM!" + ); + #[test] + #[cfg(feature = "encryption")] + fn test_aes128gcm_encrypt_decrypt_round_trip() { + let mut rng = rand::thread_rng(); + let cipher_mode = CipherMode::Aes128Gcm; + let metadata_size = cipher_mode.metadata_size(); + let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; + + let page_data = { + let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; + page.iter_mut() + .take(data_size) + .for_each(|byte| *byte = rng.gen()); + page + }; + + let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap(); + let ctx = EncryptionContext::new(CipherMode::Aes128Gcm, &key, DEFAULT_ENCRYPTED_PAGE_SIZE) + .unwrap(); + + let page_id = 42; + let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap(); + assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_ne!(&encrypted[..data_size], &page_data[..data_size]); + assert_ne!(&encrypted[..], &page_data[..]); + + let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap(); + assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_eq!(decrypted, page_data); + } + + #[test] + #[cfg(feature = "encryption")] fn test_aes_encrypt_decrypt_round_trip() { let mut rng = rand::thread_rng(); let cipher_mode = CipherMode::Aes256Gcm; @@ -555,39 +780,24 @@ mod tests { assert_eq!(decrypted, page_data); } - #[test] - fn test_aegis256_cipher_wrapper() { - let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap(); - let cipher = Aegis256Cipher::new(&key); + test_cipher_wrapper!( + test_aegis256_cipher_wrapper, + Aegis256Cipher, + generate_random_hex_key, + 32, + "Hello, AEGIS-256!" + ); - let plaintext = b"Hello, AEGIS-256!"; - let ad = b"additional data"; - - let (ciphertext, nonce) = cipher.encrypt(plaintext, ad).unwrap(); - assert_eq!(nonce.len(), 32); - assert_ne!(ciphertext[..plaintext.len()], plaintext[..]); - - let decrypted = cipher.decrypt(&ciphertext, &nonce, ad).unwrap(); - assert_eq!(decrypted, plaintext); - } - - #[test] - fn test_aegis256_raw_encryption() { - let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap(); - let ctx = EncryptionContext::new(CipherMode::Aegis256, &key, DEFAULT_ENCRYPTED_PAGE_SIZE) - .unwrap(); - - let plaintext = b"Hello, AEGIS-256!"; - let (ciphertext, nonce) = ctx.encrypt_raw(plaintext).unwrap(); - - assert_eq!(nonce.len(), 32); // AEGIS-256 uses 32-byte nonces - assert_ne!(ciphertext[..plaintext.len()], plaintext[..]); - - let decrypted = ctx.decrypt_raw(&ciphertext, &nonce).unwrap(); - assert_eq!(decrypted, plaintext); - } + test_raw_encryption!( + test_aegis256_raw_encryption, + CipherMode::Aegis256, + generate_random_hex_key, + 32, + "Hello, AEGIS-256!" + ); #[test] + #[cfg(feature = "encryption")] fn test_aegis256_encrypt_decrypt_round_trip() { let mut rng = rand::thread_rng(); let cipher_mode = CipherMode::Aegis256; @@ -615,4 +825,300 @@ mod tests { assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); assert_eq!(decrypted, page_data); } + + test_cipher_wrapper!( + test_aegis128x2_cipher_wrapper, + Aegis128X2Cipher, + generate_random_hex_key_128, + 16, + "Hello, AEGIS-128X2!" + ); + + test_raw_encryption!( + test_aegis128x2_raw_encryption, + CipherMode::Aegis128X2, + generate_random_hex_key_128, + 16, + "Hello, AEGIS-128X2!" + ); + + #[test] + #[cfg(feature = "encryption")] + fn test_aegis128x2_encrypt_decrypt_round_trip() { + let mut rng = rand::thread_rng(); + let cipher_mode = CipherMode::Aegis128X2; + let metadata_size = cipher_mode.metadata_size(); + let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; + + let page_data = { + let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; + page.iter_mut() + .take(data_size) + .for_each(|byte| *byte = rng.gen()); + page + }; + + let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap(); + let ctx = EncryptionContext::new(CipherMode::Aegis128X2, &key, DEFAULT_ENCRYPTED_PAGE_SIZE) + .unwrap(); + + let page_id = 42; + let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap(); + assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_ne!(&encrypted[..data_size], &page_data[..data_size]); + + let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap(); + assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_eq!(decrypted, page_data); + } + + test_cipher_wrapper!( + test_aegis128l_cipher_wrapper, + Aegis128LCipher, + generate_random_hex_key_128, + 16, + "Hello, AEGIS-128L!" + ); + + test_raw_encryption!( + test_aegis128l_raw_encryption, + CipherMode::Aegis128L, + generate_random_hex_key_128, + 16, + "Hello, AEGIS-128L!" + ); + + #[test] + #[cfg(feature = "encryption")] + fn test_aegis128l_encrypt_decrypt_round_trip() { + let mut rng = rand::thread_rng(); + let cipher_mode = CipherMode::Aegis128L; + let metadata_size = cipher_mode.metadata_size(); + let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; + + let page_data = { + let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; + page.iter_mut() + .take(data_size) + .for_each(|byte| *byte = rng.gen()); + page + }; + + let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap(); + let ctx = EncryptionContext::new(CipherMode::Aegis128L, &key, DEFAULT_ENCRYPTED_PAGE_SIZE) + .unwrap(); + + let page_id = 42; + let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap(); + assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_ne!(&encrypted[..data_size], &page_data[..data_size]); + + let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap(); + assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_eq!(decrypted, page_data); + } + + test_cipher_wrapper!( + test_aegis128x4_cipher_wrapper, + Aegis128X4Cipher, + generate_random_hex_key_128, + 16, + "Hello, AEGIS-128X4!" + ); + + test_raw_encryption!( + test_aegis128x4_raw_encryption, + CipherMode::Aegis128X4, + generate_random_hex_key_128, + 16, + "Hello, AEGIS-128X4!" + ); + + #[test] + #[cfg(feature = "encryption")] + fn test_aegis128x4_encrypt_decrypt_round_trip() { + let mut rng = rand::thread_rng(); + let cipher_mode = CipherMode::Aegis128X4; + let metadata_size = cipher_mode.metadata_size(); + let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; + + let page_data = { + let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; + page.iter_mut() + .take(data_size) + .for_each(|byte| *byte = rng.gen()); + page + }; + + let key = EncryptionKey::from_hex_string(&generate_random_hex_key_128()).unwrap(); + let ctx = EncryptionContext::new(CipherMode::Aegis128X4, &key, DEFAULT_ENCRYPTED_PAGE_SIZE) + .unwrap(); + + let page_id = 42; + let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap(); + assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_ne!(&encrypted[..data_size], &page_data[..data_size]); + + let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap(); + assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_eq!(decrypted, page_data); + } + + test_cipher_wrapper!( + test_aegis256x2_cipher_wrapper, + Aegis256X2Cipher, + generate_random_hex_key, + 32, + "Hello, AEGIS-256X2!" + ); + + test_raw_encryption!( + test_aegis256x2_raw_encryption, + CipherMode::Aegis256X2, + generate_random_hex_key, + 32, + "Hello, AEGIS-256X2!" + ); + + #[test] + #[cfg(feature = "encryption")] + fn test_aegis256x2_encrypt_decrypt_round_trip() { + let mut rng = rand::thread_rng(); + let cipher_mode = CipherMode::Aegis256X2; + let metadata_size = cipher_mode.metadata_size(); + let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; + + let page_data = { + let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; + page.iter_mut() + .take(data_size) + .for_each(|byte| *byte = rng.gen()); + page + }; + + let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap(); + let ctx = EncryptionContext::new(CipherMode::Aegis256X2, &key, DEFAULT_ENCRYPTED_PAGE_SIZE) + .unwrap(); + + let page_id = 42; + let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap(); + assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_ne!(&encrypted[..data_size], &page_data[..data_size]); + + let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap(); + assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_eq!(decrypted, page_data); + } + + test_cipher_wrapper!( + test_aegis256x4_cipher_wrapper, + Aegis256X4Cipher, + generate_random_hex_key, + 32, + "Hello, AEGIS-256X4!" + ); + + test_raw_encryption!( + test_aegis256x4_raw_encryption, + CipherMode::Aegis256X4, + generate_random_hex_key, + 32, + "Hello, AEGIS-256X4!" + ); + + #[test] + #[cfg(feature = "encryption")] + fn test_aegis256x4_encrypt_decrypt_round_trip() { + let mut rng = rand::thread_rng(); + let cipher_mode = CipherMode::Aegis256X4; + let metadata_size = cipher_mode.metadata_size(); + let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; + + let page_data = { + let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; + page.iter_mut() + .take(data_size) + .for_each(|byte| *byte = rng.gen()); + page + }; + + let key = EncryptionKey::from_hex_string(&generate_random_hex_key()).unwrap(); + let ctx = EncryptionContext::new(CipherMode::Aegis256X4, &key, DEFAULT_ENCRYPTED_PAGE_SIZE) + .unwrap(); + + let page_id = 42; + let encrypted = ctx.encrypt_page(&page_data, page_id).unwrap(); + assert_eq!(encrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_ne!(&encrypted[..data_size], &page_data[..data_size]); + + let decrypted = ctx.decrypt_page(&encrypted, page_id).unwrap(); + assert_eq!(decrypted.len(), DEFAULT_ENCRYPTED_PAGE_SIZE); + assert_eq!(decrypted, page_data); + } + + #[test] + fn test_cipher_mode_string_parsing() { + // Test AES-128-GCM + let mode = CipherMode::try_from("aes128gcm").unwrap(); + assert_eq!(mode, CipherMode::Aes128Gcm); + assert_eq!(mode.to_string(), "aes128gcm"); + assert_eq!(mode.required_key_size(), 16); + assert_eq!(mode.nonce_size(), 12); + assert_eq!(mode.tag_size(), 16); + + let mode = CipherMode::try_from("aes-128-gcm").unwrap(); + assert_eq!(mode, CipherMode::Aes128Gcm); + + let mode = CipherMode::try_from("aes_128_gcm").unwrap(); + assert_eq!(mode, CipherMode::Aes128Gcm); + + // Test AES-256-GCM + let mode = CipherMode::try_from("aes256gcm").unwrap(); + assert_eq!(mode, CipherMode::Aes256Gcm); + assert_eq!(mode.to_string(), "aes256gcm"); + assert_eq!(mode.required_key_size(), 32); + assert_eq!(mode.nonce_size(), 12); + + // Test that all AEGIS variants can be parsed from strings + let mode = CipherMode::try_from("aegis128x2").unwrap(); + assert_eq!(mode, CipherMode::Aegis128X2); + assert_eq!(mode.to_string(), "aegis128x2"); + assert_eq!(mode.required_key_size(), 16); + assert_eq!(mode.nonce_size(), 16); + assert_eq!(mode.tag_size(), 16); + + let mode = CipherMode::try_from("aegis-128x2").unwrap(); + assert_eq!(mode, CipherMode::Aegis128X2); + + let mode = CipherMode::try_from("aegis_128x2").unwrap(); + assert_eq!(mode, CipherMode::Aegis128X2); + + // Test AEGIS-128L + let mode = CipherMode::try_from("aegis128l").unwrap(); + assert_eq!(mode, CipherMode::Aegis128L); + assert_eq!(mode.to_string(), "aegis128l"); + assert_eq!(mode.required_key_size(), 16); + assert_eq!(mode.nonce_size(), 16); + + // Test AEGIS-128X4 + let mode = CipherMode::try_from("aegis128x4").unwrap(); + assert_eq!(mode, CipherMode::Aegis128X4); + assert_eq!(mode.to_string(), "aegis128x4"); + assert_eq!(mode.required_key_size(), 16); + assert_eq!(mode.nonce_size(), 16); + + // Test AEGIS-256X2 + let mode = CipherMode::try_from("aegis256x2").unwrap(); + assert_eq!(mode, CipherMode::Aegis256X2); + assert_eq!(mode.to_string(), "aegis256x2"); + assert_eq!(mode.required_key_size(), 32); + assert_eq!(mode.nonce_size(), 32); + + // Test AEGIS-256X4 + let mode = CipherMode::try_from("aegis256x4").unwrap(); + assert_eq!(mode, CipherMode::Aegis256X4); + assert_eq!(mode.to_string(), "aegis256x4"); + assert_eq!(mode.required_key_size(), 32); + assert_eq!(mode.nonce_size(), 32); + } } diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 36196156f..da6a2380e 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -2165,16 +2165,23 @@ impl Pager { Ok(IOResult::Done(f(header))) } - pub fn set_encryption_context(&self, cipher_mode: CipherMode, key: &EncryptionKey) { + pub fn set_encryption_context( + &self, + cipher_mode: CipherMode, + key: &EncryptionKey, + ) -> Result<()> { let page_size = self.page_size.get().unwrap().get() as usize; - let encryption_ctx = EncryptionContext::new(cipher_mode, key, page_size).unwrap(); + let encryption_ctx = EncryptionContext::new(cipher_mode, key, page_size)?; { let mut io_ctx = self.io_ctx.borrow_mut(); io_ctx.set_encryption(encryption_ctx); } - let Some(wal) = self.wal.as_ref() else { return }; + let Some(wal) = self.wal.as_ref() else { + return Ok(()); + }; wal.borrow_mut() - .set_io_context(self.io_ctx.borrow().clone()) + .set_io_context(self.io_ctx.borrow().clone()); + Ok(()) } } diff --git a/core/translate/pragma.rs b/core/translate/pragma.rs index e6c731cd5..1189ec589 100644 --- a/core/translate/pragma.rs +++ b/core/translate/pragma.rs @@ -317,13 +317,13 @@ fn update_pragma( PragmaName::EncryptionKey => { let value = parse_string(&value)?; let key = EncryptionKey::from_hex_string(&value)?; - connection.set_encryption_key(key); + connection.set_encryption_key(key)?; Ok((program, TransactionMode::None)) } PragmaName::EncryptionCipher => { let value = parse_string(&value)?; let cipher = CipherMode::try_from(value.as_str())?; - connection.set_encryption_cipher(cipher); + connection.set_encryption_cipher(cipher)?; Ok((program, TransactionMode::None)) } PragmaName::Synchronous => {