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