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