Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(75)

Unified Diff: chrome/browser/password_manager/password_store_mac.cc

Issue 115658: First phase of Mac Keychain integration. For the overall plan, see the design... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/password_manager/password_store_mac.cc
===================================================================
--- chrome/browser/password_manager/password_store_mac.cc (revision 0)
+++ chrome/browser/password_manager/password_store_mac.cc (revision 0)
@@ -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);
+}
« no previous file with comments | « chrome/browser/password_manager/password_store_mac.h ('k') | chrome/browser/password_manager/password_store_mac_internal.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698