security_framework/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 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/// 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() -> Self {
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 Self {
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 Self {
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 Self {
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 Self {
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 Self {
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 Self {
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 Self {
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 Self {
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 Self {
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(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/// A type which holds items imported from serialized data.
223///
224/// Pass a reference to `ImportOptions::items`.
225#[derive(Default)]
226pub struct SecItems {
227    /// Imported certificates.
228    pub certificates: Vec<SecCertificate>,
229    /// Imported identities.
230    pub identities: Vec<SecIdentity>,
231    /// Imported keys.
232    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] // since it requires manual intervention
294    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}