apple_security_framework/
trust.rs

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