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