apple_security/os/macos/
import_export.rs

1//! OSX specific extensions to import/export functionality.
2
3use 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
20/// An extension trait adding OSX specific functionality to `Pkcs12ImportOptions`.
21pub trait Pkcs12ImportOptionsExt {
22    /// Specifies the keychain in which to import the identity.
23    ///
24    /// If this is not called, the default keychain will be used.
25    fn keychain(&mut self, keychain: SecKeychain) -> &mut Self;
26
27    /// Specifies the access control to be associated with the identity.
28    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/// A builder type to import Security Framework types from serialized formats.
44#[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    /// Creates a new builder with default options.
58    #[inline(always)]
59    #[must_use]
60    pub fn new() -> ImportOptions<'a> {
61        ImportOptions::default()
62    }
63
64    /// Sets the filename from which the imported data came.
65    ///
66    /// The extension of the file will used as a hint for parsing.
67    #[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    /// Sets the passphrase to be used to decrypt the imported data.
74    #[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    /// Sets the passphrase to be used to decrypt the imported data.
81    #[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    /// If set, the user will be prompted to imput the passphrase used to
88    /// decrypt the imported data.
89    #[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    /// If set, imported items will have no access controls imposed on them.
96    #[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    /// Sets the title of the alert popup used with the `secure_passphrase`
103    /// option.
104    #[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    /// Sets the prompt of the alert popup used with the `secure_passphrase`
111    /// option.
112    #[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    /// Sets the object into which imported items will be placed.
119    #[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    /// Sets the keychain into which items will be imported.
126    ///
127    /// This must be specified to import `SecIdentity`s.
128    #[inline]
129    pub fn keychain(&mut self, keychain: &SecKeychain) -> &mut ImportOptions<'a> {
130        self.keychain = Some(keychain.clone());
131        self
132    }
133
134    /// Imports items from serialized data.
135    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/// A type which holds items imported from serialized data.
229///
230/// Pass a reference to `ImportOptions::items`.
231#[derive(Default)]
232pub struct SecItems {
233    /// Imported certificates.
234    pub certificates: Vec<SecCertificate>,
235    /// Imported identities.
236    pub identities: Vec<SecIdentity>,
237    /// Imported keys.
238    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] // since it requires manual intervention
301    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}