Skip to main content

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}