Skip to content

Commit

Permalink
Add SecTrustSettings bindings (#82)
Browse files Browse the repository at this point in the history
Add SecTrustSettings bindings
  • Loading branch information
kornelski committed Sep 15, 2019
2 parents 2aa2ba6 + b97b24d commit 6f6f500
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 0 deletions.
1 change: 1 addition & 0 deletions security-framework-sys/src/base.rs
Expand Up @@ -49,6 +49,7 @@ pub const errSecBadReq: OSStatus = -909;
pub const errSecAuthFailed: OSStatus = -25293;
pub const errSecTrustSettingDeny: OSStatus = -67654;
pub const errSecNotTrusted: OSStatus = -67843;
pub const errSecNoTrustSettings: OSStatus = -25263;

extern "C" {
#[cfg(target_os = "macos")]
Expand Down
1 change: 1 addition & 0 deletions security-framework-sys/src/lib.rs
Expand Up @@ -30,3 +30,4 @@ pub mod secure_transport;
#[cfg(target_os = "macos")]
pub mod transform;
pub mod trust;
pub mod trust_settings;
29 changes: 29 additions & 0 deletions security-framework-sys/src/trust_settings.rs
@@ -0,0 +1,29 @@
use base::SecCertificateRef;
use core_foundation_sys::array::CFArrayRef;
use core_foundation_sys::base::OSStatus;

pub type SecTrustSettingsDomain = u32;

pub const kSecTrustSettingsDomainUser: SecTrustSettingsDomain = 0;
pub const kSecTrustSettingsDomainAdmin: SecTrustSettingsDomain = 1;
pub const kSecTrustSettingsDomainSystem: SecTrustSettingsDomain = 2;

pub type SecTrustSettingsResult = u32;

pub const kSecTrustSettingsResultInvalid: SecTrustSettingsResult = 0;
pub const kSecTrustSettingsResultTrustRoot: SecTrustSettingsResult = 1;
pub const kSecTrustSettingsResultTrustAsRoot: SecTrustSettingsResult = 2;
pub const kSecTrustSettingsResultDeny: SecTrustSettingsResult = 3;
pub const kSecTrustSettingsResultUnspecified: SecTrustSettingsResult = 4;

extern "C" {
pub fn SecTrustSettingsCopyCertificates(
domain: SecTrustSettingsDomain,
certsOut: *mut CFArrayRef,
) -> OSStatus;
pub fn SecTrustSettingsCopyTrustSettings(
certificateRef: SecCertificateRef,
domain: SecTrustSettingsDomain,
trustSettings: *mut CFArrayRef
) -> OSStatus;
}
1 change: 1 addition & 0 deletions security-framework/src/lib.rs
Expand Up @@ -49,6 +49,7 @@ pub mod policy;
pub mod random;
pub mod secure_transport;
pub mod trust;
pub mod trust_settings;

#[cfg(target_os = "macos")]
trait Pkcs12ImportOptionsInternals {
Expand Down
279 changes: 279 additions & 0 deletions security-framework/src/trust_settings.rs
@@ -0,0 +1,279 @@
//! Querying trust settings.

use core_foundation::array::{CFArray, CFArrayRef};
use core_foundation::dictionary::CFDictionary;
use core_foundation::string::CFString;
use core_foundation::number::CFNumber;
use core_foundation::base::{TCFType, CFIndex};

use security_framework_sys::trust_settings::*;
use security_framework_sys::base::errSecSuccess;
use security_framework_sys::base::errSecNoTrustSettings;

use std::ptr;
use std::convert::TryFrom;

use cvt;
use base::Result;
use base::Error;
use certificate::SecCertificate;

/// 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 {
fn into(self) -> SecTrustSettingsDomain {
match self {
Domain::User => kSecTrustSettingsDomainUser,
Domain::Admin => kSecTrustSettingsDomainAdmin,
Domain::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 {
fn new(value: i64) -> TrustSettingsForCertificate {
match u32::try_from(value).ok() {
Some(kSecTrustSettingsResultTrustRoot) => TrustSettingsForCertificate::TrustRoot,
Some(kSecTrustSettingsResultTrustAsRoot) => TrustSettingsForCertificate::TrustAsRoot,
Some(kSecTrustSettingsResultDeny) => TrustSettingsForCertificate::Deny,
Some(kSecTrustSettingsResultUnspecified) => TrustSettingsForCertificate::Unspecified,
Some(_) | None => TrustSettingsForCertificate::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.
pub fn new(domain: Domain) -> TrustSettings {
TrustSettings { 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(kSecTrustSettingsResultTrustRoot as i64));

match trust_result {
TrustSettingsForCertificate::Unspecified => { continue; },
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 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()));
}
}
1 change: 1 addition & 0 deletions systest/build.rs
Expand Up @@ -35,6 +35,7 @@ fn main() {
.header("Security/SecRandom.h")
.header("Security/SecureTransport.h")
.header("Security/SecTrust.h")
.header("Security/SecTrustSettings.h")
.flag("-Wno-deprecated-declarations")
.type_name(|name, _, _| name.to_string())
.skip_signededness(|s| s.ends_with("Ref") || s.ends_with("Func"))
Expand Down
1 change: 1 addition & 0 deletions systest/src/main.rs
Expand Up @@ -33,5 +33,6 @@ use security_framework_sys::transform::*;
#[cfg(target_os = "macos")]
use security_framework_sys::certificate_oids::*;
use security_framework_sys::trust::*;
use security_framework_sys::trust_settings::*;

include!(concat!(env!("OUT_DIR"), "/all.rs"));

0 comments on commit 6f6f500

Please sign in to comment.