#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::convert::TryInto;
use super::crypto::*;
use super::types::*;
pub struct VolumeStorage {
data: Vec<u8>,
path: String,
}
impl VolumeStorage {
pub fn new(path: &str, size: u64) -> Self {
Self {
data: vec![0u8; size as usize],
path: path.to_string(),
}
}
pub fn from_data(path: &str, data: Vec<u8>) -> Self {
Self {
data,
path: path.to_string(),
}
}
pub fn read(&self, offset: u64, len: usize) -> Result<Vec<u8>, VaultError> {
let start = offset as usize;
let end = start + len;
if end > self.data.len() {
return Err(VaultError::IoError("Read past end of volume".to_string()));
}
Ok(self.data[start..end].to_vec())
}
pub fn write(&mut self, offset: u64, data: &[u8]) -> Result<(), VaultError> {
let start = offset as usize;
let end = start + data.len();
if end > self.data.len() {
return Err(VaultError::IoError("Write past end of volume".to_string()));
}
self.data[start..end].copy_from_slice(data);
Ok(())
}
pub fn size(&self) -> u64 {
self.data.len() as u64
}
pub fn path(&self) -> &str {
&self.path
}
pub fn into_data(self) -> Vec<u8> {
self.data
}
pub fn data(&self) -> &[u8] {
&self.data
}
}
pub fn create_volume(
password: &str,
options: &VaultCreateOptions,
) -> Result<VolumeStorage, VaultError> {
if options.size < MIN_VOLUME_SIZE {
return Err(VaultError::VolumeTooSmall);
}
let salt = generate_salt();
let keyfile_data: Vec<Vec<u8>> = Vec::new(); let keyfile_refs: Vec<&[u8]> = keyfile_data.iter().map(|v| v.as_slice()).collect();
let header_key = derive_with_keyfiles(
password.as_bytes(),
&keyfile_refs,
&salt,
options.pim,
options.hash,
);
let master_key = generate_master_key();
let secondary_key = generate_master_key();
let data_offset = HEADER_SIZE as u64;
let data_size = options.size - (HEADER_SIZE as u64 * 2);
let mut header = VaultHeader::new();
header.magic = VERA_MAGIC;
header.version = 5;
header.min_version = 5;
header.hidden_size = 0;
header.volume_size = options.size;
header.data_offset = data_offset;
header.data_size = data_size;
header.sector_size = SECTOR_SIZE as u32;
header.master_key.copy_from_slice(&master_key);
header.secondary_key.copy_from_slice(&secondary_key);
header.salt.copy_from_slice(&salt);
if options.filesystem == FilesystemType::Lcpfs {
header.flags.insert(VaultFlags::LCPFS_NATIVE);
}
if options.encryption == EncryptionAlgorithm::ChaCha20Poly1305 {
header.flags.insert(VaultFlags::CHACHA20);
}
let header_bytes = header.to_bytes();
header.crc32 = crc32(&header_bytes[256..512]);
let mut storage = VolumeStorage::new("", options.size);
storage.write(0, &salt)?;
let encrypted_header = encrypt_header(&header, &header_key, options.encryption)?;
storage.write(SALT_SIZE as u64, &encrypted_header)?;
let backup_offset = options.size - HEADER_SIZE as u64;
storage.write(backup_offset, &salt)?;
storage.write(backup_offset + SALT_SIZE as u64, &encrypted_header)?;
match options.wipe_mode {
WipeMode::Random => {
let chunk_size = 64 * 1024; let mut offset = data_offset;
while offset < data_offset + data_size {
let remaining = (data_offset + data_size - offset) as usize;
let size = core::cmp::min(chunk_size, remaining);
let random_data = generate_random_bytes(size);
storage.write(offset, &random_data)?;
offset += size as u64;
}
}
WipeMode::Zero => {
}
WipeMode::None => {
}
}
Ok(storage)
}
fn encrypt_header(
header: &VaultHeader,
header_key: &[u8; 64],
algorithm: EncryptionAlgorithm,
) -> Result<Vec<u8>, VaultError> {
let mut header_bytes = header.to_bytes();
encrypt_sector(
&mut header_bytes,
0,
&header_key[..32],
&header_key[32..],
algorithm,
);
Ok(header_bytes.to_vec())
}
fn decrypt_header(
encrypted: &[u8],
header_key: &[u8; 64],
algorithm: EncryptionAlgorithm,
) -> Result<VaultHeader, VaultError> {
if encrypted.len() < 512 {
return Err(VaultError::CorruptedHeader);
}
let mut decrypted = [0u8; 512];
decrypted.copy_from_slice(&encrypted[..512]);
decrypt_sector(
&mut decrypted,
0,
&header_key[..32],
&header_key[32..],
algorithm,
);
let header = VaultHeader::from_bytes(&decrypted);
if !header.is_valid_magic() {
return Err(VaultError::InvalidMagic);
}
let expected_crc = crc32(&decrypted[256..512]);
if header.crc32 != expected_crc {
return Err(VaultError::CorruptedHeader);
}
Ok(header)
}
pub fn open_volume(
storage: &VolumeStorage,
password: &str,
options: &VaultMountOptions,
) -> Result<(VaultHeader, EncryptionAlgorithm), VaultError> {
let salt_bytes = storage.read(0, SALT_SIZE)?;
let salt: [u8; 64] = salt_bytes
.try_into()
.map_err(|_| VaultError::CorruptedHeader)?;
let keyfile_data: Vec<Vec<u8>> = Vec::new();
let keyfile_refs: Vec<&[u8]> = keyfile_data.iter().map(|v| v.as_slice()).collect();
let hashes = if let Some(hint) = options.hash_hint {
vec![hint]
} else {
vec![
HashAlgorithm::Sha512,
HashAlgorithm::Sha256,
HashAlgorithm::Blake3,
HashAlgorithm::Whirlpool,
HashAlgorithm::Argon2id,
]
};
let encryptions = if let Some(hint) = options.encryption_hint {
vec![hint]
} else {
vec![
EncryptionAlgorithm::Aes256,
EncryptionAlgorithm::AesTwofish,
EncryptionAlgorithm::AesTwofishSerpent,
EncryptionAlgorithm::Serpent256,
EncryptionAlgorithm::Twofish256,
EncryptionAlgorithm::ChaCha20Poly1305,
EncryptionAlgorithm::Aes256MlKem1024,
EncryptionAlgorithm::ChaCha20MlKem1024,
EncryptionAlgorithm::AesTwofishSerpentMlKem1024,
]
};
let encrypted_header = storage.read(SALT_SIZE as u64, 512)?;
for hash in &hashes {
let header_key = derive_with_keyfiles(
password.as_bytes(),
&keyfile_refs,
&salt,
options.pim,
*hash,
);
for enc in &encryptions {
if let Ok(header) = decrypt_header(&encrypted_header, &header_key, *enc) {
return Ok((header, *enc));
}
}
}
if options.try_backup_header {
let backup_offset = storage.size() - HEADER_SIZE as u64;
let backup_salt = storage.read(backup_offset, SALT_SIZE)?;
let backup_header = storage.read(backup_offset + SALT_SIZE as u64, 512)?;
let salt: [u8; 64] = backup_salt
.try_into()
.map_err(|_| VaultError::CorruptedHeader)?;
for hash in &hashes {
let header_key = derive_with_keyfiles(
password.as_bytes(),
&keyfile_refs,
&salt,
options.pim,
*hash,
);
for enc in &encryptions {
if let Ok(header) = decrypt_header(&backup_header, &header_key, *enc) {
return Ok((header, *enc));
}
}
}
}
if options.try_hidden {
return open_hidden_volume(storage, password, options);
}
Err(VaultError::InvalidPassword)
}
fn open_hidden_volume(
storage: &VolumeStorage,
password: &str,
options: &VaultMountOptions,
) -> Result<(VaultHeader, EncryptionAlgorithm), VaultError> {
let hidden_offset = storage.size() - (HEADER_SIZE as u64 * 2);
let salt_bytes = storage.read(hidden_offset, SALT_SIZE)?;
let salt: [u8; 64] = salt_bytes
.try_into()
.map_err(|_| VaultError::HiddenVolumeNotFound)?;
let encrypted_header = storage.read(hidden_offset + SALT_SIZE as u64, 512)?;
let keyfile_data: Vec<Vec<u8>> = Vec::new();
let keyfile_refs: Vec<&[u8]> = keyfile_data.iter().map(|v| v.as_slice()).collect();
let hashes = vec![
HashAlgorithm::Sha512,
HashAlgorithm::Sha256,
HashAlgorithm::Blake3,
];
let encryptions = vec![
EncryptionAlgorithm::Aes256,
EncryptionAlgorithm::AesTwofishSerpent,
];
for hash in &hashes {
let header_key = derive_with_keyfiles(
password.as_bytes(),
&keyfile_refs,
&salt,
options.pim,
*hash,
);
for enc in &encryptions {
if let Ok(header) = decrypt_header(&encrypted_header, &header_key, *enc) {
return Ok((header, *enc));
}
}
}
Err(VaultError::HiddenVolumeNotFound)
}
pub fn create_hidden_volume(
storage: &mut VolumeStorage,
outer_password: &str,
hidden_password: &str,
hidden_size: u64,
options: &VaultCreateOptions,
) -> Result<(), VaultError> {
let mount_options = VaultMountOptions {
pim: options.pim,
try_backup_header: true,
..Default::default()
};
let (outer_header, _outer_enc) = open_volume(storage, outer_password, &mount_options)?;
let max_hidden_size = outer_header.data_size / 2; if hidden_size > max_hidden_size {
return Err(VaultError::InsufficientSpace);
}
let hidden_data_offset = outer_header.data_offset + outer_header.data_size - hidden_size;
let hidden_salt = generate_salt();
let hidden_master_key = generate_master_key();
let hidden_secondary_key = generate_master_key();
let mut hidden_header = VaultHeader::new();
hidden_header.magic = VERA_MAGIC;
hidden_header.version = 5;
hidden_header.min_version = 5;
hidden_header.volume_size = hidden_size;
hidden_header.data_offset = hidden_data_offset;
hidden_header.data_size = hidden_size - HEADER_SIZE as u64;
hidden_header.sector_size = SECTOR_SIZE as u32;
hidden_header.master_key.copy_from_slice(&hidden_master_key);
hidden_header
.secondary_key
.copy_from_slice(&hidden_secondary_key);
hidden_header.salt.copy_from_slice(&hidden_salt);
if options.filesystem == FilesystemType::Lcpfs {
hidden_header.flags.insert(VaultFlags::LCPFS_NATIVE);
}
let header_bytes = hidden_header.to_bytes();
hidden_header.crc32 = crc32(&header_bytes[256..512]);
let hidden_header_key = derive_header_key(
hidden_password.as_bytes(),
&hidden_salt,
options.pim,
options.hash,
);
let encrypted_hidden = encrypt_header(&hidden_header, &hidden_header_key, options.encryption)?;
let hidden_header_offset = storage.size() - (HEADER_SIZE as u64 * 2);
storage.write(hidden_header_offset, &hidden_salt)?;
storage.write(hidden_header_offset + SALT_SIZE as u64, &encrypted_hidden)?;
Ok(())
}
pub fn change_password(
storage: &mut VolumeStorage,
old_password: &str,
new_password: &str,
options: &VaultMountOptions,
new_pim: Option<u32>,
new_hash: Option<HashAlgorithm>,
) -> Result<(), VaultError> {
let (mut header, encryption) = open_volume(storage, old_password, options)?;
let salt_bytes = storage.read(0, SALT_SIZE)?;
let original_salt: [u8; 64] = salt_bytes
.try_into()
.map_err(|_| VaultError::CorruptedHeader)?;
let new_salt = generate_salt();
let pim = new_pim.unwrap_or(options.pim);
let hash = new_hash.unwrap_or(options.hash_hint.unwrap_or(HashAlgorithm::Sha512));
let new_header_key = derive_header_key(new_password.as_bytes(), &new_salt, pim, hash);
header.salt.copy_from_slice(&new_salt);
let header_bytes = header.to_bytes();
header.crc32 = crc32(&header_bytes[256..512]);
let encrypted = encrypt_header(&header, &new_header_key, encryption)?;
storage.write(0, &new_salt)?;
storage.write(SALT_SIZE as u64, &encrypted)?;
let backup_offset = storage.size() - HEADER_SIZE as u64;
storage.write(backup_offset, &new_salt)?;
storage.write(backup_offset + SALT_SIZE as u64, &encrypted)?;
Ok(())
}
pub fn backup_header(storage: &VolumeStorage) -> Result<Vec<u8>, VaultError> {
let salt = storage.read(0, SALT_SIZE)?;
let encrypted = storage.read(SALT_SIZE as u64, ENCRYPTED_HEADER_SIZE + 64)?;
let mut backup = Vec::with_capacity(SALT_SIZE + ENCRYPTED_HEADER_SIZE + 64);
backup.extend_from_slice(&salt);
backup.extend_from_slice(&encrypted);
Ok(backup)
}
pub fn restore_header(storage: &mut VolumeStorage, backup: &[u8]) -> Result<(), VaultError> {
if backup.len() < SALT_SIZE + 512 {
return Err(VaultError::CorruptedHeader);
}
storage.write(0, &backup[..SALT_SIZE + 512])?;
let backup_offset = storage.size() - HEADER_SIZE as u64;
storage.write(backup_offset, &backup[..SALT_SIZE + 512])?;
Ok(())
}
pub struct VolumeInfo {
pub size: u64,
pub data_size: u64,
pub has_backup_header: bool,
pub may_have_hidden: bool,
}
pub fn get_volume_info(storage: &VolumeStorage) -> VolumeInfo {
VolumeInfo {
size: storage.size(),
data_size: storage.size().saturating_sub(HEADER_SIZE as u64 * 2),
has_backup_header: storage.size() >= HEADER_SIZE as u64 * 2,
may_have_hidden: true, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_volume() {
let options = VaultCreateOptions {
size: 2 * 1024 * 1024, encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256, wipe_mode: WipeMode::Zero,
..Default::default()
};
let storage = create_volume("test_password", &options).unwrap();
assert_eq!(storage.size(), 2 * 1024 * 1024);
}
#[test]
fn test_open_volume() {
let password = "secure_password_123";
let options = VaultCreateOptions {
size: 2 * 1024 * 1024,
encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256,
wipe_mode: WipeMode::Zero,
..Default::default()
};
let storage = create_volume(password, &options).unwrap();
let mount_options = VaultMountOptions {
hash_hint: Some(HashAlgorithm::Sha256),
encryption_hint: Some(EncryptionAlgorithm::Aes256),
..Default::default()
};
let (header, enc) = open_volume(&storage, password, &mount_options).unwrap();
assert_eq!(header.magic, VERA_MAGIC);
assert_eq!(header.volume_size, 2 * 1024 * 1024);
assert_eq!(enc, EncryptionAlgorithm::Aes256);
}
#[test]
fn test_wrong_password() {
let options = VaultCreateOptions {
size: 2 * 1024 * 1024,
encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256,
wipe_mode: WipeMode::Zero,
..Default::default()
};
let storage = create_volume("correct_password", &options).unwrap();
let mount_options = VaultMountOptions {
hash_hint: Some(HashAlgorithm::Sha256),
encryption_hint: Some(EncryptionAlgorithm::Aes256),
try_backup_header: false,
try_hidden: false,
..Default::default()
};
let result = open_volume(&storage, "wrong_password", &mount_options);
assert!(matches!(result, Err(VaultError::InvalidPassword)));
}
#[test]
fn test_hidden_volume() {
let outer_password = "outer_secret";
let hidden_password = "hidden_secret";
let options = VaultCreateOptions {
size: 10 * 1024 * 1024, encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256,
wipe_mode: WipeMode::Zero,
..Default::default()
};
let mut storage = create_volume(outer_password, &options).unwrap();
let hidden_options = VaultCreateOptions {
size: 2 * 1024 * 1024, encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256,
wipe_mode: WipeMode::Zero,
..Default::default()
};
create_hidden_volume(
&mut storage,
outer_password,
hidden_password,
2 * 1024 * 1024,
&hidden_options,
)
.unwrap();
let mount_options = VaultMountOptions {
hash_hint: Some(HashAlgorithm::Sha256),
encryption_hint: Some(EncryptionAlgorithm::Aes256),
..Default::default()
};
let (outer_header, _) = open_volume(&storage, outer_password, &mount_options).unwrap();
assert_eq!(outer_header.volume_size, 10 * 1024 * 1024);
let hidden_mount_options = VaultMountOptions {
hash_hint: Some(HashAlgorithm::Sha256),
encryption_hint: Some(EncryptionAlgorithm::Aes256),
try_hidden: true,
..Default::default()
};
let (hidden_header, _) =
open_volume(&storage, hidden_password, &hidden_mount_options).unwrap();
assert!(hidden_header.volume_size < 10 * 1024 * 1024);
}
#[test]
fn test_change_password() {
let old_password = "old_password";
let new_password = "new_password";
let options = VaultCreateOptions {
size: 2 * 1024 * 1024,
encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256,
wipe_mode: WipeMode::Zero,
..Default::default()
};
let mut storage = create_volume(old_password, &options).unwrap();
let mount_options = VaultMountOptions {
hash_hint: Some(HashAlgorithm::Sha256),
encryption_hint: Some(EncryptionAlgorithm::Aes256),
..Default::default()
};
change_password(
&mut storage,
old_password,
new_password,
&mount_options,
None,
None,
)
.unwrap();
let result = open_volume(&storage, old_password, &mount_options);
assert!(result.is_err());
let (header, _) = open_volume(&storage, new_password, &mount_options).unwrap();
assert_eq!(header.magic, VERA_MAGIC);
}
#[test]
fn test_backup_restore_header() {
let password = "test_password";
let options = VaultCreateOptions {
size: 2 * 1024 * 1024,
encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256,
wipe_mode: WipeMode::Zero,
..Default::default()
};
let mut storage = create_volume(password, &options).unwrap();
let backup = backup_header(&storage).unwrap();
storage.write(0, &[0u8; SALT_SIZE]).unwrap();
restore_header(&mut storage, &backup).unwrap();
let mount_options = VaultMountOptions {
hash_hint: Some(HashAlgorithm::Sha256),
encryption_hint: Some(EncryptionAlgorithm::Aes256),
..Default::default()
};
let (header, _) = open_volume(&storage, password, &mount_options).unwrap();
assert_eq!(header.magic, VERA_MAGIC);
}
}