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