OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/password_manager/password_store_mac.h" | |
6 | |
7 #include <CoreServices/CoreServices.h> | |
8 #include <stddef.h> | |
9 #include <algorithm> | |
10 #include <iterator> | |
11 #include <set> | |
12 #include <string> | |
13 #include <utility> | |
14 #include <vector> | |
15 | |
16 #include "base/callback.h" | |
17 #include "base/logging.h" | |
18 #include "base/mac/foundation_util.h" | |
19 #include "base/mac/mac_logging.h" | |
20 #include "base/macros.h" | |
21 #include "base/memory/ptr_util.h" | |
22 #include "base/message_loop/message_loop.h" | |
23 #include "base/metrics/histogram_macros.h" | |
24 #include "base/strings/string_util.h" | |
25 #include "base/strings/utf_string_conversions.h" | |
26 #include "chrome/browser/mac/security_wrappers.h" | |
27 #include "chrome/browser/password_manager/password_store_mac_internal.h" | |
28 #include "components/os_crypt/os_crypt.h" | |
29 #include "components/password_manager/core/browser/affiliation_utils.h" | |
30 #include "components/password_manager/core/browser/login_database.h" | |
31 #include "components/password_manager/core/browser/password_manager_util.h" | |
32 #include "components/password_manager/core/browser/password_store_change.h" | |
33 #include "content/public/browser/browser_thread.h" | |
34 #include "crypto/apple_keychain.h" | |
35 #include "url/origin.h" | |
36 | |
37 using autofill::PasswordForm; | |
38 using crypto::AppleKeychain; | |
39 using password_manager::PasswordStoreChange; | |
40 using password_manager::PasswordStoreChangeList; | |
41 | |
42 namespace { | |
43 | |
44 // Utility class to handle the details of constructing and running a keychain | |
45 // search from a set of attributes. | |
46 class KeychainSearch { | |
47 public: | |
48 explicit KeychainSearch(const AppleKeychain& keychain); | |
49 ~KeychainSearch(); | |
50 | |
51 // Sets up a keychain search based on an non "null" (NULL for char*, | |
52 // The appropriate "Any" entry for other types) arguments. | |
53 // | |
54 // IMPORTANT: Any parameters passed in *must* remain valid for as long as the | |
55 // KeychainSearch object, since the search uses them by reference. | |
56 void Init(const char* server, | |
57 const UInt32* port, | |
58 const SecProtocolType* protocol, | |
59 const SecAuthenticationType* auth_type, | |
60 const char* security_domain, | |
61 const char* path, | |
62 const char* username, | |
63 const OSType* creator); | |
64 | |
65 // Fills |items| with all Keychain items that match the Init'd search. | |
66 // If the search fails for any reason, |items| will be unchanged. | |
67 void FindMatchingItems(std::vector<SecKeychainItemRef>* matches); | |
68 | |
69 private: | |
70 const AppleKeychain* keychain_; | |
71 SecKeychainAttributeList search_attributes_; | |
72 SecKeychainSearchRef search_ref_; | |
73 }; | |
74 | |
75 KeychainSearch::KeychainSearch(const AppleKeychain& keychain) | |
76 : keychain_(&keychain), search_ref_(NULL) { | |
77 search_attributes_.count = 0; | |
78 search_attributes_.attr = NULL; | |
79 } | |
80 | |
81 KeychainSearch::~KeychainSearch() { | |
82 if (search_attributes_.attr) { | |
83 free(search_attributes_.attr); | |
84 } | |
85 } | |
86 | |
87 void KeychainSearch::Init(const char* server, | |
88 const UInt32* port, | |
89 const SecProtocolType* protocol, | |
90 const SecAuthenticationType* auth_type, | |
91 const char* security_domain, | |
92 const char* path, | |
93 const char* username, | |
94 const OSType* creator) { | |
95 // Allocate enough to hold everything we might use. | |
96 const unsigned int kMaxEntryCount = 8; | |
97 search_attributes_.attr = | |
98 static_cast<SecKeychainAttribute*>(calloc(kMaxEntryCount, | |
99 sizeof(SecKeychainAttribute))); | |
100 unsigned int entries = 0; | |
101 // We only use search_attributes_ with SearchCreateFromAttributes, which takes | |
102 // a "const SecKeychainAttributeList *", so we trust that they won't try | |
103 // to modify the list, and that casting away const-ness is thus safe. | |
104 if (server != NULL) { | |
105 DCHECK_LT(entries, kMaxEntryCount); | |
106 search_attributes_.attr[entries].tag = kSecServerItemAttr; | |
107 search_attributes_.attr[entries].length = strlen(server); | |
108 search_attributes_.attr[entries].data = | |
109 const_cast<void*>(static_cast<const void*>(server)); | |
110 ++entries; | |
111 } | |
112 if (port != NULL && *port != kAnyPort) { | |
113 DCHECK_LE(entries, kMaxEntryCount); | |
114 search_attributes_.attr[entries].tag = kSecPortItemAttr; | |
115 search_attributes_.attr[entries].length = sizeof(*port); | |
116 search_attributes_.attr[entries].data = | |
117 const_cast<void*>(static_cast<const void*>(port)); | |
118 ++entries; | |
119 } | |
120 if (protocol != NULL && *protocol != kSecProtocolTypeAny) { | |
121 DCHECK_LE(entries, kMaxEntryCount); | |
122 search_attributes_.attr[entries].tag = kSecProtocolItemAttr; | |
123 search_attributes_.attr[entries].length = sizeof(*protocol); | |
124 search_attributes_.attr[entries].data = | |
125 const_cast<void*>(static_cast<const void*>(protocol)); | |
126 ++entries; | |
127 } | |
128 if (auth_type != NULL && *auth_type != kSecAuthenticationTypeAny) { | |
129 DCHECK_LE(entries, kMaxEntryCount); | |
130 search_attributes_.attr[entries].tag = kSecAuthenticationTypeItemAttr; | |
131 search_attributes_.attr[entries].length = sizeof(*auth_type); | |
132 search_attributes_.attr[entries].data = | |
133 const_cast<void*>(static_cast<const void*>(auth_type)); | |
134 ++entries; | |
135 } | |
136 if (security_domain != NULL && strlen(security_domain) > 0) { | |
137 DCHECK_LE(entries, kMaxEntryCount); | |
138 search_attributes_.attr[entries].tag = kSecSecurityDomainItemAttr; | |
139 search_attributes_.attr[entries].length = strlen(security_domain); | |
140 search_attributes_.attr[entries].data = | |
141 const_cast<void*>(static_cast<const void*>(security_domain)); | |
142 ++entries; | |
143 } | |
144 if (path != NULL && strlen(path) > 0 && strcmp(path, "/") != 0) { | |
145 DCHECK_LE(entries, kMaxEntryCount); | |
146 search_attributes_.attr[entries].tag = kSecPathItemAttr; | |
147 search_attributes_.attr[entries].length = strlen(path); | |
148 search_attributes_.attr[entries].data = | |
149 const_cast<void*>(static_cast<const void*>(path)); | |
150 ++entries; | |
151 } | |
152 if (username != NULL) { | |
153 DCHECK_LE(entries, kMaxEntryCount); | |
154 search_attributes_.attr[entries].tag = kSecAccountItemAttr; | |
155 search_attributes_.attr[entries].length = strlen(username); | |
156 search_attributes_.attr[entries].data = | |
157 const_cast<void*>(static_cast<const void*>(username)); | |
158 ++entries; | |
159 } | |
160 if (creator != NULL) { | |
161 DCHECK_LE(entries, kMaxEntryCount); | |
162 search_attributes_.attr[entries].tag = kSecCreatorItemAttr; | |
163 search_attributes_.attr[entries].length = sizeof(*creator); | |
164 search_attributes_.attr[entries].data = | |
165 const_cast<void*>(static_cast<const void*>(creator)); | |
166 ++entries; | |
167 } | |
168 search_attributes_.count = entries; | |
169 } | |
170 | |
171 void KeychainSearch::FindMatchingItems(std::vector<SecKeychainItemRef>* items) { | |
172 OSStatus result = keychain_->SearchCreateFromAttributes( | |
173 NULL, kSecInternetPasswordItemClass, &search_attributes_, &search_ref_); | |
174 | |
175 if (result != noErr) { | |
176 OSSTATUS_LOG(ERROR, result) << "Keychain lookup failed"; | |
177 return; | |
178 } | |
179 | |
180 SecKeychainItemRef keychain_item; | |
181 while (keychain_->SearchCopyNext(search_ref_, &keychain_item) == noErr) { | |
182 // Consumer is responsible for freeing the items. | |
183 items->push_back(keychain_item); | |
184 } | |
185 | |
186 keychain_->Free(search_ref_); | |
187 search_ref_ = NULL; | |
188 } | |
189 | |
190 PasswordStoreChangeList FormsToRemoveChangeList( | |
191 const std::vector<std::unique_ptr<PasswordForm>>& forms) { | |
192 PasswordStoreChangeList changes; | |
193 for (const auto& form : forms) { | |
194 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, *form)); | |
195 } | |
196 return changes; | |
197 } | |
198 | |
199 // Moves the content of |second| to the end of |first|. | |
200 void AppendSecondToFirst(std::vector<std::unique_ptr<PasswordForm>>* first, | |
201 std::vector<std::unique_ptr<PasswordForm>>* second) { | |
202 first->reserve(first->size() + second->size()); | |
203 std::move(second->begin(), second->end(), std::back_inserter(*first)); | |
204 second->clear(); | |
205 } | |
206 | |
207 // Returns the best match for |base_form| from |keychain_forms|, or nullptr if | |
208 // there is no suitable match. | |
209 const PasswordForm* BestKeychainFormForForm( | |
210 const PasswordForm& base_form, | |
211 const std::vector<std::unique_ptr<PasswordForm>>& keychain_forms) { | |
212 const PasswordForm* partial_match = nullptr; | |
213 for (const auto& keychain_form : keychain_forms) { | |
214 // TODO(stuartmorgan): We should really be scoring path matches and picking | |
215 // the best, rather than just checking exact-or-not (although in practice | |
216 // keychain items with paths probably came from us). | |
217 if (internal_keychain_helpers::FormsMatchForMerge( | |
218 base_form, *keychain_form, | |
219 internal_keychain_helpers::FUZZY_FORM_MATCH)) { | |
220 if (base_form.origin == keychain_form->origin) { | |
221 return keychain_form.get(); | |
222 } else if (!partial_match) { | |
223 partial_match = keychain_form.get(); | |
224 } | |
225 } | |
226 } | |
227 return partial_match; | |
228 } | |
229 | |
230 // True if the form has no password to be stored in Keychain. | |
231 bool IsLoginDatabaseOnlyForm(const PasswordForm& form) { | |
232 return form.blacklisted_by_user || !form.federation_origin.unique() || | |
233 form.scheme == PasswordForm::SCHEME_USERNAME_ONLY; | |
234 } | |
235 | |
236 } // namespace | |
237 | |
238 #pragma mark - | |
239 | |
240 // TODO(stuartmorgan): Convert most of this to private helpers in | |
241 // MacKeychainPasswordFormAdapter once it has sufficient higher-level public | |
242 // methods to provide test coverage. | |
243 namespace internal_keychain_helpers { | |
244 | |
245 // Returns a URL built from the given components. To create a URL without a | |
246 // port, pass kAnyPort for the |port| parameter. | |
247 GURL URLFromComponents(bool is_secure, const std::string& host, int port, | |
248 const std::string& path) { | |
249 GURL::Replacements url_components; | |
250 std::string scheme(is_secure ? "https" : "http"); | |
251 url_components.SetSchemeStr(scheme); | |
252 url_components.SetHostStr(host); | |
253 std::string port_string; // Must remain in scope until after we do replacing. | |
254 if (port != kAnyPort) { | |
255 std::ostringstream port_stringstream; | |
256 port_stringstream << port; | |
257 port_string = port_stringstream.str(); | |
258 url_components.SetPortStr(port_string); | |
259 } | |
260 url_components.SetPathStr(path); | |
261 | |
262 GURL url("http://dummy.com"); // ReplaceComponents needs a valid URL. | |
263 return url.ReplaceComponents(url_components); | |
264 } | |
265 | |
266 // Converts a Keychain time string to a Time object, returning true if | |
267 // time_string_bytes was parsable. | |
268 bool TimeFromKeychainTimeString(const char* time_string_bytes, | |
269 unsigned int byte_length, | |
270 base::Time* time) { | |
271 DCHECK(time); | |
272 | |
273 char* time_string = static_cast<char*>(malloc(byte_length + 1)); | |
274 memcpy(time_string, time_string_bytes, byte_length); | |
275 time_string[byte_length] = '\0'; | |
276 base::Time::Exploded exploded_time; | |
277 bzero(&exploded_time, sizeof(exploded_time)); | |
278 // The time string is of the form "yyyyMMddHHmmss'Z", in UTC time. | |
279 int assignments = sscanf(time_string, "%4d%2d%2d%2d%2d%2dZ", | |
280 &exploded_time.year, &exploded_time.month, | |
281 &exploded_time.day_of_month, &exploded_time.hour, | |
282 &exploded_time.minute, &exploded_time.second); | |
283 free(time_string); | |
284 | |
285 return assignments == 6 && base::Time::FromUTCExploded(exploded_time, time); | |
286 } | |
287 | |
288 // Returns the PasswordForm Scheme corresponding to |auth_type|. | |
289 PasswordForm::Scheme SchemeForAuthType(SecAuthenticationType auth_type) { | |
290 switch (auth_type) { | |
291 case kSecAuthenticationTypeHTMLForm: return PasswordForm::SCHEME_HTML; | |
292 case kSecAuthenticationTypeHTTPBasic: return PasswordForm::SCHEME_BASIC; | |
293 case kSecAuthenticationTypeHTTPDigest: return PasswordForm::SCHEME_DIGEST; | |
294 default: return PasswordForm::SCHEME_OTHER; | |
295 } | |
296 } | |
297 | |
298 bool FillPasswordFormFromKeychainItem(const AppleKeychain& keychain, | |
299 SecKeychainItemRef keychain_item, | |
300 PasswordForm* form, | |
301 bool extract_password_data) { | |
302 DCHECK(form); | |
303 | |
304 SecKeychainAttributeInfo attrInfo; | |
305 UInt32 tags[] = { kSecAccountItemAttr, | |
306 kSecServerItemAttr, | |
307 kSecPortItemAttr, | |
308 kSecPathItemAttr, | |
309 kSecProtocolItemAttr, | |
310 kSecAuthenticationTypeItemAttr, | |
311 kSecSecurityDomainItemAttr, | |
312 kSecCreationDateItemAttr, | |
313 kSecNegativeItemAttr }; | |
314 attrInfo.count = arraysize(tags); | |
315 attrInfo.tag = tags; | |
316 attrInfo.format = NULL; | |
317 | |
318 SecKeychainAttributeList* attrList; | |
319 UInt32 password_length; | |
320 | |
321 // If |extract_password_data| is false, do not pass in a reference to | |
322 // |password_data|. ItemCopyAttributesAndData will then extract only the | |
323 // attributes of |keychain_item| (doesn't require OS authorization), and not | |
324 // attempt to extract its password data (requires OS authorization). | |
325 void* password_data = NULL; | |
326 void** password_data_ref = extract_password_data ? &password_data : NULL; | |
327 | |
328 OSStatus result = keychain.ItemCopyAttributesAndData(keychain_item, &attrInfo, | |
329 NULL, &attrList, | |
330 &password_length, | |
331 password_data_ref); | |
332 | |
333 if (result != noErr) { | |
334 // We don't log errSecAuthFailed because that just means that the user | |
335 // chose not to allow us access to the item. | |
336 if (result != errSecAuthFailed) { | |
337 OSSTATUS_LOG(ERROR, result) << "Keychain data load failed"; | |
338 } | |
339 return false; | |
340 } | |
341 | |
342 if (extract_password_data) { | |
343 base::UTF8ToUTF16(static_cast<const char *>(password_data), password_length, | |
344 &(form->password_value)); | |
345 } | |
346 | |
347 int port = kAnyPort; | |
348 std::string server; | |
349 std::string security_domain; | |
350 std::string path; | |
351 bool is_secure = false; | |
352 for (unsigned int i = 0; i < attrList->count; i++) { | |
353 SecKeychainAttribute attr = attrList->attr[i]; | |
354 if (!attr.data) { | |
355 continue; | |
356 } | |
357 switch (attr.tag) { | |
358 case kSecAccountItemAttr: | |
359 base::UTF8ToUTF16(static_cast<const char *>(attr.data), attr.length, | |
360 &(form->username_value)); | |
361 break; | |
362 case kSecServerItemAttr: | |
363 server.assign(static_cast<const char *>(attr.data), attr.length); | |
364 break; | |
365 case kSecPortItemAttr: | |
366 port = *(static_cast<UInt32*>(attr.data)); | |
367 break; | |
368 case kSecPathItemAttr: | |
369 path.assign(static_cast<const char *>(attr.data), attr.length); | |
370 break; | |
371 case kSecProtocolItemAttr: | |
372 { | |
373 SecProtocolType protocol = *(static_cast<SecProtocolType*>(attr.data)); | |
374 // TODO(stuartmorgan): Handle proxy types | |
375 is_secure = (protocol == kSecProtocolTypeHTTPS); | |
376 break; | |
377 } | |
378 case kSecAuthenticationTypeItemAttr: | |
379 { | |
380 SecAuthenticationType auth_type = | |
381 *(static_cast<SecAuthenticationType*>(attr.data)); | |
382 form->scheme = SchemeForAuthType(auth_type); | |
383 break; | |
384 } | |
385 case kSecSecurityDomainItemAttr: | |
386 security_domain.assign(static_cast<const char *>(attr.data), | |
387 attr.length); | |
388 break; | |
389 case kSecCreationDateItemAttr: | |
390 // The only way to get a date out of Keychain is as a string. Really. | |
391 // (The docs claim it's an int, but the header is correct.) | |
392 TimeFromKeychainTimeString(static_cast<char*>(attr.data), attr.length, | |
393 &form->date_created); | |
394 break; | |
395 case kSecNegativeItemAttr: | |
396 Boolean negative_item = *(static_cast<Boolean*>(attr.data)); | |
397 if (negative_item) { | |
398 form->blacklisted_by_user = true; | |
399 } | |
400 break; | |
401 } | |
402 } | |
403 keychain.ItemFreeAttributesAndData(attrList, password_data); | |
404 | |
405 // kSecNegativeItemAttr doesn't seem to actually be in widespread use. In | |
406 // practice, other browsers seem to use a "" or " " password (and a special | |
407 // user name) to indicated blacklist entries. | |
408 if (extract_password_data && (form->password_value.empty() || | |
409 base::EqualsASCII(form->password_value, " "))) { | |
410 form->blacklisted_by_user = true; | |
411 } | |
412 | |
413 // Android facet URLs aren't parsed correctly by GURL and need to be handled | |
414 // separately. | |
415 if (password_manager::IsValidAndroidFacetURI(server)) { | |
416 form->signon_realm = server; | |
417 form->origin = GURL(); | |
418 } else { | |
419 form->origin = URLFromComponents(is_secure, server, port, path); | |
420 // TODO(stuartmorgan): Handle proxies, which need a different signon_realm | |
421 // format. | |
422 form->signon_realm = form->origin.GetOrigin().spec(); | |
423 if (form->scheme != PasswordForm::SCHEME_HTML) { | |
424 form->signon_realm.append(security_domain); | |
425 } | |
426 } | |
427 return true; | |
428 } | |
429 | |
430 bool HasChromeCreatorCode(const AppleKeychain& keychain, | |
431 SecKeychainItemRef keychain_item) { | |
432 SecKeychainAttributeInfo attr_info; | |
433 UInt32 tags[] = {kSecCreatorItemAttr}; | |
434 attr_info.count = arraysize(tags); | |
435 attr_info.tag = tags; | |
436 attr_info.format = nullptr; | |
437 | |
438 SecKeychainAttributeList* attr_list; | |
439 UInt32 password_length; | |
440 OSStatus result = keychain.ItemCopyAttributesAndData( | |
441 keychain_item, &attr_info, nullptr, &attr_list, | |
442 &password_length, nullptr); | |
443 if (result != noErr) | |
444 return false; | |
445 OSType creator_code = 0; | |
446 for (unsigned int i = 0; i < attr_list->count; i++) { | |
447 SecKeychainAttribute attr = attr_list->attr[i]; | |
448 if (!attr.data) { | |
449 continue; | |
450 } | |
451 if (attr.tag == kSecCreatorItemAttr) { | |
452 creator_code = *(static_cast<FourCharCode*>(attr.data)); | |
453 break; | |
454 } | |
455 } | |
456 keychain.ItemFreeAttributesAndData(attr_list, nullptr); | |
457 return creator_code && creator_code == base::mac::CreatorCodeForApplication(); | |
458 } | |
459 | |
460 bool FormsMatchForMerge(const PasswordForm& form_a, | |
461 const PasswordForm& form_b, | |
462 FormMatchStrictness strictness) { | |
463 if (IsLoginDatabaseOnlyForm(form_a) || IsLoginDatabaseOnlyForm(form_b)) | |
464 return false; | |
465 | |
466 bool equal_realm = form_a.signon_realm == form_b.signon_realm; | |
467 if (strictness == FUZZY_FORM_MATCH) { | |
468 equal_realm |= form_a.is_public_suffix_match; | |
469 } | |
470 return form_a.scheme == form_b.scheme && equal_realm && | |
471 form_a.username_value == form_b.username_value; | |
472 } | |
473 | |
474 // Moves entries from |forms| that represent either blacklisted or federated | |
475 // logins into |extracted|. These two types are stored only in the LoginDatabase | |
476 // and do not have corresponding Keychain entries. | |
477 void ExtractNonKeychainForms( | |
478 std::vector<std::unique_ptr<PasswordForm>>* forms, | |
479 std::vector<std::unique_ptr<PasswordForm>>* extracted) { | |
480 extracted->reserve(extracted->size() + forms->size()); | |
481 | |
482 std::vector<std::unique_ptr<PasswordForm>> remaining; | |
483 remaining.reserve(forms->size()); | |
484 | |
485 for (std::unique_ptr<PasswordForm>& form : *forms) { | |
486 if (IsLoginDatabaseOnlyForm(*form)) | |
487 extracted->push_back(std::move(form)); | |
488 else | |
489 remaining.push_back(std::move(form)); | |
490 } | |
491 forms->swap(remaining); | |
492 } | |
493 | |
494 // Takes |keychain_forms| and |database_forms| and moves the following 2 types | |
495 // of forms to |merged_forms|: | |
496 // (1) |database_forms| that by principle never have a corresponding Keychain | |
497 // entry (viz., blacklisted and federated logins), | |
498 // (2) |database_forms| which should have and do have a corresponding entry in | |
499 // |keychain_forms|. | |
500 // The database forms of type (2) have their password value updated from the | |
501 // corresponding keychain form, and all the keychain forms corresponding to some | |
502 // database form are removed from |keychain_forms| and deleted. | |
503 void MergePasswordForms( | |
504 std::vector<std::unique_ptr<PasswordForm>>* keychain_forms, | |
505 std::vector<std::unique_ptr<PasswordForm>>* database_forms, | |
506 std::vector<std::unique_ptr<PasswordForm>>* merged_forms) { | |
507 // Pull out the database blacklist items and federated logins, since they are | |
508 // used as-is rather than being merged with keychain forms. | |
509 ExtractNonKeychainForms(database_forms, merged_forms); | |
510 | |
511 // Merge the normal entries. | |
512 std::vector<std::unique_ptr<PasswordForm>> unused_database_forms; | |
513 unused_database_forms.reserve(database_forms->size()); | |
514 std::set<const PasswordForm*> used_keychain_forms; | |
515 // Move all database forms to either |merged_forms| or | |
516 // |unused_database_forms|, based on whether they have a match in the keychain | |
517 // forms or not. If there is a match, add its password to the DB form and | |
518 // mark the keychain form as used. | |
519 for (std::unique_ptr<PasswordForm>& db_form : *database_forms) { | |
520 const PasswordForm* best_match = | |
521 BestKeychainFormForForm(*db_form, *keychain_forms); | |
522 if (best_match) { | |
523 used_keychain_forms.insert(best_match); | |
524 db_form->password_value = best_match->password_value; | |
525 merged_forms->push_back(std::move(db_form)); | |
526 } else { | |
527 unused_database_forms.push_back(std::move(db_form)); | |
528 } | |
529 } | |
530 database_forms->swap(unused_database_forms); | |
531 | |
532 // Clear out all the Keychain entries we used. | |
533 std::vector<std::unique_ptr<PasswordForm>> unused_keychain_forms; | |
534 unused_keychain_forms.reserve(keychain_forms->size()); | |
535 for (std::unique_ptr<PasswordForm>& keychain_form : *keychain_forms) { | |
536 if (!base::ContainsKey(used_keychain_forms, keychain_form.get())) { | |
537 unused_keychain_forms.push_back(std::move(keychain_form)); | |
538 } | |
539 } | |
540 keychain_forms->swap(unused_keychain_forms); | |
541 } | |
542 | |
543 std::vector<ItemFormPair> ExtractAllKeychainItemAttributesIntoPasswordForms( | |
544 std::vector<SecKeychainItemRef>* keychain_items, | |
545 const AppleKeychain& keychain) { | |
546 DCHECK(keychain_items); | |
547 MacKeychainPasswordFormAdapter keychain_adapter(&keychain); | |
548 *keychain_items = keychain_adapter.GetAllPasswordFormKeychainItems(); | |
549 std::vector<ItemFormPair> item_form_pairs; | |
550 for (auto* keychain_item : *keychain_items) { | |
551 std::unique_ptr<PasswordForm> form_without_password = | |
552 base::MakeUnique<PasswordForm>(); | |
553 internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
554 keychain, keychain_item, form_without_password.get(), | |
555 false); // Load password attributes, but not password data. | |
556 item_form_pairs.push_back( | |
557 std::make_pair(keychain_item, std::move(form_without_password))); | |
558 } | |
559 return item_form_pairs; | |
560 } | |
561 | |
562 void GetPasswordsForForms( | |
563 const AppleKeychain& keychain, | |
564 std::vector<std::unique_ptr<PasswordForm>>* database_forms, | |
565 std::vector<std::unique_ptr<PasswordForm>>* passwords) { | |
566 // First load the attributes of all items in the keychain without loading | |
567 // their password data, and then match items in |database_forms| against them. | |
568 // This avoids individually searching through the keychain for passwords | |
569 // matching each form in |database_forms|, and results in a significant | |
570 // performance gain, replacing O(N) keychain search operations with a single | |
571 // operation that loads all keychain items, and then selective reads of only | |
572 // the relevant passwords. See crbug.com/263685. | |
573 std::vector<SecKeychainItemRef> keychain_items; | |
574 std::vector<ItemFormPair> item_form_pairs = | |
575 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items, | |
576 keychain); | |
577 | |
578 // Next, compare the attributes of the PasswordForms in |database_forms| | |
579 // against those in |item_form_pairs|, and extract password data for each | |
580 // matching PasswordForm using its corresponding SecKeychainItemRef. | |
581 std::vector<std::unique_ptr<PasswordForm>> unused_db_forms; | |
582 unused_db_forms.reserve(database_forms->size()); | |
583 // Move database forms with a password stored in |keychain| to |passwords|, | |
584 // including the password. The rest is moved to |unused_db_forms|. | |
585 for (std::unique_ptr<PasswordForm>& db_form : *database_forms) { | |
586 std::vector<std::unique_ptr<PasswordForm>> keychain_matches = | |
587 ExtractPasswordsMergeableWithForm(keychain, item_form_pairs, *db_form); | |
588 | |
589 std::vector<std::unique_ptr<PasswordForm>> db_form_container; | |
590 db_form_container.push_back(std::move(db_form)); | |
591 MergePasswordForms(&keychain_matches, &db_form_container, passwords); | |
592 AppendSecondToFirst(&unused_db_forms, &db_form_container); | |
593 } | |
594 database_forms->swap(unused_db_forms); | |
595 | |
596 for (SecKeychainItemRef item : keychain_items) { | |
597 keychain.Free(item); | |
598 } | |
599 } | |
600 | |
601 // TODO(stuartmorgan): signon_realm for proxies is not yet supported. | |
602 bool ExtractSignonRealmComponents(const std::string& signon_realm, | |
603 std::string* server, | |
604 UInt32* port, | |
605 bool* is_secure, | |
606 std::string* security_domain) { | |
607 // GURL does not parse Android facet URIs correctly. | |
608 if (password_manager::IsValidAndroidFacetURI(signon_realm)) { | |
609 if (server) | |
610 *server = signon_realm; | |
611 if (is_secure) | |
612 *is_secure = true; | |
613 if (port) | |
614 *port = 0; | |
615 if (security_domain) | |
616 security_domain->clear(); | |
617 return true; | |
618 } | |
619 | |
620 // The signon_realm will be the Origin portion of a URL for an HTML form, | |
621 // and the same but with the security domain as a path for HTTP auth. | |
622 GURL realm_as_url(signon_realm); | |
623 if (!realm_as_url.is_valid()) { | |
624 return false; | |
625 } | |
626 | |
627 if (server) | |
628 *server = realm_as_url.host(); | |
629 if (is_secure) | |
630 *is_secure = realm_as_url.SchemeIsCryptographic(); | |
631 if (port) | |
632 *port = realm_as_url.has_port() ? atoi(realm_as_url.port().c_str()) : 0; | |
633 if (security_domain) { | |
634 // Strip the leading '/' off of the path to get the security domain. | |
635 if (realm_as_url.path().length() > 0) | |
636 *security_domain = realm_as_url.path().substr(1); | |
637 else | |
638 security_domain->clear(); | |
639 } | |
640 return true; | |
641 } | |
642 | |
643 bool FormIsValidAndMatchesOtherForm(const PasswordForm& query_form, | |
644 const PasswordForm& other_form) { | |
645 std::string server; | |
646 std::string security_domain; | |
647 UInt32 port; | |
648 bool is_secure; | |
649 if (!ExtractSignonRealmComponents(query_form.signon_realm, &server, &port, | |
650 &is_secure, &security_domain)) { | |
651 return false; | |
652 } | |
653 return FormsMatchForMerge(query_form, other_form, STRICT_FORM_MATCH); | |
654 } | |
655 | |
656 std::vector<std::unique_ptr<PasswordForm>> ExtractPasswordsMergeableWithForm( | |
657 const AppleKeychain& keychain, | |
658 const std::vector<ItemFormPair>& item_form_pairs, | |
659 const PasswordForm& query_form) { | |
660 std::vector<std::unique_ptr<PasswordForm>> matches; | |
661 for (std::vector<ItemFormPair>::const_iterator i = item_form_pairs.begin(); | |
662 i != item_form_pairs.end(); ++i) { | |
663 if (FormIsValidAndMatchesOtherForm(query_form, *(i->second))) { | |
664 // Create a new object, since the caller is responsible for deleting the | |
665 // returned forms. | |
666 auto form_with_password = base::MakeUnique<PasswordForm>(); | |
667 FillPasswordFormFromKeychainItem( | |
668 keychain, i->first, form_with_password.get(), | |
669 true); // Load password attributes and data. | |
670 // Do not include blacklisted items found in the keychain. | |
671 if (!form_with_password->blacklisted_by_user) | |
672 matches.push_back(std::move(form_with_password)); | |
673 } | |
674 } | |
675 return matches; | |
676 } | |
677 | |
678 } // namespace internal_keychain_helpers | |
679 | |
680 #pragma mark - | |
681 | |
682 MacKeychainPasswordFormAdapter::MacKeychainPasswordFormAdapter( | |
683 const AppleKeychain* keychain) | |
684 : keychain_(keychain), finds_only_owned_(false) { | |
685 } | |
686 | |
687 std::vector<std::unique_ptr<PasswordForm>> | |
688 MacKeychainPasswordFormAdapter::PasswordsFillingForm( | |
689 const std::string& signon_realm, | |
690 PasswordForm::Scheme scheme) { | |
691 std::vector<SecKeychainItemRef> keychain_items = | |
692 MatchingKeychainItems(signon_realm, scheme, NULL, NULL); | |
693 return ConvertKeychainItemsToForms(&keychain_items); | |
694 } | |
695 | |
696 bool MacKeychainPasswordFormAdapter::HasPasswordExactlyMatchingForm( | |
697 const PasswordForm& query_form) { | |
698 SecKeychainItemRef keychain_item = KeychainItemForForm(query_form); | |
699 if (keychain_item) { | |
700 keychain_->Free(keychain_item); | |
701 return true; | |
702 } | |
703 return false; | |
704 } | |
705 | |
706 bool MacKeychainPasswordFormAdapter::HasPasswordsMergeableWithForm( | |
707 const PasswordForm& query_form) { | |
708 if (IsLoginDatabaseOnlyForm(query_form)) | |
709 return false; | |
710 std::string username = base::UTF16ToUTF8(query_form.username_value); | |
711 std::vector<SecKeychainItemRef> matches = | |
712 MatchingKeychainItems(query_form.signon_realm, query_form.scheme, | |
713 NULL, username.c_str()); | |
714 for (SecKeychainItemRef item : matches) | |
715 keychain_->Free(item); | |
716 | |
717 return !matches.empty(); | |
718 } | |
719 | |
720 std::vector<SecKeychainItemRef> | |
721 MacKeychainPasswordFormAdapter::GetAllPasswordFormKeychainItems() { | |
722 SecAuthenticationType supported_auth_types[] = { | |
723 kSecAuthenticationTypeHTMLForm, | |
724 kSecAuthenticationTypeHTTPBasic, | |
725 kSecAuthenticationTypeHTTPDigest, | |
726 }; | |
727 | |
728 std::vector<SecKeychainItemRef> matches; | |
729 for (unsigned int i = 0; i < arraysize(supported_auth_types); ++i) { | |
730 KeychainSearch keychain_search(*keychain_); | |
731 OSType creator = CreatorCodeForSearch(); | |
732 keychain_search.Init(NULL, | |
733 NULL, | |
734 NULL, | |
735 &supported_auth_types[i], | |
736 NULL, | |
737 NULL, | |
738 NULL, | |
739 creator ? &creator : NULL); | |
740 keychain_search.FindMatchingItems(&matches); | |
741 } | |
742 return matches; | |
743 } | |
744 | |
745 std::vector<std::unique_ptr<PasswordForm>> | |
746 MacKeychainPasswordFormAdapter::GetAllPasswordFormPasswords() { | |
747 std::vector<SecKeychainItemRef> items = GetAllPasswordFormKeychainItems(); | |
748 return ConvertKeychainItemsToForms(&items); | |
749 } | |
750 | |
751 bool MacKeychainPasswordFormAdapter::AddPassword(const PasswordForm& form) { | |
752 // We should never be trying to store a blacklist in the keychain. | |
753 DCHECK(!IsLoginDatabaseOnlyForm(form)); | |
754 | |
755 std::string server; | |
756 std::string security_domain; | |
757 UInt32 port; | |
758 bool is_secure; | |
759 if (!internal_keychain_helpers::ExtractSignonRealmComponents( | |
760 form.signon_realm, &server, &port, &is_secure, &security_domain)) { | |
761 return false; | |
762 } | |
763 std::string path; | |
764 // Path doesn't make sense for Android app credentials. | |
765 if (!password_manager::IsValidAndroidFacetURI(form.signon_realm)) | |
766 path = form.origin.path(); | |
767 std::string username = base::UTF16ToUTF8(form.username_value); | |
768 std::string password = base::UTF16ToUTF8(form.password_value); | |
769 SecProtocolType protocol = is_secure ? kSecProtocolTypeHTTPS | |
770 : kSecProtocolTypeHTTP; | |
771 SecKeychainItemRef new_item = NULL; | |
772 OSStatus result = keychain_->AddInternetPassword( | |
773 NULL, server.size(), server.c_str(), | |
774 security_domain.size(), security_domain.c_str(), | |
775 username.size(), username.c_str(), | |
776 path.size(), path.c_str(), | |
777 port, protocol, AuthTypeForScheme(form.scheme), | |
778 password.size(), password.c_str(), &new_item); | |
779 | |
780 if (result == noErr) { | |
781 SetKeychainItemCreatorCode(new_item, | |
782 base::mac::CreatorCodeForApplication()); | |
783 keychain_->Free(new_item); | |
784 } else if (result == errSecDuplicateItem) { | |
785 // If we collide with an existing item, find and update it instead. | |
786 SecKeychainItemRef existing_item = KeychainItemForForm(form); | |
787 if (!existing_item) { | |
788 return false; | |
789 } | |
790 bool changed = SetKeychainItemPassword(existing_item, password); | |
791 keychain_->Free(existing_item); | |
792 return changed; | |
793 } | |
794 | |
795 return result == noErr; | |
796 } | |
797 | |
798 bool MacKeychainPasswordFormAdapter::RemovePassword(const PasswordForm& form) { | |
799 SecKeychainItemRef keychain_item = KeychainItemForForm(form); | |
800 if (keychain_item == NULL) | |
801 return false; | |
802 OSStatus result = keychain_->ItemDelete(keychain_item); | |
803 keychain_->Free(keychain_item); | |
804 return result == noErr; | |
805 } | |
806 | |
807 void MacKeychainPasswordFormAdapter::SetFindsOnlyOwnedItems( | |
808 bool finds_only_owned) { | |
809 finds_only_owned_ = finds_only_owned; | |
810 } | |
811 | |
812 std::vector<std::unique_ptr<PasswordForm>> | |
813 MacKeychainPasswordFormAdapter::ConvertKeychainItemsToForms( | |
814 std::vector<SecKeychainItemRef>* items) { | |
815 std::vector<std::unique_ptr<PasswordForm>> forms; | |
816 for (SecKeychainItemRef item : *items) { | |
817 auto form = base::MakeUnique<PasswordForm>(); | |
818 if (internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
819 *keychain_, item, form.get(), true)) { | |
820 forms.push_back(std::move(form)); | |
821 } | |
822 keychain_->Free(item); | |
823 } | |
824 items->clear(); | |
825 return forms; | |
826 } | |
827 | |
828 SecKeychainItemRef MacKeychainPasswordFormAdapter::KeychainItemForForm( | |
829 const PasswordForm& form) { | |
830 // We don't store blacklist entries in the keychain, so the answer to "what | |
831 // Keychain item goes with this form" is always "nothing" for blacklists. | |
832 // Same goes for federated logins. | |
833 if (IsLoginDatabaseOnlyForm(form)) | |
834 return NULL; | |
835 | |
836 std::string path; | |
837 // Path doesn't make sense for Android app credentials. | |
838 if (!password_manager::IsValidAndroidFacetURI(form.signon_realm)) | |
839 path = form.origin.path(); | |
840 std::string username = base::UTF16ToUTF8(form.username_value); | |
841 std::vector<SecKeychainItemRef> matches = MatchingKeychainItems( | |
842 form.signon_realm, form.scheme, path.c_str(), username.c_str()); | |
843 | |
844 if (matches.empty()) { | |
845 return NULL; | |
846 } | |
847 | |
848 // Free all items after the first, since we won't be returning them. | |
849 for (auto i = matches.begin() + 1; i != matches.end(); ++i) | |
850 keychain_->Free(*i); | |
851 | |
852 return matches[0]; | |
853 } | |
854 | |
855 std::vector<SecKeychainItemRef> | |
856 MacKeychainPasswordFormAdapter::MatchingKeychainItems( | |
857 const std::string& signon_realm, | |
858 PasswordForm::Scheme scheme, | |
859 const char* path, | |
860 const char* username) { | |
861 std::vector<SecKeychainItemRef> matches; | |
862 | |
863 std::string server; | |
864 std::string security_domain; | |
865 UInt32 port; | |
866 bool is_secure; | |
867 if (!internal_keychain_helpers::ExtractSignonRealmComponents( | |
868 signon_realm, &server, &port, &is_secure, &security_domain)) { | |
869 // TODO(stuartmorgan): Proxies will currently fail here, since their | |
870 // signon_realm is not a URL. We need to detect the proxy case and handle | |
871 // it specially. | |
872 return matches; | |
873 } | |
874 SecProtocolType protocol = is_secure ? kSecProtocolTypeHTTPS | |
875 : kSecProtocolTypeHTTP; | |
876 SecAuthenticationType auth_type = AuthTypeForScheme(scheme); | |
877 const char* auth_domain = (scheme == PasswordForm::SCHEME_HTML) ? | |
878 NULL : security_domain.c_str(); | |
879 OSType creator = CreatorCodeForSearch(); | |
880 KeychainSearch keychain_search(*keychain_); | |
881 keychain_search.Init(server.c_str(), | |
882 &port, | |
883 &protocol, | |
884 &auth_type, | |
885 auth_domain, | |
886 path, | |
887 username, | |
888 creator ? &creator : NULL); | |
889 keychain_search.FindMatchingItems(&matches); | |
890 return matches; | |
891 } | |
892 | |
893 // Returns the Keychain SecAuthenticationType type corresponding to |scheme|. | |
894 SecAuthenticationType MacKeychainPasswordFormAdapter::AuthTypeForScheme( | |
895 PasswordForm::Scheme scheme) { | |
896 switch (scheme) { | |
897 case PasswordForm::SCHEME_HTML: return kSecAuthenticationTypeHTMLForm; | |
898 case PasswordForm::SCHEME_BASIC: return kSecAuthenticationTypeHTTPBasic; | |
899 case PasswordForm::SCHEME_DIGEST: return kSecAuthenticationTypeHTTPDigest; | |
900 case PasswordForm::SCHEME_OTHER: return kSecAuthenticationTypeDefault; | |
901 case PasswordForm::SCHEME_USERNAME_ONLY: | |
902 NOTREACHED(); | |
903 break; | |
904 } | |
905 NOTREACHED(); | |
906 return kSecAuthenticationTypeDefault; | |
907 } | |
908 | |
909 bool MacKeychainPasswordFormAdapter::SetKeychainItemPassword( | |
910 SecKeychainItemRef keychain_item, | |
911 const std::string& password) { | |
912 OSStatus result = keychain_->ItemModifyAttributesAndData(keychain_item, NULL, | |
913 password.size(), | |
914 password.c_str()); | |
915 return result == noErr; | |
916 } | |
917 | |
918 bool MacKeychainPasswordFormAdapter::SetKeychainItemCreatorCode( | |
919 SecKeychainItemRef keychain_item, | |
920 OSType creator_code) { | |
921 SecKeychainAttribute attr = { kSecCreatorItemAttr, sizeof(creator_code), | |
922 &creator_code }; | |
923 SecKeychainAttributeList attrList = { 1, &attr }; | |
924 OSStatus result = keychain_->ItemModifyAttributesAndData(keychain_item, | |
925 &attrList, 0, NULL); | |
926 return result == noErr; | |
927 } | |
928 | |
929 OSType MacKeychainPasswordFormAdapter::CreatorCodeForSearch() { | |
930 return finds_only_owned_ ? base::mac::CreatorCodeForApplication() : 0; | |
931 } | |
932 | |
933 #pragma mark - | |
934 | |
935 PasswordStoreMac::PasswordStoreMac( | |
936 scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner, | |
937 scoped_refptr<base::SingleThreadTaskRunner> db_thread_runner, | |
938 std::unique_ptr<AppleKeychain> keychain) | |
939 : password_manager::PasswordStore(main_thread_runner, db_thread_runner), | |
940 keychain_(std::move(keychain)), | |
941 login_metadata_db_(nullptr) { | |
942 DCHECK(keychain_); | |
943 } | |
944 | |
945 PasswordStoreMac::~PasswordStoreMac() {} | |
946 | |
947 void PasswordStoreMac::InitWithTaskRunner( | |
948 scoped_refptr<base::SingleThreadTaskRunner> background_task_runner) { | |
949 db_thread_runner_ = background_task_runner; | |
950 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
951 } | |
952 | |
953 // static | |
954 PasswordStoreMac::MigrationResult PasswordStoreMac::ImportFromKeychain( | |
955 password_manager::LoginDatabase* login_db, | |
956 crypto::AppleKeychain* keychain) { | |
957 std::vector<std::unique_ptr<PasswordForm>> database_forms; | |
958 if (!login_db->GetAutofillableLogins(&database_forms)) | |
959 return LOGIN_DB_FAILURE; | |
960 | |
961 std::vector<std::unique_ptr<PasswordForm>> uninteresting_forms; | |
962 internal_keychain_helpers::ExtractNonKeychainForms(&database_forms, | |
963 &uninteresting_forms); | |
964 // If there are no passwords to lookup in the Keychain then we're done. | |
965 if (database_forms.empty()) | |
966 return MIGRATION_OK; | |
967 | |
968 // Make sure that the encryption key is retrieved from the Keychain so the | |
969 // encryption can be done. | |
970 std::string ciphertext; | |
971 if (!OSCrypt::EncryptString("test", &ciphertext)) | |
972 return ENCRYPTOR_FAILURE; | |
973 | |
974 // Mute the Keychain. | |
975 chrome::ScopedSecKeychainSetUserInteractionAllowed user_interaction_allowed( | |
976 false); | |
977 | |
978 // Retrieve the passwords. | |
979 // Get all the password attributes first. | |
980 std::vector<SecKeychainItemRef> keychain_items; | |
981 std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs = | |
982 internal_keychain_helpers:: | |
983 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items, | |
984 *keychain); | |
985 | |
986 // Next, compare the attributes of the PasswordForms in |database_forms| | |
987 // against those in |item_form_pairs|, and extract password data for each | |
988 // matching PasswordForm using its corresponding SecKeychainItemRef. | |
989 size_t unmerged_forms_count = 0; | |
990 login_db->set_clear_password_values(false); | |
991 for (const auto& form : database_forms) { | |
992 std::vector<std::unique_ptr<PasswordForm>> keychain_matches = | |
993 internal_keychain_helpers::ExtractPasswordsMergeableWithForm( | |
994 *keychain, item_form_pairs, *form); | |
995 | |
996 const PasswordForm* best_match = | |
997 BestKeychainFormForForm(*form, keychain_matches); | |
998 if (best_match) { | |
999 form->password_value = best_match->password_value; | |
1000 PasswordStoreChangeList change = login_db->UpdateLogin(*form); | |
1001 DCHECK_EQ(1u, change.size()); | |
1002 } else { | |
1003 unmerged_forms_count++; | |
1004 bool removed = login_db->RemoveLogin(*form); | |
1005 DCHECK(removed); | |
1006 } | |
1007 } | |
1008 for (SecKeychainItemRef item : keychain_items) | |
1009 keychain->Free(item); | |
1010 | |
1011 if (unmerged_forms_count) { | |
1012 UMA_HISTOGRAM_COUNTS( | |
1013 "PasswordManager.KeychainMigration.NumPasswordsOnFailure", | |
1014 database_forms.size()); | |
1015 UMA_HISTOGRAM_COUNTS("PasswordManager.KeychainMigration.NumFailedPasswords", | |
1016 unmerged_forms_count); | |
1017 return MIGRATION_PARTIAL; | |
1018 } | |
1019 return MIGRATION_OK; | |
1020 } | |
1021 | |
1022 // static | |
1023 void PasswordStoreMac::CleanUpKeychain( | |
1024 crypto::AppleKeychain* keychain, | |
1025 const std::vector<std::unique_ptr<PasswordForm>>& forms) { | |
1026 MacKeychainPasswordFormAdapter keychain_adapter(keychain); | |
1027 keychain_adapter.SetFindsOnlyOwnedItems(true); | |
1028 for (const auto& form : forms) | |
1029 keychain_adapter.RemovePassword(*form); | |
1030 } | |
1031 | |
1032 void PasswordStoreMac::set_login_metadata_db( | |
1033 password_manager::LoginDatabase* login_db) { | |
1034 login_metadata_db_ = login_db; | |
1035 if (login_metadata_db_) | |
1036 login_metadata_db_->set_clear_password_values(true); | |
1037 } | |
1038 | |
1039 bool PasswordStoreMac::Init( | |
1040 const syncer::SyncableService::StartSyncFlare& flare, | |
1041 PrefService* prefs) { | |
1042 // The class should be used inside PasswordStoreProxyMac only. | |
1043 NOTREACHED(); | |
1044 return true; | |
1045 } | |
1046 | |
1047 void PasswordStoreMac::ReportMetricsImpl(const std::string& sync_username, | |
1048 bool custom_passphrase_sync_enabled) { | |
1049 if (!login_metadata_db_) | |
1050 return; | |
1051 login_metadata_db_->ReportMetrics(sync_username, | |
1052 custom_passphrase_sync_enabled); | |
1053 } | |
1054 | |
1055 PasswordStoreChangeList PasswordStoreMac::AddLoginImpl( | |
1056 const PasswordForm& form) { | |
1057 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1058 if (login_metadata_db_ && AddToKeychainIfNecessary(form)) | |
1059 return login_metadata_db_->AddLogin(form); | |
1060 return PasswordStoreChangeList(); | |
1061 } | |
1062 | |
1063 PasswordStoreChangeList PasswordStoreMac::UpdateLoginImpl( | |
1064 const PasswordForm& form) { | |
1065 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1066 if (!login_metadata_db_) | |
1067 return PasswordStoreChangeList(); | |
1068 | |
1069 PasswordStoreChangeList changes = login_metadata_db_->UpdateLogin(form); | |
1070 | |
1071 MacKeychainPasswordFormAdapter keychain_adapter(keychain_.get()); | |
1072 if (changes.empty() && | |
1073 !keychain_adapter.HasPasswordsMergeableWithForm(form)) { | |
1074 // If the password isn't in either the DB or the keychain, then it must have | |
1075 // been deleted after autofill happened, and should not be re-added. | |
1076 return changes; | |
1077 } | |
1078 | |
1079 // The keychain add will update if there is a collision and add if there | |
1080 // isn't, which is the behavior we want, so there's no separate update call. | |
1081 if (AddToKeychainIfNecessary(form) && changes.empty()) { | |
1082 changes = login_metadata_db_->AddLogin(form); | |
1083 } | |
1084 return changes; | |
1085 } | |
1086 | |
1087 PasswordStoreChangeList PasswordStoreMac::RemoveLoginImpl( | |
1088 const PasswordForm& form) { | |
1089 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1090 PasswordStoreChangeList changes; | |
1091 if (login_metadata_db_ && login_metadata_db_->RemoveLogin(form)) { | |
1092 // See if we own a Keychain item associated with this item. We can do an | |
1093 // exact search rather than messing around with trying to do fuzzy matching | |
1094 // because passwords that we created will always have an exact-match | |
1095 // database entry. | |
1096 // (If a user does lose their profile but not their keychain we'll treat the | |
1097 // entries we find like other imported entries anyway, so it's reasonable to | |
1098 // handle deletes on them the way we would for an imported item.) | |
1099 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_.get()); | |
1100 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); | |
1101 if (owned_keychain_adapter.HasPasswordExactlyMatchingForm(form)) { | |
1102 // If we don't have other forms using it (i.e., a form differing only by | |
1103 // the names of the form elements), delete the keychain entry. | |
1104 if (!DatabaseHasFormMatchingKeychainForm(form)) { | |
1105 owned_keychain_adapter.RemovePassword(form); | |
1106 } | |
1107 } | |
1108 | |
1109 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form)); | |
1110 } | |
1111 return changes; | |
1112 } | |
1113 | |
1114 PasswordStoreChangeList PasswordStoreMac::RemoveLoginsByURLAndTimeImpl( | |
1115 const base::Callback<bool(const GURL&)>& url_filter, | |
1116 base::Time delete_begin, | |
1117 base::Time delete_end) { | |
1118 PasswordStoreChangeList changes; | |
1119 std::vector<std::unique_ptr<PasswordForm>> forms_to_consider; | |
1120 std::vector<std::unique_ptr<PasswordForm>> forms_to_remove; | |
1121 if (login_metadata_db_ && | |
1122 login_metadata_db_->GetLoginsCreatedBetween(delete_begin, delete_end, | |
1123 &forms_to_consider)) { | |
1124 for (std::unique_ptr<PasswordForm>& form_to_consider : forms_to_consider) { | |
1125 if (url_filter.Run(form_to_consider->origin) && | |
1126 login_metadata_db_->RemoveLogin(*form_to_consider)) | |
1127 forms_to_remove.push_back(std::move(form_to_consider)); | |
1128 } | |
1129 if (!forms_to_remove.empty()) { | |
1130 RemoveKeychainForms(forms_to_remove); | |
1131 CleanOrphanedForms(&forms_to_remove); // Add the orphaned forms. | |
1132 changes = FormsToRemoveChangeList(forms_to_remove); | |
1133 LogStatsForBulkDeletion(changes.size()); | |
1134 } | |
1135 } | |
1136 return changes; | |
1137 } | |
1138 | |
1139 PasswordStoreChangeList PasswordStoreMac::RemoveLoginsCreatedBetweenImpl( | |
1140 base::Time delete_begin, | |
1141 base::Time delete_end) { | |
1142 PasswordStoreChangeList changes; | |
1143 std::vector<std::unique_ptr<PasswordForm>> forms_to_remove; | |
1144 if (login_metadata_db_ && | |
1145 login_metadata_db_->GetLoginsCreatedBetween(delete_begin, delete_end, | |
1146 &forms_to_remove) && | |
1147 login_metadata_db_->RemoveLoginsCreatedBetween(delete_begin, | |
1148 delete_end)) { | |
1149 RemoveKeychainForms(forms_to_remove); | |
1150 CleanOrphanedForms(&forms_to_remove); // Add the orphaned forms. | |
1151 changes = FormsToRemoveChangeList(forms_to_remove); | |
1152 LogStatsForBulkDeletion(changes.size()); | |
1153 } | |
1154 return changes; | |
1155 } | |
1156 | |
1157 PasswordStoreChangeList PasswordStoreMac::RemoveLoginsSyncedBetweenImpl( | |
1158 base::Time delete_begin, | |
1159 base::Time delete_end) { | |
1160 PasswordStoreChangeList changes; | |
1161 std::vector<std::unique_ptr<PasswordForm>> forms_to_remove; | |
1162 if (login_metadata_db_ && | |
1163 login_metadata_db_->GetLoginsSyncedBetween(delete_begin, delete_end, | |
1164 &forms_to_remove) && | |
1165 login_metadata_db_->RemoveLoginsSyncedBetween(delete_begin, delete_end)) { | |
1166 RemoveKeychainForms(forms_to_remove); | |
1167 CleanOrphanedForms(&forms_to_remove); // Add the orphaned forms_to_remove. | |
1168 changes = FormsToRemoveChangeList(forms_to_remove); | |
1169 LogStatsForBulkDeletionDuringRollback(changes.size()); | |
1170 } | |
1171 return changes; | |
1172 } | |
1173 | |
1174 PasswordStoreChangeList PasswordStoreMac::DisableAutoSignInForOriginsImpl( | |
1175 const base::Callback<bool(const GURL&)>& origin_filter) { | |
1176 std::vector<std::unique_ptr<PasswordForm>> forms; | |
1177 PasswordStoreChangeList changes; | |
1178 if (!login_metadata_db_ || | |
1179 !login_metadata_db_->GetAutoSignInLogins(&forms)) { | |
1180 return changes; | |
1181 } | |
1182 | |
1183 std::set<GURL> origins_to_update; | |
1184 for (const auto& form : forms) { | |
1185 if (origin_filter.Run(form->origin)) | |
1186 origins_to_update.insert(form->origin); | |
1187 } | |
1188 | |
1189 std::set<GURL> origins_updated; | |
1190 for (const GURL& origin : origins_to_update) { | |
1191 if (login_metadata_db_->DisableAutoSignInForOrigin(origin)) | |
1192 origins_updated.insert(origin); | |
1193 } | |
1194 | |
1195 for (const auto& form : forms) { | |
1196 if (origins_updated.count(form->origin)) { | |
1197 changes.push_back( | |
1198 PasswordStoreChange(PasswordStoreChange::UPDATE, *form)); | |
1199 } | |
1200 } | |
1201 | |
1202 return changes; | |
1203 } | |
1204 | |
1205 bool PasswordStoreMac::RemoveStatisticsByOriginAndTimeImpl( | |
1206 const base::Callback<bool(const GURL&)>& origin_filter, | |
1207 base::Time delete_begin, | |
1208 base::Time delete_end) { | |
1209 return login_metadata_db_ && | |
1210 login_metadata_db_->stats_table().RemoveStatsByOriginAndTime( | |
1211 origin_filter, delete_begin, delete_end); | |
1212 } | |
1213 | |
1214 std::vector<std::unique_ptr<PasswordForm>> PasswordStoreMac::FillMatchingLogins( | |
1215 const FormDigest& form) { | |
1216 chrome::ScopedSecKeychainSetUserInteractionAllowed user_interaction_allowed( | |
1217 false); | |
1218 | |
1219 std::vector<std::unique_ptr<PasswordForm>> database_forms; | |
1220 if (!login_metadata_db_ || | |
1221 !login_metadata_db_->GetLogins(form, &database_forms)) { | |
1222 return std::vector<std::unique_ptr<PasswordForm>>(); | |
1223 } | |
1224 | |
1225 // Let's gather all signon realms we want to match with keychain entries. | |
1226 std::set<std::string> realm_set; | |
1227 realm_set.insert(form.signon_realm); | |
1228 for (const std::unique_ptr<PasswordForm>& db_form : database_forms) { | |
1229 // TODO(vabr): We should not be getting different schemes here. | |
1230 // http://crbug.com/340112 | |
1231 if (form.scheme != db_form->scheme) | |
1232 continue; // Forms with different schemes never match. | |
1233 if (db_form->is_public_suffix_match || db_form->is_affiliation_based_match) | |
1234 realm_set.insert(db_form->signon_realm); | |
1235 } | |
1236 std::vector<std::unique_ptr<PasswordForm>> keychain_forms; | |
1237 for (std::set<std::string>::const_iterator realm = realm_set.begin(); | |
1238 realm != realm_set.end(); ++realm) { | |
1239 MacKeychainPasswordFormAdapter keychain_adapter(keychain_.get()); | |
1240 std::vector<std::unique_ptr<PasswordForm>> temp_keychain_forms = | |
1241 keychain_adapter.PasswordsFillingForm(*realm, form.scheme); | |
1242 AppendSecondToFirst(&keychain_forms, &temp_keychain_forms); | |
1243 } | |
1244 | |
1245 std::vector<std::unique_ptr<PasswordForm>> matched_forms; | |
1246 internal_keychain_helpers::MergePasswordForms( | |
1247 &keychain_forms, &database_forms, &matched_forms); | |
1248 | |
1249 // Strip any blacklist entries out of the unused Keychain array, then take | |
1250 // all the entries that are left (which we can use as imported passwords). | |
1251 std::vector<std::unique_ptr<PasswordForm>> keychain_blacklist_forms; | |
1252 internal_keychain_helpers::ExtractNonKeychainForms(&keychain_forms, | |
1253 &keychain_blacklist_forms); | |
1254 AppendSecondToFirst(&matched_forms, &keychain_forms); | |
1255 | |
1256 if (!database_forms.empty()) { | |
1257 RemoveDatabaseForms(&database_forms); | |
1258 NotifyLoginsChanged(FormsToRemoveChangeList(database_forms)); | |
1259 } | |
1260 | |
1261 return matched_forms; | |
1262 } | |
1263 | |
1264 std::vector<std::unique_ptr<autofill::PasswordForm>> | |
1265 PasswordStoreMac::FillLoginsForSameOrganizationName( | |
1266 const std::string& signon_realm) { | |
1267 // Not implemented. | |
1268 return std::vector<std::unique_ptr<autofill::PasswordForm>>(); | |
1269 } | |
1270 | |
1271 bool PasswordStoreMac::FillAutofillableLogins( | |
1272 std::vector<std::unique_ptr<PasswordForm>>* forms) { | |
1273 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1274 forms->clear(); | |
1275 | |
1276 std::vector<std::unique_ptr<PasswordForm>> database_forms; | |
1277 if (!login_metadata_db_ || | |
1278 !login_metadata_db_->GetAutofillableLogins(&database_forms)) | |
1279 return false; | |
1280 | |
1281 internal_keychain_helpers::GetPasswordsForForms(*keychain_, &database_forms, | |
1282 forms); | |
1283 | |
1284 if (!database_forms.empty()) { | |
1285 RemoveDatabaseForms(&database_forms); | |
1286 NotifyLoginsChanged(FormsToRemoveChangeList(database_forms)); | |
1287 } | |
1288 | |
1289 return true; | |
1290 } | |
1291 | |
1292 bool PasswordStoreMac::FillBlacklistLogins( | |
1293 std::vector<std::unique_ptr<PasswordForm>>* forms) { | |
1294 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1295 return login_metadata_db_ && login_metadata_db_->GetBlacklistLogins(forms); | |
1296 } | |
1297 | |
1298 void PasswordStoreMac::AddSiteStatsImpl( | |
1299 const password_manager::InteractionsStats& stats) { | |
1300 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1301 if (login_metadata_db_) | |
1302 login_metadata_db_->stats_table().AddRow(stats); | |
1303 } | |
1304 | |
1305 void PasswordStoreMac::RemoveSiteStatsImpl(const GURL& origin_domain) { | |
1306 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1307 if (login_metadata_db_) | |
1308 login_metadata_db_->stats_table().RemoveRow(origin_domain); | |
1309 } | |
1310 | |
1311 std::vector<password_manager::InteractionsStats> | |
1312 PasswordStoreMac::GetAllSiteStatsImpl() { | |
1313 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1314 return login_metadata_db_ | |
1315 ? login_metadata_db_->stats_table().GetAllRows() | |
1316 : std::vector<password_manager::InteractionsStats>(); | |
1317 } | |
1318 | |
1319 std::vector<password_manager::InteractionsStats> | |
1320 PasswordStoreMac::GetSiteStatsImpl(const GURL& origin_domain) { | |
1321 DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); | |
1322 return login_metadata_db_ | |
1323 ? login_metadata_db_->stats_table().GetRows(origin_domain) | |
1324 : std::vector<password_manager::InteractionsStats>(); | |
1325 } | |
1326 | |
1327 bool PasswordStoreMac::AddToKeychainIfNecessary(const PasswordForm& form) { | |
1328 if (IsLoginDatabaseOnlyForm(form)) | |
1329 return true; | |
1330 MacKeychainPasswordFormAdapter keychainAdapter(keychain_.get()); | |
1331 return keychainAdapter.AddPassword(form); | |
1332 } | |
1333 | |
1334 bool PasswordStoreMac::DatabaseHasFormMatchingKeychainForm( | |
1335 const PasswordForm& form) { | |
1336 DCHECK(login_metadata_db_); | |
1337 bool has_match = false; | |
1338 std::vector<std::unique_ptr<PasswordForm>> database_forms; | |
1339 if (!login_metadata_db_->GetLogins( | |
1340 password_manager::PasswordStore::FormDigest(form), &database_forms)) | |
1341 return false; | |
1342 for (const auto& db_form : database_forms) { | |
1343 // Below we filter out fuzzy matched forms because we are only interested | |
1344 // in exact ones. | |
1345 if (!db_form->is_public_suffix_match && | |
1346 internal_keychain_helpers::FormsMatchForMerge( | |
1347 form, *db_form, internal_keychain_helpers::STRICT_FORM_MATCH) && | |
1348 db_form->origin == form.origin) { | |
1349 has_match = true; | |
1350 break; | |
1351 } | |
1352 } | |
1353 return has_match; | |
1354 } | |
1355 | |
1356 void PasswordStoreMac::RemoveDatabaseForms( | |
1357 std::vector<std::unique_ptr<PasswordForm>>* forms) { | |
1358 DCHECK(login_metadata_db_); | |
1359 std::vector<std::unique_ptr<PasswordForm>> removed_forms; | |
1360 for (std::unique_ptr<PasswordForm>& form : *forms) { | |
1361 if (login_metadata_db_->RemoveLogin(*form)) | |
1362 removed_forms.push_back(std::move(form)); | |
1363 } | |
1364 removed_forms.swap(*forms); | |
1365 } | |
1366 | |
1367 void PasswordStoreMac::RemoveKeychainForms( | |
1368 const std::vector<std::unique_ptr<PasswordForm>>& forms) { | |
1369 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_.get()); | |
1370 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); | |
1371 for (const auto& form : forms) { | |
1372 owned_keychain_adapter.RemovePassword(*form); | |
1373 } | |
1374 } | |
1375 | |
1376 void PasswordStoreMac::CleanOrphanedForms( | |
1377 std::vector<std::unique_ptr<PasswordForm>>* orphaned_forms) { | |
1378 DCHECK(orphaned_forms); | |
1379 DCHECK(login_metadata_db_); | |
1380 | |
1381 std::vector<std::unique_ptr<PasswordForm>> database_forms; | |
1382 if (!login_metadata_db_->GetAutofillableLogins(&database_forms)) | |
1383 return; | |
1384 | |
1385 // Filter forms with corresponding Keychain entry out of |database_forms|. | |
1386 std::vector<std::unique_ptr<PasswordForm>> forms_with_keychain_entry; | |
1387 internal_keychain_helpers::GetPasswordsForForms(*keychain_, &database_forms, | |
1388 &forms_with_keychain_entry); | |
1389 | |
1390 // Clean up any orphaned database entries. | |
1391 RemoveDatabaseForms(&database_forms); | |
1392 | |
1393 // Move the orphaned DB forms to the output parameter. | |
1394 AppendSecondToFirst(orphaned_forms, &database_forms); | |
1395 } | |
OLD | NEW |