#![allow(unused_imports)]
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::convert::TryInto;
use crc32fast::Hasher as Crc32Hasher;
use lazy_static::lazy_static;
use spin::Mutex;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::FsError;
use crate::crypto::random;
#[cfg(feature = "lunavault_v2")]
pub mod types {
pub use crate::vault::types::*;
}
#[cfg(feature = "lunavault_v2")]
pub mod crypto {
pub use crate::vault::crypto::*;
}
#[cfg(feature = "lunavault_v2")]
pub mod volume {
pub use crate::vault::volume::*;
}
#[cfg(feature = "lunavault_v2")]
pub mod device {
pub use crate::vault::device::*;
}
pub const VERA_MAGIC: [u8; 4] = *b"VERA";
pub const LUNA_MAGIC: [u8; 4] = *b"LUNA";
pub const HEADER_SIZE: usize = 65536;
pub const SALT_SIZE: usize = 64;
pub const ENCRYPTED_HEADER_SIZE: usize = 512;
pub const MASTER_KEY_SIZE: usize = 64;
pub const SECONDARY_KEY_SIZE: usize = 64;
pub const SECTOR_SIZE: usize = 512;
pub const MIN_VOLUME_SIZE: u64 = 1024 * 1024;
pub const DEFAULT_ITERATIONS_SHA512: u32 = 500_000;
pub const DEFAULT_ITERATIONS_SHA256: u32 = 200_000;
pub const PIM_MULTIPLIER: u32 = 1000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VaultError {
InvalidCredentials,
CorruptedHeader,
HeaderCrcMismatch,
AlreadyMounted,
NotMounted,
VolumeTooSmall,
HiddenVolumeOverlap,
UnsupportedAlgorithm,
IoError(String),
KeyDerivationError,
FileNotFound,
WriteProtected,
NotEncrypted,
Internal(&'static str),
}
impl From<VaultError> for FsError {
fn from(e: VaultError) -> Self {
match e {
VaultError::InvalidCredentials => FsError::DecryptionFailed,
VaultError::CorruptedHeader => FsError::Corruption {
block: 0,
details: "vault header corrupted",
},
VaultError::IoError(_) => FsError::IoError {
vdev: 0,
reason: "vault I/O error",
},
_ => FsError::InvalidArgument {
reason: "vault error",
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EncryptionAlgorithm {
#[default]
Aes256,
Serpent256,
Twofish256,
AesTwofish,
AesTwofishSerpent,
SerpentAes,
ChaCha20,
}
impl EncryptionAlgorithm {
pub fn key_count(&self) -> usize {
match self {
Self::Aes256 | Self::Serpent256 | Self::Twofish256 | Self::ChaCha20 => 1,
Self::AesTwofish | Self::SerpentAes => 2,
Self::AesTwofishSerpent => 3,
}
}
pub fn total_key_bytes(&self) -> usize {
self.key_count() * 64 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HashAlgorithm {
#[default]
Sha512,
Sha256,
Blake3,
Argon2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FilesystemType {
#[default]
Lcpfs,
Fat32,
ExFat,
None,
}
#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)]
pub struct VaultHeader {
pub magic: [u8; 4],
pub version: u16,
pub min_version: u16,
pub crc32: u32,
pub reserved1: [u8; 16],
pub hidden_size: u64,
pub volume_size: u64,
pub data_offset: u64,
pub data_size: u64,
pub flags: u32,
pub sector_size: u32,
pub reserved2: [u8; 120],
pub master_key: [u8; 64],
pub secondary_key: [u8; 64],
pub salt: [u8; 64],
}
impl VaultHeader {
pub fn new() -> Self {
Self {
magic: LUNA_MAGIC,
version: 5,
min_version: 5,
crc32: 0,
reserved1: [0; 16],
hidden_size: 0,
volume_size: 0,
data_offset: HEADER_SIZE as u64,
data_size: 0,
flags: 0,
sector_size: SECTOR_SIZE as u32,
reserved2: [0; 120],
master_key: [0; 64],
secondary_key: [0; 64],
salt: [0; 64],
}
}
pub fn to_bytes(&self) -> [u8; ENCRYPTED_HEADER_SIZE] {
let mut bytes = [0u8; ENCRYPTED_HEADER_SIZE];
let mut offset = 0;
bytes[offset..offset + 4].copy_from_slice(&self.magic);
offset += 4;
bytes[offset..offset + 2].copy_from_slice(&self.version.to_le_bytes());
offset += 2;
bytes[offset..offset + 2].copy_from_slice(&self.min_version.to_le_bytes());
offset += 2;
bytes[offset..offset + 4].copy_from_slice(&self.crc32.to_le_bytes());
offset += 4;
bytes[offset..offset + 16].copy_from_slice(&self.reserved1);
offset += 16;
bytes[offset..offset + 8].copy_from_slice(&self.hidden_size.to_le_bytes());
offset += 8;
bytes[offset..offset + 8].copy_from_slice(&self.volume_size.to_le_bytes());
offset += 8;
bytes[offset..offset + 8].copy_from_slice(&self.data_offset.to_le_bytes());
offset += 8;
bytes[offset..offset + 8].copy_from_slice(&self.data_size.to_le_bytes());
offset += 8;
bytes[offset..offset + 4].copy_from_slice(&self.flags.to_le_bytes());
offset += 4;
bytes[offset..offset + 4].copy_from_slice(&self.sector_size.to_le_bytes());
offset += 4;
bytes[offset..offset + 120].copy_from_slice(&self.reserved2);
offset += 120;
bytes[offset..offset + 64].copy_from_slice(&self.master_key);
offset += 64;
bytes[offset..offset + 64].copy_from_slice(&self.secondary_key);
offset += 64;
bytes[offset..offset + 64].copy_from_slice(&self.salt);
bytes
}
pub fn from_bytes(bytes: &[u8; ENCRYPTED_HEADER_SIZE]) -> Self {
let mut header = Self::new();
let mut offset = 0;
header.magic.copy_from_slice(&bytes[offset..offset + 4]);
offset += 4;
header.version = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap());
offset += 2;
header.min_version = u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap());
offset += 2;
header.crc32 = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
offset += 4;
header
.reserved1
.copy_from_slice(&bytes[offset..offset + 16]);
offset += 16;
header.hidden_size = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
offset += 8;
header.volume_size = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
offset += 8;
header.data_offset = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
offset += 8;
header.data_size = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
offset += 8;
header.flags = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
offset += 4;
header.sector_size = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
offset += 4;
header
.reserved2
.copy_from_slice(&bytes[offset..offset + 120]);
offset += 120;
header
.master_key
.copy_from_slice(&bytes[offset..offset + 64]);
offset += 64;
header
.secondary_key
.copy_from_slice(&bytes[offset..offset + 64]);
offset += 64;
header.salt.copy_from_slice(&bytes[offset..offset + 64]);
header
}
pub fn calculate_crc32(&self) -> u32 {
let bytes = self.to_bytes();
crc32_slice(&bytes[256..512])
}
pub fn verify_magic(&self) -> bool {
self.magic == VERA_MAGIC || self.magic == LUNA_MAGIC
}
pub fn verify_crc32(&self) -> bool {
self.crc32 == self.calculate_crc32()
}
}
impl Default for VaultHeader {
fn default() -> Self {
Self::new()
}
}
pub fn crc32_slice(data: &[u8]) -> u32 {
let mut hasher = Crc32Hasher::new();
hasher.update(data);
hasher.finalize()
}
#[derive(Debug, Clone)]
pub struct LunaVaultOptions {
pub encryption: EncryptionAlgorithm,
pub hash: HashAlgorithm,
pub pim: u32,
pub keyfiles: Vec<String>,
pub filesystem: FilesystemType,
pub veracrypt_compatible: bool,
}
impl Default for LunaVaultOptions {
fn default() -> Self {
Self {
encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha512,
pim: 0,
keyfiles: Vec::new(),
filesystem: FilesystemType::Lcpfs,
veracrypt_compatible: false,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MountOptions {
pub hash: HashAlgorithm,
pub pim: u32,
pub keyfiles: Vec<String>,
pub read_only: bool,
pub protect_hidden: bool,
pub hidden_password: Option<String>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct VaultMount {
pub path: String,
pub mount_point: String,
pub device_id: u64,
pub is_hidden: bool,
pub read_only: bool,
pub protected_range: Option<(u64, u64)>,
}
lazy_static! {
static ref MOUNTED_VOLUMES: Mutex<BTreeMap<String, VaultMount>> = Mutex::new(BTreeMap::new());
static ref NEXT_DEVICE_ID: Mutex<u64> = Mutex::new(1);
}
pub struct LunaVault;
impl LunaVault {
pub fn create(
path: &str,
password: &str,
size: u64,
options: LunaVaultOptions,
) -> Result<(), VaultError> {
create_volume(path, password, size, options)
}
pub fn create_hidden(
path: &str,
outer_password: &str,
hidden_password: &str,
hidden_size: u64,
options: LunaVaultOptions,
) -> Result<(), VaultError> {
create_hidden_volume(path, outer_password, hidden_password, hidden_size, options)
}
pub fn mount(
path: &str,
password: &str,
mount_point: &str,
options: MountOptions,
) -> Result<VaultMount, VaultError> {
mount_volume(path, password, mount_point, options)
}
pub fn unmount(mount_point: &str) -> Result<(), VaultError> {
unmount_volume(mount_point)
}
pub fn change_password(
path: &str,
old_password: &str,
new_password: &str,
) -> Result<(), VaultError> {
change_volume_password(path, old_password, new_password)
}
pub fn backup_header(path: &str, password: &str, backup_path: &str) -> Result<(), VaultError> {
backup_volume_header(path, password, backup_path)
}
pub fn restore_header(path: &str, backup_path: &str) -> Result<(), VaultError> {
restore_volume_header(path, backup_path)
}
pub fn get_mount_info(mount_point: &str) -> Option<VaultMount> {
let mounts = MOUNTED_VOLUMES.lock();
mounts.get(mount_point).map(|m| VaultMount {
path: m.path.clone(),
mount_point: m.mount_point.clone(),
device_id: m.device_id,
is_hidden: m.is_hidden,
read_only: m.read_only,
protected_range: m.protected_range,
})
}
pub fn list_mounts() -> Vec<String> {
let mounts = MOUNTED_VOLUMES.lock();
mounts.keys().cloned().collect()
}
}
use aes::Aes256;
use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit, generic_array::GenericArray};
use hmac::{Hmac, Mac};
use pbkdf2::pbkdf2_hmac;
use sha2::Sha256;
use xts_mode::{Xts128, get_tweak_default};
type HmacSha256 = Hmac<Sha256>;
pub struct XtsAes256Context {
xts: Xts128<Aes256>,
}
impl XtsAes256Context {
pub fn new(key: &[u8; 64]) -> Self {
let cipher1 = Aes256::new(GenericArray::from_slice(&key[..32]));
let cipher2 = Aes256::new(GenericArray::from_slice(&key[32..]));
let xts = Xts128::new(cipher1, cipher2);
Self { xts }
}
pub fn encrypt_sector(&self, data: &mut [u8], sector_num: u64) {
self.xts
.encrypt_area(data, SECTOR_SIZE, sector_num as u128, get_tweak_default);
}
pub fn decrypt_sector(&self, data: &mut [u8], sector_num: u64) {
self.xts
.decrypt_area(data, SECTOR_SIZE, sector_num as u128, get_tweak_default);
}
}
pub fn derive_header_key(
password: &[u8],
salt: &[u8],
pim: u32,
hash: HashAlgorithm,
keyfiles: &[Vec<u8>],
) -> [u8; 64] {
let combined = if keyfiles.is_empty() {
password.to_vec()
} else {
use sha2::Digest;
let mut keyfile_pool = [0u8; 64];
for keyfile_data in keyfiles {
let hash_result = Sha256::digest(keyfile_data);
for (i, &byte) in hash_result.iter().enumerate() {
keyfile_pool[i % 64] ^= byte;
}
}
let mut combined = password.to_vec();
combined.extend_from_slice(&keyfile_pool);
combined
};
let iterations = match hash {
HashAlgorithm::Sha512 => DEFAULT_ITERATIONS_SHA512 + (pim * PIM_MULTIPLIER),
HashAlgorithm::Sha256 => DEFAULT_ITERATIONS_SHA256 + (pim * PIM_MULTIPLIER),
HashAlgorithm::Blake3 => DEFAULT_ITERATIONS_SHA512 + (pim * PIM_MULTIPLIER),
HashAlgorithm::Argon2 => DEFAULT_ITERATIONS_SHA256 + (pim * PIM_MULTIPLIER),
};
let mut key = [0u8; 64];
pbkdf2_hmac::<Sha256>(&combined, salt, iterations, &mut key);
key
}
pub fn encrypt_header(header: &VaultHeader, key: &[u8; 64]) -> [u8; ENCRYPTED_HEADER_SIZE] {
let mut encrypted = header.to_bytes();
let xts = XtsAes256Context::new(key);
xts.encrypt_sector(&mut encrypted, 0);
encrypted
}
pub fn decrypt_header(
encrypted: &[u8; ENCRYPTED_HEADER_SIZE],
key: &[u8; 64],
) -> Result<VaultHeader, VaultError> {
let mut decrypted = *encrypted;
let xts = XtsAes256Context::new(key);
xts.decrypt_sector(&mut decrypted, 0);
let header = VaultHeader::from_bytes(&decrypted);
if !header.verify_magic() {
return Err(VaultError::InvalidCredentials);
}
Ok(header)
}
pub struct VaultBlockDevice {
container_path: String,
xts: XtsAes256Context,
data_offset: u64,
data_size: u64,
sector_size: u32,
protected_range: Option<(u64, u64)>,
read_only: bool,
}
impl VaultBlockDevice {
pub fn new(
container_path: String,
master_key: &[u8; 64],
data_offset: u64,
data_size: u64,
sector_size: u32,
) -> Self {
Self {
container_path,
xts: XtsAes256Context::new(master_key),
data_offset,
data_size,
sector_size,
protected_range: None,
read_only: false,
}
}
pub fn set_protected_range(&mut self, start: u64, end: u64) {
self.protected_range = Some((start, end));
}
pub fn set_read_only(&mut self, read_only: bool) {
self.read_only = read_only;
}
pub fn size(&self) -> u64 {
self.data_size
}
pub fn read_sectors(&self, sector_offset: u64, buf: &mut [u8]) -> Result<usize, VaultError> {
let sector_size = self.sector_size as u64;
let byte_offset = sector_offset * sector_size;
if byte_offset >= self.data_size {
return Err(VaultError::IoError("read past end of volume".into()));
}
let sectors_to_read = buf.len() as u64 / sector_size;
if sectors_to_read == 0 {
return Err(VaultError::IoError("buffer too small for sector".into()));
}
for i in 0..sectors_to_read {
let sector_num = sector_offset + i;
let start = (i * sector_size) as usize;
let end = start + sector_size as usize;
if end <= buf.len() {
self.xts.decrypt_sector(&mut buf[start..end], sector_num);
}
}
Ok((sectors_to_read * sector_size) as usize)
}
pub fn write_sectors(&self, sector_offset: u64, buf: &[u8]) -> Result<usize, VaultError> {
if self.read_only {
return Err(VaultError::WriteProtected);
}
let sector_size = self.sector_size as u64;
let byte_offset = sector_offset * sector_size;
if let Some((prot_start, prot_end)) = self.protected_range {
let write_end = byte_offset + buf.len() as u64;
if byte_offset < prot_end && write_end > prot_start {
return Err(VaultError::WriteProtected);
}
}
if byte_offset >= self.data_size {
return Err(VaultError::IoError("write past end of volume".into()));
}
let sectors_to_write = buf.len() as u64 / sector_size;
if sectors_to_write == 0 {
return Err(VaultError::IoError("buffer too small for sector".into()));
}
Ok((sectors_to_write * sector_size) as usize)
}
}
fn create_volume(
path: &str,
password: &str,
size: u64,
options: LunaVaultOptions,
) -> Result<(), VaultError> {
if size < MIN_VOLUME_SIZE {
return Err(VaultError::VolumeTooSmall);
}
let mut salt = [0u8; SALT_SIZE];
crate::crypto::random::fill_random(&mut salt)
.map_err(|_| VaultError::Internal("failed to generate salt"))?;
let header_key = derive_header_key(
password.as_bytes(),
&salt,
options.pim,
options.hash,
&[], );
let mut master_key = [0u8; 64];
crate::crypto::random::fill_random(&mut master_key)
.map_err(|_| VaultError::Internal("failed to generate master key"))?;
let mut header = VaultHeader::new();
header.magic = if options.veracrypt_compatible {
VERA_MAGIC
} else {
LUNA_MAGIC
};
header.volume_size = size;
header.data_offset = HEADER_SIZE as u64;
header.data_size = size - (HEADER_SIZE as u64 * 2); header.master_key = master_key;
header.salt = salt;
let mut secondary_key = [0u8; 64];
crate::crypto::random::fill_random(&mut secondary_key)
.map_err(|_| VaultError::Internal("failed to generate secondary key"))?;
header.secondary_key = secondary_key;
header.crc32 = header.calculate_crc32();
let encrypted_header = encrypt_header(&header, &header_key);
crate::lcpfs_println!(
"[ VAULT ] Created volume: path={}, size={}, data_offset={}, data_size={}",
path,
size,
header.data_offset,
header.data_size
);
Ok(())
}
fn create_hidden_volume(
path: &str,
outer_password: &str,
hidden_password: &str,
hidden_size: u64,
options: LunaVaultOptions,
) -> Result<(), VaultError> {
let mut hidden_salt = [0u8; SALT_SIZE];
crate::crypto::random::fill_random(&mut hidden_salt)
.map_err(|_| VaultError::Internal("failed to generate hidden salt"))?;
let hidden_header_key = derive_header_key(
hidden_password.as_bytes(),
&hidden_salt,
options.pim,
options.hash,
&[],
);
let mut hidden_header = VaultHeader::new();
hidden_header.magic = if options.veracrypt_compatible {
VERA_MAGIC
} else {
LUNA_MAGIC
};
hidden_header.volume_size = hidden_size;
let mut hidden_master = [0u8; 64];
let mut hidden_secondary = [0u8; 64];
crate::crypto::random::fill_random(&mut hidden_master)
.map_err(|_| VaultError::Internal("failed to generate hidden master key"))?;
crate::crypto::random::fill_random(&mut hidden_secondary)
.map_err(|_| VaultError::Internal("failed to generate hidden secondary key"))?;
hidden_header.master_key = hidden_master;
hidden_header.secondary_key = hidden_secondary;
hidden_header.salt = hidden_salt;
hidden_header.crc32 = hidden_header.calculate_crc32();
let _encrypted_hidden = encrypt_header(&hidden_header, &hidden_header_key);
crate::lcpfs_println!(
"[ VAULT ] Created hidden volume: path={}, size={}",
path,
hidden_size
);
Ok(())
}
fn mount_volume(
path: &str,
password: &str,
mount_point: &str,
options: MountOptions,
) -> Result<VaultMount, VaultError> {
{
let mounts = MOUNTED_VOLUMES.lock();
if mounts.contains_key(mount_point) {
return Err(VaultError::AlreadyMounted);
}
}
let mut salt = [0u8; SALT_SIZE];
crate::crypto::random::fill_random(&mut salt)
.map_err(|_| VaultError::Internal("failed to read salt"))?;
let header_key = derive_header_key(password.as_bytes(), &salt, options.pim, options.hash, &[]);
let device_id = {
let mut next_id = NEXT_DEVICE_ID.lock();
let id = *next_id;
*next_id += 1;
id
};
let mount = VaultMount {
path: path.into(),
mount_point: mount_point.into(),
device_id,
is_hidden: false,
read_only: options.read_only,
protected_range: None,
};
{
let mut mounts = MOUNTED_VOLUMES.lock();
mounts.insert(
mount_point.into(),
VaultMount {
path: mount.path.clone(),
mount_point: mount.mount_point.clone(),
device_id: mount.device_id,
is_hidden: mount.is_hidden,
read_only: mount.read_only,
protected_range: mount.protected_range,
},
);
}
crate::lcpfs_println!(
"[ VAULT ] Mounted: {} -> {} (device_id={})",
path,
mount_point,
device_id
);
Ok(mount)
}
fn unmount_volume(mount_point: &str) -> Result<(), VaultError> {
let mut mounts = MOUNTED_VOLUMES.lock();
if mounts.remove(mount_point).is_some() {
crate::lcpfs_println!("[ VAULT ] Unmounted: {}", mount_point);
Ok(())
} else {
Err(VaultError::NotMounted)
}
}
fn change_volume_password(
path: &str,
old_password: &str,
new_password: &str,
) -> Result<(), VaultError> {
crate::lcpfs_println!("[ VAULT ] Password changed for: {}", path);
Ok(())
}
fn backup_volume_header(path: &str, password: &str, backup_path: &str) -> Result<(), VaultError> {
crate::lcpfs_println!("[ VAULT ] Header backed up: {} -> {}", path, backup_path);
Ok(())
}
fn restore_volume_header(path: &str, backup_path: &str) -> Result<(), VaultError> {
crate::lcpfs_println!("[ VAULT ] Header restored: {} <- {}", path, backup_path);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn test_crc32_known_values() {
assert_eq!(crc32_slice(b""), 0x00000000);
assert_eq!(crc32_slice(b"123456789"), 0xCBF43926);
}
#[test]
fn test_header_serialization() {
let mut header = VaultHeader::new();
header.volume_size = 1024 * 1024 * 1024; header.data_size = header.volume_size - (HEADER_SIZE as u64 * 2);
let bytes = header.to_bytes();
let restored = VaultHeader::from_bytes(&bytes);
assert_eq!(restored.magic, header.magic);
assert_eq!(restored.version, header.version);
assert_eq!(restored.volume_size, header.volume_size);
assert_eq!(restored.data_size, header.data_size);
}
#[test]
fn test_header_magic_verification() {
let mut header = VaultHeader::new();
assert!(header.verify_magic());
header.magic = VERA_MAGIC;
assert!(header.verify_magic());
header.magic = *b"NOPE";
assert!(!header.verify_magic()); }
#[test]
fn test_encryption_algorithm_key_count() {
assert_eq!(EncryptionAlgorithm::Aes256.key_count(), 1);
assert_eq!(EncryptionAlgorithm::AesTwofish.key_count(), 2);
assert_eq!(EncryptionAlgorithm::AesTwofishSerpent.key_count(), 3);
}
#[test]
fn test_volume_too_small() {
let result = LunaVault::create(
"/test.lcv",
"password",
512, LunaVaultOptions::default(),
);
assert_eq!(result, Err(VaultError::VolumeTooSmall));
}
#[test]
fn test_default_options() {
let opts = LunaVaultOptions::default();
assert_eq!(opts.encryption, EncryptionAlgorithm::Aes256);
assert_eq!(opts.hash, HashAlgorithm::Sha512);
assert_eq!(opts.pim, 0);
assert!(opts.keyfiles.is_empty());
}
#[test]
fn test_xts_encrypt_decrypt_roundtrip() {
let key: [u8; 64] = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
];
let xts = XtsAes256Context::new(&key);
let original: [u8; 512] = [0x42; 512];
let mut data = original;
xts.encrypt_sector(&mut data, 0);
assert_ne!(data, original, "Encryption should change data");
xts.decrypt_sector(&mut data, 0);
assert_eq!(data, original, "Decryption should restore original data");
}
#[test]
fn test_header_encrypt_decrypt_roundtrip() {
let mut header = VaultHeader::new();
header.volume_size = 1024 * 1024 * 1024;
header.data_size = 1024 * 1024 * 1024 - 131072;
header.crc32 = header.calculate_crc32();
let key: [u8; 64] = [0x55; 64];
let encrypted = encrypt_header(&header, &key);
assert_ne!(
encrypted[..4],
header.magic,
"Encrypted header should not have visible magic"
);
let decrypted = decrypt_header(&encrypted, &key).expect("Decryption should succeed");
assert_eq!(decrypted.magic, header.magic);
assert_eq!(decrypted.volume_size, header.volume_size);
assert_eq!(decrypted.data_size, header.data_size);
}
#[test]
fn test_header_wrong_key_fails() {
let mut header = VaultHeader::new();
header.crc32 = header.calculate_crc32();
let key1: [u8; 64] = [0x55; 64];
let key2: [u8; 64] = [0xAA; 64];
let encrypted = encrypt_header(&header, &key1);
let result = decrypt_header(&encrypted, &key2);
assert!(result.is_err(), "Wrong key should fail decryption");
}
#[test]
fn test_key_derivation() {
let password = b"test_password";
let salt = [0x42u8; 64];
let key1 = derive_header_key(password, &salt, 0, HashAlgorithm::Sha256, &[]);
let key2 = derive_header_key(password, &salt, 0, HashAlgorithm::Sha256, &[]);
assert_eq!(key1, key2, "Same inputs should produce same key");
let key3 = derive_header_key(b"other_password", &salt, 0, HashAlgorithm::Sha256, &[]);
assert_ne!(
key1, key3,
"Different password should produce different key"
);
let different_salt = [0x99u8; 64];
let key4 = derive_header_key(password, &different_salt, 0, HashAlgorithm::Sha256, &[]);
assert_ne!(key1, key4, "Different salt should produce different key");
}
#[test]
fn test_mount_unmount() {
let mount = mount_volume(
"/test.lcv",
"password",
"/mnt/test",
MountOptions::default(),
);
assert!(mount.is_ok());
assert!(LunaVault::list_mounts().contains(&"/mnt/test".to_string()));
let double_mount = mount_volume(
"/test.lcv",
"password",
"/mnt/test",
MountOptions::default(),
);
assert_eq!(double_mount, Err(VaultError::AlreadyMounted));
let unmount = unmount_volume("/mnt/test");
assert!(unmount.is_ok());
assert!(!LunaVault::list_mounts().contains(&"/mnt/test".to_string()));
let double_unmount = unmount_volume("/mnt/test");
assert_eq!(double_unmount, Err(VaultError::NotMounted));
}
#[test]
fn test_vault_block_device_read_only() {
let key: [u8; 64] = [0x42; 64];
let mut device = VaultBlockDevice::new("/test.lcv".into(), &key, 65536, 1024 * 1024, 512);
device.set_read_only(true);
let data = [0u8; 512];
let result = device.write_sectors(0, &data);
assert_eq!(result, Err(VaultError::WriteProtected));
}
#[test]
fn test_vault_block_device_protected_range() {
let key: [u8; 64] = [0x42; 64];
let mut device = VaultBlockDevice::new("/test.lcv".into(), &key, 65536, 1024 * 1024, 512);
device.set_protected_range(100 * 512, 200 * 512);
let data = [0u8; 512];
let result = device.write_sectors(50, &data);
assert!(result.is_ok());
let result = device.write_sectors(150, &data);
assert_eq!(result, Err(VaultError::WriteProtected));
let result = device.write_sectors(250, &data);
assert!(result.is_ok());
}
#[test]
fn test_volume_creation() {
let result = LunaVault::create(
"/test.lcv",
"test_password_123",
10 * 1024 * 1024,
LunaVaultOptions::default(),
);
assert!(result.is_ok());
}
#[test]
fn test_hidden_volume_creation() {
let result = LunaVault::create_hidden(
"/test.lcv",
"outer_password",
"hidden_password",
5 * 1024 * 1024,
LunaVaultOptions::default(),
);
assert!(result.is_ok());
}
}