Skip to main content

security_framework/
certificate.rs

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