apple_security/os/macos/
keychain.rs

1//! Keychain support.
2
3use 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    /// A type representing a keychain.
20    SecKeychain, SecKeychainRef
21}
22impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID);
23
24unsafe impl Sync for SecKeychain {}
25unsafe impl Send for SecKeychain {}
26
27impl SecKeychain {
28    /// Creates a `SecKeychain` object corresponding to the user's default
29    /// keychain.
30    #[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    /// Creates a `SecKeychain` object corresponding to the user's default
40    /// keychain for the given domain.
41    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    /// Opens a keychain from a file.
50    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    /// Unlocks the keychain.
64    ///
65    /// If a password is not specified, the user will be prompted to enter it.
66    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    /// Sets settings of the keychain.
83    #[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    /// Disables the user interface for keychain services functions that
95    /// automatically display a user interface.
96    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    /// Indicates whether keychain services functions that normally display a
108    /// user interaction are allowed to do so.
109    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/// A builder type to create new keychains.
122#[derive(Default)]
123pub struct CreateOptions {
124    password: Option<String>,
125    prompt_user: bool,
126    access: Option<SecAccess>,
127}
128
129impl CreateOptions {
130    /// Creates a new builder with default options.
131    #[inline(always)]
132    #[must_use]
133    pub fn new() -> Self {
134        Self::default()
135    }
136
137    /// Sets the password to be used to protect the keychain.
138    #[inline]
139    pub fn password(&mut self, password: &str) -> &mut Self {
140        self.password = Some(password.into());
141        self
142    }
143
144    /// If set, the user will be prompted to provide a password used to
145    /// protect the keychain.
146    #[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    /// Sets the access control applied to the keychain.
153    #[inline(always)]
154    pub fn access(&mut self, access: SecAccess) -> &mut Self {
155        self.access = Some(access);
156        self
157    }
158
159    /// Creates a new keychain at the specified location on the filesystem.
160    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            // FIXME
164            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
191/// Settings associated with a `SecKeychain`.
192pub struct KeychainSettings(SecKeychainSettings);
193
194impl KeychainSettings {
195    /// Creates a new `KeychainSettings` with default settings.
196    #[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    /// If set, the keychain will automatically lock when the computer sleeps.
208    ///
209    /// Defaults to `false`.
210    #[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    /// Sets the interval of time in seconds after which the keychain is
216    /// automatically locked.
217    ///
218    /// Defaults to `None`.
219    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"]
242/// Automatically re-enables user interaction.
243pub 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}