Chromium Code Reviews| Index: chrome/browser/mac/keychain_reauthorize.mm | 
| diff --git a/chrome/browser/mac/keychain_reauthorize.mm b/chrome/browser/mac/keychain_reauthorize.mm | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..4bf5bca20d71eaf18901d01cfc5ac034d57dca39 | 
| --- /dev/null | 
| +++ b/chrome/browser/mac/keychain_reauthorize.mm | 
| @@ -0,0 +1,118 @@ | 
| +// Copyright 2017 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "chrome/browser/mac/keychain_reauthorize.h" | 
| + | 
| +#import <Foundation/Foundation.h> | 
| +#include <Security/Security.h> | 
| + | 
| +#include <string.h> | 
| + | 
| +#include "base/logging.h" | 
| +#include "base/mac/foundation_util.h" | 
| +#include "base/mac/scoped_cftyperef.h" | 
| +#include "base/metrics/histogram.h" | 
| +#include "base/metrics/histogram_macros.h" | 
| +#include "base/scoped_generic.h" | 
| +#include "components/os_crypt/keychain_password_mac.h" | 
| +#include "crypto/apple_keychain.h" | 
| + | 
| +namespace chrome { | 
| + | 
| +namespace { | 
| + | 
| +// Reauthorizes the Safe Storage keychain item, which protects the randomly | 
| +// generated password that encrypts the user's saved passwords. This reads out | 
| +// the keychain item, deletes it, and re-adds it to the keychain. This works | 
| +// because the keychain uses an apps designated requirement as the default ACL | 
| 
 
Mark Mentovai
2017/03/01 22:00:30
“default ACL for”→“ACL for reading”
 
Mark Mentovai
2017/03/01 22:00:31
app’s
 
Greg K
2017/03/02 01:28:27
Done.
 
Greg K
2017/03/02 01:28:27
Done.
 
 | 
| +// for an item. We are signing Chrome with the current certificate (so it can | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
Don’t say “we.” Also, the bit about signing will b
 
Greg K
2017/03/02 01:28:27
Done.
 
 | 
| +// access the item), but using a designated requirement that accepts the current | 
| +// and new certificates as valid. | 
| +bool KeychainReauthorize() { | 
| + base::ScopedCFTypeRef<SecKeychainItemRef> storage_item; | 
| + UInt32 pw_length = 0; | 
| + void* password_data = nullptr; | 
| + | 
| + crypto::AppleKeychain keychain; | 
| + OSStatus error = keychain.FindGenericPassword( | 
| + NULL, strlen(KeychainPassword::service_name), | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
Use nullptr now in new code. Hooray for C++11!
 
Greg K
2017/03/02 01:28:27
Done.
 
 | 
| + KeychainPassword::service_name, strlen(KeychainPassword::account_name), | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
If git cl format formatted this, then it’s fine be
 
Greg K
2017/03/02 01:28:27
Done.
 
 | 
| + KeychainPassword::account_name, &pw_length, &password_data, | 
| + storage_item.InitializeInto()); | 
| + | 
| + std::string password = | 
| 
 
Mark Mentovai
2017/03/01 22:00:30
One of the sanitizers may not like this if !passwo
 
Greg K
2017/03/02 01:28:27
Done.
 
 | 
| + std::string(static_cast<char*>(password_data), pw_length); | 
| + if (password_data) { | 
| + memset(password_data, 0xAA, pw_length); | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
This is good. memset_s() would be even better. But
 
Greg K
2017/03/02 01:28:27
memset_s requires some LIBC extension, but let's d
 
 | 
| + keychain.ItemFreeContent(nullptr, password_data); | 
| + } | 
| + | 
| + if (error != noErr) | 
| + return false; | 
| 
 
Mark Mentovai
2017/03/01 22:00:30
Wanna say something about this?
 
Greg K
2017/03/02 01:28:27
Done.
 
 | 
| + | 
| + if (keychain.ItemDelete(storage_item) != noErr) { | 
| + LOG(ERROR) << "KeychainReauthorize failed. Cannot delete item."; | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
Aha! Whenever you’re logging something about an OS
 
 | 
| + return false; | 
| + } | 
| + | 
| + error = keychain.AddGenericPassword( | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
I’m pretty sure that the answer is no, but we can’
 
Greg K
2017/03/02 01:28:27
Definitely good to test, but it failed.
 
 | 
| + NULL, strlen(KeychainPassword::service_name), | 
| + KeychainPassword::service_name, strlen(KeychainPassword::account_name), | 
| + KeychainPassword::account_name, password.size(), password.data(), NULL); | 
| + | 
| + if (error != noErr) { | 
| + LOG(ERROR) << "Failed to re-add storage password."; | 
| + return false; | 
| + } | 
| + return true; | 
| +} | 
| + | 
| +} // namespace | 
| + | 
| +void KeychainReauthorizeIfNeeded(NSString* pref_key, int max_tries) { | 
| + NSUserDefaults* user_defaults = [NSUserDefaults standardUserDefaults]; | 
| + [user_defaults synchronize]; | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
I don’t believe that this one will do anything, be
 
Greg K
2017/03/02 01:28:27
Done.
 
 | 
| + int pref_value = [user_defaults integerForKey:pref_key]; | 
| + | 
| + if (pref_value >= max_tries) | 
| + return; | 
| + | 
| + BOOL success_value = | 
| + [user_defaults boolForKey:[pref_key stringByAppendingString:@"Success"]]; | 
| + if (success_value) | 
| + return; | 
| + | 
| + if (pref_value > 0) { | 
| + // Logs the number of previous tries that didn't complete. | 
| + if (base::mac::AmIBundled()) { | 
| + UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeeded", pref_value); | 
| + } else { | 
| + UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededAtUpdate", | 
| + pref_value); | 
| + } | 
| + } | 
| + | 
| + bool success = KeychainReauthorize(); | 
| + | 
| + ++pref_value; | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
The comment in the header was explicit that this w
 
Greg K
2017/03/02 01:28:27
That makes sense, I didn't think about that.
 
 | 
| + [user_defaults setInteger:pref_value forKey:pref_key]; | 
| + [user_defaults synchronize]; | 
| + | 
| + if (!success) | 
| + return; | 
| + | 
| + NSString* success_pref_key = [pref_key stringByAppendingString:@"Success"]; | 
| 
 
Mark Mentovai
2017/03/01 22:00:31
You already computed this above, on line 82. You s
 
Greg K
2017/03/02 01:28:27
Done.
 
 | 
| + [user_defaults setBool:YES forKey:success_pref_key]; | 
| + [user_defaults synchronize]; | 
| + | 
| + // Logs the try number (1, 2) that succeeded. | 
| + if (base::mac::AmIBundled()) { | 
| + UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededSuccess", pref_value); | 
| + } else { | 
| + UMA_HISTOGRAM_COUNTS("OSX.KeychainReauthorizeIfNeededAtUpdateSuccess", | 
| + pref_value); | 
| + } | 
| +} | 
| + | 
| +} // namespace chrome |