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