apple_security/
trust_settings.rs

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