blob: 5eddd194c7375ee3e455f65409a5ea0496c6488d [file] [log] [blame]
//! Querying trust settings.
use core_foundation::array::{CFArray, CFArrayRef};
use core_foundation::base::{CFIndex, TCFType};
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use security_framework_sys::base::errSecNoTrustSettings;
use security_framework_sys::base::errSecSuccess;
use security_framework_sys::trust_settings::*;
use std::ptr;
use crate::base::Error;
use crate::base::Result;
use crate::certificate::SecCertificate;
use crate::cvt;
/// Which set of trust settings to query
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Domain {
/// Per-user trust settings
User,
/// Locally administered, system-wide trust settings
Admin,
/// System trust settings
System,
}
impl Into<SecTrustSettingsDomain> for Domain {
#[inline]
fn into(self) -> SecTrustSettingsDomain {
match self {
Self::User => kSecTrustSettingsDomainUser,
Self::Admin => kSecTrustSettingsDomainAdmin,
Self::System => kSecTrustSettingsDomainSystem,
}
}
}
/// Trust settings for a specific certificate in a specific domain
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TrustSettingsForCertificate {
/// Not used
Invalid,
/// This is a root certificate and is trusted, either explicitly or
/// implicitly.
TrustRoot,
/// This is a non-root certificate but is explicitly trusted.
TrustAsRoot,
/// Cert is explicitly distrusted.
Deny,
/// Neither trusted nor distrusted.
Unspecified,
}
impl TrustSettingsForCertificate {
/// Create from `kSecTrustSettingsResult*` constant
fn new(value: i64) -> Self {
if value < 0 || value > i64::from(u32::max_value()) {
return Self::Invalid;
}
match value as u32 {
kSecTrustSettingsResultTrustRoot => Self::TrustRoot,
kSecTrustSettingsResultTrustAsRoot => Self::TrustAsRoot,
kSecTrustSettingsResultDeny => Self::Deny,
kSecTrustSettingsResultUnspecified => Self::Unspecified,
_ => Self::Invalid,
}
}
}
/// Allows access to the certificates and their trust settings in a given domain.
pub struct TrustSettings {
domain: Domain,
}
impl TrustSettings {
/// Create a new TrustSettings for the given domain.
///
/// You can call `iter()` to discover the certificates with settings in this domain.
///
/// Then you can call `tls_trust_settings_for_certificate()` with a given certificate
/// to learn what the aggregate trust setting for that certificate within this domain.
#[inline(always)]
pub fn new(domain: Domain) -> Self {
Self { domain }
}
/// Create an iterator over the certificates with settings in this domain.
/// This produces an empty iterator if there are no such certificates.
pub fn iter(&self) -> Result<TrustSettingsIter> {
let array = unsafe {
let mut array_ptr: CFArrayRef = ptr::null_mut();
// SecTrustSettingsCopyCertificates returns errSecNoTrustSettings
// if no items have trust settings in the given domain. We map
// that to an empty TrustSettings iterator.
match SecTrustSettingsCopyCertificates(self.domain.into(), &mut array_ptr) {
errSecNoTrustSettings => CFArray::from_CFTypes(&[]),
errSecSuccess => CFArray::<SecCertificate>::wrap_under_create_rule(array_ptr),
err => return Err(Error::from_code(err)),
}
};
Ok(TrustSettingsIter { index: 0, array })
}
/// Returns the aggregate trust setting for the given certificate.
///
/// This tells you whether the certificate should be trusted as a TLS
/// root certificate.
///
/// If the certificate has no trust settings in the given domain, the
/// `errSecItemNotFound` error is returned.
///
/// If the certificate has no specific trust settings for TLS in the
/// given domain `None` is returned.
///
/// Otherwise, the specific trust settings are aggregated and returned.
pub fn tls_trust_settings_for_certificate(
&self,
cert: &SecCertificate,
) -> Result<Option<TrustSettingsForCertificate>> {
let trust_settings = unsafe {
let mut array_ptr: CFArrayRef = ptr::null_mut();
let cert_ptr = cert.as_CFTypeRef() as *mut _;
cvt(SecTrustSettingsCopyTrustSettings(cert_ptr, self.domain.into(), &mut array_ptr))?;
CFArray::<CFDictionary>::wrap_under_create_rule(array_ptr)
};
for settings in trust_settings.iter() {
// Reject settings for non-SSL policies
let is_not_ssl_policy = {
let policy_name_key = CFString::from_static_string("kSecTrustSettingsPolicyName");
let ssl_policy_name = CFString::from_static_string("sslServer");
let maybe_name: Option<CFString> = settings
.find(policy_name_key.as_CFTypeRef() as *const _)
.map(|name| unsafe { CFString::wrap_under_get_rule(*name as *const _) });
match maybe_name {
Some(ref name) if name != &ssl_policy_name => true,
_ => false,
}
};
if is_not_ssl_policy {
continue;
}
// Evaluate "effective trust settings" for this usage constraint.
let maybe_trust_result = {
let settings_result_key = CFString::from_static_string("kSecTrustSettingsResult");
settings
.find(settings_result_key.as_CFTypeRef() as *const _)
.map(|num| unsafe { CFNumber::wrap_under_get_rule(*num as *const _) })
.and_then(|num| num.to_i64())
};
// "Note that an empty Trust Settings array means "always trust this cert,
// with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"."
let trust_result = TrustSettingsForCertificate::new(
maybe_trust_result.unwrap_or(i64::from(kSecTrustSettingsResultTrustRoot)),
);
match trust_result {
TrustSettingsForCertificate::Unspecified | TrustSettingsForCertificate::Invalid => {
continue;
}
_ => return Ok(Some(trust_result)),
}
}
// There were no more specific settings. This might mean the certificate
// is to be trusted anyway (since, eg, it's in system store), but leave
// the caller to make this decision.
Ok(None)
}
}
/// Iterator over certificates.
pub struct TrustSettingsIter {
array: CFArray<SecCertificate>,
index: CFIndex,
}
impl Iterator for TrustSettingsIter {
type Item = SecCertificate;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.array.len() {
None
} else {
let cert = self.array.get(self.index).unwrap();
self.index += 1;
Some(cert.clone())
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let left = (self.array.len() as usize).saturating_sub(self.index as usize);
(left, Some(left))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::test::certificate;
fn list_for_domain(domain: Domain) {
println!("--- domain: {:?}", domain);
let ts = TrustSettings::new(domain);
let iterator = ts.iter().unwrap();
for (i, cert) in iterator.enumerate() {
println!("cert({:?}) = {:?}", i, cert);
println!(" settings = {:?}", ts.tls_trust_settings_for_certificate(&cert));
}
println!("---");
}
#[test]
fn list_for_user() {
list_for_domain(Domain::User);
}
#[test]
fn list_for_system() {
list_for_domain(Domain::System);
}
#[test]
fn list_for_admin() {
list_for_domain(Domain::Admin);
}
#[test]
fn test_system_certs_are_present() {
let system = TrustSettings::new(Domain::System).iter().unwrap().count();
// 168 at the time of writing
assert!(system > 100);
}
#[test]
fn test_isrg_root_exists_and_is_trusted() {
let ts = TrustSettings::new(Domain::System);
assert_eq!(
ts.iter()
.unwrap()
.find(|cert| cert.subject_summary() == "ISRG Root X1")
.and_then(|cert| ts.tls_trust_settings_for_certificate(&cert).unwrap()),
None
);
// ^ this is a case where None means "always trust", according to Apple docs:
//
// "Note that an empty Trust Settings array means "always trust this cert,
// with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"."
}
#[test]
fn test_unknown_cert_is_not_trusted() {
let ts = TrustSettings::new(Domain::System);
let cert = certificate();
assert_eq!(
ts.tls_trust_settings_for_certificate(&cert).err().unwrap().message(),
Some("The specified item could not be found in the keychain.".into())
);
}
}