apple_security_framework/
item.rs1use std::{collections::HashMap, fmt, ptr};
4
5use core_foundation::{
6 array::CFArray,
7 base::{CFType, TCFType, ToVoid},
8 boolean::CFBoolean,
9 data::CFData,
10 date::CFDate,
11 dictionary::{CFDictionary, CFMutableDictionary},
12 number::CFNumber,
13 string::CFString,
14};
15use core_foundation_sys::{
16 base::{CFCopyDescription, CFGetTypeID, CFRelease, CFTypeRef},
17 string::CFStringRef,
18};
19use security_framework_sys::{
20 item::*,
21 keychain_item::{SecItemAdd, SecItemCopyMatching},
22};
23
24#[cfg(target_os = "macos")]
25use crate::os::macos::keychain::SecKeychain;
26use crate::{base::Result, certificate::SecCertificate, cvt, identity::SecIdentity, key::SecKey};
27
28#[derive(Debug, Copy, Clone)]
30pub struct ItemClass(CFStringRef);
31
32impl ItemClass {
33 #[inline(always)]
35 #[must_use]
36 pub fn generic_password() -> Self {
37 unsafe { Self(kSecClassGenericPassword) }
38 }
39
40 #[inline(always)]
42 #[must_use]
43 pub fn internet_password() -> Self {
44 unsafe { Self(kSecClassInternetPassword) }
45 }
46
47 #[inline(always)]
49 #[must_use]
50 pub fn certificate() -> Self {
51 unsafe { Self(kSecClassCertificate) }
52 }
53
54 #[inline(always)]
56 #[must_use]
57 pub fn key() -> Self {
58 unsafe { Self(kSecClassKey) }
59 }
60
61 #[inline(always)]
63 #[must_use]
64 pub fn identity() -> Self {
65 unsafe { Self(kSecClassIdentity) }
66 }
67
68 #[inline]
69 fn to_value(self) -> CFType {
70 unsafe { CFType::wrap_under_get_rule(self.0.cast()) }
71 }
72}
73
74#[derive(Debug, Copy, Clone)]
76pub struct KeyClass(CFStringRef);
77
78impl KeyClass {
79 #[inline(always)]
81 #[must_use]
82 pub fn public() -> Self {
83 unsafe { Self(kSecAttrKeyClassPublic) }
84 }
85 #[inline(always)]
87 #[must_use]
88 pub fn private() -> Self {
89 unsafe { Self(kSecAttrKeyClassPrivate) }
90 }
91 #[inline(always)]
93 #[must_use]
94 pub fn symmetric() -> Self {
95 unsafe { Self(kSecAttrKeyClassSymmetric) }
96 }
97
98 #[inline]
99 fn to_value(self) -> CFType {
100 unsafe { CFType::wrap_under_get_rule(self.0.cast()) }
101 }
102}
103
104#[derive(Debug, Copy, Clone)]
106pub enum Limit {
107 All,
109
110 Max(i64),
112}
113
114impl Limit {
115 #[inline]
116 fn to_value(self) -> CFType {
117 match self {
118 Self::All => unsafe { CFString::wrap_under_get_rule(kSecMatchLimitAll).into_CFType() },
119 Self::Max(l) => CFNumber::from(l).into_CFType(),
120 }
121 }
122}
123
124impl From<i64> for Limit {
125 #[inline]
126 fn from(limit: i64) -> Self {
127 Self::Max(limit)
128 }
129}
130
131#[derive(Default)]
133pub struct ItemSearchOptions {
134 #[cfg(target_os = "macos")]
135 keychains: Option<CFArray<SecKeychain>>,
136 #[cfg(not(target_os = "macos"))]
137 keychains: Option<CFArray<CFType>>,
138 class: Option<ItemClass>,
139 key_class: Option<KeyClass>,
140 load_refs: bool,
141 load_attributes: bool,
142 load_data: bool,
143 limit: Option<Limit>,
144 label: Option<CFString>,
145 service: Option<CFString>,
146 account: Option<CFString>,
147 access_group: Option<CFString>,
148 pub_key_hash: Option<CFData>,
149 app_label: Option<CFData>,
150}
151
152#[cfg(target_os = "macos")]
153impl crate::ItemSearchOptionsInternals for ItemSearchOptions {
154 #[inline]
155 fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
156 self.keychains = Some(CFArray::from_CFTypes(keychains));
157 self
158 }
159}
160
161impl ItemSearchOptions {
162 #[inline(always)]
164 #[must_use]
165 pub fn new() -> Self {
166 Self::default()
167 }
168
169 #[inline(always)]
171 pub fn class(&mut self, class: ItemClass) -> &mut Self {
172 self.class = Some(class);
173 self
174 }
175
176 #[inline(always)]
179 pub fn key_class(&mut self, key_class: KeyClass) -> &mut Self {
180 self.class(ItemClass::key());
181 self.key_class = Some(key_class);
182 self
183 }
184
185 #[inline(always)]
188 pub fn load_refs(&mut self, load_refs: bool) -> &mut Self {
189 self.load_refs = load_refs;
190 self
191 }
192
193 #[inline(always)]
196 pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self {
197 self.load_attributes = load_attributes;
198 self
199 }
200
201 #[inline(always)]
204 pub fn load_data(&mut self, load_data: bool) -> &mut Self {
205 self.load_data = load_data;
206 self
207 }
208
209 #[inline(always)]
213 pub fn limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self {
214 self.limit = Some(limit.into());
215 self
216 }
217
218 #[inline(always)]
220 pub fn label(&mut self, label: &str) -> &mut Self {
221 self.label = Some(CFString::new(label));
222 self
223 }
224
225 #[inline(always)]
227 pub fn service(&mut self, service: &str) -> &mut Self {
228 self.service = Some(CFString::new(service));
229 self
230 }
231
232 #[inline(always)]
234 pub fn account(&mut self, account: &str) -> &mut Self {
235 self.account = Some(CFString::new(account));
236 self
237 }
238
239 #[inline(always)]
241 pub fn access_group_token(&mut self) -> &mut Self {
242 self.access_group =
243 unsafe { Some(CFString::wrap_under_get_rule(kSecAttrAccessGroupToken)) };
244 self
245 }
246
247 #[inline(always)]
253 pub fn pub_key_hash(&mut self, pub_key_hash: &[u8]) -> &mut Self {
254 self.pub_key_hash = Some(CFData::from_buffer(pub_key_hash));
255 self
256 }
257
258 #[inline(always)]
264 pub fn application_label(&mut self, app_label: &[u8]) -> &mut Self {
265 self.app_label = Some(CFData::from_buffer(app_label));
266 self
267 }
268
269 pub fn search(&self) -> Result<Vec<SearchResult>> {
271 unsafe {
272 let mut params = vec![];
273
274 if let Some(ref keychains) = self.keychains {
275 params.push((
276 CFString::wrap_under_get_rule(kSecMatchSearchList),
277 keychains.as_CFType(),
278 ));
279 }
280
281 if let Some(class) = self.class {
282 params.push((CFString::wrap_under_get_rule(kSecClass), class.to_value()));
283 }
284
285 if let Some(key_class) = self.key_class {
286 params.push((
287 CFString::wrap_under_get_rule(kSecAttrKeyClass),
288 key_class.to_value(),
289 ));
290 }
291
292 if self.load_refs {
293 params.push((
294 CFString::wrap_under_get_rule(kSecReturnRef),
295 CFBoolean::true_value().into_CFType(),
296 ));
297 }
298
299 if self.load_attributes {
300 params.push((
301 CFString::wrap_under_get_rule(kSecReturnAttributes),
302 CFBoolean::true_value().into_CFType(),
303 ));
304 }
305
306 if self.load_data {
307 params.push((
308 CFString::wrap_under_get_rule(kSecReturnData),
309 CFBoolean::true_value().into_CFType(),
310 ));
311 }
312
313 if let Some(limit) = self.limit {
314 params.push((
315 CFString::wrap_under_get_rule(kSecMatchLimit),
316 limit.to_value(),
317 ));
318 }
319
320 if let Some(ref label) = self.label {
321 params.push((
322 CFString::wrap_under_get_rule(kSecAttrLabel),
323 label.as_CFType(),
324 ));
325 }
326
327 if let Some(ref service) = self.service {
328 params.push((
329 CFString::wrap_under_get_rule(kSecAttrService),
330 service.as_CFType(),
331 ));
332 }
333
334 if let Some(ref account) = self.account {
335 params.push((
336 CFString::wrap_under_get_rule(kSecAttrAccount),
337 account.as_CFType(),
338 ));
339 }
340
341 if let Some(ref access_group) = self.access_group {
342 params.push((
343 CFString::wrap_under_get_rule(kSecAttrAccessGroup),
344 access_group.as_CFType(),
345 ));
346 }
347
348 if let Some(ref pub_key_hash) = self.pub_key_hash {
349 params.push((
350 CFString::wrap_under_get_rule(kSecAttrPublicKeyHash),
351 pub_key_hash.as_CFType(),
352 ));
353 }
354
355 if let Some(ref app_label) = self.app_label {
356 params.push((
357 CFString::wrap_under_get_rule(kSecAttrApplicationLabel),
358 app_label.as_CFType(),
359 ));
360 }
361
362 let params = CFDictionary::from_CFType_pairs(¶ms);
363
364 let mut ret = ptr::null();
365 cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
366 if ret.is_null() {
367 return Ok(vec![]);
370 }
371 let type_id = CFGetTypeID(ret);
372
373 let mut items = vec![];
374
375 if type_id == CFArray::<CFType>::type_id() {
376 let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _);
377 for item in array.iter() {
378 items.push(get_item(item.as_CFTypeRef()));
379 }
380 } else {
381 items.push(get_item(ret));
382 CFRelease(ret);
385 }
386
387 Ok(items)
388 }
389 }
390}
391
392unsafe fn get_item(item: CFTypeRef) -> SearchResult {
393 let type_id = CFGetTypeID(item);
394
395 if type_id == CFData::type_id() {
396 let data = CFData::wrap_under_get_rule(item as *mut _);
397 let mut buf = Vec::new();
398 buf.extend_from_slice(data.bytes());
399 return SearchResult::Data(buf);
400 }
401
402 if type_id == CFDictionary::<*const u8, *const u8>::type_id() {
403 return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _));
404 }
405
406 #[cfg(target_os = "macos")]
407 {
408 use crate::os::macos::keychain_item::SecKeychainItem;
409 if type_id == SecKeychainItem::type_id() {
410 return SearchResult::Ref(Reference::KeychainItem(
411 SecKeychainItem::wrap_under_get_rule(item as *mut _),
412 ));
413 }
414 }
415
416 let reference = if type_id == SecCertificate::type_id() {
417 Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
418 } else if type_id == SecKey::type_id() {
419 Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
420 } else if type_id == SecIdentity::type_id() {
421 Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
422 } else {
423 panic!("Got bad type from SecItemCopyMatching: {}", type_id);
424 };
425
426 SearchResult::Ref(reference)
427}
428
429#[derive(Debug)]
434#[non_exhaustive]
435pub enum Reference {
436 Identity(SecIdentity),
438
439 Certificate(SecCertificate),
441
442 Key(SecKey),
444
445 #[cfg(target_os = "macos")]
449 KeychainItem(crate::os::macos::keychain_item::SecKeychainItem),
450}
451
452pub enum SearchResult {
454 Ref(Reference),
456 Dict(CFDictionary),
458 Data(Vec<u8>),
460 Other,
462}
463
464impl fmt::Debug for SearchResult {
465 #[cold]
466 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
467 match *self {
468 Self::Ref(ref reference) => fmt
469 .debug_struct("SearchResult::Ref")
470 .field("reference", reference)
471 .finish(),
472 Self::Data(ref buf) => fmt
473 .debug_struct("SearchResult::Data")
474 .field("data", buf)
475 .finish(),
476 Self::Dict(_) => {
477 let mut debug = fmt.debug_struct("SearchResult::Dict");
478 for (k, v) in self.simplify_dict().unwrap() {
479 debug.field(&k, &v);
480 }
481 debug.finish()
482 }
483 Self::Other => write!(fmt, "SearchResult::Other"),
484 }
485 }
486}
487
488impl SearchResult {
489 #[must_use]
494 pub fn simplify_dict(&self) -> Option<HashMap<String, String>> {
495 match *self {
496 Self::Dict(ref d) => unsafe {
497 let mut retmap = HashMap::new();
498 let (keys, values) = d.get_keys_and_values();
499 for (k, v) in keys.iter().zip(values.iter()) {
500 let keycfstr = CFString::wrap_under_get_rule((*k).cast());
501 let val: String = match CFGetTypeID(*v) {
502 cfstring if cfstring == CFString::type_id() => {
503 format!("{}", CFString::wrap_under_get_rule((*v).cast()))
504 }
505 cfdata if cfdata == CFData::type_id() => {
506 let buf = CFData::wrap_under_get_rule((*v).cast());
507 let mut vec = Vec::new();
508 vec.extend_from_slice(buf.bytes());
509 format!("{}", String::from_utf8_lossy(&vec))
510 }
511 cfdate if cfdate == CFDate::type_id() => format!(
512 "{}",
513 CFString::wrap_under_create_rule(CFCopyDescription(*v))
514 ),
515 _ => String::from("unknown"),
516 };
517 retmap.insert(format!("{}", keycfstr), val);
518 }
519 Some(retmap)
520 },
521 _ => None,
522 }
523 }
524}
525
526pub struct ItemAddOptions {
532 pub value: ItemAddValue,
534 pub label: Option<String>,
536 pub location: Option<Location>,
538}
539
540impl ItemAddOptions {
541 #[must_use]
543 pub fn new(value: ItemAddValue) -> Self {
544 Self {
545 value,
546 label: None,
547 location: None,
548 }
549 }
550 pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self {
552 self.label = Some(label.into());
553 self
554 }
555 pub fn set_location(&mut self, location: Location) -> &mut Self {
557 self.location = Some(location);
558 self
559 }
560 pub fn to_dictionary(&self) -> CFDictionary {
562 let mut dict = CFMutableDictionary::from_CFType_pairs(&[]);
563
564 let class_opt = match &self.value {
565 ItemAddValue::Ref(ref_) => ref_.class(),
566 ItemAddValue::Data { class, .. } => Some(*class),
567 };
568 if let Some(class) = class_opt {
569 dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
570 }
571
572 let value_pair = match &self.value {
573 ItemAddValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()),
574 ItemAddValue::Data { data, .. } => (unsafe { kSecValueData }.to_void(), data.to_void()),
575 };
576 dict.add(&value_pair.0, &value_pair.1);
577
578 if let Some(location) = &self.location {
579 match location {
580 #[cfg(any(feature = "OSX_10_15", target_os = "ios"))]
581 Location::DataProtectionKeychain => {
582 dict.add(
583 &unsafe { kSecUseDataProtectionKeychain }.to_void(),
584 &CFBoolean::true_value().to_void(),
585 );
586 }
587 #[cfg(target_os = "macos")]
588 Location::DefaultFileKeychain => {}
589 #[cfg(target_os = "macos")]
590 Location::FileKeychain(keychain) => {
591 dict.add(&unsafe { kSecUseKeychain }.to_void(), &keychain.to_void());
592 }
593 }
594 }
595
596 let label = self.label.as_deref().map(CFString::from);
597 if let Some(label) = &label {
598 dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void());
599 }
600
601 dict.to_immutable()
602 }
603}
604
605pub enum ItemAddValue {
607 Ref(AddRef),
609 Data {
611 class: ItemClass,
613 data: CFData,
615 },
616}
617
618pub enum AddRef {
620 Key(SecKey),
622 Identity(SecIdentity),
624 Certificate(SecCertificate),
626}
627
628impl AddRef {
629 fn class(&self) -> Option<ItemClass> {
630 match self {
631 AddRef::Key(_) => Some(ItemClass::key()),
632 AddRef::Identity(_) => None,
635 AddRef::Certificate(_) => Some(ItemClass::certificate()),
636 }
637 }
638 fn ref_(&self) -> CFTypeRef {
639 match self {
640 AddRef::Key(key) => key.as_CFTypeRef(),
641 AddRef::Identity(id) => id.as_CFTypeRef(),
642 AddRef::Certificate(cert) => cert.as_CFTypeRef(),
643 }
644 }
645}
646
647pub enum Location {
651 #[cfg(any(feature = "OSX_10_15", target_os = "ios"))]
660 DataProtectionKeychain,
661 #[cfg(target_os = "macos")]
664 DefaultFileKeychain,
665 #[cfg(target_os = "macos")]
667 FileKeychain(crate::os::macos::keychain::SecKeychain),
668}
669
670pub fn add_item(add_params: CFDictionary) -> Result<()> {
673 cvt(unsafe { SecItemAdd(add_params.as_concrete_TypeRef(), std::ptr::null_mut()) })
674}
675
676#[cfg(test)]
677mod test {
678 use super::*;
679
680 #[test]
681 fn find_nothing() {
682 assert!(ItemSearchOptions::new().search().is_err());
683 }
684
685 #[test]
686 fn limit_two() {
687 let results = ItemSearchOptions::new()
688 .class(ItemClass::certificate())
689 .limit(2)
690 .search()
691 .unwrap();
692 assert_eq!(results.len(), 2);
693 }
694
695 #[test]
696 fn limit_all() {
697 let results = ItemSearchOptions::new()
698 .class(ItemClass::certificate())
699 .limit(Limit::All)
700 .search()
701 .unwrap();
702 assert!(results.len() >= 2);
703 }
704}