use crate::internal::app_storage::BackendKind;
use crate::auth::AuthHandle;
#[cfg(target_os = "macos")]
use crate::capabilities::has_keychain_entitlement;
use crate::security_key::SecurityKeyHandle;
use crate::config::EnclaveConfig;
use crate::encryption::EncryptorHandle;
use crate::error::{Error, Result};
use crate::integrity::TamperEvidentHandle;
use crate::signing::SignerHandle;
pub fn create_signer(config: &EnclaveConfig) -> Result<SignerHandle> {
let storage_config = validate_and_resolve_config(config)?;
let backend = crate::internal::app_storage::AppSigningBackend::init(storage_config)
.map_err(Error::from)?;
let kind = backend.backend_kind();
Ok(SignerHandle::new(backend, kind))
}
pub fn create_encryptor(config: &EnclaveConfig) -> Result<EncryptorHandle> {
let storage_config = validate_and_resolve_config(config)?;
let kind = resolve_backend_kind();
let storage = crate::internal::app_storage::AppEncryptionStorage::init(storage_config)
.map_err(Error::from)?;
Ok(EncryptorHandle::new(storage, kind))
}
pub fn create_auth(config: &EnclaveConfig) -> Result<AuthHandle> {
let _ = config; let kind = resolve_backend_kind();
Ok(AuthHandle::new(kind))
}
pub fn create_security_key(config: &EnclaveConfig) -> SecurityKeyHandle {
crate::security_key::make_security_key_handle(config)
}
pub fn create_tamper_evident(app_name: &str) -> Result<TamperEvidentHandle> {
let effective = crate::internal::core::signing::ensure_safe_app_name(app_name);
Ok(TamperEvidentHandle::new(effective))
}
pub fn create_tamper_evident_ephemeral(app_name: &str) -> TamperEvidentHandle {
let effective = crate::internal::core::signing::ensure_safe_app_name(app_name);
TamperEvidentHandle::new_ephemeral(effective)
}
fn validate_and_resolve_config(
config: &EnclaveConfig,
) -> Result<crate::internal::app_storage::StorageConfig> {
#[cfg_attr(not(target_os = "macos"), allow(unused_mut))]
let mut sc = config.to_storage_config();
#[cfg(target_os = "macos")]
if sc.wrapping_key_user_presence
&& sc.keychain_access_group.is_none()
&& !crate::internal::core::signing::is_binary_signed()
{
return Err(Error::RequiresSigning {
feature: "wrapping_key_user_presence (requires keychain_access_group + entitlement)"
.into(),
});
}
#[cfg(not(target_os = "macos"))]
if sc.wrapping_key_user_presence || sc.keychain_access_group.is_some() {
tracing::debug!(
app = %sc.app_name,
wrapping_key_user_presence = sc.wrapping_key_user_presence,
keychain_access_group = ?sc.keychain_access_group,
"macOS-specific config options set on non-macOS platform; they will be ignored"
);
}
#[cfg(target_os = "macos")]
if let Some(ref group) = sc.keychain_access_group.clone() {
if !has_keychain_entitlement(group) {
tracing::warn!(
app = %sc.app_name,
group = %group,
"keychain_access_group requested but entitlement is absent; \
downgrading to legacy keychain (no user_presence gate)"
);
sc.keychain_access_group = None;
sc.wrapping_key_user_presence = false;
}
}
Ok(sc)
}
#[allow(clippy::needless_return, unreachable_code)]
fn resolve_backend_kind() -> BackendKind {
#[cfg(target_os = "macos")]
{
return BackendKind::SecureEnclave;
}
#[cfg(target_os = "windows")]
{
return BackendKind::Tpm;
}
#[cfg(target_os = "linux")]
{
if crate::internal::wsl::is_wsl() {
return BackendKind::TpmBridge;
}
return BackendKind::Keyring;
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
BackendKind::Keyring
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn create_auth_does_not_panic() {
let config = EnclaveConfig::new("testapp", "default");
let _handle = create_auth(&config).unwrap();
}
#[cfg(target_os = "macos")]
#[test]
fn user_presence_without_access_group_unsigned_returns_requires_signing() {
use crate::config::{MacOsConfig, PlatformConfig};
use std::time::Duration;
let config = EnclaveConfig {
app_name: "testapp".into(),
default_key_label: "key".into(),
access_policy: None,
keys_dir: None,
platform: PlatformConfig::MacOs(MacOsConfig {
wrapping_key_user_presence: true,
wrapping_key_cache_ttl: Duration::ZERO,
keychain_access_group: None,
extra_bridge_paths: Vec::new(),
}),
};
let result = create_signer(&config);
assert!(
result.is_err(),
"unsigned binary with user_presence must return an error"
);
assert!(
matches!(result, Err(Error::RequiresSigning { .. })),
"expected RequiresSigning, got: {:?}",
result
);
}
}