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