diff --git a/security-framework/src/item.rs b/security-framework/src/item.rs index 55425ade..383ebf21 100644 --- a/security-framework/src/item.rs +++ b/security-framework/src/item.rs @@ -11,7 +11,9 @@ 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 security_framework_sys::keychain_item::{ + SecItemAdd, SecItemCopyMatching, SecItemDelete, SecItemUpdate +}; use std::collections::HashMap; use std::fmt; use std::ptr; @@ -63,11 +65,6 @@ impl ItemClass { 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. @@ -90,11 +87,6 @@ impl KeyClass { #[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 @@ -291,121 +283,138 @@ impl ItemSearchOptions { self } - /// Search for objects. - pub fn search(&self) -> Result> { + /// Populates a `CFDictionary` to be passed to `update_item` or `delete_item`. + #[inline] + fn to_dictionary(&self) -> CFDictionary { unsafe { - let mut params = vec![]; + let mut params = CFMutableDictionary::from_CFType_pairs(&[]); if let Some(ref keychains) = self.keychains { - params.push(( - CFString::wrap_under_get_rule(kSecMatchSearchList), - keychains.as_CFType(), - )); + params.add( + &kSecMatchSearchList.to_void(), + &keychains.as_CFType().to_void(), + ); } if let Some(class) = self.class { - params.push((CFString::wrap_under_get_rule(kSecClass), class.to_value())); + params.add(&kSecClass.to_void(), &class.0.to_void()); } if let Some(case_insensitive) = self.case_insensitive { - params.push(( - CFString::wrap_under_get_rule(kSecMatchCaseInsensitive), - CFBoolean::from(case_insensitive).as_CFType() - )); + params.add( + &kSecMatchCaseInsensitive.to_void(), + &CFBoolean::from(case_insensitive).to_void() + ); } if let Some(key_class) = self.key_class { - params.push((CFString::wrap_under_get_rule(kSecAttrKeyClass), key_class.to_value())); + params.add( + &kSecAttrKeyClass.to_void(), + &key_class.0.to_void() + ); } if self.load_refs { - params.push(( - CFString::wrap_under_get_rule(kSecReturnRef), - CFBoolean::true_value().into_CFType(), - )); + params.add( + &kSecReturnRef.to_void(), + &CFBoolean::true_value().to_void(), + ); } if self.load_attributes { - params.push(( - CFString::wrap_under_get_rule(kSecReturnAttributes), - CFBoolean::true_value().into_CFType(), - )); + params.add( + &kSecReturnAttributes.to_void(), + &CFBoolean::true_value().to_void(), + ); } if self.load_data { - params.push(( - CFString::wrap_under_get_rule(kSecReturnData), - CFBoolean::true_value().into_CFType(), - )); + params.add( + &kSecReturnData.to_void(), + &CFBoolean::true_value().to_void(), + ); } if let Some(limit) = self.limit { - params.push(( - CFString::wrap_under_get_rule(kSecMatchLimit), - limit.to_value(), - )); + params.add( + &kSecMatchLimit.to_void(), + &limit.to_value().to_void(), + ); } if let Some(ref label) = self.label { - params.push(( - CFString::wrap_under_get_rule(kSecAttrLabel), - label.as_CFType(), - )); + params.add( + &kSecAttrLabel.to_void(), + &label.to_void(), + ); } if let Some(ref trusted_only) = self.trusted_only { - params.push(( - CFString::wrap_under_get_rule(kSecMatchTrustedOnly), - if *trusted_only { CFBoolean::true_value().into_CFType() } else { CFBoolean::false_value().into_CFType() }, - )); + params.add( + &kSecMatchTrustedOnly.to_void(), + &(if *trusted_only { + CFBoolean::true_value() + } else { + CFBoolean::false_value() + }) + .to_void() + ); } if let Some(ref service) = self.service { - params.push(( - CFString::wrap_under_get_rule(kSecAttrService), - service.as_CFType(), - )); + params.add( + &kSecAttrService.to_void(), + &service.to_void(), + ); } #[cfg(target_os = "macos")] { if let Some(ref subject) = self.subject { - params.push(( - CFString::wrap_under_get_rule(kSecMatchSubjectWholeString), - subject.as_CFType(), - )); + params.add( + &kSecMatchSubjectWholeString.to_void(), + &subject.to_void(), + ); } } if let Some(ref account) = self.account { - params.push(( - CFString::wrap_under_get_rule(kSecAttrAccount), - account.as_CFType(), - )); + params.add( + &kSecAttrAccount.to_void(), + &account.to_void(), + ); } if let Some(ref access_group) = self.access_group { - params.push(( - CFString::wrap_under_get_rule(kSecAttrAccessGroup), - access_group.as_CFType(), - )); + params.add( + &kSecAttrAccessGroup.to_void(), + &access_group.to_void(), + ); } if let Some(ref pub_key_hash) = self.pub_key_hash { - params.push(( - CFString::wrap_under_get_rule(kSecAttrPublicKeyHash), - pub_key_hash.as_CFType(), - )); + params.add( + &kSecAttrPublicKeyHash.to_void(), + &pub_key_hash.to_void(), + ); } if let Some(ref app_label) = self.app_label { - params.push(( - CFString::wrap_under_get_rule(kSecAttrApplicationLabel), - app_label.as_CFType(), - )); + params.add( + &kSecAttrApplicationLabel.to_void(), + &app_label.to_void(), + ); } - let params = CFDictionary::from_CFType_pairs(¶ms); + params.to_immutable() + } + } + + /// Search for objects. + #[inline] + pub fn search(&self) -> Result> { + unsafe { + let params = self.to_dictionary(); let mut ret = ptr::null(); cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?; @@ -433,6 +442,14 @@ impl ItemSearchOptions { Ok(items) } } + + /// Deletes objects matching the search options. + /// + /// Translates to `SecItemDelete`. + #[inline] + pub fn delete(&self) -> Result<()> { + cvt(unsafe { SecItemDelete(self.to_dictionary().as_concrete_TypeRef()) }) + } } unsafe fn get_item(item: CFTypeRef) -> SearchResult { @@ -570,8 +587,7 @@ impl SearchResult { /// 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`. +/// When finished populating options call [`Self::add`]. pub struct ItemAddOptions { /// The value (by ref or data) of the item to add, required. pub value: ItemAddValue, @@ -593,47 +609,56 @@ pub struct ItemAddOptions { impl ItemAddOptions { /// Specifies the item to add. + #[inline] #[must_use] pub fn new(value: ItemAddValue) -> Self { Self{ value, label: None, location: None, service: None, account_name: None, comment: None, description: None, access_group: None } } /// Specifies the `kSecAttrAccount` attribute. + #[inline] pub fn set_account_name(&mut self, account_name: impl AsRef) -> &mut Self { self.account_name = Some(account_name.as_ref().into()); self } /// Specifies the `kSecAttrAccessGroup` attribute. + #[inline] pub fn set_access_group(&mut self, access_group: impl AsRef) -> &mut Self { self.access_group = Some(access_group.as_ref().into()); self } /// Specifies the `kSecAttrComment` attribute. + #[inline] pub fn set_comment(&mut self, comment: impl AsRef) -> &mut Self { self.comment = Some(comment.as_ref().into()); self } /// Specifies the `kSecAttrDescription` attribute. + #[inline] pub fn set_description(&mut self, description: impl AsRef) -> &mut Self { self.description = Some(description.as_ref().into()); self } /// Specifies the `kSecAttrLabel` attribute. + #[inline] pub fn set_label(&mut self, label: impl AsRef) -> &mut Self { self.label = Some(label.as_ref().into()); self } /// Specifies which keychain to add the item to. + #[inline] pub fn set_location(&mut self, location: Location) -> &mut Self { self.location = Some(location); self } /// Specifies the `kSecAttrService` attribute. + #[inline] pub fn set_service(&mut self, service: impl AsRef) -> &mut Self { self.service = Some(service.as_ref().into()); self } - /// Populates a `CFDictionary` to be passed to + /// Populates a `CFDictionary` to be passed to `add_item`. + #[deprecated(since = "3.0.0", note = "use `ItemAddOptions::add` instead")] pub fn to_dictionary(&self) -> CFDictionary { let mut dict = CFMutableDictionary::from_CFType_pairs(&[]); @@ -689,6 +714,14 @@ impl ItemAddOptions { dict.to_immutable() } + /// Adds the item to the keychain. + /// + /// Translates to `SecItemAdd`. + #[inline] + pub fn add(&self) -> Result<()> { + #[allow(deprecated)] + cvt(unsafe { SecItemAdd(self.to_dictionary().as_concrete_TypeRef(), std::ptr::null_mut()) }) + } } /// Value of an item to add to the keychain. @@ -733,6 +766,171 @@ impl AddRef { } } +/// Builder-pattern struct for specifying options for `update_item` (`SecUpdateItem` +/// wrapper). +/// +/// When finished populating options call [`update_item`]. +#[derive(Default)] +pub struct ItemUpdateOptions { + /// Optional value (by ref or data) of the item to update. + pub value: Option, + /// Optional kSecAttrAccount attribute. + pub account_name: Option, + /// Optional kSecAttrAccessGroup attribute. + pub access_group: Option, + /// Optional kSecAttrComment attribute. + pub comment: Option, + /// Optional kSecAttrDescription attribute. + pub description: Option, + /// Optional kSecAttrLabel attribute. + pub label: Option, + /// Optional kSecAttrService attribute. + pub service: Option, + /// Optional keychain location. + pub location: Option, + /// Optional kSecClass. + /// + /// Overwrites `value`'s class if set. + pub class: Option, +} + +impl ItemUpdateOptions { + /// Specifies the item to add. + #[must_use] + pub fn new() -> Self { + Default::default() + } + + /// Specifies the value of the item. + #[inline] + pub fn set_value(&mut self, value: ItemUpdateValue) -> &mut Self { + self.value = Some(value); + self + } + /// Specifies the `kSecClass` attribute. + #[inline] + pub fn set_class(&mut self, class: ItemClass) -> &mut Self { + self.class = Some(class); + self + } + /// Specifies the `kSecAttrAccount` attribute. + #[inline] + pub fn set_account_name(&mut self, account_name: impl AsRef) -> &mut Self { + self.account_name = Some(account_name.as_ref().into()); + self + } + /// Specifies the `kSecAttrAccessGroup` attribute. + #[inline] + pub fn set_access_group(&mut self, access_group: impl AsRef) -> &mut Self { + self.access_group = Some(access_group.as_ref().into()); + self + } + /// Specifies the `kSecAttrComment` attribute. + #[inline] + pub fn set_comment(&mut self, comment: impl AsRef) -> &mut Self { + self.comment = Some(comment.as_ref().into()); + self + } + /// Specifies the `kSecAttrDescription` attribute. + #[inline] + pub fn set_description(&mut self, description: impl AsRef) -> &mut Self { + self.description = Some(description.as_ref().into()); + self + } + /// Specifies the `kSecAttrLabel` attribute. + #[inline] + pub fn set_label(&mut self, label: impl AsRef) -> &mut Self { + self.label = Some(label.as_ref().into()); + self + } + /// Specifies which keychain to add the item to. + #[inline] + pub fn set_location(&mut self, location: Location) -> &mut Self { + self.location = Some(location); + self + } + /// Specifies the `kSecAttrService` attribute. + #[inline] + pub fn set_service(&mut self, service: impl AsRef) -> &mut Self { + self.service = Some(service.as_ref().into()); + self + } + /// Populates a `CFDictionary` to be passed to `update_item`. + #[inline] + fn to_dictionary(&self) -> CFDictionary { + let mut dict = CFMutableDictionary::from_CFType_pairs(&[]); + + if let Some(ref value) = self.value { + let class_opt = match value { + ItemUpdateValue::Ref(ref_) => ref_.class(), + ItemUpdateValue::Data(_) => None, + }; + // `self.class` overwrites `value`'s class if set. + if self.class.is_none() { + if let Some(class) = class_opt { + dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void()); + } + } + let value_pair = match value { + ItemUpdateValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()), + ItemUpdateValue::Data(data) => (unsafe { kSecValueData }.to_void(), data.to_void()), + }; + dict.add(&value_pair.0, &value_pair.1); + } + if let Some(class) = self.class { + dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void()); + } + if let Some(location) = &self.location { + match location { + #[cfg(any(feature = "OSX_10_15", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] + 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()); + }, + } + } + if let Some(account_name) = &self.account_name { + dict.add(&unsafe { kSecAttrAccount }.to_void(), &account_name.to_void()); + } + if let Some(access_group) = &self.access_group { + dict.add(&unsafe { kSecAttrAccessGroup }.to_void(), &access_group.to_void()); + } + if let Some(comment) = &self.comment { + dict.add(&unsafe { kSecAttrComment }.to_void(), &comment.to_void()); + } + if let Some(description) = &self.description { + dict.add(&unsafe { kSecAttrDescription }.to_void(), &description.to_void()); + } + if let Some(label) = &self.label { + dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void()); + } + if let Some(service) = &self.service { + dict.add(&unsafe { kSecAttrService }.to_void(), &service.to_void()); + } + + dict.to_immutable() + } +} + +/// Value of an item to update in the keychain. +pub enum ItemUpdateValue { + /// Pass item by Ref (kSecValueRef) + Ref(AddRef), + /// Pass item by Data (kSecValueData) + /// + /// Note that if the [`ItemClass`] of the updated data is different to the original data + /// stored in the keychain, it should be specified using [`ItemUpdateOptions::set_class`]. + Data(CFData), +} + /// Which keychain to add an item to. /// /// @@ -758,10 +956,20 @@ pub enum Location { /// Translates to `SecItemAdd`. Use `ItemAddOptions` to build an `add_params` /// `CFDictionary`. +#[deprecated(since = "3.0.0", note = "use `ItemAddOptions::add` instead")] +#[allow(deprecated)] pub fn add_item(add_params: CFDictionary) -> Result<()> { cvt(unsafe { SecItemAdd(add_params.as_concrete_TypeRef(), std::ptr::null_mut()) }) } +/// Translates to `SecItemUpdate`. +pub fn update_item(search_params: &ItemSearchOptions, update_params: &ItemUpdateOptions) -> Result<()> { + cvt(unsafe { SecItemUpdate( + search_params.to_dictionary().as_concrete_TypeRef(), + update_params.to_dictionary().as_concrete_TypeRef() + )}) +} + #[cfg(test)] mod test { use super::*;