| //! 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<Self> { |
| 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<Vec<SecCertificate>> { |
| let mut array: CFArrayRef = ptr::null(); |
| |
| unsafe { |
| cvt(SecTrustCopyAnchorCertificates(&mut array))?; |
| } |
| |
| if array.is_null() { |
| return Ok(vec![]); |
| } |
| |
| let array = unsafe { CFArray::<SecCertificate>::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<bool> { |
| 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<I: Iterator<Item = impl AsRef<[u8]>>>( |
| &mut self, |
| ocsp_response: I, |
| ) -> Result<()> { |
| let response: Vec<CFData> = 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<I: Iterator<Item = impl AsRef<[u8]>>>( |
| &mut self, |
| scts: I, |
| ) -> Result<()> { |
| let scts: Vec<CFData> = 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<SecKey> { |
| unsafe { |
| Ok(SecKey::wrap_under_create_rule(SecTrustCopyPublicKey( |
| self.0, |
| ))) |
| } |
| } |
| |
| /// Evaluates trust. |
| #[deprecated(note = "use evaluate_with_error")] |
| pub fn evaluate(&self) -> Result<TrustResult> { |
| #[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<SecCertificate> { |
| #[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()); |
| } |
| } |