From ac1909a957d9e6082a3d2b00e566b081e4bdc05e Mon Sep 17 00:00:00 2001 From: "Adrien P." Date: Mon, 6 Jan 2025 18:25:59 +0400 Subject: [PATCH] Add keychain synchronisation and authentication --- security-framework-sys/src/item.rs | 4 ++++ security-framework/src/item.rs | 16 +++++++++++++ security-framework/src/key.rs | 36 +++++++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/security-framework-sys/src/item.rs b/security-framework-sys/src/item.rs index 920b8b4b..5427bc99 100644 --- a/security-framework-sys/src/item.rs +++ b/security-framework-sys/src/item.rs @@ -44,6 +44,10 @@ extern "C" { pub static kSecAttrTokenID: CFStringRef; #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] pub static kSecAttrTokenIDSecureEnclave: CFStringRef; + #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] + pub static kSecUseAuthenticationContext: CFStringRef; + #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] + pub static kSecAttrSynchronizable: CFStringRef; pub static kSecAttrKeySizeInBits: CFStringRef; diff --git a/security-framework/src/item.rs b/security-framework/src/item.rs index b9d31dc8..8cd5fbec 100644 --- a/security-framework/src/item.rs +++ b/security-framework/src/item.rs @@ -144,6 +144,8 @@ pub struct ItemSearchOptions { pub_key_hash: Option, serial_number: Option, app_label: Option, + #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] + authentication_context: Option, } #[cfg(target_os = "macos")] @@ -298,6 +300,15 @@ impl ItemSearchOptions { self } + // The corresponding value is of type LAContext, and represents a reusable + // local authentication context that should be used for keychain item authentication. + #[inline(always)] + #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] + pub fn authentication_context(&mut self, authentication_context: *mut std::os::raw::c_void) -> &mut Self { + self.authentication_context = unsafe { Some(CFType::wrap_under_create_rule(authentication_context)) }; + self + } + /// Populates a `CFDictionary` to be passed to `update_item` or `delete_item`. // CFDictionary should not be exposed in public Rust APIs. #[inline] @@ -396,6 +407,11 @@ impl ItemSearchOptions { params.add(&kSecAttrApplicationLabel.to_void(), &app_label.to_void()); } + #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] + if let Some(ref authentication_context) = self.authentication_context { + params.add(&kSecUseAuthenticationContext.to_void(), &authentication_context.to_void()); + } + params.to_immutable() } } diff --git a/security-framework/src/key.rs b/security-framework/src/key.rs index 0126ee4d..2cfbe73f 100644 --- a/security-framework/src/key.rs +++ b/security-framework/src/key.rs @@ -27,6 +27,8 @@ use security_framework_sys::{item::{ kSecAttrIsPermanent, kSecAttrLabel, kSecAttrKeyType, kSecAttrKeySizeInBits, kSecPrivateKeyAttrs, kSecAttrAccessControl }}; +#[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] +use security_framework_sys::item::kSecAttrSynchronizable; #[cfg(target_os = "macos")] use security_framework_sys::item::{ kSecAttrKeyType3DES, kSecAttrKeyTypeDSA, kSecAttrKeyTypeAES, @@ -34,7 +36,7 @@ use security_framework_sys::item::{ }; use security_framework_sys::base::SecKeyRef; -use security_framework_sys::key::SecKeyGetTypeID; +use security_framework_sys::key::{SecKeyGetTypeID, kSecKeyOperationTypeSign}; #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] pub use security_framework_sys::key::Algorithm; @@ -381,6 +383,8 @@ pub struct GenerateKeyOptions { /// Access control #[deprecated(note = "use set_access_control()")] pub access_control: Option, + /// kSecAttrSynchronizable + pub synchronizable: Option, } #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] @@ -422,6 +426,13 @@ impl GenerateKeyOptions { self } + /// Set `synchronizable` + #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] + pub fn set_synchronizable(&mut self, synchronizable: bool) -> &mut Self { + self.synchronizable = Some(synchronizable); + self + } + /// Collect options into a `CFDictioanry` // CFDictionary should not be exposed in public Rust APIs. #[deprecated(note = "Pass the options to SecKey::new")] @@ -449,8 +460,11 @@ impl GenerateKeyOptions { let key_type = self.key_type.unwrap_or_else(KeyType::rsa).to_str(); let size_in_bits = self.size_in_bits.unwrap_or(match () { + #[cfg(target_os = "macos")] + _ if key_type == KeyType::aes().to_str() => 256, _ if key_type == KeyType::rsa().to_str() => 2048, _ if key_type == KeyType::ec().to_str() => 256, + _ if key_type == KeyType::ec_sec_prime_random().to_str() => 256, _ => 256, }); let size_in_bits = CFNumber::from(size_in_bits as i32); @@ -458,9 +472,12 @@ impl GenerateKeyOptions { let mut attribute_key_values = vec![ (unsafe { kSecAttrKeyType }.to_void(), key_type.to_void()), (unsafe { kSecAttrKeySizeInBits }.to_void(), size_in_bits.to_void()), - (unsafe { kSecPrivateKeyAttrs }.to_void(), private_attributes.to_void()), - (unsafe { kSecPublicKeyAttrs }.to_void(), public_attributes.to_void()), ]; + if key_type != KeyType::aes().to_str() { + attribute_key_values.push((unsafe { kSecPublicKeyAttrs }.to_void(), public_attributes.to_void())); + attribute_key_values.push((unsafe { kSecPrivateKeyAttrs }.to_void(), private_attributes.to_void())); + } + let label = self.label.as_deref().map(CFString::new); if let Some(label) = &label { attribute_key_values.push((unsafe { kSecAttrLabel }.to_void(), label.to_void())); @@ -495,6 +512,19 @@ impl GenerateKeyOptions { } } + #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))] + if let Some(ref synchronizable) = self.synchronizable { + attribute_key_values.push(( + unsafe { kSecAttrSynchronizable }.to_void(), + (if *synchronizable { + CFBoolean::true_value() + } else { + CFBoolean::false_value() + }) + .to_void(), + )); + } + CFMutableDictionary::from_CFType_pairs(&attribute_key_values).to_immutable() } }