Skip to main content

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.
22#[derive(Clone)]
23#[non_exhaustive]
24pub struct ImportedIdentity {
25    /// The label of the identity.
26    pub label: Option<String>,
27    /// The ID of the identity. Typically the SHA-1 hash of the public key.
28    pub key_id: Option<Vec<u8>>,
29    /// A `SecTrust` object set up to validate this identity.
30    pub trust: Option<SecTrust>,
31    /// A certificate chain validating this identity.
32    pub cert_chain: Option<Vec<SecCertificate>>,
33    /// The identity itself.
34    pub identity: Option<SecIdentity>,
35}
36
37/// A builder type to import an identity from PKCS#12 formatted data.
38#[derive(Default)]
39pub struct Pkcs12ImportOptions {
40    passphrase: Option<CFString>,
41    #[cfg(target_os = "macos")]
42    keychain: Option<SecKeychain>,
43    #[cfg(target_os = "macos")]
44    access: Option<SecAccess>,
45}
46
47#[cfg(target_os = "macos")]
48impl Pkcs12ImportOptions {
49    /// Specifies macOS keychain in which to import the identity.
50    ///
51    /// If this is not called, the default keychain will be used.
52    #[inline(always)]
53    pub fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
54        self.keychain = Some(keychain);
55        self
56    }
57
58    /// Specifies the access control to be associated with the identity. macOS only.
59    #[inline(always)]
60    pub fn access(&mut self, access: SecAccess) -> &mut Self {
61        self.access = Some(access);
62        self
63    }
64}
65
66impl Pkcs12ImportOptions {
67    /// Creates a new builder with default options.
68    #[inline(always)]
69    #[must_use]
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// Specifies the passphrase to be used to decrypt the data.
75    ///
76    /// This must be specified, as unencrypted PKCS#12 data is not supported.
77    #[inline]
78    pub fn passphrase(&mut self, passphrase: &str) -> &mut Self {
79        self.passphrase = Some(CFString::new(passphrase));
80        self
81    }
82
83    /// Imports identities from PKCS#12 encoded data.
84    pub fn import(&self, pkcs12_data: &[u8]) -> Result<Vec<ImportedIdentity>> {
85        unsafe {
86            let pkcs12_data = CFData::from_buffer(pkcs12_data);
87
88            let mut options = vec![];
89
90            if let Some(passphrase) = &self.passphrase {
91                options.push((
92                    CFString::wrap_under_get_rule(kSecImportExportPassphrase),
93                    passphrase.as_CFType(),
94                ));
95            }
96
97            self.import_setup(&mut options);
98
99            let options = CFDictionary::from_CFType_pairs(&options);
100
101            let mut raw_items = ptr::null();
102            cvt(SecPKCS12Import(
103                pkcs12_data.as_concrete_TypeRef(),
104                options.as_concrete_TypeRef(),
105                &mut raw_items,
106            ))?;
107            let raw_items = CFArray::<CFDictionary<CFString, *const _>>::wrap_under_create_rule(raw_items);
108
109            let mut items = vec![];
110
111            for raw_item in &raw_items {
112                let label = raw_item
113                    .find(kSecImportItemLabel)
114                    .map(|label| CFString::wrap_under_get_rule((*label).cast()).to_string());
115                let key_id = raw_item
116                    .find(kSecImportItemKeyID)
117                    .map(|key_id| CFData::wrap_under_get_rule((*key_id).cast()).to_vec());
118                let trust = raw_item
119                    .find(kSecImportItemTrust)
120                    .map(|trust| SecTrust::wrap_under_get_rule(*trust as *mut _));
121                let cert_chain = raw_item
122                    .find(kSecImportItemCertChain)
123                    .map(|cert_chain| {
124                        CFArray::<SecCertificate>::wrap_under_get_rule((*cert_chain).cast())
125                            .iter()
126                            .map(|c| c.clone())
127                            .collect()
128                    });
129                let identity = raw_item
130                    .find(kSecImportItemIdentity)
131                    .map(|identity| SecIdentity::wrap_under_get_rule(*identity as *mut _));
132
133                items.push(ImportedIdentity {
134                    label,
135                    key_id,
136                    trust,
137                    cert_chain,
138                    identity,
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(keychain) = &self.keychain {
150                options.push((
151                    CFString::wrap_under_get_rule(kSecImportExportKeychain),
152                    keychain.as_CFType(),
153                ));
154            }
155
156            if let Some(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}