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
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() -> Self {
61 ImportOptions::default()
62 }
63
64 #[inline]
68 pub fn filename(&mut self, filename: &str) -> &mut Self {
69 self.filename = Some(CFString::from_str(filename).unwrap());
70 self
71 }
72
73 #[inline]
75 pub fn passphrase(&mut self, passphrase: &str) -> &mut Self {
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 Self {
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 Self {
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 Self {
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 Self {
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 Self {
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 Self {
121 self.items = Some(items);
122 self
123 }
124
125 #[inline]
129 pub fn keychain(&mut self, keychain: &SecKeychain) -> &mut Self {
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(item.as_CFTypeRef() as *mut _));
207 } else if type_id == SecIdentity::type_id() {
208 items.identities.push(SecIdentity::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
209 } else if type_id == SecKey::type_id() {
210 items.keys.push(SecKey::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
211 } else {
212 panic!("Got bad type from SecItemImport: {type_id}");
213 }
214 }
215 }
216 }
217
218 Ok(())
219 }
220}
221
222#[derive(Default)]
226pub struct SecItems {
227 pub certificates: Vec<SecCertificate>,
229 pub identities: Vec<SecIdentity>,
231 pub keys: Vec<SecKey>,
233}
234
235#[cfg(test)]
236mod test {
237 use super::*;
238 use crate::import_export::*;
239 use crate::os::macos::keychain;
240 use tempfile::tempdir;
241
242 #[test]
243 fn certificate() {
244 let data = include_bytes!("../../../test/server.der");
245 let mut items = SecItems::default();
246 ImportOptions::new()
247 .filename("server.der")
248 .items(&mut items)
249 .import(data)
250 .unwrap();
251 assert_eq!(1, items.certificates.len());
252 assert_eq!(0, items.identities.len());
253 assert_eq!(0, items.keys.len());
254 }
255
256 #[test]
257 fn key() {
258 let data = include_bytes!("../../../test/server.key");
259 let mut items = SecItems::default();
260 ImportOptions::new()
261 .filename("server.key")
262 .items(&mut items)
263 .import(data)
264 .unwrap();
265 assert_eq!(0, items.certificates.len());
266 assert_eq!(0, items.identities.len());
267 assert_eq!(1, items.keys.len());
268 }
269
270 #[test]
271 fn identity() {
272 let dir = tempdir().unwrap();
273 let keychain = keychain::CreateOptions::new()
274 .password("password")
275 .create(dir.path().join("identity.keychain"))
276 .unwrap();
277
278 let data = include_bytes!("../../../test/server.p12");
279 let mut items = SecItems::default();
280 ImportOptions::new()
281 .filename("server.p12")
282 .passphrase("password123")
283 .items(&mut items)
284 .keychain(&keychain)
285 .import(data)
286 .unwrap();
287 assert_eq!(1, items.identities.len());
288 assert_eq!(0, items.certificates.len());
289 assert_eq!(0, items.keys.len());
290 }
291
292 #[test]
293 #[ignore] fn secure_passphrase_identity() {
295 let dir = tempdir().unwrap();
296 let keychain = keychain::CreateOptions::new()
297 .password("password")
298 .create(dir.path().join("identity.keychain"))
299 .unwrap();
300
301 let data = include_bytes!("../../../test/server.p12");
302 let mut items = SecItems::default();
303 ImportOptions::new()
304 .filename("server.p12")
305 .secure_passphrase(true)
306 .alert_title("alert title")
307 .alert_prompt("alert prompt")
308 .items(&mut items)
309 .keychain(&keychain)
310 .import(data)
311 .unwrap();
312 assert_eq!(1, items.identities.len());
313 assert_eq!(0, items.certificates.len());
314 assert_eq!(0, items.keys.len());
315 }
316
317 #[test]
318 fn pkcs12_import() {
319 use super::Pkcs12ImportOptionsExt;
320
321 let dir = tempdir().unwrap();
322 let keychain = keychain::CreateOptions::new()
323 .password("password")
324 .create(dir.path().join("pkcs12_import"))
325 .unwrap();
326
327 let data = include_bytes!("../../../test/server.p12");
328 let identities = p!(Pkcs12ImportOptions::new()
329 .passphrase("password123")
330 .keychain(keychain)
331 .import(data));
332 assert_eq!(1, identities.len());
333 assert_eq!(
334 hex::encode(identities[0].key_id.as_ref().unwrap()),
335 "ed6492936dcc8907e397e573b36e633458dc33f1"
336 );
337 }
338}