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(&params);
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