OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include <stddef.h> | 5 #include <stddef.h> |
6 | 6 |
7 #include "base/logging.h" | 7 #include "base/logging.h" |
8 #include "base/macros.h" | 8 #include "base/macros.h" |
9 #include "base/time/time.h" | 9 #include "base/time/time.h" |
10 #include "crypto/mock_apple_keychain.h" | 10 #include "crypto/mock_apple_keychain.h" |
11 | 11 |
12 namespace crypto { | 12 namespace crypto { |
13 | 13 |
14 // static | |
15 const SecKeychainSearchRef MockAppleKeychain::kDummySearchRef = | |
16 reinterpret_cast<SecKeychainSearchRef>(1000); | |
17 | |
18 MockAppleKeychain::MockAppleKeychain() | 14 MockAppleKeychain::MockAppleKeychain() |
19 : locked_(false), | 15 : find_generic_result_(noErr), |
20 next_item_key_(0), | |
21 search_copy_count_(0), | |
22 keychain_item_copy_count_(0), | |
23 attribute_data_copy_count_(0), | |
24 find_generic_result_(noErr), | |
25 called_add_generic_(false), | 16 called_add_generic_(false), |
26 password_data_count_(0) {} | 17 password_data_count_(0) {} |
27 | 18 |
28 void MockAppleKeychain::InitializeKeychainData(MockKeychainItemType key) const { | 19 MockAppleKeychain::~MockAppleKeychain() {} |
29 UInt32 tags[] = { kSecAccountItemAttr, | |
30 kSecServerItemAttr, | |
31 kSecPortItemAttr, | |
32 kSecPathItemAttr, | |
33 kSecProtocolItemAttr, | |
34 kSecAuthenticationTypeItemAttr, | |
35 kSecSecurityDomainItemAttr, | |
36 kSecCreationDateItemAttr, | |
37 kSecNegativeItemAttr, | |
38 kSecCreatorItemAttr }; | |
39 keychain_attr_list_[key] = SecKeychainAttributeList(); | |
40 keychain_data_[key] = KeychainPasswordData(); | |
41 keychain_attr_list_[key].count = arraysize(tags); | |
42 keychain_attr_list_[key].attr = static_cast<SecKeychainAttribute*>( | |
43 calloc(keychain_attr_list_[key].count, sizeof(SecKeychainAttribute))); | |
44 for (unsigned int i = 0; i < keychain_attr_list_[key].count; ++i) { | |
45 keychain_attr_list_[key].attr[i].tag = tags[i]; | |
46 size_t data_size = 0; | |
47 switch (tags[i]) { | |
48 case kSecPortItemAttr: | |
49 data_size = sizeof(UInt32); | |
50 break; | |
51 case kSecProtocolItemAttr: | |
52 data_size = sizeof(SecProtocolType); | |
53 break; | |
54 case kSecAuthenticationTypeItemAttr: | |
55 data_size = sizeof(SecAuthenticationType); | |
56 break; | |
57 case kSecNegativeItemAttr: | |
58 data_size = sizeof(Boolean); | |
59 break; | |
60 case kSecCreatorItemAttr: | |
61 data_size = sizeof(OSType); | |
62 break; | |
63 } | |
64 if (data_size > 0) { | |
65 keychain_attr_list_[key].attr[i].length = data_size; | |
66 keychain_attr_list_[key].attr[i].data = calloc(1, data_size); | |
67 } | |
68 } | |
69 } | |
70 | 20 |
71 MockAppleKeychain::~MockAppleKeychain() { | 21 OSStatus MockAppleKeychain::ItemDelete(SecKeychainItemRef itemRef) const { |
72 for (MockKeychainAttributesMap::iterator it = keychain_attr_list_.begin(); | |
73 it != keychain_attr_list_.end(); | |
74 ++it) { | |
75 for (unsigned int i = 0; i < it->second.count; ++i) { | |
76 if (it->second.attr[i].data) | |
77 free(it->second.attr[i].data); | |
78 } | |
79 free(it->second.attr); | |
80 if (keychain_data_[it->first].data) | |
81 free(keychain_data_[it->first].data); | |
82 } | |
83 keychain_attr_list_.clear(); | |
84 keychain_data_.clear(); | |
85 } | |
86 | |
87 SecKeychainAttribute* MockAppleKeychain::AttributeWithTag( | |
88 const SecKeychainAttributeList& attribute_list, | |
89 UInt32 tag) { | |
90 int attribute_index = -1; | |
91 for (unsigned int i = 0; i < attribute_list.count; ++i) { | |
92 if (attribute_list.attr[i].tag == tag) { | |
93 attribute_index = i; | |
94 break; | |
95 } | |
96 } | |
97 if (attribute_index == -1) { | |
98 NOTREACHED() << "Unsupported attribute: " << tag; | |
99 return NULL; | |
100 } | |
101 return &(attribute_list.attr[attribute_index]); | |
102 } | |
103 | |
104 void MockAppleKeychain::SetTestDataBytes(MockKeychainItemType item, | |
105 UInt32 tag, | |
106 const void* data, | |
107 size_t length) { | |
108 SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | |
109 tag); | |
110 attribute->length = length; | |
111 if (length > 0) { | |
112 if (attribute->data) | |
113 free(attribute->data); | |
114 attribute->data = malloc(length); | |
115 CHECK(attribute->data); | |
116 memcpy(attribute->data, data, length); | |
117 } else { | |
118 attribute->data = NULL; | |
119 } | |
120 } | |
121 | |
122 void MockAppleKeychain::SetTestDataString(MockKeychainItemType item, | |
123 UInt32 tag, | |
124 const char* value) { | |
125 SetTestDataBytes(item, tag, value, value ? strlen(value) : 0); | |
126 } | |
127 | |
128 void MockAppleKeychain::SetTestDataPort(MockKeychainItemType item, | |
129 UInt32 value) { | |
130 SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | |
131 kSecPortItemAttr); | |
132 UInt32* data = static_cast<UInt32*>(attribute->data); | |
133 *data = value; | |
134 } | |
135 | |
136 void MockAppleKeychain::SetTestDataProtocol(MockKeychainItemType item, | |
137 SecProtocolType value) { | |
138 SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | |
139 kSecProtocolItemAttr); | |
140 SecProtocolType* data = static_cast<SecProtocolType*>(attribute->data); | |
141 *data = value; | |
142 } | |
143 | |
144 void MockAppleKeychain::SetTestDataAuthType(MockKeychainItemType item, | |
145 SecAuthenticationType value) { | |
146 SecKeychainAttribute* attribute = AttributeWithTag( | |
147 keychain_attr_list_[item], kSecAuthenticationTypeItemAttr); | |
148 SecAuthenticationType* data = static_cast<SecAuthenticationType*>( | |
149 attribute->data); | |
150 *data = value; | |
151 } | |
152 | |
153 void MockAppleKeychain::SetTestDataNegativeItem(MockKeychainItemType item, | |
154 Boolean value) { | |
155 SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | |
156 kSecNegativeItemAttr); | |
157 Boolean* data = static_cast<Boolean*>(attribute->data); | |
158 *data = value; | |
159 } | |
160 | |
161 void MockAppleKeychain::SetTestDataCreator(MockKeychainItemType item, | |
162 OSType value) { | |
163 SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | |
164 kSecCreatorItemAttr); | |
165 OSType* data = static_cast<OSType*>(attribute->data); | |
166 *data = value; | |
167 } | |
168 | |
169 void MockAppleKeychain::SetTestDataPasswordBytes(MockKeychainItemType item, | |
170 const void* data, | |
171 size_t length) { | |
172 keychain_data_[item].length = length; | |
173 if (length > 0) { | |
174 if (keychain_data_[item].data) | |
175 free(keychain_data_[item].data); | |
176 keychain_data_[item].data = malloc(length); | |
177 memcpy(keychain_data_[item].data, data, length); | |
178 } else { | |
179 keychain_data_[item].data = NULL; | |
180 } | |
181 } | |
182 | |
183 void MockAppleKeychain::SetTestDataPasswordString(MockKeychainItemType item, | |
184 const char* value) { | |
185 SetTestDataPasswordBytes(item, value, value ? strlen(value) : 0); | |
186 } | |
187 | |
188 OSStatus MockAppleKeychain::ItemCopyAttributesAndData( | |
189 SecKeychainItemRef itemRef, | |
190 SecKeychainAttributeInfo* info, | |
191 SecItemClass* itemClass, | |
192 SecKeychainAttributeList** attrList, | |
193 UInt32* length, | |
194 void** outData) const { | |
195 DCHECK(itemRef); | |
196 MockKeychainItemType key = | |
197 reinterpret_cast<MockKeychainItemType>(itemRef) - 1; | |
198 if (keychain_attr_list_.find(key) == keychain_attr_list_.end()) | |
199 return errSecInvalidItemRef; | |
200 | |
201 DCHECK(!itemClass); // itemClass not implemented in the Mock. | |
202 if (locked_ && outData) | |
203 return errSecAuthFailed; | |
204 | |
205 if (attrList) | |
206 *attrList = &(keychain_attr_list_[key]); | |
207 if (outData) { | |
208 *outData = keychain_data_[key].data; | |
209 DCHECK(length); | |
210 *length = keychain_data_[key].length; | |
211 } | |
212 | |
213 ++attribute_data_copy_count_; | |
214 return noErr; | 22 return noErr; |
215 } | 23 } |
216 | 24 |
217 OSStatus MockAppleKeychain::ItemModifyAttributesAndData( | |
218 SecKeychainItemRef itemRef, | |
219 const SecKeychainAttributeList* attrList, | |
220 UInt32 length, | |
221 const void* data) const { | |
222 DCHECK(itemRef); | |
223 if (locked_) | |
224 return errSecAuthFailed; | |
225 const char* fail_trigger = "fail_me"; | |
226 if (length == strlen(fail_trigger) && | |
227 memcmp(data, fail_trigger, length) == 0) { | |
228 return errSecAuthFailed; | |
229 } | |
230 | |
231 MockKeychainItemType key = | |
232 reinterpret_cast<MockKeychainItemType>(itemRef) - 1; | |
233 if (keychain_attr_list_.find(key) == keychain_attr_list_.end()) | |
234 return errSecInvalidItemRef; | |
235 | |
236 MockAppleKeychain* mutable_this = const_cast<MockAppleKeychain*>(this); | |
237 if (attrList) { | |
238 for (UInt32 change_attr = 0; change_attr < attrList->count; ++change_attr) { | |
239 if (attrList->attr[change_attr].tag == kSecCreatorItemAttr) { | |
240 void* data = attrList->attr[change_attr].data; | |
241 mutable_this->SetTestDataCreator(key, *(static_cast<OSType*>(data))); | |
242 } else { | |
243 NOTIMPLEMENTED(); | |
244 } | |
245 } | |
246 } | |
247 if (data) | |
248 mutable_this->SetTestDataPasswordBytes(key, data, length); | |
249 return noErr; | |
250 } | |
251 | |
252 OSStatus MockAppleKeychain::ItemFreeAttributesAndData( | |
253 SecKeychainAttributeList* attrList, | |
254 void* data) const { | |
255 --attribute_data_copy_count_; | |
256 return noErr; | |
257 } | |
258 | |
259 OSStatus MockAppleKeychain::ItemDelete(SecKeychainItemRef itemRef) const { | |
260 if (locked_) | |
261 return errSecAuthFailed; | |
262 MockKeychainItemType key = | |
263 reinterpret_cast<MockKeychainItemType>(itemRef) - 1; | |
264 | |
265 for (unsigned int i = 0; i < keychain_attr_list_[key].count; ++i) { | |
266 if (keychain_attr_list_[key].attr[i].data) | |
267 free(keychain_attr_list_[key].attr[i].data); | |
268 } | |
269 free(keychain_attr_list_[key].attr); | |
270 if (keychain_data_[key].data) | |
271 free(keychain_data_[key].data); | |
272 | |
273 keychain_attr_list_.erase(key); | |
274 keychain_data_.erase(key); | |
275 added_via_api_.erase(key); | |
276 return noErr; | |
277 } | |
278 | |
279 OSStatus MockAppleKeychain::SearchCreateFromAttributes( | |
280 CFTypeRef keychainOrArray, | |
281 SecItemClass itemClass, | |
282 const SecKeychainAttributeList* attrList, | |
283 SecKeychainSearchRef* searchRef) const { | |
284 // Figure out which of our mock items matches, and set up the array we'll use | |
285 // to generate results out of SearchCopyNext. | |
286 remaining_search_results_.clear(); | |
287 for (MockKeychainAttributesMap::const_iterator it = | |
288 keychain_attr_list_.begin(); | |
289 it != keychain_attr_list_.end(); | |
290 ++it) { | |
291 bool mock_item_matches = true; | |
292 for (UInt32 search_attr = 0; search_attr < attrList->count; ++search_attr) { | |
293 SecKeychainAttribute* mock_attribute = | |
294 AttributeWithTag(it->second, attrList->attr[search_attr].tag); | |
295 if (mock_attribute->length != attrList->attr[search_attr].length || | |
296 memcmp(mock_attribute->data, attrList->attr[search_attr].data, | |
297 attrList->attr[search_attr].length) != 0) { | |
298 mock_item_matches = false; | |
299 break; | |
300 } | |
301 } | |
302 if (mock_item_matches) | |
303 remaining_search_results_.push_back(it->first); | |
304 } | |
305 | |
306 DCHECK(searchRef); | |
307 *searchRef = kDummySearchRef; | |
308 ++search_copy_count_; | |
309 return noErr; | |
310 } | |
311 | |
312 bool MockAppleKeychain::AlreadyContainsInternetPassword( | |
313 UInt32 serverNameLength, | |
314 const char* serverName, | |
315 UInt32 securityDomainLength, | |
316 const char* securityDomain, | |
317 UInt32 accountNameLength, | |
318 const char* accountName, | |
319 UInt32 pathLength, | |
320 const char* path, | |
321 UInt16 port, | |
322 SecProtocolType protocol, | |
323 SecAuthenticationType authenticationType) const { | |
324 for (MockKeychainAttributesMap::const_iterator it = | |
325 keychain_attr_list_.begin(); | |
326 it != keychain_attr_list_.end(); | |
327 ++it) { | |
328 SecKeychainAttribute* attribute; | |
329 attribute = AttributeWithTag(it->second, kSecServerItemAttr); | |
330 if ((attribute->length != serverNameLength) || | |
331 (attribute->data == NULL && *serverName != '\0') || | |
332 (attribute->data != NULL && *serverName == '\0') || | |
333 strncmp(serverName, | |
334 (const char*) attribute->data, | |
335 serverNameLength) != 0) { | |
336 continue; | |
337 } | |
338 attribute = AttributeWithTag(it->second, kSecSecurityDomainItemAttr); | |
339 if ((attribute->length != securityDomainLength) || | |
340 (attribute->data == NULL && *securityDomain != '\0') || | |
341 (attribute->data != NULL && *securityDomain == '\0') || | |
342 strncmp(securityDomain, | |
343 (const char*) attribute->data, | |
344 securityDomainLength) != 0) { | |
345 continue; | |
346 } | |
347 attribute = AttributeWithTag(it->second, kSecAccountItemAttr); | |
348 if ((attribute->length != accountNameLength) || | |
349 (attribute->data == NULL && *accountName != '\0') || | |
350 (attribute->data != NULL && *accountName == '\0') || | |
351 strncmp(accountName, | |
352 (const char*) attribute->data, | |
353 accountNameLength) != 0) { | |
354 continue; | |
355 } | |
356 attribute = AttributeWithTag(it->second, kSecPathItemAttr); | |
357 if ((attribute->length != pathLength) || | |
358 (attribute->data == NULL && *path != '\0') || | |
359 (attribute->data != NULL && *path == '\0') || | |
360 strncmp(path, | |
361 (const char*) attribute->data, | |
362 pathLength) != 0) { | |
363 continue; | |
364 } | |
365 attribute = AttributeWithTag(it->second, kSecPortItemAttr); | |
366 if ((attribute->data == NULL) || | |
367 (port != *(static_cast<UInt32*>(attribute->data)))) { | |
368 continue; | |
369 } | |
370 attribute = AttributeWithTag(it->second, kSecProtocolItemAttr); | |
371 if ((attribute->data == NULL) || | |
372 (protocol != *(static_cast<SecProtocolType*>(attribute->data)))) { | |
373 continue; | |
374 } | |
375 attribute = AttributeWithTag(it->second, kSecAuthenticationTypeItemAttr); | |
376 if ((attribute->data == NULL) || | |
377 (authenticationType != | |
378 *(static_cast<SecAuthenticationType*>(attribute->data)))) { | |
379 continue; | |
380 } | |
381 // The keychain already has this item, since all fields other than the | |
382 // password match. | |
383 return true; | |
384 } | |
385 return false; | |
386 } | |
387 | |
388 OSStatus MockAppleKeychain::AddInternetPassword( | |
389 SecKeychainRef keychain, | |
390 UInt32 serverNameLength, | |
391 const char* serverName, | |
392 UInt32 securityDomainLength, | |
393 const char* securityDomain, | |
394 UInt32 accountNameLength, | |
395 const char* accountName, | |
396 UInt32 pathLength, | |
397 const char* path, | |
398 UInt16 port, | |
399 SecProtocolType protocol, | |
400 SecAuthenticationType authenticationType, | |
401 UInt32 passwordLength, | |
402 const void* passwordData, | |
403 SecKeychainItemRef* itemRef) const { | |
404 if (locked_) | |
405 return errSecAuthFailed; | |
406 | |
407 // Check for the magic duplicate item trigger. | |
408 if (strcmp(serverName, "some.domain.com") == 0) | |
409 return errSecDuplicateItem; | |
410 | |
411 // If the account already exists in the keychain, we don't add it. | |
412 if (AlreadyContainsInternetPassword(serverNameLength, serverName, | |
413 securityDomainLength, securityDomain, | |
414 accountNameLength, accountName, | |
415 pathLength, path, | |
416 port, protocol, | |
417 authenticationType)) { | |
418 return errSecDuplicateItem; | |
419 } | |
420 | |
421 // Pick the next unused slot. | |
422 MockKeychainItemType key = next_item_key_++; | |
423 | |
424 // Initialize keychain data storage at the target location. | |
425 InitializeKeychainData(key); | |
426 | |
427 MockAppleKeychain* mutable_this = const_cast<MockAppleKeychain*>(this); | |
428 mutable_this->SetTestDataBytes(key, kSecServerItemAttr, serverName, | |
429 serverNameLength); | |
430 mutable_this->SetTestDataBytes(key, kSecSecurityDomainItemAttr, | |
431 securityDomain, securityDomainLength); | |
432 mutable_this->SetTestDataBytes(key, kSecAccountItemAttr, accountName, | |
433 accountNameLength); | |
434 mutable_this->SetTestDataBytes(key, kSecPathItemAttr, path, pathLength); | |
435 mutable_this->SetTestDataPort(key, port); | |
436 mutable_this->SetTestDataProtocol(key, protocol); | |
437 mutable_this->SetTestDataAuthType(key, authenticationType); | |
438 mutable_this->SetTestDataPasswordBytes(key, passwordData, | |
439 passwordLength); | |
440 base::Time::Exploded exploded_time; | |
441 base::Time::Now().UTCExplode(&exploded_time); | |
442 char time_string[128]; | |
443 snprintf(time_string, sizeof(time_string), "%04d%02d%02d%02d%02d%02dZ", | |
444 exploded_time.year, exploded_time.month, exploded_time.day_of_month, | |
445 exploded_time.hour, exploded_time.minute, exploded_time.second); | |
446 mutable_this->SetTestDataString(key, kSecCreationDateItemAttr, time_string); | |
447 | |
448 added_via_api_.insert(key); | |
449 | |
450 if (itemRef) { | |
451 *itemRef = reinterpret_cast<SecKeychainItemRef>(key + 1); | |
452 ++keychain_item_copy_count_; | |
453 } | |
454 return noErr; | |
455 } | |
456 | |
457 OSStatus MockAppleKeychain::SearchCopyNext(SecKeychainSearchRef searchRef, | |
458 SecKeychainItemRef* itemRef) const { | |
459 if (remaining_search_results_.empty()) | |
460 return errSecItemNotFound; | |
461 MockKeychainItemType key = remaining_search_results_.front(); | |
462 remaining_search_results_.erase(remaining_search_results_.begin()); | |
463 *itemRef = reinterpret_cast<SecKeychainItemRef>(key + 1); | |
464 ++keychain_item_copy_count_; | |
465 return noErr; | |
466 } | |
467 | |
468 void MockAppleKeychain::Free(CFTypeRef ref) const { | |
469 if (!ref) | |
470 return; | |
471 | |
472 if (ref == kDummySearchRef) { | |
473 --search_copy_count_; | |
474 } else { | |
475 --keychain_item_copy_count_; | |
476 } | |
477 } | |
478 | |
479 int MockAppleKeychain::UnfreedSearchCount() const { | |
480 return search_copy_count_; | |
481 } | |
482 | |
483 int MockAppleKeychain::UnfreedKeychainItemCount() const { | |
484 return keychain_item_copy_count_; | |
485 } | |
486 | |
487 int MockAppleKeychain::UnfreedAttributeDataCount() const { | |
488 return attribute_data_copy_count_; | |
489 } | |
490 | |
491 bool MockAppleKeychain::CreatorCodesSetForAddedItems() const { | |
492 for (std::set<MockKeychainItemType>::const_iterator | |
493 i = added_via_api_.begin(); | |
494 i != added_via_api_.end(); | |
495 ++i) { | |
496 SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[*i], | |
497 kSecCreatorItemAttr); | |
498 OSType* data = static_cast<OSType*>(attribute->data); | |
499 if (*data == 0) | |
500 return false; | |
501 } | |
502 return true; | |
503 } | |
504 | |
505 void MockAppleKeychain::AddTestItem(const KeychainTestData& item_data) { | |
506 MockKeychainItemType key = next_item_key_++; | |
507 | |
508 InitializeKeychainData(key); | |
509 SetTestDataAuthType(key, item_data.auth_type); | |
510 SetTestDataString(key, kSecServerItemAttr, item_data.server); | |
511 SetTestDataProtocol(key, item_data.protocol); | |
512 SetTestDataString(key, kSecPathItemAttr, item_data.path); | |
513 SetTestDataPort(key, item_data.port); | |
514 SetTestDataString(key, kSecSecurityDomainItemAttr, | |
515 item_data.security_domain); | |
516 SetTestDataString(key, kSecCreationDateItemAttr, item_data.creation_date); | |
517 SetTestDataString(key, kSecAccountItemAttr, item_data.username); | |
518 SetTestDataPasswordString(key, item_data.password); | |
519 SetTestDataNegativeItem(key, item_data.negative_item); | |
520 } | |
521 | |
522 } // namespace crypto | 25 } // namespace crypto |
OLD | NEW |