| //! 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<Self> { |
| 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<u8> { |
| 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<SecKeychain>) -> 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<Vec<String>, Error> { |
| let mut array: CFArrayRef = ptr::null(); |
| unsafe { |
| cvt(SecCertificateCopyEmailAddresses( |
| self.as_concrete_TypeRef(), |
| &mut array, |
| ))?; |
| |
| let array = CFArray::<CFString>::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<u8> { |
| 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<u8> { |
| 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<Vec<u8>, 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<BigUint, CFError> { |
| 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<Option<Vec<u8>>> { |
| // 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<Vec<u8>> { |
| 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::<std::os::raw::c_void>())?; |
| let public_keysize = public_key_attributes |
| .find(unsafe { kSecAttrKeySizeInBits }.cast::<std::os::raw::c_void>())?; |
| 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<key::SecKey> { |
| 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::<String>::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); |
| } |
| } |