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 security_framework_sys::item::*;
14use security_framework_sys::keychain_item::{
15 SecItemAdd, SecItemCopyMatching, SecItemDelete, SecItemUpdate,
16};
17use std::collections::HashMap;
18use std::fmt;
19use std::ptr;
20
21use crate::base::Result;
22use crate::certificate::SecCertificate;
23use crate::cvt;
24use crate::identity::SecIdentity;
25use crate::key::SecKey;
26#[cfg(target_os = "macos")]
27use crate::os::macos::keychain::SecKeychain;
28
29#[derive(Debug, Copy, Clone)]
31pub struct ItemClass(CFStringRef);
32
33impl ItemClass {
34 #[inline(always)]
36 #[must_use]
37 pub fn generic_password() -> Self {
38 unsafe { Self(kSecClassGenericPassword) }
39 }
40
41 #[inline(always)]
43 #[must_use]
44 pub fn internet_password() -> Self {
45 unsafe { Self(kSecClassInternetPassword) }
46 }
47
48 #[inline(always)]
50 #[must_use]
51 pub fn certificate() -> Self {
52 unsafe { Self(kSecClassCertificate) }
53 }
54
55 #[inline(always)]
57 #[must_use]
58 pub fn key() -> Self {
59 unsafe { Self(kSecClassKey) }
60 }
61
62 #[inline(always)]
64 #[must_use]
65 pub fn identity() -> Self {
66 unsafe { Self(kSecClassIdentity) }
67 }
68}
69
70#[derive(Debug, Copy, Clone)]
72pub struct KeyClass(CFStringRef);
73
74impl KeyClass {
75 #[inline(always)]
77 #[must_use]
78 pub fn public() -> Self {
79 unsafe { Self(kSecAttrKeyClassPublic) }
80 }
81
82 #[inline(always)]
84 #[must_use]
85 pub fn private() -> Self {
86 unsafe { Self(kSecAttrKeyClassPrivate) }
87 }
88
89 #[inline(always)]
91 #[must_use]
92 pub fn symmetric() -> Self {
93 unsafe { Self(kSecAttrKeyClassSymmetric) }
94 }
95}
96
97#[derive(Debug, Copy, Clone)]
99pub enum Limit {
100 All,
102
103 Max(i64),
105}
106
107impl Limit {
108 #[inline]
109 fn to_value(self) -> CFType {
110 match self {
111 Self::All => unsafe { CFString::wrap_under_get_rule(kSecMatchLimitAll).into_CFType() },
112 Self::Max(l) => CFNumber::from(l).into_CFType(),
113 }
114 }
115}
116
117impl From<i64> for Limit {
118 #[inline]
119 fn from(limit: i64) -> Self {
120 Self::Max(limit)
121 }
122}
123
124#[derive(Debug, Copy, Clone)]
126pub enum CloudSync {
127 MatchSyncYes,
129 MatchSyncNo,
131 MatchSyncAny,
133}
134
135impl From<Option<bool>> for CloudSync {
136 #[inline]
137 fn from(is_sync: Option<bool>) -> Self {
138 match is_sync {
139 Some(true) => Self::MatchSyncYes,
140 Some(false) => Self::MatchSyncNo,
141 None => Self::MatchSyncAny,
142 }
143 }
144}
145
146#[derive(Default)]
148pub struct ItemSearchOptions {
149 #[cfg(target_os = "macos")]
150 keychains: Option<CFArray<SecKeychain>>,
151 #[cfg(not(target_os = "macos"))]
152 keychains: Option<CFArray<CFType>>,
153 ignore_legacy_keychains: bool, case_insensitive: Option<bool>,
155 class: Option<ItemClass>,
156 key_class: Option<KeyClass>,
157 load_refs: bool,
158 load_attributes: bool,
159 load_data: bool,
160 limit: Option<Limit>,
161 trusted_only: Option<bool>,
162 label: Option<CFString>,
163 service: Option<CFString>,
164 subject: Option<CFString>,
165 account: Option<CFString>,
166 access_group: Option<CFString>,
167 cloud_sync: Option<CloudSync>,
168 pub_key_hash: Option<CFData>,
169 serial_number: Option<CFData>,
170 app_label: Option<CFData>,
171 #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
172 authentication_context: Option<CFType>,
173 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
174 skip_authenticated_items: bool,
175}
176
177#[cfg(target_os = "macos")]
178impl ItemSearchOptions {
179 #[inline]
183 pub fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
184 self.keychains = Some(CFArray::from_CFTypes(keychains));
185 self
186 }
187
188 #[inline]
195 pub fn ignore_legacy_keychains(&mut self) -> &mut Self {
196 self.ignore_legacy_keychains = true;
197 self
198 }
199}
200
201impl ItemSearchOptions {
202 #[inline(always)]
204 #[must_use]
205 pub fn new() -> Self {
206 Self::default()
207 }
208
209 #[inline(always)]
211 pub fn class(&mut self, class: ItemClass) -> &mut Self {
212 self.class = Some(class);
213 self
214 }
215
216 #[inline(always)]
218 pub fn case_insensitive(&mut self, case_insensitive: Option<bool>) -> &mut Self {
219 self.case_insensitive = case_insensitive;
220 self
221 }
222
223 #[inline(always)]
226 pub fn key_class(&mut self, key_class: KeyClass) -> &mut Self {
227 self.class(ItemClass::key());
228 self.key_class = Some(key_class);
229 self
230 }
231
232 #[inline(always)]
235 pub fn load_refs(&mut self, load_refs: bool) -> &mut Self {
236 self.load_refs = load_refs;
237 self
238 }
239
240 #[inline(always)]
243 pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self {
244 self.load_attributes = load_attributes;
245 self
246 }
247
248 #[inline(always)]
251 pub fn load_data(&mut self, load_data: bool) -> &mut Self {
252 self.load_data = load_data;
253 self
254 }
255
256 #[inline(always)]
260 pub fn limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self {
261 self.limit = Some(limit.into());
262 self
263 }
264
265 #[inline(always)]
267 pub fn label(&mut self, label: &str) -> &mut Self {
268 self.label = Some(CFString::new(label));
269 self
270 }
271
272 #[inline(always)]
274 pub fn trusted_only(&mut self, trusted_only: Option<bool>) -> &mut Self {
275 self.trusted_only = trusted_only;
276 self
277 }
278
279 #[inline(always)]
281 pub fn service(&mut self, service: &str) -> &mut Self {
282 self.service = Some(CFString::new(service));
283 self
284 }
285
286 #[inline(always)]
288 pub fn subject(&mut self, subject: &str) -> &mut Self {
289 self.subject = Some(CFString::new(subject));
290 self
291 }
292
293 #[inline(always)]
295 pub fn account(&mut self, account: &str) -> &mut Self {
296 self.account = Some(CFString::new(account));
297 self
298 }
299
300 pub fn access_group(&mut self, access_group: &str) -> &mut Self {
302 self.access_group = Some(CFString::new(access_group));
303 self
304 }
305
306 pub fn cloud_sync<T: Into<CloudSync>>(&mut self, spec: T) -> &mut Self {
310 self.cloud_sync = Some(spec.into());
311 self
312 }
313
314 #[inline(always)]
316 pub fn access_group_token(&mut self) -> &mut Self {
317 self.access_group = unsafe { Some(CFString::wrap_under_get_rule(kSecAttrAccessGroupToken)) };
318 self
319 }
320
321 #[inline(always)]
327 pub fn pub_key_hash(&mut self, pub_key_hash: &[u8]) -> &mut Self {
328 self.pub_key_hash = Some(CFData::from_buffer(pub_key_hash));
329 self
330 }
331
332 #[inline(always)]
336 pub fn serial_number(&mut self, serial_number: &[u8]) -> &mut Self {
337 self.serial_number = Some(CFData::from_buffer(serial_number));
338 self
339 }
340
341 #[inline(always)]
347 pub fn application_label(&mut self, app_label: &[u8]) -> &mut Self {
348 self.app_label = Some(CFData::from_buffer(app_label));
349 self
350 }
351
352 #[doc(hidden)]
353 #[deprecated(note = "use local_authentication_context")]
354 #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
355 pub unsafe fn authentication_context(&mut self, authentication_context: *mut std::os::raw::c_void) -> &mut Self {
356 self.authentication_context = unsafe { Some(CFType::wrap_under_create_rule(authentication_context)) };
357 self
358 }
359
360 #[inline(always)]
363 #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
364 pub fn local_authentication_context<LAContext: TCFType>(&mut self, authentication_context: Option<LAContext>) -> &mut Self {
365 self.authentication_context = authentication_context.map(|la| la.into_CFType());
366 self
367 }
368
369 #[inline(always)]
371 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
372 pub fn skip_authenticated_items(&mut self, do_skip: bool) -> &mut Self {
373 self.skip_authenticated_items = do_skip;
374 self
375 }
376
377 #[inline]
380 fn to_dictionary(&self) -> CFDictionary {
381 unsafe {
382 let mut params = CFMutableDictionary::from_CFType_pairs(&[]);
383
384 if let Some(ref keychains) = self.keychains {
385 params.add(
386 &kSecMatchSearchList.to_void(),
387 &keychains.as_CFType().to_void(),
388 );
389 } else if self.ignore_legacy_keychains {
390 #[cfg(all(target_os = "macos", feature = "OSX_10_15"))]
391 params.add(
392 &kSecUseDataProtectionKeychain.to_void(),
393 &CFBoolean::true_value().to_void(),
394 )
395 }
396
397 if let Some(class) = self.class {
398 params.add(&kSecClass.to_void(), &class.0.to_void());
399 }
400
401 if let Some(case_insensitive) = self.case_insensitive {
402 params.add(
403 &kSecMatchCaseInsensitive.to_void(),
404 &CFBoolean::from(case_insensitive).to_void(),
405 );
406 }
407
408 if let Some(key_class) = self.key_class {
409 params.add(&kSecAttrKeyClass.to_void(), &key_class.0.to_void());
410 }
411
412 if self.load_refs {
413 params.add(&kSecReturnRef.to_void(), &CFBoolean::true_value().to_void());
414 }
415
416 if self.load_attributes {
417 params.add(
418 &kSecReturnAttributes.to_void(),
419 &CFBoolean::true_value().to_void(),
420 );
421 }
422
423 if self.load_data {
424 params.add(
425 &kSecReturnData.to_void(),
426 &CFBoolean::true_value().to_void(),
427 );
428 }
429
430 if let Some(limit) = self.limit {
431 params.add(&kSecMatchLimit.to_void(), &limit.to_value().to_void());
432 }
433
434 if let Some(ref label) = self.label {
435 params.add(&kSecAttrLabel.to_void(), &label.to_void());
436 }
437
438 if let Some(ref trusted_only) = self.trusted_only {
439 params.add(
440 &kSecMatchTrustedOnly.to_void(),
441 &CFBoolean::from(*trusted_only).to_void(),
442 );
443 }
444
445 if let Some(ref service) = self.service {
446 params.add(&kSecAttrService.to_void(), &service.to_void());
447 }
448
449 #[cfg(target_os = "macos")]
450 {
451 if let Some(ref subject) = self.subject {
452 params.add(&kSecMatchSubjectWholeString.to_void(), &subject.to_void());
453 }
454 }
455
456 if let Some(ref account) = self.account {
457 params.add(&kSecAttrAccount.to_void(), &account.to_void());
458 }
459
460 if let Some(ref access_group) = self.access_group {
461 params.add(&kSecAttrAccessGroup.to_void(), &access_group.to_void());
462 }
463
464 if let Some(ref cloud_sync) = self.cloud_sync {
465 match cloud_sync {
466 CloudSync::MatchSyncYes => {
467 params.add(&kSecAttrSynchronizable.to_void(), &CFBoolean::true_value().to_void());
468 },
469 CloudSync::MatchSyncNo => {
470 params.add(&kSecAttrSynchronizable.to_void(), &CFBoolean::false_value().to_void());
471 },
472 CloudSync::MatchSyncAny => {
473 params.add(&kSecAttrSynchronizable.to_void(), &kSecAttrSynchronizableAny.to_void());
474 },
475 }
476 }
477
478 if let Some(ref pub_key_hash) = self.pub_key_hash {
479 params.add(&kSecAttrPublicKeyHash.to_void(), &pub_key_hash.to_void());
480 }
481
482 if let Some(ref serial_number) = self.serial_number {
483 params.add(&kSecAttrSerialNumber.to_void(), &serial_number.to_void());
484 }
485
486 if let Some(ref app_label) = self.app_label {
487 params.add(&kSecAttrApplicationLabel.to_void(), &app_label.to_void());
488 }
489
490 #[cfg(any(feature = "OSX_10_13", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
491 if let Some(ref authentication_context) = self.authentication_context {
492 params.add(&kSecUseAuthenticationContext.to_void(), &authentication_context.to_void());
493 }
494
495 #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
496 if self.skip_authenticated_items {
497 params.add(&kSecUseAuthenticationUI.to_void(), &kSecUseAuthenticationUISkip.to_void());
498 }
499
500 params.to_immutable()
501 }
502 }
503
504 #[inline]
506 pub fn search(&self) -> Result<Vec<SearchResult>> {
507 unsafe {
508 let params = self.to_dictionary();
509
510 let mut ret = ptr::null();
511 cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
512 if ret.is_null() {
513 return Ok(vec![]);
516 }
517 let type_id = CFGetTypeID(ret);
518
519 let mut items = vec![];
520
521 if type_id == CFArray::<CFType>::type_id() {
522 let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _);
523 for item in array.iter() {
524 items.push(get_item(item.as_CFTypeRef()));
525 }
526 } else {
527 items.push(get_item(ret));
528 CFRelease(ret);
531 }
532
533 Ok(items)
534 }
535 }
536
537 #[inline]
541 pub fn delete(&self) -> Result<()> {
542 cvt(unsafe { SecItemDelete(self.to_dictionary().as_concrete_TypeRef()) })
543 }
544}
545
546unsafe fn get_item(item: CFTypeRef) -> SearchResult {
547 let type_id = CFGetTypeID(item);
548
549 if type_id == CFData::type_id() {
550 let data = CFData::wrap_under_get_rule(item as *mut _);
551 let mut buf = Vec::new();
552 buf.extend_from_slice(data.bytes());
553 return SearchResult::Data(buf);
554 }
555
556 if type_id == CFDictionary::<*const u8, *const u8>::type_id() {
557 return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _));
558 }
559
560 #[cfg(target_os = "macos")]
561 {
562 use crate::os::macos::keychain_item::SecKeychainItem;
563 if type_id == SecKeychainItem::type_id() {
564 return SearchResult::Ref(Reference::KeychainItem(
565 SecKeychainItem::wrap_under_get_rule(item as *mut _),
566 ));
567 }
568 }
569
570 let reference = if type_id == SecCertificate::type_id() {
571 Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
572 } else if type_id == SecKey::type_id() {
573 Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
574 } else if type_id == SecIdentity::type_id() {
575 Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
576 } else {
577 panic!("Got bad type from SecItemCopyMatching: {type_id}");
578 };
579
580 SearchResult::Ref(reference)
581}
582
583#[derive(Debug)]
589pub enum Reference {
590 Identity(SecIdentity),
592 Certificate(SecCertificate),
594 Key(SecKey),
596 #[cfg(target_os = "macos")]
600 KeychainItem(crate::os::macos::keychain_item::SecKeychainItem),
601 #[doc(hidden)]
602 __NonExhaustive,
603}
604
605pub enum SearchResult {
607 Ref(Reference),
609 Dict(CFDictionary),
611 Data(Vec<u8>),
613 Other,
615}
616
617impl fmt::Debug for SearchResult {
618 #[cold]
619 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
620 match *self {
621 Self::Ref(ref reference) => fmt
622 .debug_struct("SearchResult::Ref")
623 .field("reference", reference)
624 .finish(),
625 Self::Data(ref buf) => fmt
626 .debug_struct("SearchResult::Data")
627 .field("data", buf)
628 .finish(),
629 Self::Dict(_) => {
630 let mut debug = fmt.debug_struct("SearchResult::Dict");
631 for (k, v) in self.simplify_dict().unwrap() {
632 debug.field(&k, &v);
633 }
634 debug.finish()
635 },
636 Self::Other => write!(fmt, "SearchResult::Other"),
637 }
638 }
639}
640
641impl SearchResult {
642 #[must_use]
647 pub fn simplify_dict(&self) -> Option<HashMap<String, String>> {
648 match *self {
649 Self::Dict(ref d) => unsafe {
650 let mut retmap = HashMap::new();
651 let (keys, values) = d.get_keys_and_values();
652 for (k, v) in keys.iter().zip(values.iter()) {
653 let keycfstr = CFString::wrap_under_get_rule((*k).cast());
654 let val: String = match CFGetTypeID(*v) {
655 cfstring if cfstring == CFString::type_id() => {
656 format!("{}", CFString::wrap_under_get_rule((*v).cast()))
657 },
658 cfdata if cfdata == CFData::type_id() => {
659 let buf = CFData::wrap_under_get_rule((*v).cast());
660 let mut vec = Vec::new();
661 vec.extend_from_slice(buf.bytes());
662 format!("{}", String::from_utf8_lossy(&vec))
663 },
664 cfdate if cfdate == CFDate::type_id() => {
665 format!("{}", CFString::wrap_under_create_rule(CFCopyDescription(*v)))
666 },
667 _ => String::from("unknown"),
668 };
669 retmap.insert(format!("{keycfstr}"), val);
670 }
671 Some(retmap)
672 },
673 _ => None,
674 }
675 }
676}
677
678pub struct ItemAddOptions {
683 pub value: ItemAddValue,
685 pub account_name: Option<CFString>,
687 pub access_group: Option<CFString>,
689 pub comment: Option<CFString>,
691 pub description: Option<CFString>,
693 pub label: Option<CFString>,
695 pub service: Option<CFString>,
697 pub location: Option<Location>,
699}
700
701impl ItemAddOptions {
702 #[inline]
704 #[must_use]
705 pub fn new(value: ItemAddValue) -> Self {
706 Self {
707 value,
708 label: None,
709 location: None,
710 service: None,
711 account_name: None,
712 comment: None,
713 description: None,
714 access_group: None,
715 }
716 }
717
718 #[inline]
720 pub fn set_account_name(&mut self, account_name: impl AsRef<str>) -> &mut Self {
721 self.account_name = Some(account_name.as_ref().into());
722 self
723 }
724
725 #[inline]
727 pub fn set_access_group(&mut self, access_group: impl AsRef<str>) -> &mut Self {
728 self.access_group = Some(access_group.as_ref().into());
729 self
730 }
731
732 #[inline]
734 pub fn set_comment(&mut self, comment: impl AsRef<str>) -> &mut Self {
735 self.comment = Some(comment.as_ref().into());
736 self
737 }
738
739 #[inline]
741 pub fn set_description(&mut self, description: impl AsRef<str>) -> &mut Self {
742 self.description = Some(description.as_ref().into());
743 self
744 }
745
746 #[inline]
748 pub fn set_label(&mut self, label: impl AsRef<str>) -> &mut Self {
749 self.label = Some(label.as_ref().into());
750 self
751 }
752
753 #[inline]
755 pub fn set_location(&mut self, location: Location) -> &mut Self {
756 self.location = Some(location);
757 self
758 }
759
760 #[inline]
762 pub fn set_service(&mut self, service: impl AsRef<str>) -> &mut Self {
763 self.service = Some(service.as_ref().into());
764 self
765 }
766
767 #[deprecated(since = "3.0.0", note = "use `ItemAddOptions::add` instead")]
769 #[must_use]
771 pub fn to_dictionary(&self) -> CFDictionary {
772 let mut dict = CFMutableDictionary::from_CFType_pairs(&[]);
773
774 let class_opt = match &self.value {
775 ItemAddValue::Ref(ref_) => ref_.class(),
776 ItemAddValue::Data { class, .. } => Some(*class),
777 };
778 if let Some(class) = class_opt {
779 dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
780 }
781
782 let value_pair = match &self.value {
783 ItemAddValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()),
784 ItemAddValue::Data { data, .. } => (unsafe { kSecValueData }.to_void(), data.to_void()),
785 };
786 dict.add(&value_pair.0, &value_pair.1);
787
788 if let Some(location) = &self.location {
789 match location {
790 #[cfg(any(feature = "OSX_10_15", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
791 Location::DataProtectionKeychain => {
792 dict.add(
793 &unsafe { kSecUseDataProtectionKeychain }.to_void(),
794 &CFBoolean::true_value().to_void(),
795 );
796 }
797 #[cfg(target_os = "macos")]
798 Location::DefaultFileKeychain => {},
799 #[cfg(target_os = "macos")]
800 Location::FileKeychain(keychain) => {
801 dict.add(&unsafe { kSecUseKeychain }.to_void(), &keychain.to_void());
802 },
803 }
804 }
805 if let Some(account_name) = &self.account_name {
806 dict.add(&unsafe { kSecAttrAccount }.to_void(), &account_name.to_void());
807 }
808 if let Some(access_group) = &self.access_group {
809 dict.add(&unsafe { kSecAttrAccessGroup }.to_void(), &access_group.to_void());
810 }
811 if let Some(comment) = &self.comment {
812 dict.add(&unsafe { kSecAttrComment }.to_void(), &comment.to_void());
813 }
814 if let Some(description) = &self.description {
815 dict.add(&unsafe { kSecAttrDescription }.to_void(), &description.to_void());
816 }
817 if let Some(label) = &self.label {
818 dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void());
819 }
820 if let Some(service) = &self.service {
821 dict.add(&unsafe { kSecAttrService }.to_void(), &service.to_void());
822 }
823
824 dict.to_immutable()
825 }
826
827 #[inline]
831 pub fn add(&self) -> Result<()> {
832 #[allow(deprecated)]
833 cvt(unsafe { SecItemAdd(self.to_dictionary().as_concrete_TypeRef(), std::ptr::null_mut()) })
834 }
835}
836
837pub enum ItemAddValue {
839 Ref(AddRef),
841 Data {
843 class: ItemClass,
845 data: CFData,
847 },
848}
849
850pub enum AddRef {
852 Key(SecKey),
854 Identity(SecIdentity),
856 Certificate(SecCertificate),
858}
859
860impl AddRef {
861 fn class(&self) -> Option<ItemClass> {
862 match self {
863 Self::Key(_) => Some(ItemClass::key()),
864 Self::Identity(_) => None,
867 Self::Certificate(_) => Some(ItemClass::certificate()),
868 }
869 }
870
871 fn ref_(&self) -> CFTypeRef {
872 match self {
873 Self::Key(key) => key.as_CFTypeRef(),
874 Self::Identity(id) => id.as_CFTypeRef(),
875 Self::Certificate(cert) => cert.as_CFTypeRef(),
876 }
877 }
878}
879
880#[derive(Default)]
885pub struct ItemUpdateOptions {
886 pub value: Option<ItemUpdateValue>,
888 pub account_name: Option<CFString>,
890 pub access_group: Option<CFString>,
892 pub comment: Option<CFString>,
894 pub description: Option<CFString>,
896 pub label: Option<CFString>,
898 pub service: Option<CFString>,
900 pub location: Option<Location>,
902 pub class: Option<ItemClass>,
906}
907
908impl ItemUpdateOptions {
909 #[must_use]
911 pub fn new() -> Self {
912 Default::default()
913 }
914
915 #[inline]
917 pub fn set_value(&mut self, value: ItemUpdateValue) -> &mut Self {
918 self.value = Some(value);
919 self
920 }
921
922 #[inline]
924 pub fn set_class(&mut self, class: ItemClass) -> &mut Self {
925 self.class = Some(class);
926 self
927 }
928
929 #[inline]
931 pub fn set_account_name(&mut self, account_name: impl AsRef<str>) -> &mut Self {
932 self.account_name = Some(account_name.as_ref().into());
933 self
934 }
935
936 #[inline]
938 pub fn set_access_group(&mut self, access_group: impl AsRef<str>) -> &mut Self {
939 self.access_group = Some(access_group.as_ref().into());
940 self
941 }
942
943 #[inline]
945 pub fn set_comment(&mut self, comment: impl AsRef<str>) -> &mut Self {
946 self.comment = Some(comment.as_ref().into());
947 self
948 }
949
950 #[inline]
952 pub fn set_description(&mut self, description: impl AsRef<str>) -> &mut Self {
953 self.description = Some(description.as_ref().into());
954 self
955 }
956
957 #[inline]
959 pub fn set_label(&mut self, label: impl AsRef<str>) -> &mut Self {
960 self.label = Some(label.as_ref().into());
961 self
962 }
963
964 #[inline]
966 pub fn set_location(&mut self, location: Location) -> &mut Self {
967 self.location = Some(location);
968 self
969 }
970
971 #[inline]
973 pub fn set_service(&mut self, service: impl AsRef<str>) -> &mut Self {
974 self.service = Some(service.as_ref().into());
975 self
976 }
977
978 #[inline]
981 fn to_dictionary(&self) -> CFDictionary {
982 let mut dict = CFMutableDictionary::from_CFType_pairs(&[]);
983
984 if let Some(ref value) = self.value {
985 let class_opt = match value {
986 ItemUpdateValue::Ref(ref_) => ref_.class(),
987 ItemUpdateValue::Data(_) => None,
988 };
989 if self.class.is_none() {
991 if let Some(class) = class_opt {
992 dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
993 }
994 }
995 let value_pair = match value {
996 ItemUpdateValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()),
997 ItemUpdateValue::Data(data) => (unsafe { kSecValueData }.to_void(), data.to_void()),
998 };
999 dict.add(&value_pair.0, &value_pair.1);
1000 }
1001 if let Some(class) = self.class {
1002 dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
1003 }
1004 if let Some(location) = &self.location {
1005 match location {
1006 #[cfg(any(feature = "OSX_10_15", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
1007 Location::DataProtectionKeychain => {
1008 dict.add(
1009 &unsafe { kSecUseDataProtectionKeychain }.to_void(),
1010 &CFBoolean::true_value().to_void(),
1011 );
1012 }
1013 #[cfg(target_os = "macos")]
1014 Location::DefaultFileKeychain => {},
1015 #[cfg(target_os = "macos")]
1016 Location::FileKeychain(keychain) => {
1017 dict.add(&unsafe { kSecUseKeychain }.to_void(), &keychain.to_void());
1018 },
1019 }
1020 }
1021 if let Some(account_name) = &self.account_name {
1022 dict.add(&unsafe { kSecAttrAccount }.to_void(), &account_name.to_void());
1023 }
1024 if let Some(access_group) = &self.access_group {
1025 dict.add(&unsafe { kSecAttrAccessGroup }.to_void(), &access_group.to_void());
1026 }
1027 if let Some(comment) = &self.comment {
1028 dict.add(&unsafe { kSecAttrComment }.to_void(), &comment.to_void());
1029 }
1030 if let Some(description) = &self.description {
1031 dict.add(&unsafe { kSecAttrDescription }.to_void(), &description.to_void());
1032 }
1033 if let Some(label) = &self.label {
1034 dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void());
1035 }
1036 if let Some(service) = &self.service {
1037 dict.add(&unsafe { kSecAttrService }.to_void(), &service.to_void());
1038 }
1039
1040 dict.to_immutable()
1041 }
1042}
1043
1044pub enum ItemUpdateValue {
1046 Ref(AddRef),
1048 Data(CFData),
1053}
1054
1055pub enum Location {
1059 #[cfg(any(feature = "OSX_10_15", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
1068 DataProtectionKeychain,
1069 #[cfg(target_os = "macos")]
1072 DefaultFileKeychain,
1073 #[cfg(target_os = "macos")]
1075 FileKeychain(crate::os::macos::keychain::SecKeychain),
1076}
1077
1078impl fmt::Debug for Location {
1079 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1080 f.write_str(match self {
1081 #[cfg(any(feature = "OSX_10_15", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
1082 Self::DataProtectionKeychain => "DataProtectionKeychain",
1083 #[cfg(target_os = "macos")]
1084 Self::DefaultFileKeychain => "DefaultFileKeychain",
1085 #[cfg(target_os = "macos")]
1086 Self::FileKeychain(_) => "FileKeychain",
1087 })
1088 }
1089}
1090
1091#[deprecated(since = "3.0.0", note = "use `ItemAddOptions::add` instead")]
1094#[allow(deprecated)]
1095pub fn add_item(add_params: CFDictionary) -> Result<()> {
1096 cvt(unsafe { SecItemAdd(add_params.as_concrete_TypeRef(), std::ptr::null_mut()) })
1097}
1098
1099pub fn update_item(search_params: &ItemSearchOptions, update_params: &ItemUpdateOptions) -> Result<()> {
1101 cvt(unsafe { SecItemUpdate(
1102 search_params.to_dictionary().as_concrete_TypeRef(),
1103 update_params.to_dictionary().as_concrete_TypeRef()
1104 )})
1105}
1106
1107#[cfg(test)]
1108mod test {
1109 use super::*;
1110
1111 #[test]
1112 fn find_nothing() {
1113 assert!(ItemSearchOptions::new().search().is_err());
1114 }
1115
1116 #[test]
1117 fn limit_two() {
1118 let results = ItemSearchOptions::new()
1119 .class(ItemClass::certificate())
1120 .limit(2)
1121 .search()
1122 .unwrap();
1123 assert_eq!(results.len(), 2);
1124 }
1125
1126 #[test]
1127 fn limit_all() {
1128 let results = ItemSearchOptions::new()
1129 .class(ItemClass::certificate())
1130 .limit(Limit::All)
1131 .search()
1132 .unwrap();
1133 assert!(results.len() >= 2);
1134 }
1135}