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 bitflags::bitflags;

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

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct AccessControlFlags: u64 {
        const DEFAULTS = 0;
        const USER_PRESENCE = 1 << 0;
        const BIOMETRY_ANY = 1 << 1;
        const BIOMETRY_CURRENT_SET = 1 << 3;
        const DEVICE_PASSCODE = 1 << 4;
        const COMPANION = 1 << 5;
        const OR = 1 << 14;
        const AND = 1 << 15;
        const PRIVATE_KEY_USAGE = 1 << 30;
        const APPLICATION_PASSWORD = 1 << 31;
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AccessControlProtection {
    WhenUnlocked,
    AfterFirstUnlock,
    WhenPasscodeSetThisDeviceOnly,
    WhenUnlockedThisDeviceOnly,
    AfterFirstUnlockThisDeviceOnly,
}

impl AccessControlProtection {
    const fn as_bridge_name(self) -> &'static str {
        match self {
            Self::WhenUnlocked => "when_unlocked",
            Self::AfterFirstUnlock => "after_first_unlock",
            Self::WhenPasscodeSetThisDeviceOnly => "when_passcode_set_this_device_only",
            Self::WhenUnlockedThisDeviceOnly => "when_unlocked_this_device_only",
            Self::AfterFirstUnlockThisDeviceOnly => "after_first_unlock_this_device_only",
        }
    }
}

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

impl AccessControl {
    pub fn create(protection: AccessControlProtection, flags: AccessControlFlags) -> Result<Self> {
        let protection = bridge::cstring(protection.as_bridge_name())?;
        let mut status = 0;
        let mut error = std::ptr::null_mut();
        let raw = unsafe {
            bridge::security_access_control_create(
                protection.as_ptr(),
                flags.bits(),
                &mut status,
                &mut error,
            )
        };
        bridge::required_handle("security_access_control_create", raw, status, error)
            .map(|handle| Self { handle })
    }

    pub fn is_valid(&self) -> bool {
        !self.handle.as_ptr().is_null()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeychainEntry {
    account: String,
    service: String,
}

impl KeychainEntry {
    pub fn new(account: impl Into<String>, service: impl Into<String>) -> Self {
        Self {
            account: account.into(),
            service: service.into(),
        }
    }

    pub fn account(&self) -> &str {
        &self.account
    }

    pub fn service(&self) -> &str {
        &self.service
    }

    pub fn set(&self, password: &str) -> Result<()> {
        Keychain::set(&self.account, &self.service, password)
    }

    pub fn get(&self) -> Result<String> {
        Keychain::get(&self.account, &self.service)
    }

    pub fn delete(&self) -> Result<()> {
        Keychain::delete(&self.account, &self.service)
    }
}

pub struct Keychain;

impl Keychain {
    pub fn entry(account: impl Into<String>, service: impl Into<String>) -> KeychainEntry {
        KeychainEntry::new(account, service)
    }

    pub fn set(account: &str, service: &str, password: &str) -> Result<()> {
        let account = bridge::cstring(account)?;
        let service = bridge::cstring(service)?;
        let password = bridge::cstring(password)?;
        let mut error = std::ptr::null_mut();
        let status = unsafe {
            bridge::security_keychain_set_password(
                account.as_ptr(),
                service.as_ptr(),
                password.as_ptr(),
                &mut error,
            )
        };
        bridge::status_result("security_keychain_set_password", status, error)
    }

    pub fn get(account: &str, service: &str) -> Result<String> {
        let account = bridge::cstring(account)?;
        let service = bridge::cstring(service)?;
        let mut status = 0;
        let mut error = std::ptr::null_mut();
        let raw = unsafe {
            bridge::security_keychain_get_password(
                account.as_ptr(),
                service.as_ptr(),
                &mut status,
                &mut error,
            )
        };
        bridge::required_string("security_keychain_get_password", raw, status, error)
    }

    pub fn delete(account: &str, service: &str) -> Result<()> {
        let account = bridge::cstring(account)?;
        let service = bridge::cstring(service)?;
        let mut error = std::ptr::null_mut();
        let status = unsafe {
            bridge::security_keychain_delete_password(
                account.as_ptr(),
                service.as_ptr(),
                &mut error,
            )
        };
        bridge::status_result("security_keychain_delete_password", status, error)
    }

    pub fn list_accounts(service: &str) -> Result<Vec<String>> {
        let service = bridge::cstring(service)?;
        let mut status = 0;
        let mut error = std::ptr::null_mut();
        let raw = unsafe {
            bridge::security_keychain_list_accounts(service.as_ptr(), &mut status, &mut error)
        };
        bridge::required_json("security_keychain_list_accounts", raw, status, error)
    }
}