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/foundation_util.h"
17 #include "base/mac/mac_logging.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/affiliation_utils.h"
24 #include "components/password_manager/core/browser/login_database.h"
25 #include "components/password_manager/core/browser/password_store_change.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "crypto/apple_keychain.h"
29 using autofill::PasswordForm
;
30 using crypto::AppleKeychain
;
31 using password_manager::PasswordStoreChange
;
32 using password_manager::PasswordStoreChangeList
;
36 // Utility class to handle the details of constructing and running a keychain
37 // search from a set of attributes.
38 class KeychainSearch
{
40 explicit KeychainSearch(const AppleKeychain
& keychain
);
43 // Sets up a keycahin search based on an non "null" (NULL for char*,
44 // The appropriate "Any" entry for other types) arguments.
46 // IMPORTANT: Any paramaters passed in *must* remain valid for as long as the
47 // KeychainSearch object, since the search uses them by reference.
48 void Init(const char* server
,
50 const SecProtocolType
* protocol
,
51 const SecAuthenticationType
* auth_type
,
52 const char* security_domain
,
55 const OSType
* creator
);
57 // Fills |items| with all Keychain items that match the Init'd search.
58 // If the search fails for any reason, |items| will be unchanged.
59 void FindMatchingItems(std::vector
<SecKeychainItemRef
>* matches
);
62 const AppleKeychain
* keychain_
;
63 SecKeychainAttributeList search_attributes_
;
64 SecKeychainSearchRef search_ref_
;
67 KeychainSearch::KeychainSearch(const AppleKeychain
& keychain
)
68 : keychain_(&keychain
), search_ref_(NULL
) {
69 search_attributes_
.count
= 0;
70 search_attributes_
.attr
= NULL
;
73 KeychainSearch::~KeychainSearch() {
74 if (search_attributes_
.attr
) {
75 free(search_attributes_
.attr
);
79 void KeychainSearch::Init(const char* server
,
81 const SecProtocolType
* protocol
,
82 const SecAuthenticationType
* auth_type
,
83 const char* security_domain
,
86 const OSType
* creator
) {
87 // Allocate enough to hold everything we might use.
88 const unsigned int kMaxEntryCount
= 8;
89 search_attributes_
.attr
=
90 static_cast<SecKeychainAttribute
*>(calloc(kMaxEntryCount
,
91 sizeof(SecKeychainAttribute
)));
92 unsigned int entries
= 0;
93 // We only use search_attributes_ with SearchCreateFromAttributes, which takes
94 // a "const SecKeychainAttributeList *", so we trust that they won't try
95 // to modify the list, and that casting away const-ness is thus safe.
97 DCHECK_LT(entries
, kMaxEntryCount
);
98 search_attributes_
.attr
[entries
].tag
= kSecServerItemAttr
;
99 search_attributes_
.attr
[entries
].length
= strlen(server
);
100 search_attributes_
.attr
[entries
].data
=
101 const_cast<void*>(static_cast<const void*>(server
));
104 if (port
!= NULL
&& *port
!= kAnyPort
) {
105 DCHECK_LE(entries
, kMaxEntryCount
);
106 search_attributes_
.attr
[entries
].tag
= kSecPortItemAttr
;
107 search_attributes_
.attr
[entries
].length
= sizeof(*port
);
108 search_attributes_
.attr
[entries
].data
=
109 const_cast<void*>(static_cast<const void*>(port
));
112 if (protocol
!= NULL
&& *protocol
!= kSecProtocolTypeAny
) {
113 DCHECK_LE(entries
, kMaxEntryCount
);
114 search_attributes_
.attr
[entries
].tag
= kSecProtocolItemAttr
;
115 search_attributes_
.attr
[entries
].length
= sizeof(*protocol
);
116 search_attributes_
.attr
[entries
].data
=
117 const_cast<void*>(static_cast<const void*>(protocol
));
120 if (auth_type
!= NULL
&& *auth_type
!= kSecAuthenticationTypeAny
) {
121 DCHECK_LE(entries
, kMaxEntryCount
);
122 search_attributes_
.attr
[entries
].tag
= kSecAuthenticationTypeItemAttr
;
123 search_attributes_
.attr
[entries
].length
= sizeof(*auth_type
);
124 search_attributes_
.attr
[entries
].data
=
125 const_cast<void*>(static_cast<const void*>(auth_type
));
128 if (security_domain
!= NULL
&& strlen(security_domain
) > 0) {
129 DCHECK_LE(entries
, kMaxEntryCount
);
130 search_attributes_
.attr
[entries
].tag
= kSecSecurityDomainItemAttr
;
131 search_attributes_
.attr
[entries
].length
= strlen(security_domain
);
132 search_attributes_
.attr
[entries
].data
=
133 const_cast<void*>(static_cast<const void*>(security_domain
));
136 if (path
!= NULL
&& strlen(path
) > 0 && strcmp(path
, "/") != 0) {
137 DCHECK_LE(entries
, kMaxEntryCount
);
138 search_attributes_
.attr
[entries
].tag
= kSecPathItemAttr
;
139 search_attributes_
.attr
[entries
].length
= strlen(path
);
140 search_attributes_
.attr
[entries
].data
=
141 const_cast<void*>(static_cast<const void*>(path
));
144 if (username
!= NULL
) {
145 DCHECK_LE(entries
, kMaxEntryCount
);
146 search_attributes_
.attr
[entries
].tag
= kSecAccountItemAttr
;
147 search_attributes_
.attr
[entries
].length
= strlen(username
);
148 search_attributes_
.attr
[entries
].data
=
149 const_cast<void*>(static_cast<const void*>(username
));
152 if (creator
!= NULL
) {
153 DCHECK_LE(entries
, kMaxEntryCount
);
154 search_attributes_
.attr
[entries
].tag
= kSecCreatorItemAttr
;
155 search_attributes_
.attr
[entries
].length
= sizeof(*creator
);
156 search_attributes_
.attr
[entries
].data
=
157 const_cast<void*>(static_cast<const void*>(creator
));
160 search_attributes_
.count
= entries
;
163 void KeychainSearch::FindMatchingItems(std::vector
<SecKeychainItemRef
>* items
) {
164 OSStatus result
= keychain_
->SearchCreateFromAttributes(
165 NULL
, kSecInternetPasswordItemClass
, &search_attributes_
, &search_ref_
);
167 if (result
!= noErr
) {
168 OSSTATUS_LOG(ERROR
, result
) << "Keychain lookup failed";
172 SecKeychainItemRef keychain_item
;
173 while (keychain_
->SearchCopyNext(search_ref_
, &keychain_item
) == noErr
) {
174 // Consumer is responsible for freeing the items.
175 items
->push_back(keychain_item
);
178 keychain_
->Free(search_ref_
);
182 PasswordStoreChangeList
FormsToRemoveChangeList(
183 const std::vector
<PasswordForm
*>& forms
) {
184 PasswordStoreChangeList changes
;
185 for (std::vector
<PasswordForm
*>::const_iterator i
= forms
.begin();
186 i
!= forms
.end(); ++i
) {
187 changes
.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE
, **i
));
192 // Moves the content of |second| to the end of |first|.
193 void AppendSecondToFirst(ScopedVector
<autofill::PasswordForm
>* first
,
194 ScopedVector
<autofill::PasswordForm
>* second
) {
195 first
->insert(first
->end(), second
->begin(), second
->end());
196 second
->weak_clear();
199 // Returns the best match for |base_form| from |keychain_forms|, or nullptr if
200 // there is no suitable match.
201 const PasswordForm
* BestKeychainFormForForm(
202 const PasswordForm
& base_form
,
203 const std::vector
<PasswordForm
*>& keychain_forms
) {
204 const PasswordForm
* partial_match
= nullptr;
205 for (const auto* keychain_form
: keychain_forms
) {
206 // TODO(stuartmorgan): We should really be scoring path matches and picking
207 // the best, rather than just checking exact-or-not (although in practice
208 // keychain items with paths probably came from us).
209 if (internal_keychain_helpers::FormsMatchForMerge(
210 base_form
, *keychain_form
,
211 internal_keychain_helpers::FUZZY_FORM_MATCH
)) {
212 if (base_form
.origin
== keychain_form
->origin
) {
213 return keychain_form
;
214 } else if (!partial_match
) {
215 partial_match
= keychain_form
;
219 return partial_match
;
222 // Iterates over all elements in |forms|, passes the pointed to objects to
223 // |move_form|, and clears |forms| efficiently. FormMover needs to be a callable
224 // entity, accepting scoped_ptr<autofill::PasswordForm> as its sole argument.
225 template <typename FormMover
>
226 inline void MoveAllFormsOut(ScopedVector
<autofill::PasswordForm
>* forms
,
228 for (autofill::PasswordForm
* form_ptr
: *forms
) {
229 mover(scoped_ptr
<autofill::PasswordForm
>(form_ptr
));
231 // We moved the ownership of every form out of |forms|. For performance
232 // reasons, we can just weak_clear it, instead of nullptr-ing the respective
233 // elements and letting the vector's destructor to go through the list once
234 // more. This was tested on a benchmark, and seemed to make a difference on
243 // TODO(stuartmorgan): Convert most of this to private helpers in
244 // MacKeychainPasswordFormAdapter once it has sufficient higher-level public
245 // methods to provide test coverage.
246 namespace internal_keychain_helpers
{
248 // Returns a URL built from the given components. To create a URL without a
249 // port, pass kAnyPort for the |port| parameter.
250 GURL
URLFromComponents(bool is_secure
, const std::string
& host
, int port
,
251 const std::string
& path
) {
252 GURL::Replacements url_components
;
253 std::string
scheme(is_secure
? "https" : "http");
254 url_components
.SetSchemeStr(scheme
);
255 url_components
.SetHostStr(host
);
256 std::string port_string
; // Must remain in scope until after we do replacing.
257 if (port
!= kAnyPort
) {
258 std::ostringstream port_stringstream
;
259 port_stringstream
<< port
;
260 port_string
= port_stringstream
.str();
261 url_components
.SetPortStr(port_string
);
263 url_components
.SetPathStr(path
);
265 GURL
url("http://dummy.com"); // ReplaceComponents needs a valid URL.
266 return url
.ReplaceComponents(url_components
);
269 // Converts a Keychain time string to a Time object, returning true if
270 // time_string_bytes was parsable. If the return value is false, the value of
271 // |time| is unchanged.
272 bool TimeFromKeychainTimeString(const char* time_string_bytes
,
273 unsigned int byte_length
,
277 char* time_string
= static_cast<char*>(malloc(byte_length
+ 1));
278 memcpy(time_string
, time_string_bytes
, byte_length
);
279 time_string
[byte_length
] = '\0';
280 base::Time::Exploded exploded_time
;
281 bzero(&exploded_time
, sizeof(exploded_time
));
282 // The time string is of the form "yyyyMMddHHmmss'Z", in UTC time.
283 int assignments
= sscanf(time_string
, "%4d%2d%2d%2d%2d%2dZ",
284 &exploded_time
.year
, &exploded_time
.month
,
285 &exploded_time
.day_of_month
, &exploded_time
.hour
,
286 &exploded_time
.minute
, &exploded_time
.second
);
289 if (assignments
== 6) {
290 *time
= base::Time::FromUTCExploded(exploded_time
);
296 // Returns the PasswordForm Scheme corresponding to |auth_type|.
297 PasswordForm::Scheme
SchemeForAuthType(SecAuthenticationType auth_type
) {
299 case kSecAuthenticationTypeHTMLForm
: return PasswordForm::SCHEME_HTML
;
300 case kSecAuthenticationTypeHTTPBasic
: return PasswordForm::SCHEME_BASIC
;
301 case kSecAuthenticationTypeHTTPDigest
: return PasswordForm::SCHEME_DIGEST
;
302 default: return PasswordForm::SCHEME_OTHER
;
306 bool FillPasswordFormFromKeychainItem(const AppleKeychain
& keychain
,
307 const SecKeychainItemRef
& keychain_item
,
309 bool extract_password_data
) {
312 SecKeychainAttributeInfo attrInfo
;
313 UInt32 tags
[] = { kSecAccountItemAttr
,
317 kSecProtocolItemAttr
,
318 kSecAuthenticationTypeItemAttr
,
319 kSecSecurityDomainItemAttr
,
320 kSecCreationDateItemAttr
,
321 kSecNegativeItemAttr
};
322 attrInfo
.count
= arraysize(tags
);
324 attrInfo
.format
= NULL
;
326 SecKeychainAttributeList
*attrList
;
327 UInt32 password_length
;
329 // If |extract_password_data| is false, do not pass in a reference to
330 // |password_data|. ItemCopyAttributesAndData will then extract only the
331 // attributes of |keychain_item| (doesn't require OS authorization), and not
332 // attempt to extract its password data (requires OS authorization).
333 void* password_data
= NULL
;
334 void** password_data_ref
= extract_password_data
? &password_data
: NULL
;
336 OSStatus result
= keychain
.ItemCopyAttributesAndData(keychain_item
, &attrInfo
,
341 if (result
!= noErr
) {
342 // We don't log errSecAuthFailed because that just means that the user
343 // chose not to allow us access to the item.
344 if (result
!= errSecAuthFailed
) {
345 OSSTATUS_LOG(ERROR
, result
) << "Keychain data load failed";
350 if (extract_password_data
) {
351 base::UTF8ToUTF16(static_cast<const char *>(password_data
), password_length
,
352 &(form
->password_value
));
357 std::string security_domain
;
359 for (unsigned int i
= 0; i
< attrList
->count
; i
++) {
360 SecKeychainAttribute attr
= attrList
->attr
[i
];
365 case kSecAccountItemAttr
:
366 base::UTF8ToUTF16(static_cast<const char *>(attr
.data
), attr
.length
,
367 &(form
->username_value
));
369 case kSecServerItemAttr
:
370 server
.assign(static_cast<const char *>(attr
.data
), attr
.length
);
372 case kSecPortItemAttr
:
373 port
= *(static_cast<UInt32
*>(attr
.data
));
375 case kSecPathItemAttr
:
376 path
.assign(static_cast<const char *>(attr
.data
), attr
.length
);
378 case kSecProtocolItemAttr
:
380 SecProtocolType protocol
= *(static_cast<SecProtocolType
*>(attr
.data
));
381 // TODO(stuartmorgan): Handle proxy types
382 form
->ssl_valid
= (protocol
== kSecProtocolTypeHTTPS
);
385 case kSecAuthenticationTypeItemAttr
:
387 SecAuthenticationType auth_type
=
388 *(static_cast<SecAuthenticationType
*>(attr
.data
));
389 form
->scheme
= SchemeForAuthType(auth_type
);
392 case kSecSecurityDomainItemAttr
:
393 security_domain
.assign(static_cast<const char *>(attr
.data
),
396 case kSecCreationDateItemAttr
:
397 // The only way to get a date out of Keychain is as a string. Really.
398 // (The docs claim it's an int, but the header is correct.)
399 TimeFromKeychainTimeString(static_cast<char*>(attr
.data
), attr
.length
,
400 &form
->date_created
);
402 case kSecNegativeItemAttr
:
403 Boolean negative_item
= *(static_cast<Boolean
*>(attr
.data
));
405 form
->blacklisted_by_user
= true;
410 keychain
.ItemFreeAttributesAndData(attrList
, password_data
);
412 // kSecNegativeItemAttr doesn't seem to actually be in widespread use. In
413 // practice, other browsers seem to use a "" or " " password (and a special
414 // user name) to indicated blacklist entries.
415 if (extract_password_data
&& (form
->password_value
.empty() ||
416 EqualsASCII(form
->password_value
, " "))) {
417 form
->blacklisted_by_user
= true;
420 // Android facet URLs aren't parsed correctly by GURL and need to be handled
422 if (password_manager::IsValidAndroidFacetURI(server
)) {
423 form
->signon_realm
= server
;
424 form
->origin
= GURL();
425 form
->ssl_valid
= true;
427 form
->origin
= URLFromComponents(form
->ssl_valid
, server
, port
, path
);
428 // TODO(stuartmorgan): Handle proxies, which need a different signon_realm
430 form
->signon_realm
= form
->origin
.GetOrigin().spec();
431 if (form
->scheme
!= PasswordForm::SCHEME_HTML
) {
432 form
->signon_realm
.append(security_domain
);
438 bool FormsMatchForMerge(const PasswordForm
& form_a
,
439 const PasswordForm
& form_b
,
440 FormMatchStrictness strictness
) {
441 // We never merge blacklist entries between our store and the keychain.
442 if (form_a
.blacklisted_by_user
|| form_b
.blacklisted_by_user
) {
445 bool equal_realm
= form_a
.signon_realm
== form_b
.signon_realm
;
446 if (strictness
== FUZZY_FORM_MATCH
) {
447 equal_realm
|= (!form_a
.original_signon_realm
.empty()) &&
448 form_a
.original_signon_realm
== form_b
.signon_realm
;
450 return form_a
.scheme
== form_b
.scheme
&& equal_realm
&&
451 form_a
.username_value
== form_b
.username_value
;
454 // Moves entries from |forms| that are blacklist entries into |blacklist|.
455 void ExtractBlacklistForms(ScopedVector
<autofill::PasswordForm
>* forms
,
456 ScopedVector
<autofill::PasswordForm
>* blacklist
) {
457 blacklist
->reserve(blacklist
->size() + forms
->size());
458 ScopedVector
<autofill::PasswordForm
> non_blacklist
;
459 // Move forms in either |non_blacklist| or |blacklist|, depending on whether
460 // they are blacklisted by the user.
461 MoveAllFormsOut(forms
, [&non_blacklist
, blacklist
](
462 scoped_ptr
<autofill::PasswordForm
> form
) {
463 if (form
->blacklisted_by_user
)
464 blacklist
->push_back(form
.release());
466 non_blacklist
.push_back(form
.release());
468 forms
->swap(non_blacklist
);
471 // Takes |keychain_forms| and |database_forms| and moves the following 2 types
472 // of forms to |merged_forms|: (1) blacklisted |database_forms|, (2)
473 // |database_forms| which have a corresponding entry in |keychain_forms|. The
474 // database forms of type (2) have their password value updated from the
475 // corresponding keychain form, and all the keychain forms corresponding to some
476 // database form are removed from |keychain_forms| and deleted.
477 void MergePasswordForms(ScopedVector
<autofill::PasswordForm
>* keychain_forms
,
478 ScopedVector
<autofill::PasswordForm
>* database_forms
,
479 ScopedVector
<autofill::PasswordForm
>* merged_forms
) {
480 // Pull out the database blacklist items, since they are used as-is rather
481 // than being merged with keychain forms.
482 ExtractBlacklistForms(database_forms
, merged_forms
);
484 // Merge the normal entries.
485 ScopedVector
<autofill::PasswordForm
> unused_database_forms
;
486 unused_database_forms
.reserve(database_forms
->size());
487 std::set
<const autofill::PasswordForm
*> used_keychain_forms
;
488 // Move all database forms to either |merged_forms| or
489 // |unused_database_forms|, based on whether they have a match in the keychain
490 // forms or not. If there is a match, add its password to the DB form and
491 // mark the keychain form as used.
492 MoveAllFormsOut(database_forms
, [keychain_forms
, &used_keychain_forms
,
493 merged_forms
, &unused_database_forms
](
494 scoped_ptr
<autofill::PasswordForm
> form
) {
495 const PasswordForm
* best_match
=
496 BestKeychainFormForForm(*form
, keychain_forms
->get());
498 used_keychain_forms
.insert(best_match
);
499 form
->password_value
= best_match
->password_value
;
500 merged_forms
->push_back(form
.release());
502 unused_database_forms
.push_back(form
.release());
505 database_forms
->swap(unused_database_forms
);
507 // Clear out all the Keychain entries we used.
508 ScopedVector
<autofill::PasswordForm
> unused_keychain_forms
;
509 unused_keychain_forms
.reserve(keychain_forms
->size());
510 for (auto& keychain_form
: *keychain_forms
) {
511 if (!ContainsKey(used_keychain_forms
, keychain_form
)) {
512 unused_keychain_forms
.push_back(keychain_form
);
513 keychain_form
= nullptr;
516 keychain_forms
->swap(unused_keychain_forms
);
519 std::vector
<ItemFormPair
> ExtractAllKeychainItemAttributesIntoPasswordForms(
520 std::vector
<SecKeychainItemRef
>* keychain_items
,
521 const AppleKeychain
& keychain
) {
522 DCHECK(keychain_items
);
523 MacKeychainPasswordFormAdapter
keychain_adapter(&keychain
);
524 *keychain_items
= keychain_adapter
.GetAllPasswordFormKeychainItems();
525 std::vector
<ItemFormPair
> item_form_pairs
;
526 for (std::vector
<SecKeychainItemRef
>::iterator i
= keychain_items
->begin();
527 i
!= keychain_items
->end(); ++i
) {
528 PasswordForm
* form_without_password
= new PasswordForm();
529 internal_keychain_helpers::FillPasswordFormFromKeychainItem(
532 form_without_password
,
533 false); // Load password attributes, but not password data.
534 item_form_pairs
.push_back(std::make_pair(&(*i
), form_without_password
));
536 return item_form_pairs
;
539 void GetPasswordsForForms(const AppleKeychain
& keychain
,
540 ScopedVector
<autofill::PasswordForm
>* database_forms
,
541 ScopedVector
<autofill::PasswordForm
>* passwords
) {
542 // First load the attributes of all items in the keychain without loading
543 // their password data, and then match items in |database_forms| against them.
544 // This avoids individually searching through the keychain for passwords
545 // matching each form in |database_forms|, and results in a significant
546 // performance gain, replacing O(N) keychain search operations with a single
547 // operation that loads all keychain items, and then selective reads of only
548 // the relevant passwords. See crbug.com/263685.
549 std::vector
<SecKeychainItemRef
> keychain_items
;
550 std::vector
<ItemFormPair
> item_form_pairs
=
551 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items
,
554 // Next, compare the attributes of the PasswordForms in |database_forms|
555 // against those in |item_form_pairs|, and extract password data for each
556 // matching PasswordForm using its corresponding SecKeychainItemRef.
557 ScopedVector
<autofill::PasswordForm
> unused_db_forms
;
558 unused_db_forms
.reserve(database_forms
->size());
559 // Move database forms with a password stored in |keychain| to |passwords|,
560 // including the password. The rest is moved to |unused_db_forms|.
561 MoveAllFormsOut(database_forms
,
562 [&keychain
, &item_form_pairs
, passwords
, &unused_db_forms
](
563 scoped_ptr
<autofill::PasswordForm
> form
) {
564 ScopedVector
<autofill::PasswordForm
> keychain_matches
=
565 ExtractPasswordsMergeableWithForm(keychain
, item_form_pairs
, *form
);
567 ScopedVector
<autofill::PasswordForm
> db_form_container
;
568 db_form_container
.push_back(form
.release());
569 MergePasswordForms(&keychain_matches
, &db_form_container
, passwords
);
570 AppendSecondToFirst(&unused_db_forms
, &db_form_container
);
572 database_forms
->swap(unused_db_forms
);
574 STLDeleteContainerPairSecondPointers(item_form_pairs
.begin(),
575 item_form_pairs
.end());
576 for (SecKeychainItemRef item
: keychain_items
) {
581 // TODO(stuartmorgan): signon_realm for proxies is not yet supported.
582 bool ExtractSignonRealmComponents(const std::string
& signon_realm
,
586 std::string
* security_domain
) {
587 // GURL does not parse Android facet URIs correctly.
588 if (password_manager::IsValidAndroidFacetURI(signon_realm
)) {
590 *server
= signon_realm
;
596 security_domain
->clear();
600 // The signon_realm will be the Origin portion of a URL for an HTML form,
601 // and the same but with the security domain as a path for HTTP auth.
602 GURL
realm_as_url(signon_realm
);
603 if (!realm_as_url
.is_valid()) {
608 *server
= realm_as_url
.host();
610 *is_secure
= realm_as_url
.SchemeIsSecure();
612 *port
= realm_as_url
.has_port() ? atoi(realm_as_url
.port().c_str()) : 0;
613 if (security_domain
) {
614 // Strip the leading '/' off of the path to get the security domain.
615 if (realm_as_url
.path().length() > 0)
616 *security_domain
= realm_as_url
.path().substr(1);
618 security_domain
->clear();
623 bool FormIsValidAndMatchesOtherForm(const PasswordForm
& query_form
,
624 const PasswordForm
& other_form
) {
626 std::string security_domain
;
629 if (!ExtractSignonRealmComponents(query_form
.signon_realm
, &server
, &port
,
630 &is_secure
, &security_domain
)) {
633 return FormsMatchForMerge(query_form
, other_form
, STRICT_FORM_MATCH
);
636 ScopedVector
<autofill::PasswordForm
> ExtractPasswordsMergeableWithForm(
637 const AppleKeychain
& keychain
,
638 const std::vector
<ItemFormPair
>& item_form_pairs
,
639 const PasswordForm
& query_form
) {
640 ScopedVector
<autofill::PasswordForm
> matches
;
641 for (std::vector
<ItemFormPair
>::const_iterator i
= item_form_pairs
.begin();
642 i
!= item_form_pairs
.end(); ++i
) {
643 if (FormIsValidAndMatchesOtherForm(query_form
, *(i
->second
))) {
644 // Create a new object, since the caller is responsible for deleting the
646 scoped_ptr
<PasswordForm
> form_with_password(new PasswordForm());
647 FillPasswordFormFromKeychainItem(
648 keychain
, *(i
->first
), form_with_password
.get(),
649 true); // Load password attributes and data.
650 // Do not include blacklisted items found in the keychain.
651 if (!form_with_password
->blacklisted_by_user
)
652 matches
.push_back(form_with_password
.release());
655 return matches
.Pass();
658 } // namespace internal_keychain_helpers
662 MacKeychainPasswordFormAdapter::MacKeychainPasswordFormAdapter(
663 const AppleKeychain
* keychain
)
664 : keychain_(keychain
), finds_only_owned_(false) {
667 ScopedVector
<autofill::PasswordForm
>
668 MacKeychainPasswordFormAdapter::PasswordsFillingForm(
669 const std::string
& signon_realm
,
670 PasswordForm::Scheme scheme
) {
671 std::vector
<SecKeychainItemRef
> keychain_items
=
672 MatchingKeychainItems(signon_realm
, scheme
, NULL
, NULL
);
673 return ConvertKeychainItemsToForms(&keychain_items
);
676 bool MacKeychainPasswordFormAdapter::HasPasswordExactlyMatchingForm(
677 const PasswordForm
& query_form
) {
678 SecKeychainItemRef keychain_item
= KeychainItemForForm(query_form
);
680 keychain_
->Free(keychain_item
);
686 bool MacKeychainPasswordFormAdapter::HasPasswordsMergeableWithForm(
687 const PasswordForm
& query_form
) {
688 std::string username
= base::UTF16ToUTF8(query_form
.username_value
);
689 std::vector
<SecKeychainItemRef
> matches
=
690 MatchingKeychainItems(query_form
.signon_realm
, query_form
.scheme
,
691 NULL
, username
.c_str());
692 for (std::vector
<SecKeychainItemRef
>::iterator i
= matches
.begin();
693 i
!= matches
.end(); ++i
) {
697 return !matches
.empty();
700 std::vector
<SecKeychainItemRef
>
701 MacKeychainPasswordFormAdapter::GetAllPasswordFormKeychainItems() {
702 SecAuthenticationType supported_auth_types
[] = {
703 kSecAuthenticationTypeHTMLForm
,
704 kSecAuthenticationTypeHTTPBasic
,
705 kSecAuthenticationTypeHTTPDigest
,
708 std::vector
<SecKeychainItemRef
> matches
;
709 for (unsigned int i
= 0; i
< arraysize(supported_auth_types
); ++i
) {
710 KeychainSearch
keychain_search(*keychain_
);
711 OSType creator
= CreatorCodeForSearch();
712 keychain_search
.Init(NULL
,
715 &supported_auth_types
[i
],
719 creator
? &creator
: NULL
);
720 keychain_search
.FindMatchingItems(&matches
);
725 ScopedVector
<autofill::PasswordForm
>
726 MacKeychainPasswordFormAdapter::GetAllPasswordFormPasswords() {
727 std::vector
<SecKeychainItemRef
> items
= GetAllPasswordFormKeychainItems();
728 return ConvertKeychainItemsToForms(&items
);
731 bool MacKeychainPasswordFormAdapter::AddPassword(const PasswordForm
& form
) {
732 // We should never be trying to store a blacklist in the keychain.
733 DCHECK(!form
.blacklisted_by_user
);
736 std::string security_domain
;
739 if (!internal_keychain_helpers::ExtractSignonRealmComponents(
740 form
.signon_realm
, &server
, &port
, &is_secure
, &security_domain
)) {
744 // Path doesn't make sense for Android app credentials.
745 if (!password_manager::IsValidAndroidFacetURI(form
.signon_realm
))
746 path
= form
.origin
.path();
747 std::string username
= base::UTF16ToUTF8(form
.username_value
);
748 std::string password
= base::UTF16ToUTF8(form
.password_value
);
749 SecProtocolType protocol
= is_secure
? kSecProtocolTypeHTTPS
750 : kSecProtocolTypeHTTP
;
751 SecKeychainItemRef new_item
= NULL
;
752 OSStatus result
= keychain_
->AddInternetPassword(
753 NULL
, server
.size(), server
.c_str(),
754 security_domain
.size(), security_domain
.c_str(),
755 username
.size(), username
.c_str(),
756 path
.size(), path
.c_str(),
757 port
, protocol
, AuthTypeForScheme(form
.scheme
),
758 password
.size(), password
.c_str(), &new_item
);
760 if (result
== noErr
) {
761 SetKeychainItemCreatorCode(new_item
,
762 base::mac::CreatorCodeForApplication());
763 keychain_
->Free(new_item
);
764 } else if (result
== errSecDuplicateItem
) {
765 // If we collide with an existing item, find and update it instead.
766 SecKeychainItemRef existing_item
= KeychainItemForForm(form
);
767 if (!existing_item
) {
770 bool changed
= SetKeychainItemPassword(existing_item
, password
);
771 keychain_
->Free(existing_item
);
775 return result
== noErr
;
778 bool MacKeychainPasswordFormAdapter::RemovePassword(const PasswordForm
& form
) {
779 SecKeychainItemRef keychain_item
= KeychainItemForForm(form
);
780 if (keychain_item
== NULL
)
782 OSStatus result
= keychain_
->ItemDelete(keychain_item
);
783 keychain_
->Free(keychain_item
);
784 return result
== noErr
;
787 void MacKeychainPasswordFormAdapter::SetFindsOnlyOwnedItems(
788 bool finds_only_owned
) {
789 finds_only_owned_
= finds_only_owned
;
792 ScopedVector
<autofill::PasswordForm
>
793 MacKeychainPasswordFormAdapter::ConvertKeychainItemsToForms(
794 std::vector
<SecKeychainItemRef
>* items
) {
795 ScopedVector
<autofill::PasswordForm
> forms
;
796 for (SecKeychainItemRef item
: *items
) {
797 scoped_ptr
<PasswordForm
> form(new PasswordForm());
798 if (internal_keychain_helpers::FillPasswordFormFromKeychainItem(
799 *keychain_
, item
, form
.get(), true)) {
800 forms
.push_back(form
.release());
802 keychain_
->Free(item
);
808 SecKeychainItemRef
MacKeychainPasswordFormAdapter::KeychainItemForForm(
809 const PasswordForm
& form
) {
810 // We don't store blacklist entries in the keychain, so the answer to "what
811 // Keychain item goes with this form" is always "nothing" for blacklists.
812 if (form
.blacklisted_by_user
) {
817 // Path doesn't make sense for Android app credentials.
818 if (!password_manager::IsValidAndroidFacetURI(form
.signon_realm
))
819 path
= form
.origin
.path();
820 std::string username
= base::UTF16ToUTF8(form
.username_value
);
821 std::vector
<SecKeychainItemRef
> matches
= MatchingKeychainItems(
822 form
.signon_realm
, form
.scheme
, path
.c_str(), username
.c_str());
824 if (matches
.empty()) {
827 // Free all items after the first, since we won't be returning them.
828 for (std::vector
<SecKeychainItemRef
>::iterator i
= matches
.begin() + 1;
829 i
!= matches
.end(); ++i
) {
835 std::vector
<SecKeychainItemRef
>
836 MacKeychainPasswordFormAdapter::MatchingKeychainItems(
837 const std::string
& signon_realm
,
838 autofill::PasswordForm::Scheme scheme
,
839 const char* path
, const char* username
) {
840 std::vector
<SecKeychainItemRef
> matches
;
843 std::string security_domain
;
846 if (!internal_keychain_helpers::ExtractSignonRealmComponents(
847 signon_realm
, &server
, &port
, &is_secure
, &security_domain
)) {
848 // TODO(stuartmorgan): Proxies will currently fail here, since their
849 // signon_realm is not a URL. We need to detect the proxy case and handle
853 SecProtocolType protocol
= is_secure
? kSecProtocolTypeHTTPS
854 : kSecProtocolTypeHTTP
;
855 SecAuthenticationType auth_type
= AuthTypeForScheme(scheme
);
856 const char* auth_domain
= (scheme
== PasswordForm::SCHEME_HTML
) ?
857 NULL
: security_domain
.c_str();
858 OSType creator
= CreatorCodeForSearch();
859 KeychainSearch
keychain_search(*keychain_
);
860 keychain_search
.Init(server
.c_str(),
867 creator
? &creator
: NULL
);
868 keychain_search
.FindMatchingItems(&matches
);
872 // Returns the Keychain SecAuthenticationType type corresponding to |scheme|.
873 SecAuthenticationType
MacKeychainPasswordFormAdapter::AuthTypeForScheme(
874 PasswordForm::Scheme scheme
) {
876 case PasswordForm::SCHEME_HTML
: return kSecAuthenticationTypeHTMLForm
;
877 case PasswordForm::SCHEME_BASIC
: return kSecAuthenticationTypeHTTPBasic
;
878 case PasswordForm::SCHEME_DIGEST
: return kSecAuthenticationTypeHTTPDigest
;
879 case PasswordForm::SCHEME_OTHER
: return kSecAuthenticationTypeDefault
;
882 return kSecAuthenticationTypeDefault
;
885 bool MacKeychainPasswordFormAdapter::SetKeychainItemPassword(
886 const SecKeychainItemRef
& keychain_item
, const std::string
& password
) {
887 OSStatus result
= keychain_
->ItemModifyAttributesAndData(keychain_item
, NULL
,
890 return result
== noErr
;
893 bool MacKeychainPasswordFormAdapter::SetKeychainItemCreatorCode(
894 const SecKeychainItemRef
& keychain_item
, OSType creator_code
) {
895 SecKeychainAttribute attr
= { kSecCreatorItemAttr
, sizeof(creator_code
),
897 SecKeychainAttributeList attrList
= { 1, &attr
};
898 OSStatus result
= keychain_
->ItemModifyAttributesAndData(keychain_item
,
900 return result
== noErr
;
903 OSType
MacKeychainPasswordFormAdapter::CreatorCodeForSearch() {
904 return finds_only_owned_
? base::mac::CreatorCodeForApplication() : 0;
909 PasswordStoreMac::PasswordStoreMac(
910 scoped_refptr
<base::SingleThreadTaskRunner
> main_thread_runner
,
911 scoped_refptr
<base::SingleThreadTaskRunner
> db_thread_runner
,
912 scoped_ptr
<AppleKeychain
> keychain
,
913 scoped_ptr
<password_manager::LoginDatabase
> login_db
)
914 : password_manager::PasswordStore(main_thread_runner
, db_thread_runner
),
915 keychain_(keychain
.Pass()),
916 login_metadata_db_(login_db
.Pass()) {
917 DCHECK(keychain_
.get());
918 DCHECK(login_metadata_db_
.get());
921 PasswordStoreMac::~PasswordStoreMac() {}
923 bool PasswordStoreMac::Init(
924 const syncer::SyncableService::StartSyncFlare
& flare
) {
925 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
926 thread_
.reset(new base::Thread("Chrome_PasswordStore_Thread"));
928 if (!thread_
->Start()) {
933 ScheduleTask(base::Bind(&PasswordStoreMac::InitOnBackgroundThread
, this));
934 return password_manager::PasswordStore::Init(flare
);
937 void PasswordStoreMac::InitOnBackgroundThread() {
938 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
939 DCHECK(login_metadata_db_
);
940 if (!login_metadata_db_
->Init()) {
941 login_metadata_db_
.reset();
942 LOG(ERROR
) << "Could not create/open login database.";
946 void PasswordStoreMac::Shutdown() {
947 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
948 password_manager::PasswordStore::Shutdown();
952 // Mac stores passwords in the system keychain, which can block for an
953 // arbitrarily long time (most notably, it can block on user confirmation
954 // from a dialog). Run tasks on a dedicated thread to avoid blocking the DB
956 scoped_refptr
<base::SingleThreadTaskRunner
>
957 PasswordStoreMac::GetBackgroundTaskRunner() {
958 return (thread_
.get()) ? thread_
->message_loop_proxy() : NULL
;
961 void PasswordStoreMac::ReportMetricsImpl(const std::string
& sync_username
,
962 bool custom_passphrase_sync_enabled
) {
963 if (!login_metadata_db_
)
965 login_metadata_db_
->ReportMetrics(sync_username
,
966 custom_passphrase_sync_enabled
);
969 PasswordStoreChangeList
PasswordStoreMac::AddLoginImpl(
970 const PasswordForm
& form
) {
971 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
972 if (login_metadata_db_
&& AddToKeychainIfNecessary(form
))
973 return login_metadata_db_
->AddLogin(form
);
974 return PasswordStoreChangeList();
977 PasswordStoreChangeList
PasswordStoreMac::UpdateLoginImpl(
978 const PasswordForm
& form
) {
979 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
980 if (!login_metadata_db_
)
981 return PasswordStoreChangeList();
983 PasswordStoreChangeList changes
= login_metadata_db_
->UpdateLogin(form
);
985 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
.get());
986 if (changes
.empty() &&
987 !keychain_adapter
.HasPasswordsMergeableWithForm(form
)) {
988 // If the password isn't in either the DB or the keychain, then it must have
989 // been deleted after autofill happened, and should not be re-added.
993 // The keychain add will update if there is a collision and add if there
994 // isn't, which is the behavior we want, so there's no separate update call.
995 if (AddToKeychainIfNecessary(form
) && changes
.empty()) {
996 changes
= login_metadata_db_
->AddLogin(form
);
1001 PasswordStoreChangeList
PasswordStoreMac::RemoveLoginImpl(
1002 const PasswordForm
& form
) {
1003 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
1004 PasswordStoreChangeList changes
;
1005 if (login_metadata_db_
&& login_metadata_db_
->RemoveLogin(form
)) {
1006 // See if we own a Keychain item associated with this item. We can do an
1007 // exact search rather than messing around with trying to do fuzzy matching
1008 // because passwords that we created will always have an exact-match
1010 // (If a user does lose their profile but not their keychain we'll treat the
1011 // entries we find like other imported entries anyway, so it's reasonable to
1012 // handle deletes on them the way we would for an imported item.)
1013 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
.get());
1014 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1015 if (owned_keychain_adapter
.HasPasswordExactlyMatchingForm(form
)) {
1016 // If we don't have other forms using it (i.e., a form differing only by
1017 // the names of the form elements), delete the keychain entry.
1018 if (!DatabaseHasFormMatchingKeychainForm(form
)) {
1019 owned_keychain_adapter
.RemovePassword(form
);
1023 changes
.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE
, form
));
1028 PasswordStoreChangeList
PasswordStoreMac::RemoveLoginsCreatedBetweenImpl(
1029 base::Time delete_begin
,
1030 base::Time delete_end
) {
1031 PasswordStoreChangeList changes
;
1032 ScopedVector
<PasswordForm
> forms_to_remove
;
1033 if (login_metadata_db_
&&
1034 login_metadata_db_
->GetLoginsCreatedBetween(delete_begin
, delete_end
,
1035 &forms_to_remove
) &&
1036 login_metadata_db_
->RemoveLoginsCreatedBetween(delete_begin
,
1038 RemoveKeychainForms(forms_to_remove
.get());
1039 CleanOrphanedForms(&forms_to_remove
); // Add the orphaned forms.
1040 changes
= FormsToRemoveChangeList(forms_to_remove
.get());
1041 LogStatsForBulkDeletion(changes
.size());
1046 PasswordStoreChangeList
PasswordStoreMac::RemoveLoginsSyncedBetweenImpl(
1047 base::Time delete_begin
,
1048 base::Time delete_end
) {
1049 PasswordStoreChangeList changes
;
1050 ScopedVector
<PasswordForm
> forms_to_remove
;
1051 if (login_metadata_db_
&&
1052 login_metadata_db_
->GetLoginsSyncedBetween(delete_begin
, delete_end
,
1053 &forms_to_remove
) &&
1054 login_metadata_db_
->RemoveLoginsSyncedBetween(delete_begin
, delete_end
)) {
1055 RemoveKeychainForms(forms_to_remove
.get());
1056 CleanOrphanedForms(&forms_to_remove
); // Add the orphaned forms_to_remove.
1057 changes
= FormsToRemoveChangeList(forms_to_remove
.get());
1058 LogStatsForBulkDeletionDuringRollback(changes
.size());
1063 ScopedVector
<autofill::PasswordForm
> PasswordStoreMac::FillMatchingLogins(
1064 const autofill::PasswordForm
& form
,
1065 AuthorizationPromptPolicy prompt_policy
) {
1066 chrome::ScopedSecKeychainSetUserInteractionAllowed
user_interaction_allowed(
1067 prompt_policy
== ALLOW_PROMPT
);
1069 ScopedVector
<PasswordForm
> database_forms
;
1070 if (!login_metadata_db_
||
1071 !login_metadata_db_
->GetLogins(form
, &database_forms
)) {
1072 return ScopedVector
<autofill::PasswordForm
>();
1075 // Let's gather all signon realms we want to match with keychain entries.
1076 std::set
<std::string
> realm_set
;
1077 realm_set
.insert(form
.signon_realm
);
1078 for (const autofill::PasswordForm
* db_form
: database_forms
) {
1079 // TODO(vabr): We should not be getting different schemes here.
1080 // http://crbug.com/340112
1081 if (form
.scheme
!= db_form
->scheme
)
1082 continue; // Forms with different schemes never match.
1083 const std::string
& original_singon_realm(db_form
->original_signon_realm
);
1084 if (!original_singon_realm
.empty())
1085 realm_set
.insert(original_singon_realm
);
1087 ScopedVector
<autofill::PasswordForm
> keychain_forms
;
1088 for (std::set
<std::string
>::const_iterator realm
= realm_set
.begin();
1089 realm
!= realm_set
.end(); ++realm
) {
1090 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
.get());
1091 ScopedVector
<autofill::PasswordForm
> temp_keychain_forms
=
1092 keychain_adapter
.PasswordsFillingForm(*realm
, form
.scheme
);
1093 AppendSecondToFirst(&keychain_forms
, &temp_keychain_forms
);
1096 ScopedVector
<autofill::PasswordForm
> matched_forms
;
1097 internal_keychain_helpers::MergePasswordForms(
1098 &keychain_forms
, &database_forms
, &matched_forms
);
1100 // Strip any blacklist entries out of the unused Keychain array, then take
1101 // all the entries that are left (which we can use as imported passwords).
1102 ScopedVector
<PasswordForm
> keychain_blacklist_forms
;
1103 internal_keychain_helpers::ExtractBlacklistForms(&keychain_forms
,
1104 &keychain_blacklist_forms
);
1105 AppendSecondToFirst(&matched_forms
, &keychain_forms
);
1107 if (!database_forms
.empty()) {
1108 RemoveDatabaseForms(&database_forms
);
1109 NotifyLoginsChanged(FormsToRemoveChangeList(database_forms
.get()));
1112 return matched_forms
.Pass();
1115 void PasswordStoreMac::GetBlacklistLoginsImpl(
1116 scoped_ptr
<PasswordStore::GetLoginsRequest
> request
) {
1117 ScopedVector
<PasswordForm
> obtained_forms
;
1118 if (!FillBlacklistLogins(&obtained_forms
))
1119 obtained_forms
.clear();
1120 request
->NotifyConsumerWithResults(obtained_forms
.Pass());
1123 void PasswordStoreMac::GetAutofillableLoginsImpl(
1124 scoped_ptr
<PasswordStore::GetLoginsRequest
> request
) {
1125 ScopedVector
<PasswordForm
> obtained_forms
;
1126 if (!FillAutofillableLogins(&obtained_forms
))
1127 obtained_forms
.clear();
1128 request
->NotifyConsumerWithResults(obtained_forms
.Pass());
1131 bool PasswordStoreMac::FillAutofillableLogins(
1132 ScopedVector
<PasswordForm
>* forms
) {
1133 DCHECK_EQ(thread_
->message_loop(), base::MessageLoop::current());
1136 ScopedVector
<PasswordForm
> database_forms
;
1137 if (!login_metadata_db_
||
1138 !login_metadata_db_
->GetAutofillableLogins(&database_forms
))
1141 internal_keychain_helpers::GetPasswordsForForms(*keychain_
, &database_forms
,
1144 if (!database_forms
.empty()) {
1145 RemoveDatabaseForms(&database_forms
);
1146 NotifyLoginsChanged(FormsToRemoveChangeList(database_forms
.get()));
1152 bool PasswordStoreMac::FillBlacklistLogins(ScopedVector
<PasswordForm
>* forms
) {
1153 DCHECK_EQ(thread_
->message_loop(), base::MessageLoop::current());
1154 return login_metadata_db_
&& login_metadata_db_
->GetBlacklistLogins(forms
);
1157 bool PasswordStoreMac::AddToKeychainIfNecessary(const PasswordForm
& form
) {
1158 if (form
.blacklisted_by_user
) {
1161 MacKeychainPasswordFormAdapter
keychainAdapter(keychain_
.get());
1162 return keychainAdapter
.AddPassword(form
);
1165 bool PasswordStoreMac::DatabaseHasFormMatchingKeychainForm(
1166 const autofill::PasswordForm
& form
) {
1167 DCHECK(login_metadata_db_
);
1168 bool has_match
= false;
1169 ScopedVector
<autofill::PasswordForm
> database_forms
;
1170 if (!login_metadata_db_
->GetLogins(form
, &database_forms
))
1172 for (const autofill::PasswordForm
* db_form
: database_forms
) {
1173 // Below we filter out forms with non-empty original_signon_realm, because
1174 // those signal fuzzy matches, and we are only interested in exact ones.
1175 if (db_form
->original_signon_realm
.empty() &&
1176 internal_keychain_helpers::FormsMatchForMerge(
1177 form
, *db_form
, internal_keychain_helpers::STRICT_FORM_MATCH
) &&
1178 db_form
->origin
== form
.origin
) {
1186 void PasswordStoreMac::RemoveDatabaseForms(
1187 ScopedVector
<autofill::PasswordForm
>* forms
) {
1188 DCHECK(login_metadata_db_
);
1189 ScopedVector
<autofill::PasswordForm
> removed_forms
;
1190 MoveAllFormsOut(forms
, [this, &removed_forms
](
1191 scoped_ptr
<autofill::PasswordForm
> form
) {
1192 if (login_metadata_db_
->RemoveLogin(*form
))
1193 removed_forms
.push_back(form
.release());
1195 removed_forms
.swap(*forms
);
1198 void PasswordStoreMac::RemoveKeychainForms(
1199 const std::vector
<PasswordForm
*>& forms
) {
1200 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
.get());
1201 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1202 for (std::vector
<PasswordForm
*>::const_iterator i
= forms
.begin();
1203 i
!= forms
.end(); ++i
) {
1204 owned_keychain_adapter
.RemovePassword(**i
);
1208 void PasswordStoreMac::CleanOrphanedForms(
1209 ScopedVector
<autofill::PasswordForm
>* orphaned_forms
) {
1210 DCHECK(orphaned_forms
);
1211 DCHECK(login_metadata_db_
);
1213 ScopedVector
<autofill::PasswordForm
> database_forms
;
1214 if (!login_metadata_db_
->GetAutofillableLogins(&database_forms
))
1217 // Filter forms with corresponding Keychain entry out of |database_forms|.
1218 ScopedVector
<PasswordForm
> forms_with_keychain_entry
;
1219 internal_keychain_helpers::GetPasswordsForForms(*keychain_
, &database_forms
,
1220 &forms_with_keychain_entry
);
1222 // Clean up any orphaned database entries.
1223 RemoveDatabaseForms(&database_forms
);
1225 // Move the orphaned DB forms to the output parameter.
1226 AppendSecondToFirst(orphaned_forms
, &database_forms
);