From b9d3d0028bbef6846b017c3ce8514f012921f65f Mon Sep 17 00:00:00 2001 From: "akalin@chromium.org" Date: Sat, 17 Mar 2012 12:14:50 +0000 Subject: Move *keychain_mac* files to crypto/ This is in preparation for moving password_manager/encryptor* to crypto/. This also fixes an ODR violation; mock_keychain_mac.cc was being compiled in two targets which are linked together. BUG=118564 TEST= Review URL: http://codereview.chromium.org/9699112 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@127358 0039d316-1c4b-4281-b951-d872f2087c98 --- crypto/crypto.gyp | 4 + crypto/keychain_mac.cc | 129 +++++++++++ crypto/keychain_mac.h | 100 +++++++++ crypto/mock_keychain_mac.cc | 534 ++++++++++++++++++++++++++++++++++++++++++++ crypto/mock_keychain_mac.h | 226 +++++++++++++++++++ 5 files changed, 993 insertions(+) create mode 100644 crypto/keychain_mac.cc create mode 100644 crypto/keychain_mac.h create mode 100644 crypto/mock_keychain_mac.cc create mode 100644 crypto/mock_keychain_mac.h (limited to 'crypto') diff --git a/crypto/crypto.gyp b/crypto/crypto.gyp index ef7b87b..405ad0b 100644 --- a/crypto/crypto.gyp +++ b/crypto/crypto.gyp @@ -157,8 +157,12 @@ 'hmac_nss.cc', 'hmac_openssl.cc', 'hmac_win.cc', + 'keychain_mac.cc', + 'keychain_mac.h', 'mac_security_services_lock.cc', 'mac_security_services_lock.h', + 'mock_keychain_mac.cc', + 'mock_keychain_mac.h', 'p224_spake.cc', 'p224_spake.h', 'nss_util.cc', diff --git a/crypto/keychain_mac.cc b/crypto/keychain_mac.cc new file mode 100644 index 0000000..4d5715b --- /dev/null +++ b/crypto/keychain_mac.cc @@ -0,0 +1,129 @@ +// 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 "crypto/keychain_mac.h" + +namespace crypto { + +MacKeychain::MacKeychain() {} + +MacKeychain::~MacKeychain() {} + +OSStatus MacKeychain::ItemCopyAttributesAndData( + SecKeychainItemRef itemRef, + SecKeychainAttributeInfo* info, + SecItemClass* itemClass, + SecKeychainAttributeList** attrList, + UInt32* length, + void** outData) const { + return SecKeychainItemCopyAttributesAndData(itemRef, info, itemClass, + attrList, length, outData); +} + +OSStatus MacKeychain::ItemModifyAttributesAndData( + SecKeychainItemRef itemRef, + const SecKeychainAttributeList* attrList, + UInt32 length, + const void* data) const { + return SecKeychainItemModifyAttributesAndData(itemRef, attrList, length, + data); +} + +OSStatus MacKeychain::ItemFreeAttributesAndData( + SecKeychainAttributeList* attrList, + void* data) const { + return SecKeychainItemFreeAttributesAndData(attrList, data); +} + +OSStatus MacKeychain::ItemDelete(SecKeychainItemRef itemRef) const { + return SecKeychainItemDelete(itemRef); +} + +OSStatus MacKeychain::SearchCreateFromAttributes( + CFTypeRef keychainOrArray, + SecItemClass itemClass, + const SecKeychainAttributeList* attrList, + SecKeychainSearchRef* searchRef) const { + return SecKeychainSearchCreateFromAttributes(keychainOrArray, itemClass, + attrList, searchRef); +} + +OSStatus MacKeychain::SearchCopyNext(SecKeychainSearchRef searchRef, + SecKeychainItemRef* itemRef) const { + return SecKeychainSearchCopyNext(searchRef, itemRef); +} + +OSStatus MacKeychain::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 { + return SecKeychainAddInternetPassword(keychain, + serverNameLength, serverName, + securityDomainLength, securityDomain, + accountNameLength, accountName, + pathLength, path, + port, protocol, authenticationType, + passwordLength, passwordData, + itemRef); +} + +OSStatus MacKeychain::FindGenericPassword(CFTypeRef keychainOrArray, + UInt32 serviceNameLength, + const char* serviceName, + UInt32 accountNameLength, + const char* accountName, + UInt32* passwordLength, + void** passwordData, + SecKeychainItemRef* itemRef) const { + return SecKeychainFindGenericPassword(keychainOrArray, + serviceNameLength, + serviceName, + accountNameLength, + accountName, + passwordLength, + passwordData, + itemRef); +} + +OSStatus MacKeychain::ItemFreeContent(SecKeychainAttributeList* attrList, + void* data) const { + return SecKeychainItemFreeContent(attrList, data); +} + +OSStatus MacKeychain::AddGenericPassword(SecKeychainRef keychain, + UInt32 serviceNameLength, + const char* serviceName, + UInt32 accountNameLength, + const char* accountName, + UInt32 passwordLength, + const void* passwordData, + SecKeychainItemRef* itemRef) const { + return SecKeychainAddGenericPassword(keychain, + serviceNameLength, + serviceName, + accountNameLength, + accountName, + passwordLength, + passwordData, + itemRef); +} + +void MacKeychain::Free(CFTypeRef ref) const { + if (ref) + CFRelease(ref); +} + +} // namespace crypto diff --git a/crypto/keychain_mac.h b/crypto/keychain_mac.h new file mode 100644 index 0000000..39ba31c --- /dev/null +++ b/crypto/keychain_mac.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef CRYPTO_KEYCHAIN_MAC_H_ +#define CRYPTO_KEYCHAIN_MAC_H_ +#pragma once + +#include + +#include "base/basictypes.h" + +namespace crypto { + +// Wraps the KeychainServices API in a very thin layer, to allow it to be +// mocked out for testing. + +// See Keychain Services documentation for function documentation, as these call +// through directly to their Keychain Services equivalents (Foo -> +// SecKeychainFoo). The only exception is Free, which should be used for +// anything returned from this class that would normally be freed with +// CFRelease (to aid in testing). +class MacKeychain { + public: + MacKeychain(); + virtual ~MacKeychain(); + + virtual OSStatus ItemCopyAttributesAndData( + SecKeychainItemRef itemRef, + SecKeychainAttributeInfo* info, + SecItemClass* itemClass, + SecKeychainAttributeList** attrList, + UInt32* length, + void** outData) const; + + virtual OSStatus ItemModifyAttributesAndData( + SecKeychainItemRef itemRef, + const SecKeychainAttributeList* attrList, + UInt32 length, + const void* data) const; + + virtual OSStatus ItemFreeAttributesAndData(SecKeychainAttributeList* attrList, + void* data) const; + + virtual OSStatus ItemDelete(SecKeychainItemRef itemRef) const; + + virtual OSStatus SearchCreateFromAttributes( + CFTypeRef keychainOrArray, + SecItemClass itemClass, + const SecKeychainAttributeList* attrList, + SecKeychainSearchRef* searchRef) const; + + virtual OSStatus SearchCopyNext(SecKeychainSearchRef searchRef, + SecKeychainItemRef* itemRef) const; + + virtual OSStatus 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; + + virtual OSStatus FindGenericPassword(CFTypeRef keychainOrArray, + UInt32 serviceNameLength, + const char* serviceName, + UInt32 accountNameLength, + const char* accountName, + UInt32* passwordLength, + void** passwordData, + SecKeychainItemRef* itemRef) const; + + virtual OSStatus ItemFreeContent(SecKeychainAttributeList* attrList, + void* data) const; + + virtual OSStatus AddGenericPassword(SecKeychainRef keychain, + UInt32 serviceNameLength, + const char* serviceName, + UInt32 accountNameLength, + const char* accountName, + UInt32 passwordLength, + const void* passwordData, + SecKeychainItemRef* itemRef) const; + + // Calls CFRelease on the given ref, after checking that |ref| is non-NULL. + virtual void Free(CFTypeRef ref) const; + + private: + DISALLOW_COPY_AND_ASSIGN(MacKeychain); +}; + +} // namespace crypto + +#endif // CRYPTO_KEYCHAIN_MAC_H_ diff --git a/crypto/mock_keychain_mac.cc b/crypto/mock_keychain_mac.cc new file mode 100644 index 0000000..a3bacf6 --- /dev/null +++ b/crypto/mock_keychain_mac.cc @@ -0,0 +1,534 @@ +// 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_keychain_mac.h" + +namespace crypto { + +MockKeychain::MockKeychain() + : 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 MockKeychain::InitializeKeychainData(unsigned int 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( + 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); + } + } +} + +MockKeychain::~MockKeychain() { + for (std::map::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* MockKeychain::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 MockKeychain::SetTestDataBytes(int 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 MockKeychain::SetTestDataString(int item, UInt32 tag, const char* value) { + SetTestDataBytes(item, tag, value, value ? strlen(value) : 0); +} + +void MockKeychain::SetTestDataPort(int item, UInt32 value) { + SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], + kSecPortItemAttr); + UInt32* data = static_cast(attribute->data); + *data = value; +} + +void MockKeychain::SetTestDataProtocol(int item, SecProtocolType value) { + SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], + kSecProtocolItemAttr); + SecProtocolType* data = static_cast(attribute->data); + *data = value; +} + +void MockKeychain::SetTestDataAuthType(int item, SecAuthenticationType value) { + SecKeychainAttribute* attribute = AttributeWithTag( + keychain_attr_list_[item], kSecAuthenticationTypeItemAttr); + SecAuthenticationType* data = static_cast( + attribute->data); + *data = value; +} + +void MockKeychain::SetTestDataNegativeItem(int item, Boolean value) { + SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], + kSecNegativeItemAttr); + Boolean* data = static_cast(attribute->data); + *data = value; +} + +void MockKeychain::SetTestDataCreator(int item, OSType value) { + SecKeychainAttribute* attribute = AttributeWithTag(keychain_attr_list_[item], + kSecCreatorItemAttr); + OSType* data = static_cast(attribute->data); + *data = value; +} + +void MockKeychain::SetTestDataPasswordBytes(int 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 MockKeychain::SetTestDataPasswordString(int item, const char* value) { + SetTestDataPasswordBytes(item, value, value ? strlen(value) : 0); +} + +OSStatus MockKeychain::ItemCopyAttributesAndData( + SecKeychainItemRef itemRef, + SecKeychainAttributeInfo* info, + SecItemClass* itemClass, + SecKeychainAttributeList** attrList, + UInt32* length, + void** outData) const { + DCHECK(itemRef); + unsigned int key = reinterpret_cast(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 MockKeychain::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; + } + + unsigned int key = reinterpret_cast(itemRef) - 1; + if (keychain_attr_list_.find(key) == keychain_attr_list_.end()) + return errSecInvalidItemRef; + + MockKeychain* mutable_this = const_cast(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(data))); + } else { + NOTIMPLEMENTED(); + } + } + } + if (data) + mutable_this->SetTestDataPasswordBytes(key, data, length); + return noErr; +} + +OSStatus MockKeychain::ItemFreeAttributesAndData( + SecKeychainAttributeList* attrList, + void* data) const { + --attribute_data_copy_count_; + return noErr; +} + +OSStatus MockKeychain::ItemDelete(SecKeychainItemRef itemRef) const { + unsigned int key = reinterpret_cast(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 MockKeychain::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 (std::map::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 = reinterpret_cast(kDummySearchRef); + ++search_copy_count_; + return noErr; +} + +bool MockKeychain::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 (std::map::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(attribute->data)))) { + continue; + } + attribute = AttributeWithTag(it->second, kSecProtocolItemAttr); + if ((attribute->data == NULL) || + (protocol != *(static_cast(attribute->data)))) { + continue; + } + attribute = AttributeWithTag(it->second, kSecAuthenticationTypeItemAttr); + if ((attribute->data == NULL) || + (authenticationType != + *(static_cast(attribute->data)))) { + continue; + } + // The keychain already has this item, since all fields other than the + // password match. + return true; + } + return false; +} + +OSStatus MockKeychain::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. + unsigned int key = next_item_key_++; + + // Initialize keychain data storage at the target location. + InitializeKeychainData(key); + + MockKeychain* mutable_this = const_cast(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(key + 1); + ++keychain_item_copy_count_; + } + return noErr; +} + +OSStatus MockKeychain::SearchCopyNext(SecKeychainSearchRef searchRef, + SecKeychainItemRef* itemRef) const { + if (remaining_search_results_.empty()) + return errSecItemNotFound; + unsigned int key = remaining_search_results_.front(); + remaining_search_results_.erase(remaining_search_results_.begin()); + *itemRef = reinterpret_cast(key + 1); + ++keychain_item_copy_count_; + return noErr; +} + +OSStatus MockKeychain::FindGenericPassword(CFTypeRef keychainOrArray, + UInt32 serviceNameLength, + const char* serviceName, + UInt32 accountNameLength, + const char* accountName, + UInt32* passwordLength, + void** passwordData, + SecKeychainItemRef* itemRef) const { + // When simulating |noErr| we return canned |passwordData| and + // |passwordLenght|. Otherwise, just return given code. + if (find_generic_result_ == noErr) { + static char password[] = "my_password"; + + DCHECK(passwordData); + *passwordData = static_cast(password); + DCHECK(passwordLength); + *passwordLength = strlen(password); + password_data_count_++; + } + + return find_generic_result_; +} + +OSStatus MockKeychain::ItemFreeContent(SecKeychainAttributeList* attrList, + void* data) const { + // No-op. + password_data_count_--; + return noErr; +} + +OSStatus MockKeychain::AddGenericPassword(SecKeychainRef keychain, + UInt32 serviceNameLength, + const char* serviceName, + UInt32 accountNameLength, + const char* accountName, + UInt32 passwordLength, + const void* passwordData, + SecKeychainItemRef* itemRef) const { + called_add_generic_ = true; + + DCHECK(passwordLength > 0); + DCHECK(passwordData); + add_generic_password_ = + std::string(const_cast(static_cast(passwordData)), + passwordLength); + return noErr; +} + +void MockKeychain::Free(CFTypeRef ref) const { + if (!ref) + return; + + if (reinterpret_cast(ref) == kDummySearchRef) { + --search_copy_count_; + } else { + --keychain_item_copy_count_; + } +} + +int MockKeychain::UnfreedSearchCount() const { + return search_copy_count_; +} + +int MockKeychain::UnfreedKeychainItemCount() const { + return keychain_item_copy_count_; +} + +int MockKeychain::UnfreedAttributeDataCount() const { + return attribute_data_copy_count_; +} + +bool MockKeychain::CreatorCodesSetForAddedItems() const { + for (std::set::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(attribute->data); + if (*data == 0) + return false; + } + return true; +} + +void MockKeychain::AddTestItem(const KeychainTestData& item_data) { + unsigned int 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 diff --git a/crypto/mock_keychain_mac.h b/crypto/mock_keychain_mac.h new file mode 100644 index 0000000..d52353e --- /dev/null +++ b/crypto/mock_keychain_mac.h @@ -0,0 +1,226 @@ +// 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. + +#ifndef CRYPTO_MOCK_KEYCHAIN_MAC_H_ +#define CRYPTO_MOCK_KEYCHAIN_MAC_H_ +#pragma once + +#include +#include +#include +#include + +#include "base/compiler_specific.h" +#include "crypto/keychain_mac.h" + +namespace crypto { + +// Mock Keychain wrapper for testing code that interacts with the OS X +// Keychain. Implemented by storing SecKeychainAttributeList and +// KeychainPasswordData values in separate mutable containers and +// mapping them to integer keys. +// +// Note that "const" is pretty much meaningless for this class; the const-ness +// of MacKeychain doesn't apply to the actual keychain data, so all of the Mock +// data is mutable; don't assume that it won't change over the life of tests. +class MockKeychain : public MacKeychain { + public: + MockKeychain(); + virtual ~MockKeychain(); + + // MacKeychain implementation. + virtual OSStatus ItemCopyAttributesAndData( + SecKeychainItemRef itemRef, + SecKeychainAttributeInfo* info, + SecItemClass* itemClass, + SecKeychainAttributeList** attrList, + UInt32* length, + void** outData) const OVERRIDE; + // Pass "fail_me" as the data to get errSecAuthFailed. + virtual OSStatus ItemModifyAttributesAndData( + SecKeychainItemRef itemRef, + const SecKeychainAttributeList* attrList, + UInt32 length, + const void* data) const OVERRIDE; + virtual OSStatus ItemFreeAttributesAndData(SecKeychainAttributeList* attrList, + void* data) const OVERRIDE; + virtual OSStatus ItemDelete(SecKeychainItemRef itemRef) const OVERRIDE; + virtual OSStatus SearchCreateFromAttributes( + CFTypeRef keychainOrArray, + SecItemClass itemClass, + const SecKeychainAttributeList* attrList, + SecKeychainSearchRef* searchRef) const OVERRIDE; + virtual OSStatus SearchCopyNext(SecKeychainSearchRef searchRef, + SecKeychainItemRef* itemRef) const OVERRIDE; + // Pass "some.domain.com" as the serverName to get errSecDuplicateItem. + virtual OSStatus 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 OVERRIDE; + virtual OSStatus FindGenericPassword( + CFTypeRef keychainOrArray, + UInt32 serviceNameLength, + const char* serviceName, + UInt32 accountNameLength, + const char* accountName, + UInt32* passwordLength, + void** passwordData, + SecKeychainItemRef* itemRef) const OVERRIDE; + virtual OSStatus ItemFreeContent(SecKeychainAttributeList* attrList, + void* data) const OVERRIDE; + virtual OSStatus AddGenericPassword( + SecKeychainRef keychain, + UInt32 serviceNameLength, + const char* serviceName, + UInt32 accountNameLength, + const char* accountName, + UInt32 passwordLength, + const void* passwordData, + SecKeychainItemRef* itemRef) const OVERRIDE; + virtual void Free(CFTypeRef ref) const OVERRIDE; + + // Return the counts of objects returned by Create/Copy functions but never + // Free'd as they should have been. + int UnfreedSearchCount() const; + int UnfreedKeychainItemCount() const; + int UnfreedAttributeDataCount() const; + + // Returns true if all items added with AddInternetPassword have a creator + // code set. + bool CreatorCodesSetForAddedItems() const; + + struct KeychainTestData { + const SecAuthenticationType auth_type; + const char* server; + const SecProtocolType protocol; + const char* path; + const UInt32 port; + const char* security_domain; + const char* creation_date; + const char* username; + const char* password; + const bool negative_item; + }; + // Adds a keychain item with the given info to the test set. + void AddTestItem(const KeychainTestData& item_data); + + // |FindGenericPassword()| can return different results depending on user + // interaction with the system Keychain. For mocking purposes we allow the + // user of this class to specify the result code of the + // |FindGenericPassword()| call so we can simulate the result of different + // user interactions. + void set_find_generic_result(OSStatus result) { + find_generic_result_ = result; + } + + // Returns the true if |AddGenericPassword()| was called. + bool called_add_generic() const { return called_add_generic_; } + + // Returns the value of the password set when |AddGenericPassword()| was + // called. + std::string add_generic_password() const { return add_generic_password_; } + + // Returns the number of allocations - deallocations for password data. + int password_data_count() const { return password_data_count_; } + + private: + // Returns true if the keychain already contains a password that matches the + // attributes provided. + bool 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; + // Initializes storage for keychain data at |key|. + void InitializeKeychainData(unsigned int key) const; + // Sets the data and length of |tag| in the item-th test item. + void SetTestDataBytes(int item, UInt32 tag, const void* data, size_t length); + // Sets the data and length of |tag| in the item-th test item based on + // |value|. The null-terminator will not be included; the Keychain Services + // docs don't indicate whether it is or not, so clients should not assume + // that it will be. + void SetTestDataString(int item, UInt32 tag, const char* value); + // Sets the data of the corresponding attribute of the item-th test item to + // |value|. Assumes that the space has alread been allocated, and the length + // set. + void SetTestDataPort(int item, UInt32 value); + void SetTestDataProtocol(int item, SecProtocolType value); + void SetTestDataAuthType(int item, SecAuthenticationType value); + void SetTestDataNegativeItem(int item, Boolean value); + void SetTestDataCreator(int item, OSType value); + // Sets the password data and length for the item-th test item. + void SetTestDataPasswordBytes(int item, const void* data, size_t length); + // Sets the password for the item-th test item. As with SetTestDataString, + // the data will not be null-terminated. + void SetTestDataPasswordString(int item, const char* value); + + // Returns the address of the attribute in attribute_list with tag |tag|. + static SecKeychainAttribute* AttributeWithTag( + const SecKeychainAttributeList& attribute_list, + UInt32 tag); + + static const int kDummySearchRef = 1000; + + typedef struct KeychainPasswordData { + KeychainPasswordData() : data(NULL), length(0) {} + void* data; + UInt32 length; + } KeychainPasswordData; + + // Mutable because the MockKeychain API requires its internal keychain storage + // to be modifiable by users of this class. + mutable std::map keychain_attr_list_; + mutable std::map keychain_data_; + mutable unsigned int next_item_key_; + + // Tracks the items that should be returned in subsequent calls to + // SearchCopyNext, based on the last call to SearchCreateFromAttributes. + // We can't handle multiple active searches, since we don't track the search + // ref we return, but we don't need to for our mocking. + mutable std::vector remaining_search_results_; + + // Track copies and releases to make sure they balance. Really these should + // be maps to track per item, but this should be good enough to catch + // real mistakes. + mutable int search_copy_count_; + mutable int keychain_item_copy_count_; + mutable int attribute_data_copy_count_; + + // Tracks which items (by key) were added with AddInternetPassword. + mutable std::set added_via_api_; + + // Result code for the |FindGenericPassword()| method. + OSStatus find_generic_result_; + + // Records whether |AddGenericPassword()| gets called. + mutable bool called_add_generic_; + + // Tracks the allocations and frees of password data in |FindGenericPassword| + // and |ItemFreeContent|. + mutable unsigned int password_data_count_; + + // Records the password being set when |AddGenericPassword()| gets called. + mutable std::string add_generic_password_; +}; + +} // namespace crypto + +#endif // CRYPTO_MOCK_KEYCHAIN_MAC_H_ -- cgit v1.1