chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,88 @@
//! Access Control support.
use crate::base::{Error, Result};
use core_foundation::base::{kCFAllocatorDefault, CFOptionFlags, TCFType};
use core_foundation::string::CFString;
use core_foundation::{declare_TCFType, impl_TCFType};
use security_framework_sys::access_control::{
kSecAttrAccessibleAfterFirstUnlock, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAttrAccessibleWhenUnlocked,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, SecAccessControlCreateWithFlags,
SecAccessControlGetTypeID,
};
use security_framework_sys::base::{errSecParam, SecAccessControlRef};
use std::fmt;
use std::ptr;
declare_TCFType! {
/// A type representing sec access control settings.
SecAccessControl, SecAccessControlRef
}
impl_TCFType!(
SecAccessControl,
SecAccessControlRef,
SecAccessControlGetTypeID
);
unsafe impl Sync for SecAccessControl {}
unsafe impl Send for SecAccessControl {}
/// Specify when an item is available.
pub enum ProtectionMode {
/// The data in the keychain can only be accessed when the device is
/// unlocked. Only available if a passcode is set on the device.
AccessibleWhenPasscodeSetThisDeviceOnly,
///The data in the keychain item can be accessed only while the device is
/// unlocked by the user.
AccessibleWhenUnlockedThisDeviceOnly,
/// The data in the keychain item can be accessed only while the device is
/// unlocked by the user.
AccessibleWhenUnlocked,
/// The data in the keychain item cannot be accessed after a restart until
/// the device has been unlocked once by the user.
AccessibleAfterFirstUnlockThisDeviceOnly,
/// The data in the keychain item cannot be accessed after a restart until
/// the device has been unlocked once by the user.
AccessibleAfterFirstUnlock,
}
impl SecAccessControl {
/// Create `AccessControl` object from flags
pub fn create_with_flags(flags: CFOptionFlags) -> Result<Self> {
Self::create_with_protection(None, flags)
}
/// Create `AccessControl` object from a protection value and flags.
pub fn create_with_protection(protection: Option<ProtectionMode>, flags: CFOptionFlags) -> Result<Self> {
let protection_val = protection.map(|v| {
match v {
ProtectionMode::AccessibleWhenPasscodeSetThisDeviceOnly => unsafe { CFString::wrap_under_get_rule(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) },
ProtectionMode::AccessibleWhenUnlockedThisDeviceOnly => unsafe { CFString::wrap_under_get_rule(kSecAttrAccessibleWhenUnlockedThisDeviceOnly) },
ProtectionMode::AccessibleWhenUnlocked => unsafe { CFString::wrap_under_get_rule(kSecAttrAccessibleWhenUnlocked) },
ProtectionMode::AccessibleAfterFirstUnlockThisDeviceOnly => unsafe { CFString::wrap_under_get_rule(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) },
ProtectionMode::AccessibleAfterFirstUnlock => unsafe { CFString::wrap_under_get_rule(kSecAttrAccessibleAfterFirstUnlock) },
}
}).unwrap_or_else(|| {
unsafe { CFString::wrap_under_get_rule(kSecAttrAccessibleWhenUnlocked) }
});
unsafe {
let access_control = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
protection_val.as_CFTypeRef(),
flags,
ptr::null_mut(),
);
if access_control.is_null() {
Err(Error::from_code(errSecParam))
} else {
Ok(Self::wrap_under_create_rule(access_control))
}
}
}
}
impl fmt::Debug for SecAccessControl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SecAccessControl").finish_non_exhaustive()
}
}

View File

@@ -0,0 +1,814 @@
//! Authorization Services support.
/// # Potential improvements
///
/// * When generic specialization stabilizes prevent copying from `CString` arguments.
/// * `AuthorizationCopyRightsAsync`
/// * Provide constants for well known item names
use crate::base::{Error, Result};
#[cfg(all(target_os = "macos", feature = "job-bless"))]
use core_foundation::base::Boolean;
use core_foundation::base::{CFTypeRef, TCFType};
use core_foundation::bundle::CFBundleRef;
use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
#[cfg(all(target_os = "macos", feature = "job-bless"))]
use core_foundation::error::CFError;
#[cfg(all(target_os = "macos", feature = "job-bless"))]
use core_foundation::error::CFErrorRef;
use core_foundation::string::{CFString, CFStringRef};
use security_framework_sys::authorization as sys;
use security_framework_sys::base::errSecConversionError;
use std::ffi::{CStr, CString};
use std::fs::File;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::os::raw::c_void;
use std::ptr::addr_of;
use sys::AuthorizationExternalForm;
macro_rules! optional_str_to_cfref {
($string:ident) => {{
$string
.map(CFString::new)
.map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef())
}};
}
macro_rules! cstring_or_err {
($x:expr) => {{
CString::new($x).map_err(|_| Error::from_code(errSecConversionError))
}};
}
bitflags::bitflags! {
/// The flags used to specify authorization options.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Flags: sys::AuthorizationFlags {
/// An empty flag set that you use as a placeholder when you don't want
/// any of the other flags.
const DEFAULTS = sys::kAuthorizationFlagDefaults;
/// A flag that permits user interaction as needed.
const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed;
/// A flag that permits the Security Server to attempt to grant the
/// rights requested.
const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights;
/// A flag that permits the Security Server to grant rights on an
/// individual basis.
const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights;
/// A flag that instructs the Security Server to revoke authorization.
const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights;
/// A flag that instructs the Security Server to preauthorize the rights
/// requested.
const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize;
}
}
impl Default for Flags {
#[inline(always)]
fn default() -> Self {
Self::DEFAULTS
}
}
/// Information about an authorization right or the environment.
#[repr(C)]
pub struct AuthorizationItem(sys::AuthorizationItem);
impl AuthorizationItem {
/// The required name of the authorization right or environment data.
///
/// If `name` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
#[must_use]
pub fn name(&self) -> &str {
unsafe {
CStr::from_ptr(self.0.name)
.to_str()
.expect("AuthorizationItem::name failed to convert &str to CStr")
}
}
/// The information pertaining to the name field. Do not rely on NULL
/// termination of string data.
#[inline]
#[must_use]
pub fn value(&self) -> Option<&[u8]> {
if self.0.value.is_null() {
return None;
}
let value = unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) };
Some(value)
}
}
/// A set of authorization items returned and owned by the Security Server.
#[derive(Debug)]
#[repr(C)]
pub struct AuthorizationItemSet<'a> {
inner: *const sys::AuthorizationItemSet,
phantom: PhantomData<&'a sys::AuthorizationItemSet>,
}
impl Drop for AuthorizationItemSet<'_> {
#[inline]
fn drop(&mut self) {
unsafe {
sys::AuthorizationFreeItemSet(self.inner.cast_mut());
}
}
}
/// Used by `AuthorizationItemSetBuilder` to store data pointed to by
/// `sys::AuthorizationItemSet`.
#[derive(Debug)]
pub struct AuthorizationItemSetStorage {
/// The layout of this is a little awkward because of the requirements of
/// Apple's APIs. `items` contains pointers to data owned by `names` and
/// `values`, so we must not modify them once `items` has been set up.
names: Vec<CString>,
values: Vec<Option<Vec<u8>>>,
items: Vec<sys::AuthorizationItem>,
/// Must not be given to APIs which would attempt to modify it.
///
/// See `AuthorizationItemSet` for sets owned by the Security Server which
/// are writable.
pub set: sys::AuthorizationItemSet,
}
impl Default for AuthorizationItemSetStorage {
#[inline]
fn default() -> Self {
Self {
names: Vec::new(),
values: Vec::new(),
items: Vec::new(),
set: sys::AuthorizationItemSet {
count: 0,
items: std::ptr::null_mut(),
},
}
}
}
/// A convenience `AuthorizationItemSetBuilder` builder which enabled you to use
/// rust types. All names and values passed in will be copied.
#[derive(Debug, Default)]
pub struct AuthorizationItemSetBuilder {
storage: AuthorizationItemSetStorage,
}
// Stores AuthorizationItems contiguously, and their items separately
impl AuthorizationItemSetBuilder {
/// Creates a new `AuthorizationItemSetStore`, which simplifies creating
/// owned vectors of `AuthorizationItem`s.
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Default::default()
}
/// Adds an `AuthorizationItem` with the name set to a right and an empty
/// value.
///
/// If `name` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> {
self.storage.names.push(cstring_or_err!(name)?);
self.storage.values.push(None);
Ok(self)
}
/// Adds an `AuthorizationItem` with arbitrary data.
///
/// If `name` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self>
where
N: Into<Vec<u8>>,
V: Into<Vec<u8>>,
{
self.storage.names.push(cstring_or_err!(name)?);
self.storage.values.push(Some(value.into()));
Ok(self)
}
/// Adds an `AuthorizationItem` with NULL terminated string data.
///
/// If `name` or `value` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self>
where
N: Into<Vec<u8>>,
V: Into<Vec<u8>>,
{
self.storage.names.push(cstring_or_err!(name)?);
self.storage
.values
.push(Some(cstring_or_err!(value)?.to_bytes().to_vec()));
Ok(self)
}
/// Creates the `sys::AuthorizationItemSet`, and gives you ownership of the
/// data it points to.
#[must_use]
pub fn build(mut self) -> AuthorizationItemSetStorage {
self.storage.items = self
.storage
.names
.iter()
.zip(self.storage.values.iter())
.map(|(n, v)| sys::AuthorizationItem {
name: n.as_ptr(),
value: v
.as_ref()
.map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void),
valueLength: v.as_ref().map_or(0, |v| v.len()),
flags: 0,
})
.collect();
self.storage.set = sys::AuthorizationItemSet {
count: self.storage.items.len() as u32,
items: self.storage.items.as_ptr().cast_mut(),
};
self.storage
}
}
/// Used by `Authorization::set_item` to define the rules of he right.
#[derive(Copy, Clone)]
pub enum RightDefinition<'a> {
/// The dictionary will contain the keys and values that define the rules.
FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>),
/// The specified right's rules will be duplicated.
FromExistingRight(&'a str),
}
/// A wrapper around `AuthorizationCreate` and functions which operate on an
/// `AuthorizationRef`.
#[derive(Debug)]
pub struct Authorization {
handle: sys::AuthorizationRef,
free_flags: Flags,
}
impl TryFrom<AuthorizationExternalForm> for Authorization {
type Error = Error;
/// Internalizes the external representation of an authorization reference.
#[cold]
fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> {
let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
let status = unsafe {
sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr())
};
if status != sys::errAuthorizationSuccess {
return Err(Error::from_code(status));
}
let auth = Self {
handle: unsafe { handle.assume_init() },
free_flags: Flags::default(),
};
Ok(auth)
}
}
impl Authorization {
/// Creates an authorization object which has no environment or associated
/// rights.
#[inline]
#[allow(clippy::should_implement_trait)]
pub fn default() -> Result<Self> {
Self::new(None, None, Default::default())
}
/// Creates an authorization reference and provides an option to authorize
/// or preauthorize rights.
///
/// `rights` should be the names of the rights you want to create.
///
/// `environment` is used when authorizing or preauthorizing rights. Not
/// used in OS X v10.2 and earlier. In macOS 10.3 and later, you can pass
/// icon or prompt data to be used in the authentication dialog box. In
/// macOS 10.4 and later, you can also pass a user name and password in
/// order to authorize a user without user interaction.
#[allow(clippy::unnecessary_cast)]
#[allow(clippy::needless_pass_by_value)]
pub fn new(
// FIXME: this should have been by reference
rights: Option<AuthorizationItemSetStorage>,
environment: Option<AuthorizationItemSetStorage>,
flags: Flags,
) -> Result<Self> {
let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| {
addr_of!(r.set).cast::<sys::AuthorizationItemSet>()
});
let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| {
addr_of!(e.set).cast::<sys::AuthorizationItemSet>()
});
let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit();
let status = unsafe {
sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr())
};
if status != sys::errAuthorizationSuccess {
return Err(Error::from_code(status));
}
Ok(Self {
handle: unsafe { handle.assume_init() },
free_flags: Default::default(),
})
}
/// Internalizes the external representation of an authorization reference.
#[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")]
pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> {
external_form.try_into()
}
/// By default the rights acquired will be retained by the Security Server.
/// Use this to ensure they are destroyed and to prevent shared rights'
/// continued used by other processes.
#[inline(always)]
pub fn destroy_rights(mut self) {
self.free_flags = Flags::DESTROY_RIGHTS;
}
/// Retrieve's the right's definition as a dictionary. Use `right_exists`
/// if you want to avoid retrieving the dictionary.
///
/// `name` can be a wildcard right name.
///
/// If `name` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
// TODO: deprecate and remove. CFDictionary should not be exposed in public Rust APIs.
pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> {
let name = cstring_or_err!(name)?;
let mut dict = MaybeUninit::<CFDictionaryRef>::uninit();
let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) };
if status != sys::errAuthorizationSuccess {
return Err(Error::from_code(status));
}
let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) };
Ok(dict)
}
/// Checks if a right exists within the policy database. This is the same as
/// `get_right`, but avoids a dictionary allocation.
///
/// If `name` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> {
let name = cstring_or_err!(name)?;
let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) };
Ok(status == sys::errAuthorizationSuccess)
}
/// Removes a right from the policy database.
///
/// `name` cannot be a wildcard right name.
///
/// If `name` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> {
let name = cstring_or_err!(name)?;
let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) };
if status != sys::errAuthorizationSuccess {
return Err(Error::from_code(status));
}
Ok(())
}
/// Creates or updates a right entry in the policy database. Your process
/// must have a code signature in order to be able to add rights to the
/// authorization database.
///
/// `name` cannot be a wildcard right.
///
/// `definition` can be either a `CFDictionaryRef` containing keys defining
/// the rules or a `CFStringRef` representing the name of another right
/// whose rules you wish to duplicaate.
///
/// `description` is a key which can be used to look up localized
/// descriptions.
///
/// `bundle` will be used to get localizations from if not the main bundle.
///
/// `localeTableName` will be used to get localizations if provided.
///
/// If `name` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
pub fn set_right<T: Into<Vec<u8>>>(
&self,
name: T,
definition: RightDefinition<'_>,
description: Option<&str>,
bundle: Option<CFBundleRef>,
locale: Option<&str>,
) -> Result<()> {
let name = cstring_or_err!(name)?;
let definition_cfstring: CFString;
let definition_ref = match definition {
RightDefinition::FromDictionary(def) => def.as_CFTypeRef(),
RightDefinition::FromExistingRight(def) => {
definition_cfstring = CFString::new(def);
definition_cfstring.as_CFTypeRef()
},
};
let status = unsafe {
sys::AuthorizationRightSet(
self.handle,
name.as_ptr(),
definition_ref,
optional_str_to_cfref!(description),
bundle.unwrap_or(std::ptr::null_mut()),
optional_str_to_cfref!(locale),
)
};
if status != sys::errAuthorizationSuccess {
return Err(Error::from_code(status));
}
Ok(())
}
/// An authorization plugin can store the results of an authentication
/// operation by calling the `SetContextValue` function. You can then
/// retrieve this supporting data, such as the user name.
///
/// `tag` should specify the type of data the Security Server should return.
/// If `None`, all available information is retreieved.
///
/// If `tag` isn't convertable to a `CString` it will return
/// Err(errSecConversionError).
pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> {
let tag_with_nul: CString;
let tag_ptr = match tag {
Some(tag) => {
tag_with_nul = cstring_or_err!(tag)?;
tag_with_nul.as_ptr()
},
None => std::ptr::null(),
};
let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit();
let status = unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) };
if status != sys::errAuthorizationSuccess {
return Err(Error::from(status));
}
let set = AuthorizationItemSet {
inner: unsafe { inner.assume_init() },
phantom: PhantomData,
};
Ok(set)
}
/// Creates an external representation of an authorization reference so that
/// you can transmit it between processes.
pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> {
let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit();
let status = unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) };
if status != sys::errAuthorizationSuccess {
return Err(Error::from(status));
}
Ok(unsafe { external_form.assume_init() })
}
/// Runs an executable tool with root privileges.
/// Discards executable's output
#[cfg(target_os = "macos")]
#[inline(always)]
pub fn execute_with_privileges<P, S, I>(
&self,
command: P,
arguments: I,
flags: Flags,
) -> Result<()>
where
P: AsRef<std::path::Path>,
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
use std::os::unix::ffi::OsStrExt;
let arguments = arguments
.into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes()))
.collect::<Vec<_>>();
self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, false)?;
Ok(())
}
/// Runs an executable tool with root privileges,
/// and returns a `File` handle to its communication pipe
#[cfg(target_os = "macos")]
#[inline(always)]
pub fn execute_with_privileges_piped<P, S, I>(
&self,
command: P,
arguments: I,
flags: Flags,
) -> Result<File>
where
P: AsRef<std::path::Path>,
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
use std::os::unix::ffi::OsStrExt;
let arguments = arguments
.into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes()))
.collect::<Vec<_>>();
Ok(self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, true)?.unwrap())
}
/// Submits the executable for the given label as a `launchd` job.
#[cfg(all(target_os = "macos", feature = "job-bless"))]
pub fn job_bless(&self, label: &str) -> Result<(), CFError> {
#[link(name = "ServiceManagement", kind = "framework")]
unsafe extern "C" {
static kSMDomainSystemLaunchd: CFStringRef;
fn SMJobBless(
domain: CFStringRef,
executableLabel: CFStringRef,
auth: sys::AuthorizationRef,
error: *mut CFErrorRef,
) -> Boolean;
}
unsafe {
let mut error = std::ptr::null_mut();
SMJobBless(
kSMDomainSystemLaunchd,
CFString::new(label).as_concrete_TypeRef(),
self.handle,
&mut error,
);
if !error.is_null() {
return Err(CFError::wrap_under_create_rule(error));
}
Ok(())
}
}
// Runs an executable tool with root privileges.
#[cfg(target_os = "macos")]
fn execute_with_privileges_internal(
&self,
command: &[u8],
arguments: &[CString],
flags: Flags,
make_pipe: bool,
) -> Result<Option<File>> {
use std::os::unix::io::{FromRawFd, RawFd};
let c_cmd = cstring_or_err!(command)?;
let mut c_args = arguments.iter().map(|a| a.as_ptr().cast_mut()).collect::<Vec<_>>();
c_args.push(std::ptr::null_mut());
let mut pipe: *mut libc::FILE = std::ptr::null_mut();
let status = unsafe {
sys::AuthorizationExecuteWithPrivileges(
self.handle,
c_cmd.as_ptr(),
flags.bits(),
c_args.as_ptr(),
if make_pipe { &mut pipe } else { std::ptr::null_mut() },
)
};
crate::cvt(status)?;
Ok(if make_pipe {
if pipe.is_null() {
return Err(Error::from_code(32)); // EPIPE?
}
Some(unsafe { File::from_raw_fd(libc::fileno(pipe) as RawFd) })
} else {
None
})
}
}
impl Drop for Authorization {
#[inline]
fn drop(&mut self) {
unsafe {
sys::AuthorizationFree(self.handle, self.free_flags.bits());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_default_authorization() {
Authorization::default().unwrap();
}
#[test]
fn test_create_allowed_authorization() -> Result<()> {
let rights = AuthorizationItemSetBuilder::new()
.add_right("system.hdd.smart")?
.add_right("system.login.done")?
.build();
Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
Ok(())
}
#[test]
fn test_create_then_destroy_allowed_authorization() -> Result<()> {
let rights = AuthorizationItemSetBuilder::new()
.add_right("system.hdd.smart")?
.add_right("system.login.done")?
.build();
let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap();
auth.destroy_rights();
Ok(())
}
#[test]
fn test_create_authorization_requiring_interaction() -> Result<()> {
let rights = AuthorizationItemSetBuilder::new()
.add_right("system.privilege.admin")?
.build();
let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err();
assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed);
Ok(())
}
fn create_credentials_env() -> Result<AuthorizationItemSetStorage> {
let set = AuthorizationItemSetBuilder::new()
.add_string("username", std::env::var("USER").expect("You must set the USER environment variable"))?
.add_string("password", std::env::var("PASSWORD").expect("You must set the PASSWORD environment varible"))?
.build();
Ok(set)
}
#[test]
fn test_create_authorization_with_bad_credentials() -> Result<()> {
let rights = AuthorizationItemSetBuilder::new()
.add_right("system.privilege.admin")?
.build();
let env = AuthorizationItemSetBuilder::new()
.add_string("username", "Tim Apple")?
.add_string("password", "butterfly")?
.build();
let error =
Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err();
assert_eq!(error.code(), sys::errAuthorizationDenied);
Ok(())
}
#[test]
fn test_create_authorization_with_credentials() -> Result<()> {
if std::env::var_os("PASSWORD").is_none() {
return Ok(());
}
let rights = AuthorizationItemSetBuilder::new()
.add_right("system.privilege.admin")?
.build();
let env = create_credentials_env()?;
Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
Ok(())
}
#[test]
fn test_query_authorization_database() -> Result<()> {
assert!(Authorization::right_exists("system.hdd.smart")?);
assert!(!Authorization::right_exists("EMPTY")?);
let dict = Authorization::get_right("system.hdd.smart").unwrap();
let key = CFString::from_static_string("class");
assert!(dict.contains_key(&key));
let invalid_key = CFString::from_static_string("EMPTY");
assert!(!dict.contains_key(&invalid_key));
Ok(())
}
/// This test will only pass if its process has a valid code signature.
#[test]
fn test_modify_authorization_database() -> Result<()> {
if std::env::var_os("PASSWORD").is_none() {
return Ok(());
}
let rights = AuthorizationItemSetBuilder::new()
.add_right("config.modify.")?
.build();
let env = create_credentials_env()?;
let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap();
assert!(!Authorization::right_exists("TEST_RIGHT")?);
auth.set_right(
"TEST_RIGHT",
RightDefinition::FromExistingRight("system.hdd.smart"),
None,
None,
None,
)
.unwrap();
assert!(Authorization::right_exists("TEST_RIGHT")?);
auth.remove_right("TEST_RIGHT").unwrap();
assert!(!Authorization::right_exists("TEST_RIGHT")?);
Ok(())
}
/// This test will succeed if authorization popup is approved.
#[test]
fn test_execute_with_privileges() -> Result<()> {
if std::env::var_os("PASSWORD").is_none() {
return Ok(());
}
let rights = AuthorizationItemSetBuilder::new()
.add_right("system.privilege.admin")?
.build();
let auth = Authorization::new(
Some(rights),
None,
Flags::DEFAULTS
| Flags::INTERACTION_ALLOWED
| Flags::PREAUTHORIZE
| Flags::EXTEND_RIGHTS,
)?;
let file = auth.execute_with_privileges_piped("/bin/ls", ["/"], Flags::DEFAULTS)?;
use std::io::{self, BufRead};
for line in io::BufReader::new(file).lines() {
let _ = line.unwrap();
}
Ok(())
}
}

85
vendor/security-framework/src/base.rs vendored Normal file
View File

@@ -0,0 +1,85 @@
//! Support types for other modules.
use core_foundation::string::CFString;
use core_foundation_sys::base::OSStatus;
use std::num::NonZeroI32;
use std::{error, fmt, result};
/// A `Result` type commonly returned by functions.
pub type Result<T, E = Error> = result::Result<T, E>;
/// A Security Framework error.
#[derive(Copy, Clone)]
pub struct Error(NonZeroI32);
impl fmt::Debug for Error {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = fmt.debug_struct("Error");
builder.field("code", &self.0);
if let Some(message) = self.message() {
builder.field("message", &message);
}
builder.finish()
}
}
impl Error {
/// Creates a new `Error` from a status code.
/// The code must not be zero
#[inline]
#[must_use]
pub fn from_code(code: OSStatus) -> Self {
Self(NonZeroI32::new(code).unwrap_or_else(|| NonZeroI32::new(1).unwrap()))
}
/// Returns a string describing the current error, if available.
#[inline(always)]
#[must_use]
pub fn message(self) -> Option<String> {
self.inner_message()
}
#[cold]
fn inner_message(self) -> Option<String> {
use core_foundation::base::TCFType;
use security_framework_sys::base::SecCopyErrorMessageString;
use std::ptr;
unsafe {
let s = SecCopyErrorMessageString(self.code(), ptr::null_mut());
if s.is_null() {
None
} else {
Some(CFString::wrap_under_create_rule(s).to_string())
}
}
}
/// Returns the code of the current error.
#[inline(always)]
#[must_use]
pub const fn code(self) -> OSStatus {
self.0.get() as _
}
}
impl From<OSStatus> for Error {
#[inline(always)]
fn from(code: OSStatus) -> Self {
Self::from_code(code)
}
}
impl fmt::Display for Error {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(message) = self.message() {
write!(fmt, "{message}")
} else {
write!(fmt, "error code {}", self.code())
}
}
}
impl error::Error for Error {}

View File

@@ -0,0 +1,275 @@
//! Certificate support.
use core_foundation::array::{CFArray, CFArrayRef};
use core_foundation::base::{TCFType, ToVoid};
use core_foundation::data::CFData;
use core_foundation::dictionary::CFMutableDictionary;
use core_foundation::string::CFString;
use core_foundation::{declare_TCFType, impl_TCFType};
use core_foundation_sys::base::kCFAllocatorDefault;
#[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
use security_framework_sys::base::{errSecNotTrusted, errSecSuccess};
use security_framework_sys::base::{errSecParam, SecCertificateRef};
use security_framework_sys::certificate::*;
use security_framework_sys::keychain_item::SecItemDelete;
use std::fmt;
use std::ptr;
use crate::base::{Error, Result};
use crate::cvt;
use crate::key;
#[cfg(target_os = "macos")]
use crate::os::macos::keychain::SecKeychain;
use core_foundation::base::FromVoid;
use core_foundation::error::{CFError, CFErrorRef};
use core_foundation::number::CFNumber;
use security_framework_sys::item::kSecValueRef;
declare_TCFType! {
/// A type representing a certificate.
SecCertificate, SecCertificateRef
}
impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID);
unsafe impl Sync for SecCertificate {}
unsafe impl Send for SecCertificate {}
impl fmt::Debug for SecCertificate {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("SecCertificate")
.field("subject", &self.subject_summary())
.finish()
}
}
impl SecCertificate {
/// Creates a `SecCertificate` from DER encoded certificate data.
pub fn from_der(der_data: &[u8]) -> Result<Self> {
let der_data = CFData::from_buffer(der_data);
unsafe {
let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, der_data.as_concrete_TypeRef());
if certificate.is_null() {
Err(Error::from_code(errSecParam))
} else {
Ok(Self::wrap_under_create_rule(certificate))
}
}
}
/// Returns DER encoded data describing this certificate.
#[must_use]
pub fn to_der(&self) -> Vec<u8> {
unsafe {
let der_data = SecCertificateCopyData(self.0);
CFData::wrap_under_create_rule(der_data).to_vec()
}
}
/// Adds a certificate to a keychain.
#[cfg(target_os = "macos")]
pub fn add_to_keychain(&self, keychain: Option<SecKeychain>) -> Result<()> {
let kch = match keychain {
Some(kch) => kch,
_ => SecKeychain::default()?,
};
cvt(unsafe {
SecCertificateAddToKeychain(self.as_CFTypeRef() as *mut _, kch.as_CFTypeRef() as *mut _)
})
}
/// Returns a human readable summary of this certificate.
#[must_use]
pub fn subject_summary(&self) -> String {
unsafe {
let summary = SecCertificateCopySubjectSummary(self.0);
CFString::wrap_under_create_rule(summary).to_string()
}
}
/// Returns a vector of email addresses for the subject of the certificate.
pub fn email_addresses(&self) -> Result<Vec<String>, Error> {
let mut array: CFArrayRef = ptr::null();
unsafe {
cvt(SecCertificateCopyEmailAddresses(
self.as_concrete_TypeRef(),
&mut array,
))?;
let array = CFArray::<CFString>::wrap_under_create_rule(array);
Ok(array.into_iter().map(|p| p.to_string()).collect())
}
}
/// Returns DER encoded X.509 distinguished name of the certificate issuer.
#[must_use]
pub fn issuer(&self) -> Vec<u8> {
unsafe {
let issuer = SecCertificateCopyNormalizedIssuerSequence(self.0);
CFData::wrap_under_create_rule(issuer).to_vec()
}
}
/// Returns DER encoded X.509 distinguished name of the certificate subject.
#[must_use]
pub fn subject(&self) -> Vec<u8> {
unsafe {
let subject = SecCertificateCopyNormalizedSubjectSequence(self.0);
CFData::wrap_under_create_rule(subject).to_vec()
}
}
/// Returns DER encoded serial number of the certificate.
pub fn serial_number_bytes(&self) -> Result<Vec<u8>, CFError> {
unsafe {
let mut error: CFErrorRef = ptr::null_mut();
let serial_number = SecCertificateCopySerialNumberData(self.0, &mut error);
if error.is_null() {
Ok(CFData::wrap_under_create_rule(serial_number).to_vec())
} else {
Err(CFError::wrap_under_create_rule(error))
}
}
}
/// Returns DER encoded subjectPublicKeyInfo of certificate if available. This can be used
/// for certificate pinning.
pub fn public_key_info_der(&self) -> Result<Option<Vec<u8>>> {
// Imported from TrustKit
// https://github.com/datatheorem/TrustKit/blob/master/TrustKit/Pinning/TSKSPKIHashCache.m
let public_key = self.public_key()?;
Ok(self.pk_to_der(public_key))
}
#[must_use]
#[allow(clippy::unused_self)]
#[allow(clippy::needless_pass_by_value)]
fn pk_to_der(&self, public_key: key::SecKey) -> Option<Vec<u8>> {
use security_framework_sys::item::{kSecAttrKeySizeInBits, kSecAttrKeyType};
let public_key_attributes = public_key.attributes();
let public_key_type = public_key_attributes
.find(unsafe { kSecAttrKeyType }.cast::<std::os::raw::c_void>())?;
let public_keysize = public_key_attributes
.find(unsafe { kSecAttrKeySizeInBits }.cast::<std::os::raw::c_void>())?;
let public_keysize = unsafe { CFNumber::from_void(*public_keysize) };
let public_keysize_val = public_keysize.to_i64()? as u32;
let hdr_bytes = get_asn1_header_bytes(
unsafe { CFString::wrap_under_get_rule((*public_key_type).cast()) },
public_keysize_val,
)?;
let public_key_data = public_key.external_representation()?;
let mut out = Vec::with_capacity(hdr_bytes.len() + public_key_data.len() as usize);
out.extend_from_slice(hdr_bytes);
out.extend_from_slice(public_key_data.bytes());
Some(out)
}
/// Get public key from certificate
pub fn public_key(&self) -> Result<key::SecKey> {
use crate::policy::SecPolicy;
use crate::trust::SecTrust;
use std::slice::from_ref;
let policy = SecPolicy::create_x509();
let mut trust = SecTrust::create_with_certificates(from_ref(self), from_ref(&policy))?;
#[allow(deprecated)]
#[cfg(not(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos")))]
trust.evaluate()?;
#[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
cvt(match trust.evaluate_with_error() {
Ok(_) => errSecSuccess,
Err(_) => errSecNotTrusted,
})?;
trust.copy_public_key()
}
/// Translates to `SecItemDelete`, passing in the `SecCertificateRef`
pub fn delete(&self) -> Result<(), Error> {
let query = CFMutableDictionary::from_CFType_pairs(&[(
unsafe { kSecValueRef }.to_void(),
self.to_void(),
)]);
cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) })
}
}
fn get_asn1_header_bytes(pkt: CFString, ksz: u32) -> Option<&'static [u8]> {
use security_framework_sys::item::{kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyTypeRSA};
if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 2048 {
return Some(&RSA_2048_ASN1_HEADER);
}
if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 4096 {
return Some(&RSA_4096_ASN1_HEADER);
}
if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } && ksz == 256 {
return Some(&EC_DSA_SECP_256_R1_ASN1_HEADER);
}
if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } && ksz == 384 {
return Some(&EC_DSA_SECP_384_R1_ASN1_HEADER);
}
None
}
const RSA_2048_ASN1_HEADER: [u8; 24] = [
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
];
const RSA_4096_ASN1_HEADER: [u8; 24] = [
0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00,
];
const EC_DSA_SECP_256_R1_ASN1_HEADER: [u8; 26] = [
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
];
const EC_DSA_SECP_384_R1_ASN1_HEADER: [u8; 23] = [
0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b,
0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00,
];
#[cfg(test)]
mod test {
use crate::test::certificate;
use x509_parser::prelude::*;
#[test]
fn subject_summary() {
let cert = certificate();
assert_eq!("foobar.com", cert.subject_summary());
}
#[test]
fn email_addresses() {
let cert = certificate();
assert_eq!(Vec::<String>::new(), cert.email_addresses().unwrap());
}
#[test]
fn issuer() {
let cert = certificate();
let issuer = cert.issuer();
let (_, name) = X509Name::from_der(&issuer).unwrap();
let name_str = name.to_string_with_registry(oid_registry()).unwrap();
assert_eq!(
"C=US, ST=California, L=Palo Alto, O=Foobar LLC, OU=Dev Land, CN=foobar.com",
name_str
);
}
#[test]
fn subject() {
let cert = certificate();
let subject = cert.subject();
let (_, name) = X509Name::from_der(&subject).unwrap();
let name_str = name.to_string_with_registry(oid_registry()).unwrap();
assert_eq!(
"C=US, ST=California, L=Palo Alto, O=Foobar LLC, OU=Dev Land, CN=foobar.com",
name_str
);
}
}

View File

@@ -0,0 +1,246 @@
//! Cipher Suites supported by Secure Transport
use security_framework_sys::cipher_suite::*;
macro_rules! make_suites {
($($suite:ident),+) => {
/// TLS cipher suites.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct CipherSuite(SSLCipherSuite);
#[allow(missing_docs)]
impl CipherSuite {
$(
pub const $suite: Self = Self($suite);
)+
#[inline(always)]
#[must_use]
pub const fn from_raw(raw: SSLCipherSuite) -> Self {
Self(raw)
}
#[inline(always)]
#[must_use]
pub const fn to_raw(&self) -> SSLCipherSuite {
self.0
}
}
}
}
make_suites! {
// The commented out ones up here are aliases of the matching TLS suites
SSL_NULL_WITH_NULL_NULL,
SSL_RSA_WITH_NULL_MD5,
SSL_RSA_WITH_NULL_SHA,
SSL_RSA_EXPORT_WITH_RC4_40_MD5,
SSL_RSA_WITH_RC4_128_MD5,
SSL_RSA_WITH_RC4_128_SHA,
SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
SSL_RSA_WITH_IDEA_CBC_SHA,
SSL_RSA_EXPORT_WITH_DES40_CBC_SHA,
SSL_RSA_WITH_DES_CBC_SHA,
//SSL_RSA_WITH_3DES_EDE_CBC_SHA,
SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA,
SSL_DH_DSS_WITH_DES_CBC_SHA,
//SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA,
SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,
SSL_DH_RSA_WITH_DES_CBC_SHA,
//SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA,
SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
SSL_DHE_DSS_WITH_DES_CBC_SHA,
//SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
SSL_DHE_RSA_WITH_DES_CBC_SHA,
//SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
SSL_DH_anon_EXPORT_WITH_RC4_40_MD5,
//SSL_DH_anon_WITH_RC4_128_MD5,
SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA,
SSL_DH_anon_WITH_DES_CBC_SHA,
//SSL_DH_anon_WITH_3DES_EDE_CBC_SHA,
SSL_FORTEZZA_DMS_WITH_NULL_SHA,
SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA,
/* TLS addenda using AES, per RFC 3268 */
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_DH_DSS_WITH_AES_128_CBC_SHA,
TLS_DH_RSA_WITH_AES_128_CBC_SHA,
TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
TLS_DH_anon_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_256_CBC_SHA,
TLS_DH_DSS_WITH_AES_256_CBC_SHA,
TLS_DH_RSA_WITH_AES_256_CBC_SHA,
TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
TLS_DH_anon_WITH_AES_256_CBC_SHA,
/* ECDSA addenda, RFC 4492 */
TLS_ECDH_ECDSA_WITH_NULL_SHA,
TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_ECDSA_WITH_NULL_SHA,
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
TLS_ECDH_RSA_WITH_NULL_SHA,
TLS_ECDH_RSA_WITH_RC4_128_SHA,
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_RSA_WITH_NULL_SHA,
TLS_ECDHE_RSA_WITH_RC4_128_SHA,
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
TLS_ECDH_anon_WITH_NULL_SHA,
TLS_ECDH_anon_WITH_RC4_128_SHA,
TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
/* TLS 1.2 addenda, RFC 5246 */
/* Initial state. */
TLS_NULL_WITH_NULL_NULL,
/* Server provided RSA certificate for key exchange. */
TLS_RSA_WITH_NULL_MD5,
TLS_RSA_WITH_NULL_SHA,
TLS_RSA_WITH_RC4_128_MD5,
TLS_RSA_WITH_RC4_128_SHA,
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
//TLS_RSA_WITH_AES_128_CBC_SHA,
//TLS_RSA_WITH_AES_256_CBC_SHA,
TLS_RSA_WITH_NULL_SHA256,
TLS_RSA_WITH_AES_128_CBC_SHA256,
TLS_RSA_WITH_AES_256_CBC_SHA256,
/* Server-authenticated (and optionally client-authenticated) Diffie-Hellman. */
TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA,
TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA,
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
//TLS_DH_DSS_WITH_AES_128_CBC_SHA,
//TLS_DH_RSA_WITH_AES_128_CBC_SHA,
//TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
//TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
//TLS_DH_DSS_WITH_AES_256_CBC_SHA,
//TLS_DH_RSA_WITH_AES_256_CBC_SHA,
//TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
//TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
TLS_DH_DSS_WITH_AES_128_CBC_SHA256,
TLS_DH_RSA_WITH_AES_128_CBC_SHA256,
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
TLS_DH_DSS_WITH_AES_256_CBC_SHA256,
TLS_DH_RSA_WITH_AES_256_CBC_SHA256,
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
/* Completely anonymous Diffie-Hellman */
TLS_DH_anon_WITH_RC4_128_MD5,
TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
//TLS_DH_anon_WITH_AES_128_CBC_SHA,
//TLS_DH_anon_WITH_AES_256_CBC_SHA,
TLS_DH_anon_WITH_AES_128_CBC_SHA256,
TLS_DH_anon_WITH_AES_256_CBC_SHA256,
/* Addendum from RFC 4279, TLS PSK */
TLS_PSK_WITH_RC4_128_SHA,
TLS_PSK_WITH_3DES_EDE_CBC_SHA,
TLS_PSK_WITH_AES_128_CBC_SHA,
TLS_PSK_WITH_AES_256_CBC_SHA,
TLS_DHE_PSK_WITH_RC4_128_SHA,
TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA,
TLS_DHE_PSK_WITH_AES_128_CBC_SHA,
TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
TLS_RSA_PSK_WITH_RC4_128_SHA,
TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
/* RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
TLS_PSK_WITH_NULL_SHA,
TLS_DHE_PSK_WITH_NULL_SHA,
TLS_RSA_PSK_WITH_NULL_SHA,
/* Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites
for TLS. */
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
TLS_DH_RSA_WITH_AES_256_GCM_SHA384,
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,
TLS_DH_DSS_WITH_AES_128_GCM_SHA256,
TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
TLS_DH_anon_WITH_AES_128_GCM_SHA256,
TLS_DH_anon_WITH_AES_256_GCM_SHA384,
/* RFC 5487 - PSK with SHA-256/384 and AES GCM */
TLS_PSK_WITH_AES_128_GCM_SHA256,
TLS_PSK_WITH_AES_256_GCM_SHA384,
TLS_DHE_PSK_WITH_AES_128_GCM_SHA256,
TLS_DHE_PSK_WITH_AES_256_GCM_SHA384,
TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
TLS_PSK_WITH_AES_128_CBC_SHA256,
TLS_PSK_WITH_AES_256_CBC_SHA384,
TLS_PSK_WITH_NULL_SHA256,
TLS_PSK_WITH_NULL_SHA384,
TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
TLS_DHE_PSK_WITH_NULL_SHA256,
TLS_DHE_PSK_WITH_NULL_SHA384,
TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
TLS_RSA_PSK_WITH_NULL_SHA256,
TLS_RSA_PSK_WITH_NULL_SHA384,
/* Addenda from rfc 5289 Elliptic Curve Cipher Suites with
HMAC SHA-256/384. */
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
/* Addenda from rfc 5289 Elliptic Curve Cipher Suites with
SHA-256/384 and AES Galois Counter Mode (GCM) */
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
/* RFC 5746 - Secure Renegotiation */
TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
/*
* Tags for SSL 2 cipher kinds which are not specified
* for SSL 3.
*/
SSL_RSA_WITH_RC2_CBC_MD5,
SSL_RSA_WITH_IDEA_CBC_MD5,
SSL_RSA_WITH_DES_CBC_MD5,
SSL_RSA_WITH_3DES_EDE_CBC_MD5,
SSL_NO_SUCH_CIPHERSUITE
}

632
vendor/security-framework/src/cms.rs vendored Normal file
View File

@@ -0,0 +1,632 @@
//! Cryptographic Message Syntax support
use std::{fmt, ptr};
use core_foundation::array::CFArray;
use core_foundation::base::TCFType;
use core_foundation::data::CFData;
use core_foundation::string::CFString;
use core_foundation_sys::array::CFArrayRef;
use core_foundation_sys::base::{OSStatus, TCFTypeRef};
use core_foundation_sys::data::CFDataRef;
use core_foundation_sys::date::CFAbsoluteTime;
use core_foundation_sys::string::CFStringRef;
use security_framework_sys::cms::*;
use security_framework_sys::trust::SecTrustRef;
use crate::base::Result;
use crate::certificate::SecCertificate;
use crate::cvt;
use crate::policy::SecPolicy;
use crate::trust::SecTrust;
pub use decoder::CMSDecoder;
pub use encoder::cms_encode_content;
pub use encoder::CMSEncoder;
pub use encoder::SignedAttributes;
pub use encoder::CMS_DIGEST_ALGORITHM_SHA1;
pub use encoder::CMS_DIGEST_ALGORITHM_SHA256;
mod encoder {
use super::*;
use crate::identity::SecIdentity;
use core_foundation::{declare_TCFType, impl_TCFType};
/// SHA1 digest algorithm
pub const CMS_DIGEST_ALGORITHM_SHA1: &str = "sha1";
/// SHA256 digest algorithm
pub const CMS_DIGEST_ALGORITHM_SHA256: &str = "sha256";
bitflags::bitflags! {
/// Optional attributes you can add to a signed message
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SignedAttributes: CMSSignedAttributes {
/// Identify signature, encryption, and digest algorithms supported by the encoder
const SMIME_CAPABILITIES = kCMSAttrSmimeCapabilities;
/// Indicate that the signing certificate included with the message is the preferred one for S/MIME encryption
const SMIME_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeEncryptionKeyPrefs;
/// Indicate that the signing certificate included with the message is the preferred one for S/MIME encryption,
/// but using an attribute object identifier (OID) preferred by Microsoft
const SMIME_MS_ENCRYPTION_KEY_PREFS = kCMSAttrSmimeMSEncryptionKeyPrefs;
/// Include the signing time
const SIGNING_TIME = kCMSAttrSigningTime;
/// Include Apple codesigning hash agility
const APPLE_CODESIGNING_HASH_AGILITY = kCMSAttrAppleCodesigningHashAgility;
/// Include Apple codesigning hash agility, version 2
const APPLE_CODESIGNING_HASH_AGILITY_V2 = kCMSAttrAppleCodesigningHashAgilityV2;
/// Include the expiration time
const APPLE_EXPIRATION_TIME = kCMSAttrAppleExpirationTime;
}
}
declare_TCFType! {
/// A type representing CMS encoder
CMSEncoder, CMSEncoderRef
}
impl_TCFType!(CMSEncoder, CMSEncoderRef, CMSEncoderGetTypeID);
unsafe impl Sync for CMSEncoder {}
unsafe impl Send for CMSEncoder {}
impl fmt::Debug for CMSEncoder {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("CMSEncoder").finish()
}
}
impl CMSEncoder {
/// Create a new instance of `CMSEncoder`
pub fn create() -> Result<Self> {
let mut inner: CMSEncoderRef = ptr::null_mut();
cvt(unsafe { CMSEncoderCreate(&mut inner) })?;
Ok(Self(inner))
}
/// Sets the digest algorithm to use for the signer.
/// Can be one of the predefined constants:
///
/// * `CMS_DIGEST_ALGORITHM_SHA1`
/// * `CMS_DIGEST_ALGORITHM_SHA256`
pub fn set_signer_algorithm(&self, digest_algorithm: &str) -> Result<()> {
let alg = CFString::new(digest_algorithm);
cvt(unsafe { CMSEncoderSetSignerAlgorithm(self.0, alg.as_concrete_TypeRef()) })?;
Ok(())
}
/// Specify signers of the CMS message; implies that the message will be signed
pub fn add_signers(&self, signers: &[SecIdentity]) -> Result<()> {
let signers = CFArray::from_CFTypes(signers);
cvt(unsafe {
CMSEncoderAddSigners(
self.0,
if signers.is_empty() { ptr::null() } else { signers.as_CFTypeRef() })
})?;
Ok(())
}
/// Obtains the array of signers specified with the `add_signers` function
pub fn get_signers(&self) -> Result<Vec<SecIdentity>> {
let mut out: CFArrayRef = ptr::null_mut();
cvt(unsafe { CMSEncoderCopySigners(self.0, &mut out) })?;
if out.is_null() {
Ok(Vec::new())
} else {
let array = unsafe { CFArray::<SecIdentity>::wrap_under_create_rule(out) };
Ok(array.into_iter().map(|c| c.clone()).collect())
}
}
/// Specifies a message is to be encrypted and specifies the recipients of the message
pub fn add_recipients(&self, recipients: &[SecCertificate]) -> Result<()> {
let recipients = CFArray::from_CFTypes(recipients);
cvt(unsafe {
CMSEncoderAddRecipients(
self.0,
if recipients.is_empty() { ptr::null() } else { recipients.as_CFTypeRef() },
)
})?;
Ok(())
}
/// Obtains the array of recipients specified with the `add_recipients` function
pub fn get_recipients(&self) -> Result<Vec<SecCertificate>> {
let mut out: CFArrayRef = ptr::null_mut();
cvt(unsafe { CMSEncoderCopyRecipients(self.0, &mut out) })?;
if out.is_null() {
Ok(Vec::new())
} else {
let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
Ok(array.into_iter().map(|c| c.clone()).collect())
}
}
/// Specifies whether the signed data is to be separate from the message
pub fn set_has_detached_content(&self, has_detached_content: bool) -> Result<()> {
cvt(unsafe { CMSEncoderSetHasDetachedContent(self.0, has_detached_content.into()) })?;
Ok(())
}
/// Indicates whether the message is to have detached content
pub fn get_has_detached_content(&self) -> Result<bool> {
let mut has_detached_content = 0;
cvt(unsafe { CMSEncoderGetHasDetachedContent(self.0, &mut has_detached_content) })?;
Ok(has_detached_content != 0)
}
/// Specifies an object identifier for the encapsulated data of a signed message
pub fn set_encapsulated_content_type_oid(&self, oid: &str) -> Result<()> {
let oid = CFString::new(oid);
cvt(unsafe { CMSEncoderSetEncapsulatedContentTypeOID(self.0, oid.as_CFTypeRef()) })?;
Ok(())
}
/// Obtains the object identifier for the encapsulated data of a signed message
pub fn get_encapsulated_content_type(&self) -> Result<Vec<u8>> {
let mut out: CFDataRef = ptr::null_mut();
cvt(unsafe { CMSEncoderCopyEncapsulatedContentType(self.0, &mut out) })?;
Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
}
/// Adds certificates to a message
pub fn add_supporting_certs(&self, certs: &[SecCertificate]) -> Result<()> {
let certs = CFArray::from_CFTypes(certs);
cvt(unsafe {
CMSEncoderAddSupportingCerts(
self.0,
if !certs.is_empty() { certs.as_CFTypeRef() } else { ptr::null() })
})?;
Ok(())
}
/// Obtains the certificates added to a message with `add_supporting_certs`
pub fn get_supporting_certs(&self) -> Result<Vec<SecCertificate>> {
let mut out: CFArrayRef = ptr::null_mut();
cvt(unsafe { CMSEncoderCopySupportingCerts(self.0, &mut out) })?;
if out.is_null() {
Ok(Vec::new())
} else {
let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
Ok(array.into_iter().map(|c| c.clone()).collect())
}
}
/// Specifies attributes for a signed message
pub fn add_signed_attributes(&self, signed_attributes: SignedAttributes) -> Result<()> {
cvt(unsafe { CMSEncoderAddSignedAttributes(self.0, signed_attributes.bits()) })?;
Ok(())
}
/// Specifies which certificates to include in a signed CMS message
pub fn set_certificate_chain_mode(&self, certificate_chain_mode: CMSCertificateChainMode) -> Result<()> {
cvt(unsafe { CMSEncoderSetCertificateChainMode(self.0, certificate_chain_mode) })?;
Ok(())
}
/// Obtains a constant that indicates which certificates are to be included in a signed CMS message
pub fn get_certificate_chain_mode(&self) -> Result<CMSCertificateChainMode> {
let mut out = CMSCertificateChainMode::kCMSCertificateNone;
cvt(unsafe { CMSEncoderGetCertificateChainMode(self.0, &mut out) })?;
Ok(out)
}
/// Feeds content bytes into the encoder
pub fn update_content(&self, content: &[u8]) -> Result<()> {
cvt(unsafe { CMSEncoderUpdateContent(self.0, content.as_ptr().cast(), content.len()) })?;
Ok(())
}
/// Finishes encoding the message and obtains the encoded result
pub fn get_encoded_content(&self) -> Result<Vec<u8>> {
let mut out: CFDataRef = ptr::null_mut();
cvt(unsafe { CMSEncoderCopyEncodedContent(self.0, &mut out) })?;
Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
}
/// Returns the timestamp of a signer of a CMS message, if present
pub fn get_signer_timestamp(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
let mut out = CFAbsoluteTime::default();
cvt(unsafe { CMSEncoderCopySignerTimestamp(self.0, signer_index, &mut out) })?;
Ok(out)
}
/// Returns the timestamp of a signer of a CMS message using a particular policy, if present
pub fn get_signer_timestamp_with_policy(
&self,
timestamp_policy: Option<CFStringRef>,
signer_index: usize,
) -> Result<CFAbsoluteTime> {
let mut out = CFAbsoluteTime::default();
cvt(unsafe {
CMSEncoderCopySignerTimestampWithPolicy(
self.0,
timestamp_policy.map(|p| p.as_void_ptr()).unwrap_or(ptr::null()),
signer_index,
&mut out,
)
})?;
Ok(out)
}
}
/// Encodes a message and obtains the result in one high-level function call
pub fn cms_encode_content(
signers: &[SecIdentity],
recipients: &[SecCertificate],
content_type_oid: Option<&str>,
detached_content: bool,
signed_attributes: SignedAttributes,
content: &[u8],
) -> Result<Vec<u8>> {
let mut out: CFDataRef = ptr::null_mut();
let signers = CFArray::from_CFTypes(signers);
let recipients = CFArray::from_CFTypes(recipients);
let content_type_oid = content_type_oid.map(CFString::new);
cvt(unsafe {
CMSEncodeContent(
if signers.is_empty() { ptr::null() } else { signers.as_CFTypeRef() },
if recipients.is_empty() { ptr::null() } else { recipients.as_CFTypeRef() },
content_type_oid.as_ref().map(|oid| oid.as_CFTypeRef()).unwrap_or(ptr::null()),
detached_content.into(),
signed_attributes.bits(),
content.as_ptr().cast(),
content.len(),
&mut out,
)
})?;
Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
}
}
mod decoder {
use super::*;
use core_foundation::{declare_TCFType, impl_TCFType};
/// Holds a result of the `CMSDecoder::get_signer_status` function
pub struct SignerStatus {
/// Signature status
pub signer_status: CMSSignerStatus,
/// Trust instance that was used to verify the signers certificate
pub sec_trust: SecTrust,
/// Result of the certificate verification
pub cert_verify_result: Result<()>,
}
declare_TCFType! {
/// A type representing CMS Decoder
CMSDecoder, CMSDecoderRef
}
impl_TCFType!(CMSDecoder, CMSDecoderRef, CMSDecoderGetTypeID);
unsafe impl Sync for CMSDecoder {}
unsafe impl Send for CMSDecoder {}
impl fmt::Debug for CMSDecoder {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("CMSDecoder").finish()
}
}
impl CMSDecoder {
/// Create a new instance of `CMSDecoder`
pub fn create() -> Result<Self> {
let mut inner: CMSDecoderRef = ptr::null_mut();
cvt(unsafe { CMSDecoderCreate(&mut inner) })?;
Ok(Self(inner))
}
/// Feeds raw bytes of the message to be decoded into the decoder
pub fn update_message(&self, message: &[u8]) -> Result<()> {
cvt(unsafe { CMSDecoderUpdateMessage(self.0, message.as_ptr().cast(), message.len()) })?;
Ok(())
}
/// Indicates that there is no more data to decode
pub fn finalize_message(&self) -> Result<()> {
cvt(unsafe { CMSDecoderFinalizeMessage(self.0) })?;
Ok(())
}
/// Specifies the messages detached content, if any
pub fn set_detached_content(&self, detached_content: &[u8]) -> Result<()> {
let data = CFData::from_buffer(detached_content);
cvt(unsafe { CMSDecoderSetDetachedContent(self.0, data.as_concrete_TypeRef()) })?;
Ok(())
}
/// Obtains the detached content specified with the `set_detached_content` function
pub fn get_detached_content(&self) -> Result<Vec<u8>> {
unsafe {
let mut out: CFDataRef = ptr::null_mut();
cvt(CMSDecoderCopyDetachedContent(self.0, &mut out))?;
if out.is_null() {
Ok(Vec::new())
} else {
Ok(CFData::wrap_under_create_rule(out).to_vec())
}
}
}
/// Obtains the number of signers of a message
pub fn get_num_signers(&self) -> Result<usize> {
let mut out = 0;
cvt(unsafe { CMSDecoderGetNumSigners(self.0, &mut out) })?;
Ok(out)
}
/// Obtains the status of a CMS messages signature
pub fn get_signer_status(
&self,
signer_index: usize,
policies: &[SecPolicy],
) -> Result<SignerStatus> {
let policies = CFArray::from_CFTypes(policies);
let mut signer_status = CMSSignerStatus::kCMSSignerUnsigned;
let mut sec_trust: SecTrustRef = ptr::null_mut();
let mut verify_result = OSStatus::default();
cvt(unsafe {
CMSDecoderCopySignerStatus(
self.0,
signer_index,
if policies.is_empty() { ptr::null() } else { policies.as_CFTypeRef() },
true.into(),
&mut signer_status,
&mut sec_trust,
&mut verify_result,
)
})?;
Ok(SignerStatus {
signer_status,
sec_trust: unsafe { SecTrust::wrap_under_create_rule(sec_trust) },
cert_verify_result: cvt(verify_result),
})
}
/// Obtains the email address of the specified signer of a CMS message
pub fn get_signer_email_address(&self, signer_index: usize) -> Result<String> {
let mut out: CFStringRef = ptr::null_mut();
cvt(unsafe { CMSDecoderCopySignerEmailAddress(self.0, signer_index, &mut out) })?;
Ok(unsafe { CFString::wrap_under_create_rule(out).to_string() })
}
/// Determines whether a CMS message was encrypted
pub fn is_content_encrypted(&self) -> Result<bool> {
let mut out = 0;
cvt(unsafe { CMSDecoderIsContentEncrypted(self.0, &mut out) })?;
Ok(out != 0)
}
/// Obtains the object identifier for the encapsulated data of a signed message
pub fn get_encapsulated_content_type(&self) -> Result<Vec<u8>> {
let mut out: CFDataRef = ptr::null_mut();
if out.is_null() {
Ok(Vec::new())
} else {
cvt(unsafe { CMSDecoderCopyEncapsulatedContentType(self.0, &mut out) })?;
Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
}
}
/// Obtains an array of all of the certificates in a message
pub fn get_all_certs(&self) -> Result<Vec<SecCertificate>> {
let mut out: CFArrayRef = ptr::null_mut();
cvt(unsafe { CMSDecoderCopyAllCerts(self.0, &mut out) })?;
if out.is_null() {
Ok(Vec::new())
} else {
let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
Ok(array.into_iter().map(|c| c.clone()).collect())
}
}
/// Obtains the message content, if any
pub fn get_content(&self) -> Result<Vec<u8>> {
let mut out: CFDataRef = ptr::null_mut();
cvt(unsafe { CMSDecoderCopyContent(self.0, &mut out) })?;
if out.is_null() {
Ok(Vec::new())
} else {
Ok(unsafe { CFData::wrap_under_create_rule(out).to_vec() })
}
}
/// Obtains the signing time of a CMS message, if present
pub fn get_signer_signing_time(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
let mut out = CFAbsoluteTime::default();
cvt(unsafe { CMSDecoderCopySignerSigningTime(self.0, signer_index, &mut out) })?;
Ok(out)
}
/// Returns the timestamp of a signer of a CMS message, if present
pub fn get_signer_timestamp(&self, signer_index: usize) -> Result<CFAbsoluteTime> {
let mut out = CFAbsoluteTime::default();
cvt(unsafe { CMSDecoderCopySignerTimestamp(self.0, signer_index, &mut out) })?;
Ok(out)
}
/// Returns the timestamp of a signer of a CMS message using a given policy, if present
pub fn get_signer_timestamp_with_policy(
&self,
timestamp_policy: Option<CFStringRef>,
signer_index: usize,
) -> Result<CFAbsoluteTime> {
let mut out = CFAbsoluteTime::default();
cvt(unsafe {
CMSDecoderCopySignerTimestampWithPolicy(
self.0,
timestamp_policy.map(|p| p.as_void_ptr()).unwrap_or(ptr::null()),
signer_index,
&mut out,
)
})?;
Ok(out)
}
/// Returns an array containing the certificates from a timestamp response
pub fn get_signer_timestamp_certificates(&self, signer_index: usize) -> Result<Vec<SecCertificate>> {
let mut out: CFArrayRef = ptr::null_mut();
cvt(unsafe { CMSDecoderCopySignerTimestampCertificates(self.0, signer_index, &mut out) })?;
if out.is_null() {
Ok(Vec::new())
} else {
let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(out) };
Ok(array.into_iter().map(|c| c.clone()).collect())
}
}
}
}
#[cfg(test)]
mod tests {
use crate::cms::{cms_encode_content, CMSDecoder, SignedAttributes};
use crate::import_export::{ImportedIdentity, Pkcs12ImportOptions};
use crate::policy::SecPolicy;
use security_framework_sys::cms::CMSSignerStatus;
use std::sync::OnceLock;
const KEYSTORE: &[u8] = include_bytes!("../test/cms/keystore.p12");
const ENCRYPTED_CMS: &[u8] = include_bytes!("../test/cms/encrypted.p7m");
const SIGNED_ENCRYPTED_CMS: &[u8] = include_bytes!("../test/cms/signed-encrypted.p7m");
static KEYSTORE_IDENTITY: OnceLock<Vec<ImportedIdentity>> = OnceLock::new();
fn import_keystore() -> &'static [ImportedIdentity] {
KEYSTORE_IDENTITY.get_or_init(|| {
let mut import_opts = Pkcs12ImportOptions::new();
import_opts.passphrase("cms").import(KEYSTORE).expect("import keystore.p12")
})
}
#[test]
fn test_decode_encrypted_with_keystore_identities() {
let _ = import_keystore();
let decoder = CMSDecoder::create().expect("create");
decoder.update_message(ENCRYPTED_CMS).expect("update");
decoder.finalize_message().expect("finalize");
assert!(decoder.is_content_encrypted().unwrap());
assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
assert_eq!(decoder.get_all_certs().unwrap().len(), 0);
assert_eq!(decoder.get_num_signers().unwrap(), 0);
}
#[test]
fn test_decode_signed_and_encrypted_with_keystore_identities() {
let _ = import_keystore();
let decoder = CMSDecoder::create().unwrap();
decoder.update_message(SIGNED_ENCRYPTED_CMS).unwrap();
decoder.finalize_message().unwrap();
assert!(decoder.is_content_encrypted().unwrap());
let signed_content = decoder.get_content().unwrap();
let decoder2 = CMSDecoder::create().unwrap();
decoder2.update_message(&signed_content).unwrap();
decoder2.finalize_message().unwrap();
assert_eq!(decoder2.get_content().unwrap(), b"encrypted message\n");
assert_eq!(decoder2.get_num_signers().unwrap(), 1);
let policies = vec![SecPolicy::create_x509()];
let status = decoder2.get_signer_status(0, &policies).unwrap();
assert!(status.cert_verify_result.is_err());
assert_eq!(status.signer_status, CMSSignerStatus::kCMSSignerInvalidCert);
}
#[test]
fn test_encode_encrypted_with_keystore_identities() {
let identities = import_keystore();
let chain = identities
.iter().find_map(|id| id.cert_chain.as_ref())
.unwrap();
let message = cms_encode_content(
&[],
&chain[0..1],
None,
false,
SignedAttributes::empty(),
b"encrypted message\n",
).unwrap();
let decoder = CMSDecoder::create().unwrap();
decoder.update_message(&message).unwrap();
decoder.finalize_message().unwrap();
assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
}
#[test]
fn test_encode_signed_encrypted_with_keystore_identities() {
let identities = import_keystore();
let chain = identities
.iter().find_map(|id| id.cert_chain.as_ref())
.unwrap();
let identity = identities
.iter().find_map(|id| id.identity.as_ref())
.unwrap();
let message = cms_encode_content(
std::slice::from_ref(identity),
&chain[0..1],
None,
false,
SignedAttributes::empty(),
b"encrypted message\n",
).unwrap();
let decoder = CMSDecoder::create().unwrap();
decoder.update_message(&message).unwrap();
decoder.finalize_message().unwrap();
assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
assert_eq!(decoder.get_num_signers().unwrap(), 1);
}
#[test]
fn test_encode_with_cms_encoder_with_keystore_identities() {
let identities = import_keystore();
let chain = identities
.iter().find_map(|id| id.cert_chain.as_ref())
.unwrap();
let identity = identities
.iter().find_map(|id| id.identity.as_ref())
.unwrap();
let message = cms_encode_content(
std::slice::from_ref(identity),
&chain[0..1],
None,
false,
SignedAttributes::empty(),
b"encrypted message\n",
).unwrap();
let decoder = CMSDecoder::create().unwrap();
decoder.update_message(&message).unwrap();
decoder.finalize_message().unwrap();
assert_eq!(decoder.get_content().unwrap(), b"encrypted message\n");
assert_eq!(decoder.get_num_signers().unwrap(), 1);
}
}

View File

@@ -0,0 +1,84 @@
//! Identity support.
use core_foundation::base::{TCFType, ToVoid};
use core_foundation::dictionary::CFMutableDictionary;
use core_foundation::{declare_TCFType, impl_TCFType};
use security_framework_sys::base::SecIdentityRef;
use security_framework_sys::identity::{
SecIdentityCopyCertificate, SecIdentityCopyPrivateKey, SecIdentityGetTypeID,
};
use security_framework_sys::item::kSecValueRef;
use security_framework_sys::keychain_item::SecItemDelete;
use std::fmt;
use std::ptr;
use crate::base::{Error, Result};
use crate::certificate::SecCertificate;
use crate::cvt;
use crate::key::SecKey;
declare_TCFType! {
/// A type representing an identity.
///
/// Identities are a certificate paired with the corresponding private key.
SecIdentity, SecIdentityRef
}
impl_TCFType!(SecIdentity, SecIdentityRef, SecIdentityGetTypeID);
unsafe impl Sync for SecIdentity {}
unsafe impl Send for SecIdentity {}
impl fmt::Debug for SecIdentity {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = fmt.debug_struct("SecIdentity");
if let Ok(cert) = self.certificate() {
builder.field("certificate", &cert);
}
if let Ok(key) = self.private_key() {
builder.field("private_key", &key);
}
builder.finish()
}
}
impl SecIdentity {
/// Returns the certificate corresponding to this identity.
pub fn certificate(&self) -> Result<SecCertificate> {
unsafe {
let mut certificate = ptr::null_mut();
cvt(SecIdentityCopyCertificate(self.0, &mut certificate))?;
Ok(SecCertificate::wrap_under_create_rule(certificate))
}
}
/// Returns the private key corresponding to this identity.
pub fn private_key(&self) -> Result<SecKey> {
unsafe {
let mut key = ptr::null_mut();
cvt(SecIdentityCopyPrivateKey(self.0, &mut key))?;
Ok(SecKey::wrap_under_create_rule(key))
}
}
/// Translates to `SecItemDelete`, passing in the `SecIdentityRef`
pub fn delete(&self) -> Result<(), Error> {
let query = CFMutableDictionary::from_CFType_pairs(&[(
unsafe { kSecValueRef }.to_void(),
self.to_void(),
)]);
cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) })
}
}
#[cfg(test)]
mod test {
use super::SecIdentity;
#[test]
fn identity_has_send_bound() {
fn assert_send<T: Send>() {}
assert_send::<SecIdentity>();
}
}

View File

@@ -0,0 +1,178 @@
//! Security Framework type import/export support.
use core_foundation::array::CFArray;
use core_foundation::base::{CFType, TCFType};
use core_foundation::data::CFData;
use core_foundation::dictionary::CFDictionary;
use core_foundation::string::CFString;
use security_framework_sys::import_export::*;
use std::ptr;
use crate::base::Result;
use crate::certificate::SecCertificate;
use crate::cvt;
use crate::identity::SecIdentity;
#[cfg(target_os = "macos")]
use crate::os::macos::access::SecAccess;
#[cfg(target_os = "macos")]
use crate::os::macos::keychain::SecKeychain;
use crate::trust::SecTrust;
/// Information about an imported identity.
#[derive(Clone)]
#[non_exhaustive]
pub struct ImportedIdentity {
/// The label of the identity.
pub label: Option<String>,
/// The ID of the identity. Typically the SHA-1 hash of the public key.
pub key_id: Option<Vec<u8>>,
/// A `SecTrust` object set up to validate this identity.
pub trust: Option<SecTrust>,
/// A certificate chain validating this identity.
pub cert_chain: Option<Vec<SecCertificate>>,
/// The identity itself.
pub identity: Option<SecIdentity>,
}
/// A builder type to import an identity from PKCS#12 formatted data.
#[derive(Default)]
pub struct Pkcs12ImportOptions {
passphrase: Option<CFString>,
#[cfg(target_os = "macos")]
keychain: Option<SecKeychain>,
#[cfg(target_os = "macos")]
access: Option<SecAccess>,
}
#[cfg(target_os = "macos")]
impl Pkcs12ImportOptions {
/// Specifies macOS keychain in which to import the identity.
///
/// If this is not called, the default keychain will be used.
#[inline(always)]
pub fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
self.keychain = Some(keychain);
self
}
/// Specifies the access control to be associated with the identity. macOS only.
#[inline(always)]
pub fn access(&mut self, access: SecAccess) -> &mut Self {
self.access = Some(access);
self
}
}
impl Pkcs12ImportOptions {
/// Creates a new builder with default options.
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Specifies the passphrase to be used to decrypt the data.
///
/// This must be specified, as unencrypted PKCS#12 data is not supported.
#[inline]
pub fn passphrase(&mut self, passphrase: &str) -> &mut Self {
self.passphrase = Some(CFString::new(passphrase));
self
}
/// Imports identities from PKCS#12 encoded data.
pub fn import(&self, pkcs12_data: &[u8]) -> Result<Vec<ImportedIdentity>> {
unsafe {
let pkcs12_data = CFData::from_buffer(pkcs12_data);
let mut options = vec![];
if let Some(passphrase) = &self.passphrase {
options.push((
CFString::wrap_under_get_rule(kSecImportExportPassphrase),
passphrase.as_CFType(),
));
}
self.import_setup(&mut options);
let options = CFDictionary::from_CFType_pairs(&options);
let mut raw_items = ptr::null();
cvt(SecPKCS12Import(
pkcs12_data.as_concrete_TypeRef(),
options.as_concrete_TypeRef(),
&mut raw_items,
))?;
let raw_items = CFArray::<CFDictionary<CFString, *const _>>::wrap_under_create_rule(raw_items);
let mut items = vec![];
for raw_item in &raw_items {
let label = raw_item
.find(kSecImportItemLabel)
.map(|label| CFString::wrap_under_get_rule((*label).cast()).to_string());
let key_id = raw_item
.find(kSecImportItemKeyID)
.map(|key_id| CFData::wrap_under_get_rule((*key_id).cast()).to_vec());
let trust = raw_item
.find(kSecImportItemTrust)
.map(|trust| SecTrust::wrap_under_get_rule(*trust as *mut _));
let cert_chain = raw_item
.find(kSecImportItemCertChain)
.map(|cert_chain| {
CFArray::<SecCertificate>::wrap_under_get_rule((*cert_chain).cast())
.iter()
.map(|c| c.clone())
.collect()
});
let identity = raw_item
.find(kSecImportItemIdentity)
.map(|identity| SecIdentity::wrap_under_get_rule(*identity as *mut _));
items.push(ImportedIdentity {
label,
key_id,
trust,
cert_chain,
identity,
});
}
Ok(items)
}
}
#[cfg(target_os = "macos")]
fn import_setup(&self, options: &mut Vec<(CFString, CFType)>) {
unsafe {
if let Some(keychain) = &self.keychain {
options.push((
CFString::wrap_under_get_rule(kSecImportExportKeychain),
keychain.as_CFType(),
));
}
if let Some(access) = &self.access {
options.push((
CFString::wrap_under_get_rule(kSecImportExportAccess),
access.as_CFType(),
));
}
}
}
#[cfg(not(target_os = "macos"))]
fn import_setup(&self, _: &mut Vec<(CFString, CFType)>) {}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn missing_passphrase() {
let data = include_bytes!("../test/server.p12");
assert!(Pkcs12ImportOptions::new().import(data).is_err());
}
}

1128
vendor/security-framework/src/item.rs vendored Normal file

File diff suppressed because it is too large Load Diff

505
vendor/security-framework/src/key.rs vendored Normal file
View File

@@ -0,0 +1,505 @@
//! Encryption key support
use core_foundation::{declare_TCFType, impl_TCFType};
use crate::cvt;
use core_foundation::{
base::TCFType, string::{CFStringRef, CFString},
dictionary::CFMutableDictionary,
};
use core_foundation::base::ToVoid;
use core_foundation::boolean::CFBoolean;
use core_foundation::data::CFData;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::error::{CFError, CFErrorRef};
use security_framework_sys::{
item::{kSecAttrKeyTypeRSA, kSecValueRef},
keychain_item::SecItemDelete,
};
use security_framework_sys::{item::{
kSecAttrIsPermanent, kSecAttrLabel, kSecAttrKeyType,
kSecAttrKeySizeInBits, kSecAttrAccessControl
}};
#[cfg(target_os = "macos")]
use security_framework_sys::item::{
kSecAttrKeyType3DES, kSecAttrKeyTypeDSA, kSecAttrKeyTypeAES,
kSecAttrKeyTypeDES, kSecAttrKeyTypeRC4, kSecAttrKeyTypeCAST,
};
use security_framework_sys::base::SecKeyRef;
use security_framework_sys::key::SecKeyGetTypeID;
pub use security_framework_sys::key::Algorithm;
use security_framework_sys::key::{
SecKeyCopyAttributes, SecKeyCopyExternalRepresentation,
SecKeyCreateSignature, SecKeyCreateRandomKey,
SecKeyCopyPublicKey,
SecKeyCreateDecryptedData, SecKeyCreateEncryptedData,
};
use security_framework_sys::item::kSecAttrApplicationLabel;
use std::fmt;
use crate::base::Error;
use crate::item::Location;
use crate::access_control::SecAccessControl;
/// Types of `SecKey`s.
#[derive(Debug, Copy, Clone)]
pub struct KeyType(CFStringRef);
#[allow(missing_docs)]
impl KeyType {
#[inline(always)]
#[must_use]
pub fn rsa() -> Self {
unsafe { Self(kSecAttrKeyTypeRSA) }
}
#[cfg(target_os = "macos")]
#[inline(always)]
#[must_use]
pub fn dsa() -> Self {
unsafe { Self(kSecAttrKeyTypeDSA) }
}
#[cfg(target_os = "macos")]
#[inline(always)]
#[must_use]
pub fn aes() -> Self {
unsafe { Self(kSecAttrKeyTypeAES) }
}
#[cfg(target_os = "macos")]
#[inline(always)]
#[must_use]
pub fn des() -> Self {
unsafe { Self(kSecAttrKeyTypeDES) }
}
#[cfg(target_os = "macos")]
#[inline(always)]
#[must_use]
pub fn triple_des() -> Self {
unsafe { Self(kSecAttrKeyType3DES) }
}
#[cfg(target_os = "macos")]
#[inline(always)]
#[must_use]
pub fn rc4() -> Self {
unsafe { Self(kSecAttrKeyTypeRC4) }
}
#[cfg(target_os = "macos")]
#[inline(always)]
#[must_use]
pub fn cast() -> Self {
unsafe { Self(kSecAttrKeyTypeCAST) }
}
#[inline(always)]
#[must_use]
pub fn ec() -> Self {
use security_framework_sys::item::kSecAttrKeyTypeEC;
unsafe { Self(kSecAttrKeyTypeEC) }
}
#[inline(always)]
#[must_use]
pub fn ec_sec_prime_random() -> Self {
use security_framework_sys::item::kSecAttrKeyTypeECSECPrimeRandom;
unsafe { Self(kSecAttrKeyTypeECSECPrimeRandom) }
}
pub(crate) fn to_str(self) -> CFString {
unsafe { CFString::wrap_under_get_rule(self.0) }
}
}
declare_TCFType! {
/// A type representing an encryption key.
SecKey, SecKeyRef
}
impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID);
unsafe impl Sync for SecKey {}
unsafe impl Send for SecKey {}
impl SecKey {
/// Translates to `SecKeyCreateRandomKey`
#[allow(deprecated)]
#[doc(alias = "SecKeyCreateRandomKey")]
pub fn new(options: &GenerateKeyOptions) -> Result<Self, CFError> {
Self::generate(options.to_dictionary())
}
/// Translates to `SecKeyCreateRandomKey`
/// `GenerateKeyOptions` provides a helper to create an attribute `CFDictionary`.
#[deprecated(note = "Use SecKey::new")]
pub fn generate(attributes: CFDictionary) -> Result<Self, CFError> {
let mut error: CFErrorRef = ::std::ptr::null_mut();
let sec_key = unsafe { SecKeyCreateRandomKey(attributes.as_concrete_TypeRef(), &mut error) };
if error.is_null() {
Ok(unsafe { Self::wrap_under_create_rule(sec_key) })
} else {
Err(unsafe { CFError::wrap_under_create_rule(error) })
}
}
/// Returns the programmatic identifier for the key. For keys of class
/// kSecAttrKeyClassPublic and kSecAttrKeyClassPrivate, the value is the
/// hash of the public key.
#[must_use]
pub fn application_label(&self) -> Option<Vec<u8>> {
self.attributes()
.find(unsafe { kSecAttrApplicationLabel.to_void() })
.map(|v| unsafe { CFData::wrap_under_get_rule(v.cast()) }.to_vec())
}
/// Translates to `SecKeyCopyAttributes`
// TODO: deprecate and remove. CFDictionary should not be exposed in public Rust APIs.
#[must_use]
pub fn attributes(&self) -> CFDictionary {
let pka = unsafe { SecKeyCopyAttributes(self.to_void() as _) };
unsafe { CFDictionary::wrap_under_create_rule(pka) }
}
/// Translates to `SecKeyCopyExternalRepresentation`
// TODO: deprecate and remove. CFData should not be exposed in public Rust APIs.
#[must_use]
pub fn external_representation(&self) -> Option<CFData> {
let mut error: CFErrorRef = ::std::ptr::null_mut();
let data = unsafe { SecKeyCopyExternalRepresentation(self.to_void() as _, &mut error) };
if data.is_null() {
return None;
}
Some(unsafe { CFData::wrap_under_create_rule(data) })
}
/// Translates to `SecKeyCopyPublicKey`
#[must_use]
pub fn public_key(&self) -> Option<Self> {
let pub_seckey = unsafe { SecKeyCopyPublicKey(self.0.cast()) };
if pub_seckey.is_null() {
return None;
}
Some(unsafe { Self::wrap_under_create_rule(pub_seckey) })
}
/// Encrypts a block of data using a public key and specified algorithm
pub fn encrypt_data(&self, algorithm: Algorithm, input: &[u8]) -> Result<Vec<u8>, CFError> {
let mut error: CFErrorRef = std::ptr::null_mut();
let output = unsafe {
SecKeyCreateEncryptedData(self.as_concrete_TypeRef(), algorithm.into(), CFData::from_buffer(input).as_concrete_TypeRef(), &mut error)
};
if error.is_null() {
let output = unsafe { CFData::wrap_under_create_rule(output) };
Ok(output.to_vec())
} else {
Err(unsafe { CFError::wrap_under_create_rule(error) })
}
}
/// Decrypts a block of data using a private key and specified algorithm
pub fn decrypt_data(&self, algorithm: Algorithm, input: &[u8]) -> Result<Vec<u8>, CFError> {
let mut error: CFErrorRef = std::ptr::null_mut();
let output = unsafe {
SecKeyCreateDecryptedData(self.as_concrete_TypeRef(), algorithm.into(), CFData::from_buffer(input).as_concrete_TypeRef(), &mut error)
};
if error.is_null() {
let output = unsafe { CFData::wrap_under_create_rule(output) };
Ok(output.to_vec())
} else {
Err(unsafe { CFError::wrap_under_create_rule(error) })
}
}
/// Creates the cryptographic signature for a block of data using a private
/// key and specified algorithm.
pub fn create_signature(&self, algorithm: Algorithm, input: &[u8]) -> Result<Vec<u8>, CFError> {
let mut error: CFErrorRef = std::ptr::null_mut();
let output = unsafe {
SecKeyCreateSignature(
self.as_concrete_TypeRef(),
algorithm.into(),
CFData::from_buffer(input).as_concrete_TypeRef(),
&mut error,
)
};
if error.is_null() {
let output = unsafe { CFData::wrap_under_create_rule(output) };
Ok(output.to_vec())
} else {
Err(unsafe { CFError::wrap_under_create_rule(error) })
}
}
/// Verifies the cryptographic signature for a block of data using a public
/// key and specified algorithm.
pub fn verify_signature(&self, algorithm: Algorithm, signed_data: &[u8], signature: &[u8]) -> Result<bool, CFError> {
use security_framework_sys::key::SecKeyVerifySignature;
let mut error: CFErrorRef = std::ptr::null_mut();
let valid = unsafe {
SecKeyVerifySignature(
self.as_concrete_TypeRef(),
algorithm.into(),
CFData::from_buffer(signed_data).as_concrete_TypeRef(),
CFData::from_buffer(signature).as_concrete_TypeRef(),
&mut error,
)
};
if !error.is_null() {
return Err(unsafe { CFError::wrap_under_create_rule(error) })?;
}
Ok(valid != 0)
}
/// Performs the Diffie-Hellman style of key exchange.
pub fn key_exchange(
&self,
algorithm: Algorithm,
public_key: &Self,
requested_size: usize,
shared_info: Option<&[u8]>,
) -> Result<Vec<u8>, CFError> {
use core_foundation::data::CFData;
use security_framework_sys::item::{
kSecKeyKeyExchangeParameterRequestedSize, kSecKeyKeyExchangeParameterSharedInfo,
};
unsafe {
let mut params = vec![(
CFString::wrap_under_get_rule(kSecKeyKeyExchangeParameterRequestedSize),
CFNumber::from(requested_size as i64).into_CFType(),
)];
if let Some(shared_info) = shared_info {
params.push((
CFString::wrap_under_get_rule(kSecKeyKeyExchangeParameterSharedInfo),
CFData::from_buffer(shared_info).as_CFType(),
));
}
let parameters = CFDictionary::from_CFType_pairs(&params);
let mut error: CFErrorRef = std::ptr::null_mut();
let output = security_framework_sys::key::SecKeyCopyKeyExchangeResult(
self.as_concrete_TypeRef(),
algorithm.into(),
public_key.as_concrete_TypeRef(),
parameters.as_concrete_TypeRef(),
&mut error,
);
if error.is_null() {
let output = CFData::wrap_under_create_rule(output);
Ok(output.to_vec())
} else {
Err(CFError::wrap_under_create_rule(error))
}
}
}
/// Translates to `SecItemDelete`, passing in the `SecKeyRef`
pub fn delete(&self) -> Result<(), Error> {
let query = CFMutableDictionary::from_CFType_pairs(&[(
unsafe { kSecValueRef }.to_void(),
self.to_void(),
)]);
cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) })
}
}
/// Where to generate the key.
#[derive(Debug)]
pub enum Token {
/// Generate the key in software, compatible with all `KeyType`s.
Software,
/// Generate the key in the Secure Enclave such that the private key is not
/// extractable. Only compatible with `KeyType::ec()`.
SecureEnclave,
}
/// Helper for creating `CFDictionary` attributes for `SecKey::generate`
/// Recommended reading:
/// <https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains>
#[derive(Debug, Default)]
pub struct GenerateKeyOptions {
/// kSecAttrKeyType
#[deprecated(note = "use set_key_type()")]
pub key_type: Option<KeyType>,
/// kSecAttrKeySizeInBits
#[deprecated(note = "use set_size_in_bits()")]
pub size_in_bits: Option<u32>,
/// kSecAttrLabel
#[deprecated(note = "use set_label()")]
pub label: Option<String>,
/// kSecAttrTokenID
#[deprecated(note = "use set_token()")]
pub token: Option<Token>,
/// Which keychain to store the key in, if any.
#[deprecated(note = "use set_location()")]
pub location: Option<Location>,
/// Access control
#[deprecated(note = "use set_access_control()")]
pub access_control: Option<SecAccessControl>,
/// `kSecAttrSynchronizable`
#[cfg(feature = "sync-keychain")]
synchronizable: Option<bool>,
}
#[allow(deprecated)]
impl GenerateKeyOptions {
/// Set `key_type`
pub fn set_key_type(&mut self, key_type: KeyType) -> &mut Self {
self.key_type = Some(key_type);
self
}
/// Set `size_in_bits`
pub fn set_size_in_bits(&mut self, size_in_bits: u32) -> &mut Self {
self.size_in_bits = Some(size_in_bits);
self
}
/// Set `label`
pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self {
self.label = Some(label.into());
self
}
/// Set `token`
pub fn set_token(&mut self, token: Token) -> &mut Self {
self.token = Some(token);
self
}
/// Set `location`
pub fn set_location(&mut self, location: Location) -> &mut Self {
self.location = Some(location);
self
}
/// Set `access_control`
pub fn set_access_control(&mut self, access_control: SecAccessControl) -> &mut Self {
self.access_control = Some(access_control);
self
}
/// Set `synchronizable` (`kSecAttrSynchronizable`)
#[cfg(feature = "sync-keychain")]
pub fn set_synchronizable(&mut self, synchronizable: bool) -> &mut Self {
self.synchronizable = Some(synchronizable);
self
}
/// Collect options into a `CFDictioanry`
// CFDictionary should not be exposed in public Rust APIs.
#[deprecated(note = "Pass the options to SecKey::new")]
pub fn to_dictionary(&self) -> CFDictionary {
use security_framework_sys::item::{kSecAttrTokenID, kSecAttrTokenIDSecureEnclave};
let is_permanent = CFBoolean::from(self.location.is_some());
let mut private_attributes = CFMutableDictionary::from_CFType_pairs(&[(
unsafe { kSecAttrIsPermanent }.to_void(),
is_permanent.to_void(),
)]);
if let Some(access_control) = &self.access_control {
private_attributes.set(unsafe { kSecAttrAccessControl }.to_void(), access_control.to_void());
}
#[cfg(target_os = "macos")]
let public_attributes = CFMutableDictionary::from_CFType_pairs(&[(
unsafe { kSecAttrIsPermanent }.to_void(),
is_permanent.to_void(),
)]);
let key_type = self.key_type.unwrap_or_else(KeyType::rsa).to_str();
let size_in_bits = self.size_in_bits.unwrap_or(match () {
#[cfg(target_os = "macos")]
() if key_type == KeyType::aes().to_str() => 256,
() if key_type == KeyType::rsa().to_str() => 2048,
() if key_type == KeyType::ec().to_str() => 256,
() if key_type == KeyType::ec_sec_prime_random().to_str() => 256,
() => 256,
});
let size_in_bits = CFNumber::from(size_in_bits as i32);
let mut attribute_key_values = vec![
(unsafe { kSecAttrKeyType }.to_void(), key_type.to_void()),
(unsafe { kSecAttrKeySizeInBits }.to_void(), size_in_bits.to_void()),
];
#[cfg(target_os = "macos")]
if key_type != KeyType::aes().to_str() {
attribute_key_values.push((unsafe { security_framework_sys::item::kSecPublicKeyAttrs }.to_void(), public_attributes.to_void()));
attribute_key_values.push((unsafe { security_framework_sys::item::kSecPrivateKeyAttrs }.to_void(), private_attributes.to_void()));
}
let label = self.label.as_deref().map(CFString::new);
if let Some(label) = &label {
attribute_key_values.push((unsafe { kSecAttrLabel }.to_void(), label.to_void()));
}
#[cfg(target_os = "macos")]
match &self.location {
#[cfg(feature = "OSX_10_15")]
Some(Location::DataProtectionKeychain) => {
use security_framework_sys::item::kSecUseDataProtectionKeychain;
attribute_key_values.push((
unsafe { kSecUseDataProtectionKeychain }.to_void(),
CFBoolean::true_value().to_void(),
));
}
Some(Location::FileKeychain(keychain)) => {
attribute_key_values.push((
unsafe { security_framework_sys::item::kSecUseKeychain }.to_void(),
keychain.as_concrete_TypeRef().to_void(),
));
},
_ => {},
}
match self.token.as_ref().unwrap_or(&Token::Software) {
Token::Software => {},
Token::SecureEnclave => {
attribute_key_values.push((
unsafe { kSecAttrTokenID }.to_void(),
unsafe { kSecAttrTokenIDSecureEnclave }.to_void(),
));
}
}
#[cfg(feature = "sync-keychain")]
if let Some(synchronizable) = &self.synchronizable {
attribute_key_values.push((
unsafe { security_framework_sys::item::kSecAttrSynchronizable }.to_void(),
CFBoolean::from(*synchronizable).to_void(),
));
}
CFMutableDictionary::from_CFType_pairs(&attribute_key_values).to_immutable()
}
}
impl fmt::Debug for SecKey {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("SecKey").finish_non_exhaustive()
}
}

73
vendor/security-framework/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,73 @@
#![cfg(target_vendor = "apple")]
//! Wrappers around the macOS Security Framework.
#![warn(missing_docs)]
#![allow(non_upper_case_globals)]
#![allow(clippy::manual_non_exhaustive)] // MSRV
#![allow(clippy::bad_bit_mask)] // bitflags
#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::unreadable_literal)]
#![allow(clippy::ignore_without_reason)]
use core_foundation_sys::base::OSStatus;
use security_framework_sys::base::errSecSuccess;
use crate::base::{Error, Result};
#[cfg(test)]
macro_rules! p {
($e:expr) => {
match $e {
Ok(s) => s,
Err(e) => panic!("{:?}", e),
}
};
}
pub mod access_control;
#[cfg(target_os = "macos")]
pub mod authorization;
pub mod base;
pub mod certificate;
pub mod cipher_suite;
#[cfg(target_os = "macos")]
pub mod cms;
pub mod identity;
pub mod import_export;
pub mod item;
pub mod key;
pub mod os;
pub mod passwords;
#[doc(hidden)]
pub mod passwords_options;
pub mod policy;
pub mod random;
pub mod secure_transport;
pub mod trust;
#[cfg(target_os = "macos")]
pub mod trust_settings;
#[inline(always)]
fn cvt(err: OSStatus) -> Result<()> {
match err {
errSecSuccess => Ok(()),
err => Err(Error::from_code(err)),
}
}
#[cfg(test)]
mod test {
use crate::certificate::SecCertificate;
/// Returns the server certificate (for certificate parsing/identity tests)
pub fn certificate() -> SecCertificate {
let certificate = include_bytes!("../test/server.der");
p!(SecCertificate::from_der(certificate))
}
/// Returns the CA certificate (trust anchor for TLS verification)
pub fn ca_certificate() -> SecCertificate {
let certificate = include_bytes!("../test/ca.der");
p!(SecCertificate::from_der(certificate))
}
}

View File

@@ -0,0 +1,15 @@
#![allow(unused_imports)]
//! Access functionality.
use core_foundation::base::TCFType;
use core_foundation::{declare_TCFType, impl_TCFType};
use security_framework_sys::access::SecAccessGetTypeID;
use security_framework_sys::base::SecAccessRef;
declare_TCFType! {
/// A type representing access settings.
SecAccess, SecAccessRef
}
impl_TCFType!(SecAccess, SecAccessRef, SecAccessGetTypeID);
unsafe impl Sync for SecAccess {}
unsafe impl Send for SecAccess {}

View File

@@ -0,0 +1,235 @@
//! OSX specific extensions to certificate functionality.
use core_foundation::array::{CFArray, CFArrayIterator};
use core_foundation::base::{TCFType, ToVoid};
use core_foundation::data::CFData;
use core_foundation::dictionary::CFDictionary;
use core_foundation::error::CFError;
use core_foundation::string::CFString;
use security_framework_sys::certificate::*;
use std::ptr;
use crate::base::Error;
use crate::certificate::SecCertificate;
use crate::cvt;
use crate::key::SecKey;
use crate::os::macos::certificate_oids::CertificateOid;
use crate::os::macos::digest_transform::{Builder, DigestType};
/// An extension trait adding OSX specific functionality to `SecCertificate`.
pub trait SecCertificateExt {
/// Returns the common name associated with the certificate.
fn common_name(&self) -> Result<String, Error>;
/// Returns the public key associated with the certificate.
fn public_key(&self) -> Result<SecKey, Error>;
/// Returns the set of properties associated with the certificate.
///
/// The `keys` argument can optionally be used to filter the properties loaded to an explicit
/// subset.
fn properties(&self, keys: Option<&[CertificateOid]>) -> Result<CertificateProperties, CFError>;
/// Returns the SHA-256 fingerprint of the certificate.
fn fingerprint(&self) -> Result<[u8; 32], CFError> { unimplemented!() }
}
impl SecCertificateExt for SecCertificate {
fn common_name(&self) -> Result<String, Error> {
unsafe {
let mut string = ptr::null();
cvt(SecCertificateCopyCommonName(self.as_concrete_TypeRef(), &mut string))?;
Ok(CFString::wrap_under_create_rule(string).to_string())
}
}
fn public_key(&self) -> Result<SecKey, Error> {
unsafe {
let key = SecCertificateCopyKey(self.as_concrete_TypeRef());
if key.is_null() {
return Err(Error::from_code(-26275));
}
Ok(SecKey::wrap_under_create_rule(key))
}
}
fn properties(&self, keys: Option<&[CertificateOid]>) -> Result<CertificateProperties, CFError> {
unsafe {
let keys = keys.map(|oids| {
let oids = oids.iter().map(|oid| oid.to_str()).collect::<Vec<_>>();
CFArray::from_CFTypes(&oids)
});
let keys = match &keys {
Some(keys) => keys.as_concrete_TypeRef(),
None => ptr::null_mut(),
};
let mut error = ptr::null_mut();
let dictionary = SecCertificateCopyValues(self.as_concrete_TypeRef(), keys, &mut error);
if error.is_null() {
Ok(CertificateProperties(CFDictionary::wrap_under_create_rule(dictionary)))
} else {
Err(CFError::wrap_under_create_rule(error))
}
}
}
/// Returns the SHA-256 fingerprint of the certificate.
fn fingerprint(&self) -> Result<[u8; 32], CFError> {
let data = CFData::from_buffer(&self.to_der());
let hash = Builder::new()
.type_(DigestType::sha2())
.length(256)
.execute(&data)?;
Ok(hash.bytes().try_into().unwrap())
}
}
/// Properties associated with a certificate.
pub struct CertificateProperties(CFDictionary);
impl CertificateProperties {
/// Retrieves a specific property identified by its OID.
#[must_use]
pub fn get(&self, oid: CertificateOid) -> Option<CertificateProperty> {
unsafe {
self.0
.find(oid.as_ptr().to_void())
.map(|value| CertificateProperty(CFDictionary::wrap_under_get_rule(*value as *mut _)))
}
}
}
/// A property associated with a certificate.
pub struct CertificateProperty(CFDictionary);
impl CertificateProperty {
/// Returns the label of this property.
#[must_use]
pub fn label(&self) -> CFString {
unsafe {
CFString::wrap_under_get_rule((*self.0.get(kSecPropertyKeyLabel.to_void())).cast())
}
}
/// Returns an enum of the underlying data for this property.
#[must_use]
pub fn get(&self) -> PropertyType {
unsafe {
let type_ = CFString::wrap_under_get_rule(*self.0.get(kSecPropertyKeyType.to_void()) as *mut _);
let value = self.0.get(kSecPropertyKeyValue.to_void());
if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeSection) {
PropertyType::Section(PropertySection(CFArray::wrap_under_get_rule((*value).cast())))
} else if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeString) {
PropertyType::String(CFString::wrap_under_get_rule((*value).cast()))
} else {
PropertyType::__Unknown
}
}
}
}
/// A "section" property.
///
/// Sections are sequences of other properties.
pub struct PropertySection(CFArray<CFDictionary>);
impl PropertySection {
/// Returns an iterator over the properties in this section.
#[inline(always)]
#[must_use]
pub fn iter(&self) -> PropertySectionIter<'_> {
PropertySectionIter(self.0.iter())
}
}
impl<'a> IntoIterator for &'a PropertySection {
type IntoIter = PropertySectionIter<'a>;
type Item = CertificateProperty;
#[inline(always)]
fn into_iter(self) -> PropertySectionIter<'a> {
self.iter()
}
}
/// An iterator over the properties in a section.
pub struct PropertySectionIter<'a>(CFArrayIterator<'a, CFDictionary>);
impl Iterator for PropertySectionIter<'_> {
type Item = CertificateProperty;
#[inline]
fn next(&mut self) -> Option<CertificateProperty> {
self.0.next().map(|t| CertificateProperty(t.clone()))
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
/// An enum of the various types of properties.
pub enum PropertyType {
/// A section.
Section(PropertySection),
/// A string.
String(CFString),
#[doc(hidden)]
__Unknown,
}
#[cfg(test)]
mod test {
use super::*;
use crate::test::certificate;
use std::collections::HashMap;
#[test]
fn common_name() {
let certificate = certificate();
assert_eq!("foobar.com", p!(certificate.common_name()));
}
#[test]
fn public_key() {
let certificate = certificate();
p!(certificate.public_key());
}
#[test]
fn fingerprint() {
let certificate = certificate();
let fingerprint = p!(certificate.fingerprint());
assert_eq!(fingerprint.len(), 32);
}
#[test]
fn signature_algorithm() {
let certificate = certificate();
let properties = certificate
.properties(Some(&[CertificateOid::x509_v1_signature_algorithm()]))
.unwrap();
let value = properties
.get(CertificateOid::x509_v1_signature_algorithm())
.unwrap();
let PropertyType::Section(section) = value.get() else {
panic!()
};
let properties = section
.iter()
.map(|p| (p.label().to_string(), p.get()))
.collect::<HashMap<_, _>>();
let algorithm = match properties["Algorithm"] {
PropertyType::String(ref s) => s.to_string(),
_ => panic!(),
};
// 1.2.840.113549.1.1.11 = sha256WithRSAEncryption
assert_eq!(algorithm, "1.2.840.113549.1.1.11");
}
}

View File

@@ -0,0 +1,34 @@
//! OIDs associated with certificate properties.
use core_foundation::base::TCFType;
use core_foundation::string::CFString;
use core_foundation_sys::string::CFStringRef;
use security_framework_sys::certificate_oids::kSecOIDX509V1SignatureAlgorithm;
/// An identifier of a property of a certificate.
#[derive(Copy, Clone)]
pub struct CertificateOid(CFStringRef);
#[allow(missing_docs)]
impl CertificateOid {
#[inline(always)]
#[must_use]
pub fn x509_v1_signature_algorithm() -> Self {
unsafe { Self(kSecOIDX509V1SignatureAlgorithm) }
}
/// Returns the underlying raw pointer corresponding to this OID.
#[inline(always)]
#[must_use]
// FIXME: Don't expose CFStringRef in Rust APIs
pub fn as_ptr(&self) -> CFStringRef {
self.0
}
/// Returns the string representation of the OID.
#[inline]
#[must_use]
// FIXME: Don't expose CFString in Rust APIs
pub fn to_str(&self) -> CFString {
unsafe { CFString::wrap_under_get_rule(self.0) }
}
}

View File

@@ -0,0 +1,482 @@
//! Code signing services.
use core_foundation::base::{TCFType, TCFTypeRef, ToVoid};
use core_foundation::data::CFDataRef;
use core_foundation::dictionary::CFMutableDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::{CFString, CFStringRef};
use core_foundation::url::CFURL;
use core_foundation::{declare_TCFType, impl_TCFType};
use libc::pid_t;
use security_framework_sys::code_signing::{
kSecCSBasicValidateOnly, kSecCSCheckAllArchitectures, kSecCSCheckGatekeeperArchitectures,
kSecCSCheckNestedCode, kSecCSCheckTrustedAnchors, kSecCSConsiderExpiration,
kSecCSDoNotValidateExecutable, kSecCSDoNotValidateResources, kSecCSEnforceRevocationChecks,
kSecCSFullReport, kSecCSNoNetworkAccess, kSecCSQuickCheck, kSecCSReportProgress,
kSecCSRestrictSidebandData, kSecCSRestrictSymlinks, kSecCSRestrictToAppLike,
kSecCSSingleThreaded, kSecCSStrictValidate, kSecCSUseSoftwareSigningCert, kSecCSValidatePEH,
kSecGuestAttributeAudit, kSecGuestAttributePid, SecCodeCheckValidity,
SecCodeCopyGuestWithAttributes, SecCodeCopyPath, SecCodeCopySelf, SecCodeGetTypeID, SecCodeRef,
SecRequirementCreateWithString, SecRequirementGetTypeID, SecRequirementRef,
SecStaticCodeCheckValidity, SecStaticCodeCreateWithPath, SecStaticCodeGetTypeID,
SecStaticCodeRef,
};
use std::fmt::Debug;
use std::mem::MaybeUninit;
use std::str::FromStr;
use crate::{cvt, Result};
bitflags::bitflags! {
/// Values that can be used in the flags parameter to most code signing
/// functions.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Flags: u32 {
/// Use the default behaviour.
const NONE = 0;
/// For multi-architecture (universal) Mach-O programs, validate all
/// architectures included.
const CHECK_ALL_ARCHITECTURES = kSecCSCheckAllArchitectures;
/// Do not validate the contents of the main executable.
const DO_NOT_VALIDATE_EXECUTABLE = kSecCSDoNotValidateExecutable;
/// Do not validate the presence and contents of all bundle resources
/// if any.
const DO_NOT_VALIDATE_RESOURCES = kSecCSDoNotValidateResources;
/// Do not validate either the main executable or the bundle resources,
/// if any.
const BASIC_VALIDATE_ONLY = kSecCSBasicValidateOnly;
/// For code in bundle form, locate and recursively check embedded code.
const CHECK_NESTED_CODE = kSecCSCheckNestedCode;
/// Perform additional checks to ensure the validity of code in bundle
/// form.
const STRICT_VALIDATE = kSecCSStrictValidate;
/// Apple have not documented this flag.
const FULL_REPORT = kSecCSFullReport;
/// Apple have not documented this flag.
const CHECK_GATEKEEPER_ARCHITECTURES = kSecCSCheckGatekeeperArchitectures;
/// Apple have not documented this flag.
const RESTRICT_SYMLINKS = kSecCSRestrictSymlinks;
/// Apple have not documented this flag.
const RESTRICT_TO_APP_LIKE = kSecCSRestrictToAppLike;
/// Apple have not documented this flag.
const RESTRICT_SIDEBAND_DATA = kSecCSRestrictSidebandData;
/// Apple have not documented this flag.
const USE_SOFTWARE_SIGNING_CERT = kSecCSUseSoftwareSigningCert;
/// Apple have not documented this flag.
const VALIDATE_PEH = kSecCSValidatePEH;
/// Apple have not documented this flag.
const SINGLE_THREADED = kSecCSSingleThreaded;
/// Apple have not documented this flag.
const QUICK_CHECK = kSecCSQuickCheck;
/// Apple have not documented this flag.
const CHECK_TRUSTED_ANCHORS = kSecCSCheckTrustedAnchors;
/// Apple have not documented this flag.
const REPORT_PROGRESS = kSecCSReportProgress;
/// Apple have not documented this flag.
const NO_NETWORK_ACCESS = kSecCSNoNetworkAccess;
/// Apple have not documented this flag.
const ENFORCE_REVOCATION_CHECKS = kSecCSEnforceRevocationChecks;
/// Apple have not documented this flag.
const CONSIDER_EXPIRATION = kSecCSConsiderExpiration;
}
}
impl Default for Flags {
#[inline(always)]
fn default() -> Self {
Self::NONE
}
}
/// A helper to create guest attributes, which are normally passed as a
/// `CFDictionary` with varying types.
pub struct GuestAttributes {
inner: CFMutableDictionary,
}
impl GuestAttributes {
// Not implemented:
// - architecture
// - canonical
// - dynamic code
// - dynamic code info plist
// - hash
// - mach port
// - sub-architecture
/// Creates a new, empty `GuestAttributes`. You must add values to it in
/// order for it to be of any use.
#[must_use]
pub fn new() -> Self {
Self {
inner: CFMutableDictionary::new(),
}
}
/// The guest's audit token.
pub fn set_audit_token(&mut self, token: CFDataRef) {
let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributeAudit) };
self.inner.add(&key.as_CFTypeRef(), &token.to_void());
}
/// The guest's pid.
pub fn set_pid(&mut self, pid: pid_t) {
let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributePid) };
let pid = CFNumber::from(pid);
self.inner.add(&key.as_CFTypeRef(), &pid.as_CFTypeRef());
}
/// Support for arbirtary guest attributes.
pub fn set_other<V: ToVoid<V>>(&mut self, key: CFStringRef, value: V) {
self.inner.add(&key.as_void_ptr(), &value.to_void());
}
}
impl Default for GuestAttributes {
fn default() -> Self {
Self::new()
}
}
declare_TCFType! {
/// A code object representing signed code running on the system.
SecRequirement, SecRequirementRef
}
impl_TCFType!(SecRequirement, SecRequirementRef, SecRequirementGetTypeID);
impl FromStr for SecRequirement {
type Err = crate::base::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let text = CFString::new(s);
let mut requirement = MaybeUninit::uninit();
unsafe {
cvt(SecRequirementCreateWithString(
text.as_concrete_TypeRef(),
0,
requirement.as_mut_ptr(),
))?;
Ok(Self::wrap_under_create_rule(requirement.assume_init()))
}
}
}
declare_TCFType! {
/// A code object representing signed code running on the system.
SecCode, SecCodeRef
}
impl_TCFType!(SecCode, SecCodeRef, SecCodeGetTypeID);
impl Debug for SecCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("SecCode")
}
}
impl SecCode {
/// Retrieves the code object for the code making the call.
pub fn for_self(flags: Flags) -> Result<Self> {
let mut code = MaybeUninit::uninit();
unsafe {
cvt(SecCodeCopySelf(flags.bits(), code.as_mut_ptr()))?;
Ok(Self::wrap_under_create_rule(code.assume_init()))
}
}
/// Performs dynamic validation of signed code.
pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> {
unsafe {
cvt(SecCodeCheckValidity(
self.as_concrete_TypeRef(),
flags.bits(),
requirement.as_concrete_TypeRef(),
))
}
}
/// Asks a code host to identify one of its guests given
/// the type and value of specific attributes of the guest code.
///
/// If `host` is `None` then the code signing root of trust (currently, the
// system kernel) should be used as the code host.
pub fn copy_guest_with_attribues(
host: Option<&Self>,
attrs: &GuestAttributes,
flags: Flags,
) -> Result<Self> {
let mut code = MaybeUninit::uninit();
let host = match host {
Some(host) => host.as_concrete_TypeRef(),
None => std::ptr::null_mut(),
};
unsafe {
cvt(SecCodeCopyGuestWithAttributes(
host,
attrs.inner.as_concrete_TypeRef(),
flags.bits(),
code.as_mut_ptr(),
))?;
Ok(Self::wrap_under_create_rule(code.assume_init()))
}
}
/// Retrieves the location on disk of signed code, given a code or static
/// code object.
// FIXME: Don't expose CFURL in Rust APIs.
pub fn path(&self, flags: Flags) -> Result<CFURL> {
let mut url = MaybeUninit::uninit();
// The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef.
unsafe {
cvt(SecCodeCopyPath(
self.as_CFTypeRef() as _,
flags.bits(),
url.as_mut_ptr(),
))?;
Ok(CFURL::wrap_under_create_rule(url.assume_init()))
}
}
}
declare_TCFType! {
/// A static code object representing signed code on disk.
SecStaticCode, SecStaticCodeRef
}
impl_TCFType!(SecStaticCode, SecStaticCodeRef, SecStaticCodeGetTypeID);
impl SecStaticCode {
/// Creates a static code object representing the code at a specified file
/// system path.
pub fn from_path(path: &CFURL, flags: Flags) -> Result<Self> {
let mut code = MaybeUninit::uninit();
unsafe {
cvt(SecStaticCodeCreateWithPath(
path.as_concrete_TypeRef(),
flags.bits(),
code.as_mut_ptr(),
))?;
Ok(Self::wrap_under_create_rule(code.assume_init()))
}
}
/// Retrieves the location on disk of signed code, given a code or static
/// code object.
// FIXME: Don't expose CFURL in Rust APIs.
pub fn path(&self, flags: Flags) -> Result<CFURL> {
let mut url = MaybeUninit::uninit();
// The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef.
unsafe {
cvt(SecCodeCopyPath(
self.as_concrete_TypeRef(),
flags.bits(),
url.as_mut_ptr(),
))?;
Ok(CFURL::wrap_under_create_rule(url.assume_init()))
}
}
/// Performs dynamic validation of signed code.
pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> {
unsafe {
cvt(SecStaticCodeCheckValidity(
self.as_concrete_TypeRef(),
flags.bits(),
requirement.as_concrete_TypeRef(),
))
}
}
}
#[cfg(test)]
mod test {
use super::*;
use core_foundation::data::CFData;
use libc::{c_uint, c_void, KERN_SUCCESS};
#[test]
fn path_to_static_code_and_back() {
let path = CFURL::from_path("/bin/bash", false).unwrap();
let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap();
assert_eq!(code.path(Flags::NONE).unwrap(), path);
}
#[test]
fn self_to_path() {
let path = CFURL::from_path(std::env::current_exe().unwrap(), false).unwrap();
let code = SecCode::for_self(Flags::NONE).unwrap();
assert_eq!(code.path(Flags::NONE).unwrap(), path);
}
#[test]
fn bash_is_signed_by_apple() {
let path = CFURL::from_path("/bin/bash", false).unwrap();
let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap();
let requirement: SecRequirement = "anchor apple".parse().unwrap();
code.check_validity(Flags::NONE, &requirement).unwrap();
}
#[cfg(target_arch = "aarch64")]
#[test]
fn self_is_not_signed_by_apple() {
let code = SecCode::for_self(Flags::NONE).unwrap();
let requirement: SecRequirement = "anchor apple".parse().unwrap();
assert_eq!(
code.check_validity(Flags::NONE, &requirement).unwrap_err().code(),
// "code failed to satisfy specified code requirement(s)"
-67050
);
}
#[cfg(not(target_arch = "aarch64"))]
#[test]
fn self_is_not_signed_by_apple() {
let code = SecCode::for_self(Flags::NONE).unwrap();
let requirement: SecRequirement = "anchor apple".parse().unwrap();
assert_eq!(
code.check_validity(Flags::NONE, &requirement).unwrap_err().code(),
// "code object is not signed at all"
-67062
);
}
#[test]
fn copy_kernel_guest_with_launchd_pid() {
let mut attrs = GuestAttributes::new();
attrs.set_pid(1);
assert_eq!(
SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
.unwrap()
.path(Flags::NONE)
.unwrap()
.get_string()
.to_string(),
"file:///sbin/launchd"
);
}
#[test]
fn copy_current_guest_with_launchd_pid() {
let host_code = SecCode::for_self(Flags::NONE).unwrap();
let mut attrs = GuestAttributes::new();
attrs.set_pid(1);
assert_eq!(
SecCode::copy_guest_with_attribues(Some(&host_code), &attrs, Flags::NONE)
.unwrap_err()
.code(),
// "host has no guest with the requested attributes"
-67065
);
}
#[test]
fn copy_kernel_guest_with_unmatched_pid() {
let mut attrs = GuestAttributes::new();
attrs.set_pid(999_999_999);
assert_eq!(
SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
.unwrap_err()
.code(),
// "UNIX[No such process]"
100003
);
}
#[test]
fn copy_kernel_guest_with_current_token() {
let mut token: [u8; 32] = [0; 32];
let mut token_len = 32u32;
enum OpaqueTaskName {}
unsafe extern "C" {
fn mach_task_self() -> *const OpaqueTaskName;
fn task_info(
task_name: *const OpaqueTaskName,
task_flavor: u32,
out: *mut c_void,
out_len: *mut u32,
) -> i32;
}
const TASK_AUDIT_TOKEN: c_uint = 15;
let result = unsafe {
task_info(
mach_task_self(),
TASK_AUDIT_TOKEN,
token.as_mut_ptr().cast::<c_void>(),
&mut token_len,
)
};
assert_eq!(result, KERN_SUCCESS);
let token_data = CFData::from_buffer(&token);
let mut attrs = GuestAttributes::new();
attrs.set_audit_token(token_data.as_concrete_TypeRef());
assert_eq!(
SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
.unwrap()
.path(Flags::NONE)
.unwrap()
.to_path()
.unwrap(),
std::env::current_exe().unwrap()
);
}
#[test]
fn copy_kernel_guest_with_unmatched_token() {
let token: [u8; 32] = [0; 32];
let token_data = CFData::from_buffer(&token);
let mut attrs = GuestAttributes::new();
attrs.set_audit_token(token_data.as_concrete_TypeRef());
assert_eq!(
SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE).unwrap_err().code(),
// "UNIX[No such process]"
100003
);
}
}

View File

@@ -0,0 +1,180 @@
//! Digest Transform support
use core_foundation::base::{CFIndex, TCFType};
use core_foundation::data::CFData;
use core_foundation::error::CFError;
use core_foundation::string::CFString;
use core_foundation_sys::base::CFTypeRef;
use core_foundation_sys::data::CFDataRef;
use core_foundation_sys::string::CFStringRef;
use security_framework_sys::digest_transform::*;
use security_framework_sys::transform::kSecTransformInputAttributeName;
use std::ptr;
use crate::os::macos::transform::SecTransform;
#[derive(Debug, Copy, Clone)]
/// A type of digest.
pub struct DigestType(CFStringRef);
#[allow(missing_docs)]
impl DigestType {
#[inline(always)]
#[must_use]
pub fn hmac_md5() -> Self {
unsafe { Self(kSecDigestHMACMD5) }
}
#[inline(always)]
#[must_use]
pub fn hmac_sha1() -> Self {
unsafe { Self(kSecDigestHMACSHA1) }
}
#[inline(always)]
#[must_use]
pub fn hmac_sha2() -> Self {
unsafe { Self(kSecDigestHMACSHA2) }
}
#[inline(always)]
#[must_use]
pub fn md2() -> Self {
unsafe { Self(kSecDigestMD2) }
}
#[inline(always)]
#[must_use]
pub fn md4() -> Self {
unsafe { Self(kSecDigestMD4) }
}
#[inline(always)]
#[must_use]
pub fn md5() -> Self {
unsafe { Self(kSecDigestMD5) }
}
#[inline(always)]
#[must_use]
pub fn sha1() -> Self {
unsafe { Self(kSecDigestSHA1) }
}
#[inline(always)]
#[must_use]
pub fn sha2() -> Self {
unsafe { Self(kSecDigestSHA2) }
}
#[inline(always)]
fn to_type(self) -> CFTypeRef {
self.0 as CFTypeRef
}
}
/// A builder for digest transform operations.
pub struct Builder {
digest_type: Option<DigestType>,
digest_length: Option<CFIndex>,
hmac_key: Option<CFData>,
}
impl Default for Builder {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl Builder {
/// Returns a new builder with default settings.
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Self {
digest_type: None,
digest_length: None,
hmac_key: None,
}
}
/// Sets the type of digest to perform.
///
/// If not set, an appropriate digest will be selected for you.
#[inline]
pub fn type_(&mut self, digest_type: DigestType) -> &mut Self {
self.digest_type = Some(digest_type);
self
}
/// Sets the output length of the digest.
///
/// If not set, an appropriate length will be selected for you. Some digest
/// types only support specific output lengths.
#[inline]
pub fn length(&mut self, digest_length: CFIndex) -> &mut Self {
self.digest_length = Some(digest_length);
self
}
/// Sets the key used for HMAC digests.
///
/// Only applies to `HmacMd5`, `HmacSha1`, and `HmacSha2` digests.
#[inline]
pub fn hmac_key(&mut self, hmac_key: CFData) -> &mut Self {
self.hmac_key = Some(hmac_key);
self
}
/// Computes the digest of the data.
// FIXME: deprecate and remove: don't expose CFData in Rust APIs.
pub fn execute(&self, data: &CFData) -> Result<CFData, CFError> {
unsafe {
let digest_type = match &self.digest_type {
Some(digest_type) => digest_type.to_type(),
None => ptr::null(),
};
let digest_length = self.digest_length.unwrap_or(0);
let mut error = ptr::null_mut();
let transform = SecDigestTransformCreate(digest_type, digest_length, &mut error);
if transform.is_null() {
return Err(CFError::wrap_under_create_rule(error));
}
let mut transform = SecTransform::wrap_under_create_rule(transform);
if let Some(hmac_key) = &self.hmac_key {
let key = CFString::wrap_under_get_rule(kSecDigestHMACKeyAttribute);
transform.set_attribute(&key, hmac_key)?;
}
let key = CFString::wrap_under_get_rule(kSecTransformInputAttributeName);
transform.set_attribute(&key, data)?;
let result = transform.execute()?;
Ok(CFData::wrap_under_get_rule(result.as_CFTypeRef() as CFDataRef))
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn md5() {
let data = CFData::from_buffer("The quick brown fox jumps over the lazy dog".as_bytes());
let hash = Builder::new().type_(DigestType::md5()).execute(&data).unwrap();
assert_eq!(hex::encode(hash.bytes()), "9e107d9d372bb6826bd81d3542a419d6");
}
#[test]
fn hmac_sha1() {
let data = CFData::from_buffer("The quick brown fox jumps over the lazy dog".as_bytes());
let key = CFData::from_buffer(b"key");
let hash = Builder::new().type_(DigestType::hmac_sha1()).hmac_key(key).execute(&data).unwrap();
assert_eq!(hex::encode(hash.bytes()), "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9");
}
}

View File

@@ -0,0 +1,256 @@
//! Encryption and Decryption transform support.
use core_foundation::base::TCFType;
use core_foundation::data::CFData;
use core_foundation::error::CFError;
use core_foundation::string::CFString;
use core_foundation_sys::data::CFDataRef;
use core_foundation_sys::string::CFStringRef;
use security_framework_sys::encrypt_transform::*;
use security_framework_sys::transform::kSecTransformInputAttributeName;
use std::ptr;
use crate::key::SecKey;
use crate::os::macos::transform::SecTransform;
#[derive(Debug, Copy, Clone)]
/// The padding scheme to use for encryption.
pub struct Padding(CFStringRef);
impl Padding {
/// Do not pad.
#[inline(always)]
#[must_use]
pub fn none() -> Self {
unsafe { Self(kSecPaddingNoneKey) }
}
/// Use PKCS#1 padding.
#[inline(always)]
#[must_use]
pub fn pkcs1() -> Self {
unsafe { Self(kSecPaddingPKCS1Key) }
}
/// Use PKCS#5 padding.
#[inline(always)]
#[must_use]
pub fn pkcs5() -> Self {
unsafe { Self(kSecPaddingPKCS5Key) }
}
/// Use PKCS#7 padding.
#[inline(always)]
#[must_use]
pub fn pkcs7() -> Self {
unsafe { Self(kSecPaddingPKCS7Key) }
}
/// Use OAEP padding.
#[inline(always)]
#[must_use]
pub fn oaep() -> Self {
unsafe { Self(kSecPaddingOAEPKey) }
}
#[inline]
fn to_str(self) -> CFString {
unsafe { CFString::wrap_under_get_rule(self.0) }
}
}
/// The cipher mode to use.
///
/// Only applies to AES encryption.
#[derive(Debug, Copy, Clone)]
pub struct Mode(CFStringRef);
#[allow(missing_docs)]
impl Mode {
#[inline(always)]
#[must_use]
pub fn none() -> Self {
unsafe { Self(kSecModeNoneKey) }
}
#[inline(always)]
#[must_use]
pub fn ecb() -> Self {
unsafe { Self(kSecModeECBKey) }
}
#[inline(always)]
#[must_use]
pub fn cbc() -> Self {
unsafe { Self(kSecModeCBCKey) }
}
#[inline(always)]
#[must_use]
pub fn cfb() -> Self {
unsafe { Self(kSecModeCFBKey) }
}
#[inline(always)]
#[must_use]
pub fn ofb() -> Self {
unsafe { Self(kSecModeOFBKey) }
}
fn to_str(self) -> CFString {
unsafe { CFString::wrap_under_get_rule(self.0) }
}
}
/// A builder for encryption and decryption transform operations.
#[derive(Default)]
pub struct Builder {
padding: Option<Padding>,
mode: Option<Mode>,
iv: Option<CFData>,
}
impl Builder {
/// Creates a new `Builder` with a default configuration.
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Selects the padding scheme to use.
///
/// If not set, an appropriate scheme will be selected for you.
#[inline(always)]
pub fn padding(&mut self, padding: Padding) -> &mut Self {
self.padding = Some(padding);
self
}
/// Selects the encryption mode to use.
///
/// If not set, an appropriate mode will be selected for you.
#[inline(always)]
pub fn mode(&mut self, mode: Mode) -> &mut Self {
self.mode = Some(mode);
self
}
/// Sets the initialization vector to use.
///
/// If not set, an appropriate value will be supplied for you.
#[inline(always)]
pub fn iv(&mut self, iv: CFData) -> &mut Self {
self.iv = Some(iv);
self
}
/// Encrypts data with a provided key.
// FIXME: deprecate and remove: don't expose CFData in Rust APIs.
pub fn encrypt(&self, key: &SecKey, data: &CFData) -> Result<CFData, CFError> {
unsafe {
let mut error = ptr::null_mut();
let transform = SecEncryptTransformCreate(key.as_concrete_TypeRef(), &mut error);
if transform.is_null() {
return Err(CFError::wrap_under_create_rule(error));
}
let transform = SecTransform::wrap_under_create_rule(transform);
self.finish(transform, data)
}
}
/// Decrypts data with a provided key.
// FIXME: deprecate and remove: don't expose CFData in Rust APIs.
pub fn decrypt(&self, key: &SecKey, data: &CFData) -> Result<CFData, CFError> {
unsafe {
let mut error = ptr::null_mut();
let transform = SecDecryptTransformCreate(key.as_concrete_TypeRef(), &mut error);
if transform.is_null() {
return Err(CFError::wrap_under_create_rule(error));
}
let transform = SecTransform::wrap_under_create_rule(transform);
self.finish(transform, data)
}
}
fn finish(&self, mut transform: SecTransform, data: &CFData) -> Result<CFData, CFError> {
unsafe {
if let Some(padding) = &self.padding {
let key = CFString::wrap_under_get_rule(kSecPaddingKey);
transform.set_attribute(&key, &padding.to_str())?;
}
if let Some(mode) = &self.mode {
let key = CFString::wrap_under_get_rule(kSecEncryptionMode);
transform.set_attribute(&key, &mode.to_str())?;
}
if let Some(iv) = &self.iv {
let key = CFString::wrap_under_get_rule(kSecIVKey);
transform.set_attribute(&key, iv)?;
}
let key = CFString::wrap_under_get_rule(kSecTransformInputAttributeName);
transform.set_attribute(&key, data)?;
let result = transform.execute()?;
Ok(CFData::wrap_under_get_rule(result.as_CFTypeRef() as CFDataRef))
}
}
}
#[cfg(test)]
mod test {
use hex::FromHex;
use super::*;
use crate::os::macos::item::KeyType;
#[allow(deprecated)]
use crate::os::macos::key::SecKeyExt;
#[test]
fn cbc_mmt_256() {
// test 9
let key = "87725bd43a45608814180773f0e7ab95a3c859d83a2130e884190e44d14c6996";
let iv = "e49651988ebbb72eb8bb80bb9abbca34";
let ciphertext = "5b97a9d423f4b97413f388d9a341e727bb339f8e18a3fac2f2fb85abdc8f135deb30054a\
1afdc9b6ed7da16c55eba6b0d4d10c74e1d9a7cf8edfaeaa684ac0bd9f9d24ba674955c7\
9dc6be32aee1c260b558ff07e3a4d49d24162011ff254db8be078e8ad07e648e6bf56793\
76cb4321a5ef01afe6ad8816fcc7634669c8c4389295c9241e45fff39f3225f7745032da\
eebe99d4b19bcb215d1bfdb36eda2c24";
let plaintext = "bfe5c6354b7a3ff3e192e05775b9b75807de12e38a626b8bf0e12d5fff78e4f1775aa7d79\
2d885162e66d88930f9c3b2cdf8654f56972504803190386270f0aa43645db187af41fcea\
639b1f8026ccdd0c23e0de37094a8b941ecb7602998a4b2604e69fc04219585d854600e0a\
d6f99a53b2504043c08b1c3e214d17cde053cbdf91daa999ed5b47c37983ba3ee254bc5c7\
93837daaa8c85cfc12f7f54f699f";
let key = Vec::<u8>::from_hex(key).unwrap();
let key = CFData::from_buffer(&key);
#[allow(deprecated)]
let key = SecKey::from_data(KeyType::aes(), &key).unwrap();
let iv = Vec::<u8>::from_hex(iv).unwrap();
let ciphertext = Vec::<u8>::from_hex(ciphertext).unwrap();
let plaintext = Vec::<u8>::from_hex(plaintext).unwrap();
let decrypted = Builder::new()
.padding(Padding::none())
.iv(CFData::from_buffer(&iv))
.decrypt(&key, &CFData::from_buffer(&ciphertext))
.unwrap();
assert_eq!(plaintext, decrypted.bytes());
let encrypted = Builder::new()
.padding(Padding::none())
.iv(CFData::from_buffer(&iv))
.encrypt(&key, &CFData::from_buffer(&plaintext))
.unwrap();
assert_eq!(ciphertext, encrypted.bytes());
}
}

View File

@@ -0,0 +1,85 @@
//! OSX specific extensions to identity functionality.
use core_foundation::array::CFArray;
use core_foundation::base::TCFType;
use security_framework_sys::identity::SecIdentityCreateWithCertificate;
use std::ptr;
use crate::base::Result;
use crate::certificate::SecCertificate;
use crate::cvt;
use crate::identity::SecIdentity;
use crate::os::macos::keychain::SecKeychain;
/// An extension trait adding OSX specific functionality to `SecIdentity`.
pub trait SecIdentityExt {
/// Creates an identity corresponding to a certificate, looking in the
/// provided keychains for the corresponding private key.
///
/// To search the default keychains, use an empty slice for `keychains`.
///
/// <https://developer.apple.com/documentation/security/1401160-secidentitycreatewithcertificate>
fn with_certificate(
keychains: &[SecKeychain],
certificate: &SecCertificate,
) -> Result<SecIdentity>;
}
impl SecIdentityExt for SecIdentity {
fn with_certificate(keychains: &[SecKeychain], certificate: &SecCertificate) -> Result<Self> {
let keychains = CFArray::from_CFTypes(keychains);
unsafe {
let mut identity = ptr::null_mut();
cvt(SecIdentityCreateWithCertificate(
if !keychains.is_empty() { keychains.as_CFTypeRef() } else { ptr::null() },
certificate.as_concrete_TypeRef(),
&mut identity,
))?;
Ok(Self::wrap_under_create_rule(identity))
}
}
}
#[cfg(test)]
mod test {
use tempfile::tempdir;
use super::*;
use crate::os::macos::certificate::SecCertificateExt;
use crate::os::macos::import_export::ImportOptions;
use crate::os::macos::keychain::CreateOptions;
use crate::os::macos::test::identity;
use crate::test;
#[test]
fn certificate() {
let dir = p!(tempdir());
let identity = identity(dir.path());
let certificate = p!(identity.certificate());
assert_eq!("foobar.com", p!(certificate.common_name()));
}
#[test]
fn private_key() {
let dir = p!(tempdir());
let identity = identity(dir.path());
p!(identity.private_key());
}
#[test]
fn with_certificate() {
let dir = p!(tempdir());
let keychain = p!(CreateOptions::new()
.password("foobar")
.create(dir.path().join("test.keychain")));
let key = include_bytes!("../../../test/server.key");
p!(ImportOptions::new()
.filename("server.key")
.keychain(&keychain)
.import(key));
let cert = test::certificate();
p!(SecIdentity::with_certificate(&[keychain], &cert));
}
}

View File

@@ -0,0 +1,360 @@
//! OSX specific extensions to import/export functionality.
use core_foundation::array::CFArray;
use core_foundation::base::{CFType, TCFType};
use core_foundation::data::CFData;
use core_foundation::string::CFString;
use security_framework_sys::base::errSecSuccess;
use security_framework_sys::import_export::*;
use std::ptr;
use std::str::FromStr;
use crate::base::{Error, Result};
use crate::certificate::SecCertificate;
use crate::identity::SecIdentity;
use crate::import_export::Pkcs12ImportOptions;
use crate::key::SecKey;
use crate::os::macos::access::SecAccess;
use crate::os::macos::keychain::SecKeychain;
#[deprecated(note = "Obsolete. Use Pkcs12ImportOptions directly.")]
/// Obsolete. Use Pkcs12ImportOptions directly.
pub trait Pkcs12ImportOptionsExt {
/// Specifies the keychain in which to import the identity.
///
/// If this is not called, the default keychain will be used.
fn keychain(&mut self, keychain: SecKeychain) -> &mut Self;
/// Specifies the access control to be associated with the identity.
fn access(&mut self, access: SecAccess) -> &mut Self;
}
#[allow(deprecated)]
impl Pkcs12ImportOptionsExt for Pkcs12ImportOptions {
/// Moved to Pkcs12ImportOptions. Remove Pkcs12ImportOptionsExt trait.
fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
Self::keychain(self, keychain)
}
/// Moved to Pkcs12ImportOptions. Remove Pkcs12ImportOptionsExt trait.
fn access(&mut self, access: SecAccess) -> &mut Self {
Self::access(self, access)
}
}
/// A builder type to import Security Framework types from serialized formats.
#[derive(Default)]
pub struct ImportOptions<'a> {
filename: Option<CFString>,
input_format: Option<SecExternalFormat>,
passphrase: Option<CFType>,
secure_passphrase: bool,
no_access_control: bool,
access: Option<SecAccess>,
alert_title: Option<CFString>,
alert_prompt: Option<CFString>,
items: Option<&'a mut SecItems>,
keychain: Option<SecKeychain>,
}
impl<'a> ImportOptions<'a> {
/// Creates a new builder with default options.
#[inline(always)]
#[must_use]
pub fn new() -> Self {
ImportOptions::default()
}
/// Sets the filename from which the imported data came.
///
/// The extension of the file will used as a hint for parsing.
#[inline]
pub fn filename(&mut self, filename: &str) -> &mut Self {
self.filename = Some(CFString::from_str(filename).unwrap());
self
}
/// Require input data to be PKCS#12
#[inline]
pub fn pkcs12(&mut self) -> &mut Self {
self.input_format = Some(kSecFormatPKCS12);
self
}
/// Sets the passphrase to be used to decrypt the imported data.
#[inline]
pub fn passphrase(&mut self, passphrase: &str) -> &mut Self {
self.passphrase = Some(CFString::from_str(passphrase).unwrap().into_CFType());
self
}
/// Sets the passphrase to be used to decrypt the imported data.
#[inline]
pub fn passphrase_bytes(&mut self, passphrase: &[u8]) -> &mut Self {
self.passphrase = Some(CFData::from_buffer(passphrase).into_CFType());
self
}
/// If set, the user will be prompted to imput the passphrase used to
/// decrypt the imported data.
#[inline(always)]
pub fn secure_passphrase(&mut self, secure_passphrase: bool) -> &mut Self {
self.secure_passphrase = secure_passphrase;
self
}
/// If set, imported items will have no access controls imposed on them.
#[inline(always)]
pub fn no_access_control(&mut self, no_access_control: bool) -> &mut Self {
self.no_access_control = no_access_control;
self
}
/// Specifies the access control to be associated with the identity. macOS only.
#[inline(always)]
pub fn access(&mut self, access: SecAccess) -> &mut Self {
self.access = Some(access);
self
}
/// Sets the title of the alert popup used with the `secure_passphrase`
/// option.
#[inline]
pub fn alert_title(&mut self, alert_title: &str) -> &mut Self {
self.alert_title = Some(CFString::from_str(alert_title).unwrap());
self
}
/// Sets the prompt of the alert popup used with the `secure_passphrase`
/// option.
#[inline]
pub fn alert_prompt(&mut self, alert_prompt: &str) -> &mut Self {
self.alert_prompt = Some(CFString::from_str(alert_prompt).unwrap());
self
}
/// Sets the object into which imported items will be placed.
#[inline(always)]
pub fn items(&mut self, items: &'a mut SecItems) -> &mut Self {
self.items = Some(items);
self
}
/// Sets the keychain into which items will be imported.
///
/// This must be specified to import `SecIdentity`s.
#[inline]
pub fn keychain(&mut self, keychain: &SecKeychain) -> &mut Self {
self.keychain = Some(keychain.clone());
self
}
/// Imports items from serialized data.
pub fn import(&mut self, data: &[u8]) -> Result<()> {
let data = CFData::from_buffer(data);
let data = data.as_concrete_TypeRef();
let filename = match &self.filename {
Some(filename) => filename.as_concrete_TypeRef(),
None => ptr::null(),
};
let mut input_format_out;
let input_format_ptr = match self.input_format {
None => ptr::null_mut(),
Some(format) => {
input_format_out = format;
&mut input_format_out
},
};
let mut key_params = SecItemImportExportKeyParameters {
version: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
flags: 0,
passphrase: ptr::null(),
alertTitle: ptr::null(),
alertPrompt: ptr::null(),
accessRef: ptr::null_mut(),
keyUsage: ptr::null_mut(),
keyAttributes: ptr::null(),
};
if let Some(passphrase) = &self.passphrase {
key_params.passphrase = passphrase.as_CFTypeRef();
}
if self.secure_passphrase {
key_params.flags |= kSecKeySecurePassphrase;
}
if self.no_access_control {
key_params.flags |= kSecKeyNoAccessControl;
}
if let Some(access) = &self.access {
key_params.accessRef = access.as_concrete_TypeRef();
}
if let Some(alert_title) = &self.alert_title {
key_params.alertTitle = alert_title.as_concrete_TypeRef();
}
if let Some(alert_prompt) = &self.alert_prompt {
key_params.alertPrompt = alert_prompt.as_concrete_TypeRef();
}
let keychain = match &self.keychain {
Some(keychain) => keychain.as_concrete_TypeRef(),
None => ptr::null_mut(),
};
let mut raw_items = ptr::null();
let items_ref = match self.items {
Some(_) => &mut raw_items,
None => ptr::null_mut(),
};
unsafe {
let ret = SecItemImport(
data,
filename,
input_format_ptr,
ptr::null_mut(),
0,
&key_params,
keychain,
items_ref,
);
if ret != errSecSuccess {
return Err(Error::from_code(ret));
}
if let Some(items) = &mut self.items {
let raw_items = CFArray::<CFType>::wrap_under_create_rule(raw_items);
for item in raw_items.iter() {
let type_id = item.type_of();
if type_id == SecCertificate::type_id() {
items.certificates.push(SecCertificate::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
} else if type_id == SecIdentity::type_id() {
items.identities.push(SecIdentity::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
} else if type_id == SecKey::type_id() {
items.keys.push(SecKey::wrap_under_get_rule(item.as_CFTypeRef() as *mut _));
} else {
panic!("Got bad type from SecItemImport: {type_id}");
}
}
}
}
Ok(())
}
}
/// A type which holds items imported from serialized data.
///
/// Pass a reference to `ImportOptions::items`.
#[derive(Default)]
pub struct SecItems {
/// Imported certificates.
pub certificates: Vec<SecCertificate>,
/// Imported identities.
pub identities: Vec<SecIdentity>,
/// Imported keys.
pub keys: Vec<SecKey>,
}
#[cfg(test)]
mod test {
use super::*;
use crate::import_export::*;
use crate::os::macos::keychain;
use tempfile::tempdir;
#[test]
fn certificate() {
let data = include_bytes!("../../../test/server.der");
let mut items = SecItems::default();
ImportOptions::new()
.filename("server.der")
.items(&mut items)
.import(data)
.unwrap();
assert_eq!(1, items.certificates.len());
assert_eq!(0, items.identities.len());
assert_eq!(0, items.keys.len());
}
#[test]
fn key() {
let data = include_bytes!("../../../test/server.key");
let mut items = SecItems::default();
ImportOptions::new()
.filename("server.key")
.items(&mut items)
.import(data)
.unwrap();
assert_eq!(0, items.certificates.len());
assert_eq!(0, items.identities.len());
assert_eq!(1, items.keys.len());
}
#[test]
fn identity() {
let dir = tempdir().unwrap();
let keychain = keychain::CreateOptions::new()
.password("password")
.create(dir.path().join("identity.keychain"))
.unwrap();
let data = include_bytes!("../../../test/server.p12");
let identities = Pkcs12ImportOptions::new()
.passphrase("password123")
.keychain(keychain)
.import(data)
.unwrap();
assert_eq!(1, identities.len());
}
#[test]
#[ignore] // since it requires manual intervention
fn secure_passphrase_identity() {
let dir = tempdir().unwrap();
let keychain = keychain::CreateOptions::new()
.password("password")
.create(dir.path().join("identity.keychain"))
.unwrap();
let data = include_bytes!("../../../test/server.p12");
let mut items = SecItems::default();
ImportOptions::new()
.filename("server.p12")
.secure_passphrase(true)
.alert_title("alert title")
.alert_prompt("alert prompt")
.items(&mut items)
.keychain(&keychain)
.import(data)
.unwrap();
assert_eq!(1, items.identities.len());
assert_eq!(0, items.certificates.len());
assert_eq!(0, items.keys.len());
}
#[test]
fn pkcs12_import() {
let dir = tempdir().unwrap();
let keychain = keychain::CreateOptions::new()
.password("password")
.create(dir.path().join("pkcs12_import"))
.unwrap();
let data = include_bytes!("../../../test/server.p12");
let identities = p!(Pkcs12ImportOptions::new()
.passphrase("password123")
.keychain(keychain)
.import(data));
assert_eq!(1, identities.len());
assert!(identities[0].key_id.is_some());
assert_eq!(identities[0].key_id.as_ref().unwrap().len(), 20);
}
}

View File

@@ -0,0 +1,49 @@
//! OSX specific functionality for items.
use crate::item::ItemSearchOptions;
use crate::os::macos::keychain::SecKeychain;
// Moved to crate::Key
pub use crate::key::KeyType;
// TODO: mark as deprecated
#[doc(hidden)]
/// An obsolete trait for `ItemSearchOptions`. Use methods on `ItemSearchOptions` directly.
pub trait ItemSearchOptionsExt {
/// Search within the specified keychains.
///
/// If this is not called, the default keychain will be searched.
fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self;
// Do not extend this trait; use `impl ItemSearchOptions` directly
}
impl ItemSearchOptionsExt for ItemSearchOptions {
fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
Self::keychains(self, keychains)
}
// Do not extend this trait; use `impl ItemSearchOptions` directly
}
#[cfg(test)]
mod test {
use crate::item::*;
use crate::os::macos::certificate::SecCertificateExt;
use crate::os::macos::test::keychain;
use tempfile::tempdir;
#[test]
fn find_certificate() {
let dir = p!(tempdir());
let keychain = keychain(dir.path());
let results = p!(ItemSearchOptions::new()
.keychains(&[keychain])
.class(ItemClass::certificate())
.search());
assert_eq!(1, results.len());
let SearchResult::Ref(Reference::Certificate(certificate)) = &results[0] else {
panic!("expected certificate")
};
assert_eq!("foobar.com", p!(certificate.common_name()));
}
}

View File

@@ -0,0 +1,39 @@
//! OSX specific functionality for keys.
use core_foundation::base::TCFType;
use core_foundation::data::CFData;
use core_foundation::dictionary::CFDictionary;
use core_foundation::error::CFError;
use core_foundation::string::CFString;
use security_framework_sys::item::kSecAttrKeyType;
use std::ptr;
use crate::key::{KeyType, SecKey};
/// An extension trait adding OSX specific functionality to `SecKey`.
#[deprecated(note = "Deprecated by Apple. There's no replacement for symmetric keys")]
pub trait SecKeyExt {
/// Creates a new `SecKey` from a buffer containing key data.
fn from_data(key_type: KeyType, key_data: &CFData) -> Result<SecKey, CFError>;
}
#[allow(deprecated)]
impl SecKeyExt for SecKey {
fn from_data(key_type: KeyType, key_data: &CFData) -> Result<Self, CFError> {
unsafe {
let key = CFString::wrap_under_get_rule(kSecAttrKeyType);
let dict = CFDictionary::from_CFType_pairs(&[(key, key_type.to_str())]);
let mut err = ptr::null_mut();
let key = security_framework_sys::key::SecKeyCreateFromData(
dict.as_concrete_TypeRef(),
key_data.as_concrete_TypeRef(),
&mut err,
);
if key.is_null() {
Err(CFError::wrap_under_create_rule(err))
} else {
Ok(Self::wrap_under_create_rule(key))
}
}
}
}

View File

@@ -0,0 +1,279 @@
//! Keychain support.
use core_foundation::base::{Boolean, TCFType};
use core_foundation::{declare_TCFType, impl_TCFType};
use security_framework_sys::base::{errSecSuccess, SecKeychainRef};
use security_framework_sys::keychain::*;
use std::ffi::CString;
use std::os::raw::c_void;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::ptr;
use crate::base::{Error, Result};
use crate::cvt;
use crate::os::macos::access::SecAccess;
pub use security_framework_sys::keychain::SecPreferencesDomain;
declare_TCFType! {
/// A type representing a keychain.
SecKeychain, SecKeychainRef
}
impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID);
unsafe impl Sync for SecKeychain {}
unsafe impl Send for SecKeychain {}
impl SecKeychain {
/// Creates a `SecKeychain` object corresponding to the user's default
/// keychain.
#[inline]
#[allow(clippy::should_implement_trait)]
pub fn default() -> Result<Self> {
unsafe {
let mut keychain = ptr::null_mut();
cvt(SecKeychainCopyDefault(&mut keychain))?;
Ok(Self::wrap_under_create_rule(keychain))
}
}
/// Creates a `SecKeychain` object corresponding to the user's default
/// keychain for the given domain.
pub fn default_for_domain(domain: SecPreferencesDomain) -> Result<Self> {
unsafe {
let mut keychain = ptr::null_mut();
cvt(SecKeychainCopyDomainDefault(domain, &mut keychain))?;
Ok(Self::wrap_under_create_rule(keychain))
}
}
/// Opens a keychain from a file.
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_name = [
path.as_ref().as_os_str().as_bytes(),
std::slice::from_ref(&0),
]
.concat();
unsafe {
let mut keychain = ptr::null_mut();
cvt(SecKeychainOpen(path_name.as_ptr().cast(), &mut keychain))?;
Ok(Self::wrap_under_create_rule(keychain))
}
}
/// Unlocks the keychain.
///
/// If a password is not specified, the user will be prompted to enter it.
pub fn unlock(&mut self, password: Option<&str>) -> Result<()> {
let (len, ptr, use_password) = match password {
Some(password) => (password.len(), password.as_ptr().cast(), true),
None => (0, ptr::null(), false),
};
unsafe {
cvt(SecKeychainUnlock(
self.as_concrete_TypeRef(),
len as u32,
ptr,
Boolean::from(use_password),
))
}
}
/// Sets settings of the keychain.
#[inline]
pub fn set_settings(&mut self, settings: &KeychainSettings) -> Result<()> {
unsafe {
cvt(SecKeychainSetSettings(
self.as_concrete_TypeRef(),
&settings.0,
))
}
}
#[cfg(target_os = "macos")]
/// Disables the user interface for keychain services functions that
/// automatically display a user interface.
pub fn disable_user_interaction() -> Result<KeychainUserInteractionLock> {
let code = unsafe { SecKeychainSetUserInteractionAllowed(0u8) };
if code == errSecSuccess {
Ok(KeychainUserInteractionLock)
} else {
Err(Error::from_code(code))
}
}
#[cfg(target_os = "macos")]
/// Indicates whether keychain services functions that normally display a
/// user interaction are allowed to do so.
pub fn user_interaction_allowed() -> Result<bool> {
let mut state: Boolean = 0;
let code = unsafe { SecKeychainGetUserInteractionAllowed(&mut state) };
if code == errSecSuccess {
Ok(state != 0)
} else {
Err(Error::from_code(code))
}
}
}
/// A builder type to create new keychains.
#[derive(Default)]
pub struct CreateOptions {
password: Option<String>,
prompt_user: bool,
access: Option<SecAccess>,
}
impl CreateOptions {
/// Creates a new builder with default options.
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Sets the password to be used to protect the keychain.
#[inline]
pub fn password(&mut self, password: &str) -> &mut Self {
self.password = Some(password.into());
self
}
/// If set, the user will be prompted to provide a password used to
/// protect the keychain.
#[inline(always)]
pub fn prompt_user(&mut self, prompt_user: bool) -> &mut Self {
self.prompt_user = prompt_user;
self
}
/// Sets the access control applied to the keychain.
#[inline(always)]
pub fn access(&mut self, access: SecAccess) -> &mut Self {
self.access = Some(access);
self
}
/// Creates a new keychain at the specified location on the filesystem.
pub fn create<P: AsRef<Path>>(&self, path: P) -> Result<SecKeychain> {
unsafe {
let path_name = path.as_ref().as_os_str().as_bytes();
// FIXME
let path_name = CString::new(path_name).unwrap();
let (password, password_len) = match &self.password {
Some(password) => (password.as_ptr().cast::<c_void>(), password.len() as u32),
None => (ptr::null(), 0),
};
let access = match &self.access {
Some(access) => access.as_concrete_TypeRef(),
None => ptr::null_mut(),
};
let mut keychain = ptr::null_mut();
cvt(SecKeychainCreate(
path_name.as_ptr(),
password_len,
password,
Boolean::from(self.prompt_user),
access,
&mut keychain,
))?;
Ok(SecKeychain::wrap_under_create_rule(keychain))
}
}
}
/// Settings associated with a `SecKeychain`.
pub struct KeychainSettings(SecKeychainSettings);
impl KeychainSettings {
/// Creates a new `KeychainSettings` with default settings.
#[inline]
#[must_use]
pub fn new() -> Self {
Self(SecKeychainSettings {
version: SEC_KEYCHAIN_SETTINGS_VERS1,
lockOnSleep: 0,
useLockInterval: 0,
lockInterval: i32::MAX as u32,
})
}
/// If set, the keychain will automatically lock when the computer sleeps.
///
/// Defaults to `false`.
#[inline(always)]
pub fn set_lock_on_sleep(&mut self, lock_on_sleep: bool) {
self.0.lockOnSleep = Boolean::from(lock_on_sleep);
}
/// Sets the interval of time in seconds after which the keychain is
/// automatically locked.
///
/// Defaults to `None`.
pub fn set_lock_interval(&mut self, lock_interval: Option<u32>) {
if let Some(lock_interval) = lock_interval {
self.0.useLockInterval = 1;
self.0.lockInterval = lock_interval;
} else {
self.0.useLockInterval = 0;
self.0.lockInterval = i32::MAX as u32;
}
}
}
impl Default for KeychainSettings {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
#[cfg(target_os = "macos")]
#[must_use = "The user interaction is disabled for the lifetime of the returned object"]
/// Automatically re-enables user interaction.
pub struct KeychainUserInteractionLock;
#[cfg(target_os = "macos")]
impl Drop for KeychainUserInteractionLock {
#[inline(always)]
fn drop(&mut self) {
unsafe { SecKeychainSetUserInteractionAllowed(1u8) };
}
}
#[cfg(test)]
mod test {
use tempfile::tempdir;
use super::*;
#[test]
fn create_options() {
let dir = tempdir().unwrap();
let mut keychain = CreateOptions::new()
.password("foobar")
.create(dir.path().join("test.keychain"))
.unwrap();
keychain.set_settings(&KeychainSettings::new()).unwrap();
}
#[test]
fn disable_user_interaction() {
assert!(SecKeychain::user_interaction_allowed().unwrap());
{
let _lock = SecKeychain::disable_user_interaction().unwrap();
assert!(!SecKeychain::user_interaction_allowed().unwrap());
}
assert!(SecKeychain::user_interaction_allowed().unwrap());
}
}

View File

@@ -0,0 +1,27 @@
#![allow(unused_imports)]
//! Keychain item support.
use core_foundation::base::TCFType;
use core_foundation::{declare_TCFType, impl_TCFType};
use security_framework_sys::base::SecKeychainItemRef;
use security_framework_sys::keychain_item::SecKeychainItemGetTypeID;
use std::fmt;
declare_TCFType! {
/// A type representing a keychain item.
SecKeychainItem, SecKeychainItemRef
}
impl_TCFType!(
SecKeychainItem,
SecKeychainItemRef,
SecKeychainItemGetTypeID
);
unsafe impl Sync for SecKeychainItem {}
unsafe impl Send for SecKeychainItem {}
impl fmt::Debug for SecKeychainItem {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("SecKeychainItem").finish_non_exhaustive()
}
}

View File

@@ -0,0 +1,53 @@
//! OSX specific extensions.
pub mod access;
pub mod certificate;
pub mod certificate_oids;
pub mod code_signing;
pub mod digest_transform;
pub mod encrypt_transform;
pub mod identity;
pub mod import_export;
pub mod item;
pub mod key;
pub mod keychain;
pub mod keychain_item;
pub mod passwords;
pub mod secure_transport;
pub mod transform;
#[cfg(test)]
pub(crate) mod test {
use crate::identity::SecIdentity;
use crate::item::{ItemClass, ItemSearchOptions, Reference, SearchResult};
use crate::os::macos::keychain::SecKeychain;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
#[must_use]
pub(crate) fn identity(dir: &Path) -> SecIdentity {
// FIXME https://github.com/rust-lang/rust/issues/30018
let keychain = keychain(dir);
let mut items = p!(ItemSearchOptions::new()
.class(ItemClass::identity())
.keychains(&[keychain])
.search());
match items.pop().unwrap() {
SearchResult::Ref(Reference::Identity(identity)) => identity,
_ => panic!("expected identity"),
}
}
#[must_use]
pub(crate) fn keychain(dir: &Path) -> SecKeychain {
let path = dir.join("server.keychain");
let mut file = p!(File::create(&path));
p!(file.write_all(include_bytes!("../../../test/server.keychain")));
drop(file);
let mut keychain = p!(SecKeychain::open(&path));
p!(keychain.unlock(Some("password123")));
keychain
}
}

View File

@@ -0,0 +1,510 @@
//! Password support.
use crate::os::macos::keychain::SecKeychain;
use crate::os::macos::keychain_item::SecKeychainItem;
use core_foundation::array::CFArray;
use core_foundation::base::TCFType;
pub use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType};
use security_framework_sys::keychain::{
SecKeychainAddGenericPassword, SecKeychainAddInternetPassword, SecKeychainFindGenericPassword,
SecKeychainFindInternetPassword,
};
use security_framework_sys::keychain_item::{
SecKeychainItemDelete, SecKeychainItemFreeContent, SecKeychainItemModifyAttributesAndData,
};
use std::fmt;
use std::fmt::Write;
use std::ops::Deref;
use std::ptr;
use std::slice;
use crate::base::Result;
use crate::cvt;
/// Password slice. Use `.as_ref()` to get `&[u8]` or `.to_owned()` to get `Vec<u8>`
pub struct SecKeychainItemPassword {
data: *const u8,
data_len: usize,
}
impl fmt::Debug for SecKeychainItemPassword {
#[cold]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for _ in 0..self.data_len {
f.write_char('•')?;
}
Ok(())
}
}
impl AsRef<[u8]> for SecKeychainItemPassword {
#[inline]
fn as_ref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.data, self.data_len) }
}
}
impl Deref for SecKeychainItemPassword {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl Drop for SecKeychainItemPassword {
#[inline]
fn drop(&mut self) {
unsafe {
SecKeychainItemFreeContent(ptr::null_mut(), self.data as *mut _);
}
}
}
impl SecKeychainItem {
/// Modify keychain item in-place, replacing its password with the given one
pub fn set_password(&mut self, password: &[u8]) -> Result<()> {
unsafe {
cvt(SecKeychainItemModifyAttributesAndData(
self.as_concrete_TypeRef(),
ptr::null(),
password.len() as u32,
password.as_ptr().cast(),
))?;
}
Ok(())
}
/// Delete this item from its keychain
#[inline]
pub fn delete(self) {
unsafe {
SecKeychainItemDelete(self.as_concrete_TypeRef());
}
}
}
/// Find a generic password.
///
/// The underlying system supports passwords with 0 values, so this
/// returns a vector of bytes rather than a string.
///
/// * `keychains` is an array of keychains to search or None to search the default keychain.
/// * `service` is the name of the service to search for.
/// * `account` is the name of the account to search for.
pub fn find_generic_password(
keychains: Option<&[SecKeychain]>,
service: &str,
account: &str,
) -> Result<(SecKeychainItemPassword, SecKeychainItem)> {
let keychains_or_none = keychains.map(CFArray::from_CFTypes);
let keychains_or_null = match &keychains_or_none {
None => ptr::null(),
Some(keychains) => keychains.as_CFTypeRef(),
};
let mut data_len = 0;
let mut data = ptr::null_mut();
let mut item = ptr::null_mut();
unsafe {
cvt(SecKeychainFindGenericPassword(
keychains_or_null,
service.len() as u32,
service.as_ptr().cast(),
account.len() as u32,
account.as_ptr().cast(),
&mut data_len,
&mut data,
&mut item,
))?;
Ok((
SecKeychainItemPassword {
data: data as *const _,
data_len: data_len as usize,
},
SecKeychainItem::wrap_under_create_rule(item),
))
}
}
/// * `keychains` is an array of keychains to search or None to search the default keychain.
/// * `server`: server name.
/// * `security_domain`: security domain. This parameter is optional.
/// * `account`: account name.
/// * `path`: the path.
/// * `port`: The TCP/IP port number.
/// * `protocol`: The protocol associated with this password.
/// * `authentication_type`: The authentication scheme used.
#[allow(clippy::too_many_arguments)]
pub fn find_internet_password(
keychains: Option<&[SecKeychain]>,
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
) -> Result<(SecKeychainItemPassword, SecKeychainItem)> {
let keychains_or_none = keychains.map(CFArray::from_CFTypes);
let keychains_or_null = match &keychains_or_none {
None => ptr::null(),
Some(keychains) => keychains.as_CFTypeRef(),
};
let mut data_len = 0;
let mut data = ptr::null_mut();
let mut item = ptr::null_mut();
unsafe {
cvt(SecKeychainFindInternetPassword(
keychains_or_null,
server.len() as u32,
server.as_ptr().cast(),
security_domain.map_or(0, |s| s.len() as u32),
security_domain.map_or(ptr::null(), |s| s.as_ptr().cast()),
account.len() as u32,
account.as_ptr().cast(),
path.len() as u32,
path.as_ptr().cast(),
port.unwrap_or(0),
protocol,
authentication_type,
&mut data_len,
&mut data,
&mut item,
))?;
Ok((
SecKeychainItemPassword {
data: data as *const _,
data_len: data_len as usize,
},
SecKeychainItem::wrap_under_create_rule(item),
))
}
}
impl SecKeychain {
/// Find application password in this keychain
#[inline]
pub fn find_generic_password(
&self,
service: &str,
account: &str,
) -> Result<(SecKeychainItemPassword, SecKeychainItem)> {
find_generic_password(Some(std::slice::from_ref(self)), service, account)
}
/// Find internet password in this keychain
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn find_internet_password(
&self,
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
) -> Result<(SecKeychainItemPassword, SecKeychainItem)> {
find_internet_password(
Some(std::slice::from_ref(self)),
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
)
}
/// Update existing or add new internet password
#[allow(clippy::too_many_arguments)]
pub fn set_internet_password(
&self,
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
password: &[u8],
) -> Result<()> {
match self.find_internet_password(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
) {
Ok((_, mut item)) => item.set_password(password),
_ => self.add_internet_password(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
password,
),
}
}
/// Set a generic password.
///
/// * `keychain_opt` is the keychain to use or None to use the default keychain.
/// * `service` is the associated service name for the password.
/// * `account` is the associated account name for the password.
/// * `password` is the password itself.
pub fn set_generic_password(
&self,
service: &str,
account: &str,
password: &[u8],
) -> Result<()> {
match self.find_generic_password(service, account) {
Ok((_, mut item)) => item.set_password(password),
_ => self.add_generic_password(service, account, password),
}
}
/// Add application password to the keychain, without checking if it exists already
///
/// See `set_generic_password()`
#[inline]
pub fn add_generic_password(
&self,
service: &str,
account: &str,
password: &[u8],
) -> Result<()> {
unsafe {
cvt(SecKeychainAddGenericPassword(
self.as_CFTypeRef() as *mut _,
service.len() as u32,
service.as_ptr().cast(),
account.len() as u32,
account.as_ptr().cast(),
password.len() as u32,
password.as_ptr().cast(),
ptr::null_mut(),
))?;
}
Ok(())
}
/// Add internet password to the keychain, without checking if it exists already
///
/// See `set_internet_password()`
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn add_internet_password(
&self,
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
password: &[u8],
) -> Result<()> {
unsafe {
cvt(SecKeychainAddInternetPassword(
self.as_CFTypeRef() as *mut _,
server.len() as u32,
server.as_ptr().cast(),
security_domain.map_or(0, |s| s.len() as u32),
security_domain.map_or(ptr::null(), |s| s.as_ptr().cast()),
account.len() as u32,
account.as_ptr().cast(),
path.len() as u32,
path.as_ptr().cast(),
port.unwrap_or(0),
protocol,
authentication_type,
password.len() as u32,
password.as_ptr().cast(),
ptr::null_mut(),
))?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::os::macos::keychain::CreateOptions;
use tempfile::{tempdir, TempDir};
fn temp_keychain_setup(name: &str) -> (TempDir, SecKeychain) {
let dir = tempdir().expect("TempDir::new");
let keychain = CreateOptions::new()
.password("foobar")
.create(dir.path().join(name.to_string() + ".keychain"))
.expect("create keychain");
(dir, keychain)
}
fn temp_keychain_teardown(dir: TempDir) {
dir.close().expect("temp dir close");
}
#[test]
fn missing_password_temp() {
let (dir, keychain) = temp_keychain_setup("missing_password");
let keychains = vec![keychain];
let service = "temp_this_service_does_not_exist";
let account = "this_account_is_bogus";
let found = find_generic_password(Some(&keychains), service, account);
assert!(found.is_err());
temp_keychain_teardown(dir);
}
#[test]
fn default_keychain_test_missing_password_default() {
let service = "default_this_service_does_not_exist";
let account = "this_account_is_bogus";
let found = find_generic_password(None, service, account);
assert!(found.is_err());
}
#[test]
fn round_trip_password_temp() {
let (dir, keychain) = temp_keychain_setup("round_trip_password");
let service = "test_round_trip_password_temp";
let account = "temp_this_is_the_test_account";
let password = String::from("deadbeef").into_bytes();
keychain.set_generic_password(service, account, &password).expect("set_generic_password");
let (found, item) = keychain.find_generic_password(service, account).expect("find_generic_password");
assert_eq!(found.to_owned(), password);
item.delete();
temp_keychain_teardown(dir);
}
#[test]
fn default_keychain_test_round_trip_password_default() {
let service = "test_round_trip_password_default";
let account = "this_is_the_test_account";
let password = String::from("deadbeef").into_bytes();
SecKeychain::default()
.expect("default keychain")
.set_generic_password(service, account, &password)
.expect("set_generic_password");
let (found, item) = find_generic_password(None, service, account).expect("find_generic_password");
assert_eq!(&*found, &password[..]);
item.delete();
}
#[test]
fn change_password_temp() {
let (dir, keychain) = temp_keychain_setup("change_password");
let keychains = vec![keychain];
let service = "test_change_password_temp";
let account = "this_is_the_test_account";
let pw1 = String::from("password1").into_bytes();
let pw2 = String::from("password2").into_bytes();
keychains[0]
.set_generic_password(service, account, &pw1)
.expect("set_generic_password1");
let (found, _) = find_generic_password(Some(&keychains), service, account)
.expect("find_generic_password1");
assert_eq!(found.as_ref(), &pw1[..]);
keychains[0]
.set_generic_password(service, account, &pw2)
.expect("set_generic_password2");
let (found, item) = find_generic_password(Some(&keychains), service, account)
.expect("find_generic_password2");
assert_eq!(&*found, &pw2[..]);
item.delete();
temp_keychain_teardown(dir);
}
#[test]
fn default_keychain_test_change_password_default() {
let service = "test_change_password_default";
let account = "this_is_the_test_account";
let pw1 = String::from("password1").into_bytes();
let pw2 = String::from("password2").into_bytes();
SecKeychain::default()
.expect("default keychain")
.set_generic_password(service, account, &pw1)
.expect("set_generic_password1");
let (found, _) = find_generic_password(None, service, account).expect("find_generic_password1");
assert_eq!(found.to_owned(), pw1);
SecKeychain::default()
.expect("default keychain")
.set_generic_password(service, account, &pw2)
.expect("set_generic_password2");
let (found, item) = find_generic_password(None, service, account).expect("find_generic_password2");
assert_eq!(found.to_owned(), pw2);
item.delete();
}
#[test]
fn cross_keychain_corruption_temp() {
let (dir1, keychain1) = temp_keychain_setup("cross_corrupt1");
let (dir2, keychain2) = temp_keychain_setup("cross_corrupt2");
let keychains1 = vec![keychain1.clone()];
let keychains2 = vec![keychain2.clone()];
let both_keychains = vec![keychain1, keychain2];
let service = "temp_this_service_does_not_exist";
let account = "this_account_is_bogus";
let password = String::from("deadbeef").into_bytes();
// Make sure this password doesn't exist in either keychain.
let found = find_generic_password(Some(&both_keychains), service, account);
assert!(found.is_err());
// Set a password in one keychain.
keychains1[0]
.set_generic_password(service, account, &password)
.expect("set_generic_password");
// Make sure it's found in that keychain.
let (found, item) = find_generic_password(Some(&keychains1), service, account)
.expect("find_generic_password1");
assert_eq!(found.to_owned(), password);
// Make sure it's _not_ found in the other keychain.
let found = find_generic_password(Some(&keychains2), service, account);
assert!(found.is_err());
// Cleanup.
item.delete();
temp_keychain_teardown(dir1);
temp_keychain_teardown(dir2);
}
}

View File

@@ -0,0 +1,579 @@
//! OSX specific extensions to Secure Transport functionality.
use core_foundation::array::CFArray;
use core_foundation::base::TCFType;
use security_framework_sys::secure_transport::*;
use std::ptr;
use std::slice;
use crate::base::Result;
use crate::certificate::SecCertificate;
use crate::cvt;
use crate::secure_transport::{MidHandshakeSslStream, SslContext};
/// An extension trait adding OSX specific functionality to the `SslContext`
/// type.
pub trait SslContextExt {
/// Returns the DER encoded data specifying the parameters used for
/// Diffie-Hellman key exchange.
fn diffie_hellman_params(&self) -> Result<Option<&[u8]>>;
/// Sets the parameters used for Diffie-Hellman key exchange, in the
/// DER format used by OpenSSL.
///
/// If a cipher suite which uses Diffie-Hellman key exchange is selected,
/// parameters will automatically be generated if none are provided with
/// this method, but this process can take up to 30 seconds.
///
/// This can only be called on server-side sessions.
fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()>;
/// Returns the certificate authorities used to validate client
/// certificates.
fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>>;
/// Sets the certificate authorities used to validate client certificates,
/// replacing any that are already present.
fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>;
/// Adds certificate authorities used to validate client certificates.
fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>;
/// If enabled, server identity changes are allowed during renegotiation.
///
/// It is disabled by default to protect against triple handshake attacks.
fn allow_server_identity_change(&self) -> Result<bool>;
/// If enabled, server identity changes are allowed during renegotiation.
///
/// It is disabled by default to protect against triple handshake attacks.
#[deprecated(note = "kSSLSessionOptionAllowServerIdentityChange is deprecated by Apple")]
fn set_allow_server_identity_change(&mut self, value: bool) -> Result<()>;
/// If enabled, fallback countermeasures will be used during negotiation.
///
/// It should be enabled when renegotiating with a peer with a lower
/// maximum protocol version due to an earlier failure to connect.
fn fallback(&self) -> Result<bool>;
/// If enabled, fallback countermeasures will be used during negotiation.
///
/// It should be enabled when renegotiating with a peer with a lower
/// maximum protocol version due to an earlier failure to connect.
#[deprecated(note = "kSSLSessionOptionFallback is deprecated by Apple")]
fn set_fallback(&mut self, value: bool) -> Result<()>;
/// If enabled, the handshake process will pause and return when the client
/// hello is recieved to support server name identification.
fn break_on_client_hello(&self) -> Result<bool>;
/// If enabled, the handshake process will pause and return when the client
/// hello is recieved to support server name identification.
#[deprecated(note = "kSSLSessionOptionBreakOnClientHello is deprecated by Apple")]
fn set_break_on_client_hello(&mut self, value: bool) -> Result<()>;
}
macro_rules! impl_options {
($($(#[$a:meta])* const $opt:ident: $get:ident & $set:ident,)*) => {
$(
#[allow(deprecated)]
$(#[$a])*
#[inline]
fn $set(&mut self, value: bool) -> Result<()> {
unsafe {
cvt(SSLSetSessionOption(self.as_inner(),
$opt,
::core_foundation::base::Boolean::from(value)))
}
}
#[allow(deprecated)]
$(#[$a])*
#[inline]
fn $get(&self) -> Result<bool> {
let mut value = 0;
unsafe { cvt(SSLGetSessionOption(self.as_inner(), $opt, &mut value))?; }
Ok(value != 0)
}
)*
}
}
impl SslContextExt for SslContext {
fn diffie_hellman_params(&self) -> Result<Option<&[u8]>> {
unsafe {
let mut ptr = ptr::null();
let mut len = 0;
cvt(SSLGetDiffieHellmanParams(
self.as_inner(),
&mut ptr,
&mut len,
))?;
if ptr.is_null() {
Ok(None)
} else {
Ok(Some(slice::from_raw_parts(ptr.cast::<u8>(), len)))
}
}
}
fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()> {
unsafe {
cvt(SSLSetDiffieHellmanParams(
self.as_inner(),
dh_params.as_ptr().cast(),
dh_params.len(),
))
}
}
fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>> {
unsafe {
let mut raw_certs = ptr::null();
cvt(SSLCopyCertificateAuthorities(
self.as_inner(),
&mut raw_certs,
))?;
if raw_certs.is_null() {
Ok(None)
} else {
let certs = CFArray::<SecCertificate>::wrap_under_create_rule(raw_certs)
.iter()
.map(|c| c.clone())
.collect();
Ok(Some(certs))
}
}
}
fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> {
unsafe {
let certs = CFArray::from_CFTypes(certs);
cvt(SSLSetCertificateAuthorities(
self.as_inner(),
certs.as_CFTypeRef(),
1,
))
}
}
fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> {
unsafe {
let certs = CFArray::from_CFTypes(certs);
cvt(SSLSetCertificateAuthorities(
self.as_inner(),
certs.as_CFTypeRef(),
0,
))
}
}
impl_options! {
const kSSLSessionOptionAllowServerIdentityChange: allow_server_identity_change & set_allow_server_identity_change,
const kSSLSessionOptionFallback: fallback & set_fallback,
const kSSLSessionOptionBreakOnClientHello: break_on_client_hello & set_break_on_client_hello,
}
}
/// An extension trait adding OSX specific functionality to the
/// `MidHandshakeSslStream` type.
pub trait MidHandshakeSslStreamExt {
/// Returns `true` iff `break_on_client_hello` was set and the handshake
/// has progressed to that point.
fn client_hello_received(&self) -> bool;
}
impl<S> MidHandshakeSslStreamExt for MidHandshakeSslStream<S> {
fn client_hello_received(&self) -> bool {
self.error().code() == errSSLClientHelloReceived
}
}
#[cfg(test)]
mod test {
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use std::thread;
use tempfile::tempdir;
use super::*;
use crate::cipher_suite::CipherSuite;
use crate::os::macos::test::identity;
use crate::secure_transport::*;
use crate::test::ca_certificate;
#[test]
fn server_client() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
let stream = p!(listener.accept()).0;
let mut stream = p!(ctx.handshake(stream));
let mut buf = [0; 12];
p!(stream.read(&mut buf));
assert_eq!(&buf[..], b"hello world!");
});
let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
p!(ctx.set_break_on_server_auth(true));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
assert!(stream.server_auth_completed());
let mut peer_trust = p!(stream.context().peer_trust2()).unwrap();
p!(peer_trust.set_anchor_certificates(&[ca_certificate()]));
p!(peer_trust.evaluate_with_error());
let mut stream = p!(stream.handshake());
p!(stream.write_all(b"hello world!"));
handle.join().unwrap();
}
#[test]
fn server_client_builders() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let identity = identity(dir.path());
let builder = ServerBuilder::new(&identity, &[]);
let stream = p!(listener.accept()).0;
let mut stream = p!(builder.handshake(stream));
let mut buf = [0; 12];
p!(stream.read(&mut buf));
assert_eq!(&buf[..], b"hello world!");
});
let stream = p!(TcpStream::connect(("localhost", port)));
let mut stream = p!(ClientBuilder::new()
.anchor_certificates(&[ca_certificate()])
.handshake("foobar.com", stream));
p!(stream.write_all(b"hello world!"));
handle.join().unwrap();
}
#[test]
fn client_bad_cert() {
let _ = env_logger::try_init();
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
let stream = p!(listener.accept()).0;
let _ = ctx.handshake(stream);
});
let stream = p!(TcpStream::connect(("localhost", port)));
assert!(ClientBuilder::new().handshake("foobar.com", stream).is_err());
handle.join().unwrap();
}
#[test]
fn client() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
let stream = p!(listener.accept()).0;
let mut stream = p!(ctx.handshake(stream));
let mut buf = [0; 12];
p!(stream.read(&mut buf));
assert_eq!(&buf[..], b"hello world!");
});
let stream = p!(TcpStream::connect(("localhost", port)));
let mut stream = p!(ClientBuilder::new()
.anchor_certificates(&[ca_certificate()])
.handshake("foobar.com", stream));
p!(stream.write_all(b"hello world!"));
handle.join().unwrap();
}
#[test]
fn negotiated_cipher() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
p!(ctx.set_enabled_ciphers(&[
CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
]));
let stream = p!(listener.accept()).0;
let mut stream = p!(ctx.handshake(stream));
assert_eq!(
CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
p!(stream.context().negotiated_cipher())
);
let mut buf = [0; 1];
p!(stream.read(&mut buf));
});
let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
p!(ctx.set_break_on_server_auth(true));
p!(ctx.set_enabled_ciphers(&[
CipherSuite::TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
]));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
let mut stream = p!(stream.handshake());
assert_eq!(
CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
p!(stream.context().negotiated_cipher())
);
p!(stream.write(&[0]));
handle.join().unwrap();
}
#[test]
fn dh_params() {
let params = include_bytes!("../../../test/dhparam.der");
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
assert!(p!(ctx.diffie_hellman_params()).is_none());
p!(ctx.set_diffie_hellman_params(params));
assert_eq!(p!(ctx.diffie_hellman_params()).unwrap(), &params[..]);
}
#[test]
fn try_authenticate_no_cert() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
p!(ctx.set_client_side_authenticate(SslAuthenticate::TRY));
let cert = ca_certificate();
p!(ctx.add_certificate_authorities(&[cert]));
let stream = p!(listener.accept()).0;
let mut stream = p!(ctx.handshake(stream));
let mut buf = [0; 1];
p!(stream.read(&mut buf));
});
let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
p!(ctx.set_break_on_server_auth(true));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
let mut stream = p!(stream.handshake());
p!(stream.write(&[0]));
handle.join().unwrap();
}
#[test]
fn always_authenticate_no_cert() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS));
let stream = p!(listener.accept()).0;
match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Failure(_)) => {},
Err(err) => panic!("unexpected error {err:?}"),
}
});
let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
p!(ctx.set_break_on_server_auth(true));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
match stream.handshake() {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Failure(_)) => {},
Err(err) => panic!("unexpected error {err:?}"),
}
handle.join().unwrap();
}
#[test]
fn always_authenticate_with_cert() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS));
let stream = p!(listener.accept()).0;
match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Failure(_)) => {},
Err(err) => panic!("unexpected error {err:?}"),
}
});
let mut ctx = p!(SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM));
p!(ctx.set_break_on_server_auth(true));
let dir = p!(tempdir());
let identity = identity(dir.path());
p!(ctx.set_certificate(&identity, &[]));
let stream = p!(TcpStream::connect(("localhost", port)));
let stream = match ctx.handshake(stream) {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Interrupted(stream)) => stream,
Err(err) => panic!("unexpected error {err:?}"),
};
match stream.handshake() {
Ok(_) => panic!("unexpected success"),
Err(HandshakeError::Failure(_)) => {},
Err(err) => panic!("unexpected error {err:?}"),
}
handle.join().unwrap();
}
#[test]
fn certificate_authorities() {
let mut ctx = p!(SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM));
assert!(p!(ctx.certificate_authorities()).is_none());
p!(ctx.set_certificate_authorities(&[ca_certificate()]));
assert_eq!(p!(ctx.certificate_authorities()).unwrap().len(), 1);
}
#[test]
fn close() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let identity = identity(dir.path());
let builder = ServerBuilder::new(&identity, &[]);
let stream = p!(listener.accept()).0;
let mut stream = p!(builder.handshake(stream));
p!(stream.close());
});
let stream = p!(TcpStream::connect(("localhost", port)));
let mut stream = p!(ClientBuilder::new()
.anchor_certificates(&[ca_certificate()])
.handshake("foobar.com", stream));
let mut buf = [0; 1];
assert_eq!(p!(stream.read(&mut buf)), 0);
p!(stream.close());
p!(handle.join());
}
#[test]
fn short_read() {
let listener = p!(TcpListener::bind("localhost:0"));
let port = p!(listener.local_addr()).port();
let handle = thread::spawn(move || {
let dir = p!(tempdir());
let identity = identity(dir.path());
let builder = ServerBuilder::new(&identity, &[]);
let stream = p!(listener.accept()).0;
let mut stream = p!(builder.handshake(stream));
stream.write_all(b"hello").unwrap();
// make sure stream doesn't close
stream
});
let stream = p!(TcpStream::connect(("localhost", port)));
let mut stream = p!(ClientBuilder::new()
.anchor_certificates(&[ca_certificate()])
.handshake("foobar.com", stream));
let mut b = [0; 1];
stream.read_exact(&mut b).unwrap();
assert_eq!(stream.context().buffered_read_size().unwrap(), 4);
let mut b = [0; 5];
let read = stream.read(&mut b).unwrap();
assert_eq!(read, 4);
p!(handle.join());
}
}

View File

@@ -0,0 +1,53 @@
//! Transform support
use core_foundation::base::{CFType, TCFType};
use core_foundation::error::CFError;
use core_foundation::string::CFString;
use core_foundation::{declare_TCFType, impl_TCFType};
use security_framework_sys::transform::*;
use std::ptr;
declare_TCFType! {
/// A type representing a transform.
SecTransform, SecTransformRef
}
impl_TCFType!(SecTransform, SecTransformRef, SecTransformGetTypeID);
unsafe impl Sync for SecTransform {}
unsafe impl Send for SecTransform {}
impl SecTransform {
/// Sets an attribute of the transform.
pub fn set_attribute<T>(&mut self, key: &CFString, value: &T) -> Result<(), CFError>
where T: TCFType {
unsafe {
let mut error = ptr::null_mut();
SecTransformSetAttribute(
self.0,
key.as_concrete_TypeRef(),
value.as_CFTypeRef(),
&mut error,
);
if !error.is_null() {
return Err(CFError::wrap_under_create_rule(error));
}
Ok(())
}
}
/// Executes the transform.
///
/// The return type depends on the type of transform.
// FIXME: deprecate and remove: don't expose CFType in Rust APIs.
pub fn execute(&mut self) -> Result<CFType, CFError> {
unsafe {
let mut error = ptr::null_mut();
let result = SecTransformExecute(self.0, &mut error);
if result.is_null() {
return Err(CFError::wrap_under_create_rule(error));
}
Ok(CFType::wrap_under_create_rule(result))
}
}
}

View File

@@ -0,0 +1,4 @@
//! OS specific extensions.
#[cfg(target_os = "macos")]
pub mod macos;

View File

@@ -0,0 +1,310 @@
//! Support for password entries in the keychain. Works on both iOS and macOS.
//!
//! If you want the extended keychain facilities only available on macOS, use the
//! version of these functions in the macOS extensions module.
#[doc(inline)]
pub use crate::passwords_options::{AccessControlOptions, PasswordOptions};
use crate::base::Result;
use crate::{cvt, Error};
use core_foundation::base::TCFType;
use core_foundation::boolean::CFBoolean;
use core_foundation::data::CFData;
use core_foundation::dictionary::CFDictionary;
use core_foundation_sys::base::{CFGetTypeID, CFRelease, CFTypeRef};
use core_foundation_sys::data::CFDataRef;
use security_framework_sys::base::{errSecDuplicateItem, errSecParam};
use security_framework_sys::item::{kSecReturnData, kSecValueData};
use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType};
use security_framework_sys::keychain_item::{
SecItemAdd, SecItemCopyMatching, SecItemDelete, SecItemUpdate,
};
/// Set a generic password for the given service and account.
/// Creates or updates a keychain entry.
pub fn set_generic_password(service: &str, account: &str, password: &[u8]) -> Result<()> {
let mut options = PasswordOptions::new_generic_password(service, account);
set_password_internal(&mut options, password)
}
/// Set a generic password using the given password options.
/// Creates or updates a keychain entry.
pub fn set_generic_password_options(password: &[u8], mut options: PasswordOptions) -> Result<()> {
set_password_internal(&mut options, password)
}
/// Get the generic password for the given service and account. If no matching
/// keychain entry exists, fails with error code `errSecItemNotFound`.
#[doc(hidden)]
pub fn get_generic_password(service: &str, account: &str) -> Result<Vec<u8>> {
generic_password(PasswordOptions::new_generic_password(service, account))
}
/// Get the generic password for the given service and account. If no matching
/// keychain entry exists, fails with error code `errSecItemNotFound`.
///
/// See [`PasswordOptions`] and [`new_generic_password`](PasswordOptions::new_generic_password).
///
/// ```rust
/// use security_framework::passwords::{generic_password, PasswordOptions};
/// generic_password(PasswordOptions::new_generic_password("service", "account"));
/// ```
pub fn generic_password(mut options: PasswordOptions) -> Result<Vec<u8>> {
unsafe { options.push_query(kSecReturnData, CFBoolean::from(true)); }
let params = options.to_dictionary();
let mut ret: CFTypeRef = std::ptr::null();
cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?;
get_password_and_release(ret)
}
/// Delete the generic password keychain entry for the given service and account.
/// If none exists, fails with error code `errSecItemNotFound`.
pub fn delete_generic_password(service: &str, account: &str) -> Result<()> {
let options = PasswordOptions::new_generic_password(service, account);
delete_generic_password_options(options)
}
/// Delete the generic password keychain entry for the given service and account.
/// If none exists, fails with error code `errSecItemNotFound`.
///
/// See [`PasswordOptions`] and [`new_generic_password`](PasswordOptions::new_generic_password).
///
/// ```rust
/// use security_framework::passwords::{delete_generic_password_options, PasswordOptions};
/// delete_generic_password_options(PasswordOptions::new_generic_password("service", "account"));
/// ```
pub fn delete_generic_password_options(options: PasswordOptions) -> Result<()> {
let params = options.to_dictionary();
cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
}
/// Set an internet password for the given endpoint parameters.
/// Creates or updates a keychain entry.
#[allow(clippy::too_many_arguments)]
pub fn set_internet_password(
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
password: &[u8],
) -> Result<()> {
let mut options = PasswordOptions::new_internet_password(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
);
set_password_internal(&mut options, password)
}
/// Get the internet password for the given endpoint parameters. If no matching
/// keychain entry exists, fails with error code `errSecItemNotFound`.
pub fn get_internet_password(
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
) -> Result<Vec<u8>> {
let mut options = PasswordOptions::new_internet_password(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
);
unsafe { options.push_query(kSecReturnData, CFBoolean::from(true)); }
let params = options.to_dictionary();
let mut ret: CFTypeRef = std::ptr::null();
cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?;
get_password_and_release(ret)
}
/// Delete the internet password for the given endpoint parameters.
/// If none exists, fails with error code `errSecItemNotFound`.
pub fn delete_internet_password(
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
) -> Result<()> {
let options = PasswordOptions::new_internet_password(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
);
let params = options.to_dictionary();
cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
}
// This starts by trying to create the password with the given query params.
// If the creation attempt reveals that one exists, its password is updated.
fn set_password_internal(options: &mut PasswordOptions, password: &[u8]) -> Result<()> {
#[allow(deprecated)]
let query_without_password = options.query.len();
unsafe { options.push_query(kSecValueData, CFData::from_buffer(password)); }
let params = options.to_dictionary();
let mut ret = std::ptr::null();
let status = unsafe { SecItemAdd(params.as_concrete_TypeRef(), &mut ret) };
if status == errSecDuplicateItem {
#[allow(deprecated)]
let (query, pass) = options.query.split_at(query_without_password);
let params = CFDictionary::from_CFType_pairs(query);
let update = CFDictionary::from_CFType_pairs(pass);
cvt(unsafe { SecItemUpdate(params.as_concrete_TypeRef(), update.as_concrete_TypeRef()) })
} else {
cvt(status)
}
}
// Having retrieved a password entry, this copies and returns the password.
//
// # Safety
// The data element passed in is assumed to have been returned from a Copy
// call, so it's released after we are done with it.
fn get_password_and_release(data: CFTypeRef) -> Result<Vec<u8>> {
if !data.is_null() {
let type_id = unsafe { CFGetTypeID(data) };
if type_id == CFData::type_id() {
let val = unsafe { CFData::wrap_under_create_rule(data as CFDataRef) };
let mut vec = Vec::new();
if !val.is_empty() {
vec.extend_from_slice(val.bytes());
}
return Ok(vec);
}
// unexpected: we got a reference to some other type.
// Release it to make sure there's no leak, but
// we can't return the password in this case.
unsafe { CFRelease(data) };
}
Err(Error::from_code(errSecParam))
}
#[cfg(test)]
mod test {
use super::*;
use security_framework_sys::base::errSecItemNotFound;
#[test]
fn missing_generic() {
let name = "a string not likely to already be in the keychain as service or account";
let result = delete_generic_password(name, name);
match result {
Ok(()) => (), // this is ok because the name _might_ be in the keychain
Err(err) if err.code() == errSecItemNotFound => (),
Err(err) => panic!("missing_generic: delete failed with status: {}", err.code()),
}
let result = get_generic_password(name, name);
match result {
Ok(bytes) => panic!("missing_generic: get returned {bytes:?}"),
Err(err) if err.code() == errSecItemNotFound => (),
Err(err) => panic!("missing_generic: get failed with status: {}", err.code()),
}
let result = delete_generic_password(name, name);
match result {
Ok(()) => panic!("missing_generic: second delete found a password"),
Err(err) if err.code() == errSecItemNotFound => (),
Err(err) => panic!("missing_generic: delete failed with status: {}", err.code()),
}
}
#[test]
fn roundtrip_generic() {
let name = "roundtrip_generic";
set_generic_password(name, name, name.as_bytes()).expect("set_generic_password");
let pass = get_generic_password(name, name).expect("get_generic_password");
assert_eq!(name.as_bytes(), pass);
delete_generic_password(name, name).expect("delete_generic_password");
}
#[test]
fn update_generic() {
let name = "update_generic";
set_generic_password(name, name, name.as_bytes()).expect("set_generic_password");
let alternate = "update_generic_alternate";
set_generic_password(name, name, alternate.as_bytes()).expect("set_generic_password");
let pass = get_generic_password(name, name).expect("get_generic_password");
assert_eq!(pass, alternate.as_bytes());
delete_generic_password(name, name).expect("delete_generic_password");
}
#[test]
fn missing_internet() {
let name = "a string not likely to already be in the keychain as service or account";
let (server, domain, account, path, port, protocol, auth) =
(name, None, name, "/", Some(8080u16), SecProtocolType::HTTP, SecAuthenticationType::Any);
let result = delete_internet_password(server, domain, account, path, port, protocol, auth);
match result {
Ok(()) => (), // this is ok because the name _might_ be in the keychain
Err(err) if err.code() == errSecItemNotFound => (),
Err(err) => panic!("missing_internet: delete failed with status: {}", err.code()),
}
let result = get_internet_password(server, domain, account, path, port, protocol, auth);
match result {
Ok(bytes) => panic!("missing_internet: get returned {bytes:?}"),
Err(err) if err.code() == errSecItemNotFound => (),
Err(err) => panic!("missing_internet: get failed with status: {}", err.code()),
}
let result = delete_internet_password(server, domain, account, path, port, protocol, auth);
match result {
Ok(()) => panic!("missing_internet: second delete found a password"),
Err(err) if err.code() == errSecItemNotFound => (),
Err(err) => panic!("missing_internet: delete failed with status: {}", err.code()),
}
}
#[test]
fn roundtrip_internet() {
let name = "roundtrip_internet";
let (server, domain, account, path, port, protocol, auth) =
(name, None, name, "/", Some(8080u16), SecProtocolType::HTTP, SecAuthenticationType::Any);
set_internet_password(server, domain, account, path, port, protocol, auth, name.as_bytes())
.expect("set_internet_password");
let pass =
get_internet_password(server, domain, account, path, port, protocol, auth).expect("get_internet_password");
assert_eq!(name.as_bytes(), pass);
delete_internet_password(server, domain, account, path, port, protocol, auth)
.expect("delete_internet_password");
}
#[test]
fn update_internet() {
let name = "update_internet";
let (server, domain, account, path, port, protocol, auth) =
(name, None, name, "/", Some(8080u16), SecProtocolType::HTTP, SecAuthenticationType::Any);
// cleanup after failed test
let _ = delete_internet_password(server, domain, account, path, port, protocol, auth);
set_internet_password(server, domain, account, path, port, protocol, auth, name.as_bytes())
.expect("set_internet_password");
let alternate = "alternate_internet_password";
set_internet_password(server, domain, account, path, port, protocol, auth, alternate.as_bytes())
.expect("set_internet_password");
let pass =
get_internet_password(server, domain, account, path, port, protocol, auth).expect("get_internet_password");
assert_eq!(pass, alternate.as_bytes());
delete_internet_password(server, domain, account, path, port, protocol, auth)
.expect("delete_internet_password");
}
}

View File

@@ -0,0 +1,214 @@
//! Support for password options, to be used with the passwords module
// NB: re-export these types in the `passwords` module!
use crate::access_control::SecAccessControl;
use core_foundation::base::{CFOptionFlags, CFType, TCFType};
#[allow(unused_imports)]
use core_foundation::boolean::CFBoolean;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::{CFString, CFStringRef};
use security_framework_sys::access_control::*;
use security_framework_sys::item::{
kSecAttrAccessControl, kSecAttrAccessGroup, kSecAttrAccount, kSecAttrAuthenticationType,
kSecAttrComment, kSecAttrDescription, kSecAttrLabel,
kSecAttrPath, kSecAttrPort, kSecAttrProtocol, kSecAttrSecurityDomain, kSecAttrServer,
kSecAttrService, kSecClass, kSecClassGenericPassword, kSecClassInternetPassword,
};
use security_framework_sys::item::kSecAttrSynchronizable;
use security_framework_sys::item::kSecAttrSynchronizableAny;
#[cfg(any(feature = "OSX_10_15", not(target_os = "macos")))]
use security_framework_sys::item::kSecUseDataProtectionKeychain;
use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType};
/// `PasswordOptions` constructor
pub struct PasswordOptions {
/// query built for the keychain request
#[deprecated(note = "This field should have been private. Please use setters that don't expose CFType")]
pub query: Vec<(CFString, CFType)>,
}
bitflags::bitflags! {
/// The option flags used to configure the evaluation of a `SecAccessControl`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AccessControlOptions: CFOptionFlags {
/** Constraint to access an item with either biometry or passcode. */
const USER_PRESENCE = kSecAccessControlUserPresence;
/** Constraint to access an item with Touch ID for any enrolled fingers, or Face ID. */
const BIOMETRY_ANY = kSecAccessControlBiometryAny;
/** Constraint to access an item with Touch ID for currently enrolled fingers, or from Face ID with the currently enrolled user. */
const BIOMETRY_CURRENT_SET = kSecAccessControlBiometryCurrentSet;
/** Constraint to access an item with a passcode. */
const DEVICE_PASSCODE = kSecAccessControlDevicePasscode;
#[cfg(feature = "OSX_10_15")]
/** Constraint to access an item with a watch. */
const WATCH = kSecAccessControlWatch;
/** Indicates that at least one constraint must be satisfied. */
const OR = kSecAccessControlOr;
/** Indicates that all constraints must be satisfied. */
const AND = kSecAccessControlAnd;
/** Enable a private key to be used in signing a block of data or verifying a signed block. */
const PRIVATE_KEY_USAGE = kSecAccessControlPrivateKeyUsage;
/** Option to use an application-provided password for data encryption key generation. */
const APPLICATION_PASSWORD = kSecAccessControlApplicationPassword;
}
}
impl PasswordOptions {
/// Create a new generic password options
/// Generic passwords are identified by service and account. They have other
/// attributes, but this interface doesn't allow specifying them.
#[must_use]
pub fn new_generic_password(service: &str, account: &str) -> Self {
#[allow(deprecated)]
Self { query: vec![
(
unsafe { CFString::wrap_under_get_rule(kSecClass) },
unsafe { CFString::wrap_under_get_rule(kSecClassGenericPassword).into_CFType() },
),
(unsafe { CFString::wrap_under_get_rule(kSecAttrService) }, CFString::from(service).into_CFType()),
(unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) }, CFString::from(account).into_CFType()),
] }
}
/// Create a new internet password options
/// Internet passwords are identified by a number of attributes.
/// They can have others, but this interface doesn't allow specifying them.
#[must_use]
pub fn new_internet_password(
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
) -> Self {
#[allow(deprecated)]
let mut this = Self { query: vec![
(
unsafe { CFString::wrap_under_get_rule(kSecClass) },
unsafe { CFString::wrap_under_get_rule(kSecClassInternetPassword) }.into_CFType(),
),
(unsafe { CFString::wrap_under_get_rule(kSecAttrServer) }, CFString::from(server).into_CFType()),
(unsafe { CFString::wrap_under_get_rule(kSecAttrPath) }, CFString::from(path).into_CFType()),
(unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) }, CFString::from(account).into_CFType()),
(unsafe { CFString::wrap_under_get_rule(kSecAttrProtocol) }, CFNumber::from(protocol as i32).into_CFType()),
(
unsafe { CFString::wrap_under_get_rule(kSecAttrAuthenticationType) },
CFNumber::from(authentication_type as i32).into_CFType(),
),
] };
if let Some(domain) = security_domain {
unsafe {
this.push_query(kSecAttrSecurityDomain, CFString::from(domain));
}
}
if let Some(port) = port {
unsafe {
this.push_query(kSecAttrPort, CFNumber::from(i32::from(port)));
}
}
this
}
/// Add access control to the password
pub fn set_access_control_options(&mut self, options: AccessControlOptions) {
unsafe {
self.push_query(kSecAttrAccessControl, SecAccessControl::create_with_flags(options.bits()).unwrap());
}
}
/// Add access control to the password
pub fn set_access_control(&mut self, access_control: SecAccessControl) {
unsafe {
self.push_query(kSecAttrAccessControl, access_control);
}
}
/// Add access group to the password
pub fn set_access_group(&mut self, group: &str) {
unsafe {
self.push_query(kSecAttrAccessGroup, CFString::from(group));
}
}
/// Specify whether password is cloud-synchronized, not cloud-synchronized, or either (`None`).
///
/// Note: cloud-synchronized and not-cloud-synchronized passwords are kept
/// in completely different stores, so they are uniquely identified not just
/// by their `service` and `account` but also their cloud-synchronized option.
///
/// If you specify a non-`None` value for this option, any operation you
/// perform - whether set, get, or delete - will only affect the store matching
/// the value: Some(`true`) will only affect the cloud-synchronized store and
/// Some(`false`) will only affect the not-cloud-synchronized store.
///
/// If you specify `None` for this option, the effect depends on your operation:
///
/// - Performing a delete will delete from both stores.
/// - Performing a get will return values from both stores, but since get only
/// returns one value you can't be sure which store that value was in.
/// - Performing a set will update existing values in both stores. _But_, before
/// doing any updates, set will first try to create a new value in the
/// not-cloud-synchronized store (interpreting `None` as `false`). If
/// that creation attempt succeeds, no update will be done of any existing
/// value in the cloud-synchronized store. Thus, only if there is an existing
/// value in the not-cloud-synchronized store will set update the
/// cloud-synchronized store.
pub fn set_access_synchronized(&mut self, synchronized: Option<bool>) {
unsafe {
if let Some(synchronizable) = synchronized {
self.push_query(kSecAttrSynchronizable, CFBoolean::from(synchronizable));
} else {
let either = CFString::wrap_under_get_rule(kSecAttrSynchronizableAny);
self.push_query(kSecAttrSynchronizable, either);
}
}
}
/// Set the comment on the password
pub fn set_comment(&mut self, comment: &str) {
unsafe {
self.push_query(kSecAttrComment, CFString::from(comment));
}
}
/// Add a description to the password
pub fn set_description(&mut self, description: &str) {
unsafe {
self.push_query(kSecAttrDescription, CFString::from(description));
}
}
/// Add a label to the password
pub fn set_label(&mut self, label: &str) {
unsafe {
self.push_query(kSecAttrLabel, CFString::from(label));
}
}
#[cfg(any(feature = "OSX_10_15", not(target_os = "macos")))]
/// Use the data protection keychain (always true except on macOS)
pub fn use_protected_keychain(&mut self) {
unsafe {
self.push_query(kSecUseDataProtectionKeychain, CFBoolean::from(true));
}
}
/// The key must be a `kSec*` constant.
/// Value is any owned ObjC object, like `CFString`.
pub(crate) unsafe fn push_query(&mut self, static_key_constant: CFStringRef, value: impl TCFType) {
#[allow(deprecated)]
self.query.push((
unsafe { CFString::wrap_under_get_rule(static_key_constant) },
value.into_CFType(),
));
}
pub(crate) fn to_dictionary(&self) -> CFDictionary<CFString, CFType> {
#[allow(deprecated)]
CFDictionary::from_CFType_pairs(&self.query[..])
}
}

99
vendor/security-framework/src/policy.rs vendored Normal file
View File

@@ -0,0 +1,99 @@
//! Security Policies support.
use core_foundation::base::{CFOptionFlags, TCFType};
use core_foundation::string::CFString;
use core_foundation::{declare_TCFType, impl_TCFType};
use security_framework_sys::base::{errSecParam, SecPolicyRef};
use security_framework_sys::policy::*;
use std::fmt;
use std::ptr;
use crate::secure_transport::SslProtocolSide;
use crate::Error;
declare_TCFType! {
/// A type representing a certificate validation policy.
SecPolicy, SecPolicyRef
}
impl_TCFType!(SecPolicy, SecPolicyRef, SecPolicyGetTypeID);
unsafe impl Sync for SecPolicy {}
unsafe impl Send for SecPolicy {}
impl fmt::Debug for SecPolicy {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("SecPolicy").finish()
}
}
bitflags::bitflags! {
/// The flags used to specify revocation policy options.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RevocationPolicy: CFOptionFlags {
/// Perform revocation checking using OCSP (Online Certificate Status Protocol).
const OCSP_METHOD = kSecRevocationOCSPMethod;
/// Perform revocation checking using the CRL (Certification Revocation List) method.
const CRL_METHOD = kSecRevocationCRLMethod;
/// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred.
const PREFER_CRL = kSecRevocationPreferCRL;
/// Require a positive response to pass the policy.
const REQUIRE_POSITIVE_RESPONSE = kSecRevocationRequirePositiveResponse;
/// Consult only locally cached replies; do not use network access.
const NETWORK_ACCESS_DISABLED = kSecRevocationNetworkAccessDisabled;
/// Perform either OCSP or CRL checking.
const USE_ANY_METHOD_AVAILABLE = kSecRevocationUseAnyAvailableMethod;
}
}
impl SecPolicy {
/// Creates a `SecPolicy` for evaluating SSL certificate chains.
///
/// The side which you are evaluating should be provided (i.e. pass `SslSslProtocolSide::SERVER` if
/// you are a client looking to validate a server's certificate chain).
pub fn create_ssl(protocol_side: SslProtocolSide, hostname: Option<&str>) -> Self {
let hostname = hostname.map(CFString::new);
let hostname = hostname
.as_ref()
.map(|s| s.as_concrete_TypeRef())
.unwrap_or(ptr::null_mut());
let is_server = protocol_side == SslProtocolSide::SERVER;
unsafe {
let policy = SecPolicyCreateSSL(is_server.into(), hostname);
Self::wrap_under_create_rule(policy)
}
}
/// Creates a `SecPolicy` for checking revocation of certificates.
///
/// If you do not specify this policy creating a `SecTrust` object, the system defaults
/// will be used during evaluation.
pub fn create_revocation(options: RevocationPolicy) -> crate::Result<Self> {
let policy = unsafe { SecPolicyCreateRevocation(options.bits()) };
if policy.is_null() {
Err(Error::from_code(errSecParam))
} else {
Ok(unsafe { Self::wrap_under_create_rule(policy) })
}
}
/// Returns a policy object for the default X.509 policy.
#[must_use]
pub fn create_x509() -> Self {
unsafe {
let policy = SecPolicyCreateBasicX509();
Self::wrap_under_create_rule(policy)
}
}
}
#[cfg(test)]
mod test {
use crate::policy::SecPolicy;
use crate::secure_transport::SslProtocolSide;
#[test]
fn create_ssl() {
SecPolicy::create_ssl(SslProtocolSide::SERVER, Some("certifi.org"));
}
}

39
vendor/security-framework/src/random.rs vendored Normal file
View File

@@ -0,0 +1,39 @@
//! Randomness support.
use security_framework_sys::random::{kSecRandomDefault, SecRandomCopyBytes, SecRandomRef};
use std::io;
/// A source of random data.
pub struct SecRandom(SecRandomRef);
unsafe impl Sync for SecRandom {}
unsafe impl Send for SecRandom {}
impl Default for SecRandom {
#[inline(always)]
fn default() -> Self {
unsafe { Self(kSecRandomDefault) }
}
}
impl SecRandom {
/// Fills the buffer with cryptographically secure random bytes.
pub fn copy_bytes(&self, buf: &mut [u8]) -> io::Result<()> {
if unsafe { SecRandomCopyBytes(self.0, buf.len(), buf.as_mut_ptr().cast()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn basic() {
let mut buf = [0; 10];
SecRandom::default().copy_bytes(&mut buf).unwrap();
}
}

File diff suppressed because it is too large Load Diff

374
vendor/security-framework/src/trust.rs vendored Normal file
View File

@@ -0,0 +1,374 @@
//! Trust evaluation support.
use core_foundation::array::CFArray;
#[cfg(target_os = "macos")]
use core_foundation::array::CFArrayRef;
use core_foundation::base::TCFType;
use core_foundation::data::CFData;
use core_foundation::date::CFDate;
use core_foundation::{declare_TCFType, impl_TCFType};
use core_foundation_sys::base::{Boolean, CFIndex};
use security_framework_sys::trust::*;
use std::ptr;
use crate::base::Result;
use crate::certificate::SecCertificate;
use crate::cvt;
use crate::key::SecKey;
use crate::policy::SecPolicy;
use core_foundation::error::{CFError, CFErrorRef};
/// The result of trust evaluation.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TrustResult(SecTrustResultType);
impl TrustResult {
/// Indicates a denial by the user, do not proceed.
pub const DENY: Self = Self(kSecTrustResultDeny);
/// Indicates a trust policy failure that the user cannot override.
pub const FATAL_TRUST_FAILURE: Self = Self(kSecTrustResultFatalTrustFailure);
/// An invalid setting or result.
pub const INVALID: Self = Self(kSecTrustResultInvalid);
/// An error not related to trust validation.
pub const OTHER_ERROR: Self = Self(kSecTrustResultOtherError);
/// You may proceed.
pub const PROCEED: Self = Self(kSecTrustResultProceed);
/// Indicates a trust policy failure that the user can override.
pub const RECOVERABLE_TRUST_FAILURE: Self = Self(kSecTrustResultRecoverableTrustFailure);
/// The certificate is implicitly trusted.
pub const UNSPECIFIED: Self = Self(kSecTrustResultUnspecified);
}
impl TrustResult {
/// Returns true if the result is "successful" - specifically `PROCEED` or `UNSPECIFIED`.
#[inline]
#[must_use]
pub fn success(self) -> bool {
matches!(self, Self::PROCEED | Self::UNSPECIFIED)
}
}
declare_TCFType! {
/// A type representing a trust evaluation for a certificate.
SecTrust, SecTrustRef
}
impl_TCFType!(SecTrust, SecTrustRef, SecTrustGetTypeID);
unsafe impl Sync for SecTrust {}
unsafe impl Send for SecTrust {}
#[cfg(target_os = "macos")]
bitflags::bitflags! {
/// The option flags used to configure the evaluation of a `SecTrust`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TrustOptions: SecTrustOptionFlags {
/// Allow expired certificates (except for the root certificate).
const ALLOW_EXPIRED = kSecTrustOptionAllowExpired;
/// Allow CA certificates as leaf certificates.
const LEAF_IS_CA = kSecTrustOptionLeafIsCA;
/// Allow network downloads of CA certificates.
const FETCH_ISSUER_FROM_NET = kSecTrustOptionFetchIssuerFromNet;
/// Allow expired root certificates.
const ALLOW_EXPIRED_ROOT = kSecTrustOptionAllowExpiredRoot;
/// Require a positive revocation check for each certificate.
const REQUIRE_REVOCATION_PER_CERT = kSecTrustOptionRequireRevPerCert;
/// Use TrustSettings instead of anchors.
const USE_TRUST_SETTINGS = kSecTrustOptionUseTrustSettings;
/// Treat properly self-signed certificates as anchors implicitly.
const IMPLICIT_ANCHORS = kSecTrustOptionImplicitAnchors;
}
}
impl SecTrust {
/// Creates a `SecTrustRef` that is configured with a certificate chain, for validating
/// that chain against a collection of policies.
pub fn create_with_certificates(
certs: &[SecCertificate],
policies: &[SecPolicy],
) -> Result<Self> {
let cert_array = CFArray::from_CFTypes(certs);
let policy_array = CFArray::from_CFTypes(policies);
let mut trust = ptr::null_mut();
unsafe {
cvt(SecTrustCreateWithCertificates(
cert_array.as_CFTypeRef(),
policy_array.as_CFTypeRef(),
&mut trust,
))?;
Ok(Self(trust))
}
}
/// Sets the date and time against which the certificates in this trust object
/// are verified.
#[inline]
pub fn set_trust_verify_date(&mut self, date: &CFDate) -> Result<()> {
unsafe { cvt(SecTrustSetVerifyDate(self.0, date.as_concrete_TypeRef())) }
}
/// Sets additional anchor certificates used to validate trust.
pub fn set_anchor_certificates(&mut self, certs: &[SecCertificate]) -> Result<()> {
let certs = CFArray::from_CFTypes(certs);
unsafe {
cvt(SecTrustSetAnchorCertificates(
self.0,
certs.as_concrete_TypeRef(),
))
}
}
/// Retrieves the anchor (root) certificates stored by macOS
#[cfg(target_os = "macos")]
pub fn copy_anchor_certificates() -> Result<Vec<SecCertificate>> {
let mut array: CFArrayRef = ptr::null();
unsafe {
cvt(SecTrustCopyAnchorCertificates(&mut array))?;
}
if array.is_null() {
return Ok(vec![]);
}
let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(array) };
Ok(array.into_iter().map(|c| c.clone()).collect())
}
/// If set to `true`, only the certificates specified by
/// `set_anchor_certificates` will be trusted, but not globally trusted
/// certificates.
#[inline]
pub fn set_trust_anchor_certificates_only(&mut self, only: bool) -> Result<()> {
unsafe { cvt(SecTrustSetAnchorCertificatesOnly(self.0, Boolean::from(only))) }
}
/// Sets the policy used to evaluate trust.
#[inline]
pub fn set_policy(&mut self, policy: &SecPolicy) -> Result<()> {
unsafe { cvt(SecTrustSetPolicies(self.0, policy.as_CFTypeRef())) }
}
/// Sets option flags for customizing evaluation of a trust object.
#[cfg(target_os = "macos")]
#[inline]
pub fn set_options(&mut self, options: TrustOptions) -> Result<()> {
unsafe { cvt(SecTrustSetOptions(self.0, options.bits())) }
}
/// Indicates whether this trust object is permitted to
/// fetch missing intermediate certificates from the network.
pub fn get_network_fetch_allowed(&mut self) -> Result<bool> {
let mut allowed = 0;
unsafe { cvt(SecTrustGetNetworkFetchAllowed(self.0, &mut allowed))? };
Ok(allowed != 0)
}
/// Specifies whether this trust object is permitted to
/// fetch missing intermediate certificates from the network.
#[inline]
pub fn set_network_fetch_allowed(&mut self, allowed: bool) -> Result<()> {
unsafe { cvt(SecTrustSetNetworkFetchAllowed(self.0, u8::from(allowed))) }
}
/// Attaches Online Certificate Status Protocol (OSCP) response data
/// to this trust object.
pub fn set_trust_ocsp_response<I: Iterator<Item = impl AsRef<[u8]>>>(
&mut self,
ocsp_response: I,
) -> Result<()> {
let response: Vec<CFData> = ocsp_response
.into_iter()
.map(|bytes| CFData::from_buffer(bytes.as_ref()))
.collect();
let response = CFArray::from_CFTypes(&response);
unsafe { cvt(SecTrustSetOCSPResponse(self.0, response.as_CFTypeRef())) }
}
/// Attaches signed certificate timestamp data to this trust object.
pub fn set_signed_certificate_timestamps<I: Iterator<Item = impl AsRef<[u8]>>>(
&mut self,
scts: I,
) -> Result<()> {
let scts: Vec<CFData> = scts
.into_iter()
.map(|bytes| CFData::from_buffer(bytes.as_ref()))
.collect();
let scts = CFArray::from_CFTypes(&scts);
unsafe { cvt(SecTrustSetSignedCertificateTimestamps(self.0, scts.as_concrete_TypeRef())) }
}
/// Returns the public key for a leaf certificate after it has been evaluated.
#[inline]
pub fn copy_public_key(&mut self) -> Result<SecKey> {
unsafe { Ok(SecKey::wrap_under_create_rule(SecTrustCopyPublicKey(self.0))) }
}
/// Evaluates trust.
#[deprecated(note = "use evaluate_with_error")]
pub fn evaluate(&self) -> Result<TrustResult> {
#[allow(deprecated)]
unsafe {
let mut result = kSecTrustResultInvalid;
cvt(SecTrustEvaluate(self.0, &mut result))?;
Ok(TrustResult(result))
}
}
/// Evaluates trust. Requires macOS 10.14 (checked at runtime) or iOS,
/// otherwise it just calls `evaluate()`.
pub fn evaluate_with_error(&self) -> Result<(), CFError> {
let mut error: CFErrorRef = ::std::ptr::null_mut();
let result = unsafe { SecTrustEvaluateWithError(self.0, &mut error) };
if !result {
assert!(!error.is_null());
// SAFETY: `SecTrustEvaluateWithError` expects us to release
// the error.
let error = unsafe { CFError::wrap_under_create_rule(error) };
return Err(error);
}
Ok(())
}
/// Gets the whole evaluated certificate chain.
///
/// Note: evaluate must first be called on the `SecTrust`.
#[cfg(any(feature = "macos-12", not(target_os = "macos")))]
pub fn chain(&self) -> Vec<SecCertificate> {
let array = unsafe { SecTrustCopyCertificateChain(self.0) };
if array.is_null() {
return vec![];
}
let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(array) };
array.into_iter().map(|c| c.clone()).collect()
}
/// Returns the number of certificates in an evaluated certificate chain.
///
/// Note: evaluate must first be called on the `SecTrust`.
#[inline(always)]
#[must_use]
#[deprecated(note = "deprecated by Apple, use chain(), enable macos-12 feature")]
// FIXME: this should have been usize. Don't expose CFIndex in Rust APIs.
pub fn certificate_count(&self) -> CFIndex {
unsafe { SecTrustGetCertificateCount(self.0) }
}
/// Returns a specific certificate from the certificate chain used to evaluate trust.
///
/// Note: evaluate must first be called on the `SecTrust`.
#[deprecated(note = "deprecated by Apple, use chain(), enable macos-12 feature")]
#[must_use]
pub fn certificate_at_index(&self, ix: CFIndex) -> Option<SecCertificate> {
#[allow(deprecated)]
unsafe {
if self.certificate_count() <= ix {
None
} else {
let certificate = SecTrustGetCertificateAtIndex(self.0, ix);
Some(SecCertificate::wrap_under_get_rule(certificate.cast()))
}
}
}
}
#[cfg(test)]
mod test {
use crate::policy::SecPolicy;
use crate::secure_transport::SslProtocolSide;
use crate::test::certificate;
use crate::trust::SecTrust;
#[test]
#[allow(deprecated)]
fn create_with_certificates() {
let cert = certificate();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
assert!(!trust.evaluate().unwrap().success());
}
#[test]
fn create_with_certificates_new() {
let cert = certificate();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
assert!(trust.evaluate_with_error().is_err());
}
#[test]
#[allow(deprecated)]
fn certificate_count_and_at_index() {
let cert = certificate();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
trust.evaluate().unwrap();
let count = trust.certificate_count();
// 1 (self-signed) or 2 (CA-signed, macOS builds chain)
assert!(count >= 1);
let cert_bytes = trust.certificate_at_index(0).unwrap().to_der();
assert_eq!(cert_bytes, certificate().to_der());
}
#[test]
#[allow(deprecated)]
fn certificate_count_and_at_index_new() {
let cert = certificate();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
assert!(trust.evaluate_with_error().is_err());
let count = trust.certificate_count();
// 1 (self-signed) or 2 (CA-signed, macOS builds chain)
assert!(count >= 1);
let cert_bytes = trust.certificate_at_index(0).unwrap().to_der();
assert_eq!(cert_bytes, certificate().to_der());
}
#[test]
#[allow(deprecated)]
fn certificate_at_index_out_of_bounds() {
let cert = certificate();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
let trust = SecTrust::create_with_certificates(std::slice::from_ref(&cert), std::slice::from_ref(&ssl_policy)).unwrap();
trust.evaluate().unwrap();
assert!(trust.certificate_at_index(10).is_none());
let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
assert!(trust.evaluate_with_error().is_err());
assert!(trust.certificate_at_index(10).is_none());
}
#[test]
#[allow(deprecated)]
fn set_policy() {
let cert = certificate();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io.bogus"));
let mut trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
trust.set_policy(&ssl_policy).unwrap();
assert!(!trust.evaluate().unwrap().success());
}
#[test]
fn set_policy_new() {
let cert = certificate();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io.bogus"));
let mut trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io"));
trust.set_policy(&ssl_policy).unwrap();
assert!(trust.evaluate_with_error().is_err());
}
}

View File

@@ -0,0 +1,298 @@
//! Querying trust settings.
use core_foundation::array::{CFArray, CFArrayRef};
use core_foundation::base::{CFIndex, TCFType};
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use core_foundation_sys::base::CFTypeRef;
use security_framework_sys::base::{errSecNoTrustSettings, errSecSuccess};
use security_framework_sys::trust_settings::*;
use std::ptr;
use crate::base::{Error, Result};
use crate::certificate::SecCertificate;
use crate::cvt;
/// Which set of trust settings to query
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum Domain {
/// Per-user trust settings
User = kSecTrustSettingsDomainUser,
/// Locally administered, system-wide trust settings
Admin = kSecTrustSettingsDomainAdmin,
/// System trust settings
System = kSecTrustSettingsDomainSystem,
}
impl From<Domain> for SecTrustSettingsDomain {
#[inline]
fn from(domain: Domain) -> Self {
match domain {
Domain::User => kSecTrustSettingsDomainUser,
Domain::Admin => kSecTrustSettingsDomainAdmin,
Domain::System => kSecTrustSettingsDomainSystem,
}
}
}
/// Trust settings for a specific certificate in a specific domain
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TrustSettingsForCertificate {
/// Not used
Invalid,
/// This is a root certificate and is trusted, either explicitly or
/// implicitly.
TrustRoot,
/// This is a non-root certificate but is explicitly trusted.
TrustAsRoot,
/// Cert is explicitly distrusted.
Deny,
/// Neither trusted nor distrusted.
Unspecified,
}
impl TrustSettingsForCertificate {
/// Create from `kSecTrustSettingsResult*` constant
fn new(value: i64) -> Self {
if value < 0 || value > i64::from(u32::MAX) {
return Self::Invalid;
}
match value as u32 {
kSecTrustSettingsResultTrustRoot => Self::TrustRoot,
kSecTrustSettingsResultTrustAsRoot => Self::TrustAsRoot,
kSecTrustSettingsResultDeny => Self::Deny,
kSecTrustSettingsResultUnspecified => Self::Unspecified,
_ => Self::Invalid,
}
}
}
/// Allows access to the certificates and their trust settings in a given domain.
pub struct TrustSettings {
domain: Domain,
}
impl TrustSettings {
/// Create a new `TrustSettings` for the given domain.
///
/// You can call `iter()` to discover the certificates with settings in this domain.
///
/// Then you can call `tls_trust_settings_for_certificate()` with a given certificate
/// to learn what the aggregate trust setting for that certificate within this domain.
#[inline(always)]
#[must_use]
pub const fn new(domain: Domain) -> Self {
Self { domain }
}
/// Create an iterator over the certificates with settings in this domain.
/// This produces an empty iterator if there are no such certificates.
pub fn iter(&self) -> Result<TrustSettingsIter> {
let array = unsafe {
let mut array_ptr: CFArrayRef = ptr::null_mut();
// SecTrustSettingsCopyCertificates returns errSecNoTrustSettings
// if no items have trust settings in the given domain. We map
// that to an empty TrustSettings iterator.
match SecTrustSettingsCopyCertificates(self.domain.into(), &mut array_ptr) {
errSecNoTrustSettings => CFArray::from_CFTypes(&[]),
errSecSuccess => CFArray::<SecCertificate>::wrap_under_create_rule(array_ptr),
err => return Err(Error::from_code(err)),
}
};
Ok(TrustSettingsIter { index: 0, array })
}
///set trust settings to ""always trust this root certificate regardless of use.".
/// Sets the trust settings for the provided certificate to "always trust this root certificate
/// regardless of use."
///
/// This method configures the trust settings for the specified certificate, indicating that it should
/// always be trusted as a TLS root certificate, regardless of its usage.
///
/// If successful, the trust settings are updated for the certificate in the given domain. If the
/// certificate had no previous trust settings in the domain, new trust settings are created. If the
/// certificate had existing trust settings, they are replaced with the new settings.
///
/// It is not possible to modify per-user trust settings when not running in a GUI
/// environment, if you try it will return error `2070: errSecInternalComponent`
#[cfg(target_os = "macos")]
pub fn set_trust_settings_always(&self, cert: &SecCertificate) -> Result<()> {
let domain = self.domain;
let trust_settings: CFTypeRef = ptr::null_mut();
cvt(unsafe {
SecTrustSettingsSetTrustSettings(
cert.as_concrete_TypeRef(),
domain.into(),
trust_settings,
)
})
}
/// Returns the aggregate trust setting for the given certificate.
///
/// This tells you whether the certificate should be trusted as a TLS
/// root certificate.
///
/// If the certificate has no trust settings in the given domain, the
/// `errSecItemNotFound` error is returned.
///
/// If the certificate has no specific trust settings for TLS in the
/// given domain `None` is returned.
///
/// Otherwise, the specific trust settings are aggregated and returned.
pub fn tls_trust_settings_for_certificate(&self, cert: &SecCertificate) -> Result<Option<TrustSettingsForCertificate>> {
let trust_settings = unsafe {
let mut array_ptr: CFArrayRef = ptr::null_mut();
let cert_ptr = cert.as_CFTypeRef() as *mut _;
cvt(SecTrustSettingsCopyTrustSettings(cert_ptr, self.domain.into(), &mut array_ptr))?;
CFArray::<CFDictionary>::wrap_under_create_rule(array_ptr)
};
for settings in trust_settings.iter() {
// Reject settings for non-SSL policies
let is_not_ssl_policy = {
let policy_name_key = CFString::from_static_string("kSecTrustSettingsPolicyName");
let ssl_policy_name = CFString::from_static_string("sslServer");
let maybe_name: Option<CFString> = settings
.find(policy_name_key.as_CFTypeRef().cast())
.map(|name| unsafe { CFString::wrap_under_get_rule((*name).cast()) });
matches!(maybe_name, Some(name) if name != ssl_policy_name)
};
if is_not_ssl_policy {
continue;
}
// Evaluate "effective trust settings" for this usage constraint.
let maybe_trust_result = {
let settings_result_key = CFString::from_static_string("kSecTrustSettingsResult");
settings
.find(settings_result_key.as_CFTypeRef().cast())
.map(|num| unsafe { CFNumber::wrap_under_get_rule((*num).cast()) })
.and_then(|num| num.to_i64())
};
// "Note that an empty Trust Settings array means "always trust this cert,
// with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"."
let trust_result = TrustSettingsForCertificate::new(maybe_trust_result
.unwrap_or_else(|| i64::from(kSecTrustSettingsResultTrustRoot)));
match trust_result {
TrustSettingsForCertificate::Unspecified |
TrustSettingsForCertificate::Invalid => { continue; },
_ => return Ok(Some(trust_result)),
}
}
// There were no more specific settings. This might mean the certificate
// is to be trusted anyway (since, eg, it's in system store), but leave
// the caller to make this decision.
Ok(None)
}
}
/// Iterator over certificates.
pub struct TrustSettingsIter {
array: CFArray<SecCertificate>,
index: CFIndex,
}
impl Iterator for TrustSettingsIter {
type Item = SecCertificate;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.array.len() {
None
} else {
let cert = self.array.get(self.index).unwrap();
self.index += 1;
Some(cert.clone())
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let left = (self.array.len() as usize).saturating_sub(self.index as usize);
(left, Some(left))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::test::certificate;
fn list_for_domain(domain: Domain) {
println!("--- domain: {domain:?}");
let ts = TrustSettings::new(domain);
let iterator = ts.iter().unwrap();
for (i, cert) in iterator.enumerate() {
println!("cert({i:?}) = {cert:?}");
println!(" settings = {:?}", ts.tls_trust_settings_for_certificate(&cert));
}
println!("---");
}
#[test]
fn list_for_user() {
list_for_domain(Domain::User);
}
#[test]
fn list_for_system() {
list_for_domain(Domain::System);
}
#[test]
fn list_for_admin() {
list_for_domain(Domain::Admin);
}
#[test]
fn test_system_certs_are_present() {
let system = TrustSettings::new(Domain::System).iter().unwrap().count();
// 168 at the time of writing
assert!(system > 100);
}
#[test]
fn test_isrg_root_exists_and_is_trusted() {
let ts = TrustSettings::new(Domain::System);
assert_eq!(
ts.iter()
.unwrap()
.find(|cert| cert.subject_summary() == "ISRG Root X1")
.and_then(|cert| ts.tls_trust_settings_for_certificate(&cert).unwrap()),
None
);
// ^ this is a case where None means "always trust", according to Apple docs:
//
// "Note that an empty Trust Settings array means "always trust this cert,
// with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"."
}
#[test]
fn test_unknown_cert_is_not_trusted() {
let ts = TrustSettings::new(Domain::System);
let cert = certificate();
assert_eq!(
ts.tls_trust_settings_for_certificate(&cert).err().unwrap().message(),
Some("The specified item could not be found in the keychain.".into())
);
}
}