apple_security_framework/os/macos/
keychain.rs

1//! Keychain support.
2
3use 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    /// A type representing a keychain.
25    SecKeychain, SecKeychainRef
26}
27impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID);
28
29unsafe impl Sync for SecKeychain {}
30unsafe impl Send for SecKeychain {}
31
32impl SecKeychain {
33    /// Creates a `SecKeychain` object corresponding to the user's default
34    /// keychain.
35    #[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    /// Creates a `SecKeychain` object corresponding to the user's default
46    /// keychain for the given domain.
47    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    /// Opens a keychain from a file.
56    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    /// Unlocks the keychain.
71    ///
72    /// If a password is not specified, the user will be prompted to enter it.
73    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    /// Sets settings of the keychain.
90    #[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    /// Disables the user interface for keychain services functions that
102    /// automatically display a user interface.
103    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    /// Indicates whether keychain services functions that normally display a
115    /// user interaction are allowed to do so.
116    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/// A builder type to create new keychains.
129#[derive(Default)]
130pub struct CreateOptions {
131    password: Option<String>,
132    prompt_user: bool,
133    access: Option<SecAccess>,
134}
135
136impl CreateOptions {
137    /// Creates a new builder with default options.
138    #[inline(always)]
139    #[must_use]
140    pub fn new() -> Self {
141        Self::default()
142    }
143
144    /// Sets the password to be used to protect the keychain.
145    #[inline]
146    pub fn password(&mut self, password: &str) -> &mut Self {
147        self.password = Some(password.into());
148        self
149    }
150
151    /// If set, the user will be prompted to provide a password used to
152    /// protect the keychain.
153    #[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    /// Sets the access control applied to the keychain.
160    #[inline(always)]
161    pub fn access(&mut self, access: SecAccess) -> &mut Self {
162        self.access = Some(access);
163        self
164    }
165
166    /// Creates a new keychain at the specified location on the filesystem.
167    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            // FIXME
171            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
198/// Settings associated with a `SecKeychain`.
199pub struct KeychainSettings(SecKeychainSettings);
200
201impl KeychainSettings {
202    /// Creates a new `KeychainSettings` with default settings.
203    #[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    /// If set, the keychain will automatically lock when the computer sleeps.
215    ///
216    /// Defaults to `false`.
217    #[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    /// Sets the interval of time in seconds after which the keychain is
223    /// automatically locked.
224    ///
225    /// Defaults to `None`.
226    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"]
249/// Automatically re-enables user interaction.
250pub 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}