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