summaryrefslogtreecommitdiffstats
path: root/vendor/security-framework/src/item.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/security-framework/src/item.rs')
-rw-r--r--vendor/security-framework/src/item.rs689
1 files changed, 689 insertions, 0 deletions
diff --git a/vendor/security-framework/src/item.rs b/vendor/security-framework/src/item.rs
new file mode 100644
index 000000000..6f252e00f
--- /dev/null
+++ b/vendor/security-framework/src/item.rs
@@ -0,0 +1,689 @@
+//! Support to search for items in a keychain.
+
+use core_foundation::array::CFArray;
+use core_foundation::base::{CFType, TCFType, ToVoid};
+use core_foundation::boolean::CFBoolean;
+use core_foundation::data::CFData;
+use core_foundation::date::CFDate;
+use core_foundation::dictionary::{CFDictionary, CFMutableDictionary};
+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::{SecItemAdd, 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)]
+ #[must_use]
+ pub fn generic_password() -> Self {
+ unsafe { Self(kSecClassGenericPassword) }
+ }
+
+ /// Look for `SecKeychainItem`s corresponding to internet passwords.
+ #[inline(always)]
+ #[must_use]
+ pub fn internet_password() -> Self {
+ unsafe { Self(kSecClassInternetPassword) }
+ }
+
+ /// Look for `SecCertificate`s.
+ #[inline(always)]
+ #[must_use]
+ pub fn certificate() -> Self {
+ unsafe { Self(kSecClassCertificate) }
+ }
+
+ /// Look for `SecKey`s.
+ #[inline(always)]
+ #[must_use]
+ pub fn key() -> Self {
+ unsafe { Self(kSecClassKey) }
+ }
+
+ /// Look for `SecIdentity`s.
+ #[inline(always)]
+ #[must_use]
+ pub fn identity() -> Self {
+ unsafe { Self(kSecClassIdentity) }
+ }
+
+ #[inline]
+ fn to_value(self) -> CFType {
+ unsafe { CFType::wrap_under_get_rule(self.0.cast()) }
+ }
+}
+
+/// Specifies the type of keys to search for.
+#[derive(Debug, Copy, Clone)]
+pub struct KeyClass(CFStringRef);
+
+impl KeyClass {
+ /// `kSecAttrKeyClassPublic`
+ #[inline(always)]
+ #[must_use] pub fn public() -> Self {
+ unsafe { Self(kSecAttrKeyClassPublic) }
+ }
+ /// `kSecAttrKeyClassPrivate`
+ #[inline(always)]
+ #[must_use] pub fn private() -> Self {
+ unsafe { Self(kSecAttrKeyClassPrivate) }
+ }
+ /// `kSecAttrKeyClassSymmetric`
+ #[inline(always)]
+ #[must_use] pub fn symmetric() -> Self {
+ unsafe { Self(kSecAttrKeyClassSymmetric) }
+ }
+
+ #[inline]
+ fn to_value(self) -> CFType {
+ unsafe { CFType::wrap_under_get_rule(self.0.cast()) }
+ }
+}
+
+/// 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).into_CFType() },
+ Self::Max(l) => CFNumber::from(l).into_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>,
+ key_class: Option<KeyClass>,
+ load_refs: bool,
+ load_attributes: bool,
+ load_data: bool,
+ limit: Option<Limit>,
+ label: Option<CFString>,
+ service: Option<CFString>,
+ account: Option<CFString>,
+ access_group: Option<CFString>,
+ pub_key_hash: Option<CFData>,
+ app_label: Option<CFData>,
+}
+
+#[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)]
+ #[must_use]
+ 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
+ }
+
+ /// Search only for keys of the specified class. Also sets self.class to
+ /// ItemClass::key().
+ #[inline(always)]
+ pub fn key_class(&mut self, key_class: KeyClass) -> &mut Self {
+ self.class(ItemClass::key());
+ self.key_class = Some(key_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
+ }
+
+ /// Search for an item with the given service.
+ #[inline(always)]
+ pub fn service(&mut self, service: &str) -> &mut Self {
+ self.service = Some(CFString::new(service));
+ self
+ }
+
+ /// Search for an item with the given account.
+ #[inline(always)]
+ pub fn account(&mut self, account: &str) -> &mut Self {
+ self.account = Some(CFString::new(account));
+ 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 a certificate with the given public key hash.
+ ///
+ /// This is only compatible with [`ItemClass::certificate`], to search for
+ /// a key by public key hash use [`ItemSearchOptions::application_label`]
+ /// instead.
+ #[inline(always)]
+ pub fn pub_key_hash(&mut self, pub_key_hash: &[u8]) -> &mut Self {
+ self.pub_key_hash = Some(CFData::from_buffer(pub_key_hash));
+ self
+ }
+
+ /// Search for a key with the given public key hash.
+ ///
+ /// This is only compatible with [`ItemClass::key`], to search for a
+ /// certificate by the public key hash use [`ItemSearchOptions::pub_key_hash`]
+ /// instead.
+ #[inline(always)]
+ pub fn application_label(&mut self, app_label: &[u8]) -> &mut Self {
+ self.app_label = Some(CFData::from_buffer(app_label));
+ 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 let Some(key_class) = self.key_class {
+ params.push((CFString::wrap_under_get_rule(kSecAttrKeyClass), key_class.to_value()));
+ }
+
+ if self.load_refs {
+ params.push((
+ CFString::wrap_under_get_rule(kSecReturnRef),
+ CFBoolean::true_value().into_CFType(),
+ ));
+ }
+
+ if self.load_attributes {
+ params.push((
+ CFString::wrap_under_get_rule(kSecReturnAttributes),
+ CFBoolean::true_value().into_CFType(),
+ ));
+ }
+
+ if self.load_data {
+ params.push((
+ CFString::wrap_under_get_rule(kSecReturnData),
+ CFBoolean::true_value().into_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 service) = self.service {
+ params.push((
+ CFString::wrap_under_get_rule(kSecAttrService),
+ service.as_CFType(),
+ ));
+ }
+
+ if let Some(ref account) = self.account {
+ params.push((
+ CFString::wrap_under_get_rule(kSecAttrAccount),
+ account.as_CFType(),
+ ));
+ }
+
+ if let Some(ref access_group) = self.access_group {
+ params.push((
+ CFString::wrap_under_get_rule(kSecAttrAccessGroup),
+ access_group.as_CFType(),
+ ));
+ }
+
+ if let Some(ref pub_key_hash) = self.pub_key_hash {
+ params.push((
+ CFString::wrap_under_get_rule(kSecAttrPublicKeyHash),
+ pub_key_hash.as_CFType(),
+ ));
+ }
+
+ if let Some(ref app_label) = self.app_label {
+ params.push((
+ CFString::wrap_under_get_rule(kSecAttrApplicationLabel),
+ app_label.as_CFType(),
+ ));
+ }
+
+ let params = CFDictionary::from_CFType_pairs(&params);
+
+ let mut ret = ptr::null();
+ cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
+ if ret.is_null() {
+ // SecItemCopyMatching returns NULL if no load_* was specified,
+ // causing a segfault.
+ return Ok(vec![]);
+ }
+ 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.
+ #[must_use]
+ 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).cast());
+ let val: String = match CFGetTypeID(*v) {
+ cfstring if cfstring == CFString::type_id() => {
+ format!("{}", CFString::wrap_under_get_rule((*v).cast()))
+ }
+ cfdata if cfdata == CFData::type_id() => {
+ let buf = CFData::wrap_under_get_rule((*v).cast());
+ 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,
+ }
+ }
+}
+
+/// Builder-pattern struct for specifying options for `add_item` (`SecAddItem`
+/// wrapper).
+///
+/// When finished populating options, call `to_dictionary()` and pass the
+/// resulting `CFDictionary` to `add_item`.
+pub struct ItemAddOptions {
+ /// The value (by ref or data) of the item to add, required.
+ pub value: ItemAddValue,
+ /// Optional kSecAttrLabel attribute.
+ pub label: Option<String>,
+ /// Optional keychain location.
+ pub location: Option<Location>,
+}
+
+impl ItemAddOptions {
+ /// Specifies the item to add.
+ #[must_use] pub fn new(value: ItemAddValue) -> Self {
+ Self{ value, label: None, location: None }
+ }
+ /// Specifies the `kSecAttrLabel` attribute.
+ pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self {
+ self.label = Some(label.into());
+ self
+ }
+ /// Specifies which keychain to add the item to.
+ pub fn set_location(&mut self, location: Location) -> &mut Self {
+ self.location = Some(location);
+ self
+ }
+ /// Populates a `CFDictionary` to be passed to
+ pub fn to_dictionary(&self) -> CFDictionary {
+ let mut dict = CFMutableDictionary::from_CFType_pairs(&[]);
+
+ let class_opt = match &self.value {
+ ItemAddValue::Ref(ref_) => ref_.class(),
+ ItemAddValue::Data { class, .. } => Some(*class),
+ };
+ if let Some(class) = class_opt {
+ dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
+ }
+
+ let value_pair = match &self.value {
+ ItemAddValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()),
+ ItemAddValue::Data { data, .. } => (unsafe { kSecValueData }.to_void(), data.to_void()),
+ };
+ dict.add(&value_pair.0, &value_pair.1);
+
+ if let Some(location) = &self.location {
+ match location {
+ #[cfg(any(feature = "OSX_10_15", target_os = "ios"))]
+ Location::DataProtectionKeychain => {
+ dict.add(
+ &unsafe { kSecUseDataProtectionKeychain }.to_void(),
+ &CFBoolean::true_value().to_void(),
+ );
+ }
+ #[cfg(target_os = "macos")]
+ Location::DefaultFileKeychain => {}
+ #[cfg(target_os = "macos")]
+ Location::FileKeychain(keychain) => {
+ dict.add(&unsafe { kSecUseKeychain }.to_void(), &keychain.to_void());
+ },
+ }
+ }
+
+ let label = self.label.as_deref().map(CFString::from);
+ if let Some(label) = &label {
+ dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void());
+ }
+
+ dict.to_immutable()
+ }
+}
+
+/// Value of an item to add to the keychain.
+pub enum ItemAddValue {
+ /// Pass item by Ref (kSecValueRef)
+ Ref(AddRef),
+ /// Pass item by Data (kSecValueData)
+ Data {
+ /// The item class (kSecClass).
+ class: ItemClass,
+ /// The item data.
+ data: CFData,
+ },
+}
+
+/// Type of Ref to add to the keychain.
+pub enum AddRef {
+ /// SecKey
+ Key(SecKey),
+ /// SecIdentity
+ Identity(SecIdentity),
+ /// SecCertificate
+ Certificate(SecCertificate),
+}
+
+impl AddRef {
+ fn class(&self) -> Option<ItemClass> {
+ match self {
+ AddRef::Key(_) => Some(ItemClass::key()),
+ // kSecClass should not be specified when adding a SecIdentityRef:
+ // https://developer.apple.com/forums/thread/25751
+ AddRef::Identity(_) => None,
+ AddRef::Certificate(_) => Some(ItemClass::certificate()),
+ }
+ }
+ fn ref_(&self) -> CFTypeRef {
+ match self {
+ AddRef::Key(key) => key.as_CFTypeRef(),
+ AddRef::Identity(id) => id.as_CFTypeRef(),
+ AddRef::Certificate(cert) => cert.as_CFTypeRef(),
+ }
+ }
+}
+
+/// Which keychain to add an item to.
+///
+/// <https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains>
+pub enum Location {
+ /// Store the item in the newer DataProtectionKeychain. This is the only
+ /// keychain on iOS. On macOS, this is the newer and more consistent
+ /// keychain implementation. Keys stored in the Secure Enclave _must_ use
+ /// this keychain.
+ ///
+ /// This keychain requires the calling binary to be codesigned with
+ /// entitlements for the KeychainAccessGroups it is supposed to
+ /// access.
+ #[cfg(any(feature = "OSX_10_15", target_os = "ios"))]
+ DataProtectionKeychain,
+ /// Store the key in the default file-based keychain. On macOS, defaults to
+ /// the Login keychain.
+ #[cfg(target_os = "macos")]
+ DefaultFileKeychain,
+ /// Store the key in a specific file-based keychain.
+ #[cfg(target_os = "macos")]
+ FileKeychain(crate::os::macos::keychain::SecKeychain),
+}
+
+/// Translates to `SecItemAdd`. Use `ItemAddOptions` to build an `add_params`
+/// `CFDictionary`.
+pub fn add_item(add_params: CFDictionary) -> Result<()> {
+ cvt(unsafe { SecItemAdd(add_params.as_concrete_TypeRef(), std::ptr::null_mut()) })
+}
+
+#[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);
+ }
+}