security_framework/
trust.rs

1//! Trust evaluation support.
2use core_foundation::array::CFArray;
3#[cfg(target_os = "macos")]
4use core_foundation::array::CFArrayRef;
5use core_foundation::base::TCFType;
6use core_foundation::data::CFData;
7use core_foundation::date::CFDate;
8use core_foundation::{declare_TCFType, impl_TCFType};
9use core_foundation_sys::base::{Boolean, CFIndex};
10
11use security_framework_sys::base::{errSecNotTrusted, errSecTrustSettingDeny};
12use security_framework_sys::trust::*;
13use std::ptr;
14
15use crate::base::Result;
16use crate::certificate::SecCertificate;
17use crate::cvt;
18use crate::key::SecKey;
19use crate::policy::SecPolicy;
20use core_foundation::error::{CFError, CFErrorRef};
21
22/// The result of trust evaluation.
23#[derive(Debug, Copy, Clone, PartialEq, Eq)]
24pub struct TrustResult(SecTrustResultType);
25
26impl TrustResult {
27    /// Indicates a denial by the user, do not proceed.
28    pub const DENY: Self = Self(kSecTrustResultDeny);
29    /// Indicates a trust policy failure that the user cannot override.
30    pub const FATAL_TRUST_FAILURE: Self = Self(kSecTrustResultFatalTrustFailure);
31    /// An invalid setting or result.
32    pub const INVALID: Self = Self(kSecTrustResultInvalid);
33    /// An error not related to trust validation.
34    pub const OTHER_ERROR: Self = Self(kSecTrustResultOtherError);
35    /// You may proceed.
36    pub const PROCEED: Self = Self(kSecTrustResultProceed);
37    /// Indicates a trust policy failure that the user can override.
38    pub const RECOVERABLE_TRUST_FAILURE: Self = Self(kSecTrustResultRecoverableTrustFailure);
39    /// The certificate is implicitly trusted.
40    pub const UNSPECIFIED: Self = Self(kSecTrustResultUnspecified);
41}
42
43impl TrustResult {
44    /// Returns true if the result is "successful" - specifically `PROCEED` or `UNSPECIFIED`.
45    #[inline]
46    #[must_use]
47    pub fn success(self) -> bool {
48        matches!(self, Self::PROCEED | Self::UNSPECIFIED)
49    }
50}
51
52declare_TCFType! {
53    /// A type representing a trust evaluation for a certificate.
54    SecTrust, SecTrustRef
55}
56impl_TCFType!(SecTrust, SecTrustRef, SecTrustGetTypeID);
57
58unsafe impl Sync for SecTrust {}
59unsafe impl Send for SecTrust {}
60
61#[cfg(target_os = "macos")]
62bitflags::bitflags! {
63    /// The option flags used to configure the evaluation of a `SecTrust`.
64    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65    pub struct TrustOptions: SecTrustOptionFlags {
66        /// Allow expired certificates (except for the root certificate).
67        const ALLOW_EXPIRED = kSecTrustOptionAllowExpired;
68        /// Allow CA certificates as leaf certificates.
69        const LEAF_IS_CA = kSecTrustOptionLeafIsCA;
70        /// Allow network downloads of CA certificates.
71        const FETCH_ISSUER_FROM_NET = kSecTrustOptionFetchIssuerFromNet;
72        /// Allow expired root certificates.
73        const ALLOW_EXPIRED_ROOT = kSecTrustOptionAllowExpiredRoot;
74        /// Require a positive revocation check for each certificate.
75        const REQUIRE_REVOCATION_PER_CERT =  kSecTrustOptionRequireRevPerCert;
76        /// Use TrustSettings instead of anchors.
77        const USE_TRUST_SETTINGS = kSecTrustOptionUseTrustSettings;
78        /// Treat properly self-signed certificates as anchors implicitly.
79        const IMPLICIT_ANCHORS =  kSecTrustOptionImplicitAnchors;
80    }
81}
82
83impl SecTrust {
84    /// Creates a `SecTrustRef` that is configured with a certificate chain, for validating
85    /// that chain against a collection of policies.
86    pub fn create_with_certificates(
87        certs: &[SecCertificate],
88        policies: &[SecPolicy],
89    ) -> Result<Self> {
90        let cert_array = CFArray::from_CFTypes(certs);
91        let policy_array = CFArray::from_CFTypes(policies);
92        let mut trust = ptr::null_mut();
93        unsafe {
94            cvt(SecTrustCreateWithCertificates(
95                cert_array.as_CFTypeRef(),
96                policy_array.as_CFTypeRef(),
97                &mut trust,
98            ))?;
99            Ok(Self(trust))
100        }
101    }
102
103    /// Sets the date and time against which the certificates in this trust object
104    /// are verified.
105    #[inline]
106    pub fn set_trust_verify_date(&mut self, date: &CFDate) -> Result<()> {
107        unsafe { cvt(SecTrustSetVerifyDate(self.0, date.as_concrete_TypeRef())) }
108    }
109
110    /// Sets additional anchor certificates used to validate trust.
111    pub fn set_anchor_certificates(&mut self, certs: &[SecCertificate]) -> Result<()> {
112        let certs = CFArray::from_CFTypes(certs);
113
114        unsafe {
115            cvt(SecTrustSetAnchorCertificates(
116                self.0,
117                certs.as_concrete_TypeRef(),
118            ))
119        }
120    }
121
122    /// Retrieves the anchor (root) certificates stored by macOS
123    #[cfg(target_os = "macos")]
124    pub fn copy_anchor_certificates() -> Result<Vec<SecCertificate>> {
125        let mut array: CFArrayRef = ptr::null();
126
127        unsafe {
128            cvt(SecTrustCopyAnchorCertificates(&mut array))?;
129        }
130
131        if array.is_null() {
132            return Ok(vec![]);
133        }
134
135        let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(array) };
136        Ok(array.into_iter().map(|c| c.clone()).collect())
137    }
138
139    /// If set to `true`, only the certificates specified by
140    /// `set_anchor_certificates` will be trusted, but not globally trusted
141    /// certificates.
142    #[inline]
143    pub fn set_trust_anchor_certificates_only(&mut self, only: bool) -> Result<()> {
144        unsafe { cvt(SecTrustSetAnchorCertificatesOnly(self.0, Boolean::from(only))) }
145    }
146
147    /// Sets the policy used to evaluate trust.
148    #[inline]
149    pub fn set_policy(&mut self, policy: &SecPolicy) -> Result<()> {
150        unsafe { cvt(SecTrustSetPolicies(self.0, policy.as_CFTypeRef())) }
151    }
152
153    /// Sets option flags for customizing evaluation of a trust object.
154    #[cfg(target_os = "macos")]
155    #[inline]
156    pub fn set_options(&mut self, options: TrustOptions) -> Result<()> {
157        unsafe { cvt(SecTrustSetOptions(self.0, options.bits())) }
158    }
159
160    /// Indicates whether this trust object is permitted to
161    /// fetch missing intermediate certificates from the network.
162    pub fn get_network_fetch_allowed(&mut self) -> Result<bool> {
163        let mut allowed = 0;
164
165        unsafe { cvt(SecTrustGetNetworkFetchAllowed(self.0, &mut allowed))? };
166
167        Ok(allowed != 0)
168    }
169
170    /// Specifies whether this trust object is permitted to
171    /// fetch missing intermediate certificates from the network.
172    #[inline]
173    pub fn set_network_fetch_allowed(&mut self, allowed: bool) -> Result<()> {
174        unsafe { cvt(SecTrustSetNetworkFetchAllowed(self.0, u8::from(allowed))) }
175    }
176
177    /// Attaches Online Certificate Status Protocol (OSCP) response data
178    /// to this trust object.
179    pub fn set_trust_ocsp_response<I: Iterator<Item = impl AsRef<[u8]>>>(
180        &mut self,
181        ocsp_response: I,
182    ) -> Result<()> {
183        let response: Vec<CFData> = ocsp_response
184            .into_iter()
185            .map(|bytes| CFData::from_buffer(bytes.as_ref()))
186            .collect();
187
188        let response = CFArray::from_CFTypes(&response);
189
190        unsafe { cvt(SecTrustSetOCSPResponse(self.0, response.as_CFTypeRef())) }
191    }
192
193    /// Attaches signed certificate timestamp data to this trust object.
194    #[cfg(any(feature = "OSX_10_14", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
195    pub fn set_signed_certificate_timestamps<I: Iterator<Item = impl AsRef<[u8]>>>(
196        &mut self,
197        scts: I,
198    ) -> Result<()> {
199        let scts: Vec<CFData> = scts
200            .into_iter()
201            .map(|bytes| CFData::from_buffer(bytes.as_ref()))
202            .collect();
203
204        let scts = CFArray::from_CFTypes(&scts);
205
206        unsafe { cvt(SecTrustSetSignedCertificateTimestamps(self.0, scts.as_concrete_TypeRef())) }
207    }
208
209    /// Returns the public key for a leaf certificate after it has been evaluated.
210    #[inline]
211    pub fn copy_public_key(&mut self) -> Result<SecKey> {
212        unsafe { Ok(SecKey::wrap_under_create_rule(SecTrustCopyPublicKey(self.0))) }
213    }
214
215    /// Evaluates trust.
216    #[deprecated(note = "use evaluate_with_error")]
217    pub fn evaluate(&self) -> Result<TrustResult> {
218        #[allow(deprecated)]
219        unsafe {
220            let mut result = kSecTrustResultInvalid;
221            cvt(SecTrustEvaluate(self.0, &mut result))?;
222            Ok(TrustResult(result))
223        }
224    }
225
226    /// Evaluates trust. Requires macOS 10.14 (checked at runtime) or iOS,
227    /// otherwise it just calls `evaluate()`.
228    pub fn evaluate_with_error(&self) -> Result<(), CFError> {
229        // Here, we statically know the symbol is available.
230        #[cfg(any(feature = "OSX_10_14", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
231        let fnptr = Some(SecTrustEvaluateWithError);
232
233        // On older platforms, we try to look it up dynamically.
234        #[cfg(not(any(feature = "OSX_10_14", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos")))]
235        let fnptr = {
236            // SAFETY: The C-string is correct.
237            let fnptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"SecTrustEvaluateWithError\0".as_ptr().cast()) };
238            // SAFETY: The function pointer either has the given signature, or
239            // is NULL when the symbol is not available - which is valid to
240            // transmute to `Option<fn()>`, see:
241            // https://doc.rust-lang.org/std/option/index.html#representation
242            unsafe {
243                core::mem::transmute::<
244                    *const std::os::raw::c_void,
245                    Option<unsafe extern "C" fn(SecTrustRef, *mut CFErrorRef) -> bool>,
246                >(fnptr)
247            }
248        };
249
250        // Try to use `SecTrustEvaluateWithError` if available.
251        if let Some(fnptr) = fnptr {
252            let mut error: CFErrorRef = ::std::ptr::null_mut();
253            // SAFETY: The given pointers are valid.
254            let result = unsafe { fnptr(self.0, &mut error) };
255            if !result {
256                assert!(!error.is_null());
257                // SAFETY: `SecTrustEvaluateWithError` expects us to release
258                // the error.
259                let error = unsafe { CFError::wrap_under_create_rule(error) };
260                return Err(error);
261            }
262            Ok(())
263        } else {
264            // Otherwise, fall back to SecTrustEvaluate. This has the same
265            // semantics, though error codes are worse.
266            #[allow(deprecated)]
267            let code = match self.evaluate() {
268                Ok(res) if res.success() => return Ok(()),
269                Ok(TrustResult::DENY) => errSecTrustSettingDeny,
270                Ok(_) => errSecNotTrusted,
271                Err(err) => err.code(),
272            };
273            Err(cferror_from_osstatus(code))
274        }
275    }
276
277    /// Returns the number of certificates in an evaluated certificate chain.
278    ///
279    /// Note: evaluate must first be called on the `SecTrust`.
280    #[inline(always)]
281    #[must_use]
282    // FIXME: this should have been usize. Don't expose CFIndex in Rust APIs.
283    pub fn certificate_count(&self) -> CFIndex {
284        unsafe { SecTrustGetCertificateCount(self.0) }
285    }
286
287    /// Returns a specific certificate from the certificate chain used to evaluate trust.
288    ///
289    /// Note: evaluate must first be called on the `SecTrust`.
290    #[deprecated(note = "deprecated by Apple")]
291    #[must_use]
292    pub fn certificate_at_index(&self, ix: CFIndex) -> Option<SecCertificate> {
293        #[allow(deprecated)]
294        unsafe {
295            if self.certificate_count() <= ix {
296                None
297            } else {
298                let certificate = SecTrustGetCertificateAtIndex(self.0, ix);
299                Some(SecCertificate::wrap_under_get_rule(certificate.cast()))
300            }
301        }
302    }
303}
304
305extern "C" {
306    fn CFErrorCreate(allocator: core_foundation_sys::base::CFAllocatorRef, domain: core_foundation_sys::string::CFStringRef, code: CFIndex, userInfo: core_foundation_sys::dictionary::CFDictionaryRef) -> CFErrorRef;
307}
308
309fn cferror_from_osstatus(code: core_foundation_sys::base::OSStatus) -> CFError {
310    unsafe {
311        let error = CFErrorCreate(ptr::null_mut(), core_foundation_sys::error::kCFErrorDomainOSStatus, code as _, ptr::null_mut());
312        assert!(!error.is_null());
313        CFError::wrap_under_create_rule(error)
314    }
315}
316
317#[cfg(test)]
318mod test {
319    use crate::policy::SecPolicy;
320    use crate::secure_transport::SslProtocolSide;
321    use crate::test::certificate;
322    use crate::trust::SecTrust;
323
324    #[test]
325    #[allow(deprecated)]
326    fn create_with_certificates() {
327        let cert = certificate();
328        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
329        let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
330        assert!(!trust.evaluate().unwrap().success());
331    }
332
333    #[test]
334    fn create_with_certificates_new() {
335        let cert = certificate();
336        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
337        let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
338        assert!(trust.evaluate_with_error().is_err());
339    }
340
341    #[test]
342    #[allow(deprecated)]
343    fn certificate_count_and_at_index() {
344        let cert = certificate();
345        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
346        let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
347        trust.evaluate().unwrap();
348
349        let count = trust.certificate_count();
350        assert_eq!(count, 1);
351
352        let cert_bytes = trust.certificate_at_index(0).unwrap().to_der();
353        assert_eq!(cert_bytes, certificate().to_der());
354    }
355
356    #[test]
357    #[allow(deprecated)]
358    fn certificate_count_and_at_index_new() {
359        let cert = certificate();
360        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
361        let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
362        assert!(trust.evaluate_with_error().is_err());
363
364        let count = trust.certificate_count();
365        assert_eq!(count, 1);
366
367        let cert_bytes = trust.certificate_at_index(0).unwrap().to_der();
368        assert_eq!(cert_bytes, certificate().to_der());
369    }
370
371    #[test]
372    #[allow(deprecated)]
373    fn certificate_at_index_out_of_bounds() {
374        let cert = certificate();
375        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
376
377        let trust = SecTrust::create_with_certificates(std::slice::from_ref(&cert), std::slice::from_ref(&ssl_policy)).unwrap();
378        trust.evaluate().unwrap();
379        assert!(trust.certificate_at_index(1).is_none());
380
381        let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
382        assert!(trust.evaluate_with_error().is_err());
383        assert!(trust.certificate_at_index(1).is_none());
384    }
385
386    #[test]
387    #[allow(deprecated)]
388    fn set_policy() {
389        let cert = certificate();
390        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io.bogus"));
391        let mut trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
392        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
393        trust.set_policy(&ssl_policy).unwrap();
394        assert!(!trust.evaluate().unwrap().success());
395    }
396
397    #[test]
398    fn set_policy_new() {
399        let cert = certificate();
400        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io.bogus"));
401        let mut trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
402        let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
403        trust.set_policy(&ssl_policy).unwrap();
404        assert!(trust.evaluate_with_error().is_err());
405    }
406}