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