| //! 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()) |
| ); |
| } |
| } |