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