hardware_enclave/
factory.rs1use crate::internal::app_storage::BackendKind;
5
6use crate::auth::AuthHandle;
7#[cfg(target_os = "macos")]
8use crate::capabilities::has_keychain_entitlement;
9use crate::security_key::SecurityKeyHandle;
10
11use crate::config::EnclaveConfig;
12use crate::encryption::EncryptorHandle;
13use crate::error::{Error, Result};
14use crate::integrity::TamperEvidentHandle;
15use crate::signing::SignerHandle;
16
17pub fn create_signer(config: &EnclaveConfig) -> Result<SignerHandle> {
23 let storage_config = validate_and_resolve_config(config)?;
24 let backend = crate::internal::app_storage::AppSigningBackend::init(storage_config)
25 .map_err(Error::from)?;
26 let kind = backend.backend_kind();
27 Ok(SignerHandle::new(backend, kind))
28}
29
30pub fn create_encryptor(config: &EnclaveConfig) -> Result<EncryptorHandle> {
32 let storage_config = validate_and_resolve_config(config)?;
33 let kind = resolve_backend_kind();
34 let storage = crate::internal::app_storage::AppEncryptionStorage::init(storage_config)
35 .map_err(Error::from)?;
36 Ok(EncryptorHandle::new(storage, kind))
37}
38
39pub fn create_auth(config: &EnclaveConfig) -> Result<AuthHandle> {
46 let _ = config; let kind = resolve_backend_kind();
48 Ok(AuthHandle::new(kind))
49}
50
51pub fn create_security_key(config: &EnclaveConfig) -> SecurityKeyHandle {
62 crate::security_key::make_security_key_handle(config)
63}
64
65pub fn create_tamper_evident(app_name: &str) -> Result<TamperEvidentHandle> {
76 let effective = crate::internal::core::signing::ensure_safe_app_name(app_name);
77 Ok(TamperEvidentHandle::new(effective))
78}
79
80pub fn create_tamper_evident_ephemeral(app_name: &str) -> TamperEvidentHandle {
93 let effective = crate::internal::core::signing::ensure_safe_app_name(app_name);
94 TamperEvidentHandle::new_ephemeral(effective)
95}
96
97fn validate_and_resolve_config(
100 config: &EnclaveConfig,
101) -> Result<crate::internal::app_storage::StorageConfig> {
102 #[cfg_attr(not(target_os = "macos"), allow(unused_mut))]
105 let mut sc = config.to_storage_config();
106
107 #[cfg(target_os = "macos")]
110 if sc.wrapping_key_user_presence
111 && sc.keychain_access_group.is_none()
112 && !crate::internal::core::signing::is_binary_signed()
113 {
114 return Err(Error::RequiresSigning {
115 feature: "wrapping_key_user_presence (requires keychain_access_group + entitlement)"
116 .into(),
117 });
118 }
119
120 #[cfg(not(target_os = "macos"))]
122 if sc.wrapping_key_user_presence || sc.keychain_access_group.is_some() {
123 tracing::debug!(
124 app = %sc.app_name,
125 wrapping_key_user_presence = sc.wrapping_key_user_presence,
126 keychain_access_group = ?sc.keychain_access_group,
127 "macOS-specific config options set on non-macOS platform; they will be ignored"
128 );
129 }
130
131 #[cfg(target_os = "macos")]
133 if let Some(ref group) = sc.keychain_access_group.clone() {
134 if !has_keychain_entitlement(group) {
135 tracing::warn!(
136 app = %sc.app_name,
137 group = %group,
138 "keychain_access_group requested but entitlement is absent; \
139 downgrading to legacy keychain (no user_presence gate)"
140 );
141 sc.keychain_access_group = None;
142 sc.wrapping_key_user_presence = false;
143 }
144 }
145
146 Ok(sc)
147}
148
149#[allow(clippy::needless_return, unreachable_code)]
150fn resolve_backend_kind() -> BackendKind {
151 #[cfg(target_os = "macos")]
152 {
153 return BackendKind::SecureEnclave;
154 }
155 #[cfg(target_os = "windows")]
156 {
157 return BackendKind::Tpm;
158 }
159 #[cfg(target_os = "linux")]
160 {
161 if crate::internal::wsl::is_wsl() {
162 return BackendKind::TpmBridge;
163 }
164 return BackendKind::Keyring;
165 }
166 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
167 BackendKind::Keyring
168}
169
170#[cfg(test)]
171#[allow(clippy::unwrap_used)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn create_auth_does_not_panic() {
177 let config = EnclaveConfig::new("testapp", "default");
178 let _handle = create_auth(&config).unwrap();
179 }
180
181 #[cfg(target_os = "macos")]
182 #[test]
183 fn user_presence_without_access_group_unsigned_returns_requires_signing() {
184 use crate::config::{MacOsConfig, PlatformConfig};
185 use std::time::Duration;
186 let config = EnclaveConfig {
187 app_name: "testapp".into(),
188 default_key_label: "key".into(),
189 access_policy: None,
190 keys_dir: None,
191 platform: PlatformConfig::MacOs(MacOsConfig {
192 wrapping_key_user_presence: true,
193 wrapping_key_cache_ttl: Duration::ZERO,
194 keychain_access_group: None,
195 extra_bridge_paths: Vec::new(),
196 }),
197 };
198 let result = create_signer(&config);
200 assert!(
201 result.is_err(),
202 "unsigned binary with user_presence must return an error"
203 );
204 assert!(
205 matches!(result, Err(Error::RequiresSigning { .. })),
206 "expected RequiresSigning, got: {:?}",
207 result
208 );
209 }
210}