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 std::collections::HashMap;
15 use std::fmt;
16 use std::ptr;
17 
18 use crate::base::Result;
19 use crate::certificate::SecCertificate;
20 use crate::cvt;
21 use crate::identity::SecIdentity;
22 use crate::key::SecKey;
23 #[cfg(target_os = "macos")]
24 use crate::os::macos::keychain::SecKeychain;
25 
26 /// Specifies the type of items to search for.
27 #[derive(Debug, Copy, Clone)]
28 pub struct ItemClass(CFStringRef);
29 
30 impl ItemClass {
31     /// Look for `SecKeychainItem`s corresponding to generic passwords.
generic_password() -> Self32     pub fn generic_password() -> Self {
33         unsafe { Self(kSecClassGenericPassword) }
34     }
35 
36     /// Look for `SecKeychainItem`s corresponding to internet passwords.
internet_password() -> Self37     pub fn internet_password() -> Self {
38         unsafe { Self(kSecClassInternetPassword) }
39     }
40 
41     /// Look for `SecCertificate`s.
certificate() -> Self42     pub fn certificate() -> Self {
43         unsafe { Self(kSecClassCertificate) }
44     }
45 
46     /// Look for `SecKey`s.
key() -> Self47     pub fn key() -> Self {
48         unsafe { Self(kSecClassKey) }
49     }
50 
51     /// Look for `SecIdentity`s.
identity() -> Self52     pub fn identity() -> Self {
53         unsafe { Self(kSecClassIdentity) }
54     }
55 
to_value(self) -> CFType56     fn to_value(self) -> CFType {
57         unsafe { CFType::wrap_under_get_rule(self.0 as *const _) }
58     }
59 }
60 
61 /// A builder type to search for items in keychains.
62 #[derive(Default)]
63 pub struct ItemSearchOptions {
64     #[cfg(target_os = "macos")]
65     keychains: Option<CFArray<SecKeychain>>,
66     #[cfg(not(target_os = "macos"))]
67     keychains: Option<CFArray<CFType>>,
68     class: Option<ItemClass>,
69     load_refs: bool,
70     load_attributes: bool,
71     load_data: bool,
72     limit: Option<i64>,
73     label: Option<CFString>,
74 }
75 
76 #[cfg(target_os = "macos")]
77 impl crate::ItemSearchOptionsInternals for ItemSearchOptions {
keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self78     fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
79         self.keychains = Some(CFArray::from_CFTypes(keychains));
80         self
81     }
82 }
83 
84 impl ItemSearchOptions {
85     /// Creates a new builder with default options.
new() -> Self86     pub fn new() -> Self {
87         Self::default()
88     }
89 
90     /// Search only for items of the specified class.
class(&mut self, class: ItemClass) -> &mut Self91     pub fn class(&mut self, class: ItemClass) -> &mut Self {
92         self.class = Some(class);
93         self
94     }
95 
96     /// Deprecated.
97     ///
98     /// Replaced by `os::macos::item::ItemSearchOptionsExt::keychains`.
99     #[cfg(target_os = "macos")]
100     #[deprecated(note = "Replaced by `os::macos::item::ItemSearchOptionsExt::keychains`")]
keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self101     pub fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self {
102         self.keychains = Some(CFArray::from_CFTypes(keychains));
103         self
104     }
105 
106     /// Load Security Framework objects (`SecCertificate`, `SecKey`, etc) for
107     /// the results.
load_refs(&mut self, load_refs: bool) -> &mut Self108     pub fn load_refs(&mut self, load_refs: bool) -> &mut Self {
109         self.load_refs = load_refs;
110         self
111     }
112 
113     /// Load Security Framework object attributes for
114     /// the results.
load_attributes(&mut self, load_attributes: bool) -> &mut Self115     pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self {
116         self.load_attributes = load_attributes;
117         self
118     }
119 
120     /// Load Security Framework objects data for
121     /// the results.
load_data(&mut self, load_data: bool) -> &mut Self122     pub fn load_data(&mut self, load_data: bool) -> &mut Self {
123         self.load_data = load_data;
124         self
125     }
126 
127     /// Limit the number of search results.
128     ///
129     /// If this is not called, the default limit is 1.
limit(&mut self, limit: i64) -> &mut Self130     pub fn limit(&mut self, limit: i64) -> &mut Self {
131         self.limit = Some(limit);
132         self
133     }
134 
135     /// Search for an item with the given label.
label(&mut self, label: &str) -> &mut Self136     pub fn label(&mut self, label: &str) -> &mut Self {
137         self.label = Some(CFString::new(label));
138         self
139     }
140 
141     /// Search for objects.
search(&self) -> Result<Vec<SearchResult>>142     pub fn search(&self) -> Result<Vec<SearchResult>> {
143         unsafe {
144             let mut params = vec![];
145 
146             if let Some(ref keychains) = self.keychains {
147                 params.push((
148                     CFString::wrap_under_get_rule(kSecMatchSearchList),
149                     keychains.as_CFType(),
150                 ));
151             }
152 
153             if let Some(class) = self.class {
154                 params.push((CFString::wrap_under_get_rule(kSecClass), class.to_value()));
155             }
156 
157             if self.load_refs {
158                 params.push((
159                     CFString::wrap_under_get_rule(kSecReturnRef),
160                     CFBoolean::true_value().as_CFType(),
161                 ));
162             }
163 
164             if self.load_attributes {
165                 params.push((
166                     CFString::wrap_under_get_rule(kSecReturnAttributes),
167                     CFBoolean::true_value().as_CFType(),
168                 ));
169             }
170 
171             if self.load_data {
172                 params.push((
173                     CFString::wrap_under_get_rule(kSecReturnData),
174                     CFBoolean::true_value().as_CFType(),
175                 ));
176             }
177 
178             if let Some(limit) = self.limit {
179                 params.push((
180                     CFString::wrap_under_get_rule(kSecMatchLimit),
181                     CFNumber::from(limit).as_CFType(),
182                 ));
183             }
184 
185             if let Some(ref label) = self.label {
186                 params.push((
187                     CFString::wrap_under_get_rule(kSecAttrLabel),
188                     label.as_CFType(),
189                 ));
190             }
191 
192             let params = CFDictionary::from_CFType_pairs(&params);
193 
194             let mut ret = ptr::null();
195             cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
196             let type_id = CFGetTypeID(ret);
197 
198             let mut items = vec![];
199 
200             if type_id == CFArray::<CFType>::type_id() {
201                 let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _);
202                 for item in array.iter() {
203                     items.push(get_item(item.as_CFTypeRef()));
204                 }
205             } else {
206                 items.push(get_item(ret));
207                 // This is a bit janky, but get_item uses wrap_under_get_rule
208                 // which bumps the refcount but we want create semantics
209                 CFRelease(ret);
210             }
211 
212             Ok(items)
213         }
214     }
215 }
216 
217 #[cfg(target_os = "macos")]
get_item(item: CFTypeRef) -> SearchResult218 unsafe fn get_item(item: CFTypeRef) -> SearchResult {
219     use crate::os::macos::keychain_item::SecKeychainItem;
220 
221     let type_id = CFGetTypeID(item);
222 
223     if type_id == CFData::type_id() {
224         let data = CFData::wrap_under_get_rule(item as *mut _);
225         let mut buf = Vec::new();
226         buf.extend_from_slice(data.bytes());
227         return SearchResult::Data(buf);
228     }
229 
230     if type_id == CFDictionary::<*const u8, *const u8>::type_id() {
231         return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _));
232     }
233 
234     let reference = if type_id == SecCertificate::type_id() {
235         Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
236     } else if type_id == SecKey::type_id() {
237         Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
238     } else if type_id == SecIdentity::type_id() {
239         Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
240     } else if type_id == SecKeychainItem::type_id() {
241         Reference::KeychainItem(SecKeychainItem::wrap_under_get_rule(item as *mut _))
242     } else {
243         panic!("Got bad type from SecItemCopyMatching: {}", type_id);
244     };
245 
246     SearchResult::Ref(reference)
247 }
248 
249 #[cfg(not(target_os = "macos"))]
get_item(item: CFTypeRef) -> SearchResult250 unsafe fn get_item(item: CFTypeRef) -> SearchResult {
251     let type_id = CFGetTypeID(item);
252 
253     let reference = if type_id == SecCertificate::type_id() {
254         Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _))
255     } else if type_id == SecKey::type_id() {
256         Reference::Key(SecKey::wrap_under_get_rule(item as *mut _))
257     } else if type_id == SecIdentity::type_id() {
258         Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _))
259     } else {
260         panic!("Got bad type from SecItemCopyMatching: {}", type_id);
261     };
262 
263     SearchResult::Ref(reference)
264 }
265 
266 /// An enum including all objects which can be found by `ItemSearchOptions`.
267 #[derive(Debug)]
268 pub enum Reference {
269     /// A `SecIdentity`.
270     Identity(SecIdentity),
271     /// A `SecCertificate`.
272     Certificate(SecCertificate),
273     /// A `SecKey`.
274     Key(SecKey),
275     /// A `SecKeychainItem`.
276     ///
277     /// Only defined on OSX
278     #[cfg(target_os = "macos")]
279     KeychainItem(crate::os::macos::keychain_item::SecKeychainItem),
280     #[doc(hidden)]
281     __NonExhaustive,
282 }
283 
284 /// An individual search result.
285 pub enum SearchResult {
286     /// A reference to the Security Framework object, if asked for.
287     Ref(Reference),
288     /// A dictionary of data about the Security Framework object, if asked for.
289     Dict(CFDictionary),
290     /// The Security Framework object as bytes, if asked for.
291     Data(Vec<u8>),
292     /// An unknown representation of the Security Framework object.
293     Other,
294 }
295 
296 impl fmt::Debug for SearchResult {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result297     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
298         match *self {
299             Self::Ref(ref reference) => fmt
300                 .debug_struct("SearchResult::Ref")
301                 .field("reference", reference)
302                 .finish(),
303             Self::Data(ref buf) => fmt
304                 .debug_struct("SearchResult::Data")
305                 .field("data", buf)
306                 .finish(),
307             Self::Dict(_) => {
308                 let mut debug = fmt.debug_struct("SearchResult::Dict");
309                 for (k, v) in self.simplify_dict().unwrap() {
310                     debug.field(&k, &v);
311                 }
312                 debug.finish()
313             }
314             Self::Other => write!(fmt, "SearchResult::Other"),
315         }
316     }
317 }
318 
319 impl SearchResult {
320     /// If the search result is a `CFDict`, simplify that to a
321     /// `HashMap<String, String>`. This transformation isn't
322     /// comprehensive, it only supports CFString, CFDate, and CFData
323     /// value types.
simplify_dict(&self) -> Option<HashMap<String, String>>324     pub fn simplify_dict(&self) -> Option<HashMap<String, String>> {
325         match *self {
326             Self::Dict(ref d) => unsafe {
327                 let mut retmap = HashMap::new();
328                 let (keys, values) = d.get_keys_and_values();
329                 for (k, v) in keys.iter().zip(values.iter()) {
330                     let keycfstr = CFString::wrap_under_get_rule(*k as *const _);
331                     let val: String = match CFGetTypeID(*v) {
332                         cfstring if cfstring == CFString::type_id() => {
333                             format!("{}", CFString::wrap_under_get_rule(*v as *const _))
334                         }
335                         cfdata if cfdata == CFData::type_id() => {
336                             let buf = CFData::wrap_under_get_rule(*v as *const _);
337                             let mut vec = Vec::new();
338                             vec.extend_from_slice(buf.bytes());
339                             format!("{}", String::from_utf8_lossy(&vec))
340                         }
341                         cfdate if cfdate == CFDate::type_id() => format!(
342                             "{}",
343                             CFString::wrap_under_create_rule(CFCopyDescription(*v))
344                         ),
345                         _ => String::from("unknown"),
346                     };
347                     retmap.insert(format!("{}", keycfstr), val);
348                 }
349                 Some(retmap)
350             },
351             _ => None,
352         }
353     }
354 }
355 
356 #[cfg(test)]
357 mod test {
358     use super::*;
359 
360     #[test]
find_nothing()361     fn find_nothing() {
362         assert!(ItemSearchOptions::new().search().is_err());
363     }
364 
365     #[test]
limit_two()366     fn limit_two() {
367         let results = ItemSearchOptions::new()
368             .class(ItemClass::certificate())
369             .limit(2)
370             .search()
371             .unwrap();
372         assert_eq!(results.len(), 2);
373     }
374 }
375