Skip to main content

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#[deprecated(note = "Obsolete. Use Pkcs12ImportOptions directly.")]
21/// Obsolete. Use Pkcs12ImportOptions directly.
22pub trait Pkcs12ImportOptionsExt {
23    /// Specifies the keychain in which to import the identity.
24    ///
25    /// If this is not called, the default keychain will be used.
26    fn keychain(&mut self, keychain: SecKeychain) -> &mut Self;
27
28    /// Specifies the access control to be associated with the identity.
29    fn access(&mut self, access: SecAccess) -> &mut Self;
30}
31
32#[allow(deprecated)]
33impl Pkcs12ImportOptionsExt for Pkcs12ImportOptions {
34    /// Moved to Pkcs12ImportOptions. Remove Pkcs12ImportOptionsExt trait.
35    fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
36        Self::keychain(self, keychain)
37    }
38
39    /// Moved to Pkcs12ImportOptions. Remove Pkcs12ImportOptionsExt trait.
40    fn access(&mut self, access: SecAccess) -> &mut Self {
41        Self::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    input_format: Option<SecExternalFormat>,
50    passphrase: Option<CFType>,
51    secure_passphrase: bool,
52    no_access_control: bool,
53    access: Option<SecAccess>,
54    alert_title: Option<CFString>,
55    alert_prompt: Option<CFString>,
56    items: Option<&'a mut SecItems>,
57    keychain: Option<SecKeychain>,
58}
59
60impl<'a> ImportOptions<'a> {
61    /// Creates a new builder with default options.
62    #[inline(always)]
63    #[must_use]
64    pub fn new() -> Self {
65        ImportOptions::default()
66    }
67
68    /// Sets the filename from which the imported data came.
69    ///
70    /// The extension of the file will used as a hint for parsing.
71    #[inline]
72    pub fn filename(&mut self, filename: &str) -> &mut Self {
73        self.filename = Some(CFString::from_str(filename).unwrap());
74        self
75    }
76
77    /// Require input data to be PKCS#12
78    #[inline]
79    pub fn pkcs12(&mut self) -> &mut Self {
80        self.input_format = Some(kSecFormatPKCS12);
81        self
82    }
83
84    /// Sets the passphrase to be used to decrypt the imported data.
85    #[inline]
86    pub fn passphrase(&mut self, passphrase: &str) -> &mut Self {
87        self.passphrase = Some(CFString::from_str(passphrase).unwrap().into_CFType());
88        self
89    }
90
91    /// Sets the passphrase to be used to decrypt the imported data.
92    #[inline]
93    pub fn passphrase_bytes(&mut self, passphrase: &[u8]) -> &mut Self {
94        self.passphrase = Some(CFData::from_buffer(passphrase).into_CFType());
95        self
96    }
97
98    /// If set, the user will be prompted to imput the passphrase used to
99    /// decrypt the imported data.
100    #[inline(always)]
101    pub fn secure_passphrase(&mut self, secure_passphrase: bool) -> &mut Self {
102        self.secure_passphrase = secure_passphrase;
103        self
104    }
105
106    /// If set, imported items will have no access controls imposed on them.
107    #[inline(always)]
108    pub fn no_access_control(&mut self, no_access_control: bool) -> &mut Self {
109        self.no_access_control = no_access_control;
110        self
111    }
112
113    /// Specifies the access control to be associated with the identity. macOS only.
114    #[inline(always)]
115    pub fn access(&mut self, access: SecAccess) -> &mut Self {
116        self.access = Some(access);
117        self
118    }
119
120    /// Sets the title of the alert popup used with the `secure_passphrase`
121    /// option.
122    #[inline]
123    pub fn alert_title(&mut self, alert_title: &str) -> &mut Self {
124        self.alert_title = Some(CFString::from_str(alert_title).unwrap());
125        self
126    }
127
128    /// Sets the prompt of the alert popup used with the `secure_passphrase`
129    /// option.
130    #[inline]
131    pub fn alert_prompt(&mut self, alert_prompt: &str) -> &mut Self {
132        self.alert_prompt = Some(CFString::from_str(alert_prompt).unwrap());
133        self
134    }
135
136    /// Sets the object into which imported items will be placed.
137    #[inline(always)]
138    pub fn items(&mut self, items: &'a mut SecItems) -> &mut Self {
139        self.items = Some(items);
140        self
141    }
142
143    /// Sets the keychain into which items will be imported.
144    ///
145    /// This must be specified to import `SecIdentity`s.
146    #[inline]
147    pub fn keychain(&mut self, keychain: &SecKeychain) -> &mut Self {
148        self.keychain = Some(keychain.clone());
149        self
150    }
151
152    /// Imports items from serialized data.
153    pub fn import(&mut self, data: &[u8]) -> Result<()> {
154        let data = CFData::from_buffer(data);
155        let data = data.as_concrete_TypeRef();
156
157        let filename = match &self.filename {
158            Some(filename) => filename.as_concrete_TypeRef(),
159            None => ptr::null(),
160        };
161
162        let mut input_format_out;
163        let input_format_ptr = match self.input_format {
164            None => ptr::null_mut(),
165            Some(format) => {
166                input_format_out = format;
167                &mut input_format_out
168            },
169        };
170
171        let mut key_params = SecItemImportExportKeyParameters {
172            version: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
173            flags: 0,
174            passphrase: ptr::null(),
175            alertTitle: ptr::null(),
176            alertPrompt: ptr::null(),
177            accessRef: ptr::null_mut(),
178            keyUsage: ptr::null_mut(),
179            keyAttributes: ptr::null(),
180        };
181
182        if let Some(passphrase) = &self.passphrase {
183            key_params.passphrase = passphrase.as_CFTypeRef();
184        }
185
186        if self.secure_passphrase {
187            key_params.flags |= kSecKeySecurePassphrase;
188        }
189
190        if self.no_access_control {
191            key_params.flags |= kSecKeyNoAccessControl;
192        }
193
194        if let Some(access) = &self.access {
195            key_params.accessRef = access.as_concrete_TypeRef();
196        }
197
198        if let Some(alert_title) = &self.alert_title {
199            key_params.alertTitle = alert_title.as_concrete_TypeRef();
200        }
201
202        if let Some(alert_prompt) = &self.alert_prompt {
203            key_params.alertPrompt = alert_prompt.as_concrete_TypeRef();
204        }
205
206        let keychain = match &self.keychain {
207            Some(keychain) => keychain.as_concrete_TypeRef(),
208            None => ptr::null_mut(),
209        };
210
211        let mut raw_items = ptr::null();
212        let items_ref = match self.items {
213            Some(_) => &mut raw_items,
214            None => ptr::null_mut(),
215        };
216
217        unsafe {
218            let ret = SecItemImport(
219                data,
220                filename,
221                input_format_ptr,
222                ptr::null_mut(),
223                0,
224                &key_params,
225                keychain,
226                items_ref,
227            );
228            if ret != errSecSuccess {
229                return Err(Error::from_code(ret));
230            }
231
232            if let Some(items) = &mut self.items {
233                let raw_items = CFArray::<CFType>::wrap_under_create_rule(raw_items);
234                for item in raw_items.iter() {
235                    let type_id = item.type_of();
236                    if type_id == SecCertificate::type_id() {
237                        items.certificates.push(SecCertificate::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
238                    } else if type_id == SecIdentity::type_id() {
239                        items.identities.push(SecIdentity::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
240                    } else if type_id == SecKey::type_id() {
241                        items.keys.push(SecKey::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
242                    } else {
243                        panic!("Got bad type from SecItemImport: {type_id}");
244                    }
245                }
246            }
247        }
248
249        Ok(())
250    }
251}
252
253/// A type which holds items imported from serialized data.
254///
255/// Pass a reference to `ImportOptions::items`.
256#[derive(Default)]
257pub struct SecItems {
258    /// Imported certificates.
259    pub certificates: Vec<SecCertificate>,
260    /// Imported identities.
261    pub identities: Vec<SecIdentity>,
262    /// Imported keys.
263    pub keys: Vec<SecKey>,
264}
265
266#[cfg(test)]
267mod test {
268    use super::*;
269    use crate::import_export::*;
270    use crate::os::macos::keychain;
271    use tempfile::tempdir;
272
273    #[test]
274    fn certificate() {
275        let data = include_bytes!("../../../test/server.der");
276        let mut items = SecItems::default();
277        ImportOptions::new()
278            .filename("server.der")
279            .items(&mut items)
280            .import(data)
281            .unwrap();
282        assert_eq!(1, items.certificates.len());
283        assert_eq!(0, items.identities.len());
284        assert_eq!(0, items.keys.len());
285    }
286
287    #[test]
288    fn key() {
289        let data = include_bytes!("../../../test/server.key");
290        let mut items = SecItems::default();
291        ImportOptions::new()
292            .filename("server.key")
293            .items(&mut items)
294            .import(data)
295            .unwrap();
296        assert_eq!(0, items.certificates.len());
297        assert_eq!(0, items.identities.len());
298        assert_eq!(1, items.keys.len());
299    }
300
301    #[test]
302    fn identity() {
303        let dir = tempdir().unwrap();
304        let keychain = keychain::CreateOptions::new()
305            .password("password")
306            .create(dir.path().join("identity.keychain"))
307            .unwrap();
308
309        let data = include_bytes!("../../../test/server.p12");
310        let identities = Pkcs12ImportOptions::new()
311            .passphrase("password123")
312            .keychain(keychain)
313            .import(data)
314            .unwrap();
315        assert_eq!(1, identities.len());
316    }
317
318    #[test]
319    #[ignore] // since it requires manual intervention
320    fn secure_passphrase_identity() {
321        let dir = tempdir().unwrap();
322        let keychain = keychain::CreateOptions::new()
323            .password("password")
324            .create(dir.path().join("identity.keychain"))
325            .unwrap();
326
327        let data = include_bytes!("../../../test/server.p12");
328        let mut items = SecItems::default();
329        ImportOptions::new()
330            .filename("server.p12")
331            .secure_passphrase(true)
332            .alert_title("alert title")
333            .alert_prompt("alert prompt")
334            .items(&mut items)
335            .keychain(&keychain)
336            .import(data)
337            .unwrap();
338        assert_eq!(1, items.identities.len());
339        assert_eq!(0, items.certificates.len());
340        assert_eq!(0, items.keys.len());
341    }
342
343    #[test]
344    fn pkcs12_import() {
345        let dir = tempdir().unwrap();
346        let keychain = keychain::CreateOptions::new()
347            .password("password")
348            .create(dir.path().join("pkcs12_import"))
349            .unwrap();
350
351        let data = include_bytes!("../../../test/server.p12");
352        let identities = p!(Pkcs12ImportOptions::new()
353            .passphrase("password123")
354            .keychain(keychain)
355            .import(data));
356        assert_eq!(1, identities.len());
357        assert!(identities[0].key_id.is_some());
358        assert_eq!(identities[0].key_id.as_ref().unwrap().len(), 20);
359    }
360}