apple_security/os/macos/
keychain.rs1use core_foundation::base::{Boolean, TCFType};
4use apple_security_sys::base::{errSecSuccess, SecKeychainRef};
5use apple_security_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 apple_security_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 pub fn default() -> Result<Self> {
32 unsafe {
33 let mut keychain = ptr::null_mut();
34 cvt(SecKeychainCopyDefault(&mut keychain))?;
35 Ok(Self::wrap_under_create_rule(keychain))
36 }
37 }
38
39 pub fn default_for_domain(domain: SecPreferencesDomain) -> Result<Self> {
42 unsafe {
43 let mut keychain = ptr::null_mut();
44 cvt(SecKeychainCopyDomainDefault(domain, &mut keychain))?;
45 Ok(Self::wrap_under_create_rule(keychain))
46 }
47 }
48
49 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
51 let path_name = [
52 path.as_ref().as_os_str().as_bytes(),
53 std::slice::from_ref(&0)
54 ].concat();
55
56 unsafe {
57 let mut keychain = ptr::null_mut();
58 cvt(SecKeychainOpen(path_name.as_ptr().cast(), &mut keychain))?;
59 Ok(Self::wrap_under_create_rule(keychain))
60 }
61 }
62
63 pub fn unlock(&mut self, password: Option<&str>) -> Result<()> {
67 let (len, ptr, use_password) = match password {
68 Some(password) => (password.len(), password.as_ptr().cast(), true),
69 None => (0, ptr::null(), false),
70 };
71
72 unsafe {
73 cvt(SecKeychainUnlock(
74 self.as_concrete_TypeRef(),
75 len as u32,
76 ptr,
77 use_password as Boolean,
78 ))
79 }
80 }
81
82 #[inline]
84 pub fn set_settings(&mut self, settings: &KeychainSettings) -> Result<()> {
85 unsafe {
86 cvt(SecKeychainSetSettings(
87 self.as_concrete_TypeRef(),
88 &settings.0,
89 ))
90 }
91 }
92
93 #[cfg(target_os = "macos")]
94 pub fn disable_user_interaction() -> Result<KeychainUserInteractionLock> {
97 let code = unsafe { SecKeychainSetUserInteractionAllowed(0u8) };
98
99 if code != errSecSuccess {
100 Err(Error::from_code(code))
101 } else {
102 Ok(KeychainUserInteractionLock)
103 }
104 }
105
106 #[cfg(target_os = "macos")]
107 pub fn user_interaction_allowed() -> Result<bool> {
110 let mut state: Boolean = 0;
111 let code = unsafe { SecKeychainGetUserInteractionAllowed(&mut state) };
112
113 if code != errSecSuccess {
114 Err(Error::from_code(code))
115 } else {
116 Ok(state != 0)
117 }
118 }
119}
120
121#[derive(Default)]
123pub struct CreateOptions {
124 password: Option<String>,
125 prompt_user: bool,
126 access: Option<SecAccess>,
127}
128
129impl CreateOptions {
130 #[inline(always)]
132 #[must_use]
133 pub fn new() -> Self {
134 Self::default()
135 }
136
137 #[inline]
139 pub fn password(&mut self, password: &str) -> &mut Self {
140 self.password = Some(password.into());
141 self
142 }
143
144 #[inline(always)]
147 pub fn prompt_user(&mut self, prompt_user: bool) -> &mut Self {
148 self.prompt_user = prompt_user;
149 self
150 }
151
152 #[inline(always)]
154 pub fn access(&mut self, access: SecAccess) -> &mut Self {
155 self.access = Some(access);
156 self
157 }
158
159 pub fn create<P: AsRef<Path>>(&self, path: P) -> Result<SecKeychain> {
161 unsafe {
162 let path_name = path.as_ref().as_os_str().as_bytes();
163 let path_name = CString::new(path_name).unwrap();
165
166 let (password, password_len) = match self.password {
167 Some(ref password) => (password.as_ptr().cast::<c_void>(), password.len() as u32),
168 None => (ptr::null(), 0),
169 };
170
171 let access = match self.access {
172 Some(ref access) => access.as_concrete_TypeRef(),
173 None => ptr::null_mut(),
174 };
175
176 let mut keychain = ptr::null_mut();
177 cvt(SecKeychainCreate(
178 path_name.as_ptr(),
179 password_len,
180 password,
181 self.prompt_user as Boolean,
182 access,
183 &mut keychain,
184 ))?;
185
186 Ok(SecKeychain::wrap_under_create_rule(keychain))
187 }
188 }
189}
190
191pub struct KeychainSettings(SecKeychainSettings);
193
194impl KeychainSettings {
195 #[inline]
197 #[must_use]
198 pub fn new() -> Self {
199 Self(SecKeychainSettings {
200 version: SEC_KEYCHAIN_SETTINGS_VERS1,
201 lockOnSleep: 0,
202 useLockInterval: 0,
203 lockInterval: i32::max_value() as u32,
204 })
205 }
206
207 #[inline(always)]
211 pub fn set_lock_on_sleep(&mut self, lock_on_sleep: bool) {
212 self.0.lockOnSleep = lock_on_sleep as Boolean;
213 }
214
215 pub fn set_lock_interval(&mut self, lock_interval: Option<u32>) {
220 match lock_interval {
221 Some(lock_interval) => {
222 self.0.useLockInterval = 1;
223 self.0.lockInterval = lock_interval;
224 }
225 None => {
226 self.0.useLockInterval = 0;
227 self.0.lockInterval = i32::max_value() as u32;
228 }
229 }
230 }
231}
232
233impl Default for KeychainSettings {
234 #[inline(always)]
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240#[cfg(target_os = "macos")]
241#[must_use = "The user interaction is disabled for the lifetime of the returned object"]
242pub struct KeychainUserInteractionLock;
244
245#[cfg(target_os = "macos")]
246impl Drop for KeychainUserInteractionLock {
247 #[inline(always)]
248 fn drop(&mut self) {
249 unsafe { SecKeychainSetUserInteractionAllowed(1u8) };
250 }
251}
252
253#[cfg(test)]
254mod test {
255 use tempfile::tempdir;
256
257 use super::*;
258
259 #[test]
260 fn create_options() {
261 let dir = tempdir().unwrap();
262
263 let mut keychain = CreateOptions::new()
264 .password("foobar")
265 .create(dir.path().join("test.keychain"))
266 .unwrap();
267
268 keychain.set_settings(&KeychainSettings::new()).unwrap();
269 }
270
271 #[test]
272 fn disable_user_interaction() {
273 assert!(SecKeychain::user_interaction_allowed().unwrap());
274 {
275 let _lock = SecKeychain::disable_user_interaction().unwrap();
276 assert!(!SecKeychain::user_interaction_allowed().unwrap());
277 }
278 assert!(SecKeychain::user_interaction_allowed().unwrap());
279 }
280}