security_framework/
key.rs

1//! Encryption key support
2
3use core_foundation::{declare_TCFType, impl_TCFType};
4use crate::cvt;
5use core_foundation::{
6    base::TCFType, string::{CFStringRef, CFString},
7    dictionary::CFMutableDictionary,
8};
9use core_foundation::base::ToVoid;
10#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
11use core_foundation::boolean::CFBoolean;
12#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
13use core_foundation::data::CFData;
14#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
15use core_foundation::dictionary::CFDictionary;
16#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
17use core_foundation::number::CFNumber;
18#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
19use core_foundation::error::{CFError, CFErrorRef};
20
21use security_framework_sys::{
22    item::{kSecAttrKeyTypeRSA, kSecValueRef},
23    keychain_item::SecItemDelete,
24};
25#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
26use security_framework_sys::{item::{
27    kSecAttrIsPermanent, kSecAttrLabel, kSecAttrKeyType,
28    kSecAttrKeySizeInBits, kSecPrivateKeyAttrs, kSecAttrAccessControl
29}};
30#[cfg(target_os = "macos")]
31use security_framework_sys::item::{
32    kSecAttrKeyType3DES, kSecAttrKeyTypeDSA, kSecAttrKeyTypeAES,
33    kSecAttrKeyTypeDES, kSecAttrKeyTypeRC4, kSecAttrKeyTypeCAST,
34};
35
36use security_framework_sys::base::SecKeyRef;
37use security_framework_sys::key::SecKeyGetTypeID;
38
39#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
40pub use security_framework_sys::key::Algorithm;
41
42#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
43use security_framework_sys::key::{
44    SecKeyCopyAttributes, SecKeyCopyExternalRepresentation,
45    SecKeyCreateSignature, SecKeyCreateRandomKey,
46    SecKeyCopyPublicKey,
47    SecKeyCreateDecryptedData, SecKeyCreateEncryptedData,
48};
49#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
50use security_framework_sys::item::kSecAttrApplicationLabel;
51use std::fmt;
52
53
54use crate::base::Error;
55#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
56use crate::item::Location;
57#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
58use crate::access_control::SecAccessControl;
59/// Types of `SecKey`s.
60#[derive(Debug, Copy, Clone)]
61pub struct KeyType(CFStringRef);
62
63#[allow(missing_docs)]
64impl KeyType {
65    #[inline(always)]
66    #[must_use]
67    pub fn rsa() -> Self {
68        unsafe { Self(kSecAttrKeyTypeRSA) }
69    }
70
71    #[cfg(target_os = "macos")]
72    #[inline(always)]
73    #[must_use]
74    pub fn dsa() -> Self {
75        unsafe { Self(kSecAttrKeyTypeDSA) }
76    }
77
78    #[cfg(target_os = "macos")]
79    #[inline(always)]
80    #[must_use]
81    pub fn aes() -> Self {
82        unsafe { Self(kSecAttrKeyTypeAES) }
83    }
84
85    #[cfg(target_os = "macos")]
86    #[inline(always)]
87    #[must_use]
88    pub fn des() -> Self {
89        unsafe { Self(kSecAttrKeyTypeDES) }
90    }
91
92    #[cfg(target_os = "macos")]
93    #[inline(always)]
94    #[must_use]
95    pub fn triple_des() -> Self {
96        unsafe { Self(kSecAttrKeyType3DES) }
97    }
98
99    #[cfg(target_os = "macos")]
100    #[inline(always)]
101    #[must_use]
102    pub fn rc4() -> Self {
103        unsafe { Self(kSecAttrKeyTypeRC4) }
104    }
105
106    #[cfg(target_os = "macos")]
107    #[inline(always)]
108    #[must_use]
109    pub fn cast() -> Self {
110        unsafe { Self(kSecAttrKeyTypeCAST) }
111    }
112
113    #[inline(always)]
114    #[must_use]
115    pub fn ec() -> Self {
116        use security_framework_sys::item::kSecAttrKeyTypeEC;
117
118        unsafe { Self(kSecAttrKeyTypeEC) }
119    }
120
121    #[inline(always)]
122    #[must_use]
123    pub fn ec_sec_prime_random() -> Self {
124        use security_framework_sys::item::kSecAttrKeyTypeECSECPrimeRandom;
125
126        unsafe { Self(kSecAttrKeyTypeECSECPrimeRandom) }
127    }
128
129    pub(crate) fn to_str(self) -> CFString {
130        unsafe { CFString::wrap_under_get_rule(self.0) }
131    }
132}
133
134declare_TCFType! {
135    /// A type representing an encryption key.
136    SecKey, SecKeyRef
137}
138impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID);
139
140unsafe impl Sync for SecKey {}
141unsafe impl Send for SecKey {}
142
143impl SecKey {
144    /// Translates to `SecKeyCreateRandomKey`
145    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
146    #[allow(deprecated)]
147    #[doc(alias = "SecKeyCreateRandomKey")]
148    pub fn new(options: &GenerateKeyOptions) -> Result<Self, CFError> {
149        Self::generate(options.to_dictionary())
150    }
151
152    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
153    /// Translates to `SecKeyCreateRandomKey`
154    /// `GenerateKeyOptions` provides a helper to create an attribute `CFDictionary`.
155    #[deprecated(note = "Use SecKey::new")]
156    pub fn generate(attributes: CFDictionary) -> Result<Self, CFError> {
157        let mut error: CFErrorRef = ::std::ptr::null_mut();
158        let sec_key = unsafe { SecKeyCreateRandomKey(attributes.as_concrete_TypeRef(), &mut error) };
159        if !error.is_null() {
160            Err(unsafe { CFError::wrap_under_create_rule(error) })
161        } else {
162            Ok(unsafe { Self::wrap_under_create_rule(sec_key) })
163        }
164    }
165
166    /// Returns the programmatic identifier for the key. For keys of class
167    /// kSecAttrKeyClassPublic and kSecAttrKeyClassPrivate, the value is the
168    /// hash of the public key.
169    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
170    #[must_use]
171    pub fn application_label(&self) -> Option<Vec<u8>> {
172        self.attributes()
173            .find(unsafe { kSecAttrApplicationLabel.to_void() })
174            .map(|v| unsafe { CFData::wrap_under_get_rule(v.cast()) }.to_vec())
175    }
176
177    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
178    /// Translates to `SecKeyCopyAttributes`
179    // TODO: deprecate and remove. CFDictionary should not be exposed in public Rust APIs.
180    #[must_use]
181    pub fn attributes(&self) -> CFDictionary {
182        let pka = unsafe { SecKeyCopyAttributes(self.to_void() as _) };
183        unsafe { CFDictionary::wrap_under_create_rule(pka) }
184    }
185
186    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
187    /// Translates to `SecKeyCopyExternalRepresentation`
188    // TODO: deprecate and remove. CFData should not be exposed in public Rust APIs.
189    #[must_use]
190    pub fn external_representation(&self) -> Option<CFData> {
191        let mut error: CFErrorRef = ::std::ptr::null_mut();
192        let data = unsafe { SecKeyCopyExternalRepresentation(self.to_void() as _, &mut error) };
193        if data.is_null() {
194            return None;
195        }
196        Some(unsafe { CFData::wrap_under_create_rule(data) })
197    }
198
199    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
200    /// Translates to `SecKeyCopyPublicKey`
201    #[must_use]
202    pub fn public_key(&self) -> Option<Self> {
203        let pub_seckey = unsafe { SecKeyCopyPublicKey(self.0.cast()) };
204        if pub_seckey.is_null() {
205            return None;
206        }
207
208        Some(unsafe { Self::wrap_under_create_rule(pub_seckey) })
209    }
210
211    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
212    /// Encrypts a block of data using a public key and specified algorithm
213    pub fn encrypt_data(&self, algorithm: Algorithm, input: &[u8]) -> Result<Vec<u8>, CFError> {
214        let mut error: CFErrorRef = std::ptr::null_mut();
215
216        let output = unsafe {
217            SecKeyCreateEncryptedData(self.as_concrete_TypeRef(), algorithm.into(), CFData::from_buffer(input).as_concrete_TypeRef(), &mut error)
218        };
219
220        if error.is_null() {
221            let output = unsafe { CFData::wrap_under_create_rule(output) };
222            Ok(output.to_vec())
223        } else {
224            Err(unsafe { CFError::wrap_under_create_rule(error) })
225        }
226    }
227
228    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
229    /// Decrypts a block of data using a private key and specified algorithm
230    pub fn decrypt_data(&self, algorithm: Algorithm, input: &[u8]) -> Result<Vec<u8>, CFError> {
231        let mut error: CFErrorRef = std::ptr::null_mut();
232
233        let output = unsafe {
234            SecKeyCreateDecryptedData(self.as_concrete_TypeRef(), algorithm.into(), CFData::from_buffer(input).as_concrete_TypeRef(), &mut error)
235        };
236
237        if error.is_null() {
238            let output = unsafe { CFData::wrap_under_create_rule(output) };
239            Ok(output.to_vec())
240        } else {
241            Err(unsafe { CFError::wrap_under_create_rule(error) })
242        }
243    }
244
245    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
246    /// Creates the cryptographic signature for a block of data using a private
247    /// key and specified algorithm.
248    pub fn create_signature(&self, algorithm: Algorithm, input: &[u8]) -> Result<Vec<u8>, CFError> {
249        let mut error: CFErrorRef = std::ptr::null_mut();
250
251        let output = unsafe {
252            SecKeyCreateSignature(
253                self.as_concrete_TypeRef(),
254                algorithm.into(),
255                CFData::from_buffer(input).as_concrete_TypeRef(),
256                &mut error,
257            )
258        };
259
260        if error.is_null() {
261            let output = unsafe { CFData::wrap_under_create_rule(output) };
262            Ok(output.to_vec())
263        } else {
264            Err(unsafe { CFError::wrap_under_create_rule(error) })
265        }
266    }
267
268    /// Verifies the cryptographic signature for a block of data using a public
269    /// key and specified algorithm.
270    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
271    pub fn verify_signature(&self, algorithm: Algorithm, signed_data: &[u8], signature: &[u8]) -> Result<bool, CFError> {
272        use security_framework_sys::key::SecKeyVerifySignature;
273        let mut error: CFErrorRef = std::ptr::null_mut();
274
275        let valid = unsafe {
276            SecKeyVerifySignature(
277                self.as_concrete_TypeRef(),
278                algorithm.into(),
279                CFData::from_buffer(signed_data).as_concrete_TypeRef(),
280                CFData::from_buffer(signature).as_concrete_TypeRef(),
281                &mut error,
282            )
283        };
284
285        if !error.is_null() {
286            return Err(unsafe { CFError::wrap_under_create_rule(error) })?;
287        }
288        Ok(valid != 0)
289    }
290
291    /// Performs the Diffie-Hellman style of key exchange.
292    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
293    pub fn key_exchange(
294        &self,
295        algorithm: Algorithm,
296        public_key: &Self,
297        requested_size: usize,
298        shared_info: Option<&[u8]>,
299    ) -> Result<Vec<u8>, CFError> {
300        use core_foundation::data::CFData;
301        use security_framework_sys::item::{
302            kSecKeyKeyExchangeParameterRequestedSize, kSecKeyKeyExchangeParameterSharedInfo,
303        };
304
305        unsafe {
306            let mut params = vec![(
307                CFString::wrap_under_get_rule(kSecKeyKeyExchangeParameterRequestedSize),
308                CFNumber::from(requested_size as i64).into_CFType(),
309            )];
310
311            if let Some(shared_info) = shared_info {
312                params.push((
313                    CFString::wrap_under_get_rule(kSecKeyKeyExchangeParameterSharedInfo),
314                    CFData::from_buffer(shared_info).as_CFType(),
315                ));
316            };
317
318            let parameters = CFDictionary::from_CFType_pairs(&params);
319
320            let mut error: CFErrorRef = std::ptr::null_mut();
321
322            let output = security_framework_sys::key::SecKeyCopyKeyExchangeResult(
323                self.as_concrete_TypeRef(),
324                algorithm.into(),
325                public_key.as_concrete_TypeRef(),
326                parameters.as_concrete_TypeRef(),
327                &mut error,
328            );
329
330            if error.is_null() {
331                let output = CFData::wrap_under_create_rule(output);
332                Ok(output.to_vec())
333            } else {
334                Err(CFError::wrap_under_create_rule(error))
335            }
336        }
337    }
338
339    /// Translates to `SecItemDelete`, passing in the `SecKeyRef`
340    pub fn delete(&self) -> Result<(), Error> {
341        let query = CFMutableDictionary::from_CFType_pairs(&[(
342            unsafe { kSecValueRef }.to_void(),
343            self.to_void(),
344        )]);
345
346        cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) })
347    }
348}
349
350/// Where to generate the key.
351#[derive(Debug)]
352pub enum Token {
353    /// Generate the key in software, compatible with all `KeyType`s.
354    Software,
355    /// Generate the key in the Secure Enclave such that the private key is not
356    /// extractable. Only compatible with `KeyType::ec()`.
357    SecureEnclave,
358}
359
360/// Helper for creating `CFDictionary` attributes for `SecKey::generate`
361/// Recommended reading:
362/// <https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains>
363#[derive(Debug, Default)]
364#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
365pub struct GenerateKeyOptions {
366    /// kSecAttrKeyType
367    #[deprecated(note = "use set_key_type()")]
368    pub key_type: Option<KeyType>,
369    /// kSecAttrKeySizeInBits
370    #[deprecated(note = "use set_size_in_bits()")]
371    pub size_in_bits: Option<u32>,
372    /// kSecAttrLabel
373    #[deprecated(note = "use set_label()")]
374    pub label: Option<String>,
375    /// kSecAttrTokenID
376    #[deprecated(note = "use set_token()")]
377    pub token: Option<Token>,
378    /// Which keychain to store the key in, if any.
379    #[deprecated(note = "use set_location()")]
380    pub location: Option<Location>,
381    /// Access control
382    #[deprecated(note = "use set_access_control()")]
383    pub access_control: Option<SecAccessControl>,
384    /// `kSecAttrSynchronizable`
385    #[cfg(feature = "sync-keychain")]
386    synchronizable: Option<bool>,
387}
388
389#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
390#[allow(deprecated)]
391impl GenerateKeyOptions {
392    /// Set `key_type`
393    pub fn set_key_type(&mut self, key_type: KeyType) -> &mut Self {
394        self.key_type = Some(key_type);
395        self
396    }
397
398    /// Set `size_in_bits`
399    pub fn set_size_in_bits(&mut self, size_in_bits: u32) -> &mut Self {
400        self.size_in_bits = Some(size_in_bits);
401        self
402    }
403
404    /// Set `label`
405    pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self {
406        self.label = Some(label.into());
407        self
408    }
409
410    /// Set `token`
411    pub fn set_token(&mut self, token: Token) -> &mut Self {
412        self.token = Some(token);
413        self
414    }
415
416    /// Set `location`
417    pub fn set_location(&mut self, location: Location) -> &mut Self {
418        self.location = Some(location);
419        self
420    }
421
422    /// Set `access_control`
423    pub fn set_access_control(&mut self, access_control: SecAccessControl) -> &mut Self {
424        self.access_control = Some(access_control);
425        self
426    }
427
428    /// Set `synchronizable` (`kSecAttrSynchronizable`)
429    #[cfg(feature = "sync-keychain")]
430    pub fn set_synchronizable(&mut self, synchronizable: bool) -> &mut Self {
431        self.synchronizable = Some(synchronizable);
432        self
433    }
434
435    /// Collect options into a `CFDictioanry`
436    // CFDictionary should not be exposed in public Rust APIs.
437    #[deprecated(note = "Pass the options to SecKey::new")]
438    pub fn to_dictionary(&self) -> CFDictionary {
439        #[cfg(target_os = "macos")]
440        use security_framework_sys::item::kSecUseKeychain;
441        use security_framework_sys::item::{
442            kSecAttrTokenID, kSecAttrTokenIDSecureEnclave, kSecPublicKeyAttrs,
443        };
444
445        let is_permanent = CFBoolean::from(self.location.is_some());
446        let mut private_attributes = CFMutableDictionary::from_CFType_pairs(&[(
447            unsafe { kSecAttrIsPermanent }.to_void(),
448            is_permanent.to_void(),
449        )]);
450        if let Some(access_control) = &self.access_control {
451            private_attributes.set(unsafe { kSecAttrAccessControl }.to_void(), access_control.to_void());
452        }
453
454        let public_attributes = CFMutableDictionary::from_CFType_pairs(&[(
455            unsafe { kSecAttrIsPermanent }.to_void(),
456            is_permanent.to_void(),
457        )]);
458
459        let key_type = self.key_type.unwrap_or_else(KeyType::rsa).to_str();
460
461        let size_in_bits = self.size_in_bits.unwrap_or(match () {
462            #[cfg(target_os = "macos")]
463            _ if key_type == KeyType::aes().to_str() => 256,
464            _ if key_type == KeyType::rsa().to_str() => 2048,
465            _ if key_type == KeyType::ec().to_str() => 256,
466            _ if key_type == KeyType::ec_sec_prime_random().to_str() => 256,
467            _ => 256,
468        });
469        let size_in_bits = CFNumber::from(size_in_bits as i32);
470
471        let mut attribute_key_values = vec![
472            (unsafe { kSecAttrKeyType }.to_void(), key_type.to_void()),
473            (unsafe { kSecAttrKeySizeInBits }.to_void(), size_in_bits.to_void()),
474        ];
475        #[cfg(target_os = "macos")]
476        if key_type != KeyType::aes().to_str() {
477                attribute_key_values.push((unsafe { kSecPublicKeyAttrs }.to_void(), public_attributes.to_void()));
478                attribute_key_values.push((unsafe { kSecPrivateKeyAttrs }.to_void(), private_attributes.to_void()));
479        }
480
481        let label = self.label.as_deref().map(CFString::new);
482        if let Some(label) = &label {
483            attribute_key_values.push((unsafe { kSecAttrLabel }.to_void(), label.to_void()));
484        }
485
486        #[cfg(target_os = "macos")]
487        match &self.location {
488            #[cfg(feature = "OSX_10_15")]
489            Some(Location::DataProtectionKeychain) => {
490                use security_framework_sys::item::kSecUseDataProtectionKeychain;
491                attribute_key_values.push((
492                    unsafe { kSecUseDataProtectionKeychain }.to_void(),
493                    CFBoolean::true_value().to_void(),
494                ));
495            }
496            Some(Location::FileKeychain(keychain)) => {
497                attribute_key_values.push((
498                    unsafe { kSecUseKeychain }.to_void(),
499                    keychain.as_concrete_TypeRef().to_void(),
500                ));
501            }
502            _ => {}
503        }
504
505        match self.token.as_ref().unwrap_or(&Token::Software) {
506            Token::Software => {},
507            Token::SecureEnclave => {
508                attribute_key_values.push((
509                    unsafe { kSecAttrTokenID }.to_void(),
510                    unsafe { kSecAttrTokenIDSecureEnclave }.to_void(),
511                ));
512            }
513        }
514
515        #[cfg(feature = "sync-keychain")]
516        if let Some(ref synchronizable) = self.synchronizable {
517            attribute_key_values.push((
518                 unsafe { security_framework_sys::item::kSecAttrSynchronizable }.to_void(),
519                CFBoolean::from(*synchronizable).to_void(),
520            ));
521        }
522
523        CFMutableDictionary::from_CFType_pairs(&attribute_key_values).to_immutable()
524    }
525}
526
527impl fmt::Debug for SecKey {
528    #[cold]
529    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
530        fmt.debug_struct("SecKey").finish_non_exhaustive()
531    }
532}