Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support access control options #178

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions security-framework-sys/src/access_control.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use core_foundation_sys::base::{CFAllocatorRef, CFTypeRef, CFTypeID};
use core_foundation_sys::error::CFErrorRef;
use core_foundation_sys::base::CFOptionFlags;

use crate::base::SecAccessControlRef;

mod access_control_flags {
kornelski marked this conversation as resolved.
Show resolved Hide resolved
use super::CFOptionFlags;

pub const kSecAccessControlUserPresence: CFOptionFlags = 1 << 0;
pub const kSecAccessControlBiometryAny: CFOptionFlags = 1 << 1;
pub const kSecAccessControlBiometryCurrentSet: CFOptionFlags = 1 << 3;
pub const kSecAccessControlDevicePasscode: CFOptionFlags = 1 << 4;
pub const kSecAccessControlWatch: CFOptionFlags = 1 << 5;
pub const kSecAccessControlOr: CFOptionFlags = 1 << 14;
pub const kSecAccessControlAnd: CFOptionFlags = 1 << 15;
pub const kSecAccessControlPrivateKeyUsage: CFOptionFlags = 1 << 30;
pub const kSecAccessControlApplicationPassword: CFOptionFlags = 1 << 31;
}

pub use access_control_flags::*;

extern "C" {
pub fn SecAccessControlGetTypeID() -> CFTypeID;

pub fn SecAccessControlCreateWithFlags(
allocator: CFAllocatorRef,
protection: CFTypeRef,
flags: CFOptionFlags,
error: *mut CFErrorRef
) -> SecAccessControlRef;
}
3 changes: 3 additions & 0 deletions security-framework-sys/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub type SecCertificateRef = *mut OpaqueSecCertificateRef;
pub enum OpaqueSecAccessRef {}
pub type SecAccessRef = *mut OpaqueSecAccessRef;

pub enum OpaqueSecAccessControlRef {}
pub type SecAccessControlRef = *mut OpaqueSecAccessControlRef;

pub enum OpaqueSecKeyRef {}
pub type SecKeyRef = *mut OpaqueSecKeyRef;

Expand Down
1 change: 1 addition & 0 deletions security-framework-sys/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ extern "C" {
pub static kSecAttrSecurityDomain: CFStringRef;
pub static kSecAttrServer: CFStringRef;
pub static kSecAttrService: CFStringRef;
pub static kSecAttrAccessControl: CFStringRef;
pub static kSecAttrAccount: CFStringRef;
pub static kSecValueData: CFStringRef;
pub static kSecValueRef: CFStringRef;
Expand Down
1 change: 1 addition & 0 deletions security-framework-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern "C" {}

#[cfg(target_os = "macos")]
pub mod access;
pub mod access_control;
#[cfg(target_os = "macos")]
pub mod authorization;
pub mod base;
Expand Down
33 changes: 33 additions & 0 deletions security-framework/src/access_control.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Access Control support.

use std::ptr::{self, null};

use core_foundation::base::{TCFType, CFOptionFlags, kCFAllocatorDefault};
use security_framework_sys::access_control::{SecAccessControlGetTypeID, SecAccessControlCreateWithFlags};
use security_framework_sys::base::{SecAccessControlRef, errSecParam};
use crate::base::{Error, Result};

declare_TCFType! {
/// A type representing sec access control settings.
SecAccessControl, SecAccessControlRef
}
impl_TCFType!(SecAccessControl, SecAccessControlRef, SecAccessControlGetTypeID);

unsafe impl Sync for SecAccessControl {}
unsafe impl Send for SecAccessControl {}


impl SecAccessControl {

/// Create `AccessControl` object from flags
pub fn create_with_flags(flags: CFOptionFlags) -> Result<Self> {
unsafe {
let access_control = SecAccessControlCreateWithFlags(kCFAllocatorDefault, null(), flags, ptr::null_mut());
if access_control.is_null() {
Err(Error::from_code(errSecParam))
} else {
Ok(Self::wrap_under_create_rule(access_control))
}
}
}
}
2 changes: 2 additions & 0 deletions security-framework/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ macro_rules! p {
#[macro_use]
mod dlsym;

pub mod access_control;
#[cfg(target_os = "macos")]
pub mod authorization;
pub mod base;
Expand All @@ -44,6 +45,7 @@ pub mod item;
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub mod key;
pub mod os;
pub mod passwords_options;
pub mod passwords;
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub mod policy;
Expand Down
4 changes: 2 additions & 2 deletions security-framework/src/os/macos/access.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! Access control functionality.
//! Access 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.
/// A type representing access settings.
SecAccess, SecAccessRef
}
impl_TCFType!(SecAccess, SecAccessRef, SecAccessGetTypeID);
Expand Down
137 changes: 31 additions & 106 deletions security-framework/src/passwords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@
//! version of these functions in the macOS extensions module.

use crate::base::Result;
use crate::passwords_options::PasswordOptions;
use crate::{cvt, Error};
use core_foundation::base::{CFType, TCFType};
use core_foundation::base::{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::item::{kSecReturnData, kSecValueData,};
use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType};
use security_framework_sys::keychain_item::{
SecItemAdd, SecItemCopyMatching, SecItemDelete, SecItemUpdate,
Expand All @@ -27,19 +23,19 @@ use security_framework_sys::keychain_item::{
/// 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)
let mut options = PasswordOptions::new_generic_password(service, account);
set_password_internal(&mut options, password)
}

/// Get the generic password for the given service and account. If no matching
/// keychain entry exists, fails with error code `errSecItemNotFound`.
pub fn get_generic_password(service: &str, account: &str) -> Result<Vec<u8>> {
let mut query = generic_password_query(service, account);
query.push((
let mut options = PasswordOptions::new_generic_password(service, account);
options.query.push((
unsafe { CFString::wrap_under_get_rule(kSecReturnData) },
CFBoolean::from(true).as_CFType(),
));
let params = CFDictionary::from_CFType_pairs(&query);
let params = CFDictionary::from_CFType_pairs(&options.query);
let mut ret: CFTypeRef = std::ptr::null();
cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?;
get_password_and_release(ret)
Expand All @@ -48,8 +44,8 @@ pub fn get_generic_password(service: &str, account: &str) -> Result<Vec<u8>> {
/// 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);
let options = PasswordOptions::new_generic_password(service, account);
let params = CFDictionary::from_CFType_pairs(&options.query);
cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
}

Expand All @@ -64,18 +60,18 @@ pub fn set_internet_password(
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
password: &[u8],
password: &[u8]
) -> Result<()> {
let mut query = internet_password_query(
let mut options = PasswordOptions::new_internet_password(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
authentication_type
);
set_password_internal(&mut query, password)
set_password_internal(&mut options, password)
}

/// Get the internet password for the given endpoint parameters. If no matching
Expand All @@ -89,7 +85,7 @@ pub fn get_internet_password(
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
) -> Result<Vec<u8>> {
let mut query = internet_password_query(
let mut options = PasswordOptions::new_internet_password(
server,
security_domain,
account,
Expand All @@ -98,11 +94,11 @@ pub fn get_internet_password(
protocol,
authentication_type,
);
query.push((
options.query.push((
unsafe { CFString::wrap_under_get_rule(kSecReturnData) },
CFBoolean::from(true).as_CFType(),
));
let params = CFDictionary::from_CFType_pairs(&query);
let params = CFDictionary::from_CFType_pairs(&options.query);
let mut ret: CFTypeRef = std::ptr::null();
cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?;
get_password_and_release(ret)
Expand All @@ -119,7 +115,7 @@ pub fn delete_internet_password(
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
) -> Result<()> {
let query = internet_password_query(
let options = PasswordOptions::new_internet_password(
server,
security_domain,
account,
Expand All @@ -128,96 +124,25 @@ pub fn delete_internet_password(
protocol,
authentication_type,
);
let params = CFDictionary::from_CFType_pairs(&query);
let params = CFDictionary::from_CFType_pairs(&options.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(i32::from(port)).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((
fn set_password_internal(options: &mut PasswordOptions, password: &[u8]) -> Result<()> {
let query_len = options.query.len();
options.query.push((
unsafe { CFString::wrap_under_get_rule(kSecValueData) },
CFData::from_buffer(password).as_CFType(),
));
let params = CFDictionary::from_CFType_pairs(query);

let params = CFDictionary::from_CFType_pairs(&options.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..]);
let params = CFDictionary::from_CFType_pairs(&options.query[0..query_len]);
let update = CFDictionary::from_CFType_pairs(&options.query[query_len..]);
cvt(unsafe { SecItemUpdate(params.as_concrete_TypeRef(), update.as_concrete_TypeRef()) })
} else {
cvt(status)
Expand Down Expand Up @@ -361,7 +286,7 @@ mod test {
"/",
Some(8080u16),
SecProtocolType::HTTP,
SecAuthenticationType::Any,
SecAuthenticationType::Any
);
set_internet_password(
server,
Expand All @@ -371,7 +296,7 @@ mod test {
port,
protocol,
auth,
name.as_bytes(),
name.as_bytes()
)
.expect("set_internet_password");
let pass = get_internet_password(server, domain, account, path, port, protocol, auth)
Expand All @@ -391,7 +316,7 @@ mod test {
"/",
Some(8080u16),
SecProtocolType::HTTP,
SecAuthenticationType::Any,
SecAuthenticationType::Any
);
set_internet_password(
server,
Expand All @@ -401,7 +326,7 @@ mod test {
port,
protocol,
auth,
name.as_bytes(),
name.as_bytes()
)
.expect("set_internet_password");
let alternate = "alternate_internet_password";
Expand All @@ -413,7 +338,7 @@ mod test {
port,
protocol,
auth,
alternate.as_bytes(),
alternate.as_bytes()
)
.expect("set_internet_password");
let pass = get_internet_password(server, domain, account, path, port, protocol, auth)
Expand Down
Loading