use crate::airgapped::SignedTrustBundle;
use crate::error::WSError;
pub trait TrustStore: Send + Sync {
fn load_bundle(&self) -> Result<SignedTrustBundle, WSError>;
fn save_bundle(&self, bundle: &SignedTrustBundle) -> Result<(), WSError> {
let _ = bundle;
Err(WSError::InternalError("Bundle saving not supported".to_string()))
}
fn is_available(&self) -> bool {
true
}
fn metadata(&self) -> StorageMetadata {
StorageMetadata::default()
}
}
pub trait KeyStore: Send + Sync {
fn load_verifier_key(&self) -> Result<Vec<u8>, WSError>;
fn is_hardware_backed(&self) -> bool {
false
}
fn key_metadata(&self) -> KeyMetadata {
KeyMetadata::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct StorageMetadata {
pub storage_type: &'static str,
pub read_only: bool,
pub encrypted: bool,
pub hardware_protected: bool,
}
#[derive(Debug, Clone, Default)]
pub struct KeyMetadata {
pub key_id: Option<String>,
pub hardware_backed: bool,
pub extractable: bool,
pub algorithm: &'static str,
}
#[derive(Debug, Clone)]
pub struct MemoryTrustStore {
bundle: Option<SignedTrustBundle>,
}
impl MemoryTrustStore {
pub fn new() -> Self {
Self { bundle: None }
}
pub fn with_bundle(bundle: SignedTrustBundle) -> Self {
Self { bundle: Some(bundle) }
}
}
impl Default for MemoryTrustStore {
fn default() -> Self {
Self::new()
}
}
impl TrustStore for MemoryTrustStore {
fn load_bundle(&self) -> Result<SignedTrustBundle, WSError> {
self.bundle
.clone()
.ok_or_else(|| WSError::InternalError("No bundle in memory store".to_string()))
}
fn save_bundle(&self, _bundle: &SignedTrustBundle) -> Result<(), WSError> {
Err(WSError::InternalError("MemoryTrustStore is immutable".to_string()))
}
fn metadata(&self) -> StorageMetadata {
StorageMetadata {
storage_type: "memory",
read_only: true,
encrypted: false,
hardware_protected: false,
}
}
}
#[derive(Debug, Clone)]
pub struct MemoryKeyStore {
verifier_key: Vec<u8>,
}
impl MemoryKeyStore {
pub fn new(verifier_key: Vec<u8>) -> Self {
Self { verifier_key }
}
}
impl KeyStore for MemoryKeyStore {
fn load_verifier_key(&self) -> Result<Vec<u8>, WSError> {
Ok(self.verifier_key.clone())
}
fn key_metadata(&self) -> KeyMetadata {
KeyMetadata {
key_id: None,
hardware_backed: false,
extractable: true,
algorithm: "Ed25519",
}
}
}
#[cfg(not(target_os = "wasi"))]
#[derive(Debug, Clone)]
pub struct FileTrustStore {
path: std::path::PathBuf,
}
#[cfg(not(target_os = "wasi"))]
impl FileTrustStore {
pub fn new(path: impl Into<std::path::PathBuf>) -> Self {
Self { path: path.into() }
}
}
#[cfg(not(target_os = "wasi"))]
impl TrustStore for FileTrustStore {
fn load_bundle(&self) -> Result<SignedTrustBundle, WSError> {
let data = std::fs::read(&self.path).map_err(|e| {
WSError::InternalError(format!("Failed to read bundle file: {}", e))
})?;
SignedTrustBundle::from_json(&data)
}
fn save_bundle(&self, bundle: &SignedTrustBundle) -> Result<(), WSError> {
let data = bundle.to_json()?;
std::fs::write(&self.path, data).map_err(|e| {
WSError::InternalError(format!("Failed to write bundle file: {}", e))
})
}
fn metadata(&self) -> StorageMetadata {
StorageMetadata {
storage_type: "file",
read_only: false,
encrypted: false,
hardware_protected: false,
}
}
}
#[cfg(not(target_os = "wasi"))]
#[derive(Debug, Clone)]
pub struct FileKeyStore {
path: std::path::PathBuf,
}
#[cfg(not(target_os = "wasi"))]
impl FileKeyStore {
pub fn new(path: impl Into<std::path::PathBuf>) -> Self {
Self { path: path.into() }
}
}
#[cfg(not(target_os = "wasi"))]
impl KeyStore for FileKeyStore {
fn load_verifier_key(&self) -> Result<Vec<u8>, WSError> {
let data = std::fs::read(&self.path).map_err(|e| {
WSError::InternalError(format!("Failed to read key file: {}", e))
})?;
if data.len() == 33 {
Ok(data[1..].to_vec())
} else if data.len() == 32 {
Ok(data)
} else {
Err(WSError::InternalError(format!(
"Invalid key file size: {} (expected 32 or 33)",
data.len()
)))
}
}
fn key_metadata(&self) -> KeyMetadata {
KeyMetadata {
key_id: None,
hardware_backed: false,
extractable: true,
algorithm: "Ed25519",
}
}
}
#[derive(Debug, Clone)]
pub struct CompiledTrustStore {
data: &'static [u8],
}
impl CompiledTrustStore {
pub const fn new(data: &'static [u8]) -> Self {
Self { data }
}
}
impl TrustStore for CompiledTrustStore {
fn load_bundle(&self) -> Result<SignedTrustBundle, WSError> {
SignedTrustBundle::from_json(self.data)
}
fn metadata(&self) -> StorageMetadata {
StorageMetadata {
storage_type: "compiled",
read_only: true,
encrypted: false,
hardware_protected: false,
}
}
}
#[derive(Debug, Clone)]
pub struct CompiledKeyStore {
key: &'static [u8],
}
impl CompiledKeyStore {
pub const fn new(key: &'static [u8]) -> Self {
Self { key }
}
}
impl KeyStore for CompiledKeyStore {
fn load_verifier_key(&self) -> Result<Vec<u8>, WSError> {
if self.key.len() == 32 {
Ok(self.key.to_vec())
} else if self.key.len() == 33 {
Ok(self.key[1..].to_vec())
} else {
Err(WSError::InternalError(format!(
"Invalid compiled key size: {}",
self.key.len()
)))
}
}
fn key_metadata(&self) -> KeyMetadata {
KeyMetadata {
key_id: None,
hardware_backed: false,
extractable: false, algorithm: "Ed25519",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::airgapped::TrustBundle;
fn create_test_bundle() -> SignedTrustBundle {
use ed25519_compact::KeyPair;
let keypair = KeyPair::generate();
let bundle = TrustBundle::new(1, 365);
let seed = keypair.sk.seed();
SignedTrustBundle::sign(bundle, seed.as_ref()).unwrap()
}
#[test]
fn test_memory_trust_store() {
let bundle = create_test_bundle();
let store = MemoryTrustStore::with_bundle(bundle.clone());
let loaded = store.load_bundle().unwrap();
assert_eq!(loaded.bundle.version, bundle.bundle.version);
}
#[test]
fn test_memory_trust_store_empty() {
let store = MemoryTrustStore::new();
assert!(store.load_bundle().is_err());
}
#[test]
fn test_memory_key_store() {
let key = vec![0u8; 32];
let store = MemoryKeyStore::new(key.clone());
let loaded = store.load_verifier_key().unwrap();
assert_eq!(loaded, key);
}
#[test]
fn test_compiled_key_store() {
static KEY: &[u8] = &[1u8; 32];
let store = CompiledKeyStore::new(KEY);
let loaded = store.load_verifier_key().unwrap();
assert_eq!(loaded.len(), 32);
}
#[test]
fn test_storage_metadata() {
let store = MemoryTrustStore::new();
let meta = store.metadata();
assert_eq!(meta.storage_type, "memory");
assert!(!meta.hardware_protected);
}
#[cfg(not(target_os = "wasi"))]
#[test]
fn test_file_trust_store() {
let bundle = create_test_bundle();
let path = std::env::temp_dir().join("test-bundle-storage.json");
std::fs::write(&path, bundle.to_json().unwrap()).unwrap();
let store = FileTrustStore::new(&path);
let loaded = store.load_bundle().unwrap();
assert_eq!(loaded.bundle.version, bundle.bundle.version);
std::fs::remove_file(&path).ok();
}
}