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(¶ms);
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