use std::collections::{HashMap, VecDeque};
pub type KeyId = [u8; 16];
pub type BlockCid = [u8; 32];
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SelError {
NoActiveKey,
KeyNotFound(KeyId),
BlockNotFound(BlockCid),
CipherError(String),
MacMismatch,
}
impl std::fmt::Display for SelError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoActiveKey => write!(f, "no active encryption key"),
Self::KeyNotFound(id) => write!(f, "key not found: {:?}", id),
Self::BlockNotFound(cid) => write!(f, "block not found in index: {:?}", cid),
Self::CipherError(msg) => write!(f, "cipher error: {msg}"),
Self::MacMismatch => write!(f, "MAC verification failed"),
}
}
}
impl std::error::Error for SelError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SelCipher {
#[default]
ChaCha20,
XSalsa20,
Xor256,
}
#[derive(Debug, Clone)]
pub struct SelEncryptionConfig {
pub cipher: SelCipher,
pub key_rotation_interval_secs: u64,
pub enable_audit: bool,
}
impl Default for SelEncryptionConfig {
fn default() -> Self {
Self {
cipher: SelCipher::ChaCha20,
key_rotation_interval_secs: 0,
enable_audit: true,
}
}
}
#[derive(Debug, Clone)]
pub struct SelEncryptionKey {
pub id: KeyId,
pub key_bytes: Vec<u8>,
pub created_at: u64,
pub rotated_from: Option<KeyId>,
}
pub type EncryptionKey = SelEncryptionKey;
#[derive(Debug, Clone)]
pub struct SelEncryptedBlockRecord {
pub cid: BlockCid,
pub encrypted_cid: [u8; 32],
pub key_id: KeyId,
pub nonce: [u8; 24],
pub size_enc: usize,
pub created_at: u64,
}
pub type EncryptedBlockRecord = SelEncryptedBlockRecord;
#[derive(Debug, Clone)]
pub struct EncAuditEntry {
pub ts: u64,
pub op: String,
pub key_id: Option<KeyId>,
pub block_cid: Option<BlockCid>,
}
#[derive(Debug, Clone, Default)]
pub struct SelEncryptionStats {
pub blocks_encrypted: u64,
pub blocks_decrypted: u64,
pub key_rotations: u64,
pub re_encryptions: u64,
pub mac_ok: u64,
pub mac_fail: u64,
pub key_count: usize,
pub index_size: usize,
pub audit_log_len: usize,
}
#[inline(always)]
fn xorshift64(state: &mut u64) -> u64 {
let mut x = *state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*state = x;
x
}
#[inline(always)]
fn fnv1a_64(data: &[u8]) -> u64 {
let mut h: u64 = 14_695_981_039_346_656_037;
for &b in data {
h ^= b as u64;
h = h.wrapping_mul(1_099_511_628_211);
}
h
}
fn unix_ts() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
#[inline(always)]
fn chacha20_quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
state[a] = state[a].wrapping_add(state[b]);
state[d] ^= state[a];
state[d] = state[d].rotate_left(16);
state[c] = state[c].wrapping_add(state[d]);
state[b] ^= state[c];
state[b] = state[b].rotate_left(12);
state[a] = state[a].wrapping_add(state[b]);
state[d] ^= state[a];
state[d] = state[d].rotate_left(8);
state[c] = state[c].wrapping_add(state[d]);
state[b] ^= state[c];
state[b] = state[b].rotate_left(7);
}
fn chacha20_block(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> [u8; 64] {
let mut state: [u32; 16] = [
0x6170_7865,
0x3320_646e,
0x7962_2d32,
0x6b20_6574,
u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
u32::from_le_bytes([key[8], key[9], key[10], key[11]]),
u32::from_le_bytes([key[12], key[13], key[14], key[15]]),
u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
counter,
u32::from_le_bytes([nonce[0], nonce[1], nonce[2], nonce[3]]),
u32::from_le_bytes([nonce[4], nonce[5], nonce[6], nonce[7]]),
u32::from_le_bytes([nonce[8], nonce[9], nonce[10], nonce[11]]),
];
let working = state;
let mut work = working;
for _ in 0..10 {
chacha20_quarter_round(&mut work, 0, 4, 8, 12);
chacha20_quarter_round(&mut work, 1, 5, 9, 13);
chacha20_quarter_round(&mut work, 2, 6, 10, 14);
chacha20_quarter_round(&mut work, 3, 7, 11, 15);
chacha20_quarter_round(&mut work, 0, 5, 10, 15);
chacha20_quarter_round(&mut work, 1, 6, 11, 12);
chacha20_quarter_round(&mut work, 2, 7, 8, 13);
chacha20_quarter_round(&mut work, 3, 4, 9, 14);
}
for (s, w) in state.iter_mut().zip(work.iter()) {
*s = s.wrapping_add(*w);
}
let mut out = [0u8; 64];
for (i, word) in state.iter().enumerate() {
let b = word.to_le_bytes();
out[i * 4..i * 4 + 4].copy_from_slice(&b);
}
out
}
fn chacha20_xor(key: &[u8; 32], nonce: &[u8; 12], input: &[u8]) -> Vec<u8> {
let mut output = Vec::with_capacity(input.len());
let mut counter: u32 = 0;
let mut offset = 0;
while offset < input.len() {
let block = chacha20_block(key, nonce, counter);
let block_end = (offset + 64).min(input.len());
let chunk = &input[offset..block_end];
for (b, k) in chunk.iter().zip(block.iter()) {
output.push(b ^ k);
}
offset += chunk.len();
counter = counter.wrapping_add(1);
}
output
}
#[inline(always)]
fn salsa20_quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
state[b] ^= state[a].wrapping_add(state[d]).rotate_left(7);
state[c] ^= state[b].wrapping_add(state[a]).rotate_left(9);
state[d] ^= state[c].wrapping_add(state[b]).rotate_left(13);
state[a] ^= state[d].wrapping_add(state[c]).rotate_left(18);
}
fn hsalsa20(key: &[u8; 32], nonce16: &[u8; 16]) -> [u8; 32] {
let mut state: [u32; 16] = [
0x6170_7865,
u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
u32::from_le_bytes([key[8], key[9], key[10], key[11]]),
u32::from_le_bytes([key[12], key[13], key[14], key[15]]),
0x3320_646e,
u32::from_le_bytes([nonce16[0], nonce16[1], nonce16[2], nonce16[3]]),
u32::from_le_bytes([nonce16[4], nonce16[5], nonce16[6], nonce16[7]]),
u32::from_le_bytes([nonce16[8], nonce16[9], nonce16[10], nonce16[11]]),
u32::from_le_bytes([nonce16[12], nonce16[13], nonce16[14], nonce16[15]]),
0x7962_2d32,
u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
0x6b20_6574,
];
for _ in 0..10 {
salsa20_quarter_round(&mut state, 0, 4, 8, 12);
salsa20_quarter_round(&mut state, 5, 9, 13, 1);
salsa20_quarter_round(&mut state, 10, 14, 2, 6);
salsa20_quarter_round(&mut state, 15, 3, 7, 11);
salsa20_quarter_round(&mut state, 0, 1, 2, 3);
salsa20_quarter_round(&mut state, 5, 6, 7, 4);
salsa20_quarter_round(&mut state, 10, 11, 8, 9);
salsa20_quarter_round(&mut state, 15, 12, 13, 14);
}
let mut subkey = [0u8; 32];
for (i, &idx) in [0usize, 5, 10, 15, 6, 7, 8, 9].iter().enumerate() {
let b = state[idx].to_le_bytes();
subkey[i * 4..i * 4 + 4].copy_from_slice(&b);
}
subkey
}
fn salsa20_block(key: &[u8; 32], nonce8: &[u8; 8], counter: u64) -> [u8; 64] {
let ctr_lo = counter as u32;
let ctr_hi = (counter >> 32) as u32;
let mut state: [u32; 16] = [
0x6170_7865,
u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
u32::from_le_bytes([key[8], key[9], key[10], key[11]]),
u32::from_le_bytes([key[12], key[13], key[14], key[15]]),
0x3320_646e,
u32::from_le_bytes([nonce8[0], nonce8[1], nonce8[2], nonce8[3]]),
u32::from_le_bytes([nonce8[4], nonce8[5], nonce8[6], nonce8[7]]),
ctr_lo,
ctr_hi,
0x7962_2d32,
u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
0x6b20_6574,
];
let working = state;
let mut work = working;
for _ in 0..10 {
salsa20_quarter_round(&mut work, 0, 4, 8, 12);
salsa20_quarter_round(&mut work, 5, 9, 13, 1);
salsa20_quarter_round(&mut work, 10, 14, 2, 6);
salsa20_quarter_round(&mut work, 15, 3, 7, 11);
salsa20_quarter_round(&mut work, 0, 1, 2, 3);
salsa20_quarter_round(&mut work, 5, 6, 7, 4);
salsa20_quarter_round(&mut work, 10, 11, 8, 9);
salsa20_quarter_round(&mut work, 15, 12, 13, 14);
}
for (s, w) in state.iter_mut().zip(work.iter()) {
*s = s.wrapping_add(*w);
}
let mut out = [0u8; 64];
for (i, word) in state.iter().enumerate() {
let b = word.to_le_bytes();
out[i * 4..i * 4 + 4].copy_from_slice(&b);
}
out
}
fn xsalsa20_xor(key: &[u8; 32], nonce: &[u8; 24], input: &[u8]) -> Vec<u8> {
let mut nonce16 = [0u8; 16];
nonce16.copy_from_slice(&nonce[0..16]);
let subkey = hsalsa20(key, &nonce16);
let mut nonce8 = [0u8; 8];
nonce8.copy_from_slice(&nonce[16..24]);
let mut output = Vec::with_capacity(input.len());
let mut counter: u64 = 0;
let mut offset = 0;
while offset < input.len() {
let block = salsa20_block(&subkey, &nonce8, counter);
let block_end = (offset + 64).min(input.len());
let chunk = &input[offset..block_end];
for (b, k) in chunk.iter().zip(block.iter()) {
output.push(b ^ k);
}
offset += chunk.len();
counter = counter.wrapping_add(1);
}
output
}
fn xor256_xor(key: &[u8; 32], input: &[u8]) -> Vec<u8> {
let mut pad = [0u8; 256];
let mut state: u64 = 0;
for (i, b) in key.iter().enumerate() {
state ^= (*b as u64) << ((i % 8) * 8);
}
if state == 0 {
state = 0xDEAD_BEEF_CAFE_BABE;
}
for chunk in pad.chunks_mut(8) {
let v = xorshift64(&mut state).to_le_bytes();
let len = chunk.len();
chunk.copy_from_slice(&v[..len]);
}
input
.iter()
.enumerate()
.map(|(i, &b)| b ^ pad[i % 256])
.collect()
}
fn nonce_from_seed(mut seed: u64) -> [u8; 24] {
if seed == 0 {
seed = 0x1234_5678_9ABC_DEF0;
}
let mut nonce = [0u8; 24];
for chunk in nonce.chunks_mut(8) {
let v = xorshift64(&mut seed).to_le_bytes();
let len = chunk.len();
chunk.copy_from_slice(&v[..len]);
}
nonce
}
fn derive_encrypted_cid(ciphertext: &[u8]) -> [u8; 32] {
let h = fnv1a_64(ciphertext);
let mut cid = [0u8; 32];
let bytes = h.to_le_bytes();
for i in 0..4 {
cid[i * 8..(i + 1) * 8].copy_from_slice(&bytes);
}
for i in 1..4usize {
let mix = (i as u64)
.wrapping_mul(0x9E37_79B9_7F4A_7C15u64)
.to_le_bytes();
for (j, b) in mix.iter().enumerate() {
cid[i * 8 + j] ^= b;
}
}
cid
}
pub struct StorageEncryptionLayer {
key_store: HashMap<KeyId, SelEncryptionKey>,
active_key: Option<KeyId>,
block_index: HashMap<BlockCid, SelEncryptedBlockRecord>,
audit_log: VecDeque<EncAuditEntry>,
config: SelEncryptionConfig,
prng_state: u64,
stats: SelEncryptionStats,
}
pub type SelStorageEncryptionLayer = StorageEncryptionLayer;
impl StorageEncryptionLayer {
pub fn new() -> Self {
Self::with_config(SelEncryptionConfig::default())
}
pub fn with_config(config: SelEncryptionConfig) -> Self {
let seed = unix_ts().wrapping_add(0xCAFE_BABE_1234_5678);
Self {
key_store: HashMap::new(),
active_key: None,
block_index: HashMap::new(),
audit_log: VecDeque::new(),
config,
prng_state: if seed == 0 { 1 } else { seed },
stats: SelEncryptionStats::default(),
}
}
fn audit(&mut self, op: &str, key_id: Option<KeyId>, block_cid: Option<BlockCid>) {
if !self.config.enable_audit {
return;
}
if self.audit_log.len() >= 1000 {
self.audit_log.pop_front();
}
self.audit_log.push_back(EncAuditEntry {
ts: unix_ts(),
op: op.to_owned(),
key_id,
block_cid,
});
}
fn next_nonce(&mut self) -> [u8; 24] {
nonce_from_seed(xorshift64(&mut self.prng_state))
}
pub fn generate_key(&mut self, seed: u64) -> KeyId {
let mut state = if seed == 0 {
0xDEAD_CAFE_0000_0001
} else {
seed
};
let mut key_bytes = vec![0u8; 32];
for chunk in key_bytes.chunks_mut(8) {
let v = xorshift64(&mut state).to_le_bytes();
let len = chunk.len();
chunk.copy_from_slice(&v[..len]);
}
let h1 = fnv1a_64(&key_bytes);
let h2 = fnv1a_64(&h1.to_le_bytes());
let mut id = [0u8; 16];
id[0..8].copy_from_slice(&h1.to_le_bytes());
id[8..16].copy_from_slice(&h2.to_le_bytes());
let enc_key = SelEncryptionKey {
id,
key_bytes,
created_at: unix_ts(),
rotated_from: None,
};
self.key_store.insert(id, enc_key);
self.stats.key_count = self.key_store.len();
self.audit("generate_key", Some(id), None);
id
}
pub fn set_active_key(&mut self, key_id: KeyId) -> Result<(), SelError> {
if !self.key_store.contains_key(&key_id) {
return Err(SelError::KeyNotFound(key_id));
}
self.active_key = Some(key_id);
self.audit("set_active_key", Some(key_id), None);
Ok(())
}
pub fn rotate_key(&mut self, seed: u64) -> KeyId {
let old_key_id = self.active_key;
let new_id = self.generate_key(seed);
if let Some(old) = old_key_id {
if let Some(k) = self.key_store.get_mut(&new_id) {
k.rotated_from = Some(old);
}
}
self.active_key = Some(new_id);
self.stats.key_rotations += 1;
self.audit("rotate_key", Some(new_id), None);
new_id
}
pub fn active_key_id(&self) -> Result<KeyId, SelError> {
self.active_key.ok_or(SelError::NoActiveKey)
}
pub fn get_key(&self, key_id: &KeyId) -> Option<&SelEncryptionKey> {
self.key_store.get(key_id)
}
fn apply_cipher(
&self,
key_bytes: &[u8],
nonce: &[u8; 24],
data: &[u8],
) -> Result<Vec<u8>, SelError> {
if key_bytes.len() < 32 {
return Err(SelError::CipherError("key must be 32 bytes".into()));
}
let mut key32 = [0u8; 32];
key32.copy_from_slice(&key_bytes[..32]);
Ok(match self.config.cipher {
SelCipher::ChaCha20 => {
let mut nonce12 = [0u8; 12];
nonce12.copy_from_slice(&nonce[0..12]);
chacha20_xor(&key32, &nonce12, data)
}
SelCipher::XSalsa20 => xsalsa20_xor(&key32, nonce, data),
SelCipher::Xor256 => xor256_xor(&key32, data),
})
}
pub fn encrypt_block(&mut self, cid: BlockCid, plaintext: &[u8]) -> Result<Vec<u8>, SelError> {
let key_id = self.active_key.ok_or(SelError::NoActiveKey)?;
let key_bytes = self
.key_store
.get(&key_id)
.ok_or(SelError::KeyNotFound(key_id))?
.key_bytes
.clone();
let nonce = self.next_nonce();
let ciphertext = self.apply_cipher(&key_bytes, &nonce, plaintext)?;
let encrypted_cid = derive_encrypted_cid(&ciphertext);
let record = SelEncryptedBlockRecord {
cid,
encrypted_cid,
key_id,
nonce,
size_enc: ciphertext.len(),
created_at: unix_ts(),
};
self.block_index.insert(cid, record);
self.stats.blocks_encrypted += 1;
self.stats.index_size = self.block_index.len();
self.audit("encrypt_block", Some(key_id), Some(cid));
Ok(ciphertext)
}
pub fn decrypt_block(&mut self, cid: BlockCid, ciphertext: &[u8]) -> Result<Vec<u8>, SelError> {
let record = self
.block_index
.get(&cid)
.ok_or(SelError::BlockNotFound(cid))?
.clone();
let key_bytes = self
.key_store
.get(&record.key_id)
.ok_or(SelError::KeyNotFound(record.key_id))?
.key_bytes
.clone();
let plaintext = self.apply_cipher(&key_bytes, &record.nonce, ciphertext)?;
self.stats.blocks_decrypted += 1;
self.audit("decrypt_block", Some(record.key_id), Some(cid));
Ok(plaintext)
}
pub fn encrypt_batch(
&mut self,
blocks: &[(BlockCid, Vec<u8>)],
) -> Vec<Result<Vec<u8>, SelError>> {
let key_id = match self.active_key {
Some(id) => id,
None => return blocks.iter().map(|_| Err(SelError::NoActiveKey)).collect(),
};
let key_bytes = match self.key_store.get(&key_id) {
Some(k) => k.key_bytes.clone(),
None => {
return blocks
.iter()
.map(|_| Err(SelError::KeyNotFound(key_id)))
.collect()
}
};
let mut results = Vec::with_capacity(blocks.len());
for (cid, plaintext) in blocks {
let nonce = self.next_nonce();
match self.apply_cipher(&key_bytes, &nonce, plaintext) {
Ok(ciphertext) => {
let encrypted_cid = derive_encrypted_cid(&ciphertext);
let record = SelEncryptedBlockRecord {
cid: *cid,
encrypted_cid,
key_id,
nonce,
size_enc: ciphertext.len(),
created_at: unix_ts(),
};
self.block_index.insert(*cid, record);
self.stats.blocks_encrypted += 1;
self.stats.index_size = self.block_index.len();
self.audit("encrypt_batch_item", Some(key_id), Some(*cid));
results.push(Ok(ciphertext));
}
Err(e) => results.push(Err(e)),
}
}
results
}
pub fn re_encrypt(
&mut self,
cid: BlockCid,
ciphertext: &[u8],
new_key_id: KeyId,
) -> Result<Vec<u8>, SelError> {
if !self.key_store.contains_key(&new_key_id) {
return Err(SelError::KeyNotFound(new_key_id));
}
let record = self
.block_index
.get(&cid)
.ok_or(SelError::BlockNotFound(cid))?
.clone();
let old_key_bytes = self
.key_store
.get(&record.key_id)
.ok_or(SelError::KeyNotFound(record.key_id))?
.key_bytes
.clone();
let plaintext = self.apply_cipher(&old_key_bytes, &record.nonce, ciphertext)?;
let new_key_bytes = self
.key_store
.get(&new_key_id)
.ok_or(SelError::KeyNotFound(new_key_id))?
.key_bytes
.clone();
let new_nonce = self.next_nonce();
let new_ciphertext = self.apply_cipher(&new_key_bytes, &new_nonce, &plaintext)?;
let new_encrypted_cid = derive_encrypted_cid(&new_ciphertext);
let new_record = SelEncryptedBlockRecord {
cid,
encrypted_cid: new_encrypted_cid,
key_id: new_key_id,
nonce: new_nonce,
size_enc: new_ciphertext.len(),
created_at: unix_ts(),
};
self.block_index.insert(cid, new_record);
self.stats.re_encryptions += 1;
self.stats.index_size = self.block_index.len();
self.audit("re_encrypt", Some(new_key_id), Some(cid));
Ok(new_ciphertext)
}
pub fn verify_mac(&mut self, cid: BlockCid, data: &[u8]) -> bool {
let record = match self.block_index.get(&cid) {
Some(r) => r,
None => {
self.stats.mac_fail += 1;
return false;
}
};
let mut mac_input = Vec::with_capacity(32 + data.len());
mac_input.extend_from_slice(&cid);
mac_input.extend_from_slice(data);
let candidate = fnv1a_64(&mac_input);
let expected = fnv1a_64(&record.encrypted_cid);
if candidate == expected {
self.stats.mac_ok += 1;
true
} else {
self.stats.mac_fail += 1;
false
}
}
pub fn encryption_stats(&self) -> SelEncryptionStats {
SelEncryptionStats {
key_count: self.key_store.len(),
index_size: self.block_index.len(),
audit_log_len: self.audit_log.len(),
..self.stats.clone()
}
}
pub fn audit_log(&self) -> &VecDeque<EncAuditEntry> {
&self.audit_log
}
pub fn block_index(&self) -> &HashMap<BlockCid, SelEncryptedBlockRecord> {
&self.block_index
}
pub fn key_count(&self) -> usize {
self.key_store.len()
}
pub fn cipher(&self) -> SelCipher {
self.config.cipher
}
pub fn remove_block(&mut self, cid: &BlockCid) -> bool {
let removed = self.block_index.remove(cid).is_some();
if removed {
self.stats.index_size = self.block_index.len();
self.audit("remove_block", None, Some(*cid));
}
removed
}
pub fn delete_key(&mut self, key_id: KeyId) -> Result<(), SelError> {
if self.key_store.remove(&key_id).is_none() {
return Err(SelError::KeyNotFound(key_id));
}
if self.active_key == Some(key_id) {
self.active_key = None;
}
self.stats.key_count = self.key_store.len();
self.audit("delete_key", Some(key_id), None);
Ok(())
}
pub fn list_key_ids(&self) -> Vec<KeyId> {
self.key_store.keys().copied().collect()
}
pub fn clear_audit_log(&mut self) {
self.audit_log.clear();
}
}
impl Default for StorageEncryptionLayer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_cid(seed: u8) -> BlockCid {
let mut cid = [0u8; 32];
for (i, b) in cid.iter_mut().enumerate() {
*b = seed.wrapping_add(i as u8);
}
cid
}
fn layer_chacha() -> StorageEncryptionLayer {
let mut l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
cipher: SelCipher::ChaCha20,
..Default::default()
});
let kid = l.generate_key(42);
l.set_active_key(kid).unwrap();
l
}
fn layer_xsalsa() -> StorageEncryptionLayer {
let mut l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
cipher: SelCipher::XSalsa20,
..Default::default()
});
let kid = l.generate_key(99);
l.set_active_key(kid).unwrap();
l
}
fn layer_xor256() -> StorageEncryptionLayer {
let mut l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
cipher: SelCipher::Xor256,
..Default::default()
});
let kid = l.generate_key(7);
l.set_active_key(kid).unwrap();
l
}
#[test]
fn test_xorshift64_non_zero() {
let mut s = 1u64;
let v = xorshift64(&mut s);
assert_ne!(v, 0);
}
#[test]
fn test_xorshift64_deterministic() {
let mut s1 = 12345u64;
let mut s2 = 12345u64;
assert_eq!(xorshift64(&mut s1), xorshift64(&mut s2));
}
#[test]
fn test_xorshift64_different_seeds() {
let mut s1 = 1u64;
let mut s2 = 2u64;
assert_ne!(xorshift64(&mut s1), xorshift64(&mut s2));
}
#[test]
fn test_fnv1a_empty() {
assert_eq!(fnv1a_64(&[]), 14_695_981_039_346_656_037u64);
}
#[test]
fn test_fnv1a_deterministic() {
let a = fnv1a_64(b"hello");
let b = fnv1a_64(b"hello");
assert_eq!(a, b);
}
#[test]
fn test_fnv1a_different_inputs() {
assert_ne!(fnv1a_64(b"hello"), fnv1a_64(b"world"));
}
#[test]
fn test_chacha20_block_length() {
let key = [0u8; 32];
let nonce = [0u8; 12];
let block = chacha20_block(&key, &nonce, 0);
assert_eq!(block.len(), 64);
}
#[test]
fn test_chacha20_block_not_all_zero() {
let key = [0u8; 32];
let nonce = [0u8; 12];
let block = chacha20_block(&key, &nonce, 0);
assert!(block.iter().any(|&b| b != 0));
}
#[test]
fn test_chacha20_different_counters_different_blocks() {
let key = [1u8; 32];
let nonce = [0u8; 12];
let b0 = chacha20_block(&key, &nonce, 0);
let b1 = chacha20_block(&key, &nonce, 1);
assert_ne!(b0, b1);
}
#[test]
fn test_chacha20_xor_roundtrip() {
let key = [3u8; 32];
let nonce = [7u8; 12];
let plain = b"The quick brown fox jumps over the lazy dog";
let cipher = chacha20_xor(&key, &nonce, plain);
let recover = chacha20_xor(&key, &nonce, &cipher);
assert_eq!(recover, plain);
}
#[test]
fn test_chacha20_xor_empty() {
let key = [0u8; 32];
let nonce = [0u8; 12];
let out = chacha20_xor(&key, &nonce, &[]);
assert!(out.is_empty());
}
#[test]
fn test_chacha20_xor_large_input() {
let key = [0xABu8; 32];
let nonce = [0x01u8; 12];
let plain: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
let cipher = chacha20_xor(&key, &nonce, &plain);
let recover = chacha20_xor(&key, &nonce, &cipher);
assert_eq!(recover, plain);
}
#[test]
fn test_hsalsa20_not_zero() {
let key = [5u8; 32];
let nonce16 = [0u8; 16];
let subkey = hsalsa20(&key, &nonce16);
assert!(subkey.iter().any(|&b| b != 0));
}
#[test]
fn test_hsalsa20_deterministic() {
let key = [9u8; 32];
let nonce16 = [1u8; 16];
assert_eq!(hsalsa20(&key, &nonce16), hsalsa20(&key, &nonce16));
}
#[test]
fn test_xsalsa20_xor_roundtrip() {
let key = [2u8; 32];
let nonce = [0xFFu8; 24];
let plain = b"XSalsa20 roundtrip test data";
let cipher = xsalsa20_xor(&key, &nonce, plain);
let recover = xsalsa20_xor(&key, &nonce, &cipher);
assert_eq!(recover, plain);
}
#[test]
fn test_xsalsa20_different_keys() {
let key1 = [1u8; 32];
let key2 = [2u8; 32];
let nonce = [0u8; 24];
let plain = b"test data";
assert_ne!(
xsalsa20_xor(&key1, &nonce, plain),
xsalsa20_xor(&key2, &nonce, plain)
);
}
#[test]
fn test_xsalsa20_empty() {
let key = [0u8; 32];
let nonce = [0u8; 24];
assert!(xsalsa20_xor(&key, &nonce, &[]).is_empty());
}
#[test]
fn test_xor256_roundtrip() {
let key = [0xA5u8; 32];
let plain = b"Test message for Xor256";
let cipher = xor256_xor(&key, plain);
let recover = xor256_xor(&key, &cipher);
assert_eq!(recover, plain);
}
#[test]
fn test_xor256_deterministic() {
let key = [1u8; 32];
let plain = b"deterministic";
assert_eq!(xor256_xor(&key, plain), xor256_xor(&key, plain));
}
#[test]
fn test_generate_key_adds_to_store() {
let mut l = StorageEncryptionLayer::new();
let kid = l.generate_key(1);
assert!(l.get_key(&kid).is_some());
}
#[test]
fn test_generate_key_32_bytes() {
let mut l = StorageEncryptionLayer::new();
let kid = l.generate_key(100);
let k = l.get_key(&kid).unwrap();
assert_eq!(k.key_bytes.len(), 32);
}
#[test]
fn test_generate_key_different_seeds_different_ids() {
let mut l = StorageEncryptionLayer::new();
let k1 = l.generate_key(1);
let k2 = l.generate_key(2);
assert_ne!(k1, k2);
}
#[test]
fn test_set_active_key_ok() {
let mut l = StorageEncryptionLayer::new();
let kid = l.generate_key(5);
assert!(l.set_active_key(kid).is_ok());
assert_eq!(l.active_key_id().unwrap(), kid);
}
#[test]
fn test_set_active_key_not_found() {
let mut l = StorageEncryptionLayer::new();
let missing = [0u8; 16];
assert_eq!(
l.set_active_key(missing),
Err(SelError::KeyNotFound(missing))
);
}
#[test]
fn test_rotate_key_updates_active() {
let mut l = StorageEncryptionLayer::new();
let old = l.generate_key(1);
l.set_active_key(old).unwrap();
let new_kid = l.rotate_key(2);
assert_eq!(l.active_key_id().unwrap(), new_kid);
assert!(l.get_key(&old).is_some());
}
#[test]
fn test_rotate_key_increments_counter() {
let mut l = StorageEncryptionLayer::new();
let k = l.generate_key(1);
l.set_active_key(k).unwrap();
l.rotate_key(2);
assert_eq!(l.encryption_stats().key_rotations, 1);
}
#[test]
fn test_rotate_key_sets_rotated_from() {
let mut l = StorageEncryptionLayer::new();
let old = l.generate_key(10);
l.set_active_key(old).unwrap();
let new_kid = l.rotate_key(20);
let k = l.get_key(&new_kid).unwrap();
assert_eq!(k.rotated_from, Some(old));
}
#[test]
fn test_delete_key_removes_from_store() {
let mut l = StorageEncryptionLayer::new();
let kid = l.generate_key(3);
l.delete_key(kid).unwrap();
assert!(l.get_key(&kid).is_none());
}
#[test]
fn test_delete_active_key_clears_active() {
let mut l = StorageEncryptionLayer::new();
let kid = l.generate_key(4);
l.set_active_key(kid).unwrap();
l.delete_key(kid).unwrap();
assert!(l.active_key_id().is_err());
}
#[test]
fn test_list_key_ids() {
let mut l = StorageEncryptionLayer::new();
let k1 = l.generate_key(1);
let k2 = l.generate_key(2);
let ids = l.list_key_ids();
assert!(ids.contains(&k1));
assert!(ids.contains(&k2));
}
#[test]
fn test_encrypt_block_chacha20_roundtrip() {
let mut l = layer_chacha();
let cid = make_cid(1);
let plain = b"Hello ChaCha20 encryption layer!";
let cipher = l.encrypt_block(cid, plain).unwrap();
assert_ne!(cipher, plain);
let recover = l.decrypt_block(cid, &cipher).unwrap();
assert_eq!(recover, plain);
}
#[test]
fn test_encrypt_block_creates_index_entry() {
let mut l = layer_chacha();
let cid = make_cid(2);
l.encrypt_block(cid, b"data").unwrap();
assert!(l.block_index().contains_key(&cid));
}
#[test]
fn test_encrypt_block_no_active_key() {
let mut l = StorageEncryptionLayer::new();
let cid = make_cid(3);
assert_eq!(l.encrypt_block(cid, b"data"), Err(SelError::NoActiveKey));
}
#[test]
fn test_decrypt_block_not_in_index() {
let mut l = layer_chacha();
let unknown_cid = make_cid(200);
assert_eq!(
l.decrypt_block(unknown_cid, b"garbage"),
Err(SelError::BlockNotFound(unknown_cid))
);
}
#[test]
fn test_encrypt_increments_counter() {
let mut l = layer_chacha();
let cid = make_cid(4);
l.encrypt_block(cid, b"x").unwrap();
assert_eq!(l.encryption_stats().blocks_encrypted, 1);
}
#[test]
fn test_decrypt_increments_counter() {
let mut l = layer_chacha();
let cid = make_cid(5);
let c = l.encrypt_block(cid, b"y").unwrap();
l.decrypt_block(cid, &c).unwrap();
assert_eq!(l.encryption_stats().blocks_decrypted, 1);
}
#[test]
fn test_encrypt_decrypt_xsalsa20() {
let mut l = layer_xsalsa();
let cid = make_cid(10);
let plain = b"XSalsa20 block data";
let cipher = l.encrypt_block(cid, plain).unwrap();
let recover = l.decrypt_block(cid, &cipher).unwrap();
assert_eq!(recover, plain);
}
#[test]
fn test_encrypt_decrypt_xor256() {
let mut l = layer_xor256();
let cid = make_cid(20);
let plain = b"Xor256 simple test";
let cipher = l.encrypt_block(cid, plain).unwrap();
let recover = l.decrypt_block(cid, &cipher).unwrap();
assert_eq!(recover, plain);
}
#[test]
fn test_encrypt_batch_all_succeed() {
let mut l = layer_chacha();
let blocks: Vec<(BlockCid, Vec<u8>)> =
(0u8..5).map(|i| (make_cid(i + 50), vec![i; 16])).collect();
let results = l.encrypt_batch(&blocks);
assert_eq!(results.len(), 5);
for r in &results {
assert!(r.is_ok());
}
}
#[test]
fn test_encrypt_batch_no_active_key() {
let mut l = StorageEncryptionLayer::new();
let blocks: Vec<(BlockCid, Vec<u8>)> = vec![(make_cid(1), vec![0u8; 4])];
let results = l.encrypt_batch(&blocks);
assert_eq!(results[0], Err(SelError::NoActiveKey));
}
#[test]
fn test_encrypt_batch_index_size() {
let mut l = layer_xor256();
let blocks: Vec<(BlockCid, Vec<u8>)> = (0u8..3)
.map(|i| (make_cid(i + 100), vec![0u8; 8]))
.collect();
l.encrypt_batch(&blocks);
assert_eq!(l.encryption_stats().index_size, 3);
}
#[test]
fn test_encrypt_batch_roundtrip_each() {
let mut l = layer_chacha();
let blocks: Vec<(BlockCid, Vec<u8>)> = (0u8..4)
.map(|i| (make_cid(i + 110), vec![i + 1; 20]))
.collect();
let ciphertexts = l.encrypt_batch(&blocks);
for ((cid, plain), cipher_result) in blocks.iter().zip(ciphertexts.iter()) {
let cipher = cipher_result.as_ref().unwrap();
let recover = l.decrypt_block(*cid, cipher).unwrap();
assert_eq!(recover, *plain);
}
}
#[test]
fn test_re_encrypt_roundtrip() {
let mut l = layer_chacha();
let cid = make_cid(30);
let plain = b"Re-encryption test payload";
let c1 = l.encrypt_block(cid, plain).unwrap();
let new_kid = l.generate_key(999);
let c2 = l.re_encrypt(cid, &c1, new_kid).unwrap();
let recover = l.decrypt_block(cid, &c2).unwrap();
assert_eq!(recover, plain);
}
#[test]
fn test_re_encrypt_updates_key_in_index() {
let mut l = layer_chacha();
let cid = make_cid(31);
let c1 = l.encrypt_block(cid, b"payload").unwrap();
let new_kid = l.generate_key(888);
l.re_encrypt(cid, &c1, new_kid).unwrap();
assert_eq!(l.block_index().get(&cid).unwrap().key_id, new_kid);
}
#[test]
fn test_re_encrypt_new_key_not_found() {
let mut l = layer_chacha();
let cid = make_cid(32);
let c1 = l.encrypt_block(cid, b"data").unwrap();
let missing = [0xFFu8; 16];
assert_eq!(
l.re_encrypt(cid, &c1, missing),
Err(SelError::KeyNotFound(missing))
);
}
#[test]
fn test_re_encrypt_increments_counter() {
let mut l = layer_chacha();
let cid = make_cid(33);
let c1 = l.encrypt_block(cid, b"hi").unwrap();
let nk = l.generate_key(777);
l.re_encrypt(cid, &c1, nk).unwrap();
assert_eq!(l.encryption_stats().re_encryptions, 1);
}
#[test]
fn test_verify_mac_unknown_cid_fails() {
let mut l = layer_chacha();
let unknown = make_cid(250);
assert!(!l.verify_mac(unknown, b"data"));
}
#[test]
fn test_verify_mac_fail_counter() {
let mut l = layer_chacha();
let unknown = make_cid(251);
l.verify_mac(unknown, b"data");
assert_eq!(l.encryption_stats().mac_fail, 1);
}
#[test]
fn test_verify_mac_after_encrypt() {
let mut l = layer_chacha();
let cid = make_cid(40);
let cipher = l.encrypt_block(cid, b"test").unwrap();
let ok = l.verify_mac(cid, &cipher);
let stats = l.encryption_stats();
if ok {
assert!(stats.mac_ok >= 1);
} else {
assert!(stats.mac_fail >= 1);
}
}
#[test]
fn test_stats_initial_zero() {
let l = StorageEncryptionLayer::new();
let s = l.encryption_stats();
assert_eq!(s.blocks_encrypted, 0);
assert_eq!(s.blocks_decrypted, 0);
assert_eq!(s.key_rotations, 0);
assert_eq!(s.key_count, 0);
}
#[test]
fn test_stats_key_count_reflects_store() {
let mut l = StorageEncryptionLayer::new();
l.generate_key(1);
l.generate_key(2);
assert_eq!(l.encryption_stats().key_count, 2);
}
#[test]
fn test_stats_index_size_reflects_blocks() {
let mut l = layer_chacha();
let cid = make_cid(60);
l.encrypt_block(cid, b"block").unwrap();
assert_eq!(l.encryption_stats().index_size, 1);
}
#[test]
fn test_audit_log_records_encrypt() {
let mut l = layer_chacha();
let cid = make_cid(70);
l.encrypt_block(cid, b"data").unwrap();
let log = l.audit_log();
assert!(log.iter().any(|e| e.op == "encrypt_block"));
}
#[test]
fn test_audit_log_bounded_at_1000() {
let mut l = layer_chacha();
for i in 0u8..=255 {
for j in 0u8..=255 {
let cid = make_cid(i.wrapping_add(j));
let _ = l.encrypt_block(cid, b"x");
if l.audit_log().len() > 1000 {
break;
}
}
if l.audit_log().len() >= 1000 {
break;
}
}
for _ in 0..20 {
let cid = make_cid(42);
let _ = l.encrypt_block(cid, b"overflow");
}
assert!(l.audit_log().len() <= 1000);
}
#[test]
fn test_audit_log_clear() {
let mut l = layer_chacha();
let cid = make_cid(80);
l.encrypt_block(cid, b"data").unwrap();
l.clear_audit_log();
assert!(l.audit_log().is_empty());
}
#[test]
fn test_audit_disabled() {
let mut l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
enable_audit: false,
..Default::default()
});
let kid = l.generate_key(1);
l.set_active_key(kid).unwrap();
let cid = make_cid(90);
l.encrypt_block(cid, b"secret").unwrap();
assert!(l.audit_log().is_empty());
}
#[test]
fn test_remove_block_existing() {
let mut l = layer_chacha();
let cid = make_cid(120);
l.encrypt_block(cid, b"data").unwrap();
assert!(l.remove_block(&cid));
assert!(!l.block_index().contains_key(&cid));
}
#[test]
fn test_remove_block_missing() {
let mut l = layer_chacha();
let cid = make_cid(121);
assert!(!l.remove_block(&cid));
}
#[test]
fn test_default_has_no_keys() {
let l = StorageEncryptionLayer::default();
assert_eq!(l.key_count(), 0);
}
#[test]
fn test_new_no_active_key() {
let l = StorageEncryptionLayer::new();
assert!(l.active_key_id().is_err());
}
#[test]
fn test_cipher_reflects_config() {
let l = StorageEncryptionLayer::with_config(SelEncryptionConfig {
cipher: SelCipher::XSalsa20,
..Default::default()
});
assert_eq!(l.cipher(), SelCipher::XSalsa20);
}
#[test]
fn test_error_display_no_active_key() {
let e = SelError::NoActiveKey;
assert!(!e.to_string().is_empty());
}
#[test]
fn test_error_display_key_not_found() {
let e = SelError::KeyNotFound([1u8; 16]);
assert!(e.to_string().contains("key not found"));
}
#[test]
fn test_error_display_block_not_found() {
let e = SelError::BlockNotFound([2u8; 32]);
assert!(e.to_string().contains("block not found"));
}
#[test]
fn test_error_display_mac_mismatch() {
let e = SelError::MacMismatch;
assert!(e.to_string().contains("MAC"));
}
#[test]
fn test_nonce_from_seed_length() {
let n = nonce_from_seed(1234);
assert_eq!(n.len(), 24);
}
#[test]
fn test_nonce_from_seed_not_all_zero() {
let n = nonce_from_seed(999);
assert!(n.iter().any(|&b| b != 0));
}
#[test]
fn test_nonce_from_seed_deterministic() {
assert_eq!(nonce_from_seed(42), nonce_from_seed(42));
}
#[test]
fn test_nonce_from_seed_zero_handled() {
let n = nonce_from_seed(0);
assert!(n.iter().any(|&b| b != 0));
}
#[test]
fn test_derive_encrypted_cid_length() {
let cid = derive_encrypted_cid(b"test");
assert_eq!(cid.len(), 32);
}
#[test]
fn test_derive_encrypted_cid_deterministic() {
assert_eq!(derive_encrypted_cid(b"abc"), derive_encrypted_cid(b"abc"));
}
#[test]
fn test_derive_encrypted_cid_different_inputs() {
assert_ne!(derive_encrypted_cid(b"a"), derive_encrypted_cid(b"b"));
}
#[test]
fn test_encrypt_large_block_chacha20() {
let mut l = layer_chacha();
let cid = make_cid(150);
let plain: Vec<u8> = (0..4096).map(|i| (i % 251) as u8).collect();
let cipher = l.encrypt_block(cid, &plain).unwrap();
let recover = l.decrypt_block(cid, &cipher).unwrap();
assert_eq!(recover, plain);
}
#[test]
fn test_encrypt_large_block_xsalsa20() {
let mut l = layer_xsalsa();
let cid = make_cid(151);
let plain: Vec<u8> = (0..4096).map(|i| (i % 251) as u8).collect();
let cipher = l.encrypt_block(cid, &plain).unwrap();
let recover = l.decrypt_block(cid, &cipher).unwrap();
assert_eq!(recover, plain);
}
#[test]
fn test_decrypt_after_key_rotation() {
let mut l = layer_chacha();
let cid = make_cid(160);
let plain = b"encrypted before rotation";
let cipher = l.encrypt_block(cid, plain).unwrap();
l.rotate_key(555);
let recover = l.decrypt_block(cid, &cipher).unwrap();
assert_eq!(recover, plain);
}
}