Skip to main content

security/
code.rs

1use base64::Engine;
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5use serde_json::Value;
6
7use crate::bridge;
8use crate::error::Result;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum SigningValue {
13    Boolean(bool),
14    Integer(i64),
15    String(String),
16    Data(Vec<u8>),
17    Array(Vec<Self>),
18    Dictionary(BTreeMap<String, Self>),
19    Unknown(String),
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct SigningInformation {
24    pub identifier: Option<String>,
25    pub team_identifier: Option<String>,
26    pub entitlements: BTreeMap<String, SigningValue>,
27    pub sandboxed: bool,
28    pub status: Option<u32>,
29}
30
31impl SigningInformation {
32    pub const fn is_signed(&self) -> bool {
33        self.identifier.is_some()
34    }
35}
36
37#[derive(Debug)]
38pub struct Code {
39    handle: bridge::Handle,
40}
41
42impl Code {
43    pub fn current() -> Result<Self> {
44        let mut status = 0;
45        let mut error = std::ptr::null_mut();
46        let raw = unsafe { bridge::security_code_copy_self(&mut status, &mut error) };
47        bridge::required_handle("security_code_copy_self", raw, status, error).map(|handle| Self {
48            handle,
49        })
50    }
51
52    pub fn static_code(&self) -> Result<StaticCode> {
53        let mut status = 0;
54        let mut error = std::ptr::null_mut();
55        let raw = unsafe {
56            bridge::security_code_copy_static_code(self.handle.as_ptr(), &mut status, &mut error)
57        };
58        bridge::required_handle("security_code_copy_static_code", raw, status, error)
59            .map(StaticCode::from_handle)
60    }
61
62    pub fn signing_information(&self) -> Result<SigningInformation> {
63        self.static_code()?.signing_information()
64    }
65
66    pub fn task(&self) -> Result<Task> {
67        Task::current()
68    }
69}
70
71#[derive(Debug)]
72pub struct StaticCode {
73    handle: bridge::Handle,
74}
75
76impl StaticCode {
77    fn from_handle(handle: bridge::Handle) -> Self {
78        Self { handle }
79    }
80
81    pub fn check_validity(&self) -> Result<()> {
82        let mut error = std::ptr::null_mut();
83        let status = unsafe {
84            bridge::security_static_code_check_validity(self.handle.as_ptr(), &mut error)
85        };
86        bridge::status_result("security_static_code_check_validity", status, error)
87    }
88
89    pub fn path(&self) -> Result<PathBuf> {
90        let mut status = 0;
91        let mut error = std::ptr::null_mut();
92        let raw = unsafe {
93            bridge::security_static_code_copy_path(self.handle.as_ptr(), &mut status, &mut error)
94        };
95        bridge::required_string("security_static_code_copy_path", raw, status, error)
96            .map(PathBuf::from)
97    }
98
99    pub fn designated_requirement(&self) -> Result<String> {
100        let mut status = 0;
101        let mut error = std::ptr::null_mut();
102        let raw = unsafe {
103            bridge::security_static_code_copy_designated_requirement(
104                self.handle.as_ptr(),
105                &mut status,
106                &mut error,
107            )
108        };
109        bridge::required_string(
110            "security_static_code_copy_designated_requirement",
111            raw,
112            status,
113            error,
114        )
115    }
116
117    pub fn signing_information(&self) -> Result<SigningInformation> {
118        let mut status = 0;
119        let mut error = std::ptr::null_mut();
120        let raw = unsafe {
121            bridge::security_static_code_copy_signing_information(
122                self.handle.as_ptr(),
123                &mut status,
124                &mut error,
125            )
126        };
127        let value: Value = bridge::required_json(
128            "security_static_code_copy_signing_information",
129            raw,
130            status,
131            error,
132        )?;
133        Ok(SigningInformation {
134            identifier: find_string(&value, &["identifier", "Identifier"]),
135            team_identifier: find_string(&value, &["teamid", "TeamIdentifier", "teamIdentifier"]),
136            entitlements: find_object(
137                &value,
138                &[
139                    "entitlements-dict",
140                    "EntitlementsDict",
141                    "entitlements",
142                    "Entitlements",
143                ],
144            )
145            .map(json_object_to_map)
146            .unwrap_or_default(),
147            sandboxed: matches!(
148                find_object(
149                    &value,
150                    &[
151                        "entitlements-dict",
152                        "EntitlementsDict",
153                        "entitlements",
154                        "Entitlements",
155                    ],
156                )
157                .and_then(|value| value.get("com.apple.security.app-sandbox")),
158                Some(Value::Bool(true))
159            ),
160            status: find_integer(&value, &["status", "Status"]).and_then(|value| u32::try_from(value).ok()),
161        })
162    }
163}
164
165#[derive(Debug)]
166pub struct Task {
167    handle: bridge::Handle,
168}
169
170impl Task {
171    pub fn current() -> Result<Self> {
172        let mut status = 0;
173        let mut error = std::ptr::null_mut();
174        let raw = unsafe { bridge::security_task_create_from_self(&mut status, &mut error) };
175        bridge::required_handle("security_task_create_from_self", raw, status, error).map(|handle| Self {
176            handle,
177        })
178    }
179
180    pub fn signing_identifier(&self) -> Result<Option<String>> {
181        let mut status = 0;
182        let mut error = std::ptr::null_mut();
183        let raw = unsafe {
184            bridge::security_task_copy_signing_identifier(self.handle.as_ptr(), &mut status, &mut error)
185        };
186        if raw.is_null() && status == 0 {
187            Ok(None)
188        } else {
189            bridge::required_string("security_task_copy_signing_identifier", raw, status, error)
190                .map(Some)
191        }
192    }
193
194    pub fn entitlement(&self, entitlement: &str) -> Result<Option<Value>> {
195        let entitlement = bridge::cstring(entitlement)?;
196        let mut status = 0;
197        let mut error = std::ptr::null_mut();
198        let raw = unsafe {
199            bridge::security_task_copy_value_for_entitlement(
200                self.handle.as_ptr(),
201                entitlement.as_ptr(),
202                &mut status,
203                &mut error,
204            )
205        };
206        if raw.is_null() && status == 0 {
207            return Ok(None);
208        }
209        let value: Value = bridge::required_json(
210            "security_task_copy_value_for_entitlement",
211            raw,
212            status,
213            error,
214        )?;
215        Ok(Some(value))
216    }
217}
218
219fn find_object<'a>(value: &'a Value, keys: &[&str]) -> Option<&'a serde_json::Map<String, Value>> {
220    keys.iter()
221        .find_map(|key| value.get(*key))
222        .and_then(Value::as_object)
223}
224
225fn find_string(value: &Value, keys: &[&str]) -> Option<String> {
226    keys.iter()
227        .find_map(|key| value.get(*key))
228        .and_then(Value::as_str)
229        .map(ToOwned::to_owned)
230}
231
232fn find_integer(value: &Value, keys: &[&str]) -> Option<i64> {
233    keys.iter()
234        .find_map(|key| value.get(*key))
235        .and_then(Value::as_i64)
236}
237
238fn json_object_to_map(object: &serde_json::Map<String, Value>) -> BTreeMap<String, SigningValue> {
239    object
240        .iter()
241        .map(|(key, value)| (key.clone(), signing_value(value)))
242        .collect()
243}
244
245fn signing_value(value: &Value) -> SigningValue {
246    match value {
247        Value::Bool(value) => SigningValue::Boolean(*value),
248        Value::Number(value) => value
249            .as_i64()
250            .map_or_else(|| SigningValue::Unknown(value.to_string()), SigningValue::Integer),
251        Value::String(value) => SigningValue::String(value.clone()),
252        Value::Array(values) => {
253            if let Some(data) = data_from_wrapped_json(value) {
254                SigningValue::Data(data)
255            } else {
256                SigningValue::Array(values.iter().map(signing_value).collect())
257            }
258        }
259        Value::Object(object) => {
260            if let Some(data) = data_from_wrapped_json(value) {
261                SigningValue::Data(data)
262            } else {
263                SigningValue::Dictionary(json_object_to_map(object))
264            }
265        }
266        Value::Null => SigningValue::Unknown("null".to_owned()),
267    }
268}
269
270fn data_from_wrapped_json(value: &Value) -> Option<Vec<u8>> {
271    let object = value.as_object()?;
272    if object.get("_type")?.as_str()? != "data" {
273        return None;
274    }
275    let base64 = object.get("base64")?.as_str()?;
276    base64::engine::general_purpose::STANDARD.decode(base64).ok()
277}