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