Skip to main content

security/trust/
mod.rs

1//! `SecPolicy` and `SecTrust` wrappers.
2
3use apple_cf::CFError;
4
5use crate::certificate::Certificate;
6use crate::error::{Result, SecurityError};
7use crate::ffi;
8use crate::private::{cf_array, cf_error_description, cf_string, sec_error_message, OwnedCf};
9
10/// Owned `SecPolicyRef` used to evaluate a certificate chain.
11pub struct Policy {
12    raw: ffi::SecPolicyRef,
13}
14
15impl Policy {
16    /// Create Apple's default X.509 trust policy.
17    ///
18    /// # Errors
19    ///
20    /// Returns an error if Security.framework cannot allocate the policy object.
21    pub fn basic_x509() -> Result<Self> {
22        let raw = unsafe { ffi::SecPolicyCreateBasicX509() };
23        if raw.is_null() {
24            return Err(SecurityError::CoreFoundation(CFError::new(
25                "SecPolicyCreateBasicX509",
26            )));
27        }
28        Ok(Self { raw })
29    }
30
31    /// Create an SSL policy.
32    ///
33    /// # Errors
34    ///
35    /// Returns an error if the hostname contains NUL bytes or Security.framework cannot allocate the policy object.
36    pub fn ssl(server: bool, hostname: Option<&str>) -> Result<Self> {
37        let hostname = hostname.map(cf_string).transpose()?;
38        let raw = unsafe {
39            ffi::SecPolicyCreateSSL(
40                u8::from(server),
41                hostname
42                    .as_ref()
43                    .map_or(std::ptr::null(), OwnedCf::as_string),
44            )
45        };
46        if raw.is_null() {
47            return Err(SecurityError::CoreFoundation(CFError::new(
48                "SecPolicyCreateSSL",
49            )));
50        }
51        Ok(Self { raw })
52    }
53
54    /// Borrow the raw `SecPolicyRef`.
55    #[must_use]
56    pub const fn as_raw(&self) -> ffi::SecPolicyRef {
57        self.raw
58    }
59}
60
61impl Drop for Policy {
62    fn drop(&mut self) {
63        if !self.raw.is_null() {
64            unsafe { ffi::CFRelease(self.raw.cast()) };
65        }
66    }
67}
68
69impl core::fmt::Debug for Policy {
70    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71        f.debug_struct("Policy").field("raw", &self.raw).finish()
72    }
73}
74
75/// Owned `SecTrustRef` created from a certificate and one or more policies.
76pub struct Trust {
77    raw: ffi::SecTrustRef,
78}
79
80impl Trust {
81    /// Create a trust object from one certificate and one or more policies.
82    ///
83    /// # Errors
84    ///
85    /// Returns an error if no policies were supplied or Security.framework rejects the inputs.
86    pub fn new(certificate: &Certificate, policies: &[Policy]) -> Result<Self> {
87        if policies.is_empty() {
88            return Err(SecurityError::InvalidArgument(
89                "at least one trust policy is required".to_owned(),
90            ));
91        }
92
93        let policy_input = policy_input(policies)?;
94        let mut trust = std::ptr::null();
95        let status = unsafe {
96            ffi::SecTrustCreateWithCertificates(
97                certificate.as_raw().cast(),
98                policy_input.raw,
99                &mut trust,
100            )
101        };
102        if status != ffi::status::SUCCESS {
103            return Err(SecurityError::from_status(
104                "SecTrustCreateWithCertificates",
105                status,
106                sec_error_message(status),
107            ));
108        }
109        if trust.is_null() {
110            return Err(SecurityError::CoreFoundation(CFError::new(
111                "SecTrustCreateWithCertificates",
112            )));
113        }
114        Ok(Self { raw: trust })
115    }
116
117    /// Replace the policies used by this trust object.
118    ///
119    /// # Errors
120    ///
121    /// Returns an error if no policies were supplied or Security.framework rejects the update.
122    pub fn set_policies(&mut self, policies: &[Policy]) -> Result<()> {
123        if policies.is_empty() {
124            return Err(SecurityError::InvalidArgument(
125                "at least one trust policy is required".to_owned(),
126            ));
127        }
128        let policy_input = policy_input(policies)?;
129        let status = unsafe { ffi::SecTrustSetPolicies(self.raw, policy_input.raw) };
130        if status == ffi::status::SUCCESS {
131            Ok(())
132        } else {
133            Err(SecurityError::from_status(
134                "SecTrustSetPolicies",
135                status,
136                sec_error_message(status),
137            ))
138        }
139    }
140
141    /// Evaluate the trust object.
142    ///
143    /// # Errors
144    ///
145    /// Returns `SecurityError::TrustEvaluationFailed` when the certificate chain does not satisfy the configured policies.
146    pub fn evaluate(&self) -> Result<()> {
147        let mut error = std::ptr::null();
148        let ok = unsafe { ffi::SecTrustEvaluateWithError(self.raw, &mut error) };
149        if ok != 0 {
150            Ok(())
151        } else {
152            Err(SecurityError::TrustEvaluationFailed(cf_error_description(
153                error,
154            )))
155        }
156    }
157
158    /// Borrow the raw `SecTrustRef`.
159    #[must_use]
160    pub const fn as_raw(&self) -> ffi::SecTrustRef {
161        self.raw
162    }
163}
164
165impl Drop for Trust {
166    fn drop(&mut self) {
167        if !self.raw.is_null() {
168            unsafe { ffi::CFRelease(self.raw.cast()) };
169        }
170    }
171}
172
173impl core::fmt::Debug for Trust {
174    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
175        f.debug_struct("Trust").field("raw", &self.raw).finish()
176    }
177}
178
179struct PolicyInput {
180    raw: ffi::CFTypeRef,
181    _array: Option<OwnedCf>,
182}
183
184fn policy_input(policies: &[Policy]) -> Result<PolicyInput> {
185    if policies.len() == 1 {
186        return Ok(PolicyInput {
187            raw: policies[0].as_raw().cast(),
188            _array: None,
189        });
190    }
191
192    let policy_refs = policies
193        .iter()
194        .map(|policy| policy.as_raw().cast())
195        .collect::<Vec<_>>();
196    let array = cf_array(&policy_refs)?;
197    Ok(PolicyInput {
198        raw: array.as_ptr(),
199        _array: Some(array),
200    })
201}