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