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 authentication_context: Option<CFType>,
172 skip_authenticated_items: bool,
173}
174
175#[cfg(target_os = "macos")]
176impl ItemSearchOptions {
177 #[inline]
181 pub fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
182 self.keychains = Some(CFArray::from_CFTypes(keychains));
183 self
184 }
185
186 #[inline]
193 pub fn ignore_legacy_keychains(&mut self) -> &mut Self {
194 self.ignore_legacy_keychains = true;
195 self
196 }
197}
198
199impl ItemSearchOptions {
200 #[inline(always)]
202 #[must_use]
203 pub fn new() -> Self {
204 Self::default()
205 }
206
207 #[inline(always)]
209 pub fn class(&mut self, class: ItemClass) -> &mut Self {
210 self.class = Some(class);
211 self
212 }
213
214 #[inline(always)]
216 pub fn case_insensitive(&mut self, case_insensitive: Option<bool>) -> &mut Self {
217 self.case_insensitive = case_insensitive;
218 self
219 }
220
221 #[inline(always)]
224 pub fn key_class(&mut self, key_class: KeyClass) -> &mut Self {
225 self.class(ItemClass::key());
226 self.key_class = Some(key_class);
227 self
228 }
229
230 #[inline(always)]
233 pub fn load_refs(&mut self, load_refs: bool) -> &mut Self {
234 self.load_refs = load_refs;
235 self
236 }
237
238 #[inline(always)]
241 pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self {
242 self.load_attributes = load_attributes;
243 self
244 }
245
246 #[inline(always)]
249 pub fn load_data(&mut self, load_data: bool) -> &mut Self {
250 self.load_data = load_data;
251 self
252 }
253
254 #[inline(always)]
258 pub fn limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self {
259 self.limit = Some(limit.into());
260 self
261 }
262
263 #[inline(always)]
265 pub fn label(&mut self, label: &str) -> &mut Self {
266 self.label = Some(CFString::new(label));
267 self
268 }
269
270 #[inline(always)]
272 pub fn trusted_only(&mut self, trusted_only: Option<bool>) -> &mut Self {
273 self.trusted_only = trusted_only;
274 self
275 }
276
277 #[inline(always)]
279 pub fn service(&mut self, service: &str) -> &mut Self {
280 self.service = Some(CFString::new(service));
281 self
282 }
283
284 #[inline(always)]
286 pub fn subject(&mut self, subject: &str) -> &mut Self {
287 self.subject = Some(CFString::new(subject));
288 self
289 }
290
291 #[inline(always)]
293 pub fn account(&mut self, account: &str) -> &mut Self {
294 self.account = Some(CFString::new(account));
295 self
296 }
297
298 pub fn access_group(&mut self, access_group: &str) -> &mut Self {
300 self.access_group = Some(CFString::new(access_group));
301 self
302 }
303
304 pub fn cloud_sync<T: Into<CloudSync>>(&mut self, spec: T) -> &mut Self {
308 self.cloud_sync = Some(spec.into());
309 self
310 }
311
312 #[inline(always)]
314 pub fn access_group_token(&mut self) -> &mut Self {
315 self.access_group = unsafe { Some(CFString::wrap_under_get_rule(kSecAttrAccessGroupToken)) };
316 self
317 }
318
319 #[inline(always)]
325 pub fn pub_key_hash(&mut self, pub_key_hash: &[u8]) -> &mut Self {
326 self.pub_key_hash = Some(CFData::from_buffer(pub_key_hash));
327 self
328 }
329
330 #[inline(always)]
334 pub fn serial_number(&mut self, serial_number: &[u8]) -> &mut Self {
335 self.serial_number = Some(CFData::from_buffer(serial_number));
336 self
337 }
338
339 #[inline(always)]
345 pub fn application_label(&mut self, app_label: &[u8]) -> &mut Self {
346 self.app_label = Some(CFData::from_buffer(app_label));
347 self
348 }
349
350 #[doc(hidden)]
351 #[deprecated(note = "use local_authentication_context")]
352 pub unsafe fn authentication_context(&mut self, authentication_context: *mut std::os::raw::c_void) -> &mut Self {
353 self.authentication_context = unsafe { Some(CFType::wrap_under_create_rule(authentication_context)) };
354 self
355 }
356
357 #[inline(always)]
360 pub fn local_authentication_context<LAContext: TCFType>(&mut self, authentication_context: Option<LAContext>) -> &mut Self {
361 self.authentication_context = authentication_context.map(|la| la.into_CFType());
362 self
363 }
364
365 #[inline(always)]
367 pub fn skip_authenticated_items(&mut self, do_skip: bool) -> &mut Self {
368 self.skip_authenticated_items = do_skip;
369 self
370 }
371
372 #[inline]
375 fn to_dictionary(&self) -> CFDictionary {
376 unsafe {
377 let mut params = CFMutableDictionary::from_CFType_pairs(&[]);
378
379 if let Some(keychains) = &self.keychains {
380 params.add(
381 &kSecMatchSearchList.to_void(),
382 &keychains.as_CFType().to_void(),
383 );
384 } else if self.ignore_legacy_keychains {
385 #[cfg(all(target_os = "macos", feature = "OSX_10_15"))]
386 params.add(
387 &kSecUseDataProtectionKeychain.to_void(),
388 &CFBoolean::true_value().to_void(),
389 );
390 }
391
392 if let Some(class) = self.class {
393 params.add(&kSecClass.to_void(), &class.0.to_void());
394 }
395
396 if let Some(case_insensitive) = self.case_insensitive {
397 params.add(
398 &kSecMatchCaseInsensitive.to_void(),
399 &CFBoolean::from(case_insensitive).to_void(),
400 );
401 }
402
403 if let Some(key_class) = self.key_class {
404 params.add(&kSecAttrKeyClass.to_void(), &key_class.0.to_void());
405 }
406
407 if self.load_refs {
408 params.add(&kSecReturnRef.to_void(), &CFBoolean::true_value().to_void());
409 }
410
411 if self.load_attributes {
412 params.add(
413 &kSecReturnAttributes.to_void(),
414 &CFBoolean::true_value().to_void(),
415 );
416 }
417
418 if self.load_data {
419 params.add(
420 &kSecReturnData.to_void(),
421 &CFBoolean::true_value().to_void(),
422 );
423 }
424
425 if let Some(limit) = self.limit {
426 params.add(&kSecMatchLimit.to_void(), &limit.to_value().to_void());
427 }
428
429 if let Some(label) = &self.label {
430 params.add(&kSecAttrLabel.to_void(), &label.to_void());
431 }
432
433 if let Some(trusted_only) = &self.trusted_only {
434 params.add(
435 &kSecMatchTrustedOnly.to_void(),
436 &CFBoolean::from(*trusted_only).to_void(),
437 );
438 }
439
440 if let Some(service) = &self.service {
441 params.add(&kSecAttrService.to_void(), &service.to_void());
442 }
443
444 #[cfg(target_os = "macos")]
445 {
446 if let Some(subject) = &self.subject {
447 params.add(&kSecMatchSubjectWholeString.to_void(), &subject.to_void());
448 }
449 }
450
451 if let Some(account) = &self.account {
452 params.add(&kSecAttrAccount.to_void(), &account.to_void());
453 }
454
455 if let Some(access_group) = &self.access_group {
456 params.add(&kSecAttrAccessGroup.to_void(), &access_group.to_void());
457 }
458
459 if let Some(cloud_sync) = &self.cloud_sync {
460 match cloud_sync {
461 CloudSync::MatchSyncYes => {
462 params.add(&kSecAttrSynchronizable.to_void(), &CFBoolean::true_value().to_void());
463 },
464 CloudSync::MatchSyncNo => {
465 params.add(&kSecAttrSynchronizable.to_void(), &CFBoolean::false_value().to_void());
466 },
467 CloudSync::MatchSyncAny => {
468 params.add(&kSecAttrSynchronizable.to_void(), &kSecAttrSynchronizableAny.to_void());
469 },
470 }
471 }
472
473 if let Some(pub_key_hash) = &self.pub_key_hash {
474 params.add(&kSecAttrPublicKeyHash.to_void(), &pub_key_hash.to_void());
475 }
476
477 if let Some(serial_number) = &self.serial_number {
478 params.add(&kSecAttrSerialNumber.to_void(), &serial_number.to_void());
479 }
480
481 if let Some(app_label) = &self.app_label {
482 params.add(&kSecAttrApplicationLabel.to_void(), &app_label.to_void());
483 }
484
485 if let Some(authentication_context) = &self.authentication_context {
486 params.add(&kSecUseAuthenticationContext.to_void(), &authentication_context.to_void());
487 }
488
489 if self.skip_authenticated_items {
490 params.add(&kSecUseAuthenticationUI.to_void(), &kSecUseAuthenticationUISkip.to_void());
491 }
492
493 params.to_immutable()
494 }
495 }
496
497 #[inline]
499 pub fn search(&self) -> Result<Vec<SearchResult>> {
500 unsafe {
501 let params = self.to_dictionary();
502
503 let mut ret = ptr::null();
504 cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
505 if ret.is_null() {
506 return Ok(vec![]);
509 }
510 let type_id = CFGetTypeID(ret);
511
512 let mut items = vec![];
513
514 if type_id == CFArray::<CFType>::type_id() {
515 let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _);
516 for item in array.iter() {
517 items.push(get_item(item.as_CFTypeRef()));
518 }
519 } else {
520 items.push(get_item(ret));
521 CFRelease(ret);
524 }
525
526 Ok(items)
527 }
528 }
529
530 #[inline]
534 pub fn delete(&self) -> Result<()> {
535 cvt(unsafe { SecItemDelete(self.to_dictionary().as_concrete_TypeRef()) })
536 }
537}
538
539unsafe fn get_item(item: CFTypeRef) -> SearchResult { unsafe {
540 let type_id = CFGetTypeID(item);
541
542 if type_id == CFData::type_id() {
543 let data = CFData::wrap_under_get_rule(item as *mut _);
544 let mut buf = Vec::new();
545 buf.extend_from_slice(data.bytes());
546 return SearchResult::Data(buf);
547 }
548
549 if type_id == CFDictionary::<*const u8, *const u8>::type_id() {
550 return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _));
551 }
552
553 #[cfg(target_os = "macos")]
554 {
555 use crate::os::macos::keychain_item::SecKeychainItem;
556 if type_id == SecKeychainItem::type_id() {
557 return SearchResult::Ref(Reference::KeychainItem(
558 SecKeychainItem::wrap_under_get_rule(item as *mut _),
559 ));
560 }
561 }
562
563 let reference = if type_id == SecCertificate::type_id() {
564 Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
565 } else if type_id == SecKey::type_id() {
566 Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
567 } else if type_id == SecIdentity::type_id() {
568 Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
569 } else {
570 panic!("Got bad type from SecItemCopyMatching: {type_id}");
571 };
572
573 SearchResult::Ref(reference)
574} }
575
576#[derive(Debug)]
582pub enum Reference {
583 Identity(SecIdentity),
585 Certificate(SecCertificate),
587 Key(SecKey),
589 #[cfg(target_os = "macos")]
593 KeychainItem(crate::os::macos::keychain_item::SecKeychainItem),
594 #[doc(hidden)]
595 __NonExhaustive,
596}
597
598pub enum SearchResult {
600 Ref(Reference),
602 Dict(CFDictionary),
604 Data(Vec<u8>),
606 Other,
608}
609
610impl fmt::Debug for SearchResult {
611 #[cold]
612 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
613 match self {
614 Self::Ref(reference) => fmt
615 .debug_struct("SearchResult::Ref")
616 .field("reference", reference)
617 .finish(),
618 Self::Data(buf) => fmt
619 .debug_struct("SearchResult::Data")
620 .field("data", buf)
621 .finish(),
622 Self::Dict(_) => {
623 let mut debug = fmt.debug_struct("SearchResult::Dict");
624 for (k, v) in self.simplify_dict().unwrap() {
625 debug.field(&k, &v);
626 }
627 debug.finish()
628 },
629 Self::Other => write!(fmt, "SearchResult::Other"),
630 }
631 }
632}
633
634impl SearchResult {
635 #[must_use]
640 pub fn simplify_dict(&self) -> Option<HashMap<String, String>> {
641 match self {
642 Self::Dict(d) => unsafe {
643 let mut retmap = HashMap::new();
644 let (keys, values) = d.get_keys_and_values();
645 for (k, v) in keys.iter().zip(values.iter()) {
646 let keycfstr = CFString::wrap_under_get_rule((*k).cast());
647 let val: String = match CFGetTypeID(*v) {
648 cfstring if cfstring == CFString::type_id() => {
649 format!("{}", CFString::wrap_under_get_rule((*v).cast()))
650 },
651 cfdata if cfdata == CFData::type_id() => {
652 let buf = CFData::wrap_under_get_rule((*v).cast());
653 let mut vec = Vec::new();
654 vec.extend_from_slice(buf.bytes());
655 format!("{}", String::from_utf8_lossy(&vec))
656 },
657 cfdate if cfdate == CFDate::type_id() => {
658 format!("{}", CFString::wrap_under_create_rule(CFCopyDescription(*v)))
659 },
660 _ => String::from("unknown"),
661 };
662 retmap.insert(format!("{keycfstr}"), val);
663 }
664 Some(retmap)
665 },
666 _ => None,
667 }
668 }
669}
670
671pub struct ItemAddOptions {
676 pub value: ItemAddValue,
678 pub account_name: Option<CFString>,
680 pub access_group: Option<CFString>,
682 pub comment: Option<CFString>,
684 pub description: Option<CFString>,
686 pub label: Option<CFString>,
688 pub service: Option<CFString>,
690 pub location: Option<Location>,
692}
693
694impl ItemAddOptions {
695 #[inline]
697 #[must_use]
698 pub fn new(value: ItemAddValue) -> Self {
699 Self {
700 value,
701 label: None,
702 location: None,
703 service: None,
704 account_name: None,
705 comment: None,
706 description: None,
707 access_group: None,
708 }
709 }
710
711 #[inline]
713 pub fn set_account_name(&mut self, account_name: impl AsRef<str>) -> &mut Self {
714 self.account_name = Some(account_name.as_ref().into());
715 self
716 }
717
718 #[inline]
720 pub fn set_access_group(&mut self, access_group: impl AsRef<str>) -> &mut Self {
721 self.access_group = Some(access_group.as_ref().into());
722 self
723 }
724
725 #[inline]
727 pub fn set_comment(&mut self, comment: impl AsRef<str>) -> &mut Self {
728 self.comment = Some(comment.as_ref().into());
729 self
730 }
731
732 #[inline]
734 pub fn set_description(&mut self, description: impl AsRef<str>) -> &mut Self {
735 self.description = Some(description.as_ref().into());
736 self
737 }
738
739 #[inline]
741 pub fn set_label(&mut self, label: impl AsRef<str>) -> &mut Self {
742 self.label = Some(label.as_ref().into());
743 self
744 }
745
746 #[inline]
748 pub fn set_location(&mut self, location: Location) -> &mut Self {
749 self.location = Some(location);
750 self
751 }
752
753 #[inline]
755 pub fn set_service(&mut self, service: impl AsRef<str>) -> &mut Self {
756 self.service = Some(service.as_ref().into());
757 self
758 }
759
760 #[deprecated(since = "3.0.0", note = "use `ItemAddOptions::add` instead")]
762 #[must_use]
764 pub fn to_dictionary(&self) -> CFDictionary {
765 let mut dict = CFMutableDictionary::from_CFType_pairs(&[]);
766
767 let class_opt = match &self.value {
768 ItemAddValue::Ref(ref_) => ref_.class(),
769 ItemAddValue::Data { class, .. } => Some(*class),
770 };
771 if let Some(class) = class_opt {
772 dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
773 }
774
775 let value_pair = match &self.value {
776 ItemAddValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()),
777 ItemAddValue::Data { data, .. } => (unsafe { kSecValueData }.to_void(), data.to_void()),
778 };
779 dict.add(&value_pair.0, &value_pair.1);
780
781 if let Some(location) = &self.location {
782 match location {
783 #[cfg(any(feature = "OSX_10_15", not(target_os = "macos")))]
784 Location::DataProtectionKeychain => {
785 dict.add(
786 &unsafe { kSecUseDataProtectionKeychain }.to_void(),
787 &CFBoolean::true_value().to_void(),
788 );
789 }
790 #[cfg(target_os = "macos")]
791 Location::DefaultFileKeychain => {},
792 #[cfg(target_os = "macos")]
793 Location::FileKeychain(keychain) => {
794 dict.add(&unsafe { kSecUseKeychain }.to_void(), &keychain.to_void());
795 },
796 }
797 }
798 if let Some(account_name) = &self.account_name {
799 dict.add(&unsafe { kSecAttrAccount }.to_void(), &account_name.to_void());
800 }
801 if let Some(access_group) = &self.access_group {
802 dict.add(&unsafe { kSecAttrAccessGroup }.to_void(), &access_group.to_void());
803 }
804 if let Some(comment) = &self.comment {
805 dict.add(&unsafe { kSecAttrComment }.to_void(), &comment.to_void());
806 }
807 if let Some(description) = &self.description {
808 dict.add(&unsafe { kSecAttrDescription }.to_void(), &description.to_void());
809 }
810 if let Some(label) = &self.label {
811 dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void());
812 }
813 if let Some(service) = &self.service {
814 dict.add(&unsafe { kSecAttrService }.to_void(), &service.to_void());
815 }
816
817 dict.to_immutable()
818 }
819
820 #[inline]
824 pub fn add(&self) -> Result<()> {
825 #[allow(deprecated)]
826 cvt(unsafe { SecItemAdd(self.to_dictionary().as_concrete_TypeRef(), std::ptr::null_mut()) })
827 }
828}
829
830pub enum ItemAddValue {
832 Ref(AddRef),
834 Data {
836 class: ItemClass,
838 data: CFData,
840 },
841}
842
843pub enum AddRef {
845 Key(SecKey),
847 Identity(SecIdentity),
849 Certificate(SecCertificate),
851}
852
853impl AddRef {
854 fn class(&self) -> Option<ItemClass> {
855 match self {
856 Self::Key(_) => Some(ItemClass::key()),
857 Self::Identity(_) => None,
860 Self::Certificate(_) => Some(ItemClass::certificate()),
861 }
862 }
863
864 fn ref_(&self) -> CFTypeRef {
865 match self {
866 Self::Key(key) => key.as_CFTypeRef(),
867 Self::Identity(id) => id.as_CFTypeRef(),
868 Self::Certificate(cert) => cert.as_CFTypeRef(),
869 }
870 }
871}
872
873#[derive(Default)]
878pub struct ItemUpdateOptions {
879 pub value: Option<ItemUpdateValue>,
881 pub account_name: Option<CFString>,
883 pub access_group: Option<CFString>,
885 pub comment: Option<CFString>,
887 pub description: Option<CFString>,
889 pub label: Option<CFString>,
891 pub service: Option<CFString>,
893 pub location: Option<Location>,
895 pub class: Option<ItemClass>,
899}
900
901impl ItemUpdateOptions {
902 #[must_use]
904 pub fn new() -> Self {
905 Default::default()
906 }
907
908 #[inline]
910 pub fn set_value(&mut self, value: ItemUpdateValue) -> &mut Self {
911 self.value = Some(value);
912 self
913 }
914
915 #[inline]
917 pub fn set_class(&mut self, class: ItemClass) -> &mut Self {
918 self.class = Some(class);
919 self
920 }
921
922 #[inline]
924 pub fn set_account_name(&mut self, account_name: impl AsRef<str>) -> &mut Self {
925 self.account_name = Some(account_name.as_ref().into());
926 self
927 }
928
929 #[inline]
931 pub fn set_access_group(&mut self, access_group: impl AsRef<str>) -> &mut Self {
932 self.access_group = Some(access_group.as_ref().into());
933 self
934 }
935
936 #[inline]
938 pub fn set_comment(&mut self, comment: impl AsRef<str>) -> &mut Self {
939 self.comment = Some(comment.as_ref().into());
940 self
941 }
942
943 #[inline]
945 pub fn set_description(&mut self, description: impl AsRef<str>) -> &mut Self {
946 self.description = Some(description.as_ref().into());
947 self
948 }
949
950 #[inline]
952 pub fn set_label(&mut self, label: impl AsRef<str>) -> &mut Self {
953 self.label = Some(label.as_ref().into());
954 self
955 }
956
957 #[inline]
959 pub fn set_location(&mut self, location: Location) -> &mut Self {
960 self.location = Some(location);
961 self
962 }
963
964 #[inline]
966 pub fn set_service(&mut self, service: impl AsRef<str>) -> &mut Self {
967 self.service = Some(service.as_ref().into());
968 self
969 }
970
971 #[inline]
974 fn to_dictionary(&self) -> CFDictionary {
975 let mut dict = CFMutableDictionary::from_CFType_pairs(&[]);
976
977 if let Some(value) = &self.value {
978 let class_opt = match value {
979 ItemUpdateValue::Ref(ref_) => ref_.class(),
980 ItemUpdateValue::Data(_) => None,
981 };
982 if self.class.is_none() {
984 if let Some(class) = class_opt {
985 dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
986 }
987 }
988 let value_pair = match value {
989 ItemUpdateValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()),
990 ItemUpdateValue::Data(data) => (unsafe { kSecValueData }.to_void(), data.to_void()),
991 };
992 dict.add(&value_pair.0, &value_pair.1);
993 }
994 if let Some(class) = self.class {
995 dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void());
996 }
997 if let Some(location) = &self.location {
998 match location {
999 #[cfg(any(feature = "OSX_10_15", not(target_os = "macos")))]
1000 Location::DataProtectionKeychain => {
1001 dict.add(
1002 &unsafe { kSecUseDataProtectionKeychain }.to_void(),
1003 &CFBoolean::true_value().to_void(),
1004 );
1005 }
1006 #[cfg(target_os = "macos")]
1007 Location::DefaultFileKeychain => {},
1008 #[cfg(target_os = "macos")]
1009 Location::FileKeychain(keychain) => {
1010 dict.add(&unsafe { kSecUseKeychain }.to_void(), &keychain.to_void());
1011 },
1012 }
1013 }
1014 if let Some(account_name) = &self.account_name {
1015 dict.add(&unsafe { kSecAttrAccount }.to_void(), &account_name.to_void());
1016 }
1017 if let Some(access_group) = &self.access_group {
1018 dict.add(&unsafe { kSecAttrAccessGroup }.to_void(), &access_group.to_void());
1019 }
1020 if let Some(comment) = &self.comment {
1021 dict.add(&unsafe { kSecAttrComment }.to_void(), &comment.to_void());
1022 }
1023 if let Some(description) = &self.description {
1024 dict.add(&unsafe { kSecAttrDescription }.to_void(), &description.to_void());
1025 }
1026 if let Some(label) = &self.label {
1027 dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void());
1028 }
1029 if let Some(service) = &self.service {
1030 dict.add(&unsafe { kSecAttrService }.to_void(), &service.to_void());
1031 }
1032
1033 dict.to_immutable()
1034 }
1035}
1036
1037pub enum ItemUpdateValue {
1039 Ref(AddRef),
1041 Data(CFData),
1046}
1047
1048pub enum Location {
1052 #[cfg(any(feature = "OSX_10_15", not(target_os = "macos")))]
1061 DataProtectionKeychain,
1062 #[cfg(target_os = "macos")]
1065 DefaultFileKeychain,
1066 #[cfg(target_os = "macos")]
1068 FileKeychain(crate::os::macos::keychain::SecKeychain),
1069}
1070
1071impl fmt::Debug for Location {
1072 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1073 f.write_str(match self {
1074 #[cfg(any(feature = "OSX_10_15", not(target_os = "macos")))]
1075 Self::DataProtectionKeychain => "DataProtectionKeychain",
1076 #[cfg(target_os = "macos")]
1077 Self::DefaultFileKeychain => "DefaultFileKeychain",
1078 #[cfg(target_os = "macos")]
1079 Self::FileKeychain(_) => "FileKeychain",
1080 })
1081 }
1082}
1083
1084#[deprecated(since = "3.0.0", note = "use `ItemAddOptions::add` instead")]
1087#[allow(deprecated)]
1088pub fn add_item(add_params: CFDictionary) -> Result<()> {
1089 cvt(unsafe { SecItemAdd(add_params.as_concrete_TypeRef(), std::ptr::null_mut()) })
1090}
1091
1092pub fn update_item(search_params: &ItemSearchOptions, update_params: &ItemUpdateOptions) -> Result<()> {
1094 cvt(unsafe { SecItemUpdate(
1095 search_params.to_dictionary().as_concrete_TypeRef(),
1096 update_params.to_dictionary().as_concrete_TypeRef()
1097 )})
1098}
1099
1100#[cfg(test)]
1101mod test {
1102 use super::*;
1103
1104 #[test]
1105 fn find_nothing() {
1106 assert!(ItemSearchOptions::new().search().is_err());
1107 }
1108
1109 #[test]
1110 fn limit_two() {
1111 let results = ItemSearchOptions::new()
1112 .class(ItemClass::certificate())
1113 .limit(2)
1114 .search()
1115 .unwrap();
1116 assert_eq!(results.len(), 2);
1117 }
1118
1119 #[test]
1120 fn limit_all() {
1121 let results = ItemSearchOptions::new()
1122 .class(ItemClass::certificate())
1123 .limit(Limit::All)
1124 .search()
1125 .unwrap();
1126 assert!(results.len() >= 2);
1127 }
1128}