apple_security_framework/
trust_settings.rs

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