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