blob: 882ab5a6e50f7992396f4653b165636c9d187ba8 [file] [log] [blame]
// Copyright 2018 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 "components/password_manager/core/browser/password_hash_data.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "crypto/openssl_util.h"
#include "crypto/random.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
namespace password_manager {
namespace {
std::string CreateRandomSalt() {
constexpr size_t kSyncPasswordSaltLength = 16;
char buffer[kSyncPasswordSaltLength];
crypto::RandBytes(buffer, kSyncPasswordSaltLength);
// Explicit std::string constructor with a string length must be used in order
// to avoid treating '\0' symbols as a string ends.
std::string result(buffer, kSyncPasswordSaltLength);
return result;
}
} // namespace
PasswordHashData::PasswordHashData() = default;
PasswordHashData::PasswordHashData(const PasswordHashData& other) = default;
PasswordHashData::PasswordHashData(const std::string& username,
const base::string16& password,
bool force_update,
bool is_gaia_password)
: username(username),
length(password.size()),
salt(CreateRandomSalt()),
hash(CalculatePasswordHash(password, salt)),
force_update(force_update),
is_gaia_password(is_gaia_password) {}
bool PasswordHashData::MatchesPassword(const std::string& username,
const base::string16& password,
bool is_gaia_password) const {
if (password.size() != this->length ||
!AreUsernamesSame(username, is_gaia_password, this->username,
this->is_gaia_password)) {
return false;
}
return CalculatePasswordHash(password, this->salt) == this->hash;
}
uint64_t CalculatePasswordHash(const base::StringPiece16& text,
const std::string& salt) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
constexpr size_t kBytesFromHash = 8;
constexpr uint64_t kScryptCost = 32; // It must be a power of 2.
constexpr uint64_t kScryptBlockSize = 8;
constexpr uint64_t kScryptParallelization = 1;
constexpr size_t kScryptMaxMemory = 1024 * 1024;
uint8_t hash[kBytesFromHash];
base::StringPiece text_8bits(reinterpret_cast<const char*>(text.data()),
text.size() * 2);
const uint8_t* salt_ptr = reinterpret_cast<const uint8_t*>(salt.c_str());
int scrypt_ok = EVP_PBE_scrypt(text_8bits.data(), text_8bits.size(), salt_ptr,
salt.size(), kScryptCost, kScryptBlockSize,
kScryptParallelization, kScryptMaxMemory, hash,
kBytesFromHash);
// EVP_PBE_scrypt can only fail due to memory allocation error (which aborts
// Chromium) or invalid parameters. In case of a failure a hash could leak
// information from the stack, so using CHECK is better than DCHECK.
CHECK(scrypt_ok);
// Take 37 bits of |hash|.
uint64_t hash37 = ((static_cast<uint64_t>(hash[0]))) |
((static_cast<uint64_t>(hash[1])) << 8) |
((static_cast<uint64_t>(hash[2])) << 16) |
((static_cast<uint64_t>(hash[3])) << 24) |
(((static_cast<uint64_t>(hash[4])) & 0x1F) << 32);
return hash37;
}
std::string CanonicalizeUsername(const std::string& username,
bool is_gaia_account) {
std::vector<std::string> parts = base::SplitString(
username, "@", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (parts.size() != 2U) {
if (is_gaia_account && parts.size() == 1U)
return gaia::CanonicalizeEmail(username + "@gmail.com");
return username;
}
return gaia::CanonicalizeEmail(username);
}
bool AreUsernamesSame(const std::string& username1,
bool is_username1_gaia_account,
const std::string& username2,
bool is_username2_gaia_account) {
if (is_username1_gaia_account != is_username2_gaia_account)
return false;
return CanonicalizeUsername(username1, is_username1_gaia_account) ==
CanonicalizeUsername(username2, is_username2_gaia_account);
}
} // namespace password_manager