security_framework/os/macos/
keychain.rs

1//! Keychain support.
2use 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    /// 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    #[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    /// Creates a `SecKeychain` object corresponding to the user's default
41    /// keychain for the given domain.
42    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    /// Opens a keychain from a file.
51    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    /// Unlocks the keychain.
66    ///
67    /// If a password is not specified, the user will be prompted to enter it.
68    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    /// Sets settings of the keychain.
85    #[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    /// Disables the user interface for keychain services functions that
97    /// automatically display a user interface.
98    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    /// Indicates whether keychain services functions that normally display a
110    /// user interaction are allowed to do so.
111    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/// A builder type to create new keychains.
124#[derive(Default)]
125pub struct CreateOptions {
126    password: Option<String>,
127    prompt_user: bool,
128    access: Option<SecAccess>,
129}
130
131impl CreateOptions {
132    /// Creates a new builder with default options.
133    #[inline(always)]
134    #[must_use]
135    pub fn new() -> Self {
136        Self::default()
137    }
138
139    /// Sets the password to be used to protect the keychain.
140    #[inline]
141    pub fn password(&mut self, password: &str) -> &mut Self {
142        self.password = Some(password.into());
143        self
144    }
145
146    /// If set, the user will be prompted to provide a password used to
147    /// protect the keychain.
148    #[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    /// Sets the access control applied to the keychain.
155    #[inline(always)]
156    pub fn access(&mut self, access: SecAccess) -> &mut Self {
157        self.access = Some(access);
158        self
159    }
160
161    /// Creates a new keychain at the specified location on the filesystem.
162    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            // FIXME
166            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
193/// Settings associated with a `SecKeychain`.
194pub struct KeychainSettings(SecKeychainSettings);
195
196impl KeychainSettings {
197    /// Creates a new `KeychainSettings` with default settings.
198    #[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    /// If set, the keychain will automatically lock when the computer sleeps.
210    ///
211    /// Defaults to `false`.
212    #[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    /// Sets the interval of time in seconds after which the keychain is
218    /// automatically locked.
219    ///
220    /// Defaults to `None`.
221    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"]
241/// Automatically re-enables user interaction.
242pub 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}