//! Certificate support. use core_foundation::array::{CFArray, CFArrayRef}; use core_foundation::base::{TCFType, ToVoid}; use core_foundation::data::CFData; use core_foundation::dictionary::CFMutableDictionary; use core_foundation::string::CFString; use core_foundation_sys::base::kCFAllocatorDefault; #[cfg(target_os = "ios")] use security_framework_sys::base::{errSecNotTrusted, errSecSuccess}; use security_framework_sys::base::{errSecParam, SecCertificateRef}; use security_framework_sys::certificate::*; use security_framework_sys::keychain_item::SecItemDelete; use std::fmt; use std::ptr; use crate::base::{Error, Result}; use crate::cvt; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use crate::key; #[cfg(target_os = "macos")] use crate::os::macos::keychain::SecKeychain; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use core_foundation::base::FromVoid; #[cfg(any(feature = "OSX_10_13", target_os = "ios"))] use core_foundation::error::{CFError, CFErrorRef}; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use core_foundation::number::CFNumber; #[cfg(feature = "serial-number-bigint")] use num_bigint::BigUint; use security_framework_sys::item::kSecValueRef; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use std::ops::Deref; declare_TCFType! { /// A type representing a certificate. SecCertificate, SecCertificateRef } impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID); unsafe impl Sync for SecCertificate {} unsafe impl Send for SecCertificate {} impl fmt::Debug for SecCertificate { #[cold] fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("SecCertificate") .field("subject", &self.subject_summary()) .finish() } } impl SecCertificate { /// Creates a `SecCertificate` from DER encoded certificate data. pub fn from_der(der_data: &[u8]) -> Result { let der_data = CFData::from_buffer(der_data); unsafe { let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, der_data.as_concrete_TypeRef()); if certificate.is_null() { Err(Error::from_code(errSecParam)) } else { Ok(Self::wrap_under_create_rule(certificate)) } } } /// Returns DER encoded data describing this certificate. #[must_use] pub fn to_der(&self) -> Vec { unsafe { let der_data = SecCertificateCopyData(self.0); CFData::wrap_under_create_rule(der_data).to_vec() } } /// Adds a certificate to a keychain. #[cfg(target_os="macos")] pub fn add_to_keychain(&self, keychain: Option) -> Result<()> { let kch = match keychain { Some(kch) => kch, _ => SecKeychain::default()?, }; cvt(unsafe { SecCertificateAddToKeychain(self.as_CFTypeRef() as *mut _, kch.as_CFTypeRef() as *mut _) }) } /// Returns a human readable summary of this certificate. #[must_use] pub fn subject_summary(&self) -> String { unsafe { let summary = SecCertificateCopySubjectSummary(self.0); CFString::wrap_under_create_rule(summary).to_string() } } /// Returns a vector of email addresses for the subject of the certificate. pub fn email_addresses(&self) -> Result, Error> { let mut array: CFArrayRef = ptr::null(); unsafe { cvt(SecCertificateCopyEmailAddresses( self.as_concrete_TypeRef(), &mut array, ))?; let array = CFArray::::wrap_under_create_rule(array); Ok(array.into_iter().map(|p| p.to_string()).collect()) } } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] /// Returns DER encoded X.509 distinguished name of the certificate issuer. #[must_use] pub fn issuer(&self) -> Vec { unsafe { let issuer = SecCertificateCopyNormalizedIssuerSequence(self.0); CFData::wrap_under_create_rule(issuer).to_vec() } } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] /// Returns DER encoded X.509 distinguished name of the certificate subject. #[must_use] pub fn subject(&self) -> Vec { unsafe { let subject = SecCertificateCopyNormalizedSubjectSequence(self.0); CFData::wrap_under_create_rule(subject).to_vec() } } #[cfg(any(feature = "OSX_10_13", target_os = "ios"))] /// Returns DER encoded serial number of the certificate. pub fn serial_number_bytes(&self) -> Result, CFError> { unsafe { let mut error: CFErrorRef = ptr::null_mut(); let serial_number = SecCertificateCopySerialNumberData(self.0, &mut error); if error.is_null() { Ok(CFData::wrap_under_create_rule(serial_number).to_vec()) } else { Err(CFError::wrap_under_create_rule(error)) } } } /// Use `BigUint::from_bytes_be(serial_number_bytes())` instead #[deprecated(note = "use serial_number_bytes()")] #[cfg(feature = "serial-number-bigint")] pub fn serial_number(&self) -> Result { Ok(BigUint::from_bytes_be(&self.serial_number_bytes()?)) } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] /// Returns DER encoded subjectPublicKeyInfo of certificate if available. This can be used /// for certificate pinning. pub fn public_key_info_der(&self) -> Result>> { // Imported from TrustKit // https://github.com/datatheorem/TrustKit/blob/master/TrustKit/Pinning/TSKSPKIHashCache.m let public_key = self.public_key()?; Ok(self.pk_to_der(public_key)) } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] #[must_use] fn pk_to_der(&self, public_key: key::SecKey) -> Option> { use security_framework_sys::item::kSecAttrKeyType; use security_framework_sys::item::kSecAttrKeySizeInBits; let public_key_attributes = public_key.attributes(); let public_key_type = public_key_attributes .find(unsafe { kSecAttrKeyType }.cast::())?; let public_keysize = public_key_attributes .find(unsafe { kSecAttrKeySizeInBits }.cast::())?; let public_keysize = unsafe { CFNumber::from_void(*public_keysize.deref()) }; let public_keysize_val = public_keysize.to_i64()? as u32; let hdr_bytes = get_asn1_header_bytes( unsafe { CFString::wrap_under_get_rule(*public_key_type.deref() as _) }, public_keysize_val, )?; let public_key_data = public_key.external_representation()?; let mut out = Vec::with_capacity(hdr_bytes.len() + public_key_data.len() as usize); out.extend_from_slice(hdr_bytes); out.extend_from_slice(public_key_data.bytes()); Some(out) } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] /// Get public key from certificate pub fn public_key(&self) -> Result { use crate::policy::SecPolicy; use crate::trust::SecTrust; use std::slice::from_ref; let policy = SecPolicy::create_x509(); let mut trust = SecTrust::create_with_certificates(from_ref(self), from_ref(&policy))?; #[allow(deprecated)] #[cfg(not(target_os = "ios"))] trust.evaluate()?; #[cfg(target_os = "ios")] cvt(match trust.evaluate_with_error() { Ok(_) => errSecSuccess, Err(_) => errSecNotTrusted, })?; trust.copy_public_key() } /// Translates to `SecItemDelete`, passing in the `SecCertificateRef` pub fn delete(&self) -> Result<(), Error> { let query = CFMutableDictionary::from_CFType_pairs(&[( unsafe { kSecValueRef }.to_void(), self.to_void(), )]); cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) }) } } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] fn get_asn1_header_bytes(pkt: CFString, ksz: u32) -> Option<&'static [u8]> { use security_framework_sys::item::kSecAttrKeyTypeRSA; use security_framework_sys::item::kSecAttrKeyTypeECSECPrimeRandom; if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 2048 { return Some(&RSA_2048_ASN1_HEADER); } if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 4096 { return Some(&RSA_4096_ASN1_HEADER); } if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } && ksz == 256 { return Some(&EC_DSA_SECP_256_R1_ASN1_HEADER); } if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } && ksz == 384 { return Some(&EC_DSA_SECP_384_R1_ASN1_HEADER); } None } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] const RSA_2048_ASN1_HEADER: [u8; 24] = [ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, ]; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] const RSA_4096_ASN1_HEADER: [u8; 24] = [ 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, ]; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] const EC_DSA_SECP_256_R1_ASN1_HEADER: [u8; 26] = [ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, ]; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] const EC_DSA_SECP_384_R1_ASN1_HEADER: [u8; 23] = [ 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, ]; #[cfg(test)] mod test { use crate::test::certificate; #[cfg(feature = "serial-number-bigint")] use num_bigint::BigUint; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use x509_parser::prelude::*; #[test] fn subject_summary() { let cert = certificate(); assert_eq!("foobar.com", cert.subject_summary()); } #[test] fn email_addresses() { let cert = certificate(); assert_eq!(Vec::::new(), cert.email_addresses().unwrap()); } #[test] #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] fn issuer() { let cert = certificate(); let issuer = cert.issuer(); let (_, name) = X509Name::from_der(&issuer).unwrap(); let name_str = name.to_string_with_registry(oid_registry()).unwrap(); assert_eq!( "C=US, ST=CALIFORNIA, L=PALO ALTO, O=FOOBAR LLC, OU=DEV LAND, CN=FOOBAR.COM", name_str ); } #[test] #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] fn subject() { let cert = certificate(); let subject = cert.subject(); let (_, name) = X509Name::from_der(&subject).unwrap(); let name_str = name.to_string_with_registry(oid_registry()).unwrap(); assert_eq!( "C=US, ST=CALIFORNIA, L=PALO ALTO, O=FOOBAR LLC, OU=DEV LAND, CN=FOOBAR.COM", name_str ); } #[test] #[cfg(feature = "serial-number-bigint")] #[allow(deprecated)] fn serial_number() { let cert = certificate(); let serial_number = cert.serial_number().unwrap(); assert_eq!(BigUint::from(16452297291294946383_u128), serial_number); } }