blob: dbbd4cc03a81dd446d487c718c269ce43171a837 [file] [log] [blame]
// Copyright 2019 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/leak_detection/leak_detection_request_utils.h"
#include <string>
#include "base/containers/span.h"
#include "base/debug/dump_without_crashing.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece_forward.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/task_runner_util.h"
#include "components/password_manager/core/browser/leak_detection/encryption_utils.h"
#include "components/password_manager/core/browser/leak_detection/single_lookup_response.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "crypto/sha2.h"
#include "google_apis/gaia/core_account_id.h"
namespace password_manager {
namespace {
constexpr char kAPIScope[] =
"https://www.googleapis.com/auth/identity.passwords.leak.check";
// Returns a Google account that can be used for getting a token.
CoreAccountId GetAccountForRequest(
const signin::IdentityManager* identity_manager) {
CoreAccountInfo result = identity_manager->GetPrimaryAccountInfo(
signin::ConsentLevel::kNotRequired);
if (result.IsEmpty()) {
std::vector<CoreAccountInfo> all_accounts =
identity_manager->GetAccountsWithRefreshTokens();
if (!all_accounts.empty())
result = all_accounts.front();
}
return result.account_id;
}
// Produce |username_hash_prefix| and scrypt hash of the arguments.
// Scrypt computation is actually long.
LookupSingleLeakPayload ProduceHashes(base::StringPiece username,
base::StringPiece password) {
std::string canonicalized_username = CanonicalizeUsername(username);
LookupSingleLeakPayload payload;
payload.username_hash_prefix = BucketizeUsername(canonicalized_username);
payload.encrypted_payload =
ScryptHashUsernameAndPassword(canonicalized_username, password);
if (payload.encrypted_payload.empty())
return LookupSingleLeakPayload();
return payload;
}
// Despite the function is short, it executes long. That's why it should be done
// asynchronously.
LookupSingleLeakData PrepareLookupSingleLeakData(base::StringPiece username,
base::StringPiece password) {
LookupSingleLeakData data;
data.payload = ProduceHashes(username, password);
if (data.payload.encrypted_payload.empty())
return LookupSingleLeakData();
data.payload.encrypted_payload =
CipherEncrypt(data.payload.encrypted_payload, &data.encryption_key);
return data.payload.encrypted_payload.empty() ? LookupSingleLeakData()
: std::move(data);
}
// Despite the function is short, it executes long. That's why it should be done
// asynchronously.
LookupSingleLeakPayload PrepareLookupSingleLeakDataWithKey(
const std::string& encryption_key,
base::StringPiece username,
base::StringPiece password) {
LookupSingleLeakPayload payload = ProduceHashes(username, password);
if (payload.encrypted_payload.empty())
return LookupSingleLeakPayload();
payload.encrypted_payload =
CipherEncryptWithKey(payload.encrypted_payload, encryption_key);
return payload.encrypted_payload.empty() ? LookupSingleLeakPayload()
: std::move(payload);
}
// Searches |reencrypted_lookup_hash| in the |encrypted_leak_match_prefixes|
// array. |encryption_key| is the original client key used to encrypt the
// payload.
AnalyzeResponseResult CheckIfCredentialWasLeaked(
std::unique_ptr<SingleLookupResponse> response,
const std::string& encryption_key) {
std::string decrypted_username_password =
CipherDecrypt(response->reencrypted_lookup_hash, encryption_key);
if (decrypted_username_password.empty()) {
DLOG(ERROR) << "Can't decrypt data="
<< base::HexEncode(base::as_bytes(
base::make_span(response->reencrypted_lookup_hash)));
return AnalyzeResponseResult::kDecryptionError;
}
std::string hash_username_password =
crypto::SHA256HashString(decrypted_username_password);
const ptrdiff_t matched_prefixes =
std::count_if(response->encrypted_leak_match_prefixes.begin(),
response->encrypted_leak_match_prefixes.end(),
[&hash_username_password](const std::string& prefix) {
return base::StartsWith(hash_username_password, prefix,
base::CompareCase::SENSITIVE);
});
switch (matched_prefixes) {
case 0:
return AnalyzeResponseResult::kNotLeaked;
case 1:
return AnalyzeResponseResult::kLeaked;
default:
// In theory this should never happen, due to the API contract the server
// provides. Generate a crash dump in case it does, so that we get
// notified.
base::debug::DumpWithoutCrashing();
return AnalyzeResponseResult::kLeaked;
};
}
} // namespace
void PrepareSingleLeakRequestData(const std::string& username,
const std::string& password,
SingleLeakRequestDataCallback callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&PrepareLookupSingleLeakData, username, password),
std::move(callback));
}
void PrepareSingleLeakRequestData(
base::CancelableTaskTracker& task_tracker,
base::TaskRunner& task_runner,
const std::string& encryption_key,
const std::string& username,
const std::string& password,
base::OnceCallback<void(LookupSingleLeakPayload)> callback) {
task_tracker.PostTaskAndReplyWithResult(
&task_runner, FROM_HERE,
base::BindOnce(&PrepareLookupSingleLeakDataWithKey, encryption_key,
username, password),
std::move(callback));
}
void AnalyzeResponse(std::unique_ptr<SingleLookupResponse> response,
const std::string& encryption_key,
SingleLeakResponseAnalysisCallback callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&CheckIfCredentialWasLeaked, std::move(response),
encryption_key),
std::move(callback));
}
std::unique_ptr<signin::AccessTokenFetcher> RequestAccessToken(
signin::IdentityManager* identity_manager,
signin::AccessTokenFetcher::TokenCallback callback) {
return identity_manager->CreateAccessTokenFetcherForAccount(
GetAccountForRequest(identity_manager),
/*consumer_name=*/"leak_detection_service", {kAPIScope},
std::move(callback), signin::AccessTokenFetcher::Mode::kImmediate);
}
} // namespace password_manager