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. |