summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/keychain_mac.cc37
-rw-r--r--chrome/browser/keychain_mac.h48
-rw-r--r--chrome/browser/password_manager/password_store_mac.cc333
-rw-r--r--chrome/browser/password_manager/password_store_mac.h30
-rw-r--r--chrome/browser/password_manager/password_store_mac_internal.h71
-rw-r--r--chrome/browser/password_manager/password_store_mac_unittest.cc710
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();
+ }
+}