RPM build fix (reverted CI changes which will need to be un-reverted or made conditional) and vendor Rust dependencies to make builds much faster in any CI system.
This commit is contained in:
779
zeroidc/vendor/security-framework/src/authorization.rs
vendored
Normal file
779
zeroidc/vendor/security-framework/src/authorization.rs
vendored
Normal file
@@ -0,0 +1,779 @@
|
||||
//! 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};
|
||||
use core_foundation::base::{CFTypeRef, TCFType};
|
||||
use core_foundation::bundle::CFBundleRef;
|
||||
use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
|
||||
use core_foundation::string::{CFString, CFStringRef};
|
||||
use security_framework_sys::authorization as sys;
|
||||
use security_framework_sys::base::errSecConversionError;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::c_void;
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
ffi::{CStr, CString},
|
||||
fs::File,
|
||||
};
|
||||
use std::{convert::TryInto, marker::PhantomData};
|
||||
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.
|
||||
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() -> Flags {
|
||||
Flags::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).
|
||||
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]
|
||||
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<'a> Drop for AuthorizationItemSet<'a> {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
AuthorizationItemSetStorage {
|
||||
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)]
|
||||
pub fn new() -> AuthorizationItemSetBuilder {
|
||||
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.
|
||||
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() as *mut sys::AuthorizationItem,
|
||||
};
|
||||
|
||||
self.storage
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by `Authorization::set_item` to define the rules of he right.
|
||||
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 = Authorization {
|
||||
handle: unsafe { handle.assume_init() },
|
||||
free_flags: Default::default(),
|
||||
};
|
||||
|
||||
Ok(auth)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Authorization {
|
||||
/// Creates an authorization object which has no environment or associated
|
||||
/// rights.
|
||||
#[inline]
|
||||
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.
|
||||
pub fn new(
|
||||
rights: Option<AuthorizationItemSetStorage>,
|
||||
environment: Option<AuthorizationItemSetStorage>,
|
||||
flags: Flags,
|
||||
) -> Result<Self> {
|
||||
let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| {
|
||||
&r.set as *const sys::AuthorizationItemSet
|
||||
});
|
||||
|
||||
let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| {
|
||||
&e.set as *const 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(Authorization {
|
||||
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).
|
||||
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())
|
||||
}
|
||||
|
||||
// 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() as _).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::*;
|
||||
use core_foundation::string::CFString;
|
||||
|
||||
#[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",
|
||||
option_env!("USER").expect("You must set the USER environment variable"),
|
||||
)?
|
||||
.add_string(
|
||||
"password",
|
||||
option_env!("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 option_env!("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 option_env!("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 option_env!("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(())
|
||||
}
|
||||
}
|
||||
92
zeroidc/vendor/security-framework/src/base.rs
vendored
Normal file
92
zeroidc/vendor/security-framework/src/base.rs
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
//! Support types for other modules.
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation_sys::base::OSStatus;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::num::NonZeroI32;
|
||||
use std::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]
|
||||
pub fn from_code(code: OSStatus) -> Self {
|
||||
Self(NonZeroI32::new(code as i32).unwrap_or_else(|| NonZeroI32::new(1).unwrap()))
|
||||
}
|
||||
|
||||
/// Returns a string describing the current error, if available.
|
||||
#[inline(always)]
|
||||
pub fn message(self) -> Option<String> {
|
||||
self.inner_message()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[inline(always)]
|
||||
fn inner_message(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the code of the current error.
|
||||
#[inline(always)]
|
||||
pub 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 {}
|
||||
283
zeroidc/vendor/security-framework/src/certificate.rs
vendored
Normal file
283
zeroidc/vendor/security-framework/src/certificate.rs
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
//! Certificate support.
|
||||
|
||||
use core_foundation::array::{CFArray, CFArrayRef};
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation::data::CFData;
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation_sys::base::kCFAllocatorDefault;
|
||||
use security_framework_sys::base::{errSecParam, SecCertificateRef};
|
||||
#[cfg(target_os = "ios")]
|
||||
use security_framework_sys::base::{errSecSuccess, errSecNotTrusted};
|
||||
use security_framework_sys::certificate::*;
|
||||
use std::fmt;
|
||||
use std::ptr;
|
||||
|
||||
use crate::base::{Error, Result};
|
||||
use crate::cvt;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use crate::key;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use core_foundation::base::FromVoid;
|
||||
#[cfg(any(feature = "OSX_10_13", target_os = "ios"))]
|
||||
use core_foundation::error::{CFError, CFErrorRef};
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use core_foundation::number::CFNumber;
|
||||
#[cfg(feature = "serial-number-bigint")]
|
||||
use num_bigint::BigUint;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use security_framework_sys::item::*;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use std::ops::Deref;
|
||||
|
||||
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.
|
||||
pub fn to_der(&self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let der_data = SecCertificateCopyData(self.0);
|
||||
CFData::wrap_under_create_rule(der_data).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a human readable summary of this certificate.
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
/// Returns DER encoded X.509 distinguished name of the certificate issuer.
|
||||
pub fn issuer(&self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let issuer = SecCertificateCopyNormalizedIssuerSequence(self.0);
|
||||
CFData::wrap_under_create_rule(issuer).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
/// Returns DER encoded X.509 distinguished name of the certificate subject.
|
||||
pub fn subject(&self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let subject = SecCertificateCopyNormalizedSubjectSequence(self.0);
|
||||
CFData::wrap_under_create_rule(subject).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_13", target_os = "ios"))]
|
||||
/// 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() {
|
||||
Err(CFError::wrap_under_create_rule(error))
|
||||
} else {
|
||||
Ok(CFData::wrap_under_create_rule(serial_number).to_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Use `BigUint::from_bytes_be(serial_number_bytes())` instead
|
||||
#[deprecated(note = "use serial_number_bytes()")]
|
||||
#[cfg(feature = "serial-number-bigint")]
|
||||
pub fn serial_number(&self) -> Result<BigUint, CFError> {
|
||||
Ok(BigUint::from_bytes_be(&self.serial_number_bytes()?))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
/// 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))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
fn pk_to_der(&self, public_key: key::SecKey) -> Option<Vec<u8>> {
|
||||
let public_key_attributes = public_key.attributes();
|
||||
let public_key_type = public_key_attributes
|
||||
.find(unsafe { kSecAttrKeyType } as *const ::std::os::raw::c_void)?;
|
||||
let public_keysize = public_key_attributes
|
||||
.find(unsafe { kSecAttrKeySizeInBits } as *const ::std::os::raw::c_void)?;
|
||||
let public_keysize = unsafe { CFNumber::from_void(*public_keysize.deref()) };
|
||||
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.deref() as _) },
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
/// 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))?;
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
trust.evaluate()?;
|
||||
#[cfg(target_os = "ios")]
|
||||
cvt(match trust.evaluate_with_error() {
|
||||
Ok(_) => errSecSuccess,
|
||||
Err(_) => errSecNotTrusted,
|
||||
})?;
|
||||
trust.copy_public_key()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
fn get_asn1_header_bytes(pkt: CFString, ksz: u32) -> Option<&'static [u8]> {
|
||||
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
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
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,
|
||||
];
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
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,
|
||||
];
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
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,
|
||||
];
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
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;
|
||||
#[cfg(feature = "serial-number-bigint")]
|
||||
use num_bigint::BigUint;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
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]
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
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]
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serial-number-bigint")]
|
||||
#[allow(deprecated)]
|
||||
fn serial_number() {
|
||||
let cert = certificate();
|
||||
let serial_number = cert.serial_number().unwrap();
|
||||
assert_eq!(BigUint::from(16452297291294946383_u128), serial_number);
|
||||
}
|
||||
}
|
||||
244
zeroidc/vendor/security-framework/src/cipher_suite.rs
vendored
Normal file
244
zeroidc/vendor/security-framework/src/cipher_suite.rs
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
//! 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)]
|
||||
pub fn from_raw(raw: SSLCipherSuite) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub 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
|
||||
}
|
||||
50
zeroidc/vendor/security-framework/src/dlsym.rs
vendored
Normal file
50
zeroidc/vendor/security-framework/src/dlsym.rs
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// dlsym.rs is taken from mio
|
||||
// https://github.com/carllerche/mio/blob/master/src/sys/unix/dlsym.rs
|
||||
|
||||
use std::marker;
|
||||
use std::mem;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use libc;
|
||||
|
||||
macro_rules! dlsym {
|
||||
(fn $name:ident($($t:ty),*) -> $ret:ty) => (
|
||||
#[allow(bad_style)]
|
||||
static $name: $crate::dlsym::DlSym<unsafe extern fn($($t),*) -> $ret> =
|
||||
$crate::dlsym::DlSym {
|
||||
name: concat!(stringify!($name), "\0"),
|
||||
addr: ::std::sync::atomic::AtomicUsize::new(0),
|
||||
_marker: ::std::marker::PhantomData,
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
pub struct DlSym<F> {
|
||||
pub name: &'static str,
|
||||
pub addr: AtomicUsize,
|
||||
pub _marker: marker::PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<F> DlSym<F> {
|
||||
pub fn get(&self) -> Option<&F> {
|
||||
assert_eq!(mem::size_of::<F>(), mem::size_of::<usize>());
|
||||
unsafe {
|
||||
if self.addr.load(Ordering::SeqCst) == 0 {
|
||||
self.addr.store(fetch(self.name), Ordering::SeqCst);
|
||||
}
|
||||
if self.addr.load(Ordering::SeqCst) == 1 {
|
||||
None
|
||||
} else {
|
||||
mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn fetch(name: &str) -> usize {
|
||||
assert_eq!(name.as_bytes()[name.len() - 1], 0);
|
||||
match libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize {
|
||||
0 => 1,
|
||||
n => n,
|
||||
}
|
||||
}
|
||||
68
zeroidc/vendor/security-framework/src/identity.rs
vendored
Normal file
68
zeroidc/vendor/security-framework/src/identity.rs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
//! Identity support.
|
||||
|
||||
use core_foundation::base::TCFType;
|
||||
use security_framework_sys::base::SecIdentityRef;
|
||||
use security_framework_sys::identity::*;
|
||||
use std::fmt;
|
||||
use std::ptr;
|
||||
|
||||
use crate::base::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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::SecIdentity;
|
||||
|
||||
#[test]
|
||||
fn identity_has_send_bound() {
|
||||
fn assert_send<T: Send>() {}
|
||||
assert_send::<SecIdentity>();
|
||||
}
|
||||
}
|
||||
173
zeroidc/vendor/security-framework/src/import_export.rs
vendored
Normal file
173
zeroidc/vendor/security-framework/src/import_export.rs
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
//! 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.
|
||||
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>,
|
||||
_p: (),
|
||||
}
|
||||
|
||||
/// 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 crate::Pkcs12ImportOptionsInternals for Pkcs12ImportOptions {
|
||||
#[inline(always)]
|
||||
fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
|
||||
self.keychain = Some(keychain);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn access(&mut self, access: SecAccess) -> &mut Self {
|
||||
self.access = Some(access);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Pkcs12ImportOptions {
|
||||
/// Creates a new builder with default options.
|
||||
#[inline(always)]
|
||||
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(ref 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 as *const _).to_string());
|
||||
let key_id = raw_item
|
||||
.find(kSecImportItemKeyID)
|
||||
.map(|key_id| CFData::wrap_under_get_rule(*key_id as *const _).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 as *const _).map(
|
||||
|cert_chain| {
|
||||
CFArray::<SecCertificate>::wrap_under_get_rule(*cert_chain as *const _)
|
||||
.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,
|
||||
_p: (),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn import_setup(&self, options: &mut Vec<(CFString, CFType)>) {
|
||||
unsafe {
|
||||
if let Some(ref keychain) = self.keychain {
|
||||
options.push((
|
||||
CFString::wrap_under_get_rule(kSecImportExportKeychain),
|
||||
keychain.as_CFType(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(ref 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());
|
||||
}
|
||||
}
|
||||
423
zeroidc/vendor/security-framework/src/item.rs
vendored
Normal file
423
zeroidc/vendor/security-framework/src/item.rs
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
//! Support to search for items in a keychain.
|
||||
|
||||
use core_foundation::array::CFArray;
|
||||
use core_foundation::base::{CFType, TCFType};
|
||||
use core_foundation::boolean::CFBoolean;
|
||||
use core_foundation::data::CFData;
|
||||
use core_foundation::date::CFDate;
|
||||
use core_foundation::dictionary::CFDictionary;
|
||||
use core_foundation::number::CFNumber;
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation_sys::base::{CFCopyDescription, CFGetTypeID, CFRelease, CFTypeRef};
|
||||
use core_foundation_sys::string::CFStringRef;
|
||||
use security_framework_sys::item::*;
|
||||
use security_framework_sys::keychain_item::SecItemCopyMatching;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::ptr;
|
||||
|
||||
use crate::base::Result;
|
||||
use crate::certificate::SecCertificate;
|
||||
use crate::cvt;
|
||||
use crate::identity::SecIdentity;
|
||||
use crate::key::SecKey;
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::os::macos::keychain::SecKeychain;
|
||||
|
||||
/// Specifies the type of items to search for.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ItemClass(CFStringRef);
|
||||
|
||||
impl ItemClass {
|
||||
/// Look for `SecKeychainItem`s corresponding to generic passwords.
|
||||
#[inline(always)]
|
||||
pub fn generic_password() -> Self {
|
||||
unsafe { Self(kSecClassGenericPassword) }
|
||||
}
|
||||
|
||||
/// Look for `SecKeychainItem`s corresponding to internet passwords.
|
||||
#[inline(always)]
|
||||
pub fn internet_password() -> Self {
|
||||
unsafe { Self(kSecClassInternetPassword) }
|
||||
}
|
||||
|
||||
/// Look for `SecCertificate`s.
|
||||
#[inline(always)]
|
||||
pub fn certificate() -> Self {
|
||||
unsafe { Self(kSecClassCertificate) }
|
||||
}
|
||||
|
||||
/// Look for `SecKey`s.
|
||||
#[inline(always)]
|
||||
pub fn key() -> Self {
|
||||
unsafe { Self(kSecClassKey) }
|
||||
}
|
||||
|
||||
/// Look for `SecIdentity`s.
|
||||
#[inline(always)]
|
||||
pub fn identity() -> Self {
|
||||
unsafe { Self(kSecClassIdentity) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_value(self) -> CFType {
|
||||
unsafe { CFType::wrap_under_get_rule(self.0 as *const _) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies the number of results returned by a search
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Limit {
|
||||
/// Always return all results
|
||||
All,
|
||||
|
||||
/// Return up to the specified number of results
|
||||
Max(i64),
|
||||
}
|
||||
|
||||
impl Limit {
|
||||
#[inline]
|
||||
fn to_value(self) -> CFType {
|
||||
match self {
|
||||
Self::All => unsafe { CFString::wrap_under_get_rule(kSecMatchLimitAll).as_CFType() },
|
||||
Self::Max(l) => CFNumber::from(l).as_CFType(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Limit {
|
||||
#[inline]
|
||||
fn from(limit: i64) -> Self {
|
||||
Self::Max(limit)
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder type to search for items in keychains.
|
||||
#[derive(Default)]
|
||||
pub struct ItemSearchOptions {
|
||||
#[cfg(target_os = "macos")]
|
||||
keychains: Option<CFArray<SecKeychain>>,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
keychains: Option<CFArray<CFType>>,
|
||||
class: Option<ItemClass>,
|
||||
load_refs: bool,
|
||||
load_attributes: bool,
|
||||
load_data: bool,
|
||||
limit: Option<Limit>,
|
||||
label: Option<CFString>,
|
||||
access_group: Option<CFString>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl crate::ItemSearchOptionsInternals for ItemSearchOptions {
|
||||
#[inline]
|
||||
fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
|
||||
self.keychains = Some(CFArray::from_CFTypes(keychains));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemSearchOptions {
|
||||
/// Creates a new builder with default options.
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Search only for items of the specified class.
|
||||
#[inline(always)]
|
||||
pub fn class(&mut self, class: ItemClass) -> &mut Self {
|
||||
self.class = Some(class);
|
||||
self
|
||||
}
|
||||
|
||||
/// Load Security Framework objects (`SecCertificate`, `SecKey`, etc) for
|
||||
/// the results.
|
||||
#[inline(always)]
|
||||
pub fn load_refs(&mut self, load_refs: bool) -> &mut Self {
|
||||
self.load_refs = load_refs;
|
||||
self
|
||||
}
|
||||
|
||||
/// Load Security Framework object attributes for
|
||||
/// the results.
|
||||
#[inline(always)]
|
||||
pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self {
|
||||
self.load_attributes = load_attributes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Load Security Framework objects data for
|
||||
/// the results.
|
||||
#[inline(always)]
|
||||
pub fn load_data(&mut self, load_data: bool) -> &mut Self {
|
||||
self.load_data = load_data;
|
||||
self
|
||||
}
|
||||
|
||||
/// Limit the number of search results.
|
||||
///
|
||||
/// If this is not called, the default limit is 1.
|
||||
#[inline(always)]
|
||||
pub fn limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self {
|
||||
self.limit = Some(limit.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Search for an item with the given label.
|
||||
#[inline(always)]
|
||||
pub fn label(&mut self, label: &str) -> &mut Self {
|
||||
self.label = Some(CFString::new(label));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets kSecAttrAccessGroup to kSecAttrAccessGroupToken
|
||||
#[inline(always)]
|
||||
pub fn access_group_token(&mut self) -> &mut Self {
|
||||
self.access_group = unsafe { Some(CFString::wrap_under_get_rule(kSecAttrAccessGroupToken)) };
|
||||
self
|
||||
}
|
||||
|
||||
/// Search for objects.
|
||||
pub fn search(&self) -> Result<Vec<SearchResult>> {
|
||||
unsafe {
|
||||
let mut params = vec![];
|
||||
|
||||
if let Some(ref keychains) = self.keychains {
|
||||
params.push((
|
||||
CFString::wrap_under_get_rule(kSecMatchSearchList),
|
||||
keychains.as_CFType(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(class) = self.class {
|
||||
params.push((CFString::wrap_under_get_rule(kSecClass), class.to_value()));
|
||||
}
|
||||
|
||||
if self.load_refs {
|
||||
params.push((
|
||||
CFString::wrap_under_get_rule(kSecReturnRef),
|
||||
CFBoolean::true_value().as_CFType(),
|
||||
));
|
||||
}
|
||||
|
||||
if self.load_attributes {
|
||||
params.push((
|
||||
CFString::wrap_under_get_rule(kSecReturnAttributes),
|
||||
CFBoolean::true_value().as_CFType(),
|
||||
));
|
||||
}
|
||||
|
||||
if self.load_data {
|
||||
params.push((
|
||||
CFString::wrap_under_get_rule(kSecReturnData),
|
||||
CFBoolean::true_value().as_CFType(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(limit) = self.limit {
|
||||
params.push((
|
||||
CFString::wrap_under_get_rule(kSecMatchLimit),
|
||||
limit.to_value(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(ref label) = self.label {
|
||||
params.push((
|
||||
CFString::wrap_under_get_rule(kSecAttrLabel),
|
||||
label.as_CFType(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(ref access_group) = self.access_group {
|
||||
params.push((
|
||||
CFString::wrap_under_get_rule(kSecAttrAccessGroup),
|
||||
access_group.as_CFType(),
|
||||
));
|
||||
}
|
||||
|
||||
let params = CFDictionary::from_CFType_pairs(¶ms);
|
||||
|
||||
let mut ret = ptr::null();
|
||||
cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
|
||||
let type_id = CFGetTypeID(ret);
|
||||
|
||||
let mut items = vec![];
|
||||
|
||||
if type_id == CFArray::<CFType>::type_id() {
|
||||
let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _);
|
||||
for item in array.iter() {
|
||||
items.push(get_item(item.as_CFTypeRef()));
|
||||
}
|
||||
} else {
|
||||
items.push(get_item(ret));
|
||||
// This is a bit janky, but get_item uses wrap_under_get_rule
|
||||
// which bumps the refcount but we want create semantics
|
||||
CFRelease(ret);
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_item(item: CFTypeRef) -> SearchResult {
|
||||
let type_id = CFGetTypeID(item);
|
||||
|
||||
if type_id == CFData::type_id() {
|
||||
let data = CFData::wrap_under_get_rule(item as *mut _);
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(data.bytes());
|
||||
return SearchResult::Data(buf);
|
||||
}
|
||||
|
||||
if type_id == CFDictionary::<*const u8, *const u8>::type_id() {
|
||||
return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::os::macos::keychain_item::SecKeychainItem;
|
||||
if type_id == SecKeychainItem::type_id() {
|
||||
return SearchResult::Ref(Reference::KeychainItem(
|
||||
SecKeychainItem::wrap_under_get_rule(item as *mut _),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let reference = if type_id == SecCertificate::type_id() {
|
||||
Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
|
||||
} else if type_id == SecKey::type_id() {
|
||||
Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
|
||||
} else if type_id == SecIdentity::type_id() {
|
||||
Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
|
||||
} else {
|
||||
panic!("Got bad type from SecItemCopyMatching: {}", type_id);
|
||||
};
|
||||
|
||||
SearchResult::Ref(reference)
|
||||
}
|
||||
|
||||
/// An enum including all objects whose references can be returned from a search.
|
||||
/// Note that generic _Keychain Items_, such as passwords and preferences, do
|
||||
/// not have specific object types; they are modeled using dictionaries and so
|
||||
/// are available directly as search results in variant `SearchResult::Dict`.
|
||||
#[derive(Debug)]
|
||||
pub enum Reference {
|
||||
/// A `SecIdentity`.
|
||||
Identity(SecIdentity),
|
||||
/// A `SecCertificate`.
|
||||
Certificate(SecCertificate),
|
||||
/// A `SecKey`.
|
||||
Key(SecKey),
|
||||
/// A `SecKeychainItem`.
|
||||
///
|
||||
/// Only defined on OSX
|
||||
#[cfg(target_os = "macos")]
|
||||
KeychainItem(crate::os::macos::keychain_item::SecKeychainItem),
|
||||
#[doc(hidden)]
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
/// An individual search result.
|
||||
pub enum SearchResult {
|
||||
/// A reference to the Security Framework object, if asked for.
|
||||
Ref(Reference),
|
||||
/// A dictionary of data about the Security Framework object, if asked for.
|
||||
Dict(CFDictionary),
|
||||
/// The Security Framework object as bytes, if asked for.
|
||||
Data(Vec<u8>),
|
||||
/// An unknown representation of the Security Framework object.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SearchResult {
|
||||
#[cold]
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Ref(ref reference) => fmt
|
||||
.debug_struct("SearchResult::Ref")
|
||||
.field("reference", reference)
|
||||
.finish(),
|
||||
Self::Data(ref buf) => fmt
|
||||
.debug_struct("SearchResult::Data")
|
||||
.field("data", buf)
|
||||
.finish(),
|
||||
Self::Dict(_) => {
|
||||
let mut debug = fmt.debug_struct("SearchResult::Dict");
|
||||
for (k, v) in self.simplify_dict().unwrap() {
|
||||
debug.field(&k, &v);
|
||||
}
|
||||
debug.finish()
|
||||
}
|
||||
Self::Other => write!(fmt, "SearchResult::Other"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchResult {
|
||||
/// If the search result is a `CFDict`, simplify that to a
|
||||
/// `HashMap<String, String>`. This transformation isn't
|
||||
/// comprehensive, it only supports CFString, CFDate, and CFData
|
||||
/// value types.
|
||||
pub fn simplify_dict(&self) -> Option<HashMap<String, String>> {
|
||||
match *self {
|
||||
Self::Dict(ref d) => unsafe {
|
||||
let mut retmap = HashMap::new();
|
||||
let (keys, values) = d.get_keys_and_values();
|
||||
for (k, v) in keys.iter().zip(values.iter()) {
|
||||
let keycfstr = CFString::wrap_under_get_rule(*k as *const _);
|
||||
let val: String = match CFGetTypeID(*v) {
|
||||
cfstring if cfstring == CFString::type_id() => {
|
||||
format!("{}", CFString::wrap_under_get_rule(*v as *const _))
|
||||
}
|
||||
cfdata if cfdata == CFData::type_id() => {
|
||||
let buf = CFData::wrap_under_get_rule(*v as *const _);
|
||||
let mut vec = Vec::new();
|
||||
vec.extend_from_slice(buf.bytes());
|
||||
format!("{}", String::from_utf8_lossy(&vec))
|
||||
}
|
||||
cfdate if cfdate == CFDate::type_id() => format!(
|
||||
"{}",
|
||||
CFString::wrap_under_create_rule(CFCopyDescription(*v))
|
||||
),
|
||||
_ => String::from("unknown"),
|
||||
};
|
||||
retmap.insert(format!("{}", keycfstr), val);
|
||||
}
|
||||
Some(retmap)
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn find_nothing() {
|
||||
assert!(ItemSearchOptions::new().search().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limit_two() {
|
||||
let results = ItemSearchOptions::new()
|
||||
.class(ItemClass::certificate())
|
||||
.limit(2)
|
||||
.search()
|
||||
.unwrap();
|
||||
assert_eq!(results.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn limit_all() {
|
||||
let results = ItemSearchOptions::new()
|
||||
.class(ItemClass::certificate())
|
||||
.limit(Limit::All)
|
||||
.search()
|
||||
.unwrap();
|
||||
assert!(results.len() >= 2);
|
||||
}
|
||||
}
|
||||
84
zeroidc/vendor/security-framework/src/key.rs
vendored
Normal file
84
zeroidc/vendor/security-framework/src/key.rs
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
//! Encryption key support
|
||||
|
||||
use core_foundation::base::TCFType;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use core_foundation::base::ToVoid;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use core_foundation::data::CFData;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use core_foundation::dictionary::CFDictionary;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use core_foundation::error::{CFError, CFErrorRef};
|
||||
use security_framework_sys::base::SecKeyRef;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
pub use security_framework_sys::key::Algorithm;
|
||||
use security_framework_sys::key::SecKeyGetTypeID;
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
use security_framework_sys::key::{
|
||||
SecKeyCopyAttributes, SecKeyCopyExternalRepresentation, SecKeyCreateSignature,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
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 {
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
/// Translates to SecKeyCopyAttributes
|
||||
pub fn attributes(&self) -> CFDictionary {
|
||||
let pka = unsafe { SecKeyCopyAttributes(self.to_void() as _) };
|
||||
unsafe { CFDictionary::wrap_under_create_rule(pka) }
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
/// Translates to SecKeyCopyExternalRepresentation
|
||||
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) })
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
|
||||
/// Creates the cryptographic signature for a block of data using a private
|
||||
/// key and specified algorithm.
|
||||
pub fn create_signature(
|
||||
&self,
|
||||
algorithm: Algorithm,
|
||||
input: impl AsRef<[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_ref()).as_concrete_TypeRef(),
|
||||
&mut error,
|
||||
)
|
||||
};
|
||||
|
||||
if !error.is_null() {
|
||||
Err(unsafe { CFError::wrap_under_create_rule(error) })
|
||||
} else {
|
||||
let output = unsafe { CFData::wrap_under_create_rule(output) };
|
||||
Ok(output.to_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME
|
||||
impl fmt::Debug for SecKey {
|
||||
#[cold]
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(fmt, "SecKey")
|
||||
}
|
||||
}
|
||||
82
zeroidc/vendor/security-framework/src/lib.rs
vendored
Normal file
82
zeroidc/vendor/security-framework/src/lib.rs
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
//! Wrappers around the OSX Security Framework.
|
||||
#![warn(missing_docs)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(clippy::manual_non_exhaustive)] // MSRV
|
||||
|
||||
#[macro_use]
|
||||
extern crate core_foundation;
|
||||
|
||||
use core_foundation_sys::base::OSStatus;
|
||||
use security_framework_sys::base::errSecSuccess;
|
||||
|
||||
use crate::base::{Error, Result};
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::os::macos::access::SecAccess;
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::os::macos::keychain::SecKeychain;
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! p {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(s) => s,
|
||||
Err(e) => panic!("{:?}", e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "OSX_10_13"), any(feature = "alpn", feature = "session-tickets")))]
|
||||
#[macro_use]
|
||||
mod dlsym;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod authorization;
|
||||
pub mod base;
|
||||
pub mod certificate;
|
||||
pub mod cipher_suite;
|
||||
pub mod identity;
|
||||
pub mod import_export;
|
||||
pub mod item;
|
||||
pub mod key;
|
||||
pub mod os;
|
||||
pub mod passwords;
|
||||
pub mod policy;
|
||||
pub mod random;
|
||||
pub mod secure_transport;
|
||||
pub mod trust;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod trust_settings;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
trait Pkcs12ImportOptionsInternals {
|
||||
fn keychain(&mut self, keychain: SecKeychain) -> &mut Self;
|
||||
fn access(&mut self, access: SecAccess) -> &mut Self;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
trait ItemSearchOptionsInternals {
|
||||
fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self;
|
||||
}
|
||||
|
||||
trait AsInner {
|
||||
type Inner;
|
||||
fn as_inner(&self) -> Self::Inner;
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
pub fn certificate() -> SecCertificate {
|
||||
let certificate = include_bytes!("../test/server.der");
|
||||
p!(SecCertificate::from_der(certificate))
|
||||
}
|
||||
}
|
||||
14
zeroidc/vendor/security-framework/src/os/macos/access.rs
vendored
Normal file
14
zeroidc/vendor/security-framework/src/os/macos/access.rs
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
//! Access control functionality.
|
||||
|
||||
use core_foundation::base::TCFType;
|
||||
use security_framework_sys::access::SecAccessGetTypeID;
|
||||
use security_framework_sys::base::SecAccessRef;
|
||||
|
||||
declare_TCFType! {
|
||||
/// A type representing access control settings.
|
||||
SecAccess, SecAccessRef
|
||||
}
|
||||
impl_TCFType!(SecAccess, SecAccessRef, SecAccessGetTypeID);
|
||||
|
||||
unsafe impl Sync for SecAccess {}
|
||||
unsafe impl Send for SecAccess {}
|
||||
266
zeroidc/vendor/security-framework/src/os/macos/certificate.rs
vendored
Normal file
266
zeroidc/vendor/security-framework/src/os/macos/certificate.rs
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
//! OSX specific extensions to certificate functionality.
|
||||
|
||||
use core_foundation::array::{CFArray, CFArrayIterator};
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation::base::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::convert::TryInto;
|
||||
use std::os::raw::c_void;
|
||||
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.
|
||||
#[cfg_attr(not(feature = "OSX_10_14"), deprecated(note = "Uses deprecated SecCertificateCopyPublicKey. Enable OSX_10_14 feature to avoid it"))]
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "OSX_10_14")]
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "OSX_10_14"))]
|
||||
fn public_key(&self) -> Result<SecKey, Error> {
|
||||
#[allow(deprecated)]
|
||||
unsafe {
|
||||
let mut key = ptr::null_mut();
|
||||
cvt(SecCertificateCopyPublicKey(
|
||||
self.as_concrete_TypeRef(),
|
||||
&mut key,
|
||||
))?;
|
||||
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(ref 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.
|
||||
pub fn get(&self, oid: CertificateOid) -> Option<CertificateProperty> {
|
||||
unsafe {
|
||||
self.0.find(oid.as_ptr() as *const c_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.
|
||||
pub fn label(&self) -> CFString {
|
||||
unsafe {
|
||||
CFString::wrap_under_get_rule(*self.0.get(kSecPropertyKeyLabel.to_void()) as *const _)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an enum of the underlying data for this property.
|
||||
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 as *const _,
|
||||
)))
|
||||
} else if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeString) {
|
||||
PropertyType::String(CFString::wrap_under_get_rule(*value as *const _))
|
||||
} 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)]
|
||||
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<'a> Iterator for PropertySectionIter<'a> {
|
||||
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::os::macos::certificate_oids::CertificateOid;
|
||||
use crate::test::certificate;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn common_name() {
|
||||
let certificate = certificate();
|
||||
assert_eq!("foobar.com", p!(certificate.common_name()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn public_key() {
|
||||
let certificate = certificate();
|
||||
p!(certificate.public_key());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fingerprint() {
|
||||
let certificate = certificate();
|
||||
let fingerprint = p!(certificate.fingerprint());
|
||||
assert_eq!(
|
||||
"af9dd180a326ae08b37e6398f9262f8b9d4c55674a233a7c84975024f873655d",
|
||||
hex::encode(fingerprint)
|
||||
);
|
||||
}
|
||||
|
||||
#[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 section = match value.get() {
|
||||
PropertyType::Section(section) => section,
|
||||
_ => 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!(),
|
||||
};
|
||||
assert_eq!(algorithm, "1.2.840.113549.1.1.5");
|
||||
}
|
||||
}
|
||||
29
zeroidc/vendor/security-framework/src/os/macos/certificate_oids.rs
vendored
Normal file
29
zeroidc/vendor/security-framework/src/os/macos/certificate_oids.rs
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
//! 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::*;
|
||||
|
||||
/// An identifier of a property of a certificate.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct CertificateOid(CFStringRef);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl CertificateOid {
|
||||
#[inline(always)]
|
||||
pub fn x509_v1_signature_algorithm() -> Self {
|
||||
unsafe { Self(kSecOIDX509V1SignatureAlgorithm) }
|
||||
}
|
||||
|
||||
/// Returns the underlying raw pointer corresponding to this OID.
|
||||
#[inline(always)]
|
||||
pub fn as_ptr(&self) -> CFStringRef {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the string representation of the OID.
|
||||
#[inline]
|
||||
pub fn to_str(&self) -> CFString {
|
||||
unsafe { CFString::wrap_under_get_rule(self.0) }
|
||||
}
|
||||
}
|
||||
484
zeroidc/vendor/security-framework/src/os/macos/code_signing.rs
vendored
Normal file
484
zeroidc/vendor/security-framework/src/os/macos/code_signing.rs
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
//! Code signing services.
|
||||
|
||||
use std::{fmt::Debug, mem::MaybeUninit, str::FromStr};
|
||||
|
||||
use core_foundation::{
|
||||
base::{TCFType, TCFTypeRef, ToVoid},
|
||||
data::CFDataRef,
|
||||
dictionary::CFMutableDictionary,
|
||||
number::CFNumber,
|
||||
string::{CFString, CFStringRef},
|
||||
url::CFURL,
|
||||
};
|
||||
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 crate::{cvt, Result};
|
||||
|
||||
bitflags::bitflags! {
|
||||
|
||||
/// Values that can be used in the flags parameter to most code signing
|
||||
/// functions.
|
||||
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.
|
||||
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<&SecCode>,
|
||||
attrs: &GuestAttributes,
|
||||
flags: Flags,
|
||||
) -> Result<SecCode> {
|
||||
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(SecCode::wrap_under_create_rule(code.assume_init()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the location on disk of signed code, given a code or static
|
||||
/// code object.
|
||||
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_get_rule(code.assume_init()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the location on disk of signed code, given a code or static
|
||||
/// code object.
|
||||
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 {}
|
||||
|
||||
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() as *mut 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
|
||||
);
|
||||
}
|
||||
}
|
||||
187
zeroidc/vendor/security-framework/src/os/macos/digest_transform.rs
vendored
Normal file
187
zeroidc/vendor/security-framework/src/os/macos/digest_transform.rs
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
//! 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::*;
|
||||
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)]
|
||||
pub fn hmac_md5() -> Self {
|
||||
unsafe { Self(kSecDigestHMACMD5) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn hmac_sha1() -> Self {
|
||||
unsafe { Self(kSecDigestHMACSHA1) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn hmac_sha2() -> Self {
|
||||
unsafe { Self(kSecDigestHMACSHA2) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn md2() -> Self {
|
||||
unsafe { Self(kSecDigestMD2) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn md4() -> Self {
|
||||
unsafe { Self(kSecDigestMD4) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn md5() -> Self {
|
||||
unsafe { Self(kSecDigestMD5) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn sha1() -> Self {
|
||||
unsafe { Self(kSecDigestSHA1) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
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)]
|
||||
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.
|
||||
pub fn execute(&self, data: &CFData) -> Result<CFData, CFError> {
|
||||
unsafe {
|
||||
let digest_type = match self.digest_type {
|
||||
Some(ref 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(ref 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::*;
|
||||
use core_foundation::data::CFData;
|
||||
use hex;
|
||||
|
||||
#[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"
|
||||
);
|
||||
}
|
||||
}
|
||||
245
zeroidc/vendor/security-framework/src/os/macos/encrypt_transform.rs
vendored
Normal file
245
zeroidc/vendor/security-framework/src/os/macos/encrypt_transform.rs
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
//! 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::*;
|
||||
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)]
|
||||
pub fn none() -> Self {
|
||||
unsafe { Self(kSecPaddingNoneKey) }
|
||||
}
|
||||
|
||||
/// Use PKCS#1 padding.
|
||||
#[inline(always)]
|
||||
pub fn pkcs1() -> Self {
|
||||
unsafe { Self(kSecPaddingPKCS1Key) }
|
||||
}
|
||||
|
||||
/// Use PKCS#5 padding.
|
||||
#[inline(always)]
|
||||
pub fn pkcs5() -> Self {
|
||||
unsafe { Self(kSecPaddingPKCS5Key) }
|
||||
}
|
||||
|
||||
/// Use PKCS#7 padding.
|
||||
#[inline(always)]
|
||||
pub fn pkcs7() -> Self {
|
||||
unsafe { Self(kSecPaddingPKCS7Key) }
|
||||
}
|
||||
|
||||
/// Use OAEP padding.
|
||||
#[inline(always)]
|
||||
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)]
|
||||
pub fn none() -> Self {
|
||||
unsafe { Self(kSecModeNoneKey) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn ecb() -> Self {
|
||||
unsafe { Self(kSecModeECBKey) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn cbc() -> Self {
|
||||
unsafe { Self(kSecModeCBCKey) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn cfb() -> Self {
|
||||
unsafe { Self(kSecModeCFBKey) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
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)]
|
||||
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.
|
||||
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.
|
||||
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(ref padding) = self.padding {
|
||||
let key = CFString::wrap_under_get_rule(kSecPaddingKey);
|
||||
transform.set_attribute(&key, &padding.to_str())?;
|
||||
}
|
||||
|
||||
if let Some(ref mode) = self.mode {
|
||||
let key = CFString::wrap_under_get_rule(kSecEncryptionMode);
|
||||
transform.set_attribute(&key, &mode.to_str())?;
|
||||
}
|
||||
|
||||
if let Some(ref 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 core_foundation::data::CFData;
|
||||
use hex::FromHex;
|
||||
|
||||
use super::*;
|
||||
use crate::key::SecKey;
|
||||
use crate::os::macos::item::KeyType;
|
||||
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);
|
||||
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());
|
||||
}
|
||||
}
|
||||
85
zeroidc/vendor/security-framework/src/os/macos/identity.rs
vendored
Normal file
85
zeroidc/vendor/security-framework/src/os/macos/identity.rs
vendored
Normal 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::*;
|
||||
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.
|
||||
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(
|
||||
keychains.as_CFTypeRef(),
|
||||
certificate.as_concrete_TypeRef(),
|
||||
&mut identity,
|
||||
))?;
|
||||
Ok(Self::wrap_under_create_rule(identity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use tempdir::TempDir;
|
||||
|
||||
use super::*;
|
||||
use crate::identity::SecIdentity;
|
||||
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::new("certificate"));
|
||||
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::new("private_key"));
|
||||
let identity = identity(dir.path());
|
||||
p!(identity.private_key());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_certificate() {
|
||||
let dir = p!(TempDir::new("with_certificate"));
|
||||
|
||||
let mut 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(&mut keychain)
|
||||
.import(key));
|
||||
|
||||
let cert = test::certificate();
|
||||
p!(SecIdentity::with_certificate(&[keychain], &cert));
|
||||
}
|
||||
}
|
||||
344
zeroidc/vendor/security-framework/src/os/macos/import_export.rs
vendored
Normal file
344
zeroidc/vendor/security-framework/src/os/macos/import_export.rs
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
//! 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;
|
||||
|
||||
/// An extension trait adding OSX specific functionality to `Pkcs12ImportOptions`.
|
||||
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;
|
||||
}
|
||||
|
||||
impl Pkcs12ImportOptionsExt for Pkcs12ImportOptions {
|
||||
#[inline(always)]
|
||||
fn keychain(&mut self, keychain: SecKeychain) -> &mut Self {
|
||||
crate::Pkcs12ImportOptionsInternals::keychain(self, keychain)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn access(&mut self, access: SecAccess) -> &mut Self {
|
||||
crate::Pkcs12ImportOptionsInternals::access(self, access)
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder type to import Security Framework types from serialized formats.
|
||||
#[derive(Default)]
|
||||
pub struct ImportOptions<'a> {
|
||||
filename: Option<CFString>,
|
||||
passphrase: Option<CFType>,
|
||||
secure_passphrase: bool,
|
||||
no_access_control: bool,
|
||||
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)]
|
||||
pub fn new() -> ImportOptions<'a> {
|
||||
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 ImportOptions<'a> {
|
||||
self.filename = Some(CFString::from_str(filename).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the passphrase to be used to decrypt the imported data.
|
||||
#[inline]
|
||||
pub fn passphrase(&mut self, passphrase: &str) -> &mut ImportOptions<'a> {
|
||||
self.passphrase = Some(CFString::from_str(passphrase).unwrap().as_CFType());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the passphrase to be used to decrypt the imported data.
|
||||
#[inline]
|
||||
pub fn passphrase_bytes(&mut self, passphrase: &[u8]) -> &mut ImportOptions<'a> {
|
||||
self.passphrase = Some(CFData::from_buffer(passphrase).as_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 ImportOptions<'a> {
|
||||
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 ImportOptions<'a> {
|
||||
self.no_access_control = no_access_control;
|
||||
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 ImportOptions<'a> {
|
||||
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 ImportOptions<'a> {
|
||||
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 ImportOptions<'a> {
|
||||
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 ImportOptions<'a> {
|
||||
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(ref filename) => filename.as_concrete_TypeRef(),
|
||||
None => ptr::null(),
|
||||
};
|
||||
|
||||
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(ref 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(ref alert_title) = self.alert_title {
|
||||
key_params.alertTitle = alert_title.as_concrete_TypeRef();
|
||||
}
|
||||
|
||||
if let Some(ref alert_prompt) = self.alert_prompt {
|
||||
key_params.alertPrompt = alert_prompt.as_concrete_TypeRef();
|
||||
}
|
||||
|
||||
let keychain = match self.keychain {
|
||||
Some(ref 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 as *mut _,
|
||||
None => ptr::null_mut(),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let ret = SecItemImport(
|
||||
data,
|
||||
filename,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
&key_params,
|
||||
keychain,
|
||||
items_ref,
|
||||
);
|
||||
if ret != errSecSuccess {
|
||||
return Err(Error::from_code(ret));
|
||||
}
|
||||
|
||||
if let Some(ref mut items) = 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 hex;
|
||||
use tempdir::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::new("identity").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")
|
||||
.passphrase("password123")
|
||||
.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]
|
||||
#[ignore] // since it requires manual intervention
|
||||
fn secure_passphrase_identity() {
|
||||
let dir = TempDir::new("identity").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() {
|
||||
use super::Pkcs12ImportOptionsExt;
|
||||
|
||||
let dir = TempDir::new("pkcs12_import").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_eq!(
|
||||
hex::encode(identities[0].key_id.as_ref().unwrap()),
|
||||
"ed6492936dcc8907e397e573b36e633458dc33f1"
|
||||
);
|
||||
}
|
||||
}
|
||||
102
zeroidc/vendor/security-framework/src/os/macos/item.rs
vendored
Normal file
102
zeroidc/vendor/security-framework/src/os/macos/item.rs
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
//! OSX specific functionality for items.
|
||||
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation_sys::string::CFStringRef;
|
||||
use security_framework_sys::item::*;
|
||||
|
||||
use crate::item::ItemSearchOptions;
|
||||
use crate::os::macos::keychain::SecKeychain;
|
||||
use crate::ItemSearchOptionsInternals;
|
||||
|
||||
/// Types of `SecKey`s.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct KeyType(CFStringRef);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl KeyType {
|
||||
#[inline(always)]
|
||||
pub fn rsa() -> Self {
|
||||
unsafe { Self(kSecAttrKeyTypeRSA) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn dsa() -> Self {
|
||||
unsafe { Self(kSecAttrKeyTypeDSA) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn aes() -> Self {
|
||||
unsafe { Self(kSecAttrKeyTypeAES) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn des() -> Self {
|
||||
unsafe { Self(kSecAttrKeyTypeDES) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn triple_des() -> Self {
|
||||
unsafe { Self(kSecAttrKeyType3DES) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn rc4() -> Self {
|
||||
unsafe { Self(kSecAttrKeyTypeRC4) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn cast() -> Self {
|
||||
unsafe { Self(kSecAttrKeyTypeCAST) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "OSX_10_9")]
|
||||
#[inline(always)]
|
||||
pub fn ec() -> Self {
|
||||
unsafe { Self(kSecAttrKeyTypeEC) }
|
||||
}
|
||||
|
||||
pub(crate) fn to_str(self) -> CFString {
|
||||
unsafe { CFString::wrap_under_get_rule(self.0) }
|
||||
}
|
||||
}
|
||||
|
||||
/// An extension trait adding OSX specific functionality to `ItemSearchOptions`.
|
||||
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;
|
||||
}
|
||||
|
||||
impl ItemSearchOptionsExt for ItemSearchOptions {
|
||||
#[inline(always)]
|
||||
fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
|
||||
ItemSearchOptionsInternals::keychains(self, keychains)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::item::*;
|
||||
use crate::os::macos::certificate::SecCertificateExt;
|
||||
use crate::os::macos::item::ItemSearchOptionsExt;
|
||||
use crate::os::macos::test::keychain;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[test]
|
||||
fn find_certificate() {
|
||||
let dir = p!(TempDir::new("find_certificate"));
|
||||
let keychain = keychain(dir.path());
|
||||
let results = p!(ItemSearchOptions::new()
|
||||
.keychains(&[keychain])
|
||||
.class(ItemClass::certificate())
|
||||
.search());
|
||||
assert_eq!(1, results.len());
|
||||
let certificate = match results[0] {
|
||||
SearchResult::Ref(Reference::Certificate(ref cert)) => cert,
|
||||
_ => panic!("expected certificate"),
|
||||
};
|
||||
assert_eq!("foobar.com", p!(certificate.common_name()));
|
||||
}
|
||||
}
|
||||
39
zeroidc/vendor/security-framework/src/os/macos/key.rs
vendored
Normal file
39
zeroidc/vendor/security-framework/src/os/macos/key.rs
vendored
Normal 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 security_framework_sys::key::*;
|
||||
use std::ptr;
|
||||
|
||||
use crate::key::SecKey;
|
||||
use crate::os::macos::item::KeyType;
|
||||
|
||||
/// An extension trait adding OSX specific functionality to `SecKey`.
|
||||
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>;
|
||||
}
|
||||
|
||||
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 = 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
277
zeroidc/vendor/security-framework/src/os/macos/keychain.rs
vendored
Normal file
277
zeroidc/vendor/security-framework/src/os/macos/keychain.rs
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
//! Keychain support.
|
||||
|
||||
use core_foundation::base::{Boolean, 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]
|
||||
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();
|
||||
// FIXME
|
||||
let path_name = CString::new(path_name).unwrap();
|
||||
|
||||
unsafe {
|
||||
let mut keychain = ptr::null_mut();
|
||||
cvt(SecKeychainOpen(path_name.as_ptr(), &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() as *const _, true),
|
||||
None => (0, ptr::null(), false),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
cvt(SecKeychainUnlock(
|
||||
self.as_concrete_TypeRef(),
|
||||
len as u32,
|
||||
ptr,
|
||||
use_password as Boolean,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Err(Error::from_code(code))
|
||||
} else {
|
||||
Ok(KeychainUserInteractionLock)
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
Err(Error::from_code(code))
|
||||
} else {
|
||||
Ok(state != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
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(ref password) => (password.as_ptr() as *const c_void, password.len() as u32),
|
||||
None => (ptr::null(), 0),
|
||||
};
|
||||
|
||||
let access = match self.access {
|
||||
Some(ref access) => access.as_concrete_TypeRef(),
|
||||
None => ptr::null_mut(),
|
||||
};
|
||||
|
||||
let mut keychain = ptr::null_mut();
|
||||
cvt(SecKeychainCreate(
|
||||
path_name.as_ptr(),
|
||||
password_len,
|
||||
password,
|
||||
self.prompt_user as Boolean,
|
||||
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]
|
||||
pub fn new() -> Self {
|
||||
Self(SecKeychainSettings {
|
||||
version: SEC_KEYCHAIN_SETTINGS_VERS1,
|
||||
lockOnSleep: 0,
|
||||
useLockInterval: 0,
|
||||
lockInterval: i32::max_value() 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 = lock_on_sleep as Boolean;
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
match lock_interval {
|
||||
Some(lock_interval) => {
|
||||
self.0.useLockInterval = 1;
|
||||
self.0.lockInterval = lock_interval;
|
||||
}
|
||||
None => {
|
||||
self.0.useLockInterval = 0;
|
||||
self.0.lockInterval = i32::max_value() 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 tempdir::TempDir;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_options() {
|
||||
let dir = TempDir::new("keychain").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());
|
||||
}
|
||||
}
|
||||
27
zeroidc/vendor/security-framework/src/os/macos/keychain_item.rs
vendored
Normal file
27
zeroidc/vendor/security-framework/src/os/macos/keychain_item.rs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
//! Keychain item support.
|
||||
|
||||
use core_foundation::base::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 {}
|
||||
|
||||
// FIXME
|
||||
impl fmt::Debug for SecKeychainItem {
|
||||
#[cold]
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.write_str("SecKeychainItem")
|
||||
}
|
||||
}
|
||||
52
zeroidc/vendor/security-framework/src/os/macos/mod.rs
vendored
Normal file
52
zeroidc/vendor/security-framework/src/os/macos/mod.rs
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
//! 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 mod test {
|
||||
use crate::identity::SecIdentity;
|
||||
use crate::item::{ItemClass, ItemSearchOptions, Reference, SearchResult};
|
||||
use crate::os::macos::item::ItemSearchOptionsExt;
|
||||
use crate::os::macos::keychain::SecKeychain;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
|
||||
pub 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"),
|
||||
}
|
||||
}
|
||||
|
||||
pub 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
|
||||
}
|
||||
}
|
||||
524
zeroidc/vendor/security-framework/src/os/macos/passwords.rs
vendored
Normal file
524
zeroidc/vendor/security-framework/src/os/macos/passwords.rs
vendored
Normal file
@@ -0,0 +1,524 @@
|
||||
//! 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_CFTypeRef() as *mut _,
|
||||
ptr::null(),
|
||||
password.len() as u32,
|
||||
password.as_ptr() as *const _,
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete this item from its keychain
|
||||
#[inline]
|
||||
pub fn delete(self) {
|
||||
unsafe {
|
||||
SecKeychainItemDelete(self.as_CFTypeRef() as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(ref 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() as *const _,
|
||||
account.len() as u32,
|
||||
account.as_ptr() as *const _,
|
||||
&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(ref 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() as *const _,
|
||||
security_domain.map_or(0, |s| s.len() as u32),
|
||||
security_domain
|
||||
.map_or(ptr::null(), |s| s.as_ptr() as *const _),
|
||||
account.len() as u32,
|
||||
account.as_ptr() as *const _,
|
||||
path.len() as u32,
|
||||
path.as_ptr() as *const _,
|
||||
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(&[self.clone()]), 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(&[self.clone()]),
|
||||
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() as *const _,
|
||||
account.len() as u32,
|
||||
account.as_ptr() as *const _,
|
||||
password.len() as u32,
|
||||
password.as_ptr() as *const _,
|
||||
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() as *const _,
|
||||
security_domain.map_or(0, |s| s.len() as u32),
|
||||
security_domain
|
||||
.map_or(ptr::null(), |s| s.as_ptr() as *const _),
|
||||
account.len() as u32,
|
||||
account.as_ptr() as *const _,
|
||||
path.len() as u32,
|
||||
path.as_ptr() as *const _,
|
||||
port.unwrap_or(0),
|
||||
protocol,
|
||||
authentication_type,
|
||||
password.len() as u32,
|
||||
password.as_ptr() as *const _,
|
||||
ptr::null_mut(),
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::os::macos::keychain::{CreateOptions, SecKeychain};
|
||||
use tempdir::TempDir;
|
||||
|
||||
fn temp_keychain_setup(name: &str) -> (TempDir, SecKeychain) {
|
||||
let dir = TempDir::new("passwords").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]
|
||||
#[cfg(feature = "default_keychain_tests")]
|
||||
fn 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]
|
||||
#[cfg(feature = "default_keychain_tests")]
|
||||
fn 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]
|
||||
#[cfg(feature = "default_keychain_tests")]
|
||||
fn 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);
|
||||
}
|
||||
}
|
||||
647
zeroidc/vendor/security-framework/src/os/macos/secure_transport.rs
vendored
Normal file
647
zeroidc/vendor/security-framework/src/os/macos/secure_transport.rs
vendored
Normal file
@@ -0,0 +1,647 @@
|
||||
//! 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::secure_transport::{MidHandshakeSslStream, SslContext};
|
||||
use crate::{cvt, AsInner};
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Requires the `OSX_10_11` (or greater) feature.
|
||||
#[cfg(feature = "OSX_10_11")]
|
||||
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.
|
||||
///
|
||||
/// Requires the `OSX_10_11` (or greater) feature.
|
||||
#[cfg(feature = "OSX_10_11")]
|
||||
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.
|
||||
///
|
||||
/// Requires the `OSX_10_10` (or greater) feature.
|
||||
#[cfg(feature = "OSX_10_10")]
|
||||
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.
|
||||
///
|
||||
/// Requires the `OSX_10_10` (or greater) feature.
|
||||
#[cfg(feature = "OSX_10_10")]
|
||||
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.
|
||||
///
|
||||
/// Requires the `OSX_10_11` (or greater) feature.
|
||||
#[cfg(feature = "OSX_10_11")]
|
||||
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.
|
||||
///
|
||||
/// Requires the `OSX_10_11` (or greater) feature.
|
||||
#[cfg(feature = "OSX_10_11")]
|
||||
fn set_break_on_client_hello(&mut self, value: bool) -> Result<()>;
|
||||
}
|
||||
|
||||
macro_rules! impl_options {
|
||||
($($(#[$a:meta])* const $opt:ident: $get:ident & $set:ident,)*) => {
|
||||
$(
|
||||
$(#[$a])*
|
||||
#[inline]
|
||||
fn $set(&mut self, value: bool) -> Result<()> {
|
||||
unsafe {
|
||||
cvt(SSLSetSessionOption(self.as_inner(),
|
||||
$opt,
|
||||
value as ::core_foundation::base::Boolean))
|
||||
}
|
||||
}
|
||||
|
||||
$(#[$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 as *const u8, len)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()> {
|
||||
unsafe {
|
||||
cvt(SSLSetDiffieHellmanParams(
|
||||
self.as_inner(),
|
||||
dh_params.as_ptr() as *const _,
|
||||
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! {
|
||||
#[cfg(feature = "OSX_10_11")]
|
||||
const kSSLSessionOptionAllowServerIdentityChange: allow_server_identity_change & set_allow_server_identity_change,
|
||||
#[cfg(feature = "OSX_10_10")]
|
||||
const kSSLSessionOptionFallback: fallback & set_fallback,
|
||||
#[cfg(feature = "OSX_10_11")]
|
||||
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.
|
||||
///
|
||||
/// Requires the `OSX_10_11` (or greater) feature.
|
||||
#[cfg(feature = "OSX_10_11")]
|
||||
fn client_hello_received(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<S> MidHandshakeSslStreamExt for MidHandshakeSslStream<S> {
|
||||
#[cfg(feature = "OSX_10_11")]
|
||||
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 tempdir::TempDir;
|
||||
|
||||
use super::*;
|
||||
use crate::cipher_suite::CipherSuite;
|
||||
use crate::os::macos::test::identity;
|
||||
use crate::secure_transport::*;
|
||||
use crate::test::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::new("server_client"));
|
||||
|
||||
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(&[certificate()]));
|
||||
p!(peer_trust.evaluate_with_error());
|
||||
|
||||
let mut stream = p!(stream.handshake());
|
||||
p!(stream.write_all(b"hello world!"));
|
||||
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
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::new("server_client_builders"));
|
||||
|
||||
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(&[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::new("client_bad_cert"));
|
||||
|
||||
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]
|
||||
#[ignore]
|
||||
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::new("client_bad_cert"));
|
||||
|
||||
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(&[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::new("negotiated_cipher"));
|
||||
|
||||
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(), ¶ms[..]);
|
||||
}
|
||||
|
||||
#[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::new("negotiated_cipher"));
|
||||
|
||||
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 = 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::new("negotiated_cipher"));
|
||||
|
||||
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::new("negotiated_cipher"));
|
||||
|
||||
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::new("negotiated_cipher"));
|
||||
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(&[certificate()]));
|
||||
assert_eq!(p!(ctx.certificate_authorities()).unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
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::new("close"));
|
||||
|
||||
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(&[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]
|
||||
#[ignore]
|
||||
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::new("short_read"));
|
||||
|
||||
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(&[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());
|
||||
}
|
||||
}
|
||||
54
zeroidc/vendor/security-framework/src/os/macos/transform.rs
vendored
Normal file
54
zeroidc/vendor/security-framework/src/os/macos/transform.rs
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
//! Transform support
|
||||
|
||||
use core_foundation::base::{CFType, TCFType};
|
||||
use core_foundation::error::CFError;
|
||||
use core_foundation::string::CFString;
|
||||
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.
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
4
zeroidc/vendor/security-framework/src/os/mod.rs
vendored
Normal file
4
zeroidc/vendor/security-framework/src/os/mod.rs
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
//! OS specific extensions.
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
425
zeroidc/vendor/security-framework/src/passwords.rs
vendored
Normal file
425
zeroidc/vendor/security-framework/src/passwords.rs
vendored
Normal file
@@ -0,0 +1,425 @@
|
||||
//! 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.
|
||||
|
||||
use crate::base::Result;
|
||||
use crate::{cvt, Error};
|
||||
use core_foundation::base::{CFType, TCFType};
|
||||
use core_foundation::boolean::CFBoolean;
|
||||
use core_foundation::data::CFData;
|
||||
use core_foundation::dictionary::CFDictionary;
|
||||
use core_foundation::number::CFNumber;
|
||||
use core_foundation::string::CFString;
|
||||
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::{
|
||||
kSecAttrAccount, kSecAttrAuthenticationType, kSecAttrPath, kSecAttrPort, kSecAttrProtocol,
|
||||
kSecAttrSecurityDomain, kSecAttrServer, kSecAttrService, kSecClass, kSecClassGenericPassword,
|
||||
kSecClassInternetPassword, 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 query = generic_password_query(service, account);
|
||||
set_password_internal(&mut query, password)
|
||||
}
|
||||
|
||||
/// Get the generic password for the given service and account. If no matching
|
||||
/// keychain entry exists, fails with error code `errSecItemNotFound`.
|
||||
pub fn get_generic_password(service: &str, account: &str) -> Result<Vec<u8>> {
|
||||
let mut query = generic_password_query(service, account);
|
||||
query.push((
|
||||
unsafe { CFString::wrap_under_get_rule(kSecReturnData) },
|
||||
CFBoolean::from(true).as_CFType(),
|
||||
));
|
||||
let params = CFDictionary::from_CFType_pairs(&query);
|
||||
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 query = generic_password_query(service, account);
|
||||
let params = CFDictionary::from_CFType_pairs(&query);
|
||||
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 query = internet_password_query(
|
||||
server,
|
||||
security_domain,
|
||||
account,
|
||||
path,
|
||||
port,
|
||||
protocol,
|
||||
authentication_type,
|
||||
);
|
||||
set_password_internal(&mut query, 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 query = internet_password_query(
|
||||
server,
|
||||
security_domain,
|
||||
account,
|
||||
path,
|
||||
port,
|
||||
protocol,
|
||||
authentication_type,
|
||||
);
|
||||
query.push((
|
||||
unsafe { CFString::wrap_under_get_rule(kSecReturnData) },
|
||||
CFBoolean::from(true).as_CFType(),
|
||||
));
|
||||
let params = CFDictionary::from_CFType_pairs(&query);
|
||||
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 query = internet_password_query(
|
||||
server,
|
||||
security_domain,
|
||||
account,
|
||||
path,
|
||||
port,
|
||||
protocol,
|
||||
authentication_type,
|
||||
);
|
||||
let params = CFDictionary::from_CFType_pairs(&query);
|
||||
cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
|
||||
}
|
||||
|
||||
// Generic passwords are identified by service and account. They have other
|
||||
// attributes, but this interface doesn't allow specifying them.
|
||||
fn generic_password_query(service: &str, account: &str) -> Vec<(CFString, CFType)> {
|
||||
let query = vec![
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecClass) },
|
||||
unsafe { CFString::wrap_under_get_rule(kSecClassGenericPassword).as_CFType() },
|
||||
),
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrService) },
|
||||
CFString::from(service).as_CFType(),
|
||||
),
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) },
|
||||
CFString::from(account).as_CFType(),
|
||||
),
|
||||
];
|
||||
query
|
||||
}
|
||||
|
||||
// Internet passwords are identified by a number of attributes.
|
||||
// They can have others, but this interface doesn't allow specifying them.
|
||||
fn internet_password_query(
|
||||
server: &str,
|
||||
security_domain: Option<&str>,
|
||||
account: &str,
|
||||
path: &str,
|
||||
port: Option<u16>,
|
||||
protocol: SecProtocolType,
|
||||
authentication_type: SecAuthenticationType,
|
||||
) -> Vec<(CFString, CFType)> {
|
||||
let mut query = vec![
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecClass) },
|
||||
unsafe { CFString::wrap_under_get_rule(kSecClassInternetPassword) }.as_CFType(),
|
||||
),
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrServer) },
|
||||
CFString::from(server).as_CFType(),
|
||||
),
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrPath) },
|
||||
CFString::from(path).as_CFType(),
|
||||
),
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) },
|
||||
CFString::from(account).as_CFType(),
|
||||
),
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrProtocol) },
|
||||
CFNumber::from(protocol as i32).as_CFType(),
|
||||
),
|
||||
(
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrAuthenticationType) },
|
||||
CFNumber::from(authentication_type as i32).as_CFType(),
|
||||
),
|
||||
];
|
||||
if let Some(domain) = security_domain {
|
||||
query.push((
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrSecurityDomain) },
|
||||
CFString::from(domain).as_CFType(),
|
||||
))
|
||||
}
|
||||
if let Some(port) = port {
|
||||
query.push((
|
||||
unsafe { CFString::wrap_under_get_rule(kSecAttrPort) },
|
||||
CFNumber::from(port as i32).as_CFType(),
|
||||
))
|
||||
}
|
||||
query
|
||||
}
|
||||
|
||||
// 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(query: &mut Vec<(CFString, CFType)>, password: &[u8]) -> Result<()> {
|
||||
let query_len = query.len();
|
||||
query.push((
|
||||
unsafe { CFString::wrap_under_get_rule(kSecValueData) },
|
||||
CFData::from_buffer(password).as_CFType(),
|
||||
));
|
||||
let params = CFDictionary::from_CFType_pairs(query);
|
||||
let mut ret = std::ptr::null();
|
||||
let status = unsafe { SecItemAdd(params.as_concrete_TypeRef(), &mut ret) };
|
||||
if status == errSecDuplicateItem {
|
||||
let params = CFDictionary::from_CFType_pairs(&query[0..query_len]);
|
||||
let update = CFDictionary::from_CFType_pairs(&query[query_len..]);
|
||||
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();
|
||||
vec.extend_from_slice(val.bytes());
|
||||
return Ok(vec);
|
||||
} else {
|
||||
// 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 security_framework_sys::base::errSecItemNotFound;
|
||||
use super::*;
|
||||
|
||||
#[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,
|
||||
);
|
||||
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");
|
||||
}
|
||||
}
|
||||
103
zeroidc/vendor/security-framework/src/policy.rs
vendored
Normal file
103
zeroidc/vendor/security-framework/src/policy.rs
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
//! Security Policies support.
|
||||
use core_foundation::base::TCFType;
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
use core_foundation::base::CFOptionFlags;
|
||||
use core_foundation::string::CFString;
|
||||
use security_framework_sys::base::SecPolicyRef;
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
use security_framework_sys::base::errSecParam;
|
||||
use security_framework_sys::policy::*;
|
||||
use std::fmt;
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
use crate::Error;
|
||||
use crate::secure_transport::SslProtocolSide;
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
bitflags::bitflags! {
|
||||
/// The flags used to specify revocation policy options.
|
||||
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 as _, hostname);
|
||||
Self::wrap_under_create_rule(policy)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
/// 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.
|
||||
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
zeroidc/vendor/security-framework/src/random.rs
vendored
Normal file
39
zeroidc/vendor/security-framework/src/random.rs
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
//! Randomness support.
|
||||
|
||||
use security_framework_sys::random::*;
|
||||
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() as *mut _) } == 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();
|
||||
}
|
||||
}
|
||||
1840
zeroidc/vendor/security-framework/src/secure_transport.rs
vendored
Normal file
1840
zeroidc/vendor/security-framework/src/secure_transport.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
393
zeroidc/vendor/security-framework/src/trust.rs
vendored
Normal file
393
zeroidc/vendor/security-framework/src/trust.rs
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
//! Trust evaluation support.
|
||||
|
||||
use core_foundation::array::CFArray;
|
||||
#[cfg(target_os = "macos")]
|
||||
use core_foundation::array::CFArrayRef;
|
||||
use core_foundation::base::TCFType;
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
use core_foundation::data::CFData;
|
||||
use core_foundation::date::CFDate;
|
||||
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 {
|
||||
/// An invalid setting or result.
|
||||
pub const INVALID: Self = Self(kSecTrustResultInvalid);
|
||||
|
||||
/// You may proceed.
|
||||
pub const PROCEED: Self = Self(kSecTrustResultProceed);
|
||||
|
||||
/// Indicates a denial by the user, do not proceed.
|
||||
pub const DENY: Self = Self(kSecTrustResultDeny);
|
||||
|
||||
/// The certificate is implicitly trusted.
|
||||
pub const UNSPECIFIED: Self = Self(kSecTrustResultUnspecified);
|
||||
|
||||
/// Indicates a trust policy failure that the user can override.
|
||||
pub const RECOVERABLE_TRUST_FAILURE: Self = Self(kSecTrustResultRecoverableTrustFailure);
|
||||
|
||||
/// Indicates a trust policy failure that the user cannot override.
|
||||
pub const FATAL_TRUST_FAILURE: Self = Self(kSecTrustResultFatalTrustFailure);
|
||||
|
||||
/// An error not related to trust validation.
|
||||
pub const OTHER_ERROR: Self = Self(kSecTrustResultOtherError);
|
||||
}
|
||||
|
||||
impl TrustResult {
|
||||
/// Returns true if the result is "successful" - specifically `PROCEED` or `UNSPECIFIED`.
|
||||
#[inline]
|
||||
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`.
|
||||
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, only as Boolean)) }
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
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.
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
#[inline]
|
||||
pub fn set_network_fetch_allowed(&mut self, allowed: bool) -> Result<()> {
|
||||
unsafe { cvt(SecTrustSetNetworkFetchAllowed(self.0, allowed as u8)) }
|
||||
}
|
||||
|
||||
/// Attaches Online Certificate Status Protocol (OSCP) response data
|
||||
/// to this trust object.
|
||||
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
|
||||
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.
|
||||
#[cfg(any(feature = "OSX_10_14", target_os = "ios"))]
|
||||
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 or iOS, otherwise it just calls `evaluate()`
|
||||
pub fn evaluate_with_error(&self) -> Result<(), CFError> {
|
||||
#[cfg(any(feature = "OSX_10_14", target_os = "ios"))]
|
||||
unsafe {
|
||||
let mut error: CFErrorRef = ::std::ptr::null_mut();
|
||||
if !SecTrustEvaluateWithError(self.0, &mut error) {
|
||||
assert!(!error.is_null());
|
||||
let error = CFError::wrap_under_create_rule(error);
|
||||
return Err(error);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))]
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
use security_framework_sys::base::errSecNotTrusted;
|
||||
use security_framework_sys::base::errSecTrustSettingDeny;
|
||||
|
||||
let code = match self.evaluate() {
|
||||
Ok(res) if res.success() => return Ok(()),
|
||||
Ok(TrustResult::DENY) => errSecTrustSettingDeny,
|
||||
Ok(_) => errSecNotTrusted,
|
||||
Err(err) => err.code(),
|
||||
};
|
||||
Err(cferror_from_osstatus(code))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of certificates in an evaluated certificate chain.
|
||||
///
|
||||
/// Note: evaluate must first be called on the SecTrust.
|
||||
#[inline(always)]
|
||||
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")]
|
||||
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 as *mut _))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))]
|
||||
extern "C" {
|
||||
fn CFErrorCreate(allocator: core_foundation_sys::base::CFAllocatorRef, domain: core_foundation_sys::string::CFStringRef, code: CFIndex, userInfo: core_foundation_sys::dictionary::CFDictionaryRef) -> CFErrorRef;
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))]
|
||||
fn cferror_from_osstatus(code: core_foundation_sys::base::OSStatus) -> CFError {
|
||||
unsafe {
|
||||
let error = CFErrorCreate(ptr::null_mut(), core_foundation_sys::error::kCFErrorDomainOSStatus, code as _, ptr::null_mut());
|
||||
assert!(!error.is_null());
|
||||
CFError::wrap_under_create_rule(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[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_eq!(trust.evaluate().unwrap().success(), false)
|
||||
}
|
||||
|
||||
#[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();
|
||||
assert_eq!(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();
|
||||
assert_eq!(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(&[cert.clone()], &[ssl_policy.clone()]).unwrap();
|
||||
trust.evaluate().unwrap();
|
||||
assert!(trust.certificate_at_index(1).is_none());
|
||||
|
||||
let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap();
|
||||
assert!(trust.evaluate_with_error().is_err());
|
||||
assert!(trust.certificate_at_index(1).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_eq!(trust.evaluate().unwrap().success(), false)
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
||||
274
zeroidc/vendor/security-framework/src/trust_settings.rs
vendored
Normal file
274
zeroidc/vendor/security-framework/src/trust_settings.rs
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
//! 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 security_framework_sys::base::errSecNoTrustSettings;
|
||||
use security_framework_sys::base::errSecSuccess;
|
||||
use security_framework_sys::trust_settings::*;
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use crate::base::Error;
|
||||
use crate::base::Result;
|
||||
use crate::certificate::SecCertificate;
|
||||
use crate::cvt;
|
||||
|
||||
/// Which set of trust settings to query
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Domain {
|
||||
/// Per-user trust settings
|
||||
User,
|
||||
/// Locally administered, system-wide trust settings
|
||||
Admin,
|
||||
/// System trust settings
|
||||
System,
|
||||
}
|
||||
|
||||
impl From<Domain> for SecTrustSettingsDomain {
|
||||
#[inline]
|
||||
fn from(domain: Domain) -> SecTrustSettingsDomain {
|
||||
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_value()) {
|
||||
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)]
|
||||
pub 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 })
|
||||
}
|
||||
|
||||
/// 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() as *const _)
|
||||
.map(|name| unsafe { CFString::wrap_under_get_rule(*name as *const _) });
|
||||
|
||||
matches!(maybe_name, Some(ref 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() as *const _)
|
||||
.map(|num| unsafe { CFNumber::wrap_under_get_rule(*num as *const _) })
|
||||
.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()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user