security_framework/os/macos/
keychain.rs1use core_foundation::base::{Boolean, TCFType};
3use core_foundation::{declare_TCFType, impl_TCFType};
4use security_framework_sys::base::{errSecSuccess, SecKeychainRef};
5use security_framework_sys::keychain::*;
6use std::ffi::CString;
7use std::os::raw::c_void;
8use std::os::unix::ffi::OsStrExt;
9use std::path::Path;
10use std::ptr;
11
12use crate::base::{Error, Result};
13use crate::cvt;
14use crate::os::macos::access::SecAccess;
15
16pub use security_framework_sys::keychain::SecPreferencesDomain;
17
18declare_TCFType! {
19 SecKeychain, SecKeychainRef
21}
22impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID);
23
24unsafe impl Sync for SecKeychain {}
25unsafe impl Send for SecKeychain {}
26
27impl SecKeychain {
28 #[inline]
31 #[allow(clippy::should_implement_trait)]
32 pub fn default() -> Result<Self> {
33 unsafe {
34 let mut keychain = ptr::null_mut();
35 cvt(SecKeychainCopyDefault(&mut keychain))?;
36 Ok(Self::wrap_under_create_rule(keychain))
37 }
38 }
39
40 pub fn default_for_domain(domain: SecPreferencesDomain) -> Result<Self> {
43 unsafe {
44 let mut keychain = ptr::null_mut();
45 cvt(SecKeychainCopyDomainDefault(domain, &mut keychain))?;
46 Ok(Self::wrap_under_create_rule(keychain))
47 }
48 }
49
50 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
52 let path_name = [
53 path.as_ref().as_os_str().as_bytes(),
54 std::slice::from_ref(&0),
55 ]
56 .concat();
57
58 unsafe {
59 let mut keychain = ptr::null_mut();
60 cvt(SecKeychainOpen(path_name.as_ptr().cast(), &mut keychain))?;
61 Ok(Self::wrap_under_create_rule(keychain))
62 }
63 }
64
65 pub fn unlock(&mut self, password: Option<&str>) -> Result<()> {
69 let (len, ptr, use_password) = match password {
70 Some(password) => (password.len(), password.as_ptr().cast(), true),
71 None => (0, ptr::null(), false),
72 };
73
74 unsafe {
75 cvt(SecKeychainUnlock(
76 self.as_concrete_TypeRef(),
77 len as u32,
78 ptr,
79 Boolean::from(use_password),
80 ))
81 }
82 }
83
84 #[inline]
86 pub fn set_settings(&mut self, settings: &KeychainSettings) -> Result<()> {
87 unsafe {
88 cvt(SecKeychainSetSettings(
89 self.as_concrete_TypeRef(),
90 &settings.0,
91 ))
92 }
93 }
94
95 #[cfg(target_os = "macos")]
96 pub fn disable_user_interaction() -> Result<KeychainUserInteractionLock> {
99 let code = unsafe { SecKeychainSetUserInteractionAllowed(0u8) };
100
101 if code == errSecSuccess {
102 Ok(KeychainUserInteractionLock)
103 } else {
104 Err(Error::from_code(code))
105 }
106 }
107
108 #[cfg(target_os = "macos")]
109 pub fn user_interaction_allowed() -> Result<bool> {
112 let mut state: Boolean = 0;
113 let code = unsafe { SecKeychainGetUserInteractionAllowed(&mut state) };
114
115 if code == errSecSuccess {
116 Ok(state != 0)
117 } else {
118 Err(Error::from_code(code))
119 }
120 }
121}
122
123#[derive(Default)]
125pub struct CreateOptions {
126 password: Option<String>,
127 prompt_user: bool,
128 access: Option<SecAccess>,
129}
130
131impl CreateOptions {
132 #[inline(always)]
134 #[must_use]
135 pub fn new() -> Self {
136 Self::default()
137 }
138
139 #[inline]
141 pub fn password(&mut self, password: &str) -> &mut Self {
142 self.password = Some(password.into());
143 self
144 }
145
146 #[inline(always)]
149 pub fn prompt_user(&mut self, prompt_user: bool) -> &mut Self {
150 self.prompt_user = prompt_user;
151 self
152 }
153
154 #[inline(always)]
156 pub fn access(&mut self, access: SecAccess) -> &mut Self {
157 self.access = Some(access);
158 self
159 }
160
161 pub fn create<P: AsRef<Path>>(&self, path: P) -> Result<SecKeychain> {
163 unsafe {
164 let path_name = path.as_ref().as_os_str().as_bytes();
165 let path_name = CString::new(path_name).unwrap();
167
168 let (password, password_len) = match self.password {
169 Some(ref password) => (password.as_ptr().cast::<c_void>(), password.len() as u32),
170 None => (ptr::null(), 0),
171 };
172
173 let access = match self.access {
174 Some(ref access) => access.as_concrete_TypeRef(),
175 None => ptr::null_mut(),
176 };
177
178 let mut keychain = ptr::null_mut();
179 cvt(SecKeychainCreate(
180 path_name.as_ptr(),
181 password_len,
182 password,
183 Boolean::from(self.prompt_user),
184 access,
185 &mut keychain,
186 ))?;
187
188 Ok(SecKeychain::wrap_under_create_rule(keychain))
189 }
190 }
191}
192
193pub struct KeychainSettings(SecKeychainSettings);
195
196impl KeychainSettings {
197 #[inline]
199 #[must_use]
200 pub fn new() -> Self {
201 Self(SecKeychainSettings {
202 version: SEC_KEYCHAIN_SETTINGS_VERS1,
203 lockOnSleep: 0,
204 useLockInterval: 0,
205 lockInterval: i32::MAX as u32,
206 })
207 }
208
209 #[inline(always)]
213 pub fn set_lock_on_sleep(&mut self, lock_on_sleep: bool) {
214 self.0.lockOnSleep = Boolean::from(lock_on_sleep);
215 }
216
217 pub fn set_lock_interval(&mut self, lock_interval: Option<u32>) {
222 if let Some(lock_interval) = lock_interval {
223 self.0.useLockInterval = 1;
224 self.0.lockInterval = lock_interval;
225 } else {
226 self.0.useLockInterval = 0;
227 self.0.lockInterval = i32::MAX as u32;
228 }
229 }
230}
231
232impl Default for KeychainSettings {
233 #[inline(always)]
234 fn default() -> Self {
235 Self::new()
236 }
237}
238
239#[cfg(target_os = "macos")]
240#[must_use = "The user interaction is disabled for the lifetime of the returned object"]
241pub struct KeychainUserInteractionLock;
243
244#[cfg(target_os = "macos")]
245impl Drop for KeychainUserInteractionLock {
246 #[inline(always)]
247 fn drop(&mut self) {
248 unsafe { SecKeychainSetUserInteractionAllowed(1u8) };
249 }
250}
251
252#[cfg(test)]
253mod test {
254 use tempfile::tempdir;
255
256 use super::*;
257
258 #[test]
259 fn create_options() {
260 let dir = tempdir().unwrap();
261
262 let mut keychain = CreateOptions::new()
263 .password("foobar")
264 .create(dir.path().join("test.keychain"))
265 .unwrap();
266
267 keychain.set_settings(&KeychainSettings::new()).unwrap();
268 }
269
270 #[test]
271 fn disable_user_interaction() {
272 assert!(SecKeychain::user_interaction_allowed().unwrap());
273 {
274 let _lock = SecKeychain::disable_user_interaction().unwrap();
275 assert!(!SecKeychain::user_interaction_allowed().unwrap());
276 }
277 assert!(SecKeychain::user_interaction_allowed().unwrap());
278 }
279}