|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/time.h" | 
|  | #include "crypto/mock_apple_keychain.h" | 
|  |  | 
|  | namespace crypto { | 
|  |  | 
|  | // static | 
|  | const SecKeychainSearchRef MockAppleKeychain::kDummySearchRef = | 
|  | reinterpret_cast<SecKeychainSearchRef>(1000); | 
|  |  | 
|  | MockAppleKeychain::MockAppleKeychain() | 
|  | : next_item_key_(0), | 
|  | search_copy_count_(0), | 
|  | keychain_item_copy_count_(0), | 
|  | attribute_data_copy_count_(0), | 
|  | find_generic_result_(noErr), | 
|  | called_add_generic_(false), | 
|  | password_data_count_(0) {} | 
|  |  | 
|  | void MockAppleKeychain::InitializeKeychainData(MockKeychainItemType key) const { | 
|  | UInt32 tags[] = { kSecAccountItemAttr, | 
|  | kSecServerItemAttr, | 
|  | kSecPortItemAttr, | 
|  | kSecPathItemAttr, | 
|  | kSecProtocolItemAttr, | 
|  | kSecAuthenticationTypeItemAttr, | 
|  | kSecSecurityDomainItemAttr, | 
|  | kSecCreationDateItemAttr, | 
|  | kSecNegativeItemAttr, | 
|  | kSecCreatorItemAttr }; | 
|  | keychain_attr_list_[key] = SecKeychainAttributeList(); | 
|  | keychain_data_[key] = KeychainPasswordData(); | 
|  | keychain_attr_list_[key].count = arraysize(tags); | 
|  | keychain_attr_list_[key].attr = static_cast<SecKeychainAttribute*>( | 
|  | calloc(keychain_attr_list_[key].count, sizeof(SecKeychainAttribute))); | 
|  | for (unsigned int i = 0; i < keychain_attr_list_[key].count; ++i) { | 
|  | keychain_attr_list_[key].attr[i].tag = tags[i]; | 
|  | size_t data_size = 0; | 
|  | switch (tags[i]) { | 
|  | case kSecPortItemAttr: | 
|  | data_size = sizeof(UInt32); | 
|  | break; | 
|  | case kSecProtocolItemAttr: | 
|  | data_size = sizeof(SecProtocolType); | 
|  | break; | 
|  | case kSecAuthenticationTypeItemAttr: | 
|  | data_size = sizeof(SecAuthenticationType); | 
|  | break; | 
|  | case kSecNegativeItemAttr: | 
|  | data_size = sizeof(Boolean); | 
|  | break; | 
|  | case kSecCreatorItemAttr: | 
|  | data_size = sizeof(OSType); | 
|  | break; | 
|  | } | 
|  | if (data_size > 0) { | 
|  | keychain_attr_list_[key].attr[i].length = data_size; | 
|  | keychain_attr_list_[key].attr[i].data = calloc(1, data_size); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | MockAppleKeychain::~MockAppleKeychain() { | 
|  | for (MockKeychainAttributesMap::iterator it = keychain_attr_list_.begin(); | 
|  | it != keychain_attr_list_.end(); | 
|  | ++it) { | 
|  | for (unsigned int i = 0; i < it->second.count; ++i) { | 
|  | if (it->second.attr[i].data) | 
|  | free(it->second.attr[i].data); | 
|  | } | 
|  | free(it->second.attr); | 
|  | if (keychain_data_[it->first].data) | 
|  | free(keychain_data_[it->first].data); | 
|  | } | 
|  | keychain_attr_list_.clear(); | 
|  | keychain_data_.clear(); | 
|  | } | 
|  |  | 
|  | SecKeychainAttribute* MockAppleKeychain::AttributeWithTag( | 
|  | const SecKeychainAttributeList& attribute_list, | 
|  | UInt32 tag) { | 
|  | int attribute_index = -1; | 
|  | for (unsigned int i = 0; i < attribute_list.count; ++i) { | 
|  | if (attribute_list.attr[i].tag == tag) { | 
|  | attribute_index = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (attribute_index == -1) { | 
|  | NOTREACHED() << "Unsupported attribute: " << tag; | 
|  | return NULL; | 
|  | } | 
|  | return &(attribute_list.attr[attribute_index]); | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataBytes(MockKeychainItemType item, | 
|  | UInt32 tag, | 
|  | const void* data, | 
|  | size_t length) { | 
|  | SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | 
|  | tag); | 
|  | attribute->length = length; | 
|  | if (length > 0) { | 
|  | if (attribute->data) | 
|  | free(attribute->data); | 
|  | attribute->data = malloc(length); | 
|  | CHECK(attribute->data); | 
|  | memcpy(attribute->data, data, length); | 
|  | } else { | 
|  | attribute->data = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataString(MockKeychainItemType item, | 
|  | UInt32 tag, | 
|  | const char* value) { | 
|  | SetTestDataBytes(item, tag, value, value ? strlen(value) : 0); | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataPort(MockKeychainItemType item, | 
|  | UInt32 value) { | 
|  | SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | 
|  | kSecPortItemAttr); | 
|  | UInt32* data = static_cast<UInt32*>(attribute->data); | 
|  | *data = value; | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataProtocol(MockKeychainItemType item, | 
|  | SecProtocolType value) { | 
|  | SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | 
|  | kSecProtocolItemAttr); | 
|  | SecProtocolType* data = static_cast<SecProtocolType*>(attribute->data); | 
|  | *data = value; | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataAuthType(MockKeychainItemType item, | 
|  | SecAuthenticationType value) { | 
|  | SecKeychainAttribute* attribute = AttributeWithTag( | 
|  | keychain_attr_list_[item], kSecAuthenticationTypeItemAttr); | 
|  | SecAuthenticationType* data = static_cast<SecAuthenticationType*>( | 
|  | attribute->data); | 
|  | *data = value; | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataNegativeItem(MockKeychainItemType item, | 
|  | Boolean value) { | 
|  | SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | 
|  | kSecNegativeItemAttr); | 
|  | Boolean* data = static_cast<Boolean*>(attribute->data); | 
|  | *data = value; | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataCreator(MockKeychainItemType item, | 
|  | OSType value) { | 
|  | SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], | 
|  | kSecCreatorItemAttr); | 
|  | OSType* data = static_cast<OSType*>(attribute->data); | 
|  | *data = value; | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataPasswordBytes(MockKeychainItemType item, | 
|  | const void* data, | 
|  | size_t length) { | 
|  | keychain_data_[item].length = length; | 
|  | if (length > 0) { | 
|  | if (keychain_data_[item].data) | 
|  | free(keychain_data_[item].data); | 
|  | keychain_data_[item].data = malloc(length); | 
|  | memcpy(keychain_data_[item].data, data, length); | 
|  | } else { | 
|  | keychain_data_[item].data = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::SetTestDataPasswordString(MockKeychainItemType item, | 
|  | const char* value) { | 
|  | SetTestDataPasswordBytes(item, value, value ? strlen(value) : 0); | 
|  | } | 
|  |  | 
|  | OSStatus MockAppleKeychain::ItemCopyAttributesAndData( | 
|  | SecKeychainItemRef itemRef, | 
|  | SecKeychainAttributeInfo* info, | 
|  | SecItemClass* itemClass, | 
|  | SecKeychainAttributeList** attrList, | 
|  | UInt32* length, | 
|  | void** outData) const { | 
|  | DCHECK(itemRef); | 
|  | MockKeychainItemType key = | 
|  | reinterpret_cast<MockKeychainItemType>(itemRef) - 1; | 
|  | if (keychain_attr_list_.find(key) == keychain_attr_list_.end()) | 
|  | return errSecInvalidItemRef; | 
|  |  | 
|  | DCHECK(!itemClass);  // itemClass not implemented in the Mock. | 
|  | if (attrList) | 
|  | *attrList  = &(keychain_attr_list_[key]); | 
|  | if (outData) { | 
|  | *outData = keychain_data_[key].data; | 
|  | DCHECK(length); | 
|  | *length = keychain_data_[key].length; | 
|  | } | 
|  |  | 
|  | ++attribute_data_copy_count_; | 
|  | return noErr; | 
|  | } | 
|  |  | 
|  | OSStatus MockAppleKeychain::ItemModifyAttributesAndData( | 
|  | SecKeychainItemRef itemRef, | 
|  | const SecKeychainAttributeList* attrList, | 
|  | UInt32 length, | 
|  | const void* data) const { | 
|  | DCHECK(itemRef); | 
|  | const char* fail_trigger = "fail_me"; | 
|  | if (length == strlen(fail_trigger) && | 
|  | memcmp(data, fail_trigger, length) == 0) { | 
|  | return errSecAuthFailed; | 
|  | } | 
|  |  | 
|  | MockKeychainItemType key = | 
|  | reinterpret_cast<MockKeychainItemType>(itemRef) - 1; | 
|  | if (keychain_attr_list_.find(key) == keychain_attr_list_.end()) | 
|  | return errSecInvalidItemRef; | 
|  |  | 
|  | MockAppleKeychain* mutable_this = const_cast<MockAppleKeychain*>(this); | 
|  | if (attrList) { | 
|  | for (UInt32 change_attr = 0; change_attr < attrList->count; ++change_attr) { | 
|  | if (attrList->attr[change_attr].tag == kSecCreatorItemAttr) { | 
|  | void* data = attrList->attr[change_attr].data; | 
|  | mutable_this->SetTestDataCreator(key, *(static_cast<OSType*>(data))); | 
|  | } else { | 
|  | NOTIMPLEMENTED(); | 
|  | } | 
|  | } | 
|  | } | 
|  | if (data) | 
|  | mutable_this->SetTestDataPasswordBytes(key, data, length); | 
|  | return noErr; | 
|  | } | 
|  |  | 
|  | OSStatus MockAppleKeychain::ItemFreeAttributesAndData( | 
|  | SecKeychainAttributeList* attrList, | 
|  | void* data) const { | 
|  | --attribute_data_copy_count_; | 
|  | return noErr; | 
|  | } | 
|  |  | 
|  | OSStatus MockAppleKeychain::ItemDelete(SecKeychainItemRef itemRef) const { | 
|  | MockKeychainItemType key = | 
|  | reinterpret_cast<MockKeychainItemType>(itemRef) - 1; | 
|  |  | 
|  | for (unsigned int i = 0; i < keychain_attr_list_[key].count; ++i) { | 
|  | if (keychain_attr_list_[key].attr[i].data) | 
|  | free(keychain_attr_list_[key].attr[i].data); | 
|  | } | 
|  | free(keychain_attr_list_[key].attr); | 
|  | if (keychain_data_[key].data) | 
|  | free(keychain_data_[key].data); | 
|  |  | 
|  | keychain_attr_list_.erase(key); | 
|  | keychain_data_.erase(key); | 
|  | added_via_api_.erase(key); | 
|  | return noErr; | 
|  | } | 
|  |  | 
|  | OSStatus MockAppleKeychain::SearchCreateFromAttributes( | 
|  | CFTypeRef keychainOrArray, | 
|  | SecItemClass itemClass, | 
|  | const SecKeychainAttributeList* attrList, | 
|  | SecKeychainSearchRef* searchRef) const { | 
|  | // Figure out which of our mock items matches, and set up the array we'll use | 
|  | // to generate results out of SearchCopyNext. | 
|  | remaining_search_results_.clear(); | 
|  | for (MockKeychainAttributesMap::const_iterator it = | 
|  | keychain_attr_list_.begin(); | 
|  | it != keychain_attr_list_.end(); | 
|  | ++it) { | 
|  | bool mock_item_matches = true; | 
|  | for (UInt32 search_attr = 0; search_attr < attrList->count; ++search_attr) { | 
|  | SecKeychainAttribute* mock_attribute = | 
|  | AttributeWithTag(it->second, attrList->attr[search_attr].tag); | 
|  | if (mock_attribute->length != attrList->attr[search_attr].length || | 
|  | memcmp(mock_attribute->data, attrList->attr[search_attr].data, | 
|  | attrList->attr[search_attr].length) != 0) { | 
|  | mock_item_matches = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (mock_item_matches) | 
|  | remaining_search_results_.push_back(it->first); | 
|  | } | 
|  |  | 
|  | DCHECK(searchRef); | 
|  | *searchRef = kDummySearchRef; | 
|  | ++search_copy_count_; | 
|  | return noErr; | 
|  | } | 
|  |  | 
|  | bool MockAppleKeychain::AlreadyContainsInternetPassword( | 
|  | UInt32 serverNameLength, | 
|  | const char* serverName, | 
|  | UInt32 securityDomainLength, | 
|  | const char* securityDomain, | 
|  | UInt32 accountNameLength, | 
|  | const char* accountName, | 
|  | UInt32 pathLength, | 
|  | const char* path, | 
|  | UInt16 port, | 
|  | SecProtocolType protocol, | 
|  | SecAuthenticationType authenticationType) const { | 
|  | for (MockKeychainAttributesMap::const_iterator it = | 
|  | keychain_attr_list_.begin(); | 
|  | it != keychain_attr_list_.end(); | 
|  | ++it) { | 
|  | SecKeychainAttribute* attribute; | 
|  | attribute = AttributeWithTag(it->second, kSecServerItemAttr); | 
|  | if ((attribute->length != serverNameLength) || | 
|  | (attribute->data == NULL && *serverName != '\0') || | 
|  | (attribute->data != NULL && *serverName == '\0') || | 
|  | strncmp(serverName, | 
|  | (const char*) attribute->data, | 
|  | serverNameLength) != 0) { | 
|  | continue; | 
|  | } | 
|  | attribute = AttributeWithTag(it->second, kSecSecurityDomainItemAttr); | 
|  | if ((attribute->length != securityDomainLength) || | 
|  | (attribute->data == NULL && *securityDomain != '\0') || | 
|  | (attribute->data != NULL && *securityDomain == '\0') || | 
|  | strncmp(securityDomain, | 
|  | (const char*) attribute->data, | 
|  | securityDomainLength) != 0) { | 
|  | continue; | 
|  | } | 
|  | attribute = AttributeWithTag(it->second, kSecAccountItemAttr); | 
|  | if ((attribute->length != accountNameLength) || | 
|  | (attribute->data == NULL && *accountName != '\0') || | 
|  | (attribute->data != NULL && *accountName == '\0') || | 
|  | strncmp(accountName, | 
|  | (const char*) attribute->data, | 
|  | accountNameLength) != 0) { | 
|  | continue; | 
|  | } | 
|  | attribute = AttributeWithTag(it->second, kSecPathItemAttr); | 
|  | if ((attribute->length != pathLength) || | 
|  | (attribute->data == NULL && *path != '\0') || | 
|  | (attribute->data != NULL && *path == '\0') || | 
|  | strncmp(path, | 
|  | (const char*) attribute->data, | 
|  | pathLength) != 0) { | 
|  | continue; | 
|  | } | 
|  | attribute = AttributeWithTag(it->second, kSecPortItemAttr); | 
|  | if ((attribute->data == NULL) || | 
|  | (port != *(static_cast<UInt32*>(attribute->data)))) { | 
|  | continue; | 
|  | } | 
|  | attribute = AttributeWithTag(it->second, kSecProtocolItemAttr); | 
|  | if ((attribute->data == NULL) || | 
|  | (protocol != *(static_cast<SecProtocolType*>(attribute->data)))) { | 
|  | continue; | 
|  | } | 
|  | attribute = AttributeWithTag(it->second, kSecAuthenticationTypeItemAttr); | 
|  | if ((attribute->data == NULL) || | 
|  | (authenticationType != | 
|  | *(static_cast<SecAuthenticationType*>(attribute->data)))) { | 
|  | continue; | 
|  | } | 
|  | // The keychain already has this item, since all fields other than the | 
|  | // password match. | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | OSStatus MockAppleKeychain::AddInternetPassword( | 
|  | SecKeychainRef keychain, | 
|  | UInt32 serverNameLength, | 
|  | const char* serverName, | 
|  | UInt32 securityDomainLength, | 
|  | const char* securityDomain, | 
|  | UInt32 accountNameLength, | 
|  | const char* accountName, | 
|  | UInt32 pathLength, | 
|  | const char* path, | 
|  | UInt16 port, | 
|  | SecProtocolType protocol, | 
|  | SecAuthenticationType authenticationType, | 
|  | UInt32 passwordLength, | 
|  | const void* passwordData, | 
|  | SecKeychainItemRef* itemRef) const { | 
|  |  | 
|  | // Check for the magic duplicate item trigger. | 
|  | if (strcmp(serverName, "some.domain.com") == 0) | 
|  | return errSecDuplicateItem; | 
|  |  | 
|  | // If the account already exists in the keychain, we don't add it. | 
|  | if (AlreadyContainsInternetPassword(serverNameLength, serverName, | 
|  | securityDomainLength, securityDomain, | 
|  | accountNameLength, accountName, | 
|  | pathLength, path, | 
|  | port, protocol, | 
|  | authenticationType)) { | 
|  | return errSecDuplicateItem; | 
|  | } | 
|  |  | 
|  | // Pick the next unused slot. | 
|  | MockKeychainItemType key = next_item_key_++; | 
|  |  | 
|  | // Initialize keychain data storage at the target location. | 
|  | InitializeKeychainData(key); | 
|  |  | 
|  | MockAppleKeychain* mutable_this = const_cast<MockAppleKeychain*>(this); | 
|  | mutable_this->SetTestDataBytes(key, kSecServerItemAttr, serverName, | 
|  | serverNameLength); | 
|  | mutable_this->SetTestDataBytes(key, kSecSecurityDomainItemAttr, | 
|  | securityDomain, securityDomainLength); | 
|  | mutable_this->SetTestDataBytes(key, kSecAccountItemAttr, accountName, | 
|  | accountNameLength); | 
|  | mutable_this->SetTestDataBytes(key, kSecPathItemAttr, path, pathLength); | 
|  | mutable_this->SetTestDataPort(key, port); | 
|  | mutable_this->SetTestDataProtocol(key, protocol); | 
|  | mutable_this->SetTestDataAuthType(key, authenticationType); | 
|  | mutable_this->SetTestDataPasswordBytes(key, passwordData, | 
|  | passwordLength); | 
|  | base::Time::Exploded exploded_time; | 
|  | base::Time::Now().UTCExplode(&exploded_time); | 
|  | char time_string[128]; | 
|  | snprintf(time_string, sizeof(time_string), "%04d%02d%02d%02d%02d%02dZ", | 
|  | exploded_time.year, exploded_time.month, exploded_time.day_of_month, | 
|  | exploded_time.hour, exploded_time.minute, exploded_time.second); | 
|  | mutable_this->SetTestDataString(key, kSecCreationDateItemAttr, time_string); | 
|  |  | 
|  | added_via_api_.insert(key); | 
|  |  | 
|  | if (itemRef) { | 
|  | *itemRef = reinterpret_cast<SecKeychainItemRef>(key + 1); | 
|  | ++keychain_item_copy_count_; | 
|  | } | 
|  | return noErr; | 
|  | } | 
|  |  | 
|  | OSStatus MockAppleKeychain::SearchCopyNext(SecKeychainSearchRef searchRef, | 
|  | SecKeychainItemRef* itemRef) const { | 
|  | if (remaining_search_results_.empty()) | 
|  | return errSecItemNotFound; | 
|  | MockKeychainItemType key = remaining_search_results_.front(); | 
|  | remaining_search_results_.erase(remaining_search_results_.begin()); | 
|  | *itemRef = reinterpret_cast<SecKeychainItemRef>(key + 1); | 
|  | ++keychain_item_copy_count_; | 
|  | return noErr; | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::Free(CFTypeRef ref) const { | 
|  | if (!ref) | 
|  | return; | 
|  |  | 
|  | if (ref == kDummySearchRef) { | 
|  | --search_copy_count_; | 
|  | } else { | 
|  | --keychain_item_copy_count_; | 
|  | } | 
|  | } | 
|  |  | 
|  | int MockAppleKeychain::UnfreedSearchCount() const { | 
|  | return search_copy_count_; | 
|  | } | 
|  |  | 
|  | int MockAppleKeychain::UnfreedKeychainItemCount() const { | 
|  | return keychain_item_copy_count_; | 
|  | } | 
|  |  | 
|  | int MockAppleKeychain::UnfreedAttributeDataCount() const { | 
|  | return attribute_data_copy_count_; | 
|  | } | 
|  |  | 
|  | bool MockAppleKeychain::CreatorCodesSetForAddedItems() const { | 
|  | for (std::set<MockKeychainItemType>::const_iterator | 
|  | i = added_via_api_.begin(); | 
|  | i != added_via_api_.end(); | 
|  | ++i) { | 
|  | SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[*i], | 
|  | kSecCreatorItemAttr); | 
|  | OSType* data = static_cast<OSType*>(attribute->data); | 
|  | if (*data == 0) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void MockAppleKeychain::AddTestItem(const KeychainTestData& item_data) { | 
|  | MockKeychainItemType key = next_item_key_++; | 
|  |  | 
|  | InitializeKeychainData(key); | 
|  | SetTestDataAuthType(key, item_data.auth_type); | 
|  | SetTestDataString(key, kSecServerItemAttr, item_data.server); | 
|  | SetTestDataProtocol(key, item_data.protocol); | 
|  | SetTestDataString(key, kSecPathItemAttr, item_data.path); | 
|  | SetTestDataPort(key, item_data.port); | 
|  | SetTestDataString(key, kSecSecurityDomainItemAttr, | 
|  | item_data.security_domain); | 
|  | SetTestDataString(key, kSecCreationDateItemAttr, item_data.creation_date); | 
|  | SetTestDataString(key, kSecAccountItemAttr, item_data.username); | 
|  | SetTestDataPasswordString(key, item_data.password); | 
|  | SetTestDataNegativeItem(key, item_data.negative_item); | 
|  | } | 
|  |  | 
|  | }  // namespace crypto |