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();
426 form
->origin
= URLFromComponents(form
->ssl_valid
, server
, port
, path
);
427 // TODO(stuartmorgan): Handle proxies, which need a different signon_realm
429 form
->signon_realm
= form
->origin
.GetOrigin().spec();
430 if (form
->scheme
!= PasswordForm::SCHEME_HTML
) {
431 form
->signon_realm
.append(security_domain
);
437 bool FormsMatchForMerge(const PasswordForm
& form_a
,
438 const PasswordForm
& form_b
,
439 FormMatchStrictness strictness
) {
440 // We never merge blacklist entries between our store and the keychain.
441 if (form_a
.blacklisted_by_user
|| form_b
.blacklisted_by_user
) {
444 bool equal_realm
= form_a
.signon_realm
== form_b
.signon_realm
;
445 if (strictness
== FUZZY_FORM_MATCH
) {
446 equal_realm
|= (!form_a
.original_signon_realm
.empty()) &&
447 form_a
.original_signon_realm
== form_b
.signon_realm
;
449 return form_a
.scheme
== form_b
.scheme
&& equal_realm
&&
450 form_a
.username_value
== form_b
.username_value
;
453 // Moves entries from |forms| that are blacklist entries into |blacklist|.
454 void ExtractBlacklistForms(ScopedVector
<autofill::PasswordForm
>* forms
,
455 ScopedVector
<autofill::PasswordForm
>* blacklist
) {
456 blacklist
->reserve(blacklist
->size() + forms
->size());
457 ScopedVector
<autofill::PasswordForm
> non_blacklist
;
458 // Move forms in either |non_blacklist| or |blacklist|, depending on whether
459 // they are blacklisted by the user.
460 MoveAllFormsOut(forms
, [&non_blacklist
, blacklist
](
461 scoped_ptr
<autofill::PasswordForm
> form
) {
462 if (form
->blacklisted_by_user
)
463 blacklist
->push_back(form
.release());
465 non_blacklist
.push_back(form
.release());
467 forms
->swap(non_blacklist
);
470 // Takes |keychain_forms| and |database_forms| and moves the following 2 types
471 // of forms to |merged_forms|: (1) blacklisted |database_forms|, (2)
472 // |database_forms| which have a corresponding entry in |keychain_forms|. The
473 // database forms of type (2) have their password value updated from the
474 // corresponding keychain form, and all the keychain forms corresponding to some
475 // database form are removed from |keychain_forms| and deleted.
476 void MergePasswordForms(ScopedVector
<autofill::PasswordForm
>* keychain_forms
,
477 ScopedVector
<autofill::PasswordForm
>* database_forms
,
478 ScopedVector
<autofill::PasswordForm
>* merged_forms
) {
479 // Pull out the database blacklist items, since they are used as-is rather
480 // than being merged with keychain forms.
481 ExtractBlacklistForms(database_forms
, merged_forms
);
483 // Merge the normal entries.
484 ScopedVector
<autofill::PasswordForm
> unused_database_forms
;
485 unused_database_forms
.reserve(database_forms
->size());
486 std::set
<const autofill::PasswordForm
*> used_keychain_forms
;
487 // Move all database forms to either |merged_forms| or
488 // |unused_database_forms|, based on whether they have a match in the keychain
489 // forms or not. If there is a match, add its password to the DB form and
490 // mark the keychain form as used.
491 MoveAllFormsOut(database_forms
, [keychain_forms
, &used_keychain_forms
,
492 merged_forms
, &unused_database_forms
](
493 scoped_ptr
<autofill::PasswordForm
> form
) {
494 const PasswordForm
* best_match
=
495 BestKeychainFormForForm(*form
, keychain_forms
->get());
497 used_keychain_forms
.insert(best_match
);
498 form
->password_value
= best_match
->password_value
;
499 merged_forms
->push_back(form
.release());
501 unused_database_forms
.push_back(form
.release());
504 database_forms
->swap(unused_database_forms
);
506 // Clear out all the Keychain entries we used.
507 ScopedVector
<autofill::PasswordForm
> unused_keychain_forms
;
508 unused_keychain_forms
.reserve(keychain_forms
->size());
509 for (auto& keychain_form
: *keychain_forms
) {
510 if (!ContainsKey(used_keychain_forms
, keychain_form
)) {
511 unused_keychain_forms
.push_back(keychain_form
);
512 keychain_form
= nullptr;
515 keychain_forms
->swap(unused_keychain_forms
);
518 std::vector
<ItemFormPair
> ExtractAllKeychainItemAttributesIntoPasswordForms(
519 std::vector
<SecKeychainItemRef
>* keychain_items
,
520 const AppleKeychain
& keychain
) {
521 DCHECK(keychain_items
);
522 MacKeychainPasswordFormAdapter
keychain_adapter(&keychain
);
523 *keychain_items
= keychain_adapter
.GetAllPasswordFormKeychainItems();
524 std::vector
<ItemFormPair
> item_form_pairs
;
525 for (std::vector
<SecKeychainItemRef
>::iterator i
= keychain_items
->begin();
526 i
!= keychain_items
->end(); ++i
) {
527 PasswordForm
* form_without_password
= new PasswordForm();
528 internal_keychain_helpers::FillPasswordFormFromKeychainItem(
531 form_without_password
,
532 false); // Load password attributes, but not password data.
533 item_form_pairs
.push_back(std::make_pair(&(*i
), form_without_password
));
535 return item_form_pairs
;
538 void GetPasswordsForForms(const AppleKeychain
& keychain
,
539 ScopedVector
<autofill::PasswordForm
>* database_forms
,
540 ScopedVector
<autofill::PasswordForm
>* passwords
) {
541 // First load the attributes of all items in the keychain without loading
542 // their password data, and then match items in |database_forms| against them.
543 // This avoids individually searching through the keychain for passwords
544 // matching each form in |database_forms|, and results in a significant
545 // performance gain, replacing O(N) keychain search operations with a single
546 // operation that loads all keychain items, and then selective reads of only
547 // the relevant passwords. See crbug.com/263685.
548 std::vector
<SecKeychainItemRef
> keychain_items
;
549 std::vector
<ItemFormPair
> item_form_pairs
=
550 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items
,
553 // Next, compare the attributes of the PasswordForms in |database_forms|
554 // against those in |item_form_pairs|, and extract password data for each
555 // matching PasswordForm using its corresponding SecKeychainItemRef.
556 ScopedVector
<autofill::PasswordForm
> unused_db_forms
;
557 unused_db_forms
.reserve(database_forms
->size());
558 // Move database forms with a password stored in |keychain| to |passwords|,
559 // including the password. The rest is moved to |unused_db_forms|.
560 MoveAllFormsOut(database_forms
,
561 [&keychain
, &item_form_pairs
, passwords
, &unused_db_forms
](
562 scoped_ptr
<autofill::PasswordForm
> form
) {
563 ScopedVector
<autofill::PasswordForm
> keychain_matches
=
564 ExtractPasswordsMergeableWithForm(keychain
, item_form_pairs
, *form
);
566 ScopedVector
<autofill::PasswordForm
> db_form_container
;
567 db_form_container
.push_back(form
.release());
568 MergePasswordForms(&keychain_matches
, &db_form_container
, passwords
);
569 AppendSecondToFirst(&unused_db_forms
, &db_form_container
);
571 database_forms
->swap(unused_db_forms
);
573 STLDeleteContainerPairSecondPointers(item_form_pairs
.begin(),
574 item_form_pairs
.end());
575 for (SecKeychainItemRef item
: keychain_items
) {
580 // TODO(stuartmorgan): signon_realm for proxies is not yet supported.
581 bool ExtractSignonRealmComponents(const std::string
& signon_realm
,
585 std::string
* security_domain
) {
586 // GURL does not parse Android facet URIs correctly.
587 if (password_manager::IsValidAndroidFacetURI(signon_realm
)) {
589 *server
= signon_realm
;
595 security_domain
->clear();
599 // The signon_realm will be the Origin portion of a URL for an HTML form,
600 // and the same but with the security domain as a path for HTTP auth.
601 GURL
realm_as_url(signon_realm
);
602 if (!realm_as_url
.is_valid()) {
607 *server
= realm_as_url
.host();
609 *is_secure
= realm_as_url
.SchemeIsSecure();
611 *port
= realm_as_url
.has_port() ? atoi(realm_as_url
.port().c_str()) : 0;
612 if (security_domain
) {
613 // Strip the leading '/' off of the path to get the security domain.
614 if (realm_as_url
.path().length() > 0)
615 *security_domain
= realm_as_url
.path().substr(1);
617 security_domain
->clear();
622 bool FormIsValidAndMatchesOtherForm(const PasswordForm
& query_form
,
623 const PasswordForm
& other_form
) {
625 std::string security_domain
;
628 if (!ExtractSignonRealmComponents(query_form
.signon_realm
, &server
, &port
,
629 &is_secure
, &security_domain
)) {
632 return FormsMatchForMerge(query_form
, other_form
, STRICT_FORM_MATCH
);
635 ScopedVector
<autofill::PasswordForm
> ExtractPasswordsMergeableWithForm(
636 const AppleKeychain
& keychain
,
637 const std::vector
<ItemFormPair
>& item_form_pairs
,
638 const PasswordForm
& query_form
) {
639 ScopedVector
<autofill::PasswordForm
> matches
;
640 for (std::vector
<ItemFormPair
>::const_iterator i
= item_form_pairs
.begin();
641 i
!= item_form_pairs
.end(); ++i
) {
642 if (FormIsValidAndMatchesOtherForm(query_form
, *(i
->second
))) {
643 // Create a new object, since the caller is responsible for deleting the
645 scoped_ptr
<PasswordForm
> form_with_password(new PasswordForm());
646 FillPasswordFormFromKeychainItem(
647 keychain
, *(i
->first
), form_with_password
.get(),
648 true); // Load password attributes and data.
649 // Do not include blacklisted items found in the keychain.
650 if (!form_with_password
->blacklisted_by_user
)
651 matches
.push_back(form_with_password
.release());
654 return matches
.Pass();
657 } // namespace internal_keychain_helpers
661 MacKeychainPasswordFormAdapter::MacKeychainPasswordFormAdapter(
662 const AppleKeychain
* keychain
)
663 : keychain_(keychain
), finds_only_owned_(false) {
666 ScopedVector
<autofill::PasswordForm
>
667 MacKeychainPasswordFormAdapter::PasswordsFillingForm(
668 const std::string
& signon_realm
,
669 PasswordForm::Scheme scheme
) {
670 std::vector
<SecKeychainItemRef
> keychain_items
=
671 MatchingKeychainItems(signon_realm
, scheme
, NULL
, NULL
);
672 return ConvertKeychainItemsToForms(&keychain_items
);
675 bool MacKeychainPasswordFormAdapter::HasPasswordExactlyMatchingForm(
676 const PasswordForm
& query_form
) {
677 SecKeychainItemRef keychain_item
= KeychainItemForForm(query_form
);
679 keychain_
->Free(keychain_item
);
685 bool MacKeychainPasswordFormAdapter::HasPasswordsMergeableWithForm(
686 const PasswordForm
& query_form
) {
687 std::string username
= base::UTF16ToUTF8(query_form
.username_value
);
688 std::vector
<SecKeychainItemRef
> matches
=
689 MatchingKeychainItems(query_form
.signon_realm
, query_form
.scheme
,
690 NULL
, username
.c_str());
691 for (std::vector
<SecKeychainItemRef
>::iterator i
= matches
.begin();
692 i
!= matches
.end(); ++i
) {
696 return !matches
.empty();
699 std::vector
<SecKeychainItemRef
>
700 MacKeychainPasswordFormAdapter::GetAllPasswordFormKeychainItems() {
701 SecAuthenticationType supported_auth_types
[] = {
702 kSecAuthenticationTypeHTMLForm
,
703 kSecAuthenticationTypeHTTPBasic
,
704 kSecAuthenticationTypeHTTPDigest
,
707 std::vector
<SecKeychainItemRef
> matches
;
708 for (unsigned int i
= 0; i
< arraysize(supported_auth_types
); ++i
) {
709 KeychainSearch
keychain_search(*keychain_
);
710 OSType creator
= CreatorCodeForSearch();
711 keychain_search
.Init(NULL
,
714 &supported_auth_types
[i
],
718 creator
? &creator
: NULL
);
719 keychain_search
.FindMatchingItems(&matches
);
724 ScopedVector
<autofill::PasswordForm
>
725 MacKeychainPasswordFormAdapter::GetAllPasswordFormPasswords() {
726 std::vector
<SecKeychainItemRef
> items
= GetAllPasswordFormKeychainItems();
727 return ConvertKeychainItemsToForms(&items
);
730 bool MacKeychainPasswordFormAdapter::AddPassword(const PasswordForm
& form
) {
731 // We should never be trying to store a blacklist in the keychain.
732 DCHECK(!form
.blacklisted_by_user
);
735 std::string security_domain
;
738 if (!internal_keychain_helpers::ExtractSignonRealmComponents(
739 form
.signon_realm
, &server
, &port
, &is_secure
, &security_domain
)) {
743 // Path doesn't make sense for Android app credentials.
744 if (!password_manager::IsValidAndroidFacetURI(form
.signon_realm
))
745 path
= form
.origin
.path();
746 std::string username
= base::UTF16ToUTF8(form
.username_value
);
747 std::string password
= base::UTF16ToUTF8(form
.password_value
);
748 SecProtocolType protocol
= is_secure
? kSecProtocolTypeHTTPS
749 : kSecProtocolTypeHTTP
;
750 SecKeychainItemRef new_item
= NULL
;
751 OSStatus result
= keychain_
->AddInternetPassword(
752 NULL
, server
.size(), server
.c_str(),
753 security_domain
.size(), security_domain
.c_str(),
754 username
.size(), username
.c_str(),
755 path
.size(), path
.c_str(),
756 port
, protocol
, AuthTypeForScheme(form
.scheme
),
757 password
.size(), password
.c_str(), &new_item
);
759 if (result
== noErr
) {
760 SetKeychainItemCreatorCode(new_item
,
761 base::mac::CreatorCodeForApplication());
762 keychain_
->Free(new_item
);
763 } else if (result
== errSecDuplicateItem
) {
764 // If we collide with an existing item, find and update it instead.
765 SecKeychainItemRef existing_item
= KeychainItemForForm(form
);
766 if (!existing_item
) {
769 bool changed
= SetKeychainItemPassword(existing_item
, password
);
770 keychain_
->Free(existing_item
);
774 return result
== noErr
;
777 bool MacKeychainPasswordFormAdapter::RemovePassword(const PasswordForm
& form
) {
778 SecKeychainItemRef keychain_item
= KeychainItemForForm(form
);
779 if (keychain_item
== NULL
)
781 OSStatus result
= keychain_
->ItemDelete(keychain_item
);
782 keychain_
->Free(keychain_item
);
783 return result
== noErr
;
786 void MacKeychainPasswordFormAdapter::SetFindsOnlyOwnedItems(
787 bool finds_only_owned
) {
788 finds_only_owned_
= finds_only_owned
;
791 ScopedVector
<autofill::PasswordForm
>
792 MacKeychainPasswordFormAdapter::ConvertKeychainItemsToForms(
793 std::vector
<SecKeychainItemRef
>* items
) {
794 ScopedVector
<autofill::PasswordForm
> forms
;
795 for (SecKeychainItemRef item
: *items
) {
796 scoped_ptr
<PasswordForm
> form(new PasswordForm());
797 if (internal_keychain_helpers::FillPasswordFormFromKeychainItem(
798 *keychain_
, item
, form
.get(), true)) {
799 forms
.push_back(form
.release());
801 keychain_
->Free(item
);
807 SecKeychainItemRef
MacKeychainPasswordFormAdapter::KeychainItemForForm(
808 const PasswordForm
& form
) {
809 // We don't store blacklist entries in the keychain, so the answer to "what
810 // Keychain item goes with this form" is always "nothing" for blacklists.
811 if (form
.blacklisted_by_user
) {
815 std::string path
= form
.origin
.path();
816 std::string username
= base::UTF16ToUTF8(form
.username_value
);
817 std::vector
<SecKeychainItemRef
> matches
= MatchingKeychainItems(
818 form
.signon_realm
, form
.scheme
, path
.c_str(), username
.c_str());
820 if (matches
.empty()) {
823 // Free all items after the first, since we won't be returning them.
824 for (std::vector
<SecKeychainItemRef
>::iterator i
= matches
.begin() + 1;
825 i
!= matches
.end(); ++i
) {
831 std::vector
<SecKeychainItemRef
>
832 MacKeychainPasswordFormAdapter::MatchingKeychainItems(
833 const std::string
& signon_realm
,
834 autofill::PasswordForm::Scheme scheme
,
835 const char* path
, const char* username
) {
836 std::vector
<SecKeychainItemRef
> matches
;
839 std::string security_domain
;
842 if (!internal_keychain_helpers::ExtractSignonRealmComponents(
843 signon_realm
, &server
, &port
, &is_secure
, &security_domain
)) {
844 // TODO(stuartmorgan): Proxies will currently fail here, since their
845 // signon_realm is not a URL. We need to detect the proxy case and handle
849 SecProtocolType protocol
= is_secure
? kSecProtocolTypeHTTPS
850 : kSecProtocolTypeHTTP
;
851 SecAuthenticationType auth_type
= AuthTypeForScheme(scheme
);
852 const char* auth_domain
= (scheme
== PasswordForm::SCHEME_HTML
) ?
853 NULL
: security_domain
.c_str();
854 OSType creator
= CreatorCodeForSearch();
855 KeychainSearch
keychain_search(*keychain_
);
856 keychain_search
.Init(server
.c_str(),
863 creator
? &creator
: NULL
);
864 keychain_search
.FindMatchingItems(&matches
);
868 // Returns the Keychain SecAuthenticationType type corresponding to |scheme|.
869 SecAuthenticationType
MacKeychainPasswordFormAdapter::AuthTypeForScheme(
870 PasswordForm::Scheme scheme
) {
872 case PasswordForm::SCHEME_HTML
: return kSecAuthenticationTypeHTMLForm
;
873 case PasswordForm::SCHEME_BASIC
: return kSecAuthenticationTypeHTTPBasic
;
874 case PasswordForm::SCHEME_DIGEST
: return kSecAuthenticationTypeHTTPDigest
;
875 case PasswordForm::SCHEME_OTHER
: return kSecAuthenticationTypeDefault
;
878 return kSecAuthenticationTypeDefault
;
881 bool MacKeychainPasswordFormAdapter::SetKeychainItemPassword(
882 const SecKeychainItemRef
& keychain_item
, const std::string
& password
) {
883 OSStatus result
= keychain_
->ItemModifyAttributesAndData(keychain_item
, NULL
,
886 return result
== noErr
;
889 bool MacKeychainPasswordFormAdapter::SetKeychainItemCreatorCode(
890 const SecKeychainItemRef
& keychain_item
, OSType creator_code
) {
891 SecKeychainAttribute attr
= { kSecCreatorItemAttr
, sizeof(creator_code
),
893 SecKeychainAttributeList attrList
= { 1, &attr
};
894 OSStatus result
= keychain_
->ItemModifyAttributesAndData(keychain_item
,
896 return result
== noErr
;
899 OSType
MacKeychainPasswordFormAdapter::CreatorCodeForSearch() {
900 return finds_only_owned_
? base::mac::CreatorCodeForApplication() : 0;
905 PasswordStoreMac::PasswordStoreMac(
906 scoped_refptr
<base::SingleThreadTaskRunner
> main_thread_runner
,
907 scoped_refptr
<base::SingleThreadTaskRunner
> db_thread_runner
,
908 scoped_ptr
<AppleKeychain
> keychain
,
909 scoped_ptr
<password_manager::LoginDatabase
> login_db
)
910 : password_manager::PasswordStore(main_thread_runner
, db_thread_runner
),
911 keychain_(keychain
.Pass()),
912 login_metadata_db_(login_db
.Pass()) {
913 DCHECK(keychain_
.get());
914 DCHECK(login_metadata_db_
.get());
917 PasswordStoreMac::~PasswordStoreMac() {}
919 bool PasswordStoreMac::Init(
920 const syncer::SyncableService::StartSyncFlare
& flare
) {
921 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
922 thread_
.reset(new base::Thread("Chrome_PasswordStore_Thread"));
924 if (!thread_
->Start()) {
929 ScheduleTask(base::Bind(&PasswordStoreMac::InitOnBackgroundThread
, this));
930 return password_manager::PasswordStore::Init(flare
);
933 void PasswordStoreMac::InitOnBackgroundThread() {
934 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
935 DCHECK(login_metadata_db_
);
936 if (!login_metadata_db_
->Init()) {
937 login_metadata_db_
.reset();
938 LOG(ERROR
) << "Could not create/open login database.";
942 void PasswordStoreMac::Shutdown() {
943 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
944 password_manager::PasswordStore::Shutdown();
948 // Mac stores passwords in the system keychain, which can block for an
949 // arbitrarily long time (most notably, it can block on user confirmation
950 // from a dialog). Run tasks on a dedicated thread to avoid blocking the DB
952 scoped_refptr
<base::SingleThreadTaskRunner
>
953 PasswordStoreMac::GetBackgroundTaskRunner() {
954 return (thread_
.get()) ? thread_
->message_loop_proxy() : NULL
;
957 void PasswordStoreMac::ReportMetricsImpl(const std::string
& sync_username
,
958 bool custom_passphrase_sync_enabled
) {
959 if (!login_metadata_db_
)
961 login_metadata_db_
->ReportMetrics(sync_username
,
962 custom_passphrase_sync_enabled
);
965 PasswordStoreChangeList
PasswordStoreMac::AddLoginImpl(
966 const PasswordForm
& form
) {
967 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
968 if (login_metadata_db_
&& AddToKeychainIfNecessary(form
))
969 return login_metadata_db_
->AddLogin(form
);
970 return PasswordStoreChangeList();
973 PasswordStoreChangeList
PasswordStoreMac::UpdateLoginImpl(
974 const PasswordForm
& form
) {
975 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
976 if (!login_metadata_db_
)
977 return PasswordStoreChangeList();
979 PasswordStoreChangeList changes
= login_metadata_db_
->UpdateLogin(form
);
981 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
.get());
982 if (changes
.empty() &&
983 !keychain_adapter
.HasPasswordsMergeableWithForm(form
)) {
984 // If the password isn't in either the DB or the keychain, then it must have
985 // been deleted after autofill happened, and should not be re-added.
989 // The keychain add will update if there is a collision and add if there
990 // isn't, which is the behavior we want, so there's no separate update call.
991 if (AddToKeychainIfNecessary(form
) && changes
.empty()) {
992 changes
= login_metadata_db_
->AddLogin(form
);
997 PasswordStoreChangeList
PasswordStoreMac::RemoveLoginImpl(
998 const PasswordForm
& form
) {
999 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
1000 PasswordStoreChangeList changes
;
1001 if (login_metadata_db_
&& login_metadata_db_
->RemoveLogin(form
)) {
1002 // See if we own a Keychain item associated with this item. We can do an
1003 // exact search rather than messing around with trying to do fuzzy matching
1004 // because passwords that we created will always have an exact-match
1006 // (If a user does lose their profile but not their keychain we'll treat the
1007 // entries we find like other imported entries anyway, so it's reasonable to
1008 // handle deletes on them the way we would for an imported item.)
1009 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
.get());
1010 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1011 if (owned_keychain_adapter
.HasPasswordExactlyMatchingForm(form
)) {
1012 // If we don't have other forms using it (i.e., a form differing only by
1013 // the names of the form elements), delete the keychain entry.
1014 if (!DatabaseHasFormMatchingKeychainForm(form
)) {
1015 owned_keychain_adapter
.RemovePassword(form
);
1019 changes
.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE
, form
));
1024 PasswordStoreChangeList
PasswordStoreMac::RemoveLoginsCreatedBetweenImpl(
1025 base::Time delete_begin
,
1026 base::Time delete_end
) {
1027 PasswordStoreChangeList changes
;
1028 ScopedVector
<PasswordForm
> forms_to_remove
;
1029 if (login_metadata_db_
&&
1030 login_metadata_db_
->GetLoginsCreatedBetween(delete_begin
, delete_end
,
1031 &forms_to_remove
) &&
1032 login_metadata_db_
->RemoveLoginsCreatedBetween(delete_begin
,
1034 RemoveKeychainForms(forms_to_remove
.get());
1035 CleanOrphanedForms(&forms_to_remove
); // Add the orphaned forms.
1036 changes
= FormsToRemoveChangeList(forms_to_remove
.get());
1037 LogStatsForBulkDeletion(changes
.size());
1042 PasswordStoreChangeList
PasswordStoreMac::RemoveLoginsSyncedBetweenImpl(
1043 base::Time delete_begin
,
1044 base::Time delete_end
) {
1045 PasswordStoreChangeList changes
;
1046 ScopedVector
<PasswordForm
> forms_to_remove
;
1047 if (login_metadata_db_
&&
1048 login_metadata_db_
->GetLoginsSyncedBetween(delete_begin
, delete_end
,
1049 &forms_to_remove
) &&
1050 login_metadata_db_
->RemoveLoginsSyncedBetween(delete_begin
, delete_end
)) {
1051 RemoveKeychainForms(forms_to_remove
.get());
1052 CleanOrphanedForms(&forms_to_remove
); // Add the orphaned forms_to_remove.
1053 changes
= FormsToRemoveChangeList(forms_to_remove
.get());
1054 LogStatsForBulkDeletionDuringRollback(changes
.size());
1059 ScopedVector
<autofill::PasswordForm
> PasswordStoreMac::FillMatchingLogins(
1060 const autofill::PasswordForm
& form
,
1061 AuthorizationPromptPolicy prompt_policy
) {
1062 chrome::ScopedSecKeychainSetUserInteractionAllowed
user_interaction_allowed(
1063 prompt_policy
== ALLOW_PROMPT
);
1065 if (!login_metadata_db_
)
1066 return ScopedVector
<autofill::PasswordForm
>();
1068 ScopedVector
<PasswordForm
> database_forms
;
1069 login_metadata_db_
->GetLogins(form
, &database_forms
);
1071 // Let's gather all signon realms we want to match with keychain entries.
1072 std::set
<std::string
> realm_set
;
1073 realm_set
.insert(form
.signon_realm
);
1074 for (const autofill::PasswordForm
* db_form
: database_forms
) {
1075 // TODO(vabr): We should not be getting different schemes here.
1076 // http://crbug.com/340112
1077 if (form
.scheme
!= db_form
->scheme
)
1078 continue; // Forms with different schemes never match.
1079 const std::string
& original_singon_realm(db_form
->original_signon_realm
);
1080 if (!original_singon_realm
.empty())
1081 realm_set
.insert(original_singon_realm
);
1083 ScopedVector
<autofill::PasswordForm
> keychain_forms
;
1084 for (std::set
<std::string
>::const_iterator realm
= realm_set
.begin();
1085 realm
!= realm_set
.end(); ++realm
) {
1086 MacKeychainPasswordFormAdapter
keychain_adapter(keychain_
.get());
1087 ScopedVector
<autofill::PasswordForm
> temp_keychain_forms
=
1088 keychain_adapter
.PasswordsFillingForm(*realm
, form
.scheme
);
1089 AppendSecondToFirst(&keychain_forms
, &temp_keychain_forms
);
1092 ScopedVector
<autofill::PasswordForm
> matched_forms
;
1093 internal_keychain_helpers::MergePasswordForms(
1094 &keychain_forms
, &database_forms
, &matched_forms
);
1096 // Strip any blacklist entries out of the unused Keychain array, then take
1097 // all the entries that are left (which we can use as imported passwords).
1098 ScopedVector
<PasswordForm
> keychain_blacklist_forms
;
1099 internal_keychain_helpers::ExtractBlacklistForms(&keychain_forms
,
1100 &keychain_blacklist_forms
);
1101 AppendSecondToFirst(&matched_forms
, &keychain_forms
);
1103 if (!database_forms
.empty()) {
1104 RemoveDatabaseForms(&database_forms
);
1105 NotifyLoginsChanged(FormsToRemoveChangeList(database_forms
.get()));
1108 return matched_forms
.Pass();
1111 void PasswordStoreMac::GetBlacklistLoginsImpl(
1112 scoped_ptr
<PasswordStore::GetLoginsRequest
> request
) {
1113 ScopedVector
<autofill::PasswordForm
> obtained_forms
;
1114 FillBlacklistLogins(&obtained_forms
);
1115 request
->NotifyConsumerWithResults(obtained_forms
.Pass());
1118 void PasswordStoreMac::GetAutofillableLoginsImpl(
1119 scoped_ptr
<PasswordStore::GetLoginsRequest
> request
) {
1120 ScopedVector
<autofill::PasswordForm
> obtained_forms
;
1121 FillAutofillableLogins(&obtained_forms
);
1122 request
->NotifyConsumerWithResults(obtained_forms
.Pass());
1125 bool PasswordStoreMac::FillAutofillableLogins(
1126 ScopedVector
<autofill::PasswordForm
>* forms
) {
1127 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
1129 ScopedVector
<PasswordForm
> database_forms
;
1130 if (!login_metadata_db_
||
1131 !login_metadata_db_
->GetAutofillableLogins(&database_forms
))
1134 internal_keychain_helpers::GetPasswordsForForms(*keychain_
, &database_forms
,
1137 if (!database_forms
.empty()) {
1138 RemoveDatabaseForms(&database_forms
);
1139 NotifyLoginsChanged(FormsToRemoveChangeList(database_forms
.get()));
1145 bool PasswordStoreMac::FillBlacklistLogins(
1146 ScopedVector
<autofill::PasswordForm
>* forms
) {
1147 DCHECK(thread_
->message_loop() == base::MessageLoop::current());
1148 return login_metadata_db_
&& login_metadata_db_
->GetBlacklistLogins(forms
);
1151 bool PasswordStoreMac::AddToKeychainIfNecessary(const PasswordForm
& form
) {
1152 if (form
.blacklisted_by_user
) {
1155 MacKeychainPasswordFormAdapter
keychainAdapter(keychain_
.get());
1156 return keychainAdapter
.AddPassword(form
);
1159 bool PasswordStoreMac::DatabaseHasFormMatchingKeychainForm(
1160 const autofill::PasswordForm
& form
) {
1161 DCHECK(login_metadata_db_
);
1162 bool has_match
= false;
1163 ScopedVector
<autofill::PasswordForm
> database_forms
;
1164 login_metadata_db_
->GetLogins(form
, &database_forms
);
1165 for (const autofill::PasswordForm
* db_form
: database_forms
) {
1166 // Below we filter out forms with non-empty original_signon_realm, because
1167 // those signal fuzzy matches, and we are only interested in exact ones.
1168 if (db_form
->original_signon_realm
.empty() &&
1169 internal_keychain_helpers::FormsMatchForMerge(
1170 form
, *db_form
, internal_keychain_helpers::STRICT_FORM_MATCH
) &&
1171 db_form
->origin
== form
.origin
) {
1179 void PasswordStoreMac::RemoveDatabaseForms(
1180 ScopedVector
<autofill::PasswordForm
>* forms
) {
1181 DCHECK(login_metadata_db_
);
1182 ScopedVector
<autofill::PasswordForm
> removed_forms
;
1183 MoveAllFormsOut(forms
, [this, &removed_forms
](
1184 scoped_ptr
<autofill::PasswordForm
> form
) {
1185 if (login_metadata_db_
->RemoveLogin(*form
))
1186 removed_forms
.push_back(form
.release());
1188 removed_forms
.swap(*forms
);
1191 void PasswordStoreMac::RemoveKeychainForms(
1192 const std::vector
<PasswordForm
*>& forms
) {
1193 MacKeychainPasswordFormAdapter
owned_keychain_adapter(keychain_
.get());
1194 owned_keychain_adapter
.SetFindsOnlyOwnedItems(true);
1195 for (std::vector
<PasswordForm
*>::const_iterator i
= forms
.begin();
1196 i
!= forms
.end(); ++i
) {
1197 owned_keychain_adapter
.RemovePassword(**i
);
1201 void PasswordStoreMac::CleanOrphanedForms(
1202 ScopedVector
<autofill::PasswordForm
>* orphaned_forms
) {
1203 DCHECK(orphaned_forms
);
1204 DCHECK(login_metadata_db_
);
1206 ScopedVector
<autofill::PasswordForm
> database_forms
;
1207 login_metadata_db_
->GetAutofillableLogins(&database_forms
);
1209 // Filter forms with corresponding Keychain entry out of |database_forms|.
1210 ScopedVector
<PasswordForm
> forms_with_keychain_entry
;
1211 internal_keychain_helpers::GetPasswordsForForms(*keychain_
, &database_forms
,
1212 &forms_with_keychain_entry
);
1214 // Clean up any orphaned database entries.
1215 RemoveDatabaseForms(&database_forms
);
1217 // Move the orphaned DB forms to the output parameter.
1218 AppendSecondToFirst(orphaned_forms
, &database_forms
);