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 {
from(limit: i64) -> Self89     fn from(limit: i64) -> Self {
90         Self::Max(limit)
91     }
92 }
93 
94 /// A builder type to search for items in keychains.
95 #[derive(Default)]
96 pub struct ItemSearchOptions {
97     #[cfg(target_os = "macos")]
98     keychains: Option<CFArray<SecKeychain>>,
99     #[cfg(not(target_os = "macos"))]
100     keychains: Option<CFArray<CFType>>,
101     class: Option<ItemClass>,
102     load_refs: bool,
103     load_attributes: bool,
104     load_data: bool,
105     limit: Option<Limit>,
106     label: Option<CFString>,
107     access_group: Option<CFString>,
108 }
109 
110 #[cfg(target_os = "macos")]
111 impl crate::ItemSearchOptionsInternals for ItemSearchOptions {
112     #[inline]
keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self113     fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
114         self.keychains = Some(CFArray::from_CFTypes(keychains));
115         self
116     }
117 }
118 
119 impl ItemSearchOptions {
120     /// Creates a new builder with default options.
121     #[inline(always)]
new() -> Self122     pub fn new() -> Self {
123         Self::default()
124     }
125 
126     /// Search only for items of the specified class.
127     #[inline(always)]
class(&mut self, class: ItemClass) -> &mut Self128     pub fn class(&mut self, class: ItemClass) -> &mut Self {
129         self.class = Some(class);
130         self
131     }
132 
133 
134     /// Load Security Framework objects (`SecCertificate`, `SecKey`, etc) for
135     /// the results.
136     #[inline(always)]
load_refs(&mut self, load_refs: bool) -> &mut Self137     pub fn load_refs(&mut self, load_refs: bool) -> &mut Self {
138         self.load_refs = load_refs;
139         self
140     }
141 
142     /// Load Security Framework object attributes for
143     /// the results.
144     #[inline(always)]
load_attributes(&mut self, load_attributes: bool) -> &mut Self145     pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self {
146         self.load_attributes = load_attributes;
147         self
148     }
149 
150     /// Load Security Framework objects data for
151     /// the results.
152     #[inline(always)]
load_data(&mut self, load_data: bool) -> &mut Self153     pub fn load_data(&mut self, load_data: bool) -> &mut Self {
154         self.load_data = load_data;
155         self
156     }
157 
158     /// Limit the number of search results.
159     ///
160     /// If this is not called, the default limit is 1.
161     #[inline(always)]
limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self162     pub fn limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self {
163         self.limit = Some(limit.into());
164         self
165     }
166 
167     /// Search for an item with the given label.
168     #[inline(always)]
label(&mut self, label: &str) -> &mut Self169     pub fn label(&mut self, label: &str) -> &mut Self {
170         self.label = Some(CFString::new(label));
171         self
172     }
173 
174     /// Sets kSecAttrAccessGroup to kSecAttrAccessGroupToken
175     #[inline(always)]
access_group_token(&mut self) -> &mut Self176     pub fn access_group_token(&mut self) -> &mut Self {
177         self.access_group = unsafe { Some(CFString::wrap_under_get_rule(kSecAttrAccessGroupToken)) };
178         self
179     }
180 
181     /// Search for objects.
search(&self) -> Result<Vec<SearchResult>>182     pub fn search(&self) -> Result<Vec<SearchResult>> {
183         unsafe {
184             let mut params = vec![];
185 
186             if let Some(ref keychains) = self.keychains {
187                 params.push((
188                     CFString::wrap_under_get_rule(kSecMatchSearchList),
189                     keychains.as_CFType(),
190                 ));
191             }
192 
193             if let Some(class) = self.class {
194                 params.push((CFString::wrap_under_get_rule(kSecClass), class.to_value()));
195             }
196 
197             if self.load_refs {
198                 params.push((
199                     CFString::wrap_under_get_rule(kSecReturnRef),
200                     CFBoolean::true_value().as_CFType(),
201                 ));
202             }
203 
204             if self.load_attributes {
205                 params.push((
206                     CFString::wrap_under_get_rule(kSecReturnAttributes),
207                     CFBoolean::true_value().as_CFType(),
208                 ));
209             }
210 
211             if self.load_data {
212                 params.push((
213                     CFString::wrap_under_get_rule(kSecReturnData),
214                     CFBoolean::true_value().as_CFType(),
215                 ));
216             }
217 
218             if let Some(limit) = self.limit {
219                 params.push((
220                     CFString::wrap_under_get_rule(kSecMatchLimit),
221                     limit.to_value()
222                 ));
223             }
224 
225             if let Some(ref label) = self.label {
226                 params.push((
227                     CFString::wrap_under_get_rule(kSecAttrLabel),
228                     label.as_CFType(),
229                 ));
230             }
231 
232             if let Some(ref access_group) = self.access_group {
233                 params.push((
234                     CFString::wrap_under_get_rule(kSecAttrAccessGroup),
235                     access_group.as_CFType(),
236                 ));
237             }
238 
239             let params = CFDictionary::from_CFType_pairs(&params);
240 
241             let mut ret = ptr::null();
242             cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
243             let type_id = CFGetTypeID(ret);
244 
245             let mut items = vec![];
246 
247             if type_id == CFArray::<CFType>::type_id() {
248                 let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _);
249                 for item in array.iter() {
250                     items.push(get_item(item.as_CFTypeRef()));
251                 }
252             } else {
253                 items.push(get_item(ret));
254                 // This is a bit janky, but get_item uses wrap_under_get_rule
255                 // which bumps the refcount but we want create semantics
256                 CFRelease(ret);
257             }
258 
259             Ok(items)
260         }
261     }
262 }
263 
264 #[cfg(target_os = "macos")]
get_item(item: CFTypeRef) -> SearchResult265 unsafe fn get_item(item: CFTypeRef) -> SearchResult {
266     use crate::os::macos::keychain_item::SecKeychainItem;
267 
268     let type_id = CFGetTypeID(item);
269 
270     if type_id == CFData::type_id() {
271         let data = CFData::wrap_under_get_rule(item as *mut _);
272         let mut buf = Vec::new();
273         buf.extend_from_slice(data.bytes());
274         return SearchResult::Data(buf);
275     }
276 
277     if type_id == CFDictionary::<*const u8, *const u8>::type_id() {
278         return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _));
279     }
280 
281     let reference = if type_id == SecCertificate::type_id() {
282         Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
283     } else if type_id == SecKey::type_id() {
284         Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
285     } else if type_id == SecIdentity::type_id() {
286         Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
287     } else if type_id == SecKeychainItem::type_id() {
288         Reference::KeychainItem(SecKeychainItem::wrap_under_get_rule(item as *mut _))
289     } else {
290         panic!("Got bad type from SecItemCopyMatching: {}", type_id);
291     };
292 
293     SearchResult::Ref(reference)
294 }
295 
296 #[cfg(not(target_os = "macos"))]
get_item(item: CFTypeRef) -> SearchResult297 unsafe fn get_item(item: CFTypeRef) -> SearchResult {
298     let type_id = CFGetTypeID(item);
299 
300     let reference = if type_id == SecCertificate::type_id() {
301         Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
302     } else if type_id == SecKey::type_id() {
303         Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
304     } else if type_id == SecIdentity::type_id() {
305         Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
306     } else {
307         panic!("Got bad type from SecItemCopyMatching: {}", type_id);
308     };
309 
310     SearchResult::Ref(reference)
311 }
312 
313 /// An enum including all objects which can be found by `ItemSearchOptions`.
314 #[derive(Debug)]
315 pub enum Reference {
316     /// A `SecIdentity`.
317     Identity(SecIdentity),
318     /// A `SecCertificate`.
319     Certificate(SecCertificate),
320     /// A `SecKey`.
321     Key(SecKey),
322     /// A `SecKeychainItem`.
323     ///
324     /// Only defined on OSX
325     #[cfg(target_os = "macos")]
326     KeychainItem(crate::os::macos::keychain_item::SecKeychainItem),
327     #[doc(hidden)]
328     __NonExhaustive,
329 }
330 
331 /// An individual search result.
332 pub enum SearchResult {
333     /// A reference to the Security Framework object, if asked for.
334     Ref(Reference),
335     /// A dictionary of data about the Security Framework object, if asked for.
336     Dict(CFDictionary),
337     /// The Security Framework object as bytes, if asked for.
338     Data(Vec<u8>),
339     /// An unknown representation of the Security Framework object.
340     Other,
341 }
342 
343 impl fmt::Debug for SearchResult {
344     #[cold]
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result345     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
346         match *self {
347             Self::Ref(ref reference) => fmt
348                 .debug_struct("SearchResult::Ref")
349                 .field("reference", reference)
350                 .finish(),
351             Self::Data(ref buf) => fmt
352                 .debug_struct("SearchResult::Data")
353                 .field("data", buf)
354                 .finish(),
355             Self::Dict(_) => {
356                 let mut debug = fmt.debug_struct("SearchResult::Dict");
357                 for (k, v) in self.simplify_dict().unwrap() {
358                     debug.field(&k, &v);
359                 }
360                 debug.finish()
361             }
362             Self::Other => write!(fmt, "SearchResult::Other"),
363         }
364     }
365 }
366 
367 impl SearchResult {
368     /// If the search result is a `CFDict`, simplify that to a
369     /// `HashMap<String, String>`. This transformation isn't
370     /// comprehensive, it only supports CFString, CFDate, and CFData
371     /// value types.
simplify_dict(&self) -> Option<HashMap<String, String>>372     pub fn simplify_dict(&self) -> Option<HashMap<String, String>> {
373         match *self {
374             Self::Dict(ref d) => unsafe {
375                 let mut retmap = HashMap::new();
376                 let (keys, values) = d.get_keys_and_values();
377                 for (k, v) in keys.iter().zip(values.iter()) {
378                     let keycfstr = CFString::wrap_under_get_rule(*k as *const _);
379                     let val: String = match CFGetTypeID(*v) {
380                         cfstring if cfstring == CFString::type_id() => {
381                             format!("{}", CFString::wrap_under_get_rule(*v as *const _))
382                         }
383                         cfdata if cfdata == CFData::type_id() => {
384                             let buf = CFData::wrap_under_get_rule(*v as *const _);
385                             let mut vec = Vec::new();
386                             vec.extend_from_slice(buf.bytes());
387                             format!("{}", String::from_utf8_lossy(&vec))
388                         }
389                         cfdate if cfdate == CFDate::type_id() => format!(
390                             "{}",
391                             CFString::wrap_under_create_rule(CFCopyDescription(*v))
392                         ),
393                         _ => String::from("unknown"),
394                     };
395                     retmap.insert(format!("{}", keycfstr), val);
396                 }
397                 Some(retmap)
398             },
399             _ => None,
400         }
401     }
402 }
403 
404 #[cfg(test)]
405 mod test {
406     use super::*;
407 
408     #[test]
find_nothing()409     fn find_nothing() {
410         assert!(ItemSearchOptions::new().search().is_err());
411     }
412 
413     #[test]
limit_two()414     fn limit_two() {
415         let results = ItemSearchOptions::new()
416             .class(ItemClass::certificate())
417             .limit(2)
418             .search()
419             .unwrap();
420         assert_eq!(results.len(), 2);
421     }
422 
423     #[test]
limit_all()424     fn limit_all() {
425         let results = ItemSearchOptions::new()
426             .class(ItemClass::certificate())
427             .limit(Limit::All)
428             .search()
429             .unwrap();
430         assert!(results.len() >= 2);
431     }
432 }
433