Skip to main content

security/async_api/
mod.rs

1//! Executor-agnostic async wrappers for callback-based `Security.framework` APIs.
2//!
3//! Enabled with the `async` Cargo feature.
4//!
5//! ## Wrapped APIs
6//!
7//! - [`AsyncTrust::evaluate`] wraps `SecTrustEvaluateAsyncWithError`.
8//! - [`AsyncAuthorization::copy_rights`] wraps `AuthorizationCopyRightsAsync`.
9
10#![cfg(feature = "async")]
11
12use core::ffi::c_void;
13use core::marker::PhantomData;
14use core::pin::Pin;
15use core::task::{Context, Poll};
16use std::future::Future;
17use std::ptr;
18
19use doom_fish_utils::completion::{AsyncCompletion, AsyncCompletionFuture};
20use doom_fish_utils::panic_safe::catch_user_panic;
21use serde_json::Value;
22
23use crate::authorization::{Authorization, AuthorizationOptions};
24use crate::bridge;
25use crate::error::{status, Result, SecurityError};
26use crate::trust::Trust;
27
28fn flatten_async_result<T>(result: std::result::Result<Result<T>, String>) -> Result<T> {
29    result
30        .map_err(SecurityError::Serialization)
31        .and_then(|result| result)
32}
33
34fn bridge_status_error(
35    operation: &'static str,
36    status: i32,
37    error_raw: *mut c_void,
38) -> SecurityError {
39    match bridge::status_error(operation, status, error_raw) {
40        Ok(error) | Err(error) => error,
41    }
42}
43
44fn trust_failure(error_raw: *mut c_void) -> SecurityError {
45    match bridge::optional_string(error_raw) {
46        Ok(Some(message)) => SecurityError::TrustEvaluationFailed(message),
47        Ok(None) => SecurityError::TrustEvaluationFailed("trust evaluation failed".to_owned()),
48        Err(error) => error,
49    }
50}
51
52unsafe extern "C" fn trust_evaluate_async_cb(
53    refcon: *mut c_void,
54    trusted: bool,
55    error_raw: *mut c_void,
56) {
57    catch_user_panic("security::trust_evaluate_async_cb", || {
58        let result = if trusted {
59            Ok(())
60        } else {
61            Err(trust_failure(error_raw))
62        };
63        unsafe {
64            AsyncCompletion::<Result<()>>::complete_ok(refcon, result);
65        };
66    });
67}
68
69unsafe extern "C" fn authorization_copy_rights_async_cb(
70    refcon: *mut c_void,
71    json_raw: *mut c_void,
72    status_raw: i32,
73    error_raw: *mut c_void,
74) {
75    catch_user_panic("security::authorization_copy_rights_async_cb", || {
76        let result = if status_raw == status::SUCCESS {
77            match bridge::optional_json::<Value>(json_raw) {
78                Ok(Some(value)) => Ok(value),
79                Ok(None) => Err(SecurityError::InvalidArgument(
80                    "security_authorization_copy_rights_async_start returned no authorization rights"
81                        .to_owned(),
82                )),
83                Err(error) => Err(error),
84            }
85        } else {
86            Err(bridge_status_error(
87                "security_authorization_copy_rights_async_start",
88                status_raw,
89                error_raw,
90            ))
91        };
92        unsafe {
93            AsyncCompletion::<Result<Value>>::complete_ok(refcon, result);
94        };
95    });
96}
97
98/// Future returned by [`AsyncTrust::evaluate`].
99pub struct TrustEvaluateFuture<'a> {
100    inner: AsyncCompletionFuture<Result<()>>,
101    _owner: PhantomData<&'a Trust>,
102}
103
104impl core::fmt::Debug for TrustEvaluateFuture<'_> {
105    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
106        f.debug_struct("TrustEvaluateFuture")
107            .finish_non_exhaustive()
108    }
109}
110
111impl Future for TrustEvaluateFuture<'_> {
112    type Output = Result<()>;
113
114    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
115        Pin::new(&mut self.inner).poll(cx).map(flatten_async_result)
116    }
117}
118
119/// Future returned by [`AsyncAuthorization::copy_rights`].
120pub struct AuthorizationRightsFuture<'a> {
121    inner: AsyncCompletionFuture<Result<Value>>,
122    _owner: PhantomData<&'a Authorization>,
123}
124
125impl core::fmt::Debug for AuthorizationRightsFuture<'_> {
126    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
127        f.debug_struct("AuthorizationRightsFuture")
128            .finish_non_exhaustive()
129    }
130}
131
132impl Future for AuthorizationRightsFuture<'_> {
133    type Output = Result<Value>;
134
135    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
136        Pin::new(&mut self.inner).poll(cx).map(flatten_async_result)
137    }
138}
139
140/// Borrowed async wrapper for `SecTrustRef` evaluation.
141#[derive(Debug, Clone, Copy)]
142pub struct AsyncTrust<'a> {
143    trust: &'a Trust,
144}
145
146impl<'a> AsyncTrust<'a> {
147    /// Create a borrowed async wrapper around a trust object.
148    #[must_use]
149    pub const fn new(trust: &'a Trust) -> Self {
150        Self { trust }
151    }
152
153    /// Start `SecTrustEvaluateAsyncWithError` and await its callback.
154    pub fn evaluate(&self) -> Result<TrustEvaluateFuture<'a>> {
155        let (inner, refcon) = AsyncCompletion::<Result<()>>::create();
156        let mut error_raw = ptr::null_mut();
157        let status = unsafe {
158            bridge::security_trust_evaluate_async_start(
159                self.trust.as_ptr(),
160                refcon,
161                Some(trust_evaluate_async_cb),
162                &mut error_raw,
163            )
164        };
165        if status != status::SUCCESS {
166            let error =
167                bridge_status_error("security_trust_evaluate_async_start", status, error_raw);
168            unsafe {
169                AsyncCompletion::<Result<()>>::complete_err(refcon, error.to_string());
170            };
171            drop(inner);
172            return Err(error);
173        }
174
175        Ok(TrustEvaluateFuture {
176            inner,
177            _owner: PhantomData,
178        })
179    }
180}
181
182/// Borrowed async wrapper for `AuthorizationRef` rights requests.
183#[derive(Debug, Clone, Copy)]
184pub struct AsyncAuthorization<'a> {
185    authorization: &'a Authorization,
186}
187
188impl<'a> AsyncAuthorization<'a> {
189    /// Create a borrowed async wrapper around an authorization handle.
190    #[must_use]
191    pub const fn new(authorization: &'a Authorization) -> Self {
192        Self { authorization }
193    }
194
195    /// Start `AuthorizationCopyRightsAsync` and await the authorized-rights payload.
196    pub fn copy_rights(
197        &self,
198        rights: &[&str],
199        options: AuthorizationOptions,
200    ) -> Result<AuthorizationRightsFuture<'a>> {
201        let rights_json = bridge::json_cstring(&rights)?;
202        let (inner, refcon) = AsyncCompletion::<Result<Value>>::create();
203        let mut error_raw = ptr::null_mut();
204        let status = unsafe {
205            bridge::security_authorization_copy_rights_async_start(
206                self.authorization.as_ptr(),
207                rights_json.as_ptr(),
208                options.bits(),
209                refcon,
210                Some(authorization_copy_rights_async_cb),
211                &mut error_raw,
212            )
213        };
214        if status != status::SUCCESS {
215            let error = bridge_status_error(
216                "security_authorization_copy_rights_async_start",
217                status,
218                error_raw,
219            );
220            unsafe {
221                AsyncCompletion::<Result<Value>>::complete_err(refcon, error.to_string());
222            };
223            drop(inner);
224            return Err(error);
225        }
226
227        Ok(AuthorizationRightsFuture {
228            inner,
229            _owner: PhantomData,
230        })
231    }
232}