1 //! Keychain support.
2 
3 use core_foundation::base::{Boolean, TCFType};
4 use security_framework_sys::base::{errSecSuccess, SecKeychainRef};
5 use security_framework_sys::keychain::*;
6 use std::ffi::CString;
7 use std::os::raw::c_void;
8 use std::os::unix::ffi::OsStrExt;
9 use std::path::Path;
10 use std::ptr;
11 
12 use crate::base::{Error, Result};
13 use crate::cvt;
14 use crate::os::macos::access::SecAccess;
15 
16 pub use security_framework_sys::keychain::SecPreferencesDomain;
17 
18 declare_TCFType! {
19     /// A type representing a keychain.
20     SecKeychain, SecKeychainRef
21 }
22 impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID);
23 
24 unsafe impl Sync for SecKeychain {}
25 unsafe impl Send for SecKeychain {}
26 
27 impl SecKeychain {
28     /// Creates a `SecKeychain` object corresponding to the user's default
29     /// keychain.
30     #[inline]
default() -> Result<Self>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.
default_for_domain(domain: SecPreferencesDomain) -> Result<Self>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.
open<P: AsRef<Path>>(path: P) -> Result<Self>50     pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
51         let path_name = path.as_ref().as_os_str().as_bytes();
52         // FIXME
53         let path_name = CString::new(path_name).unwrap();
54 
55         unsafe {
56             let mut keychain = ptr::null_mut();
57             cvt(SecKeychainOpen(path_name.as_ptr(), &mut keychain))?;
58             Ok(Self::wrap_under_create_rule(keychain))
59         }
60     }
61 
62     /// Unlocks the keychain.
63     ///
64     /// If a password is not specified, the user will be prompted to enter it.
unlock(&mut self, password: Option<&str>) -> Result<()>65     pub fn unlock(&mut self, password: Option<&str>) -> Result<()> {
66         let (len, ptr, use_password) = match password {
67             Some(password) => (password.len(), password.as_ptr() as *const _, true),
68             None => (0, ptr::null(), false),
69         };
70 
71         unsafe {
72             cvt(SecKeychainUnlock(
73                 self.as_concrete_TypeRef(),
74                 len as u32,
75                 ptr,
76                 use_password as Boolean,
77             ))
78         }
79     }
80 
81     /// Sets settings of the keychain.
82     #[inline]
set_settings(&mut self, settings: &KeychainSettings) -> Result<()>83     pub fn set_settings(&mut self, settings: &KeychainSettings) -> Result<()> {
84         unsafe {
85             cvt(SecKeychainSetSettings(
86                 self.as_concrete_TypeRef(),
87                 &settings.0,
88             ))
89         }
90     }
91 
92     #[cfg(target_os = "macos")]
93     /// Disables the user interface for keychain services functions that
94     /// automatically display a user interface.
disable_user_interaction() -> Result<KeychainUserInteractionLock>95     pub fn disable_user_interaction() -> Result<KeychainUserInteractionLock> {
96         let code = unsafe { SecKeychainSetUserInteractionAllowed(0u8) };
97 
98         if code != errSecSuccess {
99             Err(Error::from_code(code))
100         } else {
101             Ok(KeychainUserInteractionLock)
102         }
103     }
104 
105     #[cfg(target_os = "macos")]
106     /// Indicates whether keychain services functions that normally display a
107     /// user interaction are allowed to do so.
user_interaction_allowed() -> Result<bool>108     pub fn user_interaction_allowed() -> Result<bool> {
109         let mut state: Boolean = 0;
110         let code = unsafe { SecKeychainGetUserInteractionAllowed(&mut state) };
111 
112         if code != errSecSuccess {
113             Err(Error::from_code(code))
114         } else {
115             Ok(state != 0)
116         }
117     }
118 }
119 
120 /// A builder type to create new keychains.
121 #[derive(Default)]
122 pub struct CreateOptions {
123     password: Option<String>,
124     prompt_user: bool,
125     access: Option<SecAccess>,
126 }
127 
128 impl CreateOptions {
129     /// Creates a new builder with default options.
130     #[inline(always)]
new() -> Self131     pub fn new() -> Self {
132         Self::default()
133     }
134 
135     /// Sets the password to be used to protect the keychain.
136     #[inline]
password(&mut self, password: &str) -> &mut Self137     pub fn password(&mut self, password: &str) -> &mut Self {
138         self.password = Some(password.into());
139         self
140     }
141 
142     /// If set, the user will be prompted to provide a password used to
143     /// protect the keychain.
144     #[inline(always)]
prompt_user(&mut self, prompt_user: bool) -> &mut Self145     pub fn prompt_user(&mut self, prompt_user: bool) -> &mut Self {
146         self.prompt_user = prompt_user;
147         self
148     }
149 
150     /// Sets the access control applied to the keychain.
151     #[inline(always)]
access(&mut self, access: SecAccess) -> &mut Self152     pub fn access(&mut self, access: SecAccess) -> &mut Self {
153         self.access = Some(access);
154         self
155     }
156 
157     /// Creates a new keychain at the specified location on the filesystem.
create<P: AsRef<Path>>(&self, path: P) -> Result<SecKeychain>158     pub fn create<P: AsRef<Path>>(&self, path: P) -> Result<SecKeychain> {
159         unsafe {
160             let path_name = path.as_ref().as_os_str().as_bytes();
161             // FIXME
162             let path_name = CString::new(path_name).unwrap();
163 
164             let (password, password_len) = match self.password {
165                 Some(ref password) => (password.as_ptr() as *const c_void, password.len() as u32),
166                 None => (ptr::null(), 0),
167             };
168 
169             let access = match self.access {
170                 Some(ref access) => access.as_concrete_TypeRef(),
171                 None => ptr::null_mut(),
172             };
173 
174             let mut keychain = ptr::null_mut();
175             cvt(SecKeychainCreate(
176                 path_name.as_ptr(),
177                 password_len,
178                 password,
179                 self.prompt_user as Boolean,
180                 access,
181                 &mut keychain,
182             ))?;
183 
184             Ok(SecKeychain::wrap_under_create_rule(keychain))
185         }
186     }
187 }
188 
189 /// Settings associated with a `SecKeychain`.
190 pub struct KeychainSettings(SecKeychainSettings);
191 
192 impl KeychainSettings {
193     /// Creates a new `KeychainSettings` with default settings.
194     #[inline]
new() -> Self195     pub fn new() -> Self {
196         Self(SecKeychainSettings {
197             version: SEC_KEYCHAIN_SETTINGS_VERS1,
198             lockOnSleep: 0,
199             useLockInterval: 0,
200             lockInterval: i32::max_value() as u32,
201         })
202     }
203 
204     /// If set, the keychain will automatically lock when the computer sleeps.
205     ///
206     /// Defaults to `false`.
207     #[inline(always)]
set_lock_on_sleep(&mut self, lock_on_sleep: bool)208     pub fn set_lock_on_sleep(&mut self, lock_on_sleep: bool) {
209         self.0.lockOnSleep = lock_on_sleep as Boolean;
210     }
211 
212     /// Sets the interval of time in seconds after which the keychain is
213     /// automatically locked.
214     ///
215     /// Defaults to `None`.
set_lock_interval(&mut self, lock_interval: Option<u32>)216     pub fn set_lock_interval(&mut self, lock_interval: Option<u32>) {
217         match lock_interval {
218             Some(lock_interval) => {
219                 self.0.useLockInterval = 1;
220                 self.0.lockInterval = lock_interval;
221             }
222             None => {
223                 self.0.useLockInterval = 0;
224                 self.0.lockInterval = i32::max_value() as u32;
225             }
226         }
227     }
228 }
229 
230 impl Default for KeychainSettings {
231     #[inline(always)]
default() -> Self232     fn default() -> Self {
233         Self::new()
234     }
235 }
236 
237 #[cfg(target_os = "macos")]
238 #[must_use = "The user interaction is disabled for the lifetime of the returned object"]
239 /// Automatically re-enables user interaction.
240 pub struct KeychainUserInteractionLock;
241 
242 #[cfg(target_os = "macos")]
243 impl Drop for KeychainUserInteractionLock {
244     #[inline(always)]
drop(&mut self)245     fn drop(&mut self) {
246         unsafe { SecKeychainSetUserInteractionAllowed(1u8) };
247     }
248 }
249 
250 #[cfg(test)]
251 mod test {
252     use tempdir::TempDir;
253 
254     use super::*;
255 
256     #[test]
create_options()257     fn create_options() {
258         let dir = TempDir::new("keychain").unwrap();
259 
260         let mut keychain = CreateOptions::new()
261             .password("foobar")
262             .create(dir.path().join("test.keychain"))
263             .unwrap();
264 
265         keychain.set_settings(&KeychainSettings::new()).unwrap();
266     }
267 
268     #[test]
disable_user_interaction()269     fn disable_user_interaction() {
270         assert!(SecKeychain::user_interaction_allowed().unwrap());
271         {
272             let _lock = SecKeychain::disable_user_interaction().unwrap();
273             assert!(!SecKeychain::user_interaction_allowed().unwrap());
274         }
275         assert!(SecKeychain::user_interaction_allowed().unwrap());
276     }
277 }
278