//! Trust evaluation support. use core_foundation::array::CFArray; #[cfg(target_os = "macos")] use core_foundation::array::CFArrayRef; use core_foundation::base::TCFType; #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] use core_foundation::data::CFData; use core_foundation::date::CFDate; use core_foundation_sys::base::{Boolean, CFIndex}; use security_framework_sys::trust::*; use std::ptr; use crate::base::Result; use crate::certificate::SecCertificate; use crate::cvt; use crate::key::SecKey; use crate::policy::SecPolicy; use core_foundation::error::{CFError, CFErrorRef}; /// The result of trust evaluation. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TrustResult(SecTrustResultType); impl TrustResult { /// An invalid setting or result. pub const INVALID: Self = Self(kSecTrustResultInvalid); /// You may proceed. pub const PROCEED: Self = Self(kSecTrustResultProceed); /// Indicates a denial by the user, do not proceed. pub const DENY: Self = Self(kSecTrustResultDeny); /// The certificate is implicitly trusted. pub const UNSPECIFIED: Self = Self(kSecTrustResultUnspecified); /// Indicates a trust policy failure that the user can override. pub const RECOVERABLE_TRUST_FAILURE: Self = Self(kSecTrustResultRecoverableTrustFailure); /// Indicates a trust policy failure that the user cannot override. pub const FATAL_TRUST_FAILURE: Self = Self(kSecTrustResultFatalTrustFailure); /// An error not related to trust validation. pub const OTHER_ERROR: Self = Self(kSecTrustResultOtherError); } impl TrustResult { /// Returns true if the result is "successful" - specifically `PROCEED` or `UNSPECIFIED`. #[inline] #[must_use] pub fn success(self) -> bool { matches!(self, Self::PROCEED | Self::UNSPECIFIED) } } declare_TCFType! { /// A type representing a trust evaluation for a certificate. SecTrust, SecTrustRef } impl_TCFType!(SecTrust, SecTrustRef, SecTrustGetTypeID); unsafe impl Sync for SecTrust {} unsafe impl Send for SecTrust {} #[cfg(target_os = "macos")] bitflags::bitflags! { /// The option flags used to configure the evaluation of a `SecTrust`. pub struct TrustOptions: SecTrustOptionFlags { /// Allow expired certificates (except for the root certificate). const ALLOW_EXPIRED = kSecTrustOptionAllowExpired; /// Allow CA certificates as leaf certificates. const LEAF_IS_CA = kSecTrustOptionLeafIsCA; /// Allow network downloads of CA certificates. const FETCH_ISSUER_FROM_NET = kSecTrustOptionFetchIssuerFromNet; /// Allow expired root certificates. const ALLOW_EXPIRED_ROOT = kSecTrustOptionAllowExpiredRoot; /// Require a positive revocation check for each certificate. const REQUIRE_REVOCATION_PER_CERT = kSecTrustOptionRequireRevPerCert; /// Use TrustSettings instead of anchors. const USE_TRUST_SETTINGS = kSecTrustOptionUseTrustSettings; /// Treat properly self-signed certificates as anchors implicitly. const IMPLICIT_ANCHORS = kSecTrustOptionImplicitAnchors; } } impl SecTrust { /// Creates a `SecTrustRef` that is configured with a certificate chain, for validating /// that chain against a collection of policies. pub fn create_with_certificates( certs: &[SecCertificate], policies: &[SecPolicy], ) -> Result { let cert_array = CFArray::from_CFTypes(certs); let policy_array = CFArray::from_CFTypes(policies); let mut trust = ptr::null_mut(); unsafe { cvt(SecTrustCreateWithCertificates( cert_array.as_CFTypeRef(), policy_array.as_CFTypeRef(), &mut trust, ))?; Ok(Self(trust)) } } /// Sets the date and time against which the certificates in this trust object /// are verified. #[inline] pub fn set_trust_verify_date(&mut self, date: &CFDate) -> Result<()> { unsafe { cvt(SecTrustSetVerifyDate(self.0, date.as_concrete_TypeRef())) } } /// Sets additional anchor certificates used to validate trust. pub fn set_anchor_certificates(&mut self, certs: &[SecCertificate]) -> Result<()> { let certs = CFArray::from_CFTypes(certs); unsafe { cvt(SecTrustSetAnchorCertificates( self.0, certs.as_concrete_TypeRef(), )) } } /// Retrieves the anchor (root) certificates stored by macOS #[cfg(target_os = "macos")] pub fn copy_anchor_certificates() -> Result> { let mut array: CFArrayRef = ptr::null(); unsafe { cvt(SecTrustCopyAnchorCertificates(&mut array))?; } if array.is_null() { return Ok(vec![]); } let array = unsafe { CFArray::::wrap_under_create_rule(array) }; Ok(array.into_iter().map(|c| c.clone()).collect()) } /// If set to `true`, only the certificates specified by /// `set_anchor_certificates` will be trusted, but not globally trusted /// certificates. #[inline] pub fn set_trust_anchor_certificates_only(&mut self, only: bool) -> Result<()> { unsafe { cvt(SecTrustSetAnchorCertificatesOnly(self.0, only as Boolean)) } } /// Sets the policy used to evaluate trust. #[inline] pub fn set_policy(&mut self, policy: &SecPolicy) -> Result<()> { unsafe { cvt(SecTrustSetPolicies(self.0, policy.as_CFTypeRef())) } } /// Sets option flags for customizing evaluation of a trust object. #[cfg(target_os = "macos")] #[inline] pub fn set_options(&mut self, options: TrustOptions) -> Result<()> { unsafe { cvt(SecTrustSetOptions(self.0, options.bits())) } } /// Indicates whether this trust object is permitted to /// fetch missing intermediate certificates from the network. #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] pub fn get_network_fetch_allowed(&mut self) -> Result { let mut allowed = 0; unsafe { cvt(SecTrustGetNetworkFetchAllowed(self.0, &mut allowed))? }; Ok(allowed != 0) } /// Specifies whether this trust object is permitted to /// fetch missing intermediate certificates from the network. #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] #[inline] pub fn set_network_fetch_allowed(&mut self, allowed: bool) -> Result<()> { unsafe { cvt(SecTrustSetNetworkFetchAllowed(self.0, allowed as u8)) } } /// Attaches Online Certificate Status Protocol (OSCP) response data /// to this trust object. #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] pub fn set_trust_ocsp_response>>( &mut self, ocsp_response: I, ) -> Result<()> { let response: Vec = ocsp_response .into_iter() .map(|bytes| CFData::from_buffer(bytes.as_ref())) .collect(); let response = CFArray::from_CFTypes(&response); unsafe { cvt(SecTrustSetOCSPResponse(self.0, response.as_CFTypeRef())) } } /// Attaches signed certificate timestamp data to this trust object. #[cfg(any(feature = "OSX_10_14", target_os = "ios"))] pub fn set_signed_certificate_timestamps>>( &mut self, scts: I, ) -> Result<()> { let scts: Vec = scts .into_iter() .map(|bytes| CFData::from_buffer(bytes.as_ref())) .collect(); let scts = CFArray::from_CFTypes(&scts); unsafe { cvt(SecTrustSetSignedCertificateTimestamps(self.0, scts.as_concrete_TypeRef())) } } /// Returns the public key for a leaf certificate after it has been evaluated. #[inline] pub fn copy_public_key(&mut self) -> Result { unsafe { Ok(SecKey::wrap_under_create_rule(SecTrustCopyPublicKey( self.0, ))) } } /// Evaluates trust. #[deprecated(note = "use evaluate_with_error")] pub fn evaluate(&self) -> Result { #[allow(deprecated)] unsafe { let mut result = kSecTrustResultInvalid; cvt(SecTrustEvaluate(self.0, &mut result))?; Ok(TrustResult(result)) } } /// Evaluates trust. Requires macOS 10.14 or iOS, otherwise it just calls `evaluate()` pub fn evaluate_with_error(&self) -> Result<(), CFError> { #[cfg(any(feature = "OSX_10_14", target_os = "ios"))] unsafe { let mut error: CFErrorRef = ::std::ptr::null_mut(); if !SecTrustEvaluateWithError(self.0, &mut error) { assert!(!error.is_null()); let error = CFError::wrap_under_create_rule(error); return Err(error); } Ok(()) } #[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))] #[allow(deprecated)] { use security_framework_sys::base::errSecNotTrusted; use security_framework_sys::base::errSecTrustSettingDeny; let code = match self.evaluate() { Ok(res) if res.success() => return Ok(()), Ok(TrustResult::DENY) => errSecTrustSettingDeny, Ok(_) => errSecNotTrusted, Err(err) => err.code(), }; Err(cferror_from_osstatus(code)) } } /// Returns the number of certificates in an evaluated certificate chain. /// /// Note: evaluate must first be called on the `SecTrust`. #[inline(always)] #[must_use] pub fn certificate_count(&self) -> CFIndex { unsafe { SecTrustGetCertificateCount(self.0) } } /// Returns a specific certificate from the certificate chain used to evaluate trust. /// /// Note: evaluate must first be called on the `SecTrust`. #[deprecated(note = "deprecated by Apple")] #[must_use] pub fn certificate_at_index(&self, ix: CFIndex) -> Option { #[allow(deprecated)] unsafe { if self.certificate_count() <= ix { None } else { let certificate = SecTrustGetCertificateAtIndex(self.0, ix); Some(SecCertificate::wrap_under_get_rule(certificate.cast())) } } } } #[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))] extern "C" { fn CFErrorCreate(allocator: core_foundation_sys::base::CFAllocatorRef, domain: core_foundation_sys::string::CFStringRef, code: CFIndex, userInfo: core_foundation_sys::dictionary::CFDictionaryRef) -> CFErrorRef; } #[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))] fn cferror_from_osstatus(code: core_foundation_sys::base::OSStatus) -> CFError { unsafe { let error = CFErrorCreate(ptr::null_mut(), core_foundation_sys::error::kCFErrorDomainOSStatus, code as _, ptr::null_mut()); assert!(!error.is_null()); CFError::wrap_under_create_rule(error) } } #[cfg(test)] mod test { use crate::policy::SecPolicy; use crate::secure_transport::SslProtocolSide; use crate::test::certificate; use crate::trust::SecTrust; #[test] #[allow(deprecated)] fn create_with_certificates() { let cert = certificate(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); assert_eq!(trust.evaluate().unwrap().success(), false) } #[test] fn create_with_certificates_new() { let cert = certificate(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); assert!(trust.evaluate_with_error().is_err()); } #[test] #[allow(deprecated)] fn certificate_count_and_at_index() { let cert = certificate(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); trust.evaluate().unwrap(); let count = trust.certificate_count(); assert_eq!(count, 1); let cert_bytes = trust.certificate_at_index(0).unwrap().to_der(); assert_eq!(cert_bytes, certificate().to_der()); } #[test] #[allow(deprecated)] fn certificate_count_and_at_index_new() { let cert = certificate(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); assert!(trust.evaluate_with_error().is_err()); let count = trust.certificate_count(); assert_eq!(count, 1); let cert_bytes = trust.certificate_at_index(0).unwrap().to_der(); assert_eq!(cert_bytes, certificate().to_der()); } #[test] #[allow(deprecated)] fn certificate_at_index_out_of_bounds() { let cert = certificate(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); let trust = SecTrust::create_with_certificates(&[cert.clone()], &[ssl_policy.clone()]).unwrap(); trust.evaluate().unwrap(); assert!(trust.certificate_at_index(1).is_none()); let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); assert!(trust.evaluate_with_error().is_err()); assert!(trust.certificate_at_index(1).is_none()); } #[test] #[allow(deprecated)] fn set_policy() { let cert = certificate(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io.bogus")); let mut trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); trust.set_policy(&ssl_policy).unwrap(); assert_eq!(trust.evaluate().unwrap().success(), false) } #[test] fn set_policy_new() { let cert = certificate(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io.bogus")); let mut trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); trust.set_policy(&ssl_policy).unwrap(); assert!(trust.evaluate_with_error().is_err()); } }