use core_foundation::array::CFArray;
use core_foundation::base::{CFType, TCFType};
use core_foundation::data::CFData;
use core_foundation::string::CFString;
use security_framework_sys::base::errSecSuccess;
use security_framework_sys::import_export::*;
use std::ptr;
use std::str::FromStr;
use crate::base::{Error, Result};
use crate::certificate::SecCertificate;
use crate::identity::SecIdentity;
use crate::import_export::Pkcs12ImportOptions;
use crate::key::SecKey;
use crate::os::macos::access::SecAccess;
use crate::os::macos::keychain::SecKeychain;
pub trait Pkcs12ImportOptionsExt {
fn keychain(&mut self, keychain: SecKeychain) -> &mut Self;
fn access(&mut self, access: SecAccess) -> &mut Self;
}
impl Pkcs12ImportOptionsExt for Pkcs12ImportOptions {
#[inline(always)]
fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
crate::Pkcs12ImportOptionsInternals::keychain(self, keychain)
}
#[inline(always)]
fn access(&mut self, access: SecAccess) -> &mut Self {
crate::Pkcs12ImportOptionsInternals::access(self, access)
}
}
#[derive(Default)]
pub struct ImportOptions<'a> {
filename: Option<CFString>,
passphrase: Option<CFType>,
secure_passphrase: bool,
no_access_control: bool,
alert_title: Option<CFString>,
alert_prompt: Option<CFString>,
items: Option<&'a mut SecItems>,
keychain: Option<SecKeychain>,
}
impl<'a> ImportOptions<'a> {
#[inline(always)]
#[must_use]
pub fn new() -> ImportOptions<'a> {
ImportOptions::default()
}
#[inline]
pub fn filename(&mut self, filename: &str) -> &mut ImportOptions<'a> {
self.filename = Some(CFString::from_str(filename).unwrap());
self
}
#[inline]
pub fn passphrase(&mut self, passphrase: &str) -> &mut ImportOptions<'a> {
self.passphrase = Some(CFString::from_str(passphrase).unwrap().into_CFType());
self
}
#[inline]
pub fn passphrase_bytes(&mut self, passphrase: &[u8]) -> &mut ImportOptions<'a> {
self.passphrase = Some(CFData::from_buffer(passphrase).into_CFType());
self
}
#[inline(always)]
pub fn secure_passphrase(&mut self, secure_passphrase: bool) -> &mut ImportOptions<'a> {
self.secure_passphrase = secure_passphrase;
self
}
#[inline(always)]
pub fn no_access_control(&mut self, no_access_control: bool) -> &mut ImportOptions<'a> {
self.no_access_control = no_access_control;
self
}
#[inline]
pub fn alert_title(&mut self, alert_title: &str) -> &mut ImportOptions<'a> {
self.alert_title = Some(CFString::from_str(alert_title).unwrap());
self
}
#[inline]
pub fn alert_prompt(&mut self, alert_prompt: &str) -> &mut ImportOptions<'a> {
self.alert_prompt = Some(CFString::from_str(alert_prompt).unwrap());
self
}
#[inline(always)]
pub fn items(&mut self, items: &'a mut SecItems) -> &mut ImportOptions<'a> {
self.items = Some(items);
self
}
#[inline]
pub fn keychain(&mut self, keychain: &SecKeychain) -> &mut ImportOptions<'a> {
self.keychain = Some(keychain.clone());
self
}
pub fn import(&mut self, data: &[u8]) -> Result<()> {
let data = CFData::from_buffer(data);
let data = data.as_concrete_TypeRef();
let filename = match self.filename {
Some(ref filename) => filename.as_concrete_TypeRef(),
None => ptr::null(),
};
let mut key_params = SecItemImportExportKeyParameters {
version: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
flags: 0,
passphrase: ptr::null(),
alertTitle: ptr::null(),
alertPrompt: ptr::null(),
accessRef: ptr::null_mut(),
keyUsage: ptr::null_mut(),
keyAttributes: ptr::null(),
};
if let Some(ref passphrase) = self.passphrase {
key_params.passphrase = passphrase.as_CFTypeRef();
}
if self.secure_passphrase {
key_params.flags |= kSecKeySecurePassphrase;
}
if self.no_access_control {
key_params.flags |= kSecKeyNoAccessControl;
}
if let Some(ref alert_title) = self.alert_title {
key_params.alertTitle = alert_title.as_concrete_TypeRef();
}
if let Some(ref alert_prompt) = self.alert_prompt {
key_params.alertPrompt = alert_prompt.as_concrete_TypeRef();
}
let keychain = match self.keychain {
Some(ref keychain) => keychain.as_concrete_TypeRef(),
None => ptr::null_mut(),
};
let mut raw_items = ptr::null();
let items_ref = match self.items {
Some(_) => std::ptr::addr_of_mut!(raw_items),
None => ptr::null_mut(),
};
unsafe {
let ret = SecItemImport(
data,
filename,
ptr::null_mut(),
ptr::null_mut(),
0,
&key_params,
keychain,
items_ref,
);
if ret != errSecSuccess {
return Err(Error::from_code(ret));
}
if let Some(ref mut items) = self.items {
let raw_items = CFArray::<CFType>::wrap_under_create_rule(raw_items);
for item in raw_items.iter() {
let type_id = item.type_of();
if type_id == SecCertificate::type_id() {
items.certificates.push(SecCertificate::wrap_under_get_rule(
item.as_CFTypeRef() as *mut _,
));
} else if type_id == SecIdentity::type_id() {
items.identities.push(SecIdentity::wrap_under_get_rule(
item.as_CFTypeRef() as *mut _,
));
} else if type_id == SecKey::type_id() {
items
.keys
.push(SecKey::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
} else {
panic!("Got bad type from SecItemImport: {type_id}");
}
}
}
}
Ok(())
}
}
#[derive(Default)]
pub struct SecItems {
pub certificates: Vec<SecCertificate>,
pub identities: Vec<SecIdentity>,
pub keys: Vec<SecKey>,
}
#[cfg(test)]
mod test {
use super::*;
use crate::import_export::*;
use crate::os::macos::keychain;
use tempfile::tempdir;
#[test]
fn certificate() {
let data = include_bytes!("../../../test/server.der");
let mut items = SecItems::default();
ImportOptions::new()
.filename("server.der")
.items(&mut items)
.import(data)
.unwrap();
assert_eq!(1, items.certificates.len());
assert_eq!(0, items.identities.len());
assert_eq!(0, items.keys.len());
}
#[test]
fn key() {
let data = include_bytes!("../../../test/server.key");
let mut items = SecItems::default();
ImportOptions::new()
.filename("server.key")
.items(&mut items)
.import(data)
.unwrap();
assert_eq!(0, items.certificates.len());
assert_eq!(0, items.identities.len());
assert_eq!(1, items.keys.len());
}
#[test]
fn identity() {
let dir = tempdir().unwrap();
let keychain = keychain::CreateOptions::new()
.password("password")
.create(dir.path().join("identity.keychain"))
.unwrap();
let data = include_bytes!("../../../test/server.p12");
let mut items = SecItems::default();
ImportOptions::new()
.filename("server.p12")
.passphrase("password123")
.items(&mut items)
.keychain(&keychain)
.import(data)
.unwrap();
assert_eq!(1, items.identities.len());
assert_eq!(0, items.certificates.len());
assert_eq!(0, items.keys.len());
}
#[test]
#[ignore] fn secure_passphrase_identity() {
let dir = tempdir().unwrap();
let keychain = keychain::CreateOptions::new()
.password("password")
.create(dir.path().join("identity.keychain"))
.unwrap();
let data = include_bytes!("../../../test/server.p12");
let mut items = SecItems::default();
ImportOptions::new()
.filename("server.p12")
.secure_passphrase(true)
.alert_title("alert title")
.alert_prompt("alert prompt")
.items(&mut items)
.keychain(&keychain)
.import(data)
.unwrap();
assert_eq!(1, items.identities.len());
assert_eq!(0, items.certificates.len());
assert_eq!(0, items.keys.len());
}
#[test]
fn pkcs12_import() {
use super::Pkcs12ImportOptionsExt;
let dir = tempdir().unwrap();
let keychain = keychain::CreateOptions::new()
.password("password")
.create(dir.path().join("pkcs12_import"))
.unwrap();
let data = include_bytes!("../../../test/server.p12");
let identities = p!(Pkcs12ImportOptions::new()
.passphrase("password123")
.keychain(keychain)
.import(data));
assert_eq!(1, identities.len());
assert_eq!(
hex::encode(identities[0].key_id.as_ref().unwrap()),
"ed6492936dcc8907e397e573b36e633458dc33f1"
);
}
}