1 // Copyright (c) 2011 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 #include "chrome/browser/password_manager/password_store_mac_internal.h"
7
8 #include <CoreServices/CoreServices.h>
9 #include <set>
10 #include <string>
11 #include <vector>
12
13 #include "base/logging.h"
14 #include "base/mac/mac_util.h"
15 #include "base/message_loop.h"
16 #include "base/stl_util-inl.h"
17 #include "base/string_util.h"
18 #include "base/task.h"
19 #include "base/utf_string_conversions.h"
20 #include "chrome/browser/keychain_mac.h"
21 #include "chrome/browser/password_manager/login_database.h"
22 #include "chrome/browser/password_manager/password_store_change.h"
23 #include "content/common/notification_service.h"
24
25 using webkit_glue::PasswordForm;
26
27 // Utility class to handle the details of constructing and running a keychain
28 // search from a set of attributes.
29 class KeychainSearch {
30 public:
31 explicit KeychainSearch(const MacKeychain& keychain);
32 ~KeychainSearch();
33
34 // Sets up a keycahin search based on an non "null" (NULL for char*,
35 // The appropriate "Any" entry for other types) arguments.
36 //
37 // IMPORTANT: Any paramaters passed in *must* remain valid for as long as the
38 // KeychainSearch object, since the search uses them by reference.
39 void Init(const char* server, const UInt32& port,
40 const SecProtocolType& protocol,
41 const SecAuthenticationType& auth_type, const char* security_domain,
42 const char* path, const char* username, OSType creator);
43
44 // Fills |items| with all Keychain items that match the Init'd search.
45 // If the search fails for any reason, |items| will be unchanged.
46 void FindMatchingItems(std::vector<SecKeychainItemRef>* matches);
47
48 private:
49 const MacKeychain* keychain_;
50 SecKeychainAttributeList search_attributes_;
51 SecKeychainSearchRef search_ref_;
52 };
53
KeychainSearch(const MacKeychain & keychain)54 KeychainSearch::KeychainSearch(const MacKeychain& keychain)
55 : keychain_(&keychain), search_ref_(NULL) {
56 search_attributes_.count = 0;
57 search_attributes_.attr = NULL;
58 }
59
~KeychainSearch()60 KeychainSearch::~KeychainSearch() {
61 if (search_attributes_.attr) {
62 free(search_attributes_.attr);
63 }
64 }
65
Init(const char * server,const UInt32 & port,const SecProtocolType & protocol,const SecAuthenticationType & auth_type,const char * security_domain,const char * path,const char * username,OSType creator)66 void KeychainSearch::Init(const char* server, const UInt32& port,
67 const SecProtocolType& protocol,
68 const SecAuthenticationType& auth_type,
69 const char* security_domain, const char* path,
70 const char* username, OSType creator) {
71 // Allocate enough to hold everything we might use.
72 const unsigned int kMaxEntryCount = 8;
73 search_attributes_.attr =
74 static_cast<SecKeychainAttribute*>(calloc(kMaxEntryCount,
75 sizeof(SecKeychainAttribute)));
76 unsigned int entries = 0;
77 // We only use search_attributes_ with SearchCreateFromAttributes, which takes
78 // a "const SecKeychainAttributeList *", so we trust that they won't try
79 // to modify the list, and that casting away const-ness is thus safe.
80 if (server != NULL) {
81 DCHECK(entries < kMaxEntryCount);
82 search_attributes_.attr[entries].tag = kSecServerItemAttr;
83 search_attributes_.attr[entries].length = strlen(server);
84 search_attributes_.attr[entries].data =
85 const_cast<void*>(reinterpret_cast<const void*>(server));
86 ++entries;
87 }
88 if (port != kAnyPort) {
89 DCHECK(entries <= kMaxEntryCount);
90 search_attributes_.attr[entries].tag = kSecPortItemAttr;
91 search_attributes_.attr[entries].length = sizeof(port);
92 search_attributes_.attr[entries].data =
93 const_cast<void*>(reinterpret_cast<const void*>(&port));
94 ++entries;
95 }
96 if (protocol != kSecProtocolTypeAny) {
97 DCHECK(entries <= kMaxEntryCount);
98 search_attributes_.attr[entries].tag = kSecProtocolItemAttr;
99 search_attributes_.attr[entries].length = sizeof(protocol);
100 search_attributes_.attr[entries].data =
101 const_cast<void*>(reinterpret_cast<const void*>(&protocol));
102 ++entries;
103 }
104 if (auth_type != kSecAuthenticationTypeAny) {
105 DCHECK(entries <= kMaxEntryCount);
106 search_attributes_.attr[entries].tag = kSecAuthenticationTypeItemAttr;
107 search_attributes_.attr[entries].length = sizeof(auth_type);
108 search_attributes_.attr[entries].data =
109 const_cast<void*>(reinterpret_cast<const void*>(&auth_type));
110 ++entries;
111 }
112 if (security_domain != NULL && strlen(security_domain) > 0) {
113 DCHECK(entries <= kMaxEntryCount);
114 search_attributes_.attr[entries].tag = kSecSecurityDomainItemAttr;
115 search_attributes_.attr[entries].length = strlen(security_domain);
116 search_attributes_.attr[entries].data =
117 const_cast<void*>(reinterpret_cast<const void*>(security_domain));
118 ++entries;
119 }
120 if (path != NULL && strlen(path) > 0 && strcmp(path, "/") != 0) {
121 DCHECK(entries <= kMaxEntryCount);
122 search_attributes_.attr[entries].tag = kSecPathItemAttr;
123 search_attributes_.attr[entries].length = strlen(path);
124 search_attributes_.attr[entries].data =
125 const_cast<void*>(reinterpret_cast<const void*>(path));
126 ++entries;
127 }
128 if (username != NULL) {
129 DCHECK(entries <= kMaxEntryCount);
130 search_attributes_.attr[entries].tag = kSecAccountItemAttr;
131 search_attributes_.attr[entries].length = strlen(username);
132 search_attributes_.attr[entries].data =
133 const_cast<void*>(reinterpret_cast<const void*>(username));
134 ++entries;
135 }
136 if (creator != 0) {
137 DCHECK(entries <= kMaxEntryCount);
138 search_attributes_.attr[entries].tag = kSecCreatorItemAttr;
139 search_attributes_.attr[entries].length = sizeof(creator);
140 search_attributes_.attr[entries].data =
141 const_cast<void*>(reinterpret_cast<const void*>(&creator));
142 ++entries;
143 }
144 search_attributes_.count = entries;
145 }
146
FindMatchingItems(std::vector<SecKeychainItemRef> * items)147 void KeychainSearch::FindMatchingItems(std::vector<SecKeychainItemRef>* items) {
148 OSStatus result = keychain_->SearchCreateFromAttributes(
149 NULL, kSecInternetPasswordItemClass, &search_attributes_, &search_ref_);
150
151 if (result != noErr) {
152 LOG(ERROR) << "Keychain lookup failed with error " << result;
153 return;
154 }
155
156 SecKeychainItemRef keychain_item;
157 while (keychain_->SearchCopyNext(search_ref_, &keychain_item) == noErr) {
158 // Consumer is responsible for freeing the items.
159 items->push_back(keychain_item);
160 }
161
162 keychain_->Free(search_ref_);
163 search_ref_ = NULL;
164 }
165
166 #pragma mark -
167
168 // TODO(stuartmorgan): Convert most of this to private helpers in
169 // MacKeychainPasswordFormAdapter once it has sufficient higher-level public
170 // methods to provide test coverage.
171 namespace internal_keychain_helpers {
172
173 // Returns a URL built from the given components. To create a URL without a
174 // port, pass kAnyPort for the |port| parameter.
URLFromComponents(bool is_secure,const std::string & host,int port,const std::string & path)175 GURL URLFromComponents(bool is_secure, const std::string& host, int port,
176 const std::string& path) {
177 GURL::Replacements url_components;
178 std::string scheme(is_secure ? "https" : "http");
179 url_components.SetSchemeStr(scheme);
180 url_components.SetHostStr(host);
181 std::string port_string; // Must remain in scope until after we do replacing.
182 if (port != kAnyPort) {
183 std::ostringstream port_stringstream;
184 port_stringstream << port;
185 port_string = port_stringstream.str();
186 url_components.SetPortStr(port_string);
187 }
188 url_components.SetPathStr(path);
189
190 GURL url("http://dummy.com"); // ReplaceComponents needs a valid URL.
191 return url.ReplaceComponents(url_components);
192 }
193
194 // Converts a Keychain time string to a Time object, returning true if
195 // time_string_bytes was parsable. If the return value is false, the value of
196 // |time| is unchanged.
TimeFromKeychainTimeString(const char * time_string_bytes,unsigned int byte_length,base::Time * time)197 bool TimeFromKeychainTimeString(const char* time_string_bytes,
198 unsigned int byte_length,
199 base::Time* time) {
200 DCHECK(time);
201
202 char* time_string = static_cast<char*>(malloc(byte_length + 1));
203 memcpy(time_string, time_string_bytes, byte_length);
204 time_string[byte_length] = '\0';
205 base::Time::Exploded exploded_time;
206 bzero(&exploded_time, sizeof(exploded_time));
207 // The time string is of the form "yyyyMMddHHmmss'Z", in UTC time.
208 int assignments = sscanf(time_string, "%4d%2d%2d%2d%2d%2dZ",
209 &exploded_time.year, &exploded_time.month,
210 &exploded_time.day_of_month, &exploded_time.hour,
211 &exploded_time.minute, &exploded_time.second);
212 free(time_string);
213
214 if (assignments == 6) {
215 *time = base::Time::FromUTCExploded(exploded_time);
216 return true;
217 }
218 return false;
219 }
220
221 // Returns the PasswordForm Scheme corresponding to |auth_type|.
SchemeForAuthType(SecAuthenticationType auth_type)222 PasswordForm::Scheme SchemeForAuthType(SecAuthenticationType auth_type) {
223 switch (auth_type) {
224 case kSecAuthenticationTypeHTMLForm: return PasswordForm::SCHEME_HTML;
225 case kSecAuthenticationTypeHTTPBasic: return PasswordForm::SCHEME_BASIC;
226 case kSecAuthenticationTypeHTTPDigest: return PasswordForm::SCHEME_DIGEST;
227 default: return PasswordForm::SCHEME_OTHER;
228 }
229 }
230
FillPasswordFormFromKeychainItem(const MacKeychain & keychain,const SecKeychainItemRef & keychain_item,PasswordForm * form)231 bool FillPasswordFormFromKeychainItem(const MacKeychain& keychain,
232 const SecKeychainItemRef& keychain_item,
233 PasswordForm* form) {
234 DCHECK(form);
235
236 SecKeychainAttributeInfo attrInfo;
237 UInt32 tags[] = { kSecAccountItemAttr,
238 kSecServerItemAttr,
239 kSecPortItemAttr,
240 kSecPathItemAttr,
241 kSecProtocolItemAttr,
242 kSecAuthenticationTypeItemAttr,
243 kSecSecurityDomainItemAttr,
244 kSecCreationDateItemAttr,
245 kSecNegativeItemAttr };
246 attrInfo.count = arraysize(tags);
247 attrInfo.tag = tags;
248 attrInfo.format = NULL;
249
250 SecKeychainAttributeList *attrList;
251 UInt32 password_length;
252 void* password_data;
253 OSStatus result = keychain.ItemCopyAttributesAndData(keychain_item, &attrInfo,
254 NULL, &attrList,
255 &password_length,
256 &password_data);
257
258 if (result != noErr) {
259 // We don't log errSecAuthFailed because that just means that the user
260 // chose not to allow us access to the item.
261 if (result != errSecAuthFailed) {
262 LOG(ERROR) << "Keychain data load failed: " << result;
263 }
264 return false;
265 }
266
267 UTF8ToUTF16(static_cast<const char *>(password_data), password_length,
268 &(form->password_value));
269
270 int port = kAnyPort;
271 std::string server;
272 std::string security_domain;
273 std::string path;
274 for (unsigned int i = 0; i < attrList->count; i++) {
275 SecKeychainAttribute attr = attrList->attr[i];
276 if (!attr.data) {
277 continue;
278 }
279 switch (attr.tag) {
280 case kSecAccountItemAttr:
281 UTF8ToUTF16(static_cast<const char *>(attr.data), attr.length,
282 &(form->username_value));
283 break;
284 case kSecServerItemAttr:
285 server.assign(static_cast<const char *>(attr.data), attr.length);
286 break;
287 case kSecPortItemAttr:
288 port = *(static_cast<UInt32*>(attr.data));
289 break;
290 case kSecPathItemAttr:
291 path.assign(static_cast<const char *>(attr.data), attr.length);
292 break;
293 case kSecProtocolItemAttr:
294 {
295 SecProtocolType protocol = *(static_cast<SecProtocolType*>(attr.data));
296 // TODO(stuartmorgan): Handle proxy types
297 form->ssl_valid = (protocol == kSecProtocolTypeHTTPS);
298 break;
299 }
300 case kSecAuthenticationTypeItemAttr:
301 {
302 SecAuthenticationType auth_type =
303 *(static_cast<SecAuthenticationType*>(attr.data));
304 form->scheme = SchemeForAuthType(auth_type);
305 break;
306 }
307 case kSecSecurityDomainItemAttr:
308 security_domain.assign(static_cast<const char *>(attr.data),
309 attr.length);
310 break;
311 case kSecCreationDateItemAttr:
312 // The only way to get a date out of Keychain is as a string. Really.
313 // (The docs claim it's an int, but the header is correct.)
314 TimeFromKeychainTimeString(static_cast<char*>(attr.data), attr.length,
315 &form->date_created);
316 break;
317 case kSecNegativeItemAttr:
318 Boolean negative_item = *(static_cast<Boolean*>(attr.data));
319 if (negative_item) {
320 form->blacklisted_by_user = true;
321 }
322 break;
323 }
324 }
325 keychain.ItemFreeAttributesAndData(attrList, password_data);
326
327 // kSecNegativeItemAttr doesn't seem to actually be in widespread use. In
328 // practice, other browsers seem to use a "" or " " password (and a special
329 // user name) to indicated blacklist entries.
330 if (form->password_value.empty() || EqualsASCII(form->password_value, " ")) {
331 form->blacklisted_by_user = true;
332 }
333
334 form->origin = URLFromComponents(form->ssl_valid, server, port, path);
335 // TODO(stuartmorgan): Handle proxies, which need a different signon_realm
336 // format.
337 form->signon_realm = form->origin.GetOrigin().spec();
338 if (form->scheme != PasswordForm::SCHEME_HTML) {
339 form->signon_realm.append(security_domain);
340 }
341 return true;
342 }
343
FormsMatchForMerge(const PasswordForm & form_a,const PasswordForm & form_b)344 bool FormsMatchForMerge(const PasswordForm& form_a,
345 const PasswordForm& form_b) {
346 // We never merge blacklist entries between our store and the keychain.
347 if (form_a.blacklisted_by_user || form_b.blacklisted_by_user) {
348 return false;
349 }
350 return form_a.scheme == form_b.scheme &&
351 form_a.signon_realm == form_b.signon_realm &&
352 form_a.username_value == form_b.username_value;
353 }
354
355 // Returns an the best match for |form| from |keychain_forms|, or NULL if there
356 // is no suitable match.
BestKeychainFormForForm(const PasswordForm & base_form,const std::vector<PasswordForm * > * keychain_forms)357 PasswordForm* BestKeychainFormForForm(
358 const PasswordForm& base_form,
359 const std::vector<PasswordForm*>* keychain_forms) {
360 PasswordForm* partial_match = NULL;
361 for (std::vector<PasswordForm*>::const_iterator i = keychain_forms->begin();
362 i != keychain_forms->end(); ++i) {
363 // TODO(stuartmorgan): We should really be scoring path matches and picking
364 // the best, rather than just checking exact-or-not (although in practice
365 // keychain items with paths probably came from us).
366 if (FormsMatchForMerge(base_form, *(*i))) {
367 if (base_form.origin == (*i)->origin) {
368 return *i;
369 } else if (!partial_match) {
370 partial_match = *i;
371 }
372 }
373 }
374 return partial_match;
375 }
376
377 // Returns entries from |forms| that are blacklist entries, after removing
378 // them from |forms|.
ExtractBlacklistForms(std::vector<PasswordForm * > * forms)379 std::vector<PasswordForm*> ExtractBlacklistForms(
380 std::vector<PasswordForm*>* forms) {
381 std::vector<PasswordForm*> blacklist_forms;
382 for (std::vector<PasswordForm*>::iterator i = forms->begin();
383 i != forms->end();) {
384 PasswordForm* form = *i;
385 if (form->blacklisted_by_user) {
386 blacklist_forms.push_back(form);
387 i = forms->erase(i);
388 } else {
389 ++i;
390 }
391 }
392 return blacklist_forms;
393 }
394
395 // Deletes and removes from v any element that exists in s.
396 template <class T>
DeleteVectorElementsInSet(std::vector<T * > * v,const std::set<T * > & s)397 void DeleteVectorElementsInSet(std::vector<T*>* v, const std::set<T*>& s) {
398 for (typename std::vector<T*>::iterator i = v->begin(); i != v->end();) {
399 T* element = *i;
400 if (s.find(element) != s.end()) {
401 delete element;
402 i = v->erase(i);
403 } else {
404 ++i;
405 }
406 }
407 }
408
MergePasswordForms(std::vector<PasswordForm * > * keychain_forms,std::vector<PasswordForm * > * database_forms,std::vector<PasswordForm * > * merged_forms)409 void MergePasswordForms(std::vector<PasswordForm*>* keychain_forms,
410 std::vector<PasswordForm*>* database_forms,
411 std::vector<PasswordForm*>* merged_forms) {
412 // Pull out the database blacklist items, since they are used as-is rather
413 // than being merged with keychain forms.
414 std::vector<PasswordForm*> database_blacklist_forms =
415 ExtractBlacklistForms(database_forms);
416
417 // Merge the normal entries.
418 std::set<PasswordForm*> used_keychain_forms;
419 for (std::vector<PasswordForm*>::iterator i = database_forms->begin();
420 i != database_forms->end();) {
421 PasswordForm* db_form = *i;
422 PasswordForm* best_match = BestKeychainFormForForm(*db_form,
423 keychain_forms);
424 if (best_match) {
425 used_keychain_forms.insert(best_match);
426 db_form->password_value = best_match->password_value;
427 merged_forms->push_back(db_form);
428 i = database_forms->erase(i);
429 } else {
430 ++i;
431 }
432 }
433
434 // Add in the blacklist entries from the database.
435 merged_forms->insert(merged_forms->end(),
436 database_blacklist_forms.begin(),
437 database_blacklist_forms.end());
438
439 // Clear out all the Keychain entries we used.
440 DeleteVectorElementsInSet(keychain_forms, used_keychain_forms);
441 }
442
GetPasswordsForForms(const MacKeychain & keychain,std::vector<PasswordForm * > * database_forms)443 std::vector<PasswordForm*> GetPasswordsForForms(
444 const MacKeychain& keychain, std::vector<PasswordForm*>* database_forms) {
445 MacKeychainPasswordFormAdapter keychain_adapter(&keychain);
446
447 std::vector<PasswordForm*> merged_forms;
448 for (std::vector<PasswordForm*>::iterator i = database_forms->begin();
449 i != database_forms->end();) {
450 std::vector<PasswordForm*> db_form_container(1, *i);
451 std::vector<PasswordForm*> keychain_matches =
452 keychain_adapter.PasswordsMergeableWithForm(**i);
453 MergePasswordForms(&keychain_matches, &db_form_container, &merged_forms);
454 if (db_form_container.empty()) {
455 i = database_forms->erase(i);
456 } else {
457 ++i;
458 }
459 STLDeleteElements(&keychain_matches);
460 }
461 return merged_forms;
462 }
463
464 } // namespace internal_keychain_helpers
465
466 #pragma mark -
467
MacKeychainPasswordFormAdapter(const MacKeychain * keychain)468 MacKeychainPasswordFormAdapter::MacKeychainPasswordFormAdapter(
469 const MacKeychain* keychain)
470 : keychain_(keychain), finds_only_owned_(false) {
471 }
472
473 std::vector<PasswordForm*>
PasswordsFillingForm(const PasswordForm & query_form)474 MacKeychainPasswordFormAdapter::PasswordsFillingForm(
475 const PasswordForm& query_form) {
476 std::vector<SecKeychainItemRef> keychain_items =
477 MatchingKeychainItems(query_form.signon_realm, query_form.scheme,
478 NULL, NULL);
479
480 return ConvertKeychainItemsToForms(&keychain_items);
481 }
482
483 std::vector<PasswordForm*>
PasswordsMergeableWithForm(const PasswordForm & query_form)484 MacKeychainPasswordFormAdapter::PasswordsMergeableWithForm(
485 const PasswordForm& query_form) {
486 std::string username = UTF16ToUTF8(query_form.username_value);
487 std::vector<SecKeychainItemRef> keychain_items =
488 MatchingKeychainItems(query_form.signon_realm, query_form.scheme,
489 NULL, username.c_str());
490
491 return ConvertKeychainItemsToForms(&keychain_items);
492 }
493
PasswordExactlyMatchingForm(const PasswordForm & query_form)494 PasswordForm* MacKeychainPasswordFormAdapter::PasswordExactlyMatchingForm(
495 const PasswordForm& query_form) {
496 SecKeychainItemRef keychain_item = KeychainItemForForm(query_form);
497 if (keychain_item) {
498 PasswordForm* form = new PasswordForm();
499 internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_,
500 keychain_item,
501 form);
502 keychain_->Free(keychain_item);
503 return form;
504 }
505 return NULL;
506 }
507
HasPasswordsMergeableWithForm(const PasswordForm & query_form)508 bool MacKeychainPasswordFormAdapter::HasPasswordsMergeableWithForm(
509 const PasswordForm& query_form) {
510 std::string username = UTF16ToUTF8(query_form.username_value);
511 std::vector<SecKeychainItemRef> matches =
512 MatchingKeychainItems(query_form.signon_realm, query_form.scheme,
513 NULL, username.c_str());
514 for (std::vector<SecKeychainItemRef>::iterator i = matches.begin();
515 i != matches.end(); ++i) {
516 keychain_->Free(*i);
517 }
518
519 return !matches.empty();
520 }
521
522 std::vector<PasswordForm*>
GetAllPasswordFormPasswords()523 MacKeychainPasswordFormAdapter::GetAllPasswordFormPasswords() {
524 SecAuthenticationType supported_auth_types[] = {
525 kSecAuthenticationTypeHTMLForm,
526 kSecAuthenticationTypeHTTPBasic,
527 kSecAuthenticationTypeHTTPDigest,
528 };
529
530 std::vector<SecKeychainItemRef> matches;
531 for (unsigned int i = 0; i < arraysize(supported_auth_types); ++i) {
532 KeychainSearch keychain_search(*keychain_);
533 keychain_search.Init(NULL, 0, kSecProtocolTypeAny, supported_auth_types[i],
534 NULL, NULL, NULL, CreatorCodeForSearch());
535 keychain_search.FindMatchingItems(&matches);
536 }
537
538 return ConvertKeychainItemsToForms(&matches);
539 }
540
AddPassword(const PasswordForm & form)541 bool MacKeychainPasswordFormAdapter::AddPassword(const PasswordForm& form) {
542 // We should never be trying to store a blacklist in the keychain.
543 DCHECK(!form.blacklisted_by_user);
544
545 std::string server;
546 std::string security_domain;
547 int port;
548 bool is_secure;
549 if (!ExtractSignonRealmComponents(form.signon_realm, &server, &port,
550 &is_secure, &security_domain)) {
551 return false;
552 }
553 std::string username = UTF16ToUTF8(form.username_value);
554 std::string password = UTF16ToUTF8(form.password_value);
555 std::string path = form.origin.path();
556 SecProtocolType protocol = is_secure ? kSecProtocolTypeHTTPS
557 : kSecProtocolTypeHTTP;
558 SecKeychainItemRef new_item = NULL;
559 OSStatus result = keychain_->AddInternetPassword(
560 NULL, server.size(), server.c_str(),
561 security_domain.size(), security_domain.c_str(),
562 username.size(), username.c_str(),
563 path.size(), path.c_str(),
564 port, protocol, AuthTypeForScheme(form.scheme),
565 password.size(), password.c_str(), &new_item);
566
567 if (result == noErr) {
568 SetKeychainItemCreatorCode(new_item,
569 base::mac::CreatorCodeForApplication());
570 keychain_->Free(new_item);
571 } else if (result == errSecDuplicateItem) {
572 // If we collide with an existing item, find and update it instead.
573 SecKeychainItemRef existing_item = KeychainItemForForm(form);
574 if (!existing_item) {
575 return false;
576 }
577 bool changed = SetKeychainItemPassword(existing_item, password);
578 keychain_->Free(existing_item);
579 return changed;
580 }
581
582 return result == noErr;
583 }
584
RemovePassword(const PasswordForm & form)585 bool MacKeychainPasswordFormAdapter::RemovePassword(const PasswordForm& form) {
586 SecKeychainItemRef keychain_item = KeychainItemForForm(form);
587 if (keychain_item == NULL)
588 return false;
589 OSStatus result = keychain_->ItemDelete(keychain_item);
590 keychain_->Free(keychain_item);
591 return result == noErr;
592 }
593
SetFindsOnlyOwnedItems(bool finds_only_owned)594 void MacKeychainPasswordFormAdapter::SetFindsOnlyOwnedItems(
595 bool finds_only_owned) {
596 finds_only_owned_ = finds_only_owned;
597 }
598
599 std::vector<PasswordForm*>
ConvertKeychainItemsToForms(std::vector<SecKeychainItemRef> * items)600 MacKeychainPasswordFormAdapter::ConvertKeychainItemsToForms(
601 std::vector<SecKeychainItemRef>* items) {
602 std::vector<PasswordForm*> keychain_forms;
603 for (std::vector<SecKeychainItemRef>::const_iterator i = items->begin();
604 i != items->end(); ++i) {
605 PasswordForm* form = new PasswordForm();
606 if (internal_keychain_helpers::FillPasswordFormFromKeychainItem(*keychain_,
607 *i, form)) {
608 keychain_forms.push_back(form);
609 }
610 keychain_->Free(*i);
611 }
612 items->clear();
613 return keychain_forms;
614 }
615
KeychainItemForForm(const PasswordForm & form)616 SecKeychainItemRef MacKeychainPasswordFormAdapter::KeychainItemForForm(
617 const PasswordForm& form) {
618 // We don't store blacklist entries in the keychain, so the answer to "what
619 // Keychain item goes with this form" is always "nothing" for blacklists.
620 if (form.blacklisted_by_user) {
621 return NULL;
622 }
623
624 std::string path = form.origin.path();
625 std::string username = UTF16ToUTF8(form.username_value);
626 std::vector<SecKeychainItemRef> matches = MatchingKeychainItems(
627 form.signon_realm, form.scheme, path.c_str(), username.c_str());
628
629 if (matches.empty()) {
630 return NULL;
631 }
632 // Free all items after the first, since we won't be returning them.
633 for (std::vector<SecKeychainItemRef>::iterator i = matches.begin() + 1;
634 i != matches.end(); ++i) {
635 keychain_->Free(*i);
636 }
637 return matches[0];
638 }
639
640 std::vector<SecKeychainItemRef>
MatchingKeychainItems(const std::string & signon_realm,webkit_glue::PasswordForm::Scheme scheme,const char * path,const char * username)641 MacKeychainPasswordFormAdapter::MatchingKeychainItems(
642 const std::string& signon_realm,
643 webkit_glue::PasswordForm::Scheme scheme,
644 const char* path, const char* username) {
645 std::vector<SecKeychainItemRef> matches;
646
647 std::string server;
648 std::string security_domain;
649 int port;
650 bool is_secure;
651 if (!ExtractSignonRealmComponents(signon_realm, &server, &port,
652 &is_secure, &security_domain)) {
653 // TODO(stuartmorgan): Proxies will currently fail here, since their
654 // signon_realm is not a URL. We need to detect the proxy case and handle
655 // it specially.
656 return matches;
657 }
658 SecProtocolType protocol = is_secure ? kSecProtocolTypeHTTPS
659 : kSecProtocolTypeHTTP;
660 SecAuthenticationType auth_type = AuthTypeForScheme(scheme);
661 const char* auth_domain = (scheme == PasswordForm::SCHEME_HTML) ?
662 NULL : security_domain.c_str();
663 KeychainSearch keychain_search(*keychain_);
664 keychain_search.Init(server.c_str(), port, protocol, auth_type,
665 auth_domain, path, username, CreatorCodeForSearch());
666 keychain_search.FindMatchingItems(&matches);
667 return matches;
668 }
669
670 // TODO(stuartmorgan): signon_realm for proxies is not yet supported.
ExtractSignonRealmComponents(const std::string & signon_realm,std::string * server,int * port,bool * is_secure,std::string * security_domain)671 bool MacKeychainPasswordFormAdapter::ExtractSignonRealmComponents(
672 const std::string& signon_realm, std::string* server, int* port,
673 bool* is_secure, std::string* security_domain) {
674 // The signon_realm will be the Origin portion of a URL for an HTML form,
675 // and the same but with the security domain as a path for HTTP auth.
676 GURL realm_as_url(signon_realm);
677 if (!realm_as_url.is_valid()) {
678 return false;
679 }
680
681 if (server)
682 *server = realm_as_url.host();
683 if (is_secure)
684 *is_secure = realm_as_url.SchemeIsSecure();
685 if (port)
686 *port = realm_as_url.has_port() ? atoi(realm_as_url.port().c_str()) : 0;
687 if (security_domain) {
688 // Strip the leading '/' off of the path to get the security domain.
689 *security_domain = realm_as_url.path().substr(1);
690 }
691 return true;
692 }
693
694 // Returns the Keychain SecAuthenticationType type corresponding to |scheme|.
AuthTypeForScheme(PasswordForm::Scheme scheme)695 SecAuthenticationType MacKeychainPasswordFormAdapter::AuthTypeForScheme(
696 PasswordForm::Scheme scheme) {
697 switch (scheme) {
698 case PasswordForm::SCHEME_HTML: return kSecAuthenticationTypeHTMLForm;
699 case PasswordForm::SCHEME_BASIC: return kSecAuthenticationTypeHTTPBasic;
700 case PasswordForm::SCHEME_DIGEST: return kSecAuthenticationTypeHTTPDigest;
701 case PasswordForm::SCHEME_OTHER: return kSecAuthenticationTypeDefault;
702 }
703 NOTREACHED();
704 return kSecAuthenticationTypeDefault;
705 }
706
SetKeychainItemPassword(const SecKeychainItemRef & keychain_item,const std::string & password)707 bool MacKeychainPasswordFormAdapter::SetKeychainItemPassword(
708 const SecKeychainItemRef& keychain_item, const std::string& password) {
709 OSStatus result = keychain_->ItemModifyAttributesAndData(keychain_item, NULL,
710 password.size(),
711 password.c_str());
712 return result == noErr;
713 }
714
SetKeychainItemCreatorCode(const SecKeychainItemRef & keychain_item,OSType creator_code)715 bool MacKeychainPasswordFormAdapter::SetKeychainItemCreatorCode(
716 const SecKeychainItemRef& keychain_item, OSType creator_code) {
717 SecKeychainAttribute attr = { kSecCreatorItemAttr, sizeof(creator_code),
718 &creator_code };
719 SecKeychainAttributeList attrList = { 1, &attr };
720 OSStatus result = keychain_->ItemModifyAttributesAndData(keychain_item,
721 &attrList, 0, NULL);
722 return result == noErr;
723 }
724
CreatorCodeForSearch()725 OSType MacKeychainPasswordFormAdapter::CreatorCodeForSearch() {
726 return finds_only_owned_ ? base::mac::CreatorCodeForApplication() : 0;
727 }
728
729 #pragma mark -
730
PasswordStoreMac(MacKeychain * keychain,LoginDatabase * login_db)731 PasswordStoreMac::PasswordStoreMac(MacKeychain* keychain,
732 LoginDatabase* login_db)
733 : keychain_(keychain), login_metadata_db_(login_db) {
734 DCHECK(keychain_.get());
735 DCHECK(login_metadata_db_.get());
736 }
737
~PasswordStoreMac()738 PasswordStoreMac::~PasswordStoreMac() {
739 if (thread_.get()) {
740 thread_->message_loop()->DeleteSoon(FROM_HERE,
741 notification_service_.release());
742 }
743 }
744
Init()745 bool PasswordStoreMac::Init() {
746 thread_.reset(new base::Thread("Chrome_PasswordStore_Thread"));
747
748 if (!thread_->Start()) {
749 thread_.reset(NULL);
750 return false;
751 }
752 ScheduleTask(NewRunnableMethod(this,
753 &PasswordStoreMac::CreateNotificationService));
754 return PasswordStore::Init();
755 }
756
ScheduleTask(Task * task)757 void PasswordStoreMac::ScheduleTask(Task* task) {
758 if (thread_.get()) {
759 thread_->message_loop()->PostTask(FROM_HERE, task);
760 }
761 }
762
ReportMetricsImpl()763 void PasswordStoreMac::ReportMetricsImpl() {
764 login_metadata_db_->ReportMetrics();
765 }
766
AddLoginImpl(const PasswordForm & form)767 void PasswordStoreMac::AddLoginImpl(const PasswordForm& form) {
768 if (AddToKeychainIfNecessary(form)) {
769 if (login_metadata_db_->AddLogin(form)) {
770 PasswordStoreChangeList changes;
771 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
772 NotificationService::current()->Notify(
773 NotificationType::LOGINS_CHANGED,
774 Source<PasswordStore>(this),
775 Details<PasswordStoreChangeList>(&changes));
776 }
777 }
778 }
779
UpdateLoginImpl(const PasswordForm & form)780 void PasswordStoreMac::UpdateLoginImpl(const PasswordForm& form) {
781 int update_count = 0;
782 if (!login_metadata_db_->UpdateLogin(form, &update_count))
783 return;
784
785 MacKeychainPasswordFormAdapter keychain_adapter(keychain_.get());
786 if (update_count == 0 &&
787 !keychain_adapter.HasPasswordsMergeableWithForm(form)) {
788 // If the password isn't in either the DB or the keychain, then it must have
789 // been deleted after autofill happened, and should not be re-added.
790 return;
791 }
792
793 // The keychain add will update if there is a collision and add if there
794 // isn't, which is the behavior we want, so there's no separate update call.
795 if (AddToKeychainIfNecessary(form)) {
796 PasswordStoreChangeList changes;
797 if (update_count == 0) {
798 if (login_metadata_db_->AddLogin(form)) {
799 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD,
800 form));
801 }
802 } else {
803 changes.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE,
804 form));
805 }
806 if (!changes.empty()) {
807 NotificationService::current()->Notify(
808 NotificationType::LOGINS_CHANGED,
809 Source<PasswordStore>(this),
810 Details<PasswordStoreChangeList>(&changes));
811 }
812 }
813 }
814
RemoveLoginImpl(const PasswordForm & form)815 void PasswordStoreMac::RemoveLoginImpl(const PasswordForm& form) {
816 if (login_metadata_db_->RemoveLogin(form)) {
817 // See if we own a Keychain item associated with this item. We can do an
818 // exact search rather than messing around with trying to do fuzzy matching
819 // because passwords that we created will always have an exact-match
820 // database entry.
821 // (If a user does lose their profile but not their keychain we'll treat the
822 // entries we find like other imported entries anyway, so it's reasonable to
823 // handle deletes on them the way we would for an imported item.)
824 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_.get());
825 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
826 PasswordForm* owned_password_form =
827 owned_keychain_adapter.PasswordExactlyMatchingForm(form);
828 if (owned_password_form) {
829 // If we don't have other forms using it (i.e., a form differing only by
830 // the names of the form elements), delete the keychain entry.
831 if (!DatabaseHasFormMatchingKeychainForm(form)) {
832 owned_keychain_adapter.RemovePassword(form);
833 }
834 }
835
836 PasswordStoreChangeList changes;
837 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form));
838 NotificationService::current()->Notify(
839 NotificationType::LOGINS_CHANGED,
840 Source<PasswordStore>(this),
841 Details<PasswordStoreChangeList>(&changes));
842 }
843 }
844
RemoveLoginsCreatedBetweenImpl(const base::Time & delete_begin,const base::Time & delete_end)845 void PasswordStoreMac::RemoveLoginsCreatedBetweenImpl(
846 const base::Time& delete_begin, const base::Time& delete_end) {
847 std::vector<PasswordForm*> forms;
848 if (login_metadata_db_->GetLoginsCreatedBetween(delete_begin, delete_end,
849 &forms)) {
850 if (login_metadata_db_->RemoveLoginsCreatedBetween(delete_begin,
851 delete_end)) {
852 // We can't delete from the Keychain by date because we may be sharing
853 // items with database entries that weren't in the delete range. Instead,
854 // we find all the Keychain items we own but aren't using any more and
855 // delete those.
856 std::vector<PasswordForm*> orphan_keychain_forms =
857 GetUnusedKeychainForms();
858 // This is inefficient, since we have to re-look-up each keychain item
859 // one at a time to delete it even though the search step already had a
860 // list of Keychain item references. If this turns out to be noticeably
861 // slow we'll need to rearchitect to allow the search and deletion steps
862 // to share.
863 RemoveKeychainForms(orphan_keychain_forms);
864 STLDeleteElements(&orphan_keychain_forms);
865
866 PasswordStoreChangeList changes;
867 for (std::vector<PasswordForm*>::const_iterator it = forms.begin();
868 it != forms.end(); ++it) {
869 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE,
870 **it));
871 }
872 NotificationService::current()->Notify(
873 NotificationType::LOGINS_CHANGED,
874 Source<PasswordStore>(this),
875 Details<PasswordStoreChangeList>(&changes));
876 }
877 }
878 }
879
GetLoginsImpl(GetLoginsRequest * request,const webkit_glue::PasswordForm & form)880 void PasswordStoreMac::GetLoginsImpl(GetLoginsRequest* request,
881 const webkit_glue::PasswordForm& form) {
882 MacKeychainPasswordFormAdapter keychain_adapter(keychain_.get());
883 std::vector<PasswordForm*> keychain_forms =
884 keychain_adapter.PasswordsFillingForm(form);
885
886 std::vector<PasswordForm*> database_forms;
887 login_metadata_db_->GetLogins(form, &database_forms);
888
889 std::vector<PasswordForm*>& merged_forms = request->value;
890 internal_keychain_helpers::MergePasswordForms(&keychain_forms,
891 &database_forms,
892 &merged_forms);
893
894 // Strip any blacklist entries out of the unused Keychain array, then take
895 // all the entries that are left (which we can use as imported passwords).
896 std::vector<PasswordForm*> keychain_blacklist_forms =
897 internal_keychain_helpers::ExtractBlacklistForms(&keychain_forms);
898 merged_forms.insert(merged_forms.end(), keychain_forms.begin(),
899 keychain_forms.end());
900 keychain_forms.clear();
901 STLDeleteElements(&keychain_blacklist_forms);
902
903 // Clean up any orphaned database entries.
904 RemoveDatabaseForms(database_forms);
905 STLDeleteElements(&database_forms);
906
907 ForwardLoginsResult(request);
908 }
909
GetBlacklistLoginsImpl(GetLoginsRequest * request)910 void PasswordStoreMac::GetBlacklistLoginsImpl(GetLoginsRequest* request) {
911 FillBlacklistLogins(&request->value);
912 ForwardLoginsResult(request);
913 }
914
GetAutofillableLoginsImpl(GetLoginsRequest * request)915 void PasswordStoreMac::GetAutofillableLoginsImpl(GetLoginsRequest* request) {
916 FillAutofillableLogins(&request->value);
917 ForwardLoginsResult(request);
918 }
919
FillAutofillableLogins(std::vector<PasswordForm * > * forms)920 bool PasswordStoreMac::FillAutofillableLogins(
921 std::vector<PasswordForm*>* forms) {
922 DCHECK(thread_->message_loop() == MessageLoop::current());
923
924 std::vector<PasswordForm*> database_forms;
925 login_metadata_db_->GetAutofillableLogins(&database_forms);
926
927 std::vector<PasswordForm*> merged_forms =
928 internal_keychain_helpers::GetPasswordsForForms(*keychain_,
929 &database_forms);
930
931 // Clean up any orphaned database entries.
932 RemoveDatabaseForms(database_forms);
933 STLDeleteElements(&database_forms);
934
935 forms->insert(forms->end(), merged_forms.begin(), merged_forms.end());
936 return true;
937 }
938
FillBlacklistLogins(std::vector<PasswordForm * > * forms)939 bool PasswordStoreMac::FillBlacklistLogins(
940 std::vector<PasswordForm*>* forms) {
941 DCHECK(thread_->message_loop() == MessageLoop::current());
942 return login_metadata_db_->GetBlacklistLogins(forms);
943 }
944
AddToKeychainIfNecessary(const PasswordForm & form)945 bool PasswordStoreMac::AddToKeychainIfNecessary(const PasswordForm& form) {
946 if (form.blacklisted_by_user) {
947 return true;
948 }
949 MacKeychainPasswordFormAdapter keychainAdapter(keychain_.get());
950 return keychainAdapter.AddPassword(form);
951 }
952
DatabaseHasFormMatchingKeychainForm(const webkit_glue::PasswordForm & form)953 bool PasswordStoreMac::DatabaseHasFormMatchingKeychainForm(
954 const webkit_glue::PasswordForm& form) {
955 bool has_match = false;
956 std::vector<PasswordForm*> database_forms;
957 login_metadata_db_->GetLogins(form, &database_forms);
958 for (std::vector<PasswordForm*>::iterator i = database_forms.begin();
959 i != database_forms.end(); ++i) {
960 if (internal_keychain_helpers::FormsMatchForMerge(form, **i) &&
961 (*i)->origin == form.origin) {
962 has_match = true;
963 break;
964 }
965 }
966 STLDeleteElements(&database_forms);
967 return has_match;
968 }
969
GetUnusedKeychainForms()970 std::vector<PasswordForm*> PasswordStoreMac::GetUnusedKeychainForms() {
971 std::vector<PasswordForm*> database_forms;
972 login_metadata_db_->GetAutofillableLogins(&database_forms);
973
974 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_.get());
975 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
976 std::vector<PasswordForm*> owned_keychain_forms =
977 owned_keychain_adapter.GetAllPasswordFormPasswords();
978
979 // Run a merge; anything left in owned_keychain_forms when we are done no
980 // longer has a matching database entry.
981 std::vector<PasswordForm*> merged_forms;
982 internal_keychain_helpers::MergePasswordForms(&owned_keychain_forms,
983 &database_forms,
984 &merged_forms);
985 STLDeleteElements(&merged_forms);
986 STLDeleteElements(&database_forms);
987
988 return owned_keychain_forms;
989 }
990
RemoveDatabaseForms(const std::vector<PasswordForm * > & forms)991 void PasswordStoreMac::RemoveDatabaseForms(
992 const std::vector<PasswordForm*>& forms) {
993 for (std::vector<PasswordForm*>::const_iterator i = forms.begin();
994 i != forms.end(); ++i) {
995 login_metadata_db_->RemoveLogin(**i);
996 }
997 }
998
RemoveKeychainForms(const std::vector<PasswordForm * > & forms)999 void PasswordStoreMac::RemoveKeychainForms(
1000 const std::vector<PasswordForm*>& forms) {
1001 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_.get());
1002 owned_keychain_adapter.SetFindsOnlyOwnedItems(true);
1003 for (std::vector<PasswordForm*>::const_iterator i = forms.begin();
1004 i != forms.end(); ++i) {
1005 owned_keychain_adapter.RemovePassword(**i);
1006 }
1007 }
1008
CreateNotificationService()1009 void PasswordStoreMac::CreateNotificationService() {
1010 notification_service_.reset(new NotificationService);
1011 }
1012