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