//! Encryption key support use crate::cvt; use core_foundation::{ base::TCFType, string::{CFStringRef, CFString}, dictionary::CFMutableDictionary, }; use core_foundation::base::ToVoid; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use core_foundation::boolean::CFBoolean; #[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::number::CFNumber; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use core_foundation::error::{CFError, CFErrorRef}; use security_framework_sys::{ item::{kSecAttrKeyTypeRSA, kSecValueRef}, keychain_item::SecItemDelete }; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use security_framework_sys::{item::{ kSecAttrIsPermanent, kSecAttrLabel, kSecAttrKeyType, kSecAttrKeySizeInBits, kSecPrivateKeyAttrs }}; #[cfg(target_os="macos")] use security_framework_sys::item::{ kSecAttrKeyType3DES, kSecAttrKeyTypeDSA, kSecAttrKeyTypeAES, kSecAttrKeyTypeDES, kSecAttrKeyTypeRC4, kSecAttrKeyTypeCAST, }; use security_framework_sys::key::SecKeyGetTypeID; use security_framework_sys::base::SecKeyRef; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] pub use security_framework_sys::key::Algorithm; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use security_framework_sys::key::{ SecKeyCopyAttributes, SecKeyCopyExternalRepresentation, SecKeyCreateSignature, SecKeyCreateRandomKey, SecKeyCopyPublicKey, }; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use security_framework_sys::item::kSecAttrApplicationLabel; use std::fmt; use crate::base::Error; #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] use crate::item::Location; /// Types of `SecKey`s. #[derive(Debug, Copy, Clone)] pub struct KeyType(CFStringRef); #[allow(missing_docs)] impl KeyType { #[inline(always)] #[must_use] pub fn rsa() -> Self { unsafe { Self(kSecAttrKeyTypeRSA) } } #[cfg(target_os = "macos")] #[inline(always)] #[must_use] pub fn dsa() -> Self { unsafe { Self(kSecAttrKeyTypeDSA) } } #[cfg(target_os = "macos")] #[inline(always)] #[must_use] pub fn aes() -> Self { unsafe { Self(kSecAttrKeyTypeAES) } } #[cfg(target_os = "macos")] #[inline(always)] #[must_use] pub fn des() -> Self { unsafe { Self(kSecAttrKeyTypeDES) } } #[cfg(target_os = "macos")] #[inline(always)] #[must_use] pub fn triple_des() -> Self { unsafe { Self(kSecAttrKeyType3DES) } } #[cfg(target_os = "macos")] #[inline(always)] #[must_use] pub fn rc4() -> Self { unsafe { Self(kSecAttrKeyTypeRC4) } } #[cfg(target_os = "macos")] #[inline(always)] #[must_use] pub fn cast() -> Self { unsafe { Self(kSecAttrKeyTypeCAST) } } #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] #[inline(always)] #[must_use] pub fn ec() -> Self { use security_framework_sys::item::kSecAttrKeyTypeEC; unsafe { Self(kSecAttrKeyTypeEC) } } pub(crate) fn to_str(self) -> CFString { unsafe { CFString::wrap_under_get_rule(self.0) } } } 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 `SecKeyCreateRandomKey` /// `GenerateKeyOptions` provides a helper to create an attribute /// `CFDictionary`. pub fn generate(attributes: CFDictionary) -> Result { let mut error: CFErrorRef = ::std::ptr::null_mut(); let sec_key = unsafe { SecKeyCreateRandomKey(attributes.as_concrete_TypeRef(), &mut error)}; if !error.is_null() { Err(unsafe { CFError::wrap_under_create_rule(error) }) } else { Ok(unsafe { SecKey::wrap_under_create_rule(sec_key) }) } } /// Returns the programmatic identifier for the key. For keys of class /// kSecAttrKeyClassPublic and kSecAttrKeyClassPrivate, the value is the /// hash of the public key. #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] pub fn application_label(&self) -> Option> { self.attributes() .find(unsafe { kSecAttrApplicationLabel.to_void() }) .map(|v| unsafe { CFData::wrap_under_get_rule(v.cast()) }.to_vec()) } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] /// Translates to `SecKeyCopyAttributes` #[must_use] 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` #[must_use] pub fn external_representation(&self) -> Option { 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"))] /// Translates to `SecKeyCopyPublicKey` #[must_use] pub fn public_key(&self) -> Option { let pub_seckey = unsafe { SecKeyCopyPublicKey(self.0.cast()) }; if pub_seckey.is_null() { return None; } Some(unsafe { SecKey::wrap_under_create_rule(pub_seckey) }) } #[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: &[u8]) -> Result, CFError> { let mut error: CFErrorRef = std::ptr::null_mut(); let output = unsafe { SecKeyCreateSignature( self.as_concrete_TypeRef(), algorithm.into(), CFData::from_buffer(input).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()) } } /// Verifies the cryptographic signature for a block of data using a public /// key and specified algorithm. #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] pub fn verify_signature(&self, algorithm: Algorithm, signed_data: &[u8], signature: &[u8]) -> Result { use security_framework_sys::key::SecKeyVerifySignature; let mut error: CFErrorRef = std::ptr::null_mut(); let valid = unsafe { SecKeyVerifySignature( self.as_concrete_TypeRef(), algorithm.into(), CFData::from_buffer(signed_data).as_concrete_TypeRef(), CFData::from_buffer(signature).as_concrete_TypeRef(), &mut error, ) }; if !error.is_null() { return Err(unsafe { CFError::wrap_under_create_rule(error) })?; } Ok(valid != 0) } /// Translates to `SecItemDelete`, passing in the `SecKeyRef` pub fn delete(&self) -> Result<(), Error> { let query = CFMutableDictionary::from_CFType_pairs(&[( unsafe { kSecValueRef }.to_void(), self.to_void(), )]); cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) }) } } /// Where to generate the key. pub enum Token { /// Generate the key in software, compatible with all `KeyType`s. Software, /// Generate the key in the Secure Enclave such that the private key is not /// extractable. Only compatible with `KeyType::ec()`. SecureEnclave, } /// Helper for creating `CFDictionary` attributes for `SecKey::generate` /// Recommended reading: /// #[derive(Default)] #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] pub struct GenerateKeyOptions { /// kSecAttrKeyType pub key_type: Option, /// kSecAttrKeySizeInBits pub size_in_bits: Option, /// kSecAttrLabel pub label: Option, /// kSecAttrTokenID pub token: Option, /// Which keychain to store the key in, if any. pub location: Option, } #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] impl GenerateKeyOptions { /// Set `key_type` pub fn set_key_type(&mut self, key_type: KeyType) -> &mut Self { self.key_type = Some(key_type); self } /// Set `size_in_bits` pub fn set_size_in_bits(&mut self, size_in_bits: u32) -> &mut Self { self.size_in_bits = Some(size_in_bits); self } /// Set `label` pub fn set_label(&mut self, label: impl Into) -> &mut Self { self.label = Some(label.into()); self } /// Set `token` pub fn set_token(&mut self, token: Token) -> &mut Self { self.token = Some(token); self } /// Set `location` pub fn set_location(&mut self, location: Location) -> &mut Self { self.location = Some(location); self } /// Collect options into a `CFDictioanry` pub fn to_dictionary(&self) -> CFDictionary { #[cfg(target_os = "macos")] use security_framework_sys::item::kSecUseKeychain; use security_framework_sys::item::{ kSecAttrTokenID, kSecAttrTokenIDSecureEnclave, kSecPublicKeyAttrs, }; let is_permanent = CFBoolean::from(self.location.is_some()); let private_attributes = CFMutableDictionary::from_CFType_pairs(&[( unsafe { kSecAttrIsPermanent }.to_void(), is_permanent.to_void(), )]); let public_attributes = CFMutableDictionary::from_CFType_pairs(&[( unsafe { kSecAttrIsPermanent }.to_void(), is_permanent.to_void(), )]); let key_type = self.key_type.unwrap_or_else(KeyType::rsa).to_str(); let size_in_bits = self.size_in_bits.unwrap_or(match () { _ if key_type == KeyType::rsa().to_str() => 2048, _ if key_type == KeyType::ec().to_str() => 256, _ => 256, }); let size_in_bits = CFNumber::from(size_in_bits as i32); 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(), ), ]; 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())); } #[cfg(target_os = "macos")] match &self.location { #[cfg(feature = "OSX_10_15")] Some(Location::DataProtectionKeychain) => { use security_framework_sys::item::kSecUseDataProtectionKeychain; attribute_key_values.push(( unsafe { kSecUseDataProtectionKeychain }.to_void(), CFBoolean::true_value().to_void(), )); } Some(Location::FileKeychain(keychain)) => { attribute_key_values.push(( unsafe { kSecUseKeychain }.to_void(), keychain.as_concrete_TypeRef().to_void(), )); } _ => {} } match self.token.as_ref().unwrap_or(&Token::Software) { Token::Software => {}, Token::SecureEnclave => { attribute_key_values.push(( unsafe { kSecAttrTokenID }.to_void(), unsafe { kSecAttrTokenIDSecureEnclave }.to_void(), )); } } CFMutableDictionary::from_CFType_pairs(&attribute_key_values).to_immutable() } } impl fmt::Debug for SecKey { #[cold] fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("SecKey").finish_non_exhaustive() } }