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