#![allow(dead_code, unused_imports, unused_qualifications, unreachable_patterns)]
#[cfg(target_os = "linux")]
mod backend_marker;
pub mod encryption;
pub mod error;
#[cfg(feature = "mock")]
pub mod mock;
pub mod platform;
pub mod signing;
pub use encryption::{AppEncryptionStorage, EncryptionStorage};
pub use error::{Result, StorageError};
#[cfg(feature = "mock")]
pub use mock::MockEncryptionStorage;
pub use platform::BackendKind;
pub use signing::AppSigningBackend;
pub use crate::internal::core::metadata::KeyMeta;
pub use crate::internal::core::traits::{EnclaveEncryptor, EnclaveKeyManager, EnclaveSigner};
pub use crate::internal::core::types::{AccessPolicy, KeyType};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowsSoftwareFallback {
Disabled,
VmOnly,
}
#[derive(Debug, Clone)]
pub struct StorageConfig {
pub app_name: String,
pub key_label: String,
pub access_policy: AccessPolicy,
pub extra_bridge_paths: Vec<String>,
pub keys_dir: Option<std::path::PathBuf>,
pub force_keyring: bool,
pub wrapping_key_user_presence: bool,
pub wrapping_key_cache_ttl: std::time::Duration,
pub keychain_access_group: Option<String>,
pub prefer_windows_hello_ux: bool,
pub windows_software_fallback: WindowsSoftwareFallback,
pub dpapi_app_key: Option<[u8; 32]>,
}
#[cfg(feature = "mock")]
pub const MOCK_STORAGE_ENV: &str = "ENCLAVEAPP_MOCK_STORAGE";
pub fn create_encryption_storage(mut config: StorageConfig) -> Result<Box<dyn EncryptionStorage>> {
config.app_name = crate::internal::core::signing::ensure_safe_app_name(&config.app_name);
#[cfg(feature = "mock")]
{
if let Ok(val) = std::env::var(MOCK_STORAGE_ENV) {
if !val.is_empty() {
tracing::warn!(
app = %config.app_name,
"{MOCK_STORAGE_ENV} is set — returning MockEncryptionStorage (no hardware backing)"
);
return Ok(Box::new(MockEncryptionStorage::for_app(&config.app_name)));
}
}
}
let storage = AppEncryptionStorage::init(config)?;
Ok(Box::new(storage))
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn storage_config_debug() {
let config = StorageConfig {
app_name: "test".into(),
key_label: "key".into(),
access_policy: AccessPolicy::None,
extra_bridge_paths: vec![],
keys_dir: None,
force_keyring: false,
wrapping_key_user_presence: false,
wrapping_key_cache_ttl: std::time::Duration::ZERO,
keychain_access_group: None,
prefer_windows_hello_ux: false,
windows_software_fallback: WindowsSoftwareFallback::Disabled,
dpapi_app_key: None,
};
let debug = format!("{config:?}");
assert!(debug.contains("test"));
assert!(debug.contains("key"));
}
#[test]
fn storage_config_clone() {
let config = StorageConfig {
app_name: "test".into(),
key_label: "key".into(),
access_policy: AccessPolicy::BiometricOnly,
extra_bridge_paths: vec!["/custom/path".into()],
keys_dir: Some(std::path::PathBuf::from("/custom/keys")),
force_keyring: false,
wrapping_key_user_presence: false,
wrapping_key_cache_ttl: std::time::Duration::ZERO,
keychain_access_group: None,
prefer_windows_hello_ux: false,
windows_software_fallback: WindowsSoftwareFallback::Disabled,
dpapi_app_key: None,
};
let cloned = config.clone();
assert_eq!(cloned.app_name, "test");
assert_eq!(cloned.key_label, "key");
assert_eq!(cloned.access_policy, AccessPolicy::BiometricOnly);
assert_eq!(cloned.extra_bridge_paths.len(), 1);
}
#[test]
fn storage_error_display() {
let err = StorageError::NotAvailable;
assert_eq!(err.to_string(), "hardware security module not available");
let err = StorageError::EncryptionFailed("bad key".into());
assert_eq!(err.to_string(), "encryption failed: bad key");
let err = StorageError::DecryptionFailed("corrupt".into());
assert_eq!(err.to_string(), "decryption failed: corrupt");
let err = StorageError::SigningFailed("timeout".into());
assert_eq!(err.to_string(), "signing failed: timeout");
let err = StorageError::KeyInitFailed("no hardware".into());
assert_eq!(err.to_string(), "key initialization failed: no hardware");
let err = StorageError::KeyNotFound("missing".into());
assert_eq!(err.to_string(), "key not found: missing");
let err = StorageError::PolicyMismatch("None vs BiometricOnly".into());
assert_eq!(
err.to_string(),
"key policy mismatch: None vs BiometricOnly"
);
let err = StorageError::PlatformError("unsupported".into());
assert_eq!(err.to_string(), "platform error: unsupported");
}
#[test]
fn re_exports_work() {
let _ = AccessPolicy::None;
let _ = AccessPolicy::Any;
let _ = AccessPolicy::BiometricOnly;
let _ = AccessPolicy::PasswordOnly;
let _ = KeyType::Signing;
let _ = KeyType::Encryption;
let _ = BackendKind::SecureEnclave;
}
#[test]
fn storage_config_default_field_values() {
let config = StorageConfig {
app_name: "myapp".into(),
key_label: "default".into(),
access_policy: AccessPolicy::None,
extra_bridge_paths: vec![],
keys_dir: None,
force_keyring: false,
wrapping_key_user_presence: false,
wrapping_key_cache_ttl: std::time::Duration::ZERO,
keychain_access_group: None,
prefer_windows_hello_ux: false,
windows_software_fallback: WindowsSoftwareFallback::Disabled,
dpapi_app_key: None,
};
assert_eq!(config.app_name, "myapp");
assert_eq!(config.key_label, "default");
assert_eq!(config.access_policy, AccessPolicy::None);
assert!(config.extra_bridge_paths.is_empty());
assert!(config.keys_dir.is_none());
assert!(!config.force_keyring);
assert!(!config.wrapping_key_user_presence);
assert_eq!(config.wrapping_key_cache_ttl, std::time::Duration::ZERO);
assert!(config.keychain_access_group.is_none());
}
#[test]
fn storage_config_with_access_group() {
let config = StorageConfig {
app_name: "app".into(),
key_label: "key".into(),
access_policy: AccessPolicy::Any,
extra_bridge_paths: vec![],
keys_dir: None,
force_keyring: false,
wrapping_key_user_presence: true,
wrapping_key_cache_ttl: std::time::Duration::from_secs(30),
keychain_access_group: Some("TEAMID.com.example".into()),
prefer_windows_hello_ux: false,
windows_software_fallback: WindowsSoftwareFallback::Disabled,
dpapi_app_key: None,
};
assert!(config.wrapping_key_user_presence);
assert_eq!(
config.wrapping_key_cache_ttl,
std::time::Duration::from_secs(30)
);
assert_eq!(
config.keychain_access_group.as_deref(),
Some("TEAMID.com.example")
);
}
#[test]
fn storage_config_with_keys_dir_override() {
let dir = std::path::PathBuf::from("/custom/keys");
let config = StorageConfig {
app_name: "app".into(),
key_label: "key".into(),
access_policy: AccessPolicy::None,
extra_bridge_paths: vec!["/extra/path".into()],
keys_dir: Some(dir.clone()),
force_keyring: true,
wrapping_key_user_presence: false,
wrapping_key_cache_ttl: std::time::Duration::ZERO,
keychain_access_group: None,
prefer_windows_hello_ux: false,
windows_software_fallback: WindowsSoftwareFallback::Disabled,
dpapi_app_key: None,
};
assert_eq!(config.keys_dir.as_ref(), Some(&dir));
assert!(config.force_keyring);
assert_eq!(config.extra_bridge_paths.len(), 1);
}
#[cfg(feature = "mock")]
#[test]
fn mock_storage_env_constant_is_non_empty() {
assert!(!MOCK_STORAGE_ENV.is_empty());
assert!(MOCK_STORAGE_ENV.contains("ENCLAVEAPP"));
}
}