apple_security_framework/
key.rs

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