1use core_foundation::array::CFArray;
4use core_foundation::base::{CFType, TCFType, ToVoid};
5use core_foundation::boolean::CFBoolean;
6use core_foundation::data::CFData;
7use core_foundation::date::CFDate;
8use core_foundation::dictionary::{CFDictionary, CFMutableDictionary};
9use core_foundation::number::CFNumber;
10use core_foundation::string::CFString;
11use core_foundation_sys::base::{CFCopyDescription, CFGetTypeID, CFRelease, CFTypeRef};
12use core_foundation_sys::string::CFStringRef;
13use apple_security_sys::item::*;
14use apple_security_sys::keychain_item::{SecItemAdd, SecItemCopyMatching};
15use std::collections::HashMap;
16use std::fmt;
17use std::ptr;
18
19use crate::base::Result;
20use crate::certificate::SecCertificate;
21use crate::cvt;
22use crate::identity::SecIdentity;
23use crate::key::SecKey;
24#[cfg(target_os = "macos")]
25use crate::os::macos::keychain::SecKeychain;
26
27#[derive(Debug, Copy, Clone)]
29pub struct ItemClass(CFStringRef);
30
31impl ItemClass {
32 #[inline(always)]
34 #[must_use]
35 pub fn generic_password() -> Self {
36 unsafe { Self(kSecClassGenericPassword) }
37 }
38
39 #[inline(always)]
41 #[must_use]
42 pub fn internet_password() -> Self {
43 unsafe { Self(kSecClassInternetPassword) }
44 }
45
46 #[inline(always)]
48 #[must_use]
49 pub fn certificate() -> Self {
50 unsafe { Self(kSecClassCertificate) }
51 }
52
53 #[inline(always)]
55 #[must_use]
56 pub fn key() -> Self {
57 unsafe { Self(kSecClassKey) }
58 }
59
60 #[inline(always)]
62 #[must_use]
63 pub fn identity() -> Self {
64 unsafe { Self(kSecClassIdentity) }
65 }
66
67 #[inline]
68 fn to_value(self) -> CFType {
69 unsafe { CFType::wrap_under_get_rule(self.0.cast()) }
70 }
71}
72
73#[derive(Debug, Copy, Clone)]
75pub struct KeyClass(CFStringRef);
76
77impl KeyClass {
78 #[inline(always)]
80 #[must_use] pub fn public() -> Self {
81 unsafe { Self(kSecAttrKeyClassPublic) }
82 }
83 #[inline(always)]
85 #[must_use] pub fn private() -> Self {
86 unsafe { Self(kSecAttrKeyClassPrivate) }
87 }
88 #[inline(always)]
90 #[must_use] pub fn symmetric() -> Self {
91 unsafe { Self(kSecAttrKeyClassSymmetric) }
92 }
93
94 #[inline]
95 fn to_value(self) -> CFType {
96 unsafe { CFType::wrap_under_get_rule(self.0.cast()) }
97 }
98}
99
100#[derive(Debug, Copy, Clone)]
102pub enum Limit {
103 All,
105
106 Max(i64),
108}
109
110impl Limit {
111 #[inline]
112 fn to_value(self) -> CFType {
113 match self {
114 Self::All => unsafe { CFString::wrap_under_get_rule(kSecMatchLimitAll).into_CFType() },
115 Self::Max(l) => CFNumber::from(l).into_CFType(),
116 }
117 }
118}
119
120impl From<i64> for Limit {
121 #[inline]
122 fn from(limit: i64) -> Self {
123 Self::Max(limit)
124 }
125}
126
127#[derive(Default)]
129pub struct ItemSearchOptions {
130 #[cfg(target_os = "macos")]
131 keychains: Option<CFArray<SecKeychain>>,
132 #[cfg(not(target_os = "macos"))]
133 keychains: Option<CFArray<CFType>>,
134 class: Option<ItemClass>,
135 key_class: Option<KeyClass>,
136 load_refs: bool,
137 load_attributes: bool,
138 load_data: bool,
139 limit: Option<Limit>,
140 label: Option<CFString>,
141 service: Option<CFString>,
142 account: Option<CFString>,
143 access_group: Option<CFString>,
144 pub_key_hash: Option<CFData>,
145 app_label: Option<CFData>,
146}
147
148#[cfg(target_os = "macos")]
149impl crate::ItemSearchOptionsInternals for ItemSearchOptions {
150 #[inline]
151 fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
152 self.keychains = Some(CFArray::from_CFTypes(keychains));
153 self
154 }
155}
156
157impl ItemSearchOptions {
158 #[inline(always)]
160 #[must_use]
161 pub fn new() -> Self {
162 Self::default()
163 }
164
165 #[inline(always)]
167 pub fn class(&mut self, class: ItemClass) -> &mut Self {
168 self.class = Some(class);
169 self
170 }
171
172 #[inline(always)]
175 pub fn key_class(&mut self, key_class: KeyClass) -> &mut Self {
176 self.class(ItemClass::key());
177 self.key_class = Some(key_class);
178 self
179 }
180
181 #[inline(always)]
184 pub fn load_refs(&mut self, load_refs: bool) -> &mut Self {
185 self.load_refs = load_refs;
186 self
187 }
188
189 #[inline(always)]
192 pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self {
193 self.load_attributes = load_attributes;
194 self
195 }
196
197 #[inline(always)]
200 pub fn load_data(&mut self, load_data: bool) -> &mut Self {
201 self.load_data = load_data;
202 self
203 }
204
205 #[inline(always)]
209 pub fn limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self {
210 self.limit = Some(limit.into());
211 self
212 }
213
214 #[inline(always)]
216 pub fn label(&mut self, label: &str) -> &mut Self {
217 self.label = Some(CFString::new(label));
218 self
219 }
220
221 #[inline(always)]
223 pub fn service(&mut self, service: &str) -> &mut Self {
224 self.service = Some(CFString::new(service));
225 self
226 }
227
228 #[inline(always)]
230 pub fn account(&mut self, account: &str) -> &mut Self {
231 self.account = Some(CFString::new(account));
232 self
233 }
234
235 #[inline(always)]
237 pub fn access_group_token(&mut self) -> &mut Self {
238 self.access_group = unsafe { Some(CFString::wrap_under_get_rule(kSecAttrAccessGroupToken)) };
239 self
240 }
241
242 #[inline(always)]
248 pub fn pub_key_hash(&mut self, pub_key_hash: &[u8]) -> &mut Self {
249 self.pub_key_hash = Some(CFData::from_buffer(pub_key_hash));
250 self
251 }
252
253 #[inline(always)]
259 pub fn application_label(&mut self, app_label: &[u8]) -> &mut Self {
260 self.app_label = Some(CFData::from_buffer(app_label));
261 self
262 }
263
264 pub fn search(&self) -> Result<Vec<SearchResult>> {
266 unsafe {
267 let mut params = vec![];
268
269 if let Some(ref keychains) = self.keychains {
270 params.push((
271 CFString::wrap_under_get_rule(kSecMatchSearchList),
272 keychains.as_CFType(),
273 ));
274 }
275
276 if let Some(class) = self.class {
277 params.push((CFString::wrap_under_get_rule(kSecClass), class.to_value()));
278 }
279
280 if let Some(key_class) = self.key_class {
281 params.push((CFString::wrap_under_get_rule(kSecAttrKeyClass), key_class.to_value()));
282 }
283
284 if self.load_refs {
285 params.push((
286 CFString::wrap_under_get_rule(kSecReturnRef),
287 CFBoolean::true_value().into_CFType(),
288 ));
289 }
290
291 if self.load_attributes {
292 params.push((
293 CFString::wrap_under_get_rule(kSecReturnAttributes),
294 CFBoolean::true_value().into_CFType(),
295 ));
296 }
297
298 if self.load_data {
299 params.push((
300 CFString::wrap_under_get_rule(kSecReturnData),
301 CFBoolean::true_value().into_CFType(),
302 ));
303 }
304
305 if let Some(limit) = self.limit {
306 params.push((
307 CFString::wrap_under_get_rule(kSecMatchLimit),
308 limit.to_value(),
309 ));
310 }
311
312 if let Some(ref label) = self.label {
313 params.push((
314 CFString::wrap_under_get_rule(kSecAttrLabel),
315 label.as_CFType(),
316 ));
317 }
318
319 if let Some(ref service) = self.service {
320 params.push((
321 CFString::wrap_under_get_rule(kSecAttrService),
322 service.as_CFType(),
323 ));
324 }
325
326 if let Some(ref account) = self.account {
327 params.push((
328 CFString::wrap_under_get_rule(kSecAttrAccount),
329 account.as_CFType(),
330 ));
331 }
332
333 if let Some(ref access_group) = self.access_group {
334 params.push((
335 CFString::wrap_under_get_rule(kSecAttrAccessGroup),
336 access_group.as_CFType(),
337 ));
338 }
339
340 if let Some(ref pub_key_hash) = self.pub_key_hash {
341 params.push((
342 CFString::wrap_under_get_rule(kSecAttrPublicKeyHash),
343 pub_key_hash.as_CFType(),
344 ));
345 }
346
347 if let Some(ref app_label) = self.app_label {
348 params.push((
349 CFString::wrap_under_get_rule(kSecAttrApplicationLabel),
350 app_label.as_CFType(),
351 ));
352 }
353
354 let params = CFDictionary::from_CFType_pairs(¶ms);
355
356 let mut ret = ptr::null();
357 cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
358 if ret.is_null() {
359 return Ok(vec![]);
362 }
363 let type_id = CFGetTypeID(ret);
364
365 let mut items = vec![];
366
367 if type_id == CFArray::<CFType>::type_id() {
368 let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _);
369 for item in array.iter() {
370 items.push(get_item(item.as_CFTypeRef()));
371 }
372 } else {
373 items.push(get_item(ret));
374 CFRelease(ret);
377 }
378
379 Ok(items)
380 }
381 }
382}
383
384unsafe fn get_item(item: CFTypeRef) -> SearchResult {
385 let type_id = CFGetTypeID(item);
386
387 if type_id == CFData::type_id() {
388 let data = CFData::wrap_under_get_rule(item as *mut _);
389 let mut buf = Vec::new();
390 buf.extend_from_slice(data.bytes());
391 return SearchResult::Data(buf);
392 }
393
394 if type_id == CFDictionary::<*const u8, *const u8>::type_id() {
395 return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _));
396 }
397
398 #[cfg(target_os = "macos")]
399 {
400 use crate::os::macos::keychain_item::SecKeychainItem;
401 if type_id == SecKeychainItem::type_id() {
402 return SearchResult::Ref(Reference::KeychainItem(
403 SecKeychainItem::wrap_under_get_rule(item as *mut _),
404 ));
405 }
406 }
407
408 let reference = if type_id == SecCertificate::type_id() {
409 Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
410 } else if type_id == SecKey::type_id() {
411 Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
412 } else if type_id == SecIdentity::type_id() {
413 Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
414 } else {
415 panic!("Got bad type from SecItemCopyMatching: {}", type_id);
416 };
417
418 SearchResult::Ref(reference)
419}
420
421#[derive(Debug)]
426pub enum Reference {
427 Identity(SecIdentity),
429 Certificate(SecCertificate),
431 Key(SecKey),
433 #[cfg(target_os = "macos")]
437 KeychainItem(crate::os::macos::keychain_item::SecKeychainItem),
438 #[doc(hidden)]
439 __NonExhaustive,
440}
441
442pub enum SearchResult {
444 Ref(Reference),
446 Dict(CFDictionary),
448 Data(Vec<u8>),
450 Other,
452}
453
454impl fmt::Debug for SearchResult {
455 #[cold]
456 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
457 match *self {
458 Self::Ref(ref reference) => fmt
459 .debug_struct("SearchResult::Ref")
460 .field("reference", reference)
461 .finish(),
462 Self::Data(ref buf) => fmt
463 .debug_struct("SearchResult::Data")
464 .field("data", buf)
465 .finish(),
466 Self::Dict(_) => {
467 let mut debug = fmt.debug_struct("SearchResult::Dict");
468 for (k, v) in self.simplify_dict().unwrap() {
469 debug.field(&k, &v);
470 }
471 debug.finish()
472 }
473 Self::Other => write!(fmt, "SearchResult::Other"),
474 }
475 }
476}
477
478impl SearchResult {
479 #[must_use]
484 pub fn simplify_dict(&self) -> Option<HashMap<String, String>> {
485 match *self {
486 Self::Dict(ref d) => unsafe {
487 let mut retmap = HashMap::new();
488 let (keys, values) = d.get_keys_and_values();
489 for (k, v) in keys.iter().zip(values.iter()) {
490 let keycfstr = CFString::wrap_under_get_rule((*k).cast());
491 let val: String = match CFGetTypeID(*v) {
492 cfstring if cfstring == CFString::type_id() => {
493 format!("{}", CFString::wrap_under_get_rule((*v).cast()))
494 }
495 cfdata if cfdata == CFData::type_id() => {
496 let buf = CFData::wrap_under_get_rule((*v).cast());
497 let mut vec = Vec::new();
498 vec.extend_from_slice(buf.bytes());
499 format!("{}", String::from_utf8_lossy(&vec))
500 }
501 cfdate if cfdate == CFDate::type_id() => format!(
502 "{}",
503 CFString::wrap_under_create_rule(CFCopyDescription(*v))
504 ),
505 _ => String::from("unknown"),
506 };
507 retmap.insert(format!("{}", keycfstr), val);
508 }
509 Some(retmap)
510 },
511 _ => None,
512 }
513 }
514}
515
516pub struct ItemAddOptions {
522 pub value: ItemAddValue,
524 pub label: Option<String>,
526 pub location: Option<Location>,
528}
529
530impl ItemAddOptions {
531 #[must_use] pub fn new(value: ItemAddValue) -> Self {
533 Self{ value, label: None, location: None }
534 }
535 pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self {
537 self.label = Some(label.into());
538 self
539 }
540 pub fn set_location(&mut self, location: Location) -> &mut Self {
542 self.location = Some(location);
543 self
544 }
545 pub fn to_dictionary(&self) -> CFDictionary {
547 let mut dict = CFMutableDictionary::from_CFType_pairs(&[]);
548
549 let class_opt = match &self.value {
550 ItemAddValue::Ref(ref_) => ref_.class(),
551 ItemAddValue::Data { class, .. } => Some(*class),
552 };
553 if let Some(class) = class_opt {
554 dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
555 }
556
557 let value_pair = match &self.value {
558 ItemAddValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()),
559 ItemAddValue::Data { data, .. } => (unsafe { kSecValueData }.to_void(), data.to_void()),
560 };
561 dict.add(&value_pair.0, &value_pair.1);
562
563 if let Some(location) = &self.location {
564 match location {
565 #[cfg(any(feature = "OSX_10_15", target_os = "ios"))]
566 Location::DataProtectionKeychain => {
567 dict.add(
568 &unsafe { kSecUseDataProtectionKeychain }.to_void(),
569 &CFBoolean::true_value().to_void(),
570 );
571 }
572 #[cfg(target_os = "macos")]
573 Location::DefaultFileKeychain => {}
574 #[cfg(target_os = "macos")]
575 Location::FileKeychain(keychain) => {
576 dict.add(&unsafe { kSecUseKeychain }.to_void(), &keychain.to_void());
577 },
578 }
579 }
580
581 let label = self.label.as_deref().map(CFString::from);
582 if let Some(label) = &label {
583 dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void());
584 }
585
586 dict.to_immutable()
587 }
588}
589
590pub enum ItemAddValue {
592 Ref(AddRef),
594 Data {
596 class: ItemClass,
598 data: CFData,
600 },
601}
602
603pub enum AddRef {
605 Key(SecKey),
607 Identity(SecIdentity),
609 Certificate(SecCertificate),
611}
612
613impl AddRef {
614 fn class(&self) -> Option<ItemClass> {
615 match self {
616 AddRef::Key(_) => Some(ItemClass::key()),
617 AddRef::Identity(_) => None,
620 AddRef::Certificate(_) => Some(ItemClass::certificate()),
621 }
622 }
623 fn ref_(&self) -> CFTypeRef {
624 match self {
625 AddRef::Key(key) => key.as_CFTypeRef(),
626 AddRef::Identity(id) => id.as_CFTypeRef(),
627 AddRef::Certificate(cert) => cert.as_CFTypeRef(),
628 }
629 }
630}
631
632pub enum Location {
636 #[cfg(any(feature = "OSX_10_15", target_os = "ios"))]
645 DataProtectionKeychain,
646 #[cfg(target_os = "macos")]
649 DefaultFileKeychain,
650 #[cfg(target_os = "macos")]
652 FileKeychain(crate::os::macos::keychain::SecKeychain),
653}
654
655pub fn add_item(add_params: CFDictionary) -> Result<()> {
658 cvt(unsafe { SecItemAdd(add_params.as_concrete_TypeRef(), std::ptr::null_mut()) })
659}
660
661#[cfg(test)]
662mod test {
663 use super::*;
664
665 #[test]
666 fn find_nothing() {
667 assert!(ItemSearchOptions::new().search().is_err());
668 }
669
670 #[test]
671 fn limit_two() {
672 let results = ItemSearchOptions::new()
673 .class(ItemClass::certificate())
674 .limit(2)
675 .search()
676 .unwrap();
677 assert_eq!(results.len(), 2);
678 }
679
680 #[test]
681 fn limit_all() {
682 let results = ItemSearchOptions::new()
683 .class(ItemClass::certificate())
684 .limit(Limit::All)
685 .search()
686 .unwrap();
687 assert!(results.len() >= 2);
688 }
689}