use core_foundation::base::{Boolean, TCFType};
use security_framework_sys::base::SecKeychainRef;
use security_framework_sys::keychain::*;
use std::ffi::CString;
use std::os::raw::c_void;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::ptr;
use crate::base::Result;
use crate::cvt;
use crate::os::macos::access::SecAccess;
declare_TCFType! {
SecKeychain, SecKeychainRef
}
impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID);
unsafe impl Sync for SecKeychain {}
unsafe impl Send for SecKeychain {}
impl SecKeychain {
pub fn default() -> Result<Self> {
unsafe {
let mut keychain = ptr::null_mut();
cvt(SecKeychainCopyDefault(&mut keychain))?;
Ok(Self::wrap_under_create_rule(keychain))
}
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_name = path.as_ref().as_os_str().as_bytes();
let path_name = CString::new(path_name).unwrap();
unsafe {
let mut keychain = ptr::null_mut();
cvt(SecKeychainOpen(path_name.as_ptr(), &mut keychain))?;
Ok(Self::wrap_under_create_rule(keychain))
}
}
pub fn unlock(&mut self, password: Option<&str>) -> Result<()> {
let (len, ptr, use_password) = match password {
Some(password) => (password.len(), password.as_ptr() as *const _, true),
None => (0, ptr::null(), false),
};
unsafe {
cvt(SecKeychainUnlock(
self.as_concrete_TypeRef(),
len as u32,
ptr,
use_password as Boolean,
))
}
}
pub fn set_settings(&mut self, settings: &KeychainSettings) -> Result<()> {
unsafe {
cvt(SecKeychainSetSettings(
self.as_concrete_TypeRef(),
&settings.0,
))
}
}
}
#[derive(Default)]
pub struct CreateOptions {
password: Option<String>,
prompt_user: bool,
access: Option<SecAccess>,
}
impl CreateOptions {
pub fn new() -> Self {
Self::default()
}
pub fn password(&mut self, password: &str) -> &mut Self {
self.password = Some(password.into());
self
}
pub fn prompt_user(&mut self, prompt_user: bool) -> &mut Self {
self.prompt_user = prompt_user;
self
}
pub fn access(&mut self, access: SecAccess) -> &mut Self {
self.access = Some(access);
self
}
pub fn create<P: AsRef<Path>>(&self, path: P) -> Result<SecKeychain> {
unsafe {
let path_name = path.as_ref().as_os_str().as_bytes();
let path_name = CString::new(path_name).unwrap();
let (password, password_len) = match self.password {
Some(ref password) => (password.as_ptr() as *const c_void, password.len() as u32),
None => (ptr::null(), 0),
};
let access = match self.access {
Some(ref access) => access.as_concrete_TypeRef(),
None => ptr::null_mut(),
};
let mut keychain = ptr::null_mut();
cvt(SecKeychainCreate(
path_name.as_ptr(),
password_len,
password,
self.prompt_user as Boolean,
access,
&mut keychain,
))?;
Ok(SecKeychain::wrap_under_create_rule(keychain))
}
}
}
pub struct KeychainSettings(SecKeychainSettings);
impl KeychainSettings {
pub fn new() -> Self {
Self(SecKeychainSettings {
version: SEC_KEYCHAIN_SETTINGS_VERS1,
lockOnSleep: 0,
useLockInterval: 0,
lockInterval: i32::max_value() as u32,
})
}
pub fn set_lock_on_sleep(&mut self, lock_on_sleep: bool) {
self.0.lockOnSleep = lock_on_sleep as Boolean;
}
pub fn set_lock_interval(&mut self, lock_interval: Option<u32>) {
match lock_interval {
Some(lock_interval) => {
self.0.useLockInterval = 1;
self.0.lockInterval = lock_interval;
}
None => {
self.0.useLockInterval = 0;
self.0.lockInterval = i32::max_value() as u32;
}
}
}
}
#[cfg(test)]
mod test {
use tempdir::TempDir;
use super::*;
#[test]
fn create_options() {
let dir = TempDir::new("keychain").unwrap();
let mut keychain = CreateOptions::new()
.password("foobar")
.create(dir.path().join("test.keychain"))
.unwrap();
keychain.set_settings(&KeychainSettings::new()).unwrap();
}
}