security_framework/
certificate.rs

1//! Certificate support.
2use core_foundation::{declare_TCFType, impl_TCFType};
3use core_foundation::array::{CFArray, CFArrayRef};
4use core_foundation::base::{TCFType, ToVoid};
5use core_foundation::data::CFData;
6use core_foundation::dictionary::CFMutableDictionary;
7use core_foundation::string::CFString;
8use core_foundation_sys::base::kCFAllocatorDefault;
9#[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
10use security_framework_sys::base::{errSecNotTrusted, errSecSuccess};
11use security_framework_sys::base::{errSecParam, SecCertificateRef};
12use security_framework_sys::certificate::*;
13use security_framework_sys::keychain_item::SecItemDelete;
14use std::fmt;
15use std::ptr;
16
17use crate::base::{Error, Result};
18use crate::cvt;
19#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
20use crate::key;
21#[cfg(target_os = "macos")]
22use crate::os::macos::keychain::SecKeychain;
23#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
24use core_foundation::base::FromVoid;
25#[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
26use core_foundation::error::{CFError, CFErrorRef};
27#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
28use core_foundation::number::CFNumber;
29use security_framework_sys::item::kSecValueRef;
30
31declare_TCFType! {
32    /// A type representing a certificate.
33    SecCertificate, SecCertificateRef
34}
35impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID);
36
37unsafe impl Sync for SecCertificate {}
38unsafe impl Send for SecCertificate {}
39
40impl fmt::Debug for SecCertificate {
41    #[cold]
42    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
43        fmt.debug_struct("SecCertificate")
44            .field("subject", &self.subject_summary())
45            .finish()
46    }
47}
48
49impl SecCertificate {
50    /// Creates a `SecCertificate` from DER encoded certificate data.
51    pub fn from_der(der_data: &[u8]) -> Result<Self> {
52        let der_data = CFData::from_buffer(der_data);
53        unsafe {
54            let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, der_data.as_concrete_TypeRef());
55            if certificate.is_null() {
56                Err(Error::from_code(errSecParam))
57            } else {
58                Ok(Self::wrap_under_create_rule(certificate))
59            }
60        }
61    }
62
63    /// Returns DER encoded data describing this certificate.
64    #[must_use]
65    pub fn to_der(&self) -> Vec<u8> {
66        unsafe {
67            let der_data = SecCertificateCopyData(self.0);
68            CFData::wrap_under_create_rule(der_data).to_vec()
69        }
70    }
71
72    /// Adds a certificate to a keychain.
73    #[cfg(target_os = "macos")]
74    pub fn add_to_keychain(&self, keychain: Option<SecKeychain>) -> Result<()> {
75        let kch = match keychain {
76            Some(kch) => kch,
77            _ => SecKeychain::default()?,
78        };
79        cvt(unsafe {
80            SecCertificateAddToKeychain(self.as_CFTypeRef() as *mut _, kch.as_CFTypeRef() as *mut _)
81        })
82    }
83
84    /// Returns a human readable summary of this certificate.
85    #[must_use]
86    pub fn subject_summary(&self) -> String {
87        unsafe {
88            let summary = SecCertificateCopySubjectSummary(self.0);
89            CFString::wrap_under_create_rule(summary).to_string()
90        }
91    }
92
93    /// Returns a vector of email addresses for the subject of the certificate.
94    pub fn email_addresses(&self) -> Result<Vec<String>, Error> {
95        let mut array: CFArrayRef = ptr::null();
96        unsafe {
97            cvt(SecCertificateCopyEmailAddresses(
98                self.as_concrete_TypeRef(),
99                &mut array,
100            ))?;
101
102            let array = CFArray::<CFString>::wrap_under_create_rule(array);
103            Ok(array.into_iter().map(|p| p.to_string()).collect())
104        }
105    }
106
107    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
108    /// Returns DER encoded X.509 distinguished name of the certificate issuer.
109    #[must_use]
110    pub fn issuer(&self) -> Vec<u8> {
111        unsafe {
112            let issuer = SecCertificateCopyNormalizedIssuerSequence(self.0);
113            CFData::wrap_under_create_rule(issuer).to_vec()
114        }
115    }
116
117    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
118    /// Returns DER encoded X.509 distinguished name of the certificate subject.
119    #[must_use]
120    pub fn subject(&self) -> Vec<u8> {
121        unsafe {
122            let subject = SecCertificateCopyNormalizedSubjectSequence(self.0);
123            CFData::wrap_under_create_rule(subject).to_vec()
124        }
125    }
126
127    #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
128    /// Returns DER encoded serial number of the certificate.
129    pub fn serial_number_bytes(&self) -> Result<Vec<u8>, CFError> {
130        unsafe {
131            let mut error: CFErrorRef = ptr::null_mut();
132            let serial_number = SecCertificateCopySerialNumberData(self.0, &mut error);
133            if error.is_null() {
134                Ok(CFData::wrap_under_create_rule(serial_number).to_vec())
135            } else {
136                Err(CFError::wrap_under_create_rule(error))
137            }
138        }
139    }
140
141    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
142    /// Returns DER encoded subjectPublicKeyInfo of certificate if available. This can be used
143    /// for certificate pinning.
144    pub fn public_key_info_der(&self) -> Result<Option<Vec<u8>>> {
145        // Imported from TrustKit
146        // https://github.com/datatheorem/TrustKit/blob/master/TrustKit/Pinning/TSKSPKIHashCache.m
147        let public_key = self.public_key()?;
148        Ok(self.pk_to_der(public_key))
149    }
150
151    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
152    #[must_use]
153    fn pk_to_der(&self, public_key: key::SecKey) -> Option<Vec<u8>> {
154        use security_framework_sys::item::{kSecAttrKeySizeInBits, kSecAttrKeyType};
155
156        let public_key_attributes = public_key.attributes();
157        let public_key_type = public_key_attributes
158            .find(unsafe { kSecAttrKeyType }.cast::<std::os::raw::c_void>())?;
159        let public_keysize = public_key_attributes
160            .find(unsafe { kSecAttrKeySizeInBits }.cast::<std::os::raw::c_void>())?;
161        let public_keysize = unsafe { CFNumber::from_void(*public_keysize) };
162        let public_keysize_val = public_keysize.to_i64()? as u32;
163        let hdr_bytes = get_asn1_header_bytes(
164            unsafe { CFString::wrap_under_get_rule(*public_key_type as _) },
165            public_keysize_val,
166        )?;
167        let public_key_data = public_key.external_representation()?;
168        let mut out = Vec::with_capacity(hdr_bytes.len() + public_key_data.len() as usize);
169        out.extend_from_slice(hdr_bytes);
170        out.extend_from_slice(public_key_data.bytes());
171        Some(out)
172    }
173
174    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
175    /// Get public key from certificate
176    pub fn public_key(&self) -> Result<key::SecKey> {
177        use crate::policy::SecPolicy;
178        use crate::trust::SecTrust;
179        use std::slice::from_ref;
180
181        let policy = SecPolicy::create_x509();
182        let mut trust = SecTrust::create_with_certificates(from_ref(self), from_ref(&policy))?;
183        #[allow(deprecated)]
184        #[cfg(not(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos")))]
185        trust.evaluate()?;
186        #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
187        cvt(match trust.evaluate_with_error() {
188            Ok(_) => errSecSuccess,
189            Err(_) => errSecNotTrusted,
190        })?;
191        trust.copy_public_key()
192    }
193
194    /// Translates to `SecItemDelete`, passing in the `SecCertificateRef`
195    pub fn delete(&self) -> Result<(), Error> {
196        let query = CFMutableDictionary::from_CFType_pairs(&[(
197            unsafe { kSecValueRef }.to_void(),
198            self.to_void(),
199        )]);
200
201        cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) })
202    }
203}
204
205#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
206fn get_asn1_header_bytes(pkt: CFString, ksz: u32) -> Option<&'static [u8]> {
207    use security_framework_sys::item::{kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyTypeRSA};
208
209    if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 2048 {
210        return Some(&RSA_2048_ASN1_HEADER);
211    }
212    if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 4096 {
213        return Some(&RSA_4096_ASN1_HEADER);
214    }
215    if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } && ksz == 256 {
216        return Some(&EC_DSA_SECP_256_R1_ASN1_HEADER);
217    }
218    if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } && ksz == 384 {
219        return Some(&EC_DSA_SECP_384_R1_ASN1_HEADER);
220    }
221    None
222}
223
224#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
225const RSA_2048_ASN1_HEADER: [u8; 24] = [
226    0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
227    0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
228];
229
230#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
231const RSA_4096_ASN1_HEADER: [u8; 24] = [
232    0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
233    0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00,
234];
235
236#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
237const EC_DSA_SECP_256_R1_ASN1_HEADER: [u8; 26] = [
238    0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
239    0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
240];
241
242#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
243const EC_DSA_SECP_384_R1_ASN1_HEADER: [u8; 23] = [
244    0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b,
245    0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00,
246];
247
248#[cfg(test)]
249mod test {
250    use crate::test::certificate;
251    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
252    use x509_parser::prelude::*;
253
254    #[test]
255    fn subject_summary() {
256        let cert = certificate();
257        assert_eq!("foobar.com", cert.subject_summary());
258    }
259
260    #[test]
261    fn email_addresses() {
262        let cert = certificate();
263        assert_eq!(Vec::<String>::new(), cert.email_addresses().unwrap());
264    }
265
266    #[test]
267    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
268    fn issuer() {
269        let cert = certificate();
270        let issuer = cert.issuer();
271        let (_, name) = X509Name::from_der(&issuer).unwrap();
272        let name_str = name.to_string_with_registry(oid_registry()).unwrap();
273        assert_eq!(
274            "C=US, ST=CALIFORNIA, L=PALO ALTO, O=FOOBAR LLC, OU=DEV LAND, CN=FOOBAR.COM",
275            name_str
276        );
277    }
278
279    #[test]
280    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
281    fn subject() {
282        let cert = certificate();
283        let subject = cert.subject();
284        let (_, name) = X509Name::from_der(&subject).unwrap();
285        let name_str = name.to_string_with_registry(oid_registry()).unwrap();
286        assert_eq!(
287            "C=US, ST=CALIFORNIA, L=PALO ALTO, O=FOOBAR LLC, OU=DEV LAND, CN=FOOBAR.COM",
288            name_str
289        );
290    }
291}