blob: 18cd4b307e53f0e4c49f1d7edcd8b7c0f90c898e [file] [log] [blame]
// Copyright 2013 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/signin/local_auth.h"
#include <memory>
#include "base/base64.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
#include "components/os_crypt/os_crypt.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "crypto/random.h"
#include "crypto/secure_util.h"
#include "crypto/symmetric_key.h"
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, 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,
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).
std::unique_ptr<crypto::SymmetricKey> password_key(
crypto::SymmetricKey::DeriveKeyFromPasswordUsingPbkdf2(
crypto::SymmetricKey::AES, password, salt, encoding.iteration_count,
encoding.hash_bits));
std::string password_hash = password_key->key();
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,
const HashEncoding& encoding) {
// Encrypt the hash using the OS account-password protection (if available).
std::string encoded;
const bool success = OSCrypt::EncryptString(record, &encoded);
DCHECK(success);
// Convert binary record to text for preference database.
std::string encoded64;
base::Base64Encode(encoded, &encoded64);
// Stuff the "encoding" value into the first byte.
encoded64.insert(0, &encoding.version, sizeof(encoding.version));
return encoded64;
}
bool DecodePasswordHashRecord(const std::string& encoded,
std::string* decoded,
char* encoding) {
// Extract the "encoding" value from the first byte and validate.
if (encoded.length() < 1)
return false;
*encoding = encoded[0];
if (!GetEncodingForVersion(*encoding))
return false;
// Stored record is base64; convert to binary.
std::string unbase64;
if (!base::Base64Decode(encoded.substr(1), &unbase64))
return false;
// Decrypt the record using the OS account-password protection (if available).
return OSCrypt::DecryptString(unbase64, decoded);
}
} // namespace
std::string LocalAuth::TruncateStringByBits(const std::string& str,
const size_t len_bits) {
return ::TruncateStringByBits(str, len_bits);
}
void LocalAuth::SetLocalAuthCredentialsWithEncoding(
ProfileAttributesEntry* entry,
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(base::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, encoding);
// Group all fields into a single record for storage;
std::string record;
record.append(salt_str);
record.append(password_hash);
// Encode it and store it.
std::string encoded = EncodePasswordHashRecord(record, encoding);
entry->SetLocalAuthCredentials(encoded);
}
void LocalAuth::SetLocalAuthCredentials(ProfileAttributesEntry* entry,
const std::string& password) {
DCHECK(entry);
DCHECK(password.length());
SetLocalAuthCredentialsWithEncoding(
entry, password, '0' + NUM_HASH_ENCODINGS);
}
void LocalAuth::SetLocalAuthCredentials(const Profile* profile,
const std::string& password) {
DCHECK(g_browser_process->profile_manager()->IsValidProfile(profile));
ProfileAttributesEntry* entry = nullptr;
bool has_entry = g_browser_process->profile_manager()->
GetProfileAttributesStorage().
GetProfileAttributesWithPath(profile->GetPath(), &entry);
DCHECK(has_entry);
SetLocalAuthCredentials(entry, password);
}
bool LocalAuth::ValidateLocalAuthCredentials(ProfileAttributesEntry* entry,
const std::string& password) {
DCHECK(entry);
std::string record;
char encoding;
std::string encodedhash = entry->GetLocalAuthCredentials();
if (encodedhash.length() == 0 && password.length() == 0)
return true;
if (!DecodePasswordHashRecord(encodedhash, &record, &encoding))
return false;
std::string password_hash;
const char* password_saved;
const char* password_check;
size_t password_length;
const HashEncoding* hash_encoding = GetEncodingForVersion(encoding);
if (!hash_encoding) {
// Unknown encoding.
return false;
}
// 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(entry, password);
return passwords_match;
}
bool LocalAuth::ValidateLocalAuthCredentials(const Profile* profile,
const std::string& password) {
DCHECK(g_browser_process->profile_manager()->IsValidProfile(profile));
ProfileAttributesEntry* entry;
if (!g_browser_process->profile_manager()->GetProfileAttributesStorage().
GetProfileAttributesWithPath(profile->GetPath(), &entry)) {
NOTREACHED();
return false;
}
return ValidateLocalAuthCredentials(entry, password);
}