#![allow(
dead_code,
unused_imports,
unused_qualifications,
unreachable_patterns,
deprecated
)]
use aes_gcm::aead::Aead;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use rand::RngCore;
use sha2::{Digest, Sha256};
use super::encryption::EncryptionStorage;
use super::error::{Result, StorageError};
use super::platform::BackendKind;
const NONCE_SIZE: usize = 12;
pub struct MockEncryptionStorage {
cipher: Aes256Gcm,
}
impl std::fmt::Debug for MockEncryptionStorage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MockEncryptionStorage")
.field("cipher", &"<Aes256Gcm>")
.finish()
}
}
impl MockEncryptionStorage {
pub fn new() -> Self {
let mut key_bytes = [0_u8; 32];
rand::thread_rng().fill_bytes(&mut key_bytes);
Self::from_key_bytes(key_bytes)
}
pub fn for_app(app_name: &str) -> Self {
let mut hasher = Sha256::new();
hasher.update(b"enclaveapp-app-storage mock v1\0");
hasher.update(app_name.as_bytes());
let key_bytes: [u8; 32] = hasher.finalize().into();
Self::from_key_bytes(key_bytes)
}
fn from_key_bytes(key_bytes: [u8; 32]) -> Self {
let cipher = Aes256Gcm::new_from_slice(&key_bytes)
.expect("32-byte key is always valid for AES-256-GCM");
Self { cipher }
}
}
impl Default for MockEncryptionStorage {
fn default() -> Self {
Self::new()
}
}
impl EncryptionStorage for MockEncryptionStorage {
fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
let mut nonce_bytes = [0_u8; NONCE_SIZE];
rand::thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = self
.cipher
.encrypt(nonce, plaintext)
.map_err(|e| StorageError::EncryptionFailed(e.to_string()))?;
let mut output = Vec::with_capacity(NONCE_SIZE + ciphertext.len());
output.extend_from_slice(&nonce_bytes);
output.extend_from_slice(&ciphertext);
Ok(output)
}
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
if ciphertext.len() < NONCE_SIZE {
return Err(StorageError::DecryptionFailed(
"ciphertext too short to contain nonce".into(),
));
}
let (nonce_bytes, encrypted) = ciphertext.split_at(NONCE_SIZE);
let nonce = Nonce::from_slice(nonce_bytes);
self.cipher
.decrypt(nonce, encrypted)
.map_err(|e| StorageError::DecryptionFailed(e.to_string()))
}
fn destroy(&self) -> Result<()> {
Ok(())
}
fn is_available(&self) -> bool {
true
}
fn backend_name(&self) -> &'static str {
"Mock (AES-GCM)"
}
fn backend_kind(&self) -> BackendKind {
BackendKind::Keyring
}
}
use crate::internal::core::{
traits::{EnclaveKeyManager, EnclaveSigner},
types::{AccessPolicy, KeyType},
Error as CoreError, Result as CoreResult,
};
use elliptic_curve::sec1::ToEncodedPoint;
use p256::{
ecdsa::{signature::Signer, DerSignature, SigningKey},
SecretKey,
};
use std::path::{Path, PathBuf};
use zeroize::Zeroizing;
pub struct MockSigner {
keys_dir: PathBuf,
}
impl std::fmt::Debug for MockSigner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MockSigner")
.field("keys_dir", &self.keys_dir)
.finish()
}
}
impl MockSigner {
pub fn with_keys_dir(keys_dir: PathBuf) -> Self {
drop(std::fs::create_dir_all(&keys_dir));
Self { keys_dir }
}
fn key_path(&self, label: &str) -> PathBuf {
self.keys_dir.join(format!("{label}.mock-key"))
}
}
impl Default for MockSigner {
fn default() -> Self {
Self::with_keys_dir(std::env::temp_dir().join("hardware-enclave-mock-signer"))
}
}
#[allow(unsafe_code)]
unsafe impl Send for MockSigner {}
#[allow(unsafe_code)]
unsafe impl Sync for MockSigner {}
fn load_secret(path: &Path) -> CoreResult<SecretKey> {
if !path.exists() {
let label = path
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
return Err(CoreError::KeyNotFound { label });
}
let bytes = Zeroizing::new(std::fs::read(path).map_err(|e| CoreError::KeyOperation {
operation: "mock_load".into(),
detail: e.to_string(),
})?);
SecretKey::from_slice(&bytes).map_err(|e| CoreError::KeyOperation {
operation: "mock_parse".into(),
detail: e.to_string(),
})
}
impl EnclaveKeyManager for MockSigner {
fn generate(
&self,
label: &str,
_key_type: KeyType,
_policy: AccessPolicy,
) -> CoreResult<Vec<u8>> {
let secret = SecretKey::random(&mut elliptic_curve::rand_core::OsRng);
let pub_bytes = secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();
let key_bytes = Zeroizing::new(secret.to_bytes().to_vec());
std::fs::write(self.key_path(label), &*key_bytes).map_err(|e| CoreError::KeyOperation {
operation: "mock_save".into(),
detail: e.to_string(),
})?;
Ok(pub_bytes)
}
fn public_key(&self, label: &str) -> CoreResult<Vec<u8>> {
let secret = load_secret(&self.key_path(label))?;
Ok(secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec())
}
fn list_keys(&self) -> CoreResult<Vec<String>> {
let mut labels = Vec::new();
if let Ok(entries) = std::fs::read_dir(&self.keys_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().map(|e| e == "mock-key").unwrap_or(false) {
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
labels.push(stem.to_string());
}
}
}
}
Ok(labels)
}
fn delete_key(&self, label: &str) -> CoreResult<()> {
let path = self.key_path(label);
if !path.exists() {
return Err(CoreError::KeyNotFound {
label: label.to_string(),
});
}
std::fs::remove_file(&path).map_err(|e| CoreError::KeyOperation {
operation: "mock_delete".into(),
detail: e.to_string(),
})
}
fn is_available(&self) -> bool {
true
}
}
impl EnclaveSigner for MockSigner {
fn sign(&self, label: &str, data: &[u8]) -> CoreResult<Vec<u8>> {
let secret = load_secret(&self.key_path(label))?;
let signing_key = SigningKey::from(&secret);
let sig: DerSignature = signing_key.sign(data);
Ok(sig.to_bytes().to_vec())
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn encrypt_decrypt_roundtrip() {
let storage = MockEncryptionStorage::new();
let plaintext = b"hello, secure world!";
let ciphertext = storage.encrypt(plaintext).unwrap();
assert_ne!(ciphertext, plaintext);
assert!(ciphertext.len() > plaintext.len());
let decrypted = storage.decrypt(&ciphertext).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn encrypt_produces_different_ciphertexts() {
let storage = MockEncryptionStorage::new();
let plaintext = b"same input";
let ct1 = storage.encrypt(plaintext).unwrap();
let ct2 = storage.encrypt(plaintext).unwrap();
assert_ne!(ct1, ct2);
}
#[test]
fn decrypt_empty_input_fails() {
let storage = MockEncryptionStorage::new();
assert!(storage.decrypt(&[]).is_err());
}
#[test]
fn decrypt_short_input_fails() {
let storage = MockEncryptionStorage::new();
assert!(storage.decrypt(&[0_u8; 5]).is_err());
}
#[test]
fn decrypt_tampered_ciphertext_fails() {
let storage = MockEncryptionStorage::new();
let mut ciphertext = storage.encrypt(b"sensitive data").unwrap();
if let Some(byte) = ciphertext.get_mut(NONCE_SIZE + 1) {
*byte ^= 0xff;
}
assert!(storage.decrypt(&ciphertext).is_err());
}
#[test]
fn decrypt_wrong_key_fails() {
let s1 = MockEncryptionStorage::new();
let s2 = MockEncryptionStorage::new();
let ciphertext = s1.encrypt(b"key-specific").unwrap();
assert!(s2.decrypt(&ciphertext).is_err());
}
#[test]
fn decrypt_truncated_by_one_byte_fails() {
let storage = MockEncryptionStorage::new();
let mut ciphertext = storage.encrypt(b"truncation test").unwrap();
assert!(
ciphertext.len() > NONCE_SIZE,
"ciphertext must exceed nonce"
);
ciphertext.pop(); let result = storage.decrypt(&ciphertext);
assert!(
result.is_err(),
"truncated ciphertext must return Err, not panic"
);
}
#[test]
fn encrypt_empty_plaintext() {
let storage = MockEncryptionStorage::new();
let ciphertext = storage.encrypt(b"").unwrap();
let decrypted = storage.decrypt(&ciphertext).unwrap();
assert!(decrypted.is_empty());
}
#[test]
fn encrypt_large_plaintext() {
let storage = MockEncryptionStorage::new();
let plaintext = vec![0xAB_u8; 100_000];
let ciphertext = storage.encrypt(&plaintext).unwrap();
let decrypted = storage.decrypt(&ciphertext).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn trait_methods() {
let storage = MockEncryptionStorage::new();
assert!(storage.is_available());
assert_eq!(storage.backend_name(), "Mock (AES-GCM)");
assert_eq!(storage.backend_kind(), BackendKind::Keyring);
assert!(storage.destroy().is_ok());
}
#[test]
fn default_impl_works() {
let storage = MockEncryptionStorage::default();
let ciphertext = storage.encrypt(b"default test").unwrap();
let decrypted = storage.decrypt(&ciphertext).unwrap();
assert_eq!(decrypted, b"default test");
}
#[test]
fn encrypt_decrypt_various_sizes() {
let storage = MockEncryptionStorage::new();
for size in [0, 1, 100, 10_000] {
let plaintext = vec![0xAA_u8; size];
let ciphertext = storage.encrypt(&plaintext).unwrap();
let decrypted = storage.decrypt(&ciphertext).unwrap();
assert_eq!(decrypted, plaintext, "roundtrip failed for size {size}");
}
}
}