Chromium Code Reviews| Index: chrome/browser/password_manager/password_store_mac.cc |
| diff --git a/chrome/browser/password_manager/password_store_mac.cc b/chrome/browser/password_manager/password_store_mac.cc |
| index 13ceae02d4991cf0dd38976bee75bc2e15dc9fb7..6e165c2e21482087f93bdd69d5320613435350b3 100644 |
| --- a/chrome/browser/password_manager/password_store_mac.cc |
| +++ b/chrome/browser/password_manager/password_store_mac.cc |
| @@ -16,10 +16,12 @@ |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/mac_logging.h" |
| #include "base/message_loop/message_loop.h" |
| +#include "base/metrics/histogram_macros.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/mac/security_wrappers.h" |
| +#include "components/os_crypt/os_crypt.h" |
| #include "components/password_manager/core/browser/affiliation_utils.h" |
| #include "components/password_manager/core/browser/login_database.h" |
| #include "components/password_manager/core/browser/password_store_change.h" |
| @@ -435,6 +437,35 @@ bool FillPasswordFormFromKeychainItem(const AppleKeychain& keychain, |
| return true; |
| } |
| +bool HasChromeCreatorCode(const AppleKeychain& keychain, |
| + const SecKeychainItemRef& keychain_item) { |
| + SecKeychainAttributeInfo attrInfo; |
|
stuartmorgan
2015/06/26 21:26:08
attr_info
vasilii
2015/06/29 14:12:45
Done.
|
| + UInt32 tags[] = {kSecCreatorItemAttr}; |
| + attrInfo.count = arraysize(tags); |
| + attrInfo.tag = tags; |
| + attrInfo.format = NULL; |
|
stuartmorgan
2015/06/26 21:26:08
nullptr
vasilii
2015/06/29 14:12:45
Done.
|
| + |
| + SecKeychainAttributeList* attrList; |
|
stuartmorgan
2015/06/26 21:26:08
attr_list
vasilii
2015/06/29 14:12:44
Done.
|
| + UInt32 password_length; |
| + OSStatus result = keychain.ItemCopyAttributesAndData( |
| + keychain_item, &attrInfo, nullptr, &attrList, &password_length, nullptr); |
| + if (result != noErr) |
| + return false; |
| + OSType creator_code = 0; |
| + for (unsigned int i = 0; i < attrList->count; i++) { |
| + SecKeychainAttribute attr = attrList->attr[i]; |
| + if (!attr.data) { |
| + continue; |
| + } |
| + if (attr.tag == kSecCreatorItemAttr) { |
| + creator_code = *(static_cast<FourCharCode*>(attr.data)); |
| + break; |
| + } |
| + } |
| + keychain.ItemFreeAttributesAndData(attrList, nullptr); |
| + return creator_code && creator_code == base::mac::CreatorCodeForApplication(); |
| +} |
| + |
| bool FormsMatchForMerge(const PasswordForm& form_a, |
| const PasswordForm& form_b, |
| FormMatchStrictness strictness) { |
| @@ -922,7 +953,7 @@ PasswordStoreMac::PasswordStoreMac( |
| : password_manager::PasswordStore(main_thread_runner, db_thread_runner), |
| keychain_(keychain.Pass()), |
| login_metadata_db_(login_db) { |
| - DCHECK(keychain_.get()); |
| + DCHECK(keychain_); |
| login_metadata_db_->set_clear_password_values(true); |
| } |
| @@ -934,6 +965,87 @@ void PasswordStoreMac::InitWithTaskRunner( |
| db_thread_runner_ = background_task_runner; |
| } |
| +PasswordStoreMac::MigrationResult PasswordStoreMac::ImportFromKeychain() { |
| + DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread()); |
| + if (!login_metadata_db_) |
| + return LOGIN_DB_UNAVAILABLE; |
| + ScopedVector<PasswordForm> database_forms; |
| + if (!login_metadata_db_->GetAutofillableLogins(&database_forms)) |
| + return LOGIN_DB_FAILURE; |
| + ScopedVector<PasswordForm> uninteresting_forms; |
|
Ryan Sleevi
2015/06/26 16:44:35
nits (feel free to ignore) - I found the transitio
vasilii
2015/06/29 14:12:45
Done.
|
| + internal_keychain_helpers::ExtractNonKeychainForms(&database_forms, |
| + &uninteresting_forms); |
| + // If there are no passwords to lookup in the Keychain then we're done. |
| + if (database_forms.empty()) |
| + return MIGRATION_OK; |
| + |
| + // Make sure that the encryption key is retrieved from the Keychain so the |
| + // encryption can be done. |
| + DCHECK(!database_forms[0]->origin.is_empty()); |
| + std::string ciphertext; |
| + if (!OSCrypt::EncryptString(database_forms[0]->origin.spec(), &ciphertext)) |
|
stuartmorgan
2015/06/26 21:26:07
Why are you pulling a random field out for this, i
vasilii
2015/06/29 14:12:44
Done. Binary size?
|
| + return ENCRYPTOR_FAILURE; |
| + // Mute the Keychain. |
| + chrome::ScopedSecKeychainSetUserInteractionAllowed user_interaction_allowed( |
| + false); |
| + |
| + // Retrieve the passwords. |
| + // Get all the password attributes first. |
| + std::vector<SecKeychainItemRef> keychain_items; |
| + std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs = |
| + internal_keychain_helpers:: |
| + ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items, |
|
stuartmorgan
2015/06/26 21:26:08
Indent 4 more; it's a continuation
vasilii
2015/06/29 14:12:44
Done.
|
| + *keychain_); |
| + |
| + // Next, compare the attributes of the PasswordForms in |database_forms| |
| + // against those in |item_form_pairs|, and extract password data for each |
| + // matching PasswordForm using its corresponding SecKeychainItemRef. |
| + int unmerged_forms_count = 0; |
| + int chrome_owned_locked_forms_count = 0; |
|
Ryan Sleevi
2015/06/26 16:44:35
STYLE: (usually a SECURITY BUG, but here it just s
vasilii
2015/06/29 14:12:44
Done. Though implicit conversion to int in UMA_HIS
Ilya Sherman
2015/06/29 23:10:52
You could use a checked_cast (from safe_numerics.h
vasilii
2015/06/30 08:45:49
It contains CHECK(), and I don't want to have a cr
|
| + for (PasswordForm* form : database_forms) { |
| + ScopedVector<autofill::PasswordForm> keychain_matches = |
| + internal_keychain_helpers::ExtractPasswordsMergeableWithForm( |
|
stuartmorgan
2015/06/26 21:26:08
This seems wasteful (it may do extra Keychain look
vasilii
2015/06/29 14:12:44
I think it should return exactly one form (which i
|
| + *keychain_, item_form_pairs, *form); |
| + |
| + const PasswordForm* best_match = |
| + BestKeychainFormForForm(*form, keychain_matches.get()); |
| + if (best_match) { |
| + form->password_value = best_match->password_value; |
| + } else { |
| + unmerged_forms_count++; |
| + // Check if the corresponding keychain items are created by Chrome |
|
stuartmorgan
2015/06/26 21:26:07
s/the/any/ (since there may be none). Also, missin
vasilii
2015/06/29 14:12:45
Done.
|
| + for (const auto& item_form_pair : item_form_pairs) { |
| + if (internal_keychain_helpers::FormIsValidAndMatchesOtherForm( |
| + *form, *item_form_pair.second) && |
| + internal_keychain_helpers::HasChromeCreatorCode( |
| + *keychain_, *item_form_pair.first)) |
| + chrome_owned_locked_forms_count++; |
| + } |
| + } |
| + } |
| + STLDeleteContainerPairSecondPointers(item_form_pairs.begin(), |
| + item_form_pairs.end()); |
| + for (SecKeychainItemRef item : keychain_items) { |
| + keychain_->Free(item); |
| + } |
|
Ryan Sleevi
2015/06/26 16:44:35
nit: Since it looks like the previous comment wasn
vasilii
2015/06/29 14:12:44
Done.
|
| + if (unmerged_forms_count) { |
| + UMA_HISTOGRAM_COUNTS( |
| + "PasswordManager.KeychainMigration.NumPasswordsOnFailure", |
|
stuartmorgan
2015/06/26 21:26:08
Indent these lines 4, not 2.
vasilii
2015/06/29 14:12:44
Done.
|
| + database_forms.size()); |
| + UMA_HISTOGRAM_COUNTS("PasswordManager.KeychainMigration.NumFailedPasswords", |
| + unmerged_forms_count); |
| + UMA_HISTOGRAM_COUNTS( |
| + "PasswordManager.KeychainMigration.NumChromeOwnedFailedPasswords", |
| + chrome_owned_locked_forms_count); |
| + return KEYCHAIN_BLOCKED; |
| + } |
| + // Now all the passwords are available. It's time to update LoginDatabase. |
| + login_metadata_db_->set_clear_password_values(false); |
| + for (PasswordForm* form : database_forms) |
| + login_metadata_db_->UpdateLogin(*form); |
| + return MIGRATION_OK; |
| +} |
| + |
| bool PasswordStoreMac::Init( |
| const syncer::SyncableService::StartSyncFlare& flare) { |
| // The class should be used inside PasswordStoreProxyMac only. |