apple_security_framework/os/macos/
import_export.rs

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