1 //! Support to search for items in a keychain.
2
3 use core_foundation::array::CFArray;
4 use core_foundation::base::{CFType, TCFType};
5 use core_foundation::boolean::CFBoolean;
6 use core_foundation::data::CFData;
7 use core_foundation::date::CFDate;
8 use core_foundation::dictionary::CFDictionary;
9 use core_foundation::number::CFNumber;
10 use core_foundation::string::CFString;
11 use core_foundation_sys::base::{CFCopyDescription, CFGetTypeID, CFRelease, CFTypeRef};
12 use core_foundation_sys::string::CFStringRef;
13 use security_framework_sys::item::*;
14 use security_framework_sys::keychain_item::SecItemCopyMatching;
15 use std::collections::HashMap;
16 use std::fmt;
17 use std::ptr;
18
19 use crate::base::Result;
20 use crate::certificate::SecCertificate;
21 use crate::cvt;
22 use crate::identity::SecIdentity;
23 use crate::key::SecKey;
24 #[cfg(target_os = "macos")]
25 use crate::os::macos::keychain::SecKeychain;
26
27 /// Specifies the type of items to search for.
28 #[derive(Debug, Copy, Clone)]
29 pub struct ItemClass(CFStringRef);
30
31 impl ItemClass {
32 /// Look for `SecKeychainItem`s corresponding to generic passwords.
33 #[inline(always)]
generic_password() -> Self34 pub fn generic_password() -> Self {
35 unsafe { Self(kSecClassGenericPassword) }
36 }
37
38 /// Look for `SecKeychainItem`s corresponding to internet passwords.
39 #[inline(always)]
internet_password() -> Self40 pub fn internet_password() -> Self {
41 unsafe { Self(kSecClassInternetPassword) }
42 }
43
44 /// Look for `SecCertificate`s.
45 #[inline(always)]
certificate() -> Self46 pub fn certificate() -> Self {
47 unsafe { Self(kSecClassCertificate) }
48 }
49
50 /// Look for `SecKey`s.
51 #[inline(always)]
key() -> Self52 pub fn key() -> Self {
53 unsafe { Self(kSecClassKey) }
54 }
55
56 /// Look for `SecIdentity`s.
57 #[inline(always)]
identity() -> Self58 pub fn identity() -> Self {
59 unsafe { Self(kSecClassIdentity) }
60 }
61
62 #[inline]
to_value(self) -> CFType63 fn to_value(self) -> CFType {
64 unsafe { CFType::wrap_under_get_rule(self.0 as *const _) }
65 }
66 }
67
68 /// Specifies the number of results returned by a search
69 #[derive(Debug, Copy, Clone)]
70 pub enum Limit {
71 /// Always return all results
72 All,
73
74 /// Return up to the specified number of results
75 Max(i64),
76 }
77
78 impl Limit {
79 #[inline]
to_value(self) -> CFType80 fn to_value(self) -> CFType {
81 match self {
82 Self::All => unsafe { CFString::wrap_under_get_rule(kSecMatchLimitAll).as_CFType() }
83 Self::Max(l) => CFNumber::from(l).as_CFType()
84 }
85 }
86 }
87
88 impl From<i64> for Limit {
89 #[inline]
from(limit: i64) -> Self90 fn from(limit: i64) -> Self {
91 Self::Max(limit)
92 }
93 }
94
95 /// A builder type to search for items in keychains.
96 #[derive(Default)]
97 pub struct ItemSearchOptions {
98 #[cfg(target_os = "macos")]
99 keychains: Option<CFArray<SecKeychain>>,
100 #[cfg(not(target_os = "macos"))]
101 keychains: Option<CFArray<CFType>>,
102 class: Option<ItemClass>,
103 load_refs: bool,
104 load_attributes: bool,
105 load_data: bool,
106 limit: Option<Limit>,
107 label: Option<CFString>,
108 access_group: Option<CFString>,
109 }
110
111 #[cfg(target_os = "macos")]
112 impl crate::ItemSearchOptionsInternals for ItemSearchOptions {
113 #[inline]
keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self114 fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
115 self.keychains = Some(CFArray::from_CFTypes(keychains));
116 self
117 }
118 }
119
120 impl ItemSearchOptions {
121 /// Creates a new builder with default options.
122 #[inline(always)]
new() -> Self123 pub fn new() -> Self {
124 Self::default()
125 }
126
127 /// Search only for items of the specified class.
128 #[inline(always)]
class(&mut self, class: ItemClass) -> &mut Self129 pub fn class(&mut self, class: ItemClass) -> &mut Self {
130 self.class = Some(class);
131 self
132 }
133
134
135 /// Load Security Framework objects (`SecCertificate`, `SecKey`, etc) for
136 /// the results.
137 #[inline(always)]
load_refs(&mut self, load_refs: bool) -> &mut Self138 pub fn load_refs(&mut self, load_refs: bool) -> &mut Self {
139 self.load_refs = load_refs;
140 self
141 }
142
143 /// Load Security Framework object attributes for
144 /// the results.
145 #[inline(always)]
load_attributes(&mut self, load_attributes: bool) -> &mut Self146 pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self {
147 self.load_attributes = load_attributes;
148 self
149 }
150
151 /// Load Security Framework objects data for
152 /// the results.
153 #[inline(always)]
load_data(&mut self, load_data: bool) -> &mut Self154 pub fn load_data(&mut self, load_data: bool) -> &mut Self {
155 self.load_data = load_data;
156 self
157 }
158
159 /// Limit the number of search results.
160 ///
161 /// If this is not called, the default limit is 1.
162 #[inline(always)]
limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self163 pub fn limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self {
164 self.limit = Some(limit.into());
165 self
166 }
167
168 /// Search for an item with the given label.
169 #[inline(always)]
label(&mut self, label: &str) -> &mut Self170 pub fn label(&mut self, label: &str) -> &mut Self {
171 self.label = Some(CFString::new(label));
172 self
173 }
174
175 /// Sets kSecAttrAccessGroup to kSecAttrAccessGroupToken
176 #[inline(always)]
access_group_token(&mut self) -> &mut Self177 pub fn access_group_token(&mut self) -> &mut Self {
178 self.access_group = unsafe { Some(CFString::wrap_under_get_rule(kSecAttrAccessGroupToken)) };
179 self
180 }
181
182 /// Search for objects.
search(&self) -> Result<Vec<SearchResult>>183 pub fn search(&self) -> Result<Vec<SearchResult>> {
184 unsafe {
185 let mut params = vec![];
186
187 if let Some(ref keychains) = self.keychains {
188 params.push((
189 CFString::wrap_under_get_rule(kSecMatchSearchList),
190 keychains.as_CFType(),
191 ));
192 }
193
194 if let Some(class) = self.class {
195 params.push((CFString::wrap_under_get_rule(kSecClass), class.to_value()));
196 }
197
198 if self.load_refs {
199 params.push((
200 CFString::wrap_under_get_rule(kSecReturnRef),
201 CFBoolean::true_value().as_CFType(),
202 ));
203 }
204
205 if self.load_attributes {
206 params.push((
207 CFString::wrap_under_get_rule(kSecReturnAttributes),
208 CFBoolean::true_value().as_CFType(),
209 ));
210 }
211
212 if self.load_data {
213 params.push((
214 CFString::wrap_under_get_rule(kSecReturnData),
215 CFBoolean::true_value().as_CFType(),
216 ));
217 }
218
219 if let Some(limit) = self.limit {
220 params.push((
221 CFString::wrap_under_get_rule(kSecMatchLimit),
222 limit.to_value()
223 ));
224 }
225
226 if let Some(ref label) = self.label {
227 params.push((
228 CFString::wrap_under_get_rule(kSecAttrLabel),
229 label.as_CFType(),
230 ));
231 }
232
233 if let Some(ref access_group) = self.access_group {
234 params.push((
235 CFString::wrap_under_get_rule(kSecAttrAccessGroup),
236 access_group.as_CFType(),
237 ));
238 }
239
240 let params = CFDictionary::from_CFType_pairs(¶ms);
241
242 let mut ret = ptr::null();
243 cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
244 let type_id = CFGetTypeID(ret);
245
246 let mut items = vec![];
247
248 if type_id == CFArray::<CFType>::type_id() {
249 let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _);
250 for item in array.iter() {
251 items.push(get_item(item.as_CFTypeRef()));
252 }
253 } else {
254 items.push(get_item(ret));
255 // This is a bit janky, but get_item uses wrap_under_get_rule
256 // which bumps the refcount but we want create semantics
257 CFRelease(ret);
258 }
259
260 Ok(items)
261 }
262 }
263 }
264
265 #[cfg(target_os = "macos")]
get_item(item: CFTypeRef) -> SearchResult266 unsafe fn get_item(item: CFTypeRef) -> SearchResult {
267 use crate::os::macos::keychain_item::SecKeychainItem;
268
269 let type_id = CFGetTypeID(item);
270
271 if type_id == CFData::type_id() {
272 let data = CFData::wrap_under_get_rule(item as *mut _);
273 let mut buf = Vec::new();
274 buf.extend_from_slice(data.bytes());
275 return SearchResult::Data(buf);
276 }
277
278 if type_id == CFDictionary::<*const u8, *const u8>::type_id() {
279 return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _));
280 }
281
282 let reference = if type_id == SecCertificate::type_id() {
283 Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
284 } else if type_id == SecKey::type_id() {
285 Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
286 } else if type_id == SecIdentity::type_id() {
287 Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
288 } else if type_id == SecKeychainItem::type_id() {
289 Reference::KeychainItem(SecKeychainItem::wrap_under_get_rule(item as *mut _))
290 } else {
291 panic!("Got bad type from SecItemCopyMatching: {}", type_id);
292 };
293
294 SearchResult::Ref(reference)
295 }
296
297 #[cfg(not(target_os = "macos"))]
get_item(item: CFTypeRef) -> SearchResult298 unsafe fn get_item(item: CFTypeRef) -> SearchResult {
299 let type_id = CFGetTypeID(item);
300
301 let reference = if type_id == SecCertificate::type_id() {
302 Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
303 } else if type_id == SecKey::type_id() {
304 Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
305 } else if type_id == SecIdentity::type_id() {
306 Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
307 } else {
308 panic!("Got bad type from SecItemCopyMatching: {}", type_id);
309 };
310
311 SearchResult::Ref(reference)
312 }
313
314 /// An enum including all objects which can be found by `ItemSearchOptions`.
315 #[derive(Debug)]
316 pub enum Reference {
317 /// A `SecIdentity`.
318 Identity(SecIdentity),
319 /// A `SecCertificate`.
320 Certificate(SecCertificate),
321 /// A `SecKey`.
322 Key(SecKey),
323 /// A `SecKeychainItem`.
324 ///
325 /// Only defined on OSX
326 #[cfg(target_os = "macos")]
327 KeychainItem(crate::os::macos::keychain_item::SecKeychainItem),
328 #[doc(hidden)]
329 __NonExhaustive,
330 }
331
332 /// An individual search result.
333 pub enum SearchResult {
334 /// A reference to the Security Framework object, if asked for.
335 Ref(Reference),
336 /// A dictionary of data about the Security Framework object, if asked for.
337 Dict(CFDictionary),
338 /// The Security Framework object as bytes, if asked for.
339 Data(Vec<u8>),
340 /// An unknown representation of the Security Framework object.
341 Other,
342 }
343
344 impl fmt::Debug for SearchResult {
345 #[cold]
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result346 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
347 match *self {
348 Self::Ref(ref reference) => fmt
349 .debug_struct("SearchResult::Ref")
350 .field("reference", reference)
351 .finish(),
352 Self::Data(ref buf) => fmt
353 .debug_struct("SearchResult::Data")
354 .field("data", buf)
355 .finish(),
356 Self::Dict(_) => {
357 let mut debug = fmt.debug_struct("SearchResult::Dict");
358 for (k, v) in self.simplify_dict().unwrap() {
359 debug.field(&k, &v);
360 }
361 debug.finish()
362 }
363 Self::Other => write!(fmt, "SearchResult::Other"),
364 }
365 }
366 }
367
368 impl SearchResult {
369 /// If the search result is a `CFDict`, simplify that to a
370 /// `HashMap<String, String>`. This transformation isn't
371 /// comprehensive, it only supports CFString, CFDate, and CFData
372 /// value types.
simplify_dict(&self) -> Option<HashMap<String, String>>373 pub fn simplify_dict(&self) -> Option<HashMap<String, String>> {
374 match *self {
375 Self::Dict(ref d) => unsafe {
376 let mut retmap = HashMap::new();
377 let (keys, values) = d.get_keys_and_values();
378 for (k, v) in keys.iter().zip(values.iter()) {
379 let keycfstr = CFString::wrap_under_get_rule(*k as *const _);
380 let val: String = match CFGetTypeID(*v) {
381 cfstring if cfstring == CFString::type_id() => {
382 format!("{}", CFString::wrap_under_get_rule(*v as *const _))
383 }
384 cfdata if cfdata == CFData::type_id() => {
385 let buf = CFData::wrap_under_get_rule(*v as *const _);
386 let mut vec = Vec::new();
387 vec.extend_from_slice(buf.bytes());
388 format!("{}", String::from_utf8_lossy(&vec))
389 }
390 cfdate if cfdate == CFDate::type_id() => format!(
391 "{}",
392 CFString::wrap_under_create_rule(CFCopyDescription(*v))
393 ),
394 _ => String::from("unknown"),
395 };
396 retmap.insert(format!("{}", keycfstr), val);
397 }
398 Some(retmap)
399 },
400 _ => None,
401 }
402 }
403 }
404
405 #[cfg(test)]
406 mod test {
407 use super::*;
408
409 #[test]
find_nothing()410 fn find_nothing() {
411 assert!(ItemSearchOptions::new().search().is_err());
412 }
413
414 #[test]
limit_two()415 fn limit_two() {
416 let results = ItemSearchOptions::new()
417 .class(ItemClass::certificate())
418 .limit(2)
419 .search()
420 .unwrap();
421 assert_eq!(results.len(), 2);
422 }
423
424 #[test]
limit_all()425 fn limit_all() {
426 let results = ItemSearchOptions::new()
427 .class(ItemClass::certificate())
428 .limit(Limit::All)
429 .search()
430 .unwrap();
431 assert!(results.len() >= 2);
432 }
433 }
434