| //! 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<Self, CFError> { |
| 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<Vec<u8>> { |
| 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<CFData> { |
| 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<Self> { |
| 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<Vec<u8>, 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<bool, CFError> { |
| 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: |
| /// <https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains> |
| #[derive(Default)] |
| #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] |
| pub struct GenerateKeyOptions { |
| /// kSecAttrKeyType |
| pub key_type: Option<KeyType>, |
| /// kSecAttrKeySizeInBits |
| pub size_in_bits: Option<u32>, |
| /// kSecAttrLabel |
| pub label: Option<String>, |
| /// kSecAttrTokenID |
| pub token: Option<Token>, |
| /// Which keychain to store the key in, if any. |
| pub location: Option<Location>, |
| } |
| |
| #[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<String>) -> &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() |
| } |
| } |