apple_security/
key.rs

1//! Encryption key support
2
3use crate::cvt;
4use core_foundation::{
5    base::TCFType, string::{CFStringRef, CFString},
6    dictionary::CFMutableDictionary,
7};
8use core_foundation::base::ToVoid;
9#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
10use core_foundation::boolean::CFBoolean;
11#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
12use core_foundation::data::CFData;
13#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
14use core_foundation::dictionary::CFDictionary;
15#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
16use core_foundation::number::CFNumber;
17#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
18use core_foundation::error::{CFError, CFErrorRef};
19
20use apple_security_sys::{
21    item::{kSecAttrKeyTypeRSA, kSecValueRef}, 
22    keychain_item::SecItemDelete
23};
24#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
25use apple_security_sys::{item::{
26    kSecAttrIsPermanent, kSecAttrLabel, kSecAttrKeyType,
27    kSecAttrKeySizeInBits, kSecPrivateKeyAttrs
28}};
29#[cfg(target_os="macos")]
30use apple_security_sys::item::{
31    kSecAttrKeyType3DES, kSecAttrKeyTypeDSA, kSecAttrKeyTypeAES,
32    kSecAttrKeyTypeDES, kSecAttrKeyTypeRC4, kSecAttrKeyTypeCAST,
33};
34
35use apple_security_sys::key::SecKeyGetTypeID;
36use apple_security_sys::base::SecKeyRef;
37
38#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
39pub use apple_security_sys::key::Algorithm;
40
41#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
42use apple_security_sys::key::{
43    SecKeyCopyAttributes, SecKeyCopyExternalRepresentation,
44    SecKeyCreateSignature, SecKeyCreateRandomKey,
45    SecKeyCopyPublicKey,
46};
47#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
48use apple_security_sys::item::kSecAttrApplicationLabel;
49use std::fmt;
50
51use crate::base::Error;
52#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
53use crate::item::Location;
54
55/// Types of `SecKey`s.
56#[derive(Debug, Copy, Clone)]
57pub struct KeyType(CFStringRef);
58
59#[allow(missing_docs)]
60impl KeyType {
61    #[inline(always)]
62    #[must_use]
63    pub fn rsa() -> Self {
64        unsafe { Self(kSecAttrKeyTypeRSA) }
65    }
66
67    #[cfg(target_os = "macos")]
68    #[inline(always)]
69    #[must_use]
70    pub fn dsa() -> Self {
71        unsafe { Self(kSecAttrKeyTypeDSA) }
72    }
73
74    #[cfg(target_os = "macos")]
75    #[inline(always)]
76    #[must_use]
77    pub fn aes() -> Self {
78        unsafe { Self(kSecAttrKeyTypeAES) }
79    }
80
81    #[cfg(target_os = "macos")]
82    #[inline(always)]
83    #[must_use]
84    pub fn des() -> Self {
85        unsafe { Self(kSecAttrKeyTypeDES) }
86    }
87
88    #[cfg(target_os = "macos")]
89    #[inline(always)]
90    #[must_use]
91    pub fn triple_des() -> Self {
92        unsafe { Self(kSecAttrKeyType3DES) }
93    }
94
95    #[cfg(target_os = "macos")]
96    #[inline(always)]
97    #[must_use]
98    pub fn rc4() -> Self {
99        unsafe { Self(kSecAttrKeyTypeRC4) }
100    }
101
102    #[cfg(target_os = "macos")]
103    #[inline(always)]
104    #[must_use]
105    pub fn cast() -> Self {
106        unsafe { Self(kSecAttrKeyTypeCAST) }
107    }
108
109    #[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
110    #[inline(always)]
111    #[must_use]
112    pub fn ec() -> Self {
113        use apple_security_sys::item::kSecAttrKeyTypeEC;
114
115        unsafe { Self(kSecAttrKeyTypeEC) }
116    }
117
118    pub(crate) fn to_str(self) -> CFString {
119        unsafe { CFString::wrap_under_get_rule(self.0) }
120    }
121}
122
123declare_TCFType! {
124    /// A type representing an encryption key.
125    SecKey, SecKeyRef
126}
127impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID);
128
129unsafe impl Sync for SecKey {}
130unsafe impl Send for SecKey {}
131
132impl SecKey {
133    #[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
134    /// Translates to `SecKeyCreateRandomKey`
135    /// `GenerateKeyOptions` provides a helper to create an attribute
136    /// `CFDictionary`.
137    pub fn generate(attributes: CFDictionary) -> Result<Self, CFError> {
138        let mut error: CFErrorRef = ::std::ptr::null_mut();
139        let sec_key = unsafe { SecKeyCreateRandomKey(attributes.as_concrete_TypeRef(), &mut error)};
140        if !error.is_null() {
141            Err(unsafe { CFError::wrap_under_create_rule(error) })
142        } else {
143            Ok(unsafe { SecKey::wrap_under_create_rule(sec_key) })
144        }
145    }
146
147    /// Returns the programmatic identifier for the key. For keys of class
148    /// kSecAttrKeyClassPublic and kSecAttrKeyClassPrivate, the value is the
149    /// hash of the public key.
150    #[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
151    pub fn application_label(&self) -> Option<Vec<u8>> {
152        self.attributes()
153            .find(unsafe { kSecAttrApplicationLabel.to_void() })
154            .map(|v| unsafe { CFData::wrap_under_get_rule(v.cast()) }.to_vec())
155    }
156
157    #[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
158    /// Translates to `SecKeyCopyAttributes`
159    #[must_use]
160    pub fn attributes(&self) -> CFDictionary {
161        let pka = unsafe { SecKeyCopyAttributes(self.to_void() as _) };
162        unsafe { CFDictionary::wrap_under_create_rule(pka) }
163    }
164
165    #[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
166    /// Translates to `SecKeyCopyExternalRepresentation`
167    #[must_use]
168    pub fn external_representation(&self) -> Option<CFData> {
169        let mut error: CFErrorRef = ::std::ptr::null_mut();
170        let data = unsafe { SecKeyCopyExternalRepresentation(self.to_void() as _, &mut error) };
171        if data.is_null() {
172            return None;
173        }
174        Some(unsafe { CFData::wrap_under_create_rule(data) })
175    }
176
177    #[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
178    /// Translates to `SecKeyCopyPublicKey`
179    #[must_use]
180    pub fn public_key(&self) -> Option<Self> {
181        let pub_seckey = unsafe { SecKeyCopyPublicKey(self.0.cast()) };
182        if pub_seckey.is_null() {
183            return None;
184        }
185
186        Some(unsafe { SecKey::wrap_under_create_rule(pub_seckey) })
187    }
188
189    #[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
190    /// Creates the cryptographic signature for a block of data using a private
191    /// key and specified algorithm.
192    pub fn create_signature(&self, algorithm: Algorithm, input: &[u8]) -> Result<Vec<u8>, CFError> {
193        let mut error: CFErrorRef = std::ptr::null_mut();
194
195        let output = unsafe {
196            SecKeyCreateSignature(
197                self.as_concrete_TypeRef(),
198                algorithm.into(),
199                CFData::from_buffer(input).as_concrete_TypeRef(),
200                &mut error,
201            )
202        };
203
204        if !error.is_null() {
205            Err(unsafe { CFError::wrap_under_create_rule(error) })
206        } else {
207            let output = unsafe { CFData::wrap_under_create_rule(output) };
208            Ok(output.to_vec())
209        }
210    }
211
212    /// Verifies the cryptographic signature for a block of data using a public
213    /// key and specified algorithm.
214    #[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
215    pub fn verify_signature(&self, algorithm: Algorithm, signed_data: &[u8], signature: &[u8]) -> Result<bool, CFError> {
216        use apple_security_sys::key::SecKeyVerifySignature;
217        let mut error: CFErrorRef = std::ptr::null_mut();
218
219        let valid = unsafe {
220            SecKeyVerifySignature(
221                self.as_concrete_TypeRef(),
222                algorithm.into(),
223                CFData::from_buffer(signed_data).as_concrete_TypeRef(),
224                CFData::from_buffer(signature).as_concrete_TypeRef(),
225                &mut error,
226            )
227        };
228
229        if !error.is_null() {
230            return Err(unsafe { CFError::wrap_under_create_rule(error) })?;
231        }
232        Ok(valid != 0)
233    }
234
235    /// Translates to `SecItemDelete`, passing in the `SecKeyRef`
236    pub fn delete(&self) -> Result<(), Error> {
237        let query = CFMutableDictionary::from_CFType_pairs(&[(
238            unsafe { kSecValueRef }.to_void(),
239            self.to_void(),
240        )]);
241
242        cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) })
243    }
244}
245
246/// Where to generate the key.
247pub enum Token {
248    /// Generate the key in software, compatible with all `KeyType`s.
249    Software,
250    /// Generate the key in the Secure Enclave such that the private key is not
251    /// extractable. Only compatible with `KeyType::ec()`.
252    SecureEnclave,
253}
254
255/// Helper for creating `CFDictionary` attributes for `SecKey::generate`
256/// Recommended reading:
257/// <https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains>
258#[derive(Default)]
259#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
260pub struct GenerateKeyOptions {
261    /// kSecAttrKeyType
262    pub key_type: Option<KeyType>,
263    /// kSecAttrKeySizeInBits
264    pub size_in_bits: Option<u32>,
265    /// kSecAttrLabel
266    pub label: Option<String>,
267    /// kSecAttrTokenID
268    pub token: Option<Token>,
269    /// Which keychain to store the key in, if any.
270    pub location: Option<Location>,
271}
272
273#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
274impl GenerateKeyOptions {
275    /// Set `key_type`
276    pub fn set_key_type(&mut self, key_type: KeyType) -> &mut Self {
277        self.key_type = Some(key_type);
278        self
279    }
280    /// Set `size_in_bits`
281    pub fn set_size_in_bits(&mut self, size_in_bits: u32) -> &mut Self {
282        self.size_in_bits = Some(size_in_bits);
283        self
284    }
285    /// Set `label`
286    pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self {
287        self.label = Some(label.into());
288        self
289    }
290    /// Set `token`
291    pub fn set_token(&mut self, token: Token) -> &mut Self {
292        self.token = Some(token);
293        self
294    }
295    /// Set `location`
296    pub fn set_location(&mut self, location: Location) -> &mut Self {
297        self.location = Some(location);
298        self
299    }
300
301    /// Collect options into a `CFDictioanry`
302    pub fn to_dictionary(&self) -> CFDictionary {
303        #[cfg(target_os = "macos")]
304        use apple_security_sys::item::kSecUseKeychain;
305        use apple_security_sys::item::{
306            kSecAttrTokenID, kSecAttrTokenIDSecureEnclave, kSecPublicKeyAttrs,
307        };
308
309        let is_permanent = CFBoolean::from(self.location.is_some());
310        let private_attributes = CFMutableDictionary::from_CFType_pairs(&[(
311            unsafe { kSecAttrIsPermanent }.to_void(),
312            is_permanent.to_void(),
313        )]);
314
315        let public_attributes = CFMutableDictionary::from_CFType_pairs(&[(
316            unsafe { kSecAttrIsPermanent }.to_void(),
317            is_permanent.to_void(),
318        )]);
319
320        let key_type = self.key_type.unwrap_or_else(KeyType::rsa).to_str();
321
322        let size_in_bits = self.size_in_bits.unwrap_or(match () {
323            _ if key_type == KeyType::rsa().to_str() => 2048,
324            _ if key_type == KeyType::ec().to_str() => 256,
325            _ => 256,
326        });
327        let size_in_bits = CFNumber::from(size_in_bits as i32);
328
329        let mut attribute_key_values = vec![
330            (unsafe { kSecAttrKeyType }.to_void(), key_type.to_void()),
331            (
332                unsafe { kSecAttrKeySizeInBits }.to_void(),
333                size_in_bits.to_void(),
334            ),
335            (
336                unsafe { kSecPrivateKeyAttrs }.to_void(),
337                private_attributes.to_void(),
338            ),
339            (
340                unsafe { kSecPublicKeyAttrs }.to_void(),
341                public_attributes.to_void(),
342            ),
343        ];
344        let label = self.label.as_deref().map(CFString::new);
345        if let Some(label) = &label {
346            attribute_key_values.push((unsafe { kSecAttrLabel }.to_void(), label.to_void()));
347        }
348
349        #[cfg(target_os = "macos")]
350        match &self.location {
351            #[cfg(feature = "OSX_10_15")]
352            Some(Location::DataProtectionKeychain) => {
353                use apple_security_sys::item::kSecUseDataProtectionKeychain;
354                attribute_key_values.push((
355                    unsafe { kSecUseDataProtectionKeychain }.to_void(),
356                    CFBoolean::true_value().to_void(),
357                ));
358            }
359            Some(Location::FileKeychain(keychain)) => {
360                attribute_key_values.push((
361                    unsafe { kSecUseKeychain }.to_void(),
362                    keychain.as_concrete_TypeRef().to_void(),
363                ));
364            }
365            _ => {}
366        }
367
368        match self.token.as_ref().unwrap_or(&Token::Software) {
369            Token::Software => {},
370            Token::SecureEnclave => {
371                attribute_key_values.push((
372                    unsafe { kSecAttrTokenID }.to_void(),
373                    unsafe { kSecAttrTokenIDSecureEnclave }.to_void(),
374                ));
375            }
376        }
377
378        CFMutableDictionary::from_CFType_pairs(&attribute_key_values).to_immutable()
379    }
380}
381
382impl fmt::Debug for SecKey {
383    #[cold]
384    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
385        fmt.debug_struct("SecKey").finish_non_exhaustive()
386    }
387}