diff options
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/keychain_mac.cc | 37 | ||||
-rw-r--r-- | chrome/browser/keychain_mac.h | 48 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_mac.cc | 333 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_mac.h | 30 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_mac_internal.h | 71 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_mac_unittest.cc | 710 |
6 files changed, 1229 insertions, 0 deletions
diff --git a/chrome/browser/keychain_mac.cc b/chrome/browser/keychain_mac.cc new file mode 100644 index 0000000..cf2adbd --- /dev/null +++ b/chrome/browser/keychain_mac.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2009 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 "chrome/browser/keychain_mac.h" + +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::ItemFreeAttributesAndData( + SecKeychainAttributeList *attrList, void *data) const { + return SecKeychainItemFreeAttributesAndData(attrList, data); +} + +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); +} + +void MacKeychain::Free(CFTypeRef ref) const { + if (ref) { + CFRelease(ref); + } +} diff --git a/chrome/browser/keychain_mac.h b/chrome/browser/keychain_mac.h new file mode 100644 index 0000000..a07d2a0 --- /dev/null +++ b/chrome/browser/keychain_mac.h @@ -0,0 +1,48 @@ +// Copyright (c) 2009 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 CHROME_BROWSER_KEYCHAIN_MAC_H_ +#define CHROME_BROWSER_KEYCHAIN_MAC_H_ + +#include <Security/Security.h> + +#include "base/basictypes.h" + +// 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 ItemFreeAttributesAndData(SecKeychainAttributeList *attrList, + void *data) const; + + virtual OSStatus SearchCreateFromAttributes( + CFTypeRef keychainOrArray, SecItemClass itemClass, + const SecKeychainAttributeList *attrList, + SecKeychainSearchRef *searchRef) const; + + virtual OSStatus SearchCopyNext(SecKeychainSearchRef searchRef, + 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); +}; + +#endif // CHROME_BROWSER_KEYCHAIN_MAC_H_ diff --git a/chrome/browser/password_manager/password_store_mac.cc b/chrome/browser/password_manager/password_store_mac.cc new file mode 100644 index 0000000..50a06e6 --- /dev/null +++ b/chrome/browser/password_manager/password_store_mac.cc @@ -0,0 +1,333 @@ +// Copyright (c) 2009 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 "chrome/browser/password_manager/password_store_mac.h" +#include "chrome/browser/password_manager/password_store_mac_internal.h" + +#include <CoreServices/CoreServices.h> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/time.h" +#include "chrome/browser/keychain_mac.h" + +namespace internal_keychain_helpers { + +// TODO(stuartmorgan): signon_realm for proxies is not yet supported. +bool ExtractSignonRealmComponents(const std::string& signon_realm, + std::string* server, int* port, + bool* is_secure, + std::string* security_domain) { + // The signon_realm will be the Origin portion of a URL for an HTML form, + // and the same but with the security domain as a path for HTTP auth. + GURL realm_as_url(signon_realm); + if (!realm_as_url.is_valid()) { + return false; + } + + if (server) + *server = realm_as_url.host(); + if (is_secure) + *is_secure = realm_as_url.SchemeIsSecure(); + if (port) + *port = realm_as_url.has_port() ? atoi(realm_as_url.port().c_str()) : 0; + if (security_domain) { + // Strip the leading '/' off of the path to get the security domain. + *security_domain = realm_as_url.path().substr(1); + } + return true; +} + +GURL URLFromComponents(bool is_secure, const std::string& host, int port, + const std::string& path) { + GURL::Replacements url_components; + std::string scheme(is_secure ? "https" : "http"); + url_components.SetSchemeStr(scheme); + url_components.SetHostStr(host); + std::string port_string; // Must remain in scope until after we do replacing. + if (port != kAnyPort) { + std::ostringstream port_stringstream; + port_stringstream << port; + port_string = port_stringstream.str(); + url_components.SetPortStr(port_string); + } + url_components.SetPathStr(path); + + GURL url("http://dummy.com"); // ReplaceComponents needs a valid URL. + return url.ReplaceComponents(url_components); +} + +// The time string is of the form "yyyyMMddHHmmss'Z", in UTC time. +bool TimeFromKeychainTimeString(const char* time_string_bytes, + unsigned int byte_length, + base::Time* time) { + DCHECK(time); + + char* time_string = static_cast<char*>(malloc(byte_length + 1)); + memcpy(time_string, time_string_bytes, byte_length); + time_string[byte_length] = '\0'; + base::Time::Exploded exploded_time; + bzero(&exploded_time, sizeof(exploded_time)); + int assignments = sscanf(time_string, "%4d%2d%2d%2d%2d%2dZ", + &exploded_time.year, &exploded_time.month, + &exploded_time.day_of_month, &exploded_time.hour, + &exploded_time.minute, &exploded_time.second); + free(time_string); + + if (assignments == 6) { + *time = base::Time::FromUTCExploded(exploded_time); + return true; + } + return false; +} + +SecAuthenticationType AuthTypeForScheme(PasswordForm::Scheme scheme) { + switch (scheme) { + case PasswordForm::SCHEME_HTML: return kSecAuthenticationTypeHTMLForm; + case PasswordForm::SCHEME_BASIC: return kSecAuthenticationTypeHTTPBasic; + case PasswordForm::SCHEME_DIGEST: return kSecAuthenticationTypeHTTPDigest; + case PasswordForm::SCHEME_OTHER: return kSecAuthenticationTypeDefault; + } + NOTREACHED(); + return kSecAuthenticationTypeDefault; +} + +PasswordForm::Scheme SchemeForAuthType(SecAuthenticationType auth_type) { + switch (auth_type) { + case kSecAuthenticationTypeHTMLForm: return PasswordForm::SCHEME_HTML; + case kSecAuthenticationTypeHTTPBasic: return PasswordForm::SCHEME_BASIC; + case kSecAuthenticationTypeHTTPDigest: return PasswordForm::SCHEME_DIGEST; + default: return PasswordForm::SCHEME_OTHER; + } +} + +// Searches |keychain| for all items usable for the given signon_realm, and +// puts them in |items|. The caller is responsible for calling keychain->Free +// on each of them when it is finished with them. +void FindMatchingKeychainItems(const MacKeychain& keychain, + const std::string& signon_realm, + PasswordForm::Scheme scheme, + std::vector<SecKeychainItemRef>* items) { + // Construct a keychain search based on the signon_realm and scheme. + std::string server; + std::string security_domain; + int port; + bool is_secure; + if (!internal_keychain_helpers::ExtractSignonRealmComponents( + signon_realm, &server, &port, &is_secure, &security_domain)) { + // TODO(stuartmorgan): Proxies will currently fail here, since their + // signon_realm is not a URL. We need to detect the proxy case and handle + // it specially. + return; + } + + const char* server_c_str = server.c_str(); + UInt32 port_uint = port; + SecProtocolType protocol = is_secure ? kSecProtocolTypeHTTPS + : kSecProtocolTypeHTTP; + SecAuthenticationType auth_type = + internal_keychain_helpers::AuthTypeForScheme(scheme); + const char* security_domain_c_str = security_domain.c_str(); + + // kSecSecurityDomainItemAttr must be last, so that it can be "removed" when + // not applicable. + SecKeychainAttribute attributes[] = { + { kSecServerItemAttr, strlen(server_c_str), + const_cast<void*>(reinterpret_cast<const void*>(server_c_str)) }, + { kSecPortItemAttr, sizeof(port_uint), static_cast<void*>(&port_uint) }, + { kSecProtocolItemAttr, sizeof(protocol), static_cast<void*>(&protocol) }, + { kSecAuthenticationTypeItemAttr, sizeof(auth_type), + static_cast<void*>(&auth_type) }, + { kSecSecurityDomainItemAttr, strlen(security_domain_c_str), + const_cast<void*>(reinterpret_cast<const void*>(security_domain_c_str)) } + }; + SecKeychainAttributeList search_attributes = { arraysize(attributes), + attributes }; + // For HTML forms, we don't want the security domain to be part of the + // search, so trim that off of the attribute list. + if (scheme == PasswordForm::SCHEME_HTML) { + search_attributes.count -= 1; + } + + SecKeychainSearchRef keychain_search = NULL; + OSStatus result = keychain.SearchCreateFromAttributes( + NULL, kSecInternetPasswordItemClass, &search_attributes, + &keychain_search); + + if (result != noErr) { + LOG(ERROR) << "Keychain lookup failed for " << server << " with error " + << result; + return; + } + + SecKeychainItemRef keychain_item; + while (keychain.SearchCopyNext(keychain_search, &keychain_item) == noErr) { + // Consumer is responsible for deleting the forms when they are done. + items->push_back(keychain_item); + } + + keychain.Free(keychain_search); +} + +bool FillPasswordFormFromKeychainItem(const MacKeychain& keychain, + const SecKeychainItemRef& keychain_item, + PasswordForm* form) { + DCHECK(form); + + SecKeychainAttributeInfo attrInfo; + UInt32 tags[] = { kSecAccountItemAttr, + kSecServerItemAttr, + kSecPortItemAttr, + kSecPathItemAttr, + kSecProtocolItemAttr, + kSecAuthenticationTypeItemAttr, + kSecSecurityDomainItemAttr, + kSecCreationDateItemAttr, + kSecNegativeItemAttr }; + attrInfo.count = arraysize(tags); + attrInfo.tag = tags; + attrInfo.format = NULL; + + SecKeychainAttributeList *attrList; + UInt32 password_length; + void* password_data; + OSStatus result = keychain.ItemCopyAttributesAndData(keychain_item, &attrInfo, + NULL, &attrList, + &password_length, + &password_data); + + if (result != noErr) { + // We don't log errSecAuthFailed because that just means that the user + // chose not to allow us access to the item. + if (result != errSecAuthFailed) { + LOG(ERROR) << "Keychain data load failed: " << result; + } + return false; + } + + UTF8ToWide(static_cast<const char *>(password_data), password_length, + &(form->password_value)); + + int port = kAnyPort; + std::string server; + std::string security_domain; + std::string path; + for (unsigned int i = 0; i < attrList->count; i++) { + SecKeychainAttribute attr = attrList->attr[i]; + if (!attr.data) { + continue; + } + switch (attr.tag) { + case kSecAccountItemAttr: + UTF8ToWide(static_cast<const char *>(attr.data), attr.length, + &(form->username_value)); + break; + case kSecServerItemAttr: + server.assign(static_cast<const char *>(attr.data), attr.length); + break; + case kSecPortItemAttr: + port = *(static_cast<UInt32*>(attr.data)); + break; + case kSecPathItemAttr: + path.assign(static_cast<const char *>(attr.data), attr.length); + break; + case kSecProtocolItemAttr: + { + SecProtocolType protocol = *(static_cast<SecProtocolType*>(attr.data)); + // TODO(stuartmorgan): Handle proxy types + form->ssl_valid = (protocol == kSecProtocolTypeHTTPS); + break; + } + case kSecAuthenticationTypeItemAttr: + { + SecAuthenticationType auth_type = + *(static_cast<SecAuthenticationType*>(attr.data)); + form->scheme = internal_keychain_helpers::SchemeForAuthType(auth_type); + break; + } + case kSecSecurityDomainItemAttr: + security_domain.assign(static_cast<const char *>(attr.data), + attr.length); + break; + case kSecCreationDateItemAttr: + // The only way to get a date out of Keychain is as a string. Really. + // (The docs claim it's an int, but the header is correct.) + internal_keychain_helpers::TimeFromKeychainTimeString( + static_cast<char*>(attr.data), attr.length, &form->date_created); + break; + case kSecNegativeItemAttr: + Boolean negative_item = *(static_cast<Boolean*>(attr.data)); + if (negative_item) { + form->blacklisted_by_user = true; + } + break; + } + } + keychain.ItemFreeAttributesAndData(attrList, password_data); + + // kSecNegativeItemAttr doesn't seem to actually be in widespread use. In + // practice, other browsers seem to use a "" or " " password (and a special + // user name) to indicated blacklist entries. + if (form->password_value.empty() || form->password_value == L" ") { + form->blacklisted_by_user = true; + } + + form->origin = internal_keychain_helpers::URLFromComponents(form->ssl_valid, + server, port, + path); + // TODO(stuartmorgan): Handle proxies, which need a different signon_realm + // format. + form->signon_realm = form->origin.GetOrigin().spec(); + if (form->scheme != PasswordForm::SCHEME_HTML) { + form->signon_realm.append(security_domain); + } + return true; +} + +} // internal_keychain_helpers + + +PasswordStoreMac::PasswordStoreMac(MacKeychain* keychain) + : keychain_(keychain) { + DCHECK(keychain_.get()); +} + +void PasswordStoreMac::AddLoginImpl(const PasswordForm& form) { + NOTIMPLEMENTED(); +} + +void PasswordStoreMac::UpdateLoginImpl(const PasswordForm& form) { + NOTIMPLEMENTED(); +} + +void PasswordStoreMac::RemoveLoginImpl(const PasswordForm& form) { + NOTIMPLEMENTED(); +} + +void PasswordStoreMac::GetLoginsImpl(GetLoginsRequest* request) { + std::vector<SecKeychainItemRef> keychain_items; + + internal_keychain_helpers::FindMatchingKeychainItems( + *keychain_, request->form.signon_realm, request->form.scheme, + &keychain_items); + + std::vector<PasswordForm*> forms; + + for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin(); + i != keychain_items.end(); ++i) { + // Consumer is responsible for deleting the forms when they are done... + PasswordForm* form = new PasswordForm(); + SecKeychainItemRef keychain_item = *i; + if (internal_keychain_helpers::FillPasswordFormFromKeychainItem( + *keychain_, keychain_item, form)) { + forms.push_back(form); + } + // ... but we need to clean up the keychain item. + keychain_->Free(keychain_item); + } + + NotifyConsumer(request, forms); +} diff --git a/chrome/browser/password_manager/password_store_mac.h b/chrome/browser/password_manager/password_store_mac.h new file mode 100644 index 0000000..61bce2d --- /dev/null +++ b/chrome/browser/password_manager/password_store_mac.h @@ -0,0 +1,30 @@ +// Copyright (c) 2009 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 CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_H_ +#define CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_H_ + +#include "base/scoped_ptr.h" +#include "chrome/browser/password_manager/password_store.h" + +class MacKeychain; + +class PasswordStoreMac : public PasswordStore { + public: + // Takes ownership of |keychain|, which must not be NULL. + explicit PasswordStoreMac(MacKeychain* keychain); + virtual ~PasswordStoreMac() {} + + private: + void AddLoginImpl(const PasswordForm& form); + void UpdateLoginImpl(const PasswordForm& form); + void RemoveLoginImpl(const PasswordForm& form); + void GetLoginsImpl(GetLoginsRequest* request); + + scoped_ptr<MacKeychain> keychain_; + + DISALLOW_COPY_AND_ASSIGN(PasswordStoreMac); +}; + +#endif // CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_H_ diff --git a/chrome/browser/password_manager/password_store_mac_internal.h b/chrome/browser/password_manager/password_store_mac_internal.h new file mode 100644 index 0000000..250a15fc --- /dev/null +++ b/chrome/browser/password_manager/password_store_mac_internal.h @@ -0,0 +1,71 @@ +// Copyright (c) 2009 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 CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_INTERNAL_H_ +#define CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_INTERNAL_H_ + +#include <Security/Security.h> + +#include <string> +#include <vector> + +#include "base/time.h" +#include "chrome/browser/keychain_mac.h" + +namespace internal_keychain_helpers { + +// Takes a PasswordForm's signon_realm and parses it into its component parts, +// which are returned though the appropriate out parameters. +// Returns true if it can be successfully parsed, in which case all out params +// that are non-NULL will be set. If there is no port, port will be 0. +// If the return value is false, the state of the our params is undefined. +bool ExtractSignonRealmComponents(const std::string& signon_realm, + std::string* server, int* port, + bool* is_secure, + std::string* security_domain); + +// Returns a URL built from the given components. To create a URL without a +// port, pass kAnyPort for the |port| parameter. +GURL URLFromComponents(bool is_secure, const std::string& host, int port, + const std::string& path); + +// Converts a Keychain time string to a Time object, returning true if +// time_string_bytes was parsable. If the return value is false, the value of +// |time| is unchanged. +bool TimeFromKeychainTimeString(const char* time_string_bytes, + unsigned int byte_length, + base::Time* time); + +// Returns the Keychain SecAuthenticationType type corresponding to |scheme|. +SecAuthenticationType AuthTypeForScheme(PasswordForm::Scheme scheme); + +// Returns the PasswordForm Scheme corresponding to |auth_type|. +PasswordForm::Scheme SchemeForAuthType(SecAuthenticationType auth_type); + +// Searches |keychain| for all items usable for the given signon_realm, and +// puts them in |items|. The caller is responsible for calling keychain->Free +// on each of them when it is finished with them. +void FindMatchingKeychainItems(const MacKeychain& keychain, + const std::string& signon_realm, + PasswordForm::Scheme scheme, + std::vector<SecKeychainItemRef>* items); + +// Sets the fields of |form| based on the keychain data from |keychain_item|. +// Fields that can't be determined from |keychain_item| will be unchanged. +// +// IMPORTANT: This function can cause the OS to trigger UI (to allow access to +// the keychain item if we aren't trusted for the item), and block until the UI +// is dismissed. +// +// If excessive prompting for access to other applications' keychain items +// becomes an issue, the password storage API will need to be refactored to +// allow the password to be retrieved later (accessing other fields doesn't +// require authorization). +bool FillPasswordFormFromKeychainItem(const MacKeychain& keychain, + const SecKeychainItemRef& keychain_item, + PasswordForm* form); + +} // internal_keychain_helpers + +#endif // CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_INTERNAL_H_ diff --git a/chrome/browser/password_manager/password_store_mac_unittest.cc b/chrome/browser/password_manager/password_store_mac_unittest.cc new file mode 100644 index 0000000..d268ae7f3 --- /dev/null +++ b/chrome/browser/password_manager/password_store_mac_unittest.cc @@ -0,0 +1,710 @@ +// Copyright (c) 2009 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 "testing/gtest/include/gtest/gtest.h" + +#include "base/basictypes.h" +#include "chrome/browser/password_manager/password_store_mac.h" +#include "chrome/browser/password_manager/password_store_mac_internal.h" + +#pragma mark Mock Keychain + +// TODO(stuartmorgan): Replace this with gMock. You know, once we have it. +// The basic idea of this mock is that it has a static array of data to use +// for ItemCopyAttributesAndData, and SecKeychainItemRef values are just indexes +// into that array (offset by 1 to prevent problems with client null-checking +// refs), cast to pointers. +class MockKeychain : public MacKeychain { + public: + MockKeychain(); + virtual ~MockKeychain(); + virtual OSStatus ItemCopyAttributesAndData( + SecKeychainItemRef itemRef, SecKeychainAttributeInfo *info, + SecItemClass *itemClass, SecKeychainAttributeList **attrList, + UInt32 *length, void **outData) const; + virtual OSStatus ItemFreeAttributesAndData(SecKeychainAttributeList *attrList, + void *data) const; + virtual OSStatus SearchCreateFromAttributes( + CFTypeRef keychainOrArray, SecItemClass itemClass, + const SecKeychainAttributeList *attrList, + SecKeychainSearchRef *searchRef) const; + virtual OSStatus SearchCopyNext(SecKeychainSearchRef searchRef, + SecKeychainItemRef *itemRef) const; + virtual void Free(CFTypeRef ref) const; + + // Causes a test failure unless everything returned from + // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext + // was correctly freed. + void ExpectCreatesAndFreesBalanced(); + + private: + // 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); + // Sets the password for the item-th test item. As with SetTestDataString, + // the data will not be null-terminated. + void SetTestDataPassword(int item, const char* value); + + // Returns the index of |tag| in |attribute_list|, or -1 if it's not found. + static int IndexForTag(const SecKeychainAttributeList& attribute_list, + UInt32 tag); + + static const int kDummySearchRef = 1000; + + typedef struct { + void* data; + UInt32 length; + } KeychainPasswordData; + + SecKeychainAttributeList* keychain_attr_list_; + KeychainPasswordData* keychain_data_; + unsigned int item_count_; + + // 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<unsigned int> 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_; +}; + +MockKeychain::MockKeychain() + : search_copy_count_(0), keychain_item_copy_count_(0), + attribute_data_copy_count_(0) { + UInt32 tags[] = { kSecAccountItemAttr, + kSecServerItemAttr, + kSecPortItemAttr, + kSecPathItemAttr, + kSecProtocolItemAttr, + kSecAuthenticationTypeItemAttr, + kSecSecurityDomainItemAttr, + kSecCreationDateItemAttr, + kSecNegativeItemAttr }; + + // Create the test keychain data to return from ItemCopyAttributesAndData, + // and set up everything that's consistent across all the items. + item_count_ = 8; + keychain_attr_list_ = static_cast<SecKeychainAttributeList*>( + calloc(item_count_, sizeof(SecKeychainAttributeList))); + keychain_data_ = static_cast<KeychainPasswordData*>( + calloc(item_count_, sizeof(KeychainPasswordData))); + for (unsigned int i = 0; i < item_count_; ++i) { + keychain_attr_list_[i].count = arraysize(tags); + keychain_attr_list_[i].attr = static_cast<SecKeychainAttribute*>( + calloc(keychain_attr_list_[i].count, sizeof(SecKeychainAttribute))); + for (unsigned int j = 0; j < keychain_attr_list_[i].count; ++j) { + keychain_attr_list_[i].attr[j].tag = tags[j]; + size_t data_size = 0; + switch (tags[j]) { + 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; + } + if (data_size > 0) { + keychain_attr_list_[i].attr[j].length = data_size; + keychain_attr_list_[i].attr[j].data = calloc(1, data_size); + } + } + } + + // Basic HTML form. + unsigned int item = 0; + CHECK(item < item_count_); + SetTestDataString(item, kSecAccountItemAttr, "joe_user"); + SetTestDataString(item, kSecServerItemAttr, "some.domain.com"); + SetTestDataProtocol(item, kSecProtocolTypeHTTP); + SetTestDataAuthType(item, kSecAuthenticationTypeHTMLForm); + SetTestDataString(item, kSecCreationDateItemAttr, "20020601171500Z"); + SetTestDataPassword(item, "sekrit"); + + // HTML form with path. + ++item; + CHECK(item < item_count_); + SetTestDataString(item, kSecAccountItemAttr, "joe_user"); + SetTestDataString(item, kSecServerItemAttr, "some.domain.com"); + SetTestDataString(item, kSecPathItemAttr, "insecure.html"); + SetTestDataProtocol(item, kSecProtocolTypeHTTP); + SetTestDataAuthType(item, kSecAuthenticationTypeHTMLForm); + SetTestDataString(item, kSecCreationDateItemAttr, "19991231235959Z"); + SetTestDataPassword(item, "sekrit"); + + // Secure HTML form with path. + ++item; + CHECK(item < item_count_); + SetTestDataString(item, kSecAccountItemAttr, "secure_user"); + SetTestDataString(item, kSecServerItemAttr, "some.domain.com"); + SetTestDataString(item, kSecPathItemAttr, "secure.html"); + SetTestDataProtocol(item, kSecProtocolTypeHTTPS); + SetTestDataAuthType(item, kSecAuthenticationTypeHTMLForm); + SetTestDataString(item, kSecCreationDateItemAttr, "20100908070605Z"); + SetTestDataPassword(item, "password"); + + // True negative item. + ++item; + CHECK(item < item_count_); + SetTestDataString(item, kSecServerItemAttr, "dont.remember.com"); + SetTestDataProtocol(item, kSecProtocolTypeHTTP); + SetTestDataAuthType(item, kSecAuthenticationTypeHTMLForm); + SetTestDataString(item, kSecCreationDateItemAttr, "20000101000000Z"); + SetTestDataNegativeItem(item, true); + + // De-facto negative item, type one. + ++item; + CHECK(item < item_count_); + SetTestDataString(item, kSecAccountItemAttr, "Password Not Stored"); + SetTestDataString(item, kSecServerItemAttr, "dont.remember.com"); + SetTestDataProtocol(item, kSecProtocolTypeHTTP); + SetTestDataAuthType(item, kSecAuthenticationTypeHTMLForm); + SetTestDataString(item, kSecCreationDateItemAttr, "20000101000000Z"); + SetTestDataPassword(item, ""); + + // De-facto negative item, type two. + ++item; + CHECK(item < item_count_); + SetTestDataString(item, kSecServerItemAttr, "dont.remember.com"); + SetTestDataProtocol(item, kSecProtocolTypeHTTPS); + SetTestDataAuthType(item, kSecAuthenticationTypeHTMLForm); + SetTestDataString(item, kSecCreationDateItemAttr, "20000101000000Z"); + SetTestDataPassword(item, " "); + + // HTTP auth basic, with port and path. + ++item; + CHECK(item < item_count_); + SetTestDataString(item, kSecAccountItemAttr, "basic_auth_user"); + SetTestDataString(item, kSecServerItemAttr, "some.domain.com"); + SetTestDataString(item, kSecSecurityDomainItemAttr, "low_security"); + SetTestDataString(item, kSecPathItemAttr, "insecure.html"); + SetTestDataProtocol(item, kSecProtocolTypeHTTP); + SetTestDataPort(item, 4567); + SetTestDataAuthType(item, kSecAuthenticationTypeHTTPBasic); + SetTestDataString(item, kSecCreationDateItemAttr, "19980330100000Z"); + SetTestDataPassword(item, "basic"); + + // HTTP auth digest, secure. + ++item; + CHECK(item < item_count_); + SetTestDataString(item, kSecAccountItemAttr, "digest_auth_user"); + SetTestDataString(item, kSecServerItemAttr, "some.domain.com"); + SetTestDataString(item, kSecSecurityDomainItemAttr, "high_security"); + SetTestDataProtocol(item, kSecProtocolTypeHTTPS); + SetTestDataAuthType(item, kSecAuthenticationTypeHTTPDigest); + SetTestDataString(item, kSecCreationDateItemAttr, "19980330100000Z"); + SetTestDataPassword(item, "digest"); +} + +MockKeychain::~MockKeychain() { + for (unsigned int i = 0; i < item_count_; ++i) { + for (unsigned int j = 0; j < keychain_attr_list_[i].count; ++j) { + if (keychain_attr_list_[i].attr[j].data) { + free(keychain_attr_list_[i].attr[j].data); + } + } + free(keychain_attr_list_[i].attr); + if (keychain_data_[i].data) { + free(keychain_data_[i].data); + } + } + free(keychain_attr_list_); + free(keychain_data_); +} + +void MockKeychain::ExpectCreatesAndFreesBalanced() { + EXPECT_EQ(0, search_copy_count_); + EXPECT_EQ(0, keychain_item_copy_count_); + EXPECT_EQ(0, attribute_data_copy_count_); +} + +int MockKeychain::IndexForTag(const SecKeychainAttributeList& attribute_list, + UInt32 tag) { + for (unsigned int i = 0; i < attribute_list.count; ++i) { + if (attribute_list.attr[i].tag == tag) { + return i; + } + } + DCHECK(false); + return -1; +} + +void MockKeychain::SetTestDataString(int item, UInt32 tag, const char* value) { + int attribute_index = IndexForTag(keychain_attr_list_[item], tag); + size_t data_size = strlen(value); + keychain_attr_list_[item].attr[attribute_index].length = data_size; + if (data_size > 0) { + keychain_attr_list_[item].attr[attribute_index].data = malloc(data_size); + // Use memcpy rather than str*cpy because we are deliberately omitting the + // null-terminator (see method declaration comment). + CHECK(keychain_attr_list_[item].attr[attribute_index].data); + memcpy(keychain_attr_list_[item].attr[attribute_index].data, value, + data_size); + } else { + keychain_attr_list_[item].attr[attribute_index].data = NULL; + } +} + +void MockKeychain::SetTestDataPort(int item, UInt32 value) { + int attribute_index = IndexForTag(keychain_attr_list_[item], + kSecPortItemAttr); + void* data = keychain_attr_list_[item].attr[attribute_index].data; + *(static_cast<UInt32*>(data)) = value; +} + +void MockKeychain::SetTestDataProtocol(int item, SecProtocolType value) { + int attribute_index = IndexForTag(keychain_attr_list_[item], + kSecProtocolItemAttr); + void* data = keychain_attr_list_[item].attr[attribute_index].data; + *(static_cast<SecProtocolType*>(data)) = value; +} + +void MockKeychain::SetTestDataAuthType(int item, SecAuthenticationType value) { + int attribute_index = IndexForTag(keychain_attr_list_[item], + kSecAuthenticationTypeItemAttr); + void* data = keychain_attr_list_[item].attr[attribute_index].data; + *(static_cast<SecAuthenticationType*>(data)) = value; +} + +void MockKeychain::SetTestDataNegativeItem(int item, Boolean value) { + int attribute_index = IndexForTag(keychain_attr_list_[item], + kSecNegativeItemAttr); + void* data = keychain_attr_list_[item].attr[attribute_index].data; + *(static_cast<Boolean*>(data)) = value; +} + +void MockKeychain::SetTestDataPassword(int item, const char* value) { + size_t data_size = strlen(value); + keychain_data_[item].length = data_size; + if (data_size > 0) { + keychain_data_[item].data = malloc(data_size); + // Use memcpy rather than str*cpy because we are deliberately omitting the + // null-terminator (see method declaration comment). + memcpy(keychain_data_[item].data, value, data_size); + } else { + keychain_data_[item].data = NULL; + } +} + +OSStatus MockKeychain::ItemCopyAttributesAndData( + SecKeychainItemRef itemRef, SecKeychainAttributeInfo *info, + SecItemClass *itemClass, SecKeychainAttributeList **attrList, + UInt32 *length, void **outData) const { + DCHECK(itemRef); + unsigned int item_index = reinterpret_cast<unsigned int>(itemRef) - 1; + if (item_index >= item_count_) { + return errSecInvalidItemRef; + } + + DCHECK(!itemClass); // itemClass not implemented in the Mock. + if (attrList) { + *attrList = &(keychain_attr_list_[item_index]); + } + if (outData) { + *outData = keychain_data_[item_index].data; + DCHECK(length); + *length = keychain_data_[item_index].length; + } + + ++attribute_data_copy_count_; + return noErr; +} + +OSStatus MockKeychain::ItemFreeAttributesAndData( + SecKeychainAttributeList *attrList, + void *data) const { + --attribute_data_copy_count_; + 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 (unsigned int mock_item = 0; mock_item < item_count_; ++mock_item) { + bool mock_item_matches = true; + for (UInt32 search_attr = 0; search_attr < attrList->count; ++search_attr) { + int mock_attr = IndexForTag(keychain_attr_list_[mock_item], + attrList->attr[search_attr].tag); + SecKeychainAttribute* mock_attribute = + &(keychain_attr_list_[mock_item].attr[mock_attr]); + 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(mock_item); + } + } + + DCHECK(searchRef); + *searchRef = reinterpret_cast<SecKeychainSearchRef>(kDummySearchRef); + ++search_copy_count_; + return noErr; +} + +OSStatus MockKeychain::SearchCopyNext(SecKeychainSearchRef searchRef, + SecKeychainItemRef *itemRef) const { + if (remaining_search_results_.empty()) { + return errSecItemNotFound; + } + unsigned int index = remaining_search_results_.front(); + remaining_search_results_.erase(remaining_search_results_.begin()); + *itemRef = reinterpret_cast<SecKeychainItemRef>(index + 1); + ++keychain_item_copy_count_; + return noErr; +} + +void MockKeychain::Free(CFTypeRef ref) const { + if (!ref) { + return; + } + + if (reinterpret_cast<int>(ref) == kDummySearchRef) { + --search_copy_count_; + } else { + --keychain_item_copy_count_; + } +} + +#pragma mark Unit Tests +//////////////////////////////////////////////////////////////////////////////// + +TEST(PasswordStoreMacTest, TestSignonRealmParsing) { + typedef struct { + const char* signon_realm; + const bool expected_parsed; + const char* expected_server; + const bool expected_is_secure; + const int expected_port; + const char* expected_security_domain; + } TestData; + + TestData test_data[] = { + // HTML form signon realms. + { "http://www.domain.com/", + true, "www.domain.com", false, 0, "" }, + { "https://foo.org:9999/", + true, "foo.org", true, 9999, "" }, + // HTTP auth signon realms. + { "http://httpauth.com:8080/lowsecurity", + true, "httpauth.com", false, 8080, "lowsecurity" }, + { "https://httpauth.com/highsecurity", + true, "httpauth.com", true, 0 , "highsecurity" }, + // Bogus realms + { "blahblahblah", + false, false, "", 0, "" }, + { "foo/bar/baz", + false, false, "", 0, "" }, + }; + + for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { + std::string server; + std::string security_domain; + bool is_secure = false; + int port = -1; + bool parsed = internal_keychain_helpers::ExtractSignonRealmComponents( + std::string(test_data[i].signon_realm), &server, &port, &is_secure, + &security_domain); + EXPECT_EQ(test_data[i].expected_parsed, parsed) << "In iteration " << i; + + if (!parsed) { + continue; // If parse failed, out params are undefined. + } + EXPECT_EQ(std::string(test_data[i].expected_server), server) + << "In iteration " << i; + EXPECT_EQ(std::string(test_data[i].expected_security_domain), + security_domain) + << "In iteration " << i; + EXPECT_EQ(test_data[i].expected_is_secure, is_secure) + << "In iteration " << i; + EXPECT_EQ(test_data[i].expected_port, port) << "In iteration " << i; + } + + // NULLs are allowed for out params. + bool parsed = internal_keychain_helpers::ExtractSignonRealmComponents( + std::string("http://foo.bar.com:1234/baz"), NULL, NULL, NULL, NULL); + EXPECT_TRUE(parsed); +} + +TEST(PasswordStoreMacTest, TestURLConstruction) { + std::string host("exampledomain.com"); + std::string path("/path/to/page.html"); + + GURL full_url = internal_keychain_helpers::URLFromComponents(false, host, + 1234, path); + EXPECT_TRUE(full_url.is_valid()); + EXPECT_EQ(GURL("http://exampledomain.com:1234/path/to/page.html"), full_url); + + GURL simple_secure_url = internal_keychain_helpers::URLFromComponents( + true, host, 0, std::string("")); + EXPECT_TRUE(simple_secure_url.is_valid()); + EXPECT_EQ(GURL("https://exampledomain.com/"), simple_secure_url); +} + +TEST(PasswordStoreMacTest, TestKeychainTime) { + typedef struct { + const char* time_string; + const bool expected_parsed; + const int expected_year; + const int expected_month; + const int expected_day; + const int expected_hour; + const int expected_minute; + const int expected_second; + } TestData; + + TestData test_data[] = { + // HTML form signon realms. + { "19980330100000Z", true, 1998, 3, 30, 10, 0, 0 }, + { "19991231235959Z", true, 1999, 12, 31, 23, 59, 59 }, + { "20000101000000Z", true, 2000, 1, 1, 0, 0, 0 }, + { "20011112012843Z", true, 2001, 11, 12, 1, 28, 43 }, + { "20020601171530Z", true, 2002, 6, 1, 17, 15, 30 }, + { "20100908070605Z", true, 2010, 9, 8, 7, 6, 5 }, + { "20010203040", false, 0, 0, 0, 0, 0, 0 }, + }; + + for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { + base::Time time; + bool parsed = internal_keychain_helpers::TimeFromKeychainTimeString( + test_data[i].time_string, strlen(test_data[i].time_string), &time); + EXPECT_EQ(test_data[i].expected_parsed, parsed) << "In iteration " << i; + if (!parsed) { + continue; + } + + base::Time::Exploded exploded_time; + time.UTCExplode(&exploded_time); + EXPECT_EQ(test_data[i].expected_year, exploded_time.year) + << "In iteration " << i; + EXPECT_EQ(test_data[i].expected_month, exploded_time.month) + << "In iteration " << i; + EXPECT_EQ(test_data[i].expected_day, exploded_time.day_of_month) + << "In iteration " << i; + EXPECT_EQ(test_data[i].expected_hour, exploded_time.hour) + << "In iteration " << i; + EXPECT_EQ(test_data[i].expected_minute, exploded_time.minute) + << "In iteration " << i; + EXPECT_EQ(test_data[i].expected_second, exploded_time.second) + << "In iteration " << i; + } +} + +TEST(PasswordStoreMacTest, TestAuthTypeSchemeTranslation) { + // Our defined types should round-trip correctly. + SecAuthenticationType auth_types[] = { kSecAuthenticationTypeHTMLForm, + kSecAuthenticationTypeHTTPBasic, + kSecAuthenticationTypeHTTPDigest }; + const int auth_count = sizeof(auth_types) / sizeof(SecAuthenticationType); + for (int i = 0; i < auth_count; ++i) { + SecAuthenticationType round_tripped_auth_type = + internal_keychain_helpers::AuthTypeForScheme( + internal_keychain_helpers::SchemeForAuthType(auth_types[i])); + EXPECT_EQ(auth_types[i], round_tripped_auth_type); + } + // Anything else should become SCHEME_OTHER and come back as Default. + PasswordForm::Scheme scheme_for_other_auth_type = + internal_keychain_helpers::SchemeForAuthType(kSecAuthenticationTypeNTLM); + SecAuthenticationType round_tripped_other_auth_type = + internal_keychain_helpers::AuthTypeForScheme(scheme_for_other_auth_type); + EXPECT_EQ(PasswordForm::SCHEME_OTHER, scheme_for_other_auth_type); + EXPECT_EQ(kSecAuthenticationTypeDefault, round_tripped_other_auth_type); +} + +TEST(PasswordStoreMacTest, TestKeychainToFormTranslation) { + typedef struct { + const PasswordForm::Scheme scheme; + const char* signon_realm; + const char* origin; + const wchar_t* username; // Set to NULL to check for a blacklist entry. + const wchar_t* password; + const bool ssl_valid; + const int creation_year; + const int creation_month; + const int creation_day; + const int creation_hour; + const int creation_minute; + const int creation_second; + } TestExpectations; + + TestExpectations expected[] = { + { PasswordForm::SCHEME_HTML, "http://some.domain.com/", + "http://some.domain.com/", L"joe_user", L"sekrit", false, + 2002, 6, 1, 17, 15, 0 }, + { PasswordForm::SCHEME_HTML, "http://some.domain.com/", + "http://some.domain.com/insecure.html", L"joe_user", L"sekrit", false, + 1999, 12, 31, 23, 59, 59 }, + { PasswordForm::SCHEME_HTML, "https://some.domain.com/", + "https://some.domain.com/secure.html", L"secure_user", L"password", true, + 2010, 9, 8, 7, 6, 5 }, + { PasswordForm::SCHEME_HTML, "http://dont.remember.com/", + "http://dont.remember.com/", NULL, NULL, false, + 2000, 1, 1, 0, 0, 0 }, + { PasswordForm::SCHEME_HTML, "http://dont.remember.com/", + "http://dont.remember.com/", NULL, NULL, false, + 2000, 1, 1, 0, 0, 0 }, + { PasswordForm::SCHEME_HTML, "https://dont.remember.com/", + "https://dont.remember.com/", NULL, NULL, true, + 2000, 1, 1, 0, 0, 0 }, + { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security", + "http://some.domain.com:4567/insecure.html", L"basic_auth_user", L"basic", + false, 1998, 03, 30, 10, 00, 00 }, + { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security", + "https://some.domain.com/", L"digest_auth_user", L"digest", true, + 1998, 3, 30, 10, 0, 0 }, + }; + + MockKeychain mock_keychain; + + for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(expected); ++i) { + // Create our fake KeychainItemRef; see MockKeychain docs. + SecKeychainItemRef keychain_item = + reinterpret_cast<SecKeychainItemRef>(i + 1); + PasswordForm form; + bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( + mock_keychain, keychain_item, &form); + + EXPECT_TRUE(parsed) << "In iteration " << i; + mock_keychain.ExpectCreatesAndFreesBalanced(); + + EXPECT_EQ(expected[i].scheme, form.scheme) << "In iteration " << i; + EXPECT_EQ(GURL(expected[i].origin), form.origin) << "In iteration " << i; + EXPECT_EQ(expected[i].ssl_valid, form.ssl_valid) << "In iteration " << i; + EXPECT_EQ(std::string(expected[i].signon_realm), form.signon_realm) + << "In iteration " << i; + if (expected[i].username) { + EXPECT_EQ(std::wstring(expected[i].username), form.username_value) + << "In iteration " << i; + EXPECT_EQ(std::wstring(expected[i].password), form.password_value) + << "In iteration " << i; + EXPECT_FALSE(form.blacklisted_by_user) << "In iteration " << i; + } else { + EXPECT_TRUE(form.blacklisted_by_user) << "In iteration " << i; + } + base::Time::Exploded exploded_time; + form.date_created.UTCExplode(&exploded_time); + EXPECT_EQ(expected[i].creation_year, exploded_time.year) + << "In iteration " << i; + EXPECT_EQ(expected[i].creation_month, exploded_time.month) + << "In iteration " << i; + EXPECT_EQ(expected[i].creation_day, exploded_time.day_of_month) + << "In iteration " << i; + EXPECT_EQ(expected[i].creation_hour, exploded_time.hour) + << "In iteration " << i; + EXPECT_EQ(expected[i].creation_minute, exploded_time.minute) + << "In iteration " << i; + EXPECT_EQ(expected[i].creation_second, exploded_time.second) + << "In iteration " << i; + } + + { + // Use an invalid ref, to make sure errors are reported. + SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(99); + PasswordForm form; + bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( + mock_keychain, keychain_item, &form); + mock_keychain.ExpectCreatesAndFreesBalanced(); + EXPECT_FALSE(parsed); + } +} + +static void FreeKeychainItems(const MacKeychain& keychain, + std::vector<SecKeychainItemRef>* items) { + for (std::vector<SecKeychainItemRef>::iterator i = items->begin(); + i != items->end(); ++i) { + keychain.Free(*i); + } + items->clear(); +} + +TEST(PasswordStoreMacTest, TestKeychainSearch) { + MockKeychain mock_keychain; + + { // An HTML form we've seen. + std::vector<SecKeychainItemRef> matching_items; + internal_keychain_helpers::FindMatchingKeychainItems( + mock_keychain, std::string("http://some.domain.com/"), + PasswordForm::SCHEME_HTML, &matching_items); + EXPECT_EQ(static_cast<size_t>(2), matching_items.size()); + FreeKeychainItems(mock_keychain, &matching_items); + mock_keychain.ExpectCreatesAndFreesBalanced(); + } + + { // An HTML form we haven't seen + std::vector<SecKeychainItemRef> matching_items; + internal_keychain_helpers::FindMatchingKeychainItems( + mock_keychain, std::string("http://www.unseendomain.com/"), + PasswordForm::SCHEME_HTML, &matching_items); + EXPECT_EQ(static_cast<size_t>(0), matching_items.size()); + FreeKeychainItems(mock_keychain, &matching_items); + mock_keychain.ExpectCreatesAndFreesBalanced(); + } + + { // Basic auth that should match. + std::vector<SecKeychainItemRef> matching_items; + internal_keychain_helpers::FindMatchingKeychainItems( + mock_keychain, std::string("http://some.domain.com:4567/low_security"), + PasswordForm::SCHEME_BASIC, &matching_items); + EXPECT_EQ(static_cast<size_t>(1), matching_items.size()); + FreeKeychainItems(mock_keychain, &matching_items); + mock_keychain.ExpectCreatesAndFreesBalanced(); + } + + { // Basic auth with the wrong port. + std::vector<SecKeychainItemRef> matching_items; + internal_keychain_helpers::FindMatchingKeychainItems( + mock_keychain, std::string("http://some.domain.com:1111/low_security"), + PasswordForm::SCHEME_BASIC, &matching_items); + EXPECT_EQ(static_cast<size_t>(0), matching_items.size()); + FreeKeychainItems(mock_keychain, &matching_items); + mock_keychain.ExpectCreatesAndFreesBalanced(); + } + + { // Digest auth we've saved under https, visited with http. + std::vector<SecKeychainItemRef> matching_items; + internal_keychain_helpers::FindMatchingKeychainItems( + mock_keychain, std::string("http://some.domain.com/high_security"), + PasswordForm::SCHEME_DIGEST, &matching_items); + EXPECT_EQ(static_cast<size_t>(0), matching_items.size()); + FreeKeychainItems(mock_keychain, &matching_items); + mock_keychain.ExpectCreatesAndFreesBalanced(); + } + + { // Digest auth that should match. + std::vector<SecKeychainItemRef> matching_items; + internal_keychain_helpers::FindMatchingKeychainItems( + mock_keychain, std::string("https://some.domain.com/high_security"), + PasswordForm::SCHEME_DIGEST, &matching_items); + EXPECT_EQ(static_cast<size_t>(1), matching_items.size()); + FreeKeychainItems(mock_keychain, &matching_items); + mock_keychain.ExpectCreatesAndFreesBalanced(); + } +} |