apple_clis/security/
find_certificate.rs1use crate::prelude::*;
2
3use openssl::x509::X509;
4use openssl::{error::ErrorStack as OpenSslError, nid::Nid, x509::X509NameRef};
5use serde::Serialize;
6use thiserror::Error;
7
8use super::SecurityCLIInstance;
9
10impl SecurityCLIInstance {
11 const DEVELOPER_NAME_SCHEMAS: [&'static str; 2] = ["Developer:", "Development:"];
12
13 fn get_pem_list(&self, name_substr: &str) -> bossy::Result<bossy::Output> {
14 self
15 .bossy_command()
16 .with_args(["find-certificate", "-p", "-a", "-c", name_substr])
17 .run_and_wait_for_output()
18 }
19
20 fn get_developer_pem_list(&self) -> bossy::Result<Vec<bossy::Output>> {
21 Self::DEVELOPER_NAME_SCHEMAS
22 .iter()
23 .map(|name| self.get_pem_list(name))
24 .collect()
25 }
26
27 pub fn get_developer_pems(&self) -> Result<Vec<X509>> {
28 let certs = self
29 .get_developer_pem_list()?
30 .into_iter()
31 .map(|output| X509::stack_from_pem(output.stdout()).map_err(Error::X509ParseFailed))
32 .collect::<Result<Vec<_>>>()?;
33 let certs = certs.into_iter().flatten();
34 Ok(certs.collect())
35 }
36
37 #[instrument(skip_all, ret)]
38 pub fn get_developer_certs(&self) -> Result<Vec<Certificate>> {
39 Ok(
40 self
41 .get_developer_pems()?
42 .into_iter()
43 .filter_map(|cert| Certificate::try_from_x509(cert).ok())
44 .collect(),
45 )
46 }
47}
48
49#[derive(Debug, Serialize)]
50pub struct Certificate {
51 pub common_name: String,
53 pub organization_name: Option<String>,
55}
56
57#[derive(Debug, Error)]
58pub enum X509FieldError {
59 #[error("Missing X509 field {name:?} ({id:?})")]
60 FieldMissing { name: &'static str, id: Nid },
61
62 #[error("Field contained invalid UTF-8: {0}")]
63 FieldNotValidUtf8(#[source] OpenSslError),
64}
65
66#[tracing::instrument(level = "trace", skip(subject_name, field_name, field_nid))]
67fn get_x509_field(
68 subject_name: &X509NameRef,
69 field_name: &'static str,
70 field_nid: Nid,
71) -> std::result::Result<String, X509FieldError> {
72 subject_name
73 .entries_by_nid(field_nid)
74 .next()
75 .ok_or(X509FieldError::FieldMissing {
76 name: field_name,
77 id: field_nid,
78 })?
79 .data()
80 .as_utf8()
81 .map_err(X509FieldError::FieldNotValidUtf8)
82 .map(|s| s.to_string())
83}
84
85#[derive(Debug, thiserror::Error)]
86pub enum FromX509Error {
87 #[error("Common Name missing in cert: {0}")]
88 CommonNameMissing(#[from] X509FieldError),
89}
90
91impl Certificate {
92 #[tracing::instrument(level = "trace", skip(cert))]
93 pub fn try_from_x509(cert: X509) -> std::result::Result<Self, FromX509Error> {
94 let subject = cert.subject_name();
95 let common_name = get_x509_field(subject, "Common Name", Nid::COMMONNAME)?;
96 let organization_name = get_x509_field(subject, "Organization", Nid::ORGANIZATIONNAME).ok();
97 Ok(Certificate {
98 common_name,
99 organization_name,
100 })
101 }
102}