security_framework/
import_export.rs

1//! Security Framework type import/export support.
2
3use core_foundation::array::CFArray;
4use core_foundation::base::{CFType, TCFType};
5use core_foundation::data::CFData;
6use core_foundation::dictionary::CFDictionary;
7use core_foundation::string::CFString;
8use security_framework_sys::import_export::*;
9use std::ptr;
10
11use crate::base::Result;
12use crate::certificate::SecCertificate;
13use crate::cvt;
14use crate::identity::SecIdentity;
15#[cfg(target_os = "macos")]
16use crate::os::macos::access::SecAccess;
17#[cfg(target_os = "macos")]
18use crate::os::macos::keychain::SecKeychain;
19use crate::trust::SecTrust;
20
21/// Information about an imported identity.
22pub struct ImportedIdentity {
23    /// The label of the identity.
24    pub label: Option<String>,
25    /// The ID of the identity. Typically the SHA-1 hash of the public key.
26    pub key_id: Option<Vec<u8>>,
27    /// A `SecTrust` object set up to validate this identity.
28    pub trust: Option<SecTrust>,
29    /// A certificate chain validating this identity.
30    pub cert_chain: Option<Vec<SecCertificate>>,
31    /// The identity itself.
32    pub identity: Option<SecIdentity>,
33    _p: (),
34}
35
36/// A builder type to import an identity from PKCS#12 formatted data.
37#[derive(Default)]
38pub struct Pkcs12ImportOptions {
39    passphrase: Option<CFString>,
40    #[cfg(target_os = "macos")]
41    keychain: Option<SecKeychain>,
42    #[cfg(target_os = "macos")]
43    access: Option<SecAccess>,
44}
45
46#[cfg(target_os = "macos")]
47impl Pkcs12ImportOptions {
48    /// Specifies macOS keychain in which to import the identity.
49    ///
50    /// If this is not called, the default keychain will be used.
51    #[inline(always)]
52    pub fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
53        self.keychain = Some(keychain);
54        self
55    }
56
57    /// Specifies the access control to be associated with the identity. macOS only.
58    #[inline(always)]
59    pub fn access(&mut self, access: SecAccess) -> &mut Self {
60        self.access = Some(access);
61        self
62    }
63}
64
65impl Pkcs12ImportOptions {
66    /// Creates a new builder with default options.
67    #[inline(always)]
68    #[must_use]
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    /// Specifies the passphrase to be used to decrypt the data.
74    ///
75    /// This must be specified, as unencrypted PKCS#12 data is not supported.
76    #[inline]
77    pub fn passphrase(&mut self, passphrase: &str) -> &mut Self {
78        self.passphrase = Some(CFString::new(passphrase));
79        self
80    }
81
82    /// Imports identities from PKCS#12 encoded data.
83    pub fn import(&self, pkcs12_data: &[u8]) -> Result<Vec<ImportedIdentity>> {
84        unsafe {
85            let pkcs12_data = CFData::from_buffer(pkcs12_data);
86
87            let mut options = vec![];
88
89            if let Some(ref passphrase) = self.passphrase {
90                options.push((
91                    CFString::wrap_under_get_rule(kSecImportExportPassphrase),
92                    passphrase.as_CFType(),
93                ));
94            }
95
96            self.import_setup(&mut options);
97
98            let options = CFDictionary::from_CFType_pairs(&options);
99
100            let mut raw_items = ptr::null();
101            cvt(SecPKCS12Import(
102                pkcs12_data.as_concrete_TypeRef(),
103                options.as_concrete_TypeRef(),
104                &mut raw_items,
105            ))?;
106            let raw_items = CFArray::<CFDictionary<CFString, *const _>>::wrap_under_create_rule(raw_items);
107
108            let mut items = vec![];
109
110            for raw_item in &raw_items {
111                let label = raw_item
112                    .find(kSecImportItemLabel)
113                    .map(|label| CFString::wrap_under_get_rule((*label).cast()).to_string());
114                let key_id = raw_item
115                    .find(kSecImportItemKeyID)
116                    .map(|key_id| CFData::wrap_under_get_rule((*key_id).cast()).to_vec());
117                let trust = raw_item
118                    .find(kSecImportItemTrust)
119                    .map(|trust| SecTrust::wrap_under_get_rule(*trust as *mut _));
120                let cert_chain = raw_item
121                    .find(kSecImportItemCertChain.cast())
122                    .map(|cert_chain| {
123                        CFArray::<SecCertificate>::wrap_under_get_rule((*cert_chain).cast())
124                            .iter()
125                            .map(|c| c.clone())
126                            .collect()
127                    });
128                let identity = raw_item
129                    .find(kSecImportItemIdentity)
130                    .map(|identity| SecIdentity::wrap_under_get_rule(*identity as *mut _));
131
132                items.push(ImportedIdentity {
133                    label,
134                    key_id,
135                    trust,
136                    cert_chain,
137                    identity,
138                    _p: (),
139                });
140            }
141
142            Ok(items)
143        }
144    }
145
146    #[cfg(target_os = "macos")]
147    fn import_setup(&self, options: &mut Vec<(CFString, CFType)>) {
148        unsafe {
149            if let Some(ref keychain) = self.keychain {
150                options.push((
151                    CFString::wrap_under_get_rule(kSecImportExportKeychain),
152                    keychain.as_CFType(),
153                ));
154            }
155
156            if let Some(ref access) = self.access {
157                options.push((
158                    CFString::wrap_under_get_rule(kSecImportExportAccess),
159                    access.as_CFType(),
160                ));
161            }
162        }
163    }
164
165    #[cfg(not(target_os = "macos"))]
166    fn import_setup(&self, _: &mut Vec<(CFString, CFType)>) {}
167}
168
169#[cfg(test)]
170mod test {
171    use super::*;
172
173    #[test]
174    fn missing_passphrase() {
175        let data = include_bytes!("../test/server.p12");
176        assert!(Pkcs12ImportOptions::new().import(data).is_err());
177    }
178}