| Index: chrome/browser/signin/local_auth.cc
|
| diff --git a/chrome/browser/signin/local_auth.cc b/chrome/browser/signin/local_auth.cc
|
| index 7748898f8e7a2c08decd9f033792208cb5f62c9c..9b20c3228769ff2066b178e335e551d31a43061e 100644
|
| --- a/chrome/browser/signin/local_auth.cc
|
| +++ b/chrome/browser/signin/local_auth.cc
|
| @@ -22,21 +22,81 @@
|
|
|
| namespace {
|
|
|
| +struct HashEncoding {
|
| + char version;
|
| + unsigned hash_bits;
|
| + unsigned hash_bytes;
|
| + unsigned iteration_count;
|
| + unsigned stored_bits;
|
| + unsigned stored_bytes;
|
| +
|
| + public:
|
| + HashEncoding(char version,
|
| + unsigned hash_bits,
|
| + unsigned hash_bytes,
|
| + unsigned iteration_count,
|
| + unsigned stored_bits,
|
| + unsigned stored_bytes) :
|
| + version(version),
|
| + hash_bits(hash_bits),
|
| + hash_bytes(hash_bytes),
|
| + iteration_count(iteration_count),
|
| + stored_bits(stored_bits),
|
| + stored_bytes(stored_bytes) {}
|
| +};
|
| +
|
| // WARNING: Changing these values will make it impossible to do off-line
|
| // authentication until the next successful on-line authentication. To change
|
| -// these safely, change the "encoding" version below and make verification
|
| -// handle multiple values.
|
| -const char kHash1Encoding = '1';
|
| +// these safely, add a new HashEncoding object below and increment
|
| +// NUM_HASH_ENCODINGS.
|
| +const char kHash1Version = '1';
|
| const unsigned kHash1Bits = 256;
|
| const unsigned kHash1Bytes = kHash1Bits / 8;
|
| const unsigned kHash1IterationCount = 100000;
|
|
|
| +// Store 13 bits to provide pin-like security (8192 possible values), without
|
| +// providing a complete oracle for the user's GAIA password.
|
| +const char kHash2Version = '2';
|
| +const unsigned kHash2Bits = 256;
|
| +const unsigned kHash2Bytes = kHash2Bits / 8;
|
| +const unsigned kHash2IterationCount = 100000;
|
| +const unsigned kHash2StoredBits = 13;
|
| +const unsigned kHash2StoredBytes = (kHash2StoredBits + 7) / 8;
|
| +
|
| +const int NUM_HASH_ENCODINGS = 2;
|
| +HashEncoding encodings[NUM_HASH_ENCODINGS] = {
|
| + HashEncoding(
|
| + kHash1Version, kHash1Bits, kHash1Bytes, kHash1IterationCount, 0, 0),
|
| + HashEncoding(
|
| + kHash2Version, kHash2Bits, kHash2Bytes, kHash2IterationCount,
|
| + kHash2StoredBits, kHash2StoredBytes)
|
| +};
|
| +
|
| +const HashEncoding* GetEncodingForVersion(char version) {
|
| + // Note that versions are 1-indexed.
|
| + DCHECK(version > '0' && version <= ('0' + NUM_HASH_ENCODINGS));
|
| + return &encodings[(version - '0') - 1];
|
| +}
|
| +
|
| +std::string TruncateStringByBits(const std::string& str,
|
| + const size_t len_bits) {
|
| + if (len_bits % 8 == 0)
|
| + return str.substr(0, len_bits / 8);
|
| +
|
| + // The initial truncation copies whole bytes
|
| + int number_bytes = (len_bits + 7) / 8;
|
| + std::string truncated_string = str.substr(0, number_bytes);
|
| +
|
| + // Keep the prescribed number of bits from the last byte.
|
| + unsigned last_char_bitmask = (1 << (len_bits % 8)) - 1;
|
| + truncated_string[number_bytes - 1] &= last_char_bitmask;
|
| + return truncated_string;
|
| +}
|
| +
|
| std::string CreateSecurePasswordHash(const std::string& salt,
|
| const std::string& password,
|
| - char encoding) {
|
| - DCHECK_EQ(kHash1Bytes, salt.length());
|
| - DCHECK_EQ(kHash1Encoding, encoding); // Currently support only one method.
|
| -
|
| + const HashEncoding& encoding) {
|
| + DCHECK_EQ(encoding.hash_bytes, salt.length());
|
| base::Time start_time = base::Time::Now();
|
|
|
| // Library call to create secure password hash as SymmetricKey (uses PBKDF2).
|
| @@ -44,22 +104,26 @@ std::string CreateSecurePasswordHash(const std::string& salt,
|
| crypto::SymmetricKey::DeriveKeyFromPassword(
|
| crypto::SymmetricKey::AES,
|
| password, salt,
|
| - kHash1IterationCount, kHash1Bits));
|
| + encoding.iteration_count, encoding.hash_bits));
|
| std::string password_hash;
|
| const bool success = password_key->GetRawKey(&password_hash);
|
| DCHECK(success);
|
| - DCHECK_EQ(kHash1Bytes, password_hash.length());
|
| + DCHECK_EQ(encoding.hash_bytes, password_hash.length());
|
|
|
| UMA_HISTOGRAM_TIMES("PasswordHash.CreateTime",
|
| base::Time::Now() - start_time);
|
|
|
| + if (encoding.stored_bits) {
|
| + password_hash = TruncateStringByBits(password_hash, encoding.stored_bits);
|
| + DCHECK_EQ(encoding.stored_bytes, password_hash.length());
|
| + }
|
| + DCHECK_EQ(encoding.stored_bytes ? encoding.stored_bytes : encoding.hash_bytes,
|
| + password_hash.length());
|
| return password_hash;
|
| }
|
|
|
| std::string EncodePasswordHashRecord(const std::string& record,
|
| - char encoding) {
|
| - DCHECK_EQ(kHash1Encoding, encoding); // Currently support only one method.
|
| -
|
| + const HashEncoding& encoding) {
|
| // Encrypt the hash using the OS account-password protection (if available).
|
| std::string encoded;
|
| const bool success = OSCrypt::EncryptString(record, &encoded);
|
| @@ -70,7 +134,7 @@ std::string EncodePasswordHashRecord(const std::string& record,
|
| base::Base64Encode(encoded, &encoded64);
|
|
|
| // Stuff the "encoding" value into the first byte.
|
| - encoded64.insert(0, &encoding, sizeof(encoding));
|
| + encoded64.insert(0, &encoding.version, sizeof(encoding.version));
|
|
|
| return encoded64;
|
| }
|
| @@ -82,7 +146,7 @@ bool DecodePasswordHashRecord(const std::string& encoded,
|
| if (encoded.length() < 1)
|
| return false;
|
| *encoding = encoded[0];
|
| - if (*encoding != kHash1Encoding)
|
| + if (!GetEncodingForVersion(*encoding))
|
| return false;
|
|
|
| // Stored record is base64; convert to binary.
|
| @@ -104,33 +168,33 @@ size_t GetProfileInfoIndexOfProfile(const Profile* profile) {
|
|
|
| } // namespace
|
|
|
| -namespace chrome {
|
| +std::string LocalAuth::TruncateStringByBits(const std::string& str,
|
| + const size_t len_bits) {
|
| + return ::TruncateStringByBits(str, len_bits);
|
| +}
|
|
|
| -void RegisterLocalAuthPrefs(user_prefs::PrefRegistrySyncable* registry) {
|
| +void LocalAuth::RegisterLocalAuthPrefs(
|
| + user_prefs::PrefRegistrySyncable* registry) {
|
| registry->RegisterStringPref(
|
| prefs::kGoogleServicesPasswordHash,
|
| std::string(),
|
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
|
| }
|
|
|
| -void SetLocalAuthCredentials(size_t info_index,
|
| - const std::string& password) {
|
| - if (info_index == std::string::npos) {
|
| - NOTREACHED();
|
| - return;
|
| - }
|
| - DCHECK(password.length());
|
| +void LocalAuth::SetLocalAuthCredentialsWithEncoding(size_t info_index,
|
| + const std::string& password,
|
| + char encoding_version) {
|
| + const HashEncoding& encoding = encodings[(encoding_version - '0') - 1];
|
|
|
| // Salt should be random data, as long as the hash length, and different with
|
| // every save.
|
| std::string salt_str;
|
| - crypto::RandBytes(WriteInto(&salt_str, kHash1Bytes + 1), kHash1Bytes);
|
| - DCHECK_EQ(kHash1Bytes, salt_str.length());
|
| + crypto::RandBytes(WriteInto(&salt_str, encoding.hash_bytes + 1),
|
| + encoding.hash_bytes);
|
|
|
| // Perform secure hash of password for storage.
|
| std::string password_hash = CreateSecurePasswordHash(
|
| - salt_str, password, kHash1Encoding);
|
| - DCHECK_EQ(kHash1Bytes, password_hash.length());
|
| + salt_str, password, encoding);
|
|
|
| // Group all fields into a single record for storage;
|
| std::string record;
|
| @@ -138,19 +202,30 @@ void SetLocalAuthCredentials(size_t info_index,
|
| record.append(password_hash);
|
|
|
| // Encode it and store it.
|
| - std::string encoded = EncodePasswordHashRecord(record, kHash1Encoding);
|
| + std::string encoded = EncodePasswordHashRecord(record, encoding);
|
| ProfileInfoCache& info =
|
| g_browser_process->profile_manager()->GetProfileInfoCache();
|
| info.SetLocalAuthCredentialsOfProfileAtIndex(info_index, encoded);
|
| }
|
|
|
| -void SetLocalAuthCredentials(const Profile* profile,
|
| - const std::string& password) {
|
| +void LocalAuth::SetLocalAuthCredentials(size_t info_index,
|
| + const std::string& password) {
|
| + if (info_index == std::string::npos) {
|
| + NOTREACHED();
|
| + return;
|
| + }
|
| + DCHECK(password.length());
|
| + SetLocalAuthCredentialsWithEncoding(
|
| + info_index, password, '0' + NUM_HASH_ENCODINGS);
|
| +}
|
| +
|
| +void LocalAuth::SetLocalAuthCredentials(const Profile* profile,
|
| + const std::string& password) {
|
| SetLocalAuthCredentials(GetProfileInfoIndexOfProfile(profile), password);
|
| }
|
|
|
| -bool ValidateLocalAuthCredentials(size_t info_index,
|
| - const std::string& password) {
|
| +bool LocalAuth::ValidateLocalAuthCredentials(size_t info_index,
|
| + const std::string& password) {
|
| if (info_index == std::string::npos) {
|
| NOTREACHED();
|
| return false;
|
| @@ -174,28 +249,31 @@ bool ValidateLocalAuthCredentials(size_t info_index,
|
| const char* password_check;
|
| size_t password_length;
|
|
|
| - if (encoding == '1') {
|
| - // Validate correct length; extract salt and password hash.
|
| - if (record.length() != 2 * kHash1Bytes)
|
| - return false;
|
| - std::string salt_str(record.data(), kHash1Bytes);
|
| - password_saved = record.data() + kHash1Bytes;
|
| - password_hash = CreateSecurePasswordHash(salt_str, password, encoding);
|
| - password_check = password_hash.data();
|
| - password_length = kHash1Bytes;
|
| - } else {
|
| - // unknown encoding
|
| + const HashEncoding* hash_encoding = GetEncodingForVersion(encoding);
|
| + if (!hash_encoding) {
|
| + // Unknown encoding.
|
| return false;
|
| }
|
|
|
| - return crypto::SecureMemEqual(password_saved, password_check,
|
| - password_length);
|
| + // Extract salt.
|
| + std::string salt_str(record.data(), hash_encoding->hash_bytes);
|
| + // Extract password.
|
| + password_saved = record.data() + hash_encoding->hash_bytes;
|
| + password_hash = CreateSecurePasswordHash(salt_str, password, *hash_encoding);
|
| + password_length = hash_encoding->stored_bytes;
|
| + password_check = password_hash.data();
|
| +
|
| + bool passwords_match = crypto::SecureMemEqual(
|
| + password_saved, password_check, password_length);
|
| +
|
| + // Update the stored credentials to the latest encoding if necessary.
|
| + if (passwords_match && (hash_encoding->version - '0') != NUM_HASH_ENCODINGS)
|
| + SetLocalAuthCredentials(info_index, password);
|
| + return passwords_match;
|
| }
|
|
|
| -bool ValidateLocalAuthCredentials(const Profile* profile,
|
| - const std::string& password) {
|
| +bool LocalAuth::ValidateLocalAuthCredentials(const Profile* profile,
|
| + const std::string& password) {
|
| return ValidateLocalAuthCredentials(GetProfileInfoIndexOfProfile(profile),
|
| password);
|
| }
|
| -
|
| -} // namespace chrome
|
|
|