security-rs 0.2.1

Safe Rust bindings for Apple's Security framework — keychain, identity, certificates, trust, authorization, CMS, SecureTransport, and cryptographic primitives on macOS
Documentation
use serde::Serialize;
use serde_json::Value;

use crate::bridge::{self, Handle};
use crate::error::{Result, SecurityError};

pub type RevocationFlags = u32;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PolicyIdentifier {
    AppleX509Basic,
    AppleSsl,
    AppleSmime,
    AppleEap,
    AppleIpsec,
    ApplePkinitClient,
    ApplePkinitServer,
    AppleCodeSigning,
    MacAppStoreReceipt,
    AppleIdValidation,
    AppleTimeStamping,
    AppleRevocation,
    ApplePassbookSigning,
    ApplePayIssuerEncryption,
    AppleSslServer,
    AppleSslClient,
    AppleEapServer,
    AppleEapClient,
    AppleIpsecServer,
    AppleIpsecClient,
}

impl PolicyIdentifier {
    const fn as_bridge_name(self) -> &'static str {
        match self {
            Self::AppleX509Basic => "apple_x509_basic",
            Self::AppleSsl => "apple_ssl",
            Self::AppleSmime => "apple_smime",
            Self::AppleEap => "apple_eap",
            Self::AppleIpsec => "apple_ipsec",
            Self::ApplePkinitClient => "apple_pkinit_client",
            Self::ApplePkinitServer => "apple_pkinit_server",
            Self::AppleCodeSigning => "apple_code_signing",
            Self::MacAppStoreReceipt => "mac_app_store_receipt",
            Self::AppleIdValidation => "apple_id_validation",
            Self::AppleTimeStamping => "apple_time_stamping",
            Self::AppleRevocation => "apple_revocation",
            Self::ApplePassbookSigning => "apple_passbook_signing",
            Self::ApplePayIssuerEncryption => "apple_pay_issuer_encryption",
            Self::AppleSslServer => "apple_ssl_server",
            Self::AppleSslClient => "apple_ssl_client",
            Self::AppleEapServer => "apple_eap_server",
            Self::AppleEapClient => "apple_eap_client",
            Self::AppleIpsecServer => "apple_ipsec_server",
            Self::AppleIpsecClient => "apple_ipsec_client",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(untagged)]
pub enum PolicyName {
    Single(String),
    Multiple(Vec<String>),
}

impl From<String> for PolicyName {
    fn from(value: String) -> Self {
        Self::Single(value)
    }
}

impl From<&str> for PolicyName {
    fn from(value: &str) -> Self {
        Self::Single(value.to_owned())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
pub struct PolicyProperties {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<PolicyName>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub revocation_flags: Option<RevocationFlags>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub team_identifier: Option<String>,
}

impl PolicyProperties {
    const fn is_empty(&self) -> bool {
        self.name.is_none()
            && self.client.is_none()
            && self.revocation_flags.is_none()
            && self.team_identifier.is_none()
    }
}

#[derive(Debug)]
pub struct Policy {
    handle: Handle,
}

impl Policy {
    pub(crate) fn from_handle(handle: Handle) -> Self {
        Self { handle }
    }

    pub(crate) fn handle(&self) -> &Handle {
        &self.handle
    }

    pub fn basic_x509() -> Result<Self> {
        let mut status = 0;
        let mut error = std::ptr::null_mut();
        let raw = unsafe { bridge::security_policy_create_basic_x509(&mut status, &mut error) };
        bridge::required_handle("security_policy_create_basic_x509", raw, status, error)
            .map(Self::from_handle)
    }

    pub fn ssl(server: bool, hostname: Option<&str>) -> Result<Self> {
        let hostname = hostname.map(bridge::cstring).transpose()?;
        let mut status = 0;
        let mut error = std::ptr::null_mut();
        let raw = unsafe {
            bridge::security_policy_create_ssl(
                server,
                hostname
                    .as_ref()
                    .map_or(std::ptr::null(), |value| value.as_c_str().as_ptr()),
                &mut status,
                &mut error,
            )
        };
        bridge::required_handle("security_policy_create_ssl", raw, status, error)
            .map(Self::from_handle)
    }

    pub fn revocation(flags: RevocationFlags) -> Result<Self> {
        let mut status = 0;
        let mut error = std::ptr::null_mut();
        let raw =
            unsafe { bridge::security_policy_create_revocation(flags, &mut status, &mut error) };
        bridge::required_handle("security_policy_create_revocation", raw, status, error)
            .map(Self::from_handle)
    }

    pub fn with_properties(
        identifier: PolicyIdentifier,
        properties: &PolicyProperties,
    ) -> Result<Self> {
        let identifier = bridge::cstring(identifier.as_bridge_name())?;
        let properties = if properties.is_empty() {
            None
        } else {
            let json = serde_json::to_string(properties).map_err(|error| {
                SecurityError::Serialization(format!("policy properties JSON failed: {error}"))
            })?;
            Some(bridge::cstring(&json)?)
        };
        let mut status = 0;
        let mut error = std::ptr::null_mut();
        let raw = unsafe {
            bridge::security_policy_create_with_properties(
                identifier.as_ptr(),
                properties
                    .as_ref()
                    .map_or(std::ptr::null(), |value| value.as_c_str().as_ptr()),
                &mut status,
                &mut error,
            )
        };
        bridge::required_handle("security_policy_create_with_properties", raw, status, error)
            .map(Self::from_handle)
    }

    pub fn properties(&self) -> Result<Value> {
        let mut status = 0;
        let mut error = std::ptr::null_mut();
        let raw = unsafe {
            bridge::security_policy_copy_properties(self.handle.as_ptr(), &mut status, &mut error)
        };
        bridge::required_json("security_policy_copy_properties", raw, status, error)
    }
}