apple_security/os/macos/
import_export.rs1use core_foundation::array::CFArray;
4use core_foundation::base::{CFType, TCFType};
5use core_foundation::data::CFData;
6use core_foundation::string::CFString;
7use apple_security_sys::base::errSecSuccess;
8use apple_security_sys::import_export::*;
9use std::ptr;
10use std::str::FromStr;
11
12use crate::base::{Error, Result};
13use crate::certificate::SecCertificate;
14use crate::identity::SecIdentity;
15use crate::import_export::Pkcs12ImportOptions;
16use crate::key::SecKey;
17use crate::os::macos::access::SecAccess;
18use crate::os::macos::keychain::SecKeychain;
19
20pub trait Pkcs12ImportOptionsExt {
22 fn keychain(&mut self, keychain: SecKeychain) -> &mut Self;
26
27 fn access(&mut self, access: SecAccess) -> &mut Self;
29}
30
31impl Pkcs12ImportOptionsExt for Pkcs12ImportOptions {
32 #[inline(always)]
33 fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
34 crate::Pkcs12ImportOptionsInternals::keychain(self, keychain)
35 }
36
37 #[inline(always)]
38 fn access(&mut self, access: SecAccess) -> &mut Self {
39 crate::Pkcs12ImportOptionsInternals::access(self, access)
40 }
41}
42
43#[derive(Default)]
45pub struct ImportOptions<'a> {
46 filename: Option<CFString>,
47 passphrase: Option<CFType>,
48 secure_passphrase: bool,
49 no_access_control: bool,
50 alert_title: Option<CFString>,
51 alert_prompt: Option<CFString>,
52 items: Option<&'a mut SecItems>,
53 keychain: Option<SecKeychain>,
54}
55
56impl<'a> ImportOptions<'a> {
57 #[inline(always)]
59 #[must_use]
60 pub fn new() -> ImportOptions<'a> {
61 ImportOptions::default()
62 }
63
64 #[inline]
68 pub fn filename(&mut self, filename: &str) -> &mut ImportOptions<'a> {
69 self.filename = Some(CFString::from_str(filename).unwrap());
70 self
71 }
72
73 #[inline]
75 pub fn passphrase(&mut self, passphrase: &str) -> &mut ImportOptions<'a> {
76 self.passphrase = Some(CFString::from_str(passphrase).unwrap().into_CFType());
77 self
78 }
79
80 #[inline]
82 pub fn passphrase_bytes(&mut self, passphrase: &[u8]) -> &mut ImportOptions<'a> {
83 self.passphrase = Some(CFData::from_buffer(passphrase).into_CFType());
84 self
85 }
86
87 #[inline(always)]
90 pub fn secure_passphrase(&mut self, secure_passphrase: bool) -> &mut ImportOptions<'a> {
91 self.secure_passphrase = secure_passphrase;
92 self
93 }
94
95 #[inline(always)]
97 pub fn no_access_control(&mut self, no_access_control: bool) -> &mut ImportOptions<'a> {
98 self.no_access_control = no_access_control;
99 self
100 }
101
102 #[inline]
105 pub fn alert_title(&mut self, alert_title: &str) -> &mut ImportOptions<'a> {
106 self.alert_title = Some(CFString::from_str(alert_title).unwrap());
107 self
108 }
109
110 #[inline]
113 pub fn alert_prompt(&mut self, alert_prompt: &str) -> &mut ImportOptions<'a> {
114 self.alert_prompt = Some(CFString::from_str(alert_prompt).unwrap());
115 self
116 }
117
118 #[inline(always)]
120 pub fn items(&mut self, items: &'a mut SecItems) -> &mut ImportOptions<'a> {
121 self.items = Some(items);
122 self
123 }
124
125 #[inline]
129 pub fn keychain(&mut self, keychain: &SecKeychain) -> &mut ImportOptions<'a> {
130 self.keychain = Some(keychain.clone());
131 self
132 }
133
134 pub fn import(&mut self, data: &[u8]) -> Result<()> {
136 let data = CFData::from_buffer(data);
137 let data = data.as_concrete_TypeRef();
138
139 let filename = match self.filename {
140 Some(ref filename) => filename.as_concrete_TypeRef(),
141 None => ptr::null(),
142 };
143
144 let mut key_params = SecItemImportExportKeyParameters {
145 version: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
146 flags: 0,
147 passphrase: ptr::null(),
148 alertTitle: ptr::null(),
149 alertPrompt: ptr::null(),
150 accessRef: ptr::null_mut(),
151 keyUsage: ptr::null_mut(),
152 keyAttributes: ptr::null(),
153 };
154
155 if let Some(ref passphrase) = self.passphrase {
156 key_params.passphrase = passphrase.as_CFTypeRef();
157 }
158
159 if self.secure_passphrase {
160 key_params.flags |= kSecKeySecurePassphrase;
161 }
162
163 if self.no_access_control {
164 key_params.flags |= kSecKeyNoAccessControl;
165 }
166
167 if let Some(ref alert_title) = self.alert_title {
168 key_params.alertTitle = alert_title.as_concrete_TypeRef();
169 }
170
171 if let Some(ref alert_prompt) = self.alert_prompt {
172 key_params.alertPrompt = alert_prompt.as_concrete_TypeRef();
173 }
174
175 let keychain = match self.keychain {
176 Some(ref keychain) => keychain.as_concrete_TypeRef(),
177 None => ptr::null_mut(),
178 };
179
180 let mut raw_items = ptr::null();
181 let items_ref = match self.items {
182 Some(_) => std::ptr::addr_of_mut!(raw_items),
183 None => ptr::null_mut(),
184 };
185
186 unsafe {
187 let ret = SecItemImport(
188 data,
189 filename,
190 ptr::null_mut(),
191 ptr::null_mut(),
192 0,
193 &key_params,
194 keychain,
195 items_ref,
196 );
197 if ret != errSecSuccess {
198 return Err(Error::from_code(ret));
199 }
200
201 if let Some(ref mut items) = self.items {
202 let raw_items = CFArray::<CFType>::wrap_under_create_rule(raw_items);
203 for item in raw_items.iter() {
204 let type_id = item.type_of();
205 if type_id == SecCertificate::type_id() {
206 items.certificates.push(SecCertificate::wrap_under_get_rule(
207 item.as_CFTypeRef() as *mut _,
208 ));
209 } else if type_id == SecIdentity::type_id() {
210 items.identities.push(SecIdentity::wrap_under_get_rule(
211 item.as_CFTypeRef() as *mut _,
212 ));
213 } else if type_id == SecKey::type_id() {
214 items
215 .keys
216 .push(SecKey::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
217 } else {
218 panic!("Got bad type from SecItemImport: {}", type_id);
219 }
220 }
221 }
222 }
223
224 Ok(())
225 }
226}
227
228#[derive(Default)]
232pub struct SecItems {
233 pub certificates: Vec<SecCertificate>,
235 pub identities: Vec<SecIdentity>,
237 pub keys: Vec<SecKey>,
239}
240
241#[cfg(test)]
242mod test {
243 use super::*;
244 use crate::import_export::*;
245 use crate::os::macos::keychain;
246 use hex;
247 use tempfile::tempdir;
248
249 #[test]
250 fn certificate() {
251 let data = include_bytes!("../../../test/server.der");
252 let mut items = SecItems::default();
253 ImportOptions::new()
254 .filename("server.der")
255 .items(&mut items)
256 .import(data)
257 .unwrap();
258 assert_eq!(1, items.certificates.len());
259 assert_eq!(0, items.identities.len());
260 assert_eq!(0, items.keys.len());
261 }
262
263 #[test]
264 fn key() {
265 let data = include_bytes!("../../../test/server.key");
266 let mut items = SecItems::default();
267 ImportOptions::new()
268 .filename("server.key")
269 .items(&mut items)
270 .import(data)
271 .unwrap();
272 assert_eq!(0, items.certificates.len());
273 assert_eq!(0, items.identities.len());
274 assert_eq!(1, items.keys.len());
275 }
276
277 #[test]
278 fn identity() {
279 let dir = tempdir().unwrap();
280 let keychain = keychain::CreateOptions::new()
281 .password("password")
282 .create(dir.path().join("identity.keychain"))
283 .unwrap();
284
285 let data = include_bytes!("../../../test/server.p12");
286 let mut items = SecItems::default();
287 ImportOptions::new()
288 .filename("server.p12")
289 .passphrase("password123")
290 .items(&mut items)
291 .keychain(&keychain)
292 .import(data)
293 .unwrap();
294 assert_eq!(1, items.identities.len());
295 assert_eq!(0, items.certificates.len());
296 assert_eq!(0, items.keys.len());
297 }
298
299 #[test]
300 #[ignore] fn secure_passphrase_identity() {
302 let dir = tempdir().unwrap();
303 let keychain = keychain::CreateOptions::new()
304 .password("password")
305 .create(dir.path().join("identity.keychain"))
306 .unwrap();
307
308 let data = include_bytes!("../../../test/server.p12");
309 let mut items = SecItems::default();
310 ImportOptions::new()
311 .filename("server.p12")
312 .secure_passphrase(true)
313 .alert_title("alert title")
314 .alert_prompt("alert prompt")
315 .items(&mut items)
316 .keychain(&keychain)
317 .import(data)
318 .unwrap();
319 assert_eq!(1, items.identities.len());
320 assert_eq!(0, items.certificates.len());
321 assert_eq!(0, items.keys.len());
322 }
323
324 #[test]
325 fn pkcs12_import() {
326 use super::Pkcs12ImportOptionsExt;
327
328 let dir = tempdir().unwrap();
329 let keychain = keychain::CreateOptions::new()
330 .password("password")
331 .create(dir.path().join("pkcs12_import"))
332 .unwrap();
333
334 let data = include_bytes!("../../../test/server.p12");
335 let identities = p!(Pkcs12ImportOptions::new()
336 .passphrase("password123")
337 .keychain(keychain)
338 .import(data));
339 assert_eq!(1, identities.len());
340 assert_eq!(
341 hex::encode(identities[0].key_id.as_ref().unwrap()),
342 "ed6492936dcc8907e397e573b36e633458dc33f1"
343 );
344 }
345}