Commit 3a85f695 authored by Viktor Semeniuk's avatar Viktor Semeniuk Committed by Commit Bot

Implement Hash Affiliation Fetcher.

This CL introduces Hash Affiliation Fetcher, that makes a request to Google endpoint to get the affiliations and groups for requested facets.
Instead of sending facet URLs in a plain text it uses SHA-256 to compute a hash and eventually sends just a 16-bit short hash prefix represented by uint64_t.

Bug: 1108279
Change-Id: I3a4b64c10bdd998dcf7081eec92d70b2431baef1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2440502
Commit-Queue: Viktor Semeniuk <vsemeniuk@google.com>
Reviewed-by: default avatarChristian Dullweber <dullweber@chromium.org>
Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#825312}
parent c2818190
......@@ -15,8 +15,6 @@ template <typename MessageT>
bool ParseFacets(const std::vector<FacetURI>& requested_facet_uris,
const MessageT& response,
std::vector<std::vector<Facet>>& result) {
result.reserve(requested_facet_uris.size());
std::map<FacetURI, size_t> facet_uri_to_class_index;
for (const auto& equivalence_class : response) {
std::vector<Facet> facets;
......
......@@ -7,11 +7,41 @@
#include <memory>
#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
#include "crypto/sha2.h"
#include "google_apis/google_api_keys.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "url/gurl.h"
namespace {
const int kPrefixLength = 16;
uint64_t ComputeHashPrefix(const password_manager::FacetURI& uri) {
static_assert(kPrefixLength < 64,
"Prefix should not be longer than 8 bytes.");
int bytes_count = kPrefixLength / 8 + (kPrefixLength % 8 != 0);
uint8_t hash[bytes_count];
crypto::SHA256HashString(uri.canonical_spec(), hash, bytes_count);
uint64_t result = 0;
for (int i = 0; i < bytes_count; i++) {
result <<= 8;
result |= hash[i];
}
int bits_to_clear = kPrefixLength % 8;
result &= (~0 << bits_to_clear);
int bits_to_shift = ((8 - bytes_count) * 8);
result <<= bits_to_shift;
return result;
}
} // namespace
namespace password_manager {
HashAffiliationFetcher::HashAffiliationFetcher(
......@@ -21,15 +51,59 @@ HashAffiliationFetcher::HashAffiliationFetcher(
HashAffiliationFetcher::~HashAffiliationFetcher() = default;
// TODO: Add an implementation.
void HashAffiliationFetcher::StartRequest(
const std::vector<FacetURI>& facet_uris,
RequestInfo request_info) {}
RequestInfo request_info) {
requested_facet_uris_ = facet_uris;
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("affiliation_lookup_by_hash", R"(
semantics {
sender: "Hash Affiliation Fetcher"
description:
" Chrome can obtain information about affiliated and grouped "
" websites as well as link to directly change password using this "
" request. Chrome sends only hash prefixes of the websites. "
trigger: "After triggering change password action"
data:
"Hash prefixes of websites URLs on which user's credentials were "
"compromised."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This feature is used as a substitute for affiliation fetcher "
"that sends full facet URLs in a plain form. If the standard "
"affiliation fetcher cannot be used for privacy reasons then this "
"request is made as an alternative solution."
policy_exception_justification:
"Not implemented. This request has no limitation as it is intended "
"to be used when sync is turned off or passphrase is used. "
})");
// Prepare the payload based on |facet_uris| and |request_info|.
affiliation_pb::LookupAffiliationByHashPrefixRequest lookup_request;
lookup_request.set_hash_prefix_length(kPrefixLength);
for (const FacetURI& uri : facet_uris)
lookup_request.add_hash_prefixes(ComputeHashPrefix(uri));
*lookup_request.mutable_mask() = CreateLookupMask(request_info);
std::string serialized_request;
bool success = lookup_request.SerializeToString(&serialized_request);
DCHECK(success);
FinalizeRequest(serialized_request, BuildQueryURL(), traffic_annotation);
}
const std::vector<FacetURI>& HashAffiliationFetcher::GetRequestedFacetURIs()
const {
return requested_facet_uris_;
}
// static
GURL HashAffiliationFetcher::BuildQueryURL() {
return net::AppendQueryParameter(
......
......@@ -4,29 +4,85 @@
#include "components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.h"
#include <memory>
#include <string>
#include "base/strings/string_number_conversions_win.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/password_manager/core/browser/android_affiliation/affiliation_api.pb.h"
#include "components/password_manager/core/browser/android_affiliation/mock_affiliation_fetcher_delegate.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace password_manager {
constexpr char k1ExampleURL[] = "https://1.example.com";
constexpr uint64_t k1ExampleHash16LenPrefix = 10506334980701945856ULL;
constexpr char k2ExampleURL[] = "https://2.example.com";
constexpr uint64_t k2ExampleHash16LenPrefix = 9324421553493901312ULL;
class HashAffiliationFetcherTest : public testing::Test {
public:
HashAffiliationFetcherTest() = default;
HashAffiliationFetcherTest() {
test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
[&](const network::ResourceRequest& request) {
intercepted_body_ = network::GetUploadData(request);
intercepted_headers_ = request.headers;
}));
}
~HashAffiliationFetcherTest() override = default;
protected:
void VerifyRequestPayload(const std::vector<uint64_t>& expected_hash_prefixes,
HashAffiliationFetcher::RequestInfo request_info);
void WaitForResponse() { task_environment_.RunUntilIdle(); }
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory() {
return test_shared_loader_factory_;
}
private:
base::test::TaskEnvironment task_environment_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
std::string intercepted_body_;
net::HttpRequestHeaders intercepted_headers_;
};
void HashAffiliationFetcherTest::VerifyRequestPayload(
const std::vector<uint64_t>& expected_hash_prefixes,
HashAffiliationFetcher::RequestInfo request_info) {
affiliation_pb::LookupAffiliationByHashPrefixRequest request;
ASSERT_TRUE(request.ParseFromString(intercepted_body_));
std::vector<uint64_t> actual_hash_prefixes;
for (const auto prefix : request.hash_prefixes())
actual_hash_prefixes.push_back(prefix);
std::string content_type;
intercepted_headers_.GetHeader(net::HttpRequestHeaders::kContentType,
&content_type);
EXPECT_EQ("application/x-protobuf", content_type);
EXPECT_THAT(actual_hash_prefixes,
testing::UnorderedElementsAreArray(expected_hash_prefixes));
// Change password info requires grouping info enabled.
EXPECT_EQ(request.mask().grouping_info(), request_info.change_password_info);
EXPECT_EQ(request.mask().change_password_info(),
request_info.change_password_info);
}
TEST_F(HashAffiliationFetcherTest, BuildQueryURL) {
network::TestURLLoaderFactory test_url_loader_factory;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory);
MockAffiliationFetcherDelegate mock_delegate;
HashAffiliationFetcher fetcher(test_shared_loader_factory(), &mock_delegate);
HashAffiliationFetcher fetcher(test_shared_loader_factory, &mock_delegate);
GURL query_url = fetcher.BuildQueryURL();
EXPECT_EQ("https", query_url.scheme());
......@@ -34,4 +90,42 @@ TEST_F(HashAffiliationFetcherTest, BuildQueryURL) {
EXPECT_EQ("/affiliation/v1/affiliation:lookupByHashPrefix", query_url.path());
}
TEST_F(HashAffiliationFetcherTest, GetRequestedFacetURIs) {
MockAffiliationFetcherDelegate mock_delegate;
HashAffiliationFetcher fetcher(test_shared_loader_factory(), &mock_delegate);
HashAffiliationFetcher::RequestInfo request_info{.change_password_info =
true};
std::vector<FacetURI> requested_uris;
requested_uris.push_back(FacetURI::FromCanonicalSpec(k1ExampleURL));
requested_uris.push_back(FacetURI::FromCanonicalSpec(k2ExampleURL));
fetcher.StartRequest(requested_uris, request_info);
WaitForResponse();
EXPECT_THAT(fetcher.GetRequestedFacetURIs(),
testing::UnorderedElementsAreArray(requested_uris));
}
TEST_F(HashAffiliationFetcherTest,
VerifyPayloadForMultipleHashesRequestWith16LengthPrefix) {
MockAffiliationFetcherDelegate mock_delegate;
HashAffiliationFetcher fetcher(test_shared_loader_factory(), &mock_delegate);
HashAffiliationFetcher::RequestInfo request_info{.change_password_info =
true};
std::vector<FacetURI> requested_uris;
requested_uris.push_back(FacetURI::FromCanonicalSpec(k1ExampleURL));
requested_uris.push_back(FacetURI::FromCanonicalSpec(k2ExampleURL));
fetcher.StartRequest(requested_uris, request_info);
WaitForResponse();
std::vector<uint64_t> hash_prefixes;
hash_prefixes.push_back(k1ExampleHash16LenPrefix);
hash_prefixes.push_back(k2ExampleHash16LenPrefix);
ASSERT_NO_FATAL_FAILURE(VerifyRequestPayload(hash_prefixes, request_info));
}
} // namespace password_manager
......@@ -12,6 +12,7 @@ Refer to README.md for content description and update process.
<item id="accounts_image_fetcher" added_in_milestone="66" hash_code="98658519" type="0" content_hash_code="45432230" os_list="linux,windows" file_path="components/signin/internal/identity_manager/account_fetcher_service.cc"/>
<item id="adb_client_socket" added_in_milestone="65" hash_code="87775794" type="0" content_hash_code="56654828" os_list="linux,windows" file_path="chrome/browser/devtools/device/adb/adb_client_socket.cc"/>
<item id="affiliation_lookup" added_in_milestone="62" hash_code="111904019" type="0" content_hash_code="81061452" os_list="linux,windows" file_path="components/password_manager/core/browser/android_affiliation/affiliation_fetcher.cc"/>
<item id="affiliation_lookup_by_hash" added_in_milestone="87" hash_code="57748571" type="0" content_hash_code="40566404" os_list="linux,windows" file_path="components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.cc"/>
<item id="android_device_manager_socket" added_in_milestone="65" hash_code="37249086" type="0" content_hash_code="6436865" os_list="linux,windows" file_path="chrome/browser/devtools/device/android_device_manager.cc"/>
<item id="android_web_socket" added_in_milestone="65" hash_code="39356976" type="0" content_hash_code="12310113" os_list="linux,windows" file_path="chrome/browser/devtools/device/android_web_socket.cc"/>
<item id="appcache_update_job" added_in_milestone="62" hash_code="25790702" type="0" content_hash_code="27424887" os_list="linux,windows" file_path="content/browser/appcache/appcache_update_url_loader_request.cc"/>
......
......@@ -222,6 +222,7 @@ hidden="true" so that these annotations don't show up in the document.
<group name="Identity and Security">
<sender name="Accounts, Identity, and Authentication">
<traffic_annotation unique_id="accounts_image_fetcher"/>
<traffic_annotation unique_id="affiliation_lookup_by_hash"/>
<traffic_annotation unique_id="device_management_service"/>
<traffic_annotation unique_id="lookup_single_password_leak"/>
</sender>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment