#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
pub const VERA_MAGIC: [u8; 4] = *b"VERA";
pub const TRUE_MAGIC: [u8; 4] = *b"TRUE";
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 = 448;
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_PIM: u32 = 0;
pub const SHA512_BASE_ITERATIONS: u32 = 500000;
pub const SHA256_BASE_ITERATIONS: u32 = 200000;
pub const PIM_MULTIPLIER: u32 = 1000;
#[derive(Clone)]
#[repr(C)]
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: VaultFlags,
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: VERA_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: VaultFlags::default(),
sector_size: SECTOR_SIZE as u32,
reserved2: [0; 120],
master_key: [0; 64],
secondary_key: [0; 64],
salt: [0; 64],
}
}
pub fn is_valid_magic(&self) -> bool {
self.magic == VERA_MAGIC || self.magic == TRUE_MAGIC || self.magic == LUNA_MAGIC
}
pub fn to_bytes(&self) -> [u8; 512] {
let mut buf = [0u8; 512];
buf[0..4].copy_from_slice(&self.magic);
buf[4..6].copy_from_slice(&self.version.to_le_bytes());
buf[6..8].copy_from_slice(&self.min_version.to_le_bytes());
buf[8..12].copy_from_slice(&self.crc32.to_le_bytes());
buf[12..28].copy_from_slice(&self.reserved1);
buf[28..36].copy_from_slice(&self.hidden_size.to_le_bytes());
buf[36..44].copy_from_slice(&self.volume_size.to_le_bytes());
buf[44..52].copy_from_slice(&self.data_offset.to_le_bytes());
buf[52..60].copy_from_slice(&self.data_size.to_le_bytes());
buf[60..64].copy_from_slice(&self.flags.bits().to_le_bytes());
buf[64..68].copy_from_slice(&self.sector_size.to_le_bytes());
buf[68..188].copy_from_slice(&self.reserved2);
buf[188..252].copy_from_slice(&self.master_key);
buf[252..316].copy_from_slice(&self.secondary_key);
buf[316..380].copy_from_slice(&self.salt);
buf
}
pub fn from_bytes(buf: &[u8; 512]) -> Self {
let mut header = Self::new();
header.magic.copy_from_slice(&buf[0..4]);
header.version = u16::from_le_bytes([buf[4], buf[5]]);
header.min_version = u16::from_le_bytes([buf[6], buf[7]]);
header.crc32 = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]);
header.reserved1.copy_from_slice(&buf[12..28]);
header.hidden_size = u64::from_le_bytes(buf[28..36].try_into().unwrap());
header.volume_size = u64::from_le_bytes(buf[36..44].try_into().unwrap());
header.data_offset = u64::from_le_bytes(buf[44..52].try_into().unwrap());
header.data_size = u64::from_le_bytes(buf[52..60].try_into().unwrap());
header.flags =
VaultFlags::from_bits_truncate(u32::from_le_bytes(buf[60..64].try_into().unwrap()));
header.sector_size = u32::from_le_bytes(buf[64..68].try_into().unwrap());
header.reserved2.copy_from_slice(&buf[68..188]);
header.master_key.copy_from_slice(&buf[188..252]);
header.secondary_key.copy_from_slice(&buf[252..316]);
header.salt.copy_from_slice(&buf[316..380]);
header
}
}
impl Default for VaultHeader {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct VaultFlags(u32);
impl VaultFlags {
pub const NONE: Self = Self(0);
pub const SYSTEM: Self = Self(1 << 0);
pub const HIDDEN_OS: Self = Self(1 << 1);
pub const READ_ONLY: Self = Self(1 << 2);
pub const BACKUP_HEADER: Self = Self(1 << 3);
pub const LCPFS_NATIVE: Self = Self(1 << 16);
pub const CHACHA20: Self = Self(1 << 17);
pub const fn bits(&self) -> u32 {
self.0
}
pub const fn from_bits_truncate(bits: u32) -> Self {
Self(bits)
}
pub const fn contains(&self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
pub fn remove(&mut self, other: Self) {
self.0 &= !other.0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EncryptionAlgorithm {
#[default]
Aes256,
Serpent256,
Twofish256,
AesTwofish,
TwofishAes,
AesTwofishSerpent,
SerpentTwofishAes,
SerpentAes,
ChaCha20Poly1305,
Aes256MlKem1024,
ChaCha20MlKem1024,
AesTwofishSerpentMlKem1024,
}
impl EncryptionAlgorithm {
pub fn key_count(&self) -> usize {
match self {
Self::Aes256 | Self::Serpent256 | Self::Twofish256 | Self::ChaCha20Poly1305 => 1,
Self::Aes256MlKem1024 | Self::ChaCha20MlKem1024 => 1,
Self::AesTwofish | Self::TwofishAes | Self::SerpentAes => 2,
Self::AesTwofishSerpent | Self::SerpentTwofishAes => 3,
Self::AesTwofishSerpentMlKem1024 => 3,
}
}
pub fn total_key_size(&self) -> usize {
self.key_count() * 64
}
pub fn is_cascade(&self) -> bool {
self.key_count() > 1
}
pub fn is_post_quantum(&self) -> bool {
matches!(
self,
Self::Aes256MlKem1024 | Self::ChaCha20MlKem1024 | Self::AesTwofishSerpentMlKem1024
)
}
pub fn id(&self) -> u8 {
match self {
Self::Aes256 => 0,
Self::Serpent256 => 1,
Self::Twofish256 => 2,
Self::AesTwofish => 3,
Self::TwofishAes => 4,
Self::AesTwofishSerpent => 5,
Self::SerpentTwofishAes => 6,
Self::SerpentAes => 7,
Self::ChaCha20Poly1305 => 128,
Self::Aes256MlKem1024 => 192,
Self::ChaCha20MlKem1024 => 193,
Self::AesTwofishSerpentMlKem1024 => 194,
}
}
pub fn from_id(id: u8) -> Option<Self> {
match id {
0 => Some(Self::Aes256),
1 => Some(Self::Serpent256),
2 => Some(Self::Twofish256),
3 => Some(Self::AesTwofish),
4 => Some(Self::TwofishAes),
5 => Some(Self::AesTwofishSerpent),
6 => Some(Self::SerpentTwofishAes),
7 => Some(Self::SerpentAes),
128 => Some(Self::ChaCha20Poly1305),
192 => Some(Self::Aes256MlKem1024),
193 => Some(Self::ChaCha20MlKem1024),
194 => Some(Self::AesTwofishSerpentMlKem1024),
_ => None,
}
}
}
impl fmt::Display for EncryptionAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Aes256 => write!(f, "AES-256-XTS"),
Self::Serpent256 => write!(f, "Serpent-256-XTS"),
Self::Twofish256 => write!(f, "Twofish-256-XTS"),
Self::AesTwofish => write!(f, "AES-Twofish"),
Self::TwofishAes => write!(f, "Twofish-AES"),
Self::AesTwofishSerpent => write!(f, "AES-Twofish-Serpent"),
Self::SerpentTwofishAes => write!(f, "Serpent-Twofish-AES"),
Self::SerpentAes => write!(f, "Serpent-AES"),
Self::ChaCha20Poly1305 => write!(f, "ChaCha20-Poly1305"),
Self::Aes256MlKem1024 => write!(f, "AES-256-XTS + ML-KEM-1024 (Hybrid PQC)"),
Self::ChaCha20MlKem1024 => write!(f, "ChaCha20-Poly1305 + ML-KEM-1024 (Hybrid PQC)"),
Self::AesTwofishSerpentMlKem1024 => {
write!(f, "AES-Twofish-Serpent + ML-KEM-1024 (Hybrid PQC)")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HashAlgorithm {
#[default]
Sha512,
Sha256,
Blake3,
Argon2id,
Whirlpool,
Ripemd160,
}
impl HashAlgorithm {
pub fn base_iterations(&self) -> u32 {
match self {
Self::Sha512 => SHA512_BASE_ITERATIONS,
Self::Sha256 => SHA256_BASE_ITERATIONS,
Self::Blake3 => SHA512_BASE_ITERATIONS,
Self::Argon2id => 3, Self::Whirlpool => SHA512_BASE_ITERATIONS,
Self::Ripemd160 => 655331, }
}
pub fn iterations_with_pim(&self, pim: u32) -> u32 {
if *self == Self::Argon2id {
3 + pim
} else {
self.base_iterations() + (pim * PIM_MULTIPLIER)
}
}
pub fn output_size(&self) -> usize {
match self {
Self::Sha512 => 64,
Self::Sha256 => 32,
Self::Blake3 => 32,
Self::Argon2id => 64,
Self::Whirlpool => 64,
Self::Ripemd160 => 20,
}
}
pub fn id(&self) -> u8 {
match self {
Self::Sha512 => 0,
Self::Sha256 => 1,
Self::Blake3 => 2,
Self::Argon2id => 3,
Self::Whirlpool => 4,
Self::Ripemd160 => 5,
}
}
pub fn from_id(id: u8) -> Option<Self> {
match id {
0 => Some(Self::Sha512),
1 => Some(Self::Sha256),
2 => Some(Self::Blake3),
3 => Some(Self::Argon2id),
4 => Some(Self::Whirlpool),
5 => Some(Self::Ripemd160),
_ => None,
}
}
}
impl fmt::Display for HashAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Sha512 => write!(f, "SHA-512"),
Self::Sha256 => write!(f, "SHA-256"),
Self::Blake3 => write!(f, "BLAKE3"),
Self::Argon2id => write!(f, "Argon2id"),
Self::Whirlpool => write!(f, "Whirlpool"),
Self::Ripemd160 => write!(f, "RIPEMD-160"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FilesystemType {
#[default]
Lcpfs,
Fat32,
ExFat,
Ntfs,
Ext4,
None,
}
#[derive(Debug, Clone)]
pub enum VaultError {
InvalidPassword,
CorruptedHeader,
InvalidMagic,
VolumeTooSmall,
IoError(String),
EncryptionError(String),
HeaderNotFound,
HiddenVolumeNotFound,
AlreadyMounted,
NotMounted,
UnsupportedAlgorithm,
InsufficientSpace,
ProtectedAreaViolation,
VersionMismatch {
required: u16,
found: u16,
},
}
impl fmt::Display for VaultError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidPassword => write!(f, "Invalid password or keyfile"),
Self::CorruptedHeader => write!(f, "Corrupted header (CRC mismatch)"),
Self::InvalidMagic => write!(f, "Invalid magic bytes"),
Self::VolumeTooSmall => write!(f, "Volume size too small (minimum 1 MB)"),
Self::IoError(msg) => write!(f, "IO error: {}", msg),
Self::EncryptionError(msg) => write!(f, "Encryption error: {}", msg),
Self::HeaderNotFound => write!(f, "Header not found"),
Self::HiddenVolumeNotFound => write!(f, "Hidden volume not found"),
Self::AlreadyMounted => write!(f, "Volume already mounted"),
Self::NotMounted => write!(f, "Volume not mounted"),
Self::UnsupportedAlgorithm => write!(f, "Unsupported algorithm"),
Self::InsufficientSpace => write!(f, "Insufficient space for hidden volume"),
Self::ProtectedAreaViolation => write!(f, "Write to protected hidden volume area"),
Self::VersionMismatch { required, found } => {
write!(
f,
"Version mismatch: requires {}, found {}",
required, found
)
}
}
}
}
#[derive(Clone)]
pub struct VaultCreateOptions {
pub size: u64,
pub encryption: EncryptionAlgorithm,
pub hash: HashAlgorithm,
pub pim: u32,
pub keyfiles: Vec<String>,
pub filesystem: FilesystemType,
pub wipe_mode: WipeMode,
}
impl Default for VaultCreateOptions {
fn default() -> Self {
Self {
size: 100 * 1024 * 1024, encryption: EncryptionAlgorithm::default(),
hash: HashAlgorithm::default(),
pim: DEFAULT_PIM,
keyfiles: Vec::new(),
filesystem: FilesystemType::default(),
wipe_mode: WipeMode::Random,
}
}
}
#[derive(Clone, Default)]
pub struct VaultMountOptions {
pub pim: u32,
pub hash_hint: Option<HashAlgorithm>,
pub encryption_hint: Option<EncryptionAlgorithm>,
pub keyfiles: Vec<String>,
pub read_only: bool,
pub protect_hidden: bool,
pub hidden_password: Option<String>,
pub try_backup_header: bool,
pub try_hidden: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WipeMode {
#[default]
Random,
Zero,
None,
}
pub const PQC_HYBRID_CT_BYTES: usize = 1600;
pub const PQC_HYBRID_PK_BYTES: usize = 1600;
pub const PQC_HYBRID_SIG_BYTES: usize = 3373;
pub const PQC_HYBRID_SIG_PK_BYTES: usize = 1984;
pub const PQC_HEADER_EXT_SIZE: usize = 12288;
#[derive(Clone)]
#[repr(C)]
pub struct PqcHeaderExtension {
pub magic: [u8; 4],
pub version: u16,
pub reserved1: [u8; 2],
pub flags: u32,
pub algorithm_id: u8,
pub reserved2: [u8; 3],
pub kem_ciphertext: [u8; PQC_HYBRID_CT_BYTES],
pub recipient_public_key: [u8; PQC_HYBRID_PK_BYTES],
pub wrapped_master_key: [u8; 156],
pub header_signature: [u8; PQC_HYBRID_SIG_BYTES],
pub signer_public_key: [u8; PQC_HYBRID_SIG_PK_BYTES],
pub crc32: u32,
pub reserved_padding: [u8; 3555],
}
pub const PQC_MAGIC: [u8; 4] = *b"PQC1";
impl PqcHeaderExtension {
pub fn new() -> Self {
Self {
magic: PQC_MAGIC,
version: 1,
reserved1: [0; 2],
flags: 0,
algorithm_id: 0,
reserved2: [0; 3],
kem_ciphertext: [0; PQC_HYBRID_CT_BYTES],
recipient_public_key: [0; PQC_HYBRID_PK_BYTES],
wrapped_master_key: [0; 156],
header_signature: [0; PQC_HYBRID_SIG_BYTES],
signer_public_key: [0; PQC_HYBRID_SIG_PK_BYTES],
crc32: 0,
reserved_padding: [0; 3555],
}
}
pub fn is_valid_magic(&self) -> bool {
self.magic == PQC_MAGIC
}
pub fn to_bytes(&self) -> [u8; PQC_HEADER_EXT_SIZE] {
let mut buf = [0u8; PQC_HEADER_EXT_SIZE];
let mut offset = 0;
buf[offset..offset + 4].copy_from_slice(&self.magic);
offset += 4;
buf[offset..offset + 2].copy_from_slice(&self.version.to_le_bytes());
offset += 2;
buf[offset..offset + 2].copy_from_slice(&self.reserved1);
offset += 2;
buf[offset..offset + 4].copy_from_slice(&self.flags.to_le_bytes());
offset += 4;
buf[offset] = self.algorithm_id;
offset += 1;
buf[offset..offset + 3].copy_from_slice(&self.reserved2);
offset += 3;
buf[offset..offset + PQC_HYBRID_CT_BYTES].copy_from_slice(&self.kem_ciphertext);
offset += PQC_HYBRID_CT_BYTES;
buf[offset..offset + PQC_HYBRID_PK_BYTES].copy_from_slice(&self.recipient_public_key);
offset += PQC_HYBRID_PK_BYTES;
buf[offset..offset + 156].copy_from_slice(&self.wrapped_master_key);
offset += 156;
buf[offset..offset + PQC_HYBRID_SIG_BYTES].copy_from_slice(&self.header_signature);
offset += PQC_HYBRID_SIG_BYTES;
buf[offset..offset + PQC_HYBRID_SIG_PK_BYTES].copy_from_slice(&self.signer_public_key);
offset += PQC_HYBRID_SIG_PK_BYTES;
buf[offset..offset + 4].copy_from_slice(&self.crc32.to_le_bytes());
offset += 4;
buf[offset..offset + 3555].copy_from_slice(&self.reserved_padding);
buf
}
pub fn from_bytes(buf: &[u8]) -> Option<Self> {
if buf.len() < PQC_HEADER_EXT_SIZE {
return None;
}
let mut ext = Self::new();
let mut offset = 0;
ext.magic.copy_from_slice(&buf[offset..offset + 4]);
offset += 4;
ext.version = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
offset += 2;
ext.reserved1.copy_from_slice(&buf[offset..offset + 2]);
offset += 2;
ext.flags = u32::from_le_bytes(buf[offset..offset + 4].try_into().ok()?);
offset += 4;
ext.algorithm_id = buf[offset];
offset += 1;
ext.reserved2.copy_from_slice(&buf[offset..offset + 3]);
offset += 3;
ext.kem_ciphertext
.copy_from_slice(&buf[offset..offset + PQC_HYBRID_CT_BYTES]);
offset += PQC_HYBRID_CT_BYTES;
ext.recipient_public_key
.copy_from_slice(&buf[offset..offset + PQC_HYBRID_PK_BYTES]);
offset += PQC_HYBRID_PK_BYTES;
ext.wrapped_master_key
.copy_from_slice(&buf[offset..offset + 156]);
offset += 156;
ext.header_signature
.copy_from_slice(&buf[offset..offset + PQC_HYBRID_SIG_BYTES]);
offset += PQC_HYBRID_SIG_BYTES;
ext.signer_public_key
.copy_from_slice(&buf[offset..offset + PQC_HYBRID_SIG_PK_BYTES]);
offset += PQC_HYBRID_SIG_PK_BYTES;
ext.crc32 = u32::from_le_bytes(buf[offset..offset + 4].try_into().ok()?);
Some(ext)
}
}
impl Default for PqcHeaderExtension {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct VaultMount {
pub container_path: String,
pub mount_point: String,
pub device_id: u64,
pub is_hidden: bool,
pub read_only: bool,
pub protected_start: Option<u64>,
pub protected_end: Option<u64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_header_serialize_deserialize() {
let mut header = VaultHeader::new();
header.volume_size = 100 * 1024 * 1024;
header.data_size = header.volume_size - (HEADER_SIZE as u64 * 2);
header.master_key[0] = 0xAB;
header.salt[0] = 0xCD;
let bytes = header.to_bytes();
let restored = VaultHeader::from_bytes(&bytes);
assert_eq!(restored.magic, VERA_MAGIC);
assert_eq!(restored.volume_size, header.volume_size);
assert_eq!(restored.data_size, header.data_size);
assert_eq!(restored.master_key[0], 0xAB);
assert_eq!(restored.salt[0], 0xCD);
}
#[test]
fn test_valid_magic() {
let mut header = VaultHeader::new();
assert!(header.is_valid_magic());
header.magic = TRUE_MAGIC;
assert!(header.is_valid_magic());
header.magic = LUNA_MAGIC;
assert!(header.is_valid_magic());
header.magic = *b"FAKE";
assert!(!header.is_valid_magic());
}
#[test]
fn test_encryption_algorithm() {
assert_eq!(EncryptionAlgorithm::Aes256.key_count(), 1);
assert_eq!(EncryptionAlgorithm::AesTwofish.key_count(), 2);
assert_eq!(EncryptionAlgorithm::AesTwofishSerpent.key_count(), 3);
assert!(!EncryptionAlgorithm::Aes256.is_cascade());
assert!(EncryptionAlgorithm::AesTwofishSerpent.is_cascade());
let id = EncryptionAlgorithm::Serpent256.id();
assert_eq!(
EncryptionAlgorithm::from_id(id),
Some(EncryptionAlgorithm::Serpent256)
);
}
#[test]
fn test_pqc_algorithms() {
assert!(EncryptionAlgorithm::Aes256MlKem1024.is_post_quantum());
assert!(EncryptionAlgorithm::ChaCha20MlKem1024.is_post_quantum());
assert!(EncryptionAlgorithm::AesTwofishSerpentMlKem1024.is_post_quantum());
assert!(!EncryptionAlgorithm::Aes256.is_post_quantum());
assert!(!EncryptionAlgorithm::ChaCha20Poly1305.is_post_quantum());
assert!(!EncryptionAlgorithm::AesTwofishSerpent.is_post_quantum());
let pqc_id = EncryptionAlgorithm::Aes256MlKem1024.id();
assert_eq!(
EncryptionAlgorithm::from_id(pqc_id),
Some(EncryptionAlgorithm::Aes256MlKem1024)
);
let cascade_pqc_id = EncryptionAlgorithm::AesTwofishSerpentMlKem1024.id();
assert_eq!(
EncryptionAlgorithm::from_id(cascade_pqc_id),
Some(EncryptionAlgorithm::AesTwofishSerpentMlKem1024)
);
assert_eq!(
EncryptionAlgorithm::AesTwofishSerpentMlKem1024.key_count(),
3
);
assert!(EncryptionAlgorithm::AesTwofishSerpentMlKem1024.is_cascade());
}
#[test]
fn test_hash_algorithm_iterations() {
let sha512 = HashAlgorithm::Sha512;
assert_eq!(sha512.iterations_with_pim(0), 500000);
assert_eq!(sha512.iterations_with_pim(10), 510000);
let argon2 = HashAlgorithm::Argon2id;
assert_eq!(argon2.iterations_with_pim(0), 3);
assert_eq!(argon2.iterations_with_pim(5), 8);
}
#[test]
fn test_vault_flags() {
let mut flags = VaultFlags::NONE;
assert!(!flags.contains(VaultFlags::READ_ONLY));
flags.insert(VaultFlags::READ_ONLY);
assert!(flags.contains(VaultFlags::READ_ONLY));
flags.insert(VaultFlags::LCPFS_NATIVE);
assert!(flags.contains(VaultFlags::READ_ONLY));
assert!(flags.contains(VaultFlags::LCPFS_NATIVE));
flags.remove(VaultFlags::READ_ONLY);
assert!(!flags.contains(VaultFlags::READ_ONLY));
assert!(flags.contains(VaultFlags::LCPFS_NATIVE));
}
}