hardware_enclave/config.rs
1// Copyright 2026 Jay Gowdy
2// SPDX-License-Identifier: MIT
3
4use std::path::PathBuf;
5use std::time::Duration;
6
7use crate::internal::app_storage::StorageConfig;
8pub use crate::internal::app_storage::WindowsSoftwareFallback;
9use crate::types::AccessPolicy;
10
11/// Platform-specific escape hatches. Use `PlatformConfig::Default` for the common case.
12#[derive(Debug, Clone, Default)]
13pub enum PlatformConfig {
14 /// Auto-detect the platform and apply sensible defaults: no wrapping-key user-presence,
15 /// no keychain access group, standard keys directory.
16 #[default]
17 Default,
18 /// macOS-specific overrides. See [`MacOsConfig`].
19 MacOs(MacOsConfig),
20 /// Windows-specific overrides. See [`WindowsConfig`].
21 Windows(WindowsConfig),
22 /// Linux-specific overrides. See [`LinuxConfig`].
23 Linux(LinuxConfig),
24}
25
26/// macOS-specific configuration overrides.
27///
28/// When using struct-update syntax (`..MacOsConfig::default()`), newly added
29/// security-relevant fields will use their default values. Review the changelog
30/// when updating the crate version to check for new fields.
31#[derive(Debug, Clone)]
32pub struct MacOsConfig {
33 /// Protect the wrapping key with a `.userPresence` ACL (requires `keychain_access_group`
34 /// and the `keychain-access-groups` entitlement). Each decrypt/sign will prompt once per
35 /// `wrapping_key_cache_ttl`.
36 pub wrapping_key_user_presence: bool,
37 /// How long a loaded wrapping key may be reused without another LAContext prompt.
38 /// `Duration::ZERO` means prompt on every operation.
39 pub wrapping_key_cache_ttl: Duration,
40 /// `<TEAMID>.<group>` access group. Requires keychain-access-groups entitlement.
41 pub keychain_access_group: Option<String>,
42 /// Extra WSL bridge discovery paths.
43 pub extra_bridge_paths: Vec<String>,
44}
45
46impl Default for MacOsConfig {
47 fn default() -> Self {
48 Self {
49 wrapping_key_user_presence: false,
50 wrapping_key_cache_ttl: Duration::ZERO,
51 keychain_access_group: None,
52 extra_bridge_paths: Vec::new(),
53 }
54 }
55}
56
57/// Windows-specific configuration overrides.
58///
59/// When using struct-update syntax (`..WindowsConfig::default()`), newly added
60/// security-relevant fields will use their default values. Review the changelog
61/// when updating the crate version to check for new fields.
62#[derive(Debug, Clone)]
63pub struct WindowsConfig {
64 /// Surface a Windows Hello biometric/PIN prompt at encrypt/decrypt time.
65 /// When `false`, uses the legacy CryptUI password dialog.
66 pub prefer_windows_hello_ux: bool,
67 /// Whether a VM without a usable TPM may fall back to DPAPI-backed software keys.
68 pub software_fallback: WindowsSoftwareFallback,
69 /// Optional application-layer AES-256-GCM key applied around DPAPI when the
70 /// software fallback is in use. Defeats automated DPAPI oracle tools that don't
71 /// carry knowledge of this binary.
72 pub dpapi_app_key: Option<[u8; 32]>,
73}
74
75impl Default for WindowsConfig {
76 fn default() -> Self {
77 Self {
78 prefer_windows_hello_ux: false,
79 software_fallback: WindowsSoftwareFallback::Disabled,
80 dpapi_app_key: None,
81 }
82 }
83}
84
85/// Linux-specific configuration.
86#[derive(Debug, Clone, Default)]
87pub struct LinuxConfig {
88 /// Force the software keyring backend, bypassing WSL bridge detection and TPM probing.
89 pub force_keyring: bool,
90 /// Additional paths to search for the Windows TPM bridge executable (WSL only).
91 pub extra_bridge_paths: Vec<String>,
92}
93
94/// Configuration for all enclave handles created via factory functions.
95#[derive(Debug, Clone)]
96pub struct EnclaveConfig {
97 /// Requested app identifier. The `-unsigned` suffix is applied automatically
98 /// for unsigned binaries to prevent key namespace collisions.
99 pub app_name: String,
100 /// Default key label for factory-initialized keys.
101 pub default_key_label: String,
102 /// Default access policy for new keys. When None, the factory picks
103 /// AccessPolicy::None for signed binaries and AccessPolicy::Any for unsigned.
104 pub access_policy: Option<AccessPolicy>,
105 /// Override key storage directory (default: platform default).
106 pub keys_dir: Option<PathBuf>,
107 /// Platform-specific overrides.
108 pub platform: PlatformConfig,
109}
110
111impl EnclaveConfig {
112 /// Create a config with sensible defaults. The binary's signing state is detected
113 /// automatically; unsigned binaries get `-unsigned` appended to `app_name`.
114 pub fn new(app_name: impl Into<String>, default_key_label: impl Into<String>) -> Self {
115 Self {
116 app_name: app_name.into(),
117 default_key_label: default_key_label.into(),
118 access_policy: None,
119 keys_dir: None,
120 platform: PlatformConfig::Default,
121 }
122 }
123
124 /// Resolved effective app name (with -unsigned applied if needed).
125 pub fn effective_app_name(&self) -> String {
126 crate::internal::core::signing::ensure_safe_app_name(&self.app_name)
127 }
128
129 /// Resolved access policy: explicit override, or signed->None / unsigned->Any.
130 pub fn resolved_access_policy(&self) -> AccessPolicy {
131 self.access_policy.unwrap_or_else(|| {
132 if crate::internal::core::signing::is_binary_signed() {
133 AccessPolicy::None
134 } else {
135 AccessPolicy::Any
136 }
137 })
138 }
139
140 /// Build a StorageConfig for enclaveapp-app-storage.
141 pub(crate) fn to_storage_config(&self) -> StorageConfig {
142 let (
143 wrapping_key_user_presence,
144 wrapping_key_cache_ttl,
145 keychain_access_group,
146 extra_bridge_paths,
147 force_keyring,
148 prefer_windows_hello_ux,
149 windows_software_fallback,
150 dpapi_app_key,
151 ) = match &self.platform {
152 PlatformConfig::MacOs(m) => (
153 m.wrapping_key_user_presence,
154 m.wrapping_key_cache_ttl,
155 m.keychain_access_group.clone(),
156 m.extra_bridge_paths.clone(),
157 false,
158 false,
159 WindowsSoftwareFallback::Disabled,
160 None,
161 ),
162 PlatformConfig::Windows(w) => (
163 false,
164 Duration::ZERO,
165 None,
166 Vec::new(),
167 false,
168 w.prefer_windows_hello_ux,
169 w.software_fallback,
170 w.dpapi_app_key,
171 ),
172 PlatformConfig::Linux(l) => (
173 false,
174 Duration::ZERO,
175 None,
176 l.extra_bridge_paths.clone(),
177 l.force_keyring,
178 false,
179 WindowsSoftwareFallback::Disabled,
180 None,
181 ),
182 PlatformConfig::Default => (
183 false,
184 Duration::ZERO,
185 None,
186 Vec::new(),
187 false,
188 false,
189 WindowsSoftwareFallback::Disabled,
190 None,
191 ),
192 };
193
194 StorageConfig {
195 app_name: self.effective_app_name(),
196 key_label: self.default_key_label.clone(),
197 access_policy: self.resolved_access_policy(),
198 extra_bridge_paths,
199 keys_dir: self.keys_dir.clone(),
200 force_keyring,
201 wrapping_key_user_presence,
202 wrapping_key_cache_ttl,
203 keychain_access_group,
204 prefer_windows_hello_ux,
205 windows_software_fallback,
206 dpapi_app_key,
207 }
208 }
209}