394 lines
14 KiB
Rust
394 lines
14 KiB
Rust
//! 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]
|
|
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)]
|
|
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")]
|
|
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 as *mut _))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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());
|
|
}
|
|
}
|