// 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/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