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