1 //! Querying trust settings. 2 3 use core_foundation::array::{CFArray, CFArrayRef}; 4 use core_foundation::base::{CFIndex, TCFType}; 5 use core_foundation::dictionary::CFDictionary; 6 use core_foundation::number::CFNumber; 7 use core_foundation::string::CFString; 8 9 use security_framework_sys::base::errSecNoTrustSettings; 10 use security_framework_sys::base::errSecSuccess; 11 use security_framework_sys::trust_settings::*; 12 13 use std::ptr; 14 15 use crate::base::Error; 16 use crate::base::Result; 17 use crate::certificate::SecCertificate; 18 use crate::cvt; 19 20 /// Which set of trust settings to query 21 #[derive(Debug, Copy, Clone, PartialEq, Eq)] 22 pub enum Domain { 23 /// Per-user trust settings 24 User, 25 /// Locally administered, system-wide trust settings 26 Admin, 27 /// System trust settings 28 System, 29 } 30 31 impl From<Domain> for SecTrustSettingsDomain { 32 #[inline] from(domain: Domain) -> SecTrustSettingsDomain33 fn from (domain: Domain) -> SecTrustSettingsDomain { 34 match domain { 35 Domain::User => kSecTrustSettingsDomainUser, 36 Domain::Admin => kSecTrustSettingsDomainAdmin, 37 Domain::System => kSecTrustSettingsDomainSystem, 38 } 39 } 40 } 41 42 /// Trust settings for a specific certificate in a specific domain 43 #[derive(Debug, Copy, Clone, PartialEq, Eq)] 44 pub enum TrustSettingsForCertificate { 45 /// Not used 46 Invalid, 47 48 /// This is a root certificate and is trusted, either explicitly or 49 /// implicitly. 50 TrustRoot, 51 52 /// This is a non-root certificate but is explicitly trusted. 53 TrustAsRoot, 54 55 /// Cert is explicitly distrusted. 56 Deny, 57 58 /// Neither trusted nor distrusted. 59 Unspecified, 60 } 61 62 impl TrustSettingsForCertificate { 63 /// Create from `kSecTrustSettingsResult*` constant new(value: i64) -> Self64 fn new(value: i64) -> Self { 65 if value < 0 || value > i64::from(u32::max_value()) { 66 return Self::Invalid; 67 } 68 match value as u32 { 69 kSecTrustSettingsResultTrustRoot => Self::TrustRoot, 70 kSecTrustSettingsResultTrustAsRoot => Self::TrustAsRoot, 71 kSecTrustSettingsResultDeny => Self::Deny, 72 kSecTrustSettingsResultUnspecified => Self::Unspecified, 73 _ => Self::Invalid, 74 } 75 } 76 } 77 78 /// Allows access to the certificates and their trust settings in a given domain. 79 pub struct TrustSettings { 80 domain: Domain, 81 } 82 83 impl TrustSettings { 84 /// Create a new TrustSettings for the given domain. 85 /// 86 /// You can call `iter()` to discover the certificates with settings in this domain. 87 /// 88 /// Then you can call `tls_trust_settings_for_certificate()` with a given certificate 89 /// to learn what the aggregate trust setting for that certificate within this domain. 90 #[inline(always)] new(domain: Domain) -> Self91 pub fn new(domain: Domain) -> Self { 92 Self { domain } 93 } 94 95 /// Create an iterator over the certificates with settings in this domain. 96 /// This produces an empty iterator if there are no such certificates. iter(&self) -> Result<TrustSettingsIter>97 pub fn iter(&self) -> Result<TrustSettingsIter> { 98 let array = unsafe { 99 let mut array_ptr: CFArrayRef = ptr::null_mut(); 100 101 // SecTrustSettingsCopyCertificates returns errSecNoTrustSettings 102 // if no items have trust settings in the given domain. We map 103 // that to an empty TrustSettings iterator. 104 match SecTrustSettingsCopyCertificates(self.domain.into(), &mut array_ptr) { 105 errSecNoTrustSettings => CFArray::from_CFTypes(&[]), 106 errSecSuccess => CFArray::<SecCertificate>::wrap_under_create_rule(array_ptr), 107 err => return Err(Error::from_code(err)), 108 } 109 }; 110 111 Ok(TrustSettingsIter { index: 0, array }) 112 } 113 114 /// Returns the aggregate trust setting for the given certificate. 115 /// 116 /// This tells you whether the certificate should be trusted as a TLS 117 /// root certificate. 118 /// 119 /// If the certificate has no trust settings in the given domain, the 120 /// `errSecItemNotFound` error is returned. 121 /// 122 /// If the certificate has no specific trust settings for TLS in the 123 /// given domain `None` is returned. 124 /// 125 /// Otherwise, the specific trust settings are aggregated and returned. tls_trust_settings_for_certificate(&self, cert: &SecCertificate) -> Result<Option<TrustSettingsForCertificate>>126 pub fn tls_trust_settings_for_certificate(&self, cert: &SecCertificate) 127 -> Result<Option<TrustSettingsForCertificate>> { 128 let trust_settings = unsafe { 129 let mut array_ptr: CFArrayRef = ptr::null_mut(); 130 let cert_ptr = cert.as_CFTypeRef() as *mut _; 131 cvt(SecTrustSettingsCopyTrustSettings(cert_ptr, 132 self.domain.into(), 133 &mut array_ptr))?; 134 CFArray::<CFDictionary>::wrap_under_create_rule(array_ptr) 135 }; 136 137 for settings in trust_settings.iter() { 138 // Reject settings for non-SSL policies 139 let is_not_ssl_policy = { 140 let policy_name_key = CFString::from_static_string("kSecTrustSettingsPolicyName"); 141 let ssl_policy_name = CFString::from_static_string("sslServer"); 142 143 let maybe_name: Option<CFString> = settings 144 .find(policy_name_key.as_CFTypeRef() as *const _) 145 .map(|name| unsafe { CFString::wrap_under_get_rule(*name as *const _) }); 146 147 matches!(maybe_name, Some(ref name) if name != &ssl_policy_name) 148 }; 149 150 if is_not_ssl_policy { 151 continue; 152 } 153 154 // Evaluate "effective trust settings" for this usage constraint. 155 let maybe_trust_result = { 156 let settings_result_key = CFString::from_static_string("kSecTrustSettingsResult"); 157 settings 158 .find(settings_result_key.as_CFTypeRef() as *const _) 159 .map(|num| unsafe { CFNumber::wrap_under_get_rule(*num as *const _) }) 160 .and_then(|num| num.to_i64()) 161 }; 162 163 // "Note that an empty Trust Settings array means "always trust this cert, 164 // with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"." 165 let trust_result = TrustSettingsForCertificate::new(maybe_trust_result 166 .unwrap_or_else(|| i64::from(kSecTrustSettingsResultTrustRoot))); 167 168 match trust_result { 169 TrustSettingsForCertificate::Unspecified | 170 TrustSettingsForCertificate::Invalid => { continue; }, 171 _ => return Ok(Some(trust_result)), 172 } 173 } 174 175 // There were no more specific settings. This might mean the certificate 176 // is to be trusted anyway (since, eg, it's in system store), but leave 177 // the caller to make this decision. 178 Ok(None) 179 } 180 } 181 182 /// Iterator over certificates. 183 pub struct TrustSettingsIter { 184 array: CFArray<SecCertificate>, 185 index: CFIndex, 186 } 187 188 impl Iterator for TrustSettingsIter { 189 type Item = SecCertificate; 190 191 #[inline] next(&mut self) -> Option<Self::Item>192 fn next(&mut self) -> Option<Self::Item> { 193 if self.index >= self.array.len() { 194 None 195 } else { 196 let cert = self.array.get(self.index) 197 .unwrap(); 198 self.index += 1; 199 Some(cert.clone()) 200 } 201 } 202 203 #[inline] size_hint(&self) -> (usize, Option<usize>)204 fn size_hint(&self) -> (usize, Option<usize>) { 205 let left = (self.array.len() as usize).saturating_sub(self.index as usize); 206 (left, Some(left)) 207 } 208 } 209 210 #[cfg(test)] 211 mod test { 212 use super::*; 213 use crate::test::certificate; 214 list_for_domain(domain: Domain)215 fn list_for_domain(domain: Domain) { 216 println!("--- domain: {:?}", domain); 217 let ts = TrustSettings::new(domain); 218 let iterator = ts.iter() 219 .unwrap(); 220 221 for (i, cert) in iterator.enumerate() { 222 println!("cert({:?}) = {:?}", i, cert); 223 println!(" settings = {:?}", ts.tls_trust_settings_for_certificate(&cert)); 224 } 225 println!("---"); 226 } 227 228 #[test] list_for_user()229 fn list_for_user() { 230 list_for_domain(Domain::User); 231 } 232 233 #[test] list_for_system()234 fn list_for_system() { 235 list_for_domain(Domain::System); 236 } 237 238 #[test] list_for_admin()239 fn list_for_admin() { 240 list_for_domain(Domain::Admin); 241 } 242 243 #[test] test_system_certs_are_present()244 fn test_system_certs_are_present() { 245 let system = TrustSettings::new(Domain::System).iter().unwrap().count(); 246 247 // 168 at the time of writing 248 assert!(system > 100); 249 } 250 251 #[test] test_isrg_root_exists_and_is_trusted()252 fn test_isrg_root_exists_and_is_trusted() { 253 let ts = TrustSettings::new(Domain::System); 254 assert_eq!(ts 255 .iter() 256 .unwrap() 257 .find(|cert| cert.subject_summary() == "ISRG Root X1") 258 .and_then(|cert| ts.tls_trust_settings_for_certificate(&cert).unwrap()), 259 None); 260 // ^ this is a case where None means "always trust", according to Apple docs: 261 // 262 // "Note that an empty Trust Settings array means "always trust this cert, 263 // with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"." 264 } 265 266 #[test] test_unknown_cert_is_not_trusted()267 fn test_unknown_cert_is_not_trusted() { 268 let ts = TrustSettings::new(Domain::System); 269 let cert = certificate(); 270 assert_eq!(ts.tls_trust_settings_for_certificate(&cert) 271 .err() 272 .unwrap() 273 .message(), 274 Some("The specified item could not be found in the keychain.".into())); 275 } 276 } 277