1use 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#[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 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 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 #[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 #[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 #[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 #[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 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 #[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 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
246pub enum Token {
248 Software,
250 SecureEnclave,
253}
254
255#[derive(Default)]
259#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
260pub struct GenerateKeyOptions {
261 pub key_type: Option<KeyType>,
263 pub size_in_bits: Option<u32>,
265 pub label: Option<String>,
267 pub token: Option<Token>,
269 pub location: Option<Location>,
271}
272
273#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
274impl GenerateKeyOptions {
275 pub fn set_key_type(&mut self, key_type: KeyType) -> &mut Self {
277 self.key_type = Some(key_type);
278 self
279 }
280 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 pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self {
287 self.label = Some(label.into());
288 self
289 }
290 pub fn set_token(&mut self, token: Token) -> &mut Self {
292 self.token = Some(token);
293 self
294 }
295 pub fn set_location(&mut self, location: Location) -> &mut Self {
297 self.location = Some(location);
298 self
299 }
300
301 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}