1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/password_manager/password_store_mac.h"
6 #include "chrome/browser/password_manager/password_store_mac_internal.h"
8 #include <CoreServices/CoreServices.h>
14 #include "base/callback.h"
15 #include "base/logging.h"
16 #include "base/mac/mac_logging.h"
17 #include "base/mac/mac_util.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/stl_util.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "chrome/browser/mac/security_wrappers.h"
23 #include "components/password_manager/core/browser/login_database.h"
24 #include "components/password_manager/core/browser/password_store_change.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "crypto/apple_keychain.h"
28 using autofill::PasswordForm;
29 using crypto::AppleKeychain;
31 // Utility class to handle the details of constructing and running a keychain
32 // search from a set of attributes.
33 class KeychainSearch {
35 explicit KeychainSearch(const AppleKeychain& keychain);
38 // Sets up a keycahin search based on an non "null" (NULL for char*,
39 // The appropriate "Any" entry for other types) arguments.
41 // IMPORTANT: Any paramaters passed in *must* remain valid for as long as the
42 // KeychainSearch object, since the search uses them by reference.
43 void Init(const char* server, const UInt32& port,
44 const SecProtocolType& protocol,
45 const SecAuthenticationType& auth_type, const char* security_domain,
46 const char* path, const char* username, OSType creator);
48 // Fills |items| with all Keychain items that match the Init'd search.
49 // If the search fails for any reason, |items| will be unchanged.
50 void FindMatchingItems(std::vector<SecKeychainItemRef>* matches);
53 const AppleKeychain* keychain_;
54 SecKeychainAttributeList search_attributes_;
55 SecKeychainSearchRef search_ref_;
58 KeychainSearch::KeychainSearch(const AppleKeychain& keychain)
59 : keychain_(&keychain), search_ref_(NULL) {
60 search_attributes_.count = 0;
61 search_attributes_.attr = NULL;
64 KeychainSearch::~KeychainSearch() {
65 if (search_attributes_.attr) {
66 free(search_attributes_.attr);
70 void KeychainSearch::Init(const char* server, const UInt32& port,
71 const SecProtocolType& protocol,
72 const SecAuthenticationType& auth_type,
73 const char* security_domain, const char* path,
74 const char* username, OSType creator) {
75 // Allocate enough to hold everything we might use.
76 const unsigned int kMaxEntryCount = 8;
77 search_attributes_.attr =
78 static_cast<SecKeychainAttribute*>(calloc(kMaxEntryCount,
79 sizeof(SecKeychainAttribute)));
80 unsigned int entries = 0;
81 // We only use search_attributes_ with SearchCreateFromAttributes, which takes
82 // a "const SecKeychainAttributeList *", so we trust that they won't try
83 // to modify the list, and that casting away const-ness is thus safe.
85 DCHECK_LT(entries, kMaxEntryCount);
86 search_attributes_.attr[entries].tag = kSecServerItemAttr;
87 search_attributes_.attr[entries].length = strlen(server);
88 search_attributes_.attr[entries].data =
89 const_cast<void*>(reinterpret_cast<const void*>(server));
92 if (port != kAnyPort) {
93 DCHECK_LE(entries, kMaxEntryCount);
94 search_attributes_.attr[entries].tag = kSecPortItemAttr;
95 search_attributes_.attr[entries].length = sizeof(port);
96 search_attributes_.attr[entries].data =
97 const_cast<void*>(reinterpret_cast<const void*>(&port));
100 if (protocol != kSecProtocolTypeAny) {
101 DCHECK_LE(entries, kMaxEntryCount);
102 search_attributes_.attr[entries].tag = kSecProtocolItemAttr;
103 search_attributes_.attr[entries].length = sizeof(protocol);
104 search_attributes_.attr[entries].data =
105 const_cast<void*>(reinterpret_cast<const void*>(&protocol));
108 if (auth_type != kSecAuthenticationTypeAny) {
109 DCHECK_LE(entries, kMaxEntryCount);
110 search_attributes_.attr[entries].tag = kSecAuthenticationTypeItemAttr;
111 search_attributes_.attr[entries].length = sizeof(auth_type);
112 search_attributes_.attr[entries].data =
113 const_cast<void*>(reinterpret_cast<const void*>(&auth_type));
116 if (security_domain != NULL && strlen(security_domain) > 0) {
117 DCHECK_LE(entries, kMaxEntryCount);
118 search_attributes_.attr[entries].tag = kSecSecurityDomainItemAttr;
119 search_attributes_.attr[entries].length = strlen(security_domain);
120 search_attributes_.attr[entries].data =
121 const_cast<void*>(reinterpret_cast<const void*>(security_domain));
124 if (path != NULL && strlen(path) > 0 && strcmp(path, "/") != 0) {
125 DCHECK_LE(entries, kMaxEntryCount);
126 search_attributes_.attr[entries].tag = kSecPathItemAttr;
127 search_attributes_.attr[entries].length = strlen(path);
128 search_attributes_.attr[entries].data =
129 const_cast<void*>(reinterpret_cast<const void*>(path));
132 if (username != NULL) {
133 DCHECK_LE(entries, kMaxEntryCount);
134 search_attributes_.attr[entries].tag = kSecAccountItemAttr;
135 search_attributes_.attr[entries].length = strlen(username);
136 search_attributes_.attr[entries].data =
137 const_cast<void*>(reinterpret_cast<const void*>(username));
141 DCHECK_LE(entries, kMaxEntryCount);
142 search_attributes_.attr[entries].tag = kSecCreatorItemAttr;
143 search_attributes_.attr[entries].length = sizeof(creator);
144 search_attributes_.attr[entries].data =
145 const_cast<void*>(reinterpret_cast<const void*>(&creator));
148 search_attributes_.count = entries;
151 void KeychainSearch::FindMatchingItems(std::vector<SecKeychainItemRef>* items) {
152 OSStatus result = keychain_->SearchCreateFromAttributes(
153 NULL, kSecInternetPasswordItemClass, &search_attributes_, &search_ref_);
155 if (result != noErr) {
156 OSSTATUS_LOG(ERROR, result) << "Keychain lookup failed";
160 SecKeychainItemRef keychain_item;
161 while (keychain_->SearchCopyNext(search_ref_, &keychain_item) == noErr) {
162 // Consumer is responsible for freeing the items.
163 items->push_back(keychain_item);
166 keychain_->Free(search_ref_);
172 // TODO(stuartmorgan): Convert most of this to private helpers in
173 // MacKeychainPasswordFormAdapter once it has sufficient higher-level public
174 // methods to provide test coverage.
175 namespace internal_keychain_helpers {
177 // Returns a URL built from the given components. To create a URL without a
178 // port, pass kAnyPort for the |port| parameter.
179 GURL URLFromComponents(bool is_secure, const std::string& host, int port,
180 const std::string& path) {
181 GURL::Replacements url_components;
182 std::string scheme(is_secure ? "https" : "http");
183 url_components.SetSchemeStr(scheme);
184 url_components.SetHostStr(host);
185 std::string port_string; // Must remain in scope until after we do replacing.
186 if (port != kAnyPort) {
187 std::ostringstream port_stringstream;
188 port_stringstream << port;
189 port_string = port_stringstream.str();
190 url_components.SetPortStr(port_string);
192 url_components.SetPathStr(path);
194 GURL url("http://dummy.com"); // ReplaceComponents needs a valid URL.
195 return url.ReplaceComponents(url_components);
198 // Converts a Keychain time string to a Time object, returning true if
199 // time_string_bytes was parsable. If the return value is false, the value of
200 // |time| is unchanged.
201 bool TimeFromKeychainTimeString(const char* time_string_bytes,
202 unsigned int byte_length,
206 char* time_string = static_cast<char*>(malloc(byte_length + 1));
207 memcpy(time_string, time_string_bytes, byte_length);
208 time_string[byte_length] = '\0';
209 base::Time::Exploded exploded_time;
210 bzero(&exploded_time, sizeof(exploded_time));
211 // The time string is of the form "yyyyMMddHHmmss'Z", in UTC time.
212 int assignments = sscanf(time_string, "%4d%2d%2d%2d%2d%2dZ",
213 &exploded_time.year, &exploded_time.month,
214 &exploded_time.day_of_month, &exploded_time.hour,
215 &exploded_time.minute, &exploded_time.second);
218 if (assignments == 6) {
219 *time = base::Time::FromUTCExploded(exploded_time);
225 // Returns the PasswordForm Scheme corresponding to |auth_type|.
226 PasswordForm::Scheme SchemeForAuthType(SecAuthenticationType auth_type) {
228 case kSecAuthenticationTypeHTMLForm: return PasswordForm::SCHEME_HTML;
229 case kSecAuthenticationTypeHTTPBasic: return PasswordForm::SCHEME_BASIC;
230 case kSecAuthenticationTypeHTTPDigest: return PasswordForm::SCHEME_DIGEST;
231 default: return PasswordForm::SCHEME_OTHER;
235 bool FillPasswordFormFromKeychainItem(const AppleKeychain& keychain,
236 const SecKeychainItemRef& keychain_item,
238 bool extract_password_data) {
241 SecKeychainAttributeInfo attrInfo;
242 UInt32 tags[] = { kSecAccountItemAttr,
246 kSecProtocolItemAttr,
247 kSecAuthenticationTypeItemAttr,
248 kSecSecurityDomainItemAttr,
249 kSecCreationDateItemAttr,
250 kSecNegativeItemAttr };
251 attrInfo.count = arraysize(tags);
253 attrInfo.format = NULL;
255 SecKeychainAttributeList *attrList;
256 UInt32 password_length;
258 // If |extract_password_data| is false, do not pass in a reference to
259 // |password_data|. ItemCopyAttributesAndData will then extract only the
260 // attributes of |keychain_item| (doesn't require OS authorization), and not
261 // attempt to extract its password data (requires OS authorization).
262 void* password_data = NULL;
263 void** password_data_ref = extract_password_data ? &password_data : NULL;
265 OSStatus result = keychain.ItemCopyAttributesAndData(keychain_item, &attrInfo,
270 if (result != noErr) {
271 // We don't log errSecAuthFailed because that just means that the user
272 // chose not to allow us access to the item.
273 if (result != errSecAuthFailed) {
274 OSSTATUS_LOG(ERROR, result) << "Keychain data load failed";
279 if (extract_password_data) {
280 base::UTF8ToUTF16(static_cast<const char *>(password_data), password_length,
281 &(form->password_value));
286 std::string security_domain;
288 for (unsigned int i = 0; i < attrList->count; i++) {
289 SecKeychainAttribute attr = attrList->attr[i];
294 case kSecAccountItemAttr:
295 base::UTF8ToUTF16(static_cast<const char *>(attr.data), attr.length,
296 &(form->username_value));
298 case kSecServerItemAttr:
299 server.assign(static_cast<const char *>(attr.data), attr.length);
301 case kSecPortItemAttr:
302 port = *(static_cast<UInt32*>(attr.data));
304 case kSecPathItemAttr:
305 path.assign(static_cast<const char *>(attr.data), attr.length);
307 case kSecProtocolItemAttr:
309 SecProtocolType protocol = *(static_cast<SecProtocolType*>(attr.data));
310 // TODO(stuartmorgan): Handle proxy types
311 form->ssl_valid = (protocol == kSecProtocolTypeHTTPS);
314 case kSecAuthenticationTypeItemAttr:
316 SecAuthenticationType auth_type =
317 *(static_cast<SecAuthenticationType*>(attr.data));
318 form->scheme = SchemeForAuthType(auth_type);
321 case kSecSecurityDomainItemAttr:
322 security_domain.assign(static_cast<const char *>(attr.data),
325 case kSecCreationDateItemAttr:
326 // The only way to get a date out of Keychain is as a string. Really.
327 // (The docs claim it's an int, but the header is correct.)
328 TimeFromKeychainTimeString(static_cast<char*>(attr.data), attr.length,
329 &form->date_created);
331 case kSecNegativeItemAttr:
332 Boolean negative_item = *(static_cast<Boolean*>(attr.data));
334 form->blacklisted_by_user = true;
339 keychain.ItemFreeAttributesAndData(attrList, password_data);
341 // kSecNegativeItemAttr doesn't seem to actually be in widespread use. In
342 // practice, other browsers seem to use a "" or " " password (and a special
343 // user name) to indicated blacklist entries.
344 if (extract_password_data && (form->password_value.empty() ||
345 EqualsASCII(form->password_value, " "))) {
346 form->blacklisted_by_user = true;
349 form->origin = URLFromComponents(form->ssl_valid, server, port, path);
350 // TODO(stuartmorgan): Handle proxies, which need a different signon_realm
352 form->signon_realm = form->origin.GetOrigin().spec();
353 if (form->scheme != PasswordForm::SCHEME_HTML) {
354 form->signon_realm.append(security_domain);
359 bool FormsMatchForMerge(const PasswordForm& form_a,
360 const PasswordForm& form_b,
361 FormMatchStrictness strictness) {
362 // We never merge blacklist entries between our store and the keychain.
363 if (form_a.blacklisted_by_user || form_b.blacklisted_by_user) {
366 bool equal_realm = form_a.signon_realm == form_b.signon_realm;
367 if (strictness == FUZZY_FORM_MATCH) {
368 equal_realm |= (!form_a.original_signon_realm.empty()) &&
369 form_a.original_signon_realm == form_b.signon_realm;
371 return form_a.scheme == form_b.scheme && equal_realm &&
372 form_a.username_value == form_b.username_value;
375 // Returns an the best match for |base_form| from |keychain_forms|, or NULL if
376 // there is no suitable match.
377 PasswordForm* BestKeychainFormForForm(
378 const PasswordForm& base_form,
379 const std::vector<PasswordForm*>* keychain_forms) {
380 PasswordForm* partial_match = NULL;
381 for (std::vector<PasswordForm*>::const_iterator i = keychain_forms->begin();
382 i != keychain_forms->end(); ++i) {
383 // TODO(stuartmorgan): We should really be scoring path matches and picking
384 // the best, rather than just checking exact-or-not (although in practice
385 // keychain items with paths probably came from us).
386 if (FormsMatchForMerge(base_form, *(*i), FUZZY_FORM_MATCH)) {
387 if (base_form.origin == (*i)->origin) {
389 } else if (!partial_match) {
394 return partial_match;
397 // Returns entries from |forms| that are blacklist entries, after removing
398 // them from |forms|.
399 std::vector<PasswordForm*> ExtractBlacklistForms(
400 std::vector<PasswordForm*>* forms) {
401 std::vector<PasswordForm*> blacklist_forms;
402 for (std::vector<PasswordForm*>::iterator i = forms->begin();
403 i != forms->end();) {
404 PasswordForm* form = *i;
405 if (form->blacklisted_by_user) {
406 blacklist_forms.push_back(form);
412 return blacklist_forms;
415 // Deletes and removes from v any element that exists in s.
417 void DeleteVectorElementsInSet(std::vector<T*>* v, const std::set<T*>& s) {
418 for (typename std::vector<T*>::iterator i = v->begin(); i != v->end();) {
420 if (s.find(element) != s.end()) {
429 void MergePasswordForms(std::vector<PasswordForm*>* keychain_forms,
430 std::vector<PasswordForm*>* database_forms,
431 std::vector<PasswordForm*>* merged_forms) {
432 // Pull out the database blacklist items, since they are used as-is rather
433 // than being merged with keychain forms.
434 std::vector<PasswordForm*> database_blacklist_forms =
435 ExtractBlacklistForms(database_forms);
437 // Merge the normal entries.
438 std::set<PasswordForm*> used_keychain_forms;
439 for (std::vector<PasswordForm*>::iterator i = database_forms->begin();
440 i != database_forms->end();) {
441 PasswordForm* db_form = *i;
442 PasswordForm* best_match = BestKeychainFormForForm(*db_form,
445 used_keychain_forms.insert(best_match);
446 db_form->password_value = best_match->password_value;
447 merged_forms->push_back(db_form);
448 i = database_forms->erase(i);
454 // Add in the blacklist entries from the database.
455 merged_forms->insert(merged_forms->end(),
456 database_blacklist_forms.begin(),
457 database_blacklist_forms.end());
459 // Clear out all the Keychain entries we used.
460 DeleteVectorElementsInSet(keychain_forms, used_keychain_forms);
463 std::vector<ItemFormPair> ExtractAllKeychainItemAttributesIntoPasswordForms(
464 std::vector<SecKeychainItemRef>* keychain_items,
465 const AppleKeychain& keychain) {
466 DCHECK(keychain_items);
467 MacKeychainPasswordFormAdapter keychain_adapter(&keychain);
468 *keychain_items = keychain_adapter.GetAllPasswordFormKeychainItems();
469 std::vector<ItemFormPair> item_form_pairs;
470 for (std::vector<SecKeychainItemRef>::iterator i = keychain_items->begin();
471 i != keychain_items->end(); ++i) {
472 PasswordForm* form_without_password = new PasswordForm();
473 internal_keychain_helpers::FillPasswordFormFromKeychainItem(
476 form_without_password,
477 false); // Load password attributes, but not password data.
478 item_form_pairs.push_back(std::make_pair(&(*i), form_without_password));
480 return item_form_pairs;
483 std::vector<PasswordForm*> GetPasswordsForForms(
484 const AppleKeychain& keychain,
485 std::vector<PasswordForm*>* database_forms) {
486 // First load the attributes of all items in the keychain without loading
487 // their password data, and then match items in |database_forms| against them.
488 // This avoids individually searching through the keychain for passwords
489 // matching each form in |database_forms|, and results in a significant
490 // performance gain, replacing O(N) keychain search operations with a single
491 // operation that loads all keychain items, and then selective reads of only
492 // the relevant passwords. See crbug.com/263685.
493 std::vector<SecKeychainItemRef> keychain_items;
494 std::vector<ItemFormPair> item_form_pairs =
495 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items,
498 // Next, compare the attributes of the PasswordForms in |database_forms|
499 // against those in |item_form_pairs|, and extract password data for each
500 // matching PasswordForm using its corresponding SecKeychainItemRef.
501 std::vector<PasswordForm*> merged_forms;
502 for (std::vector<PasswordForm*>::iterator i = database_forms->begin();
503 i != database_forms->end();) {
504 std::vector<PasswordForm*> db_form_container(1, *i);
505 std::vector<PasswordForm*> keychain_matches =
506 ExtractPasswordsMergeableWithForm(keychain, item_form_pairs, **i);
507 MergePasswordForms(&keychain_matches, &db_form_container, &merged_forms);
508 if (db_form_container.empty()) {
509 i = database_forms->erase(i);
513 STLDeleteElements(&keychain_matches);
516 // Clean up temporary PasswordForms and SecKeychainItemRefs.
517 STLDeleteContainerPairSecondPointers(item_form_pairs.begin(),
518 item_form_pairs.end());
519 for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin();
520 i != keychain_items.end(); ++i) {
526 // TODO(stuartmorgan): signon_realm for proxies is not yet supported.
527 bool ExtractSignonRealmComponents(
528 const std::string& signon_realm, std::string* server, int* port,
529 bool* is_secure, std::string* security_domain) {
530 // The signon_realm will be the Origin portion of a URL for an HTML form,
531 // and the same but with the security domain as a path for HTTP auth.
532 GURL realm_as_url(signon_realm);
533 if (!realm_as_url.is_valid()) {
538 *server = realm_as_url.host();
540 *is_secure = realm_as_url.SchemeIsSecure();
542 *port = realm_as_url.has_port() ? atoi(realm_as_url.port().c_str()) : 0;
543 if (security_domain) {
544 // Strip the leading '/' off of the path to get the security domain.
545 if (realm_as_url.path().length() > 0)
546 *security_domain = realm_as_url.path().substr(1);
548 security_domain->clear();
553 bool FormIsValidAndMatchesOtherForm(const PasswordForm& query_form,
554 const PasswordForm& other_form) {
556 std::string security_domain;
559 if (!ExtractSignonRealmComponents(query_form.signon_realm, &server, &port,
560 &is_secure, &security_domain)) {
563 return internal_keychain_helpers::FormsMatchForMerge(
564 query_form, other_form, STRICT_FORM_MATCH);
567 std::vector<PasswordForm*> ExtractPasswordsMergeableWithForm(
568 const AppleKeychain& keychain,
569 const std::vector<ItemFormPair>& item_form_pairs,
570 const PasswordForm& query_form) {
571 std::vector<PasswordForm*> matches;
572 for (std::vector<ItemFormPair>::const_iterator i = item_form_pairs.begin();
573 i != item_form_pairs.end(); ++i) {
574 if (FormIsValidAndMatchesOtherForm(query_form, *(i->second))) {
575 // Create a new object, since the caller is responsible for deleting the
577 scoped_ptr<PasswordForm> form_with_password(new PasswordForm());
578 internal_keychain_helpers::FillPasswordFormFromKeychainItem(
581 form_with_password.get(),
582 true); // Load password attributes and data.
583 // Do not include blacklisted items found in the keychain.
584 if (!form_with_password->blacklisted_by_user)
585 matches.push_back(form_with_password.release());
591 } // namespace internal_keychain_helpers
595 MacKeychainPasswordFormAdapter::MacKeychainPasswordFormAdapter(
596 const AppleKeychain* keychain)
597 : keychain_(keychain), finds_only_owned_(false) {
600 std::vector<PasswordForm*> MacKeychainPasswordFormAdapter::PasswordsFillingForm(
601 const std::string& signon_realm,
602 PasswordForm::Scheme scheme) {
603 std::vector<SecKeychainItemRef> keychain_items =
604 MatchingKeychainItems(signon_realm, scheme, NULL, NULL);
606 return ConvertKeychainItemsToForms(&keychain_items);
609 PasswordForm* MacKeychainPasswordFormAdapter::PasswordExactlyMatchingForm(
610 const PasswordForm& query_form) {
611 SecKeychainItemRef keychain_item = KeychainItemForForm(query_form);
613 PasswordForm* form = new PasswordForm();
614 internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_,
618 keychain_->Free(keychain_item);
624 bool MacKeychainPasswordFormAdapter::HasPasswordsMergeableWithForm(
625 const PasswordForm& query_form) {
626 std::string username = base::UTF16ToUTF8(query_form.username_value);
627 std::vector<SecKeychainItemRef> matches =
628 MatchingKeychainItems(query_form.signon_realm, query_form.scheme,
629 NULL, username.c_str());
630 for (std::vector<SecKeychainItemRef>::iterator i = matches.begin();
631 i != matches.end(); ++i) {
635 return !matches.empty();
638 std::vector<SecKeychainItemRef>
639 MacKeychainPasswordFormAdapter::GetAllPasswordFormKeychainItems() {
640 SecAuthenticationType supported_auth_types[] = {
641 kSecAuthenticationTypeHTMLForm,
642 kSecAuthenticationTypeHTTPBasic,
643 kSecAuthenticationTypeHTTPDigest,
646 std::vector<SecKeychainItemRef> matches;
647 for (unsigned int i = 0; i < arraysize(supported_auth_types); ++i) {
648 KeychainSearch keychain_search(*keychain_);
649 keychain_search.Init(NULL, 0, kSecProtocolTypeAny, supported_auth_types[i],
650 NULL, NULL, NULL, CreatorCodeForSearch());
651 keychain_search.FindMatchingItems(&matches);
656 std::vector<PasswordForm*>
657 MacKeychainPasswordFormAdapter::GetAllPasswordFormPasswords() {
658 std::vector<SecKeychainItemRef> items = GetAllPasswordFormKeychainItems();
659 return ConvertKeychainItemsToForms(&items);
662 bool MacKeychainPasswordFormAdapter::AddPassword(const PasswordForm& form) {
663 // We should never be trying to store a blacklist in the keychain.
664 DCHECK(!form.blacklisted_by_user);
667 std::string security_domain;
670 if (!internal_keychain_helpers::ExtractSignonRealmComponents(
671 form.signon_realm, &server, &port, &is_secure, &security_domain)) {
674 std::string username = base::UTF16ToUTF8(form.username_value);
675 std::string password = base::UTF16ToUTF8(form.password_value);
676 std::string path = form.origin.path();
677 SecProtocolType protocol = is_secure ? kSecProtocolTypeHTTPS
678 : kSecProtocolTypeHTTP;
679 SecKeychainItemRef new_item = NULL;
680 OSStatus result = keychain_->AddInternetPassword(
681 NULL, server.size(), server.c_str(),
682 security_domain.size(), security_domain.c_str(),
683 username.size(), username.c_str(),
684 path.size(), path.c_str(),
685 port, protocol, AuthTypeForScheme(form.scheme),
686 password.size(), password.c_str(), &new_item);
688 if (result == noErr) {
689 SetKeychainItemCreatorCode(new_item,
690 base::mac::CreatorCodeForApplication());
691 keychain_->Free(new_item);
692 } else if (result == errSecDuplicateItem) {
693 // If we collide with an existing item, find and update it instead.
694 SecKeychainItemRef existing_item = KeychainItemForForm(form);
695 if (!existing_item) {
698 bool changed = SetKeychainItemPassword(existing_item, password);
699 keychain_->Free(existing_item);
703 return result == noErr;
706 bool MacKeychainPasswordFormAdapter::RemovePassword(const PasswordForm& form) {
707 SecKeychainItemRef keychain_item = KeychainItemForForm(form);
708 if (keychain_item == NULL)
710 OSStatus result = keychain_->ItemDelete(keychain_item);
711 keychain_->Free(keychain_item);
712 return result == noErr;
715 void MacKeychainPasswordFormAdapter::SetFindsOnlyOwnedItems(
716 bool finds_only_owned) {
717 finds_only_owned_ = finds_only_owned;
720 std::vector<PasswordForm*>
721 MacKeychainPasswordFormAdapter::ConvertKeychainItemsToForms(
722 std::vector<SecKeychainItemRef>* items) {
723 std::vector<PasswordForm*> keychain_forms;
724 for (std::vector<SecKeychainItemRef>::const_iterator i = items->begin();
725 i != items->end(); ++i) {
726 PasswordForm* form = new PasswordForm();
727 if (internal_keychain_helpers::FillPasswordFormFromKeychainItem(
728 *keychain_, *i, form, true)) {
729 keychain_forms.push_back(form);
734 return keychain_forms;
737 SecKeychainItemRef MacKeychainPasswordFormAdapter::KeychainItemForForm(
738 const PasswordForm& form) {
739 // We don't store blacklist entries in the keychain, so the answer to "what
740 // Keychain item goes with this form" is always "nothing" for blacklists.
741 if (form.blacklisted_by_user) {
745 std::string path = form.origin.path();
746 std::string username = base::UTF16ToUTF8(form.username_value);
747 std::vector<SecKeychainItemRef> matches = MatchingKeychainItems(
748 form.signon_realm, form.scheme, path.c_str(), username.c_str());
750 if (matches.empty()) {
753 // Free all items after the first, since we won't be returning them.
754 for (std::vector<SecKeychainItemRef>::iterator i = matches.begin() + 1;
755 i != matches.end(); ++i) {
761 std::vector<SecKeychainItemRef>
762 MacKeychainPasswordFormAdapter::MatchingKeychainItems(
763 const std::string& signon_realm,
764 autofill::PasswordForm::Scheme scheme,
765 const char* path, const char* username) {
766 std::vector<SecKeychainItemRef> matches;
769 std::string security_domain;
772 if (!internal_keychain_helpers::ExtractSignonRealmComponents(
773 signon_realm, &server, &port, &is_secure, &security_domain)) {
774 // TODO(stuartmorgan): Proxies will currently fail here, since their
775 // signon_realm is not a URL. We need to detect the proxy case and handle
779 SecProtocolType protocol = is_secure ? kSecProtocolTypeHTTPS
780 : kSecProtocolTypeHTTP;
781 SecAuthenticationType auth_type = AuthTypeForScheme(scheme);
782 const char* auth_domain = (scheme == PasswordForm::SCHEME_HTML) ?
783 NULL : security_domain.c_str();
784 KeychainSearch keychain_search(*keychain_);
785 keychain_search.Init(server.c_str(), port, protocol, auth_type,
786 auth_domain, path, username, CreatorCodeForSearch());
787 keychain_search.FindMatchingItems(&matches);
791 // Returns the Keychain SecAuthenticationType type corresponding to |scheme|.
792 SecAuthenticationType MacKeychainPasswordFormAdapter::AuthTypeForScheme(
793 PasswordForm::Scheme scheme) {
795 case PasswordForm::SCHEME_HTML: return kSecAuthenticationTypeHTMLForm;
796 case PasswordForm::SCHEME_BASIC: return kSecAuthenticationTypeHTTPBasic;
797 case PasswordForm::SCHEME_DIGEST: return kSecAuthenticationTypeHTTPDigest;
798 case PasswordForm::SCHEME_OTHER: return kSecAuthenticationTypeDefault;
801 return kSecAuthenticationTypeDefault;
804 bool MacKeychainPasswordFormAdapter::SetKeychainItemPassword(
805 const SecKeychainItemRef& keychain_item, const std::string& password) {
806 OSStatus result = keychain_->ItemModifyAttributesAndData(keychain_item, NULL,
809 return result == noErr;
812 bool MacKeychainPasswordFormAdapter::SetKeychainItemCreatorCode(
813 const SecKeychainItemRef& keychain_item, OSType creator_code) {
814 SecKeychainAttribute attr = { kSecCreatorItemAttr, sizeof(creator_code),
816 SecKeychainAttributeList attrList = { 1, &attr };
817 OSStatus result = keychain_->ItemModifyAttributesAndData(keychain_item,
819 return result == noErr;
822 OSType MacKeychainPasswordFormAdapter::CreatorCodeForSearch() {
823 return finds_only_owned_ ? base::mac::CreatorCodeForApplication() : 0;
828 PasswordStoreMac::PasswordStoreMac(
829 scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner,
830 scoped_refptr<base::SingleThreadTaskRunner> db_thread_runner,
831 AppleKeychain* keychain,
832 LoginDatabase* login_db)
833 : PasswordStore(main_thread_runner, db_thread_runner),
835 login_metadata_db_(login_db) {
836 DCHECK(keychain_.get());
837 DCHECK(login_metadata_db_.get());
840 PasswordStoreMac::~PasswordStoreMac() {}
842 bool PasswordStoreMac::Init(
843 const syncer::SyncableService::StartSyncFlare& flare) {
844 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
845 thread_.reset(new base::Thread("Chrome_PasswordStore_Thread"));
847 if (!thread_->Start()) {
851 return PasswordStore::Init(flare);
854 void PasswordStoreMac::Shutdown() {
855 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
856 PasswordStore::Shutdown();
860 // Mac stores passwords in the system keychain, which can block for an
861 // arbitrarily long time (most notably, it can block on user confirmation
862 // from a dialog). Run tasks on a dedicated thread to avoid blocking the DB
864 scoped_refptr<base::SingleThreadTaskRunner>
865 PasswordStoreMac::GetBackgroundTaskRunner() {
866 return (thread_.get()) ? thread_->message_loop_proxy() : NULL;
869 void PasswordStoreMac::ReportMetricsImpl() {
870 login_metadata_db_->ReportMetrics();
873 PasswordStoreChangeList PasswordStoreMac::AddLoginImpl(
874 const PasswordForm& form) {
875 PasswordStoreChangeList changes;
876 if (AddToKeychainIfNecessary(form)) {
877 if (login_metadata_db_->AddLogin(form)) {
878 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
884 PasswordStoreChangeList PasswordStoreMac::UpdateLoginImpl(
885 const PasswordForm& form) {
886 PasswordStoreChangeList changes;
887 int update_count = 0;
888 if (!login_metadata_db_->UpdateLogin(form, &update_count))
891 MacKeychainPasswordFormAdapter keychain_adapter(keychain_.get());
892 if (update_count == 0 &&
893 !keychain_adapter.HasPasswordsMergeableWithForm(form)) {
894 // If the password isn't in either the DB or the keychain, then it must have
895 // been deleted after autofill happened, and should not be re-added.
899 // The keychain add will update if there is a collision and add if there
900 // isn't, which is the behavior we want, so there's no separate update call.
901 if (AddToKeychainIfNecessary(form)) {
902 if (update_count == 0) {
903 if (login_metadata_db_->AddLogin(form)) {
904 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD,
908 changes.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE,
915 PasswordStoreChangeList PasswordStoreMac::RemoveLoginImpl(
916 const PasswordForm& form) {
917 PasswordStoreChangeList changes;
918 if (login_metadata_db_->RemoveLogin(form)) {
919 // See if we own a Keychain item associated with this item. We can do an
920 // exact search rather than messing around with trying to do fuzzy matching
921 // because passwords that we created will always have an exact-match
923 // (If a user does lose their profile but not their keychain we'll treat the
924 // entries we find like other imported entries anyway, so it's reasonable to
925 // handle deletes on them the way we would for an imported item.)
926 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_.get());
927 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
928 PasswordForm* owned_password_form =
929 owned_keychain_adapter.PasswordExactlyMatchingForm(form);
930 if (owned_password_form) {
931 // If we don't have other forms using it (i.e., a form differing only by
932 // the names of the form elements), delete the keychain entry.
933 if (!DatabaseHasFormMatchingKeychainForm(form)) {
934 owned_keychain_adapter.RemovePassword(form);
938 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form));
943 PasswordStoreChangeList PasswordStoreMac::RemoveLoginsCreatedBetweenImpl(
944 const base::Time& delete_begin, const base::Time& delete_end) {
945 PasswordStoreChangeList changes;
946 std::vector<PasswordForm*> forms;
947 if (login_metadata_db_->GetLoginsCreatedBetween(delete_begin, delete_end,
949 if (login_metadata_db_->RemoveLoginsCreatedBetween(delete_begin,
951 // We can't delete from the Keychain by date because we may be sharing
952 // items with database entries that weren't in the delete range. Instead,
953 // we find all the Keychain items we own but aren't using any more and
955 std::vector<PasswordForm*> orphan_keychain_forms =
956 GetUnusedKeychainForms();
957 // This is inefficient, since we have to re-look-up each keychain item
958 // one at a time to delete it even though the search step already had a
959 // list of Keychain item references. If this turns out to be noticeably
960 // slow we'll need to rearchitect to allow the search and deletion steps
962 RemoveKeychainForms(orphan_keychain_forms);
963 STLDeleteElements(&orphan_keychain_forms);
965 for (std::vector<PasswordForm*>::const_iterator it = forms.begin();
966 it != forms.end(); ++it) {
967 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE,
970 LogStatsForBulkDeletion(changes.size());
976 void PasswordStoreMac::GetLoginsImpl(
977 const autofill::PasswordForm& form,
978 AuthorizationPromptPolicy prompt_policy,
979 const ConsumerCallbackRunner& callback_runner) {
980 chrome::ScopedSecKeychainSetUserInteractionAllowed user_interaction_allowed(
981 prompt_policy == ALLOW_PROMPT);
983 std::vector<PasswordForm*> database_forms;
984 login_metadata_db_->GetLogins(form, &database_forms);
986 // Let's gather all signon realms we want to match with keychain entries.
987 std::set<std::string> realm_set;
988 realm_set.insert(form.signon_realm);
989 for (std::vector<PasswordForm*>::const_iterator db_form =
990 database_forms.begin();
991 db_form != database_forms.end();
993 // TODO(vabr): We should not be getting different schemes here.
994 // http://crbug.com/340112
995 if (form.scheme != (*db_form)->scheme)
996 continue; // Forms with different schemes never match.
997 const std::string& original_singon_realm((*db_form)->original_signon_realm);
998 if (!original_singon_realm.empty())
999 realm_set.insert(original_singon_realm);
1001 std::vector<PasswordForm*> keychain_forms;
1002 for (std::set<std::string>::const_iterator realm = realm_set.begin();
1003 realm != realm_set.end();
1005 MacKeychainPasswordFormAdapter keychain_adapter(keychain_.get());
1006 std::vector<PasswordForm*> temp_keychain_forms =
1007 keychain_adapter.PasswordsFillingForm(*realm, form.scheme);
1008 keychain_forms.insert(keychain_forms.end(),
1009 temp_keychain_forms.begin(),
1010 temp_keychain_forms.end());
1013 std::vector<PasswordForm*> matched_forms;
1014 internal_keychain_helpers::MergePasswordForms(&keychain_forms,
1018 // Strip any blacklist entries out of the unused Keychain array, then take
1019 // all the entries that are left (which we can use as imported passwords).
1020 std::vector<PasswordForm*> keychain_blacklist_forms =
1021 internal_keychain_helpers::ExtractBlacklistForms(&keychain_forms);
1022 matched_forms.insert(matched_forms.end(),
1023 keychain_forms.begin(),
1024 keychain_forms.end());
1025 keychain_forms.clear();
1026 STLDeleteElements(&keychain_blacklist_forms);
1028 // Clean up any orphaned database entries.
1029 RemoveDatabaseForms(database_forms);
1030 STLDeleteElements(&database_forms);
1032 callback_runner.Run(matched_forms);
1035 void PasswordStoreMac::GetBlacklistLoginsImpl(GetLoginsRequest* request) {
1036 FillBlacklistLogins(request->result());
1037 ForwardLoginsResult(request);
1040 void PasswordStoreMac::GetAutofillableLoginsImpl(GetLoginsRequest* request) {
1041 FillAutofillableLogins(request->result());
1042 ForwardLoginsResult(request);
1045 bool PasswordStoreMac::FillAutofillableLogins(
1046 std::vector<PasswordForm*>* forms) {
1047 DCHECK(thread_->message_loop() == base::MessageLoop::current());
1049 std::vector<PasswordForm*> database_forms;
1050 login_metadata_db_->GetAutofillableLogins(&database_forms);
1052 std::vector<PasswordForm*> merged_forms =
1053 internal_keychain_helpers::GetPasswordsForForms(*keychain_,
1056 // Clean up any orphaned database entries.
1057 RemoveDatabaseForms(database_forms);
1058 STLDeleteElements(&database_forms);
1060 forms->insert(forms->end(), merged_forms.begin(), merged_forms.end());
1064 bool PasswordStoreMac::FillBlacklistLogins(
1065 std::vector<PasswordForm*>* forms) {
1066 DCHECK(thread_->message_loop() == base::MessageLoop::current());
1067 return login_metadata_db_->GetBlacklistLogins(forms);
1070 bool PasswordStoreMac::AddToKeychainIfNecessary(const PasswordForm& form) {
1071 if (form.blacklisted_by_user) {
1074 MacKeychainPasswordFormAdapter keychainAdapter(keychain_.get());
1075 return keychainAdapter.AddPassword(form);
1078 bool PasswordStoreMac::DatabaseHasFormMatchingKeychainForm(
1079 const autofill::PasswordForm& form) {
1080 bool has_match = false;
1081 std::vector<PasswordForm*> database_forms;
1082 login_metadata_db_->GetLogins(form, &database_forms);
1083 for (std::vector<PasswordForm*>::iterator i = database_forms.begin();
1084 i != database_forms.end(); ++i) {
1085 // Below we filter out forms with non-empty original_signon_realm, because
1086 // those signal fuzzy matches, and we are only interested in exact ones.
1087 if ((*i)->original_signon_realm.empty() &&
1088 internal_keychain_helpers::FormsMatchForMerge(
1089 form, **i, internal_keychain_helpers::STRICT_FORM_MATCH) &&
1090 (*i)->origin == form.origin) {
1095 STLDeleteElements(&database_forms);
1099 std::vector<PasswordForm*> PasswordStoreMac::GetUnusedKeychainForms() {
1100 std::vector<PasswordForm*> database_forms;
1101 login_metadata_db_->GetAutofillableLogins(&database_forms);
1103 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_.get());
1104 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1105 std::vector<PasswordForm*> owned_keychain_forms =
1106 owned_keychain_adapter.GetAllPasswordFormPasswords();
1108 // Run a merge; anything left in owned_keychain_forms when we are done no
1109 // longer has a matching database entry.
1110 std::vector<PasswordForm*> merged_forms;
1111 internal_keychain_helpers::MergePasswordForms(&owned_keychain_forms,
1114 STLDeleteElements(&merged_forms);
1115 STLDeleteElements(&database_forms);
1117 return owned_keychain_forms;
1120 void PasswordStoreMac::RemoveDatabaseForms(
1121 const std::vector<PasswordForm*>& forms) {
1122 for (std::vector<PasswordForm*>::const_iterator i = forms.begin();
1123 i != forms.end(); ++i) {
1124 login_metadata_db_->RemoveLogin(**i);
1128 void PasswordStoreMac::RemoveKeychainForms(
1129 const std::vector<PasswordForm*>& forms) {
1130 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_.get());
1131 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1132 for (std::vector<PasswordForm*>::const_iterator i = forms.begin();
1133 i != forms.end(); ++i) {
1134 owned_keychain_adapter.RemovePassword(**i);