blob: d28f443a6b30948e80f2ecda0bf0825875740372 [file] [log] [blame]
//! Trust evaluation support.
use core_foundation::array::CFArray;
use core_foundation::base::TCFType;
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 {
match self {
Self::PROCEED | Self::UNSPECIFIED => true,
_ => false,
}
}
}
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 {}
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 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())) }
}
/// If set to `true`, only the certificates specified by
/// `set_anchor_certificates` will be trusted, but not globally trusted
/// certificates.
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.
pub fn set_policy(&mut self, policy: &SecPolicy) -> Result<()> {
unsafe { cvt(SecTrustSetPolicies(self.0, policy.as_CFTypeRef())) }
}
/// 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.
pub fn evaluate(&self) -> Result<TrustResult> {
unsafe {
let mut result = kSecTrustResultInvalid;
cvt(SecTrustEvaluate(self.0, &mut result))?;
Ok(TrustResult(result))
}
}
/// Evaluates trust. Requires macOS 10.14, otherwise it just calls `evaluate()`
pub fn evaluate_with_error(&self) -> Result<(), CFError> {
#[cfg(feature = "OSX_10_14")]
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(feature = "OSX_10_14"))]
{
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.
pub fn certificate_at_index(&self, ix: CFIndex) -> Option<SecCertificate> {
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(feature = "OSX_10_14"))]
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(feature = "OSX_10_14"))]
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]
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]
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]
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]
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.clone()], &[ssl_policy.clone()]).unwrap();
assert!(trust.evaluate_with_error().is_err());
assert!(trust.certificate_at_index(1).is_none());
}
#[test]
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());
}
}