blob: 4e6392d9dcb17dc6943bf23329cee4295911d8f1 [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 "chrome/browser/net/trial_comparison_cert_verifier_controller.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/safe_browsing/certificate_reporting_service_factory.h"
#include "chrome/browser/safe_browsing/certificate_reporting_service_test_utils.h"
#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
#include "chrome/browser/ssl/cert_logger.pb.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/safe_browsing/common/safe_browsing_prefs.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "net/base/net_errors.h"
#include "net/cert/cert_verify_result.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/test/cert_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_data_directory.h"
#include "net/url_request/url_request_test_util.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/trial_comparison_cert_verifier.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using certificate_reporting_test_utils::CertificateReportingServiceTestHelper;
using certificate_reporting_test_utils::ReportExpectation;
using certificate_reporting_test_utils::RetryStatus;
using net::test::IsError;
using testing::_;
using testing::Mock;
using testing::Return;
using testing::SetArgPointee;
using testing::StrictMock;
namespace {
MATCHER_P(CertChainMatches, expected_cert, "") {
net::CertificateList actual_certs =
net::X509Certificate::CreateCertificateListFromBytes(
arg.data(), arg.size(),
net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
if (actual_certs.empty()) {
*result_listener << "failed to parse arg";
return false;
}
std::vector<std::string> actual_der_certs;
for (const auto& cert : actual_certs) {
actual_der_certs.emplace_back(
net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()));
}
std::vector<std::string> expected_der_certs;
expected_der_certs.emplace_back(
net::x509_util::CryptoBufferAsStringPiece(expected_cert->cert_buffer()));
for (const auto& buffer : expected_cert->intermediate_buffers()) {
expected_der_certs.emplace_back(
net::x509_util::CryptoBufferAsStringPiece(buffer.get()));
}
return actual_der_certs == expected_der_certs;
}
} // namespace
class MockTrialComparisonCertVerifierConfigClient
: public network::mojom::TrialComparisonCertVerifierConfigClient {
public:
MockTrialComparisonCertVerifierConfigClient(
network::mojom::TrialComparisonCertVerifierConfigClientRequest
config_client_request)
: binding_(this, std::move(config_client_request)) {}
MOCK_METHOD1(OnTrialConfigUpdated, void(bool allowed));
private:
mojo::Binding<network::mojom::TrialComparisonCertVerifierConfigClient>
binding_;
};
class TrialComparisonCertVerifierControllerTest : public testing::Test {
public:
void SetUp() override {
cert_chain_1_ = CreateCertificateChainFromFile(
net::GetTestCertsDirectory(), "multi-root-chain1.pem",
net::X509Certificate::FORMAT_AUTO);
ASSERT_TRUE(cert_chain_1_);
leaf_cert_1_ = net::X509Certificate::CreateFromBuffer(
bssl::UpRef(cert_chain_1_->cert_buffer()), {});
ASSERT_TRUE(leaf_cert_1_);
cert_chain_2_ = CreateCertificateChainFromFile(
net::GetTestCertsDirectory(), "multi-root-chain2.pem",
net::X509Certificate::FORMAT_AUTO);
ASSERT_TRUE(cert_chain_2_);
ok_result_.verified_cert = cert_chain_1_;
bad_result_.verified_cert = cert_chain_2_;
bad_result_.cert_status = net::CERT_STATUS_DATE_INVALID;
reporting_service_test_helper_ =
base::MakeRefCounted<CertificateReportingServiceTestHelper>();
CertificateReportingServiceFactory::GetInstance()
->SetReportEncryptionParamsForTesting(
reporting_service_test_helper()->server_public_key(),
reporting_service_test_helper()->server_public_key_version());
CertificateReportingServiceFactory::GetInstance()
->SetURLLoaderFactoryForTesting(reporting_service_test_helper_);
reporting_service_test_helper()->SetFailureMode(
certificate_reporting_test_utils::REPORTS_SUCCESSFUL);
if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
system_request_context_getter_ =
base::MakeRefCounted<net::TestURLRequestContextGetter>(
base::CreateSingleThreadTaskRunnerWithTraits(
{content::BrowserThread::IO}));
TestingBrowserProcess::GetGlobal()->SetSystemRequestContext(
system_request_context_getter_.get());
}
// Creating the profile before the SafeBrowsingService ensures the
// ServiceManagerConnection is initialized.
profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(profile_manager_->SetUp());
ASSERT_TRUE(g_browser_process->profile_manager());
profile_ = profile_manager_->CreateTestingProfile("profile1");
sb_service_ =
base::MakeRefCounted<safe_browsing::TestSafeBrowsingService>();
TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(
sb_service_.get());
g_browser_process->safe_browsing_service()->Initialize();
// Initialize CertificateReportingService for |profile_|.
ASSERT_TRUE(reporting_service());
base::RunLoop().RunUntilIdle();
}
void CreateController(Profile* profile) {
network::mojom::TrialComparisonCertVerifierConfigClientPtr config_client;
auto config_client_request = mojo::MakeRequest(&config_client);
trial_controller_ =
std::make_unique<TrialComparisonCertVerifierController>(profile);
trial_controller_->AddClient(std::move(config_client),
mojo::MakeRequest(&report_client_));
mock_config_client_ = std::make_unique<
StrictMock<MockTrialComparisonCertVerifierConfigClient>>(
std::move(config_client_request));
}
void CreateController() { CreateController(profile()); }
void TearDown() override {
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Ensure mock expectations are checked.
mock_config_client_ = nullptr;
if (TestingBrowserProcess::GetGlobal()->safe_browsing_service()) {
TestingBrowserProcess::GetGlobal()->safe_browsing_service()->ShutDown();
TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(nullptr);
}
TestingBrowserProcess::GetGlobal()->SetSystemRequestContext(nullptr);
system_request_context_getter_ = nullptr;
TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(
false);
}
TestingProfile* profile() { return profile_; }
sync_preferences::TestingPrefServiceSyncable* pref_service() {
return profile_->GetTestingPrefService();
}
TrialComparisonCertVerifierController& trial_controller() {
return *trial_controller_;
}
network::mojom::TrialComparisonCertVerifierReportClientPtr& report_client() {
return report_client_;
}
MockTrialComparisonCertVerifierConfigClient& mock_config_client() {
return *mock_config_client_;
}
CertificateReportingServiceTestHelper* reporting_service_test_helper() {
return reporting_service_test_helper_.get();
}
CertificateReportingService* reporting_service() const {
return CertificateReportingServiceFactory::GetForBrowserContext(profile_);
}
protected:
scoped_refptr<net::X509Certificate> cert_chain_1_;
scoped_refptr<net::X509Certificate> cert_chain_2_;
scoped_refptr<net::X509Certificate> leaf_cert_1_;
net::CertVerifyResult ok_result_;
net::CertVerifyResult bad_result_;
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_;
private:
scoped_refptr<CertificateReportingServiceTestHelper>
reporting_service_test_helper_;
content::TestBrowserThreadBundle thread_bundle_;
scoped_refptr<safe_browsing::SafeBrowsingService> sb_service_;
scoped_refptr<net::URLRequestContextGetter> system_request_context_getter_;
std::unique_ptr<TestingProfileManager> profile_manager_;
TestingProfile* profile_;
network::mojom::TrialComparisonCertVerifierReportClientPtr report_client_;
std::unique_ptr<TrialComparisonCertVerifierController> trial_controller_;
std::unique_ptr<StrictMock<MockTrialComparisonCertVerifierConfigClient>>
mock_config_client_;
};
TEST_F(TrialComparisonCertVerifierControllerTest, NothingEnabled) {
CreateController();
// Trial should not be allowed.
EXPECT_FALSE(trial_controller().IsAllowed());
// Enable the SBER pref, shouldn't matter since it's a non-official build and
// field trial isn't enabled.
safe_browsing::SetExtendedReportingPref(pref_service(), true);
// Trial still not allowed, and OnTrialConfigUpdated should not be called
// either.
EXPECT_FALSE(trial_controller().IsAllowed());
// Attempting to send a report should also do nothing.
report_client()->SendTrialReport("hostname", leaf_cert_1_, false, false,
false, false, ok_result_, ok_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect no report since the trial is not allowed.
reporting_service_test_helper()->ExpectNoRequests(reporting_service());
}
TEST_F(TrialComparisonCertVerifierControllerTest,
OfficialBuildTrialNotEnabled) {
TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
CreateController();
EXPECT_FALSE(trial_controller().IsAllowed());
safe_browsing::SetExtendedReportingPref(pref_service(), true);
// Trial still not allowed, and OnTrialConfigUpdated should not be called
// either.
EXPECT_FALSE(trial_controller().IsAllowed());
// Attempting to send a report should do nothing.
report_client()->SendTrialReport("hostname", leaf_cert_1_, false, false,
false, false, ok_result_, ok_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect no report since the trial is not allowed.
reporting_service_test_helper()->ExpectNoRequests(reporting_service());
}
TEST_F(TrialComparisonCertVerifierControllerTest,
NotOfficialBuildTrialEnabled) {
scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_->InitAndEnableFeature(
features::kCertDualVerificationTrialFeature);
CreateController();
EXPECT_FALSE(trial_controller().IsAllowed());
#if defined(OFFICIAL_BUILD) && defined(GOOGLE_CHROME_BUILD)
// In a real official build, expect the trial config to be updated.
EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(true)).Times(1);
#endif
safe_browsing::SetExtendedReportingPref(pref_service(), true);
#if defined(OFFICIAL_BUILD) && defined(GOOGLE_CHROME_BUILD)
// In a real official build, expect the trial to be allowed now. (Don't
// need to test sending reports here, since that'll be tested by
// OfficialBuildTrialEnabled.)
EXPECT_TRUE(trial_controller().IsAllowed());
#else
// Trial still not allowed, and OnTrialConfigUpdated should not be called
// either.
EXPECT_FALSE(trial_controller().IsAllowed());
// Attempting to send a report should do nothing.
report_client()->SendTrialReport("hostname", leaf_cert_1_, false, false,
false, false, ok_result_, ok_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect no report since the trial is not allowed.
reporting_service_test_helper()->ExpectNoRequests(reporting_service());
#endif
}
TEST_F(TrialComparisonCertVerifierControllerTest, OfficialBuildTrialEnabled) {
TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_->InitAndEnableFeature(
features::kCertDualVerificationTrialFeature);
CreateController();
EXPECT_FALSE(trial_controller().IsAllowed());
// Enable the SBER pref, which should trigger the OnTrialConfigUpdated
// callback.
EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(true)).Times(1);
safe_browsing::SetExtendedReportingPref(pref_service(), true);
// Trial should now be allowed.
EXPECT_TRUE(trial_controller().IsAllowed());
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// OnTrialConfigUpdated should have been called.
Mock::VerifyAndClear(&mock_config_client());
// Report should be sent.
report_client()->SendTrialReport("127.0.0.1", leaf_cert_1_, false, false,
false, false, ok_result_, bad_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect a report.
std::vector<std::string> full_reports;
reporting_service_test_helper()->WaitForRequestsDestroyed(
ReportExpectation::Successful({{"127.0.0.1", RetryStatus::NOT_RETRIED}}),
&full_reports);
ASSERT_EQ(1U, full_reports.size());
chrome_browser_ssl::CertLoggerRequest report;
ASSERT_TRUE(report.ParseFromString(full_reports[0]));
EXPECT_EQ(0, report.cert_error_size());
EXPECT_EQ(0, report.cert_status_size());
ASSERT_TRUE(report.has_features_info());
ASSERT_TRUE(report.features_info().has_trial_verification_info());
const chrome_browser_ssl::TrialVerificationInfo& trial_info =
report.features_info().trial_verification_info();
ASSERT_EQ(1, trial_info.cert_error_size());
EXPECT_EQ(chrome_browser_ssl::CertLoggerRequest::ERR_CERT_DATE_INVALID,
trial_info.cert_error()[0]);
EXPECT_EQ(0, trial_info.cert_status_size());
EXPECT_THAT(report.unverified_cert_chain(), CertChainMatches(leaf_cert_1_));
EXPECT_THAT(report.cert_chain(), CertChainMatches(cert_chain_1_));
EXPECT_THAT(trial_info.cert_chain(), CertChainMatches(cert_chain_2_));
// Disable the SBER pref again, which should trigger the OnTrialConfigUpdated
// callback.
EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(false)).Times(1);
safe_browsing::SetExtendedReportingPref(pref_service(), false);
// Not allowed now.
EXPECT_FALSE(trial_controller().IsAllowed());
// Attempting to send a report should do nothing now.
report_client()->SendTrialReport("hostname", leaf_cert_1_, false, false,
false, false, ok_result_, bad_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect no report since the trial is not allowed.
reporting_service_test_helper()->ExpectNoRequests(reporting_service());
}
TEST_F(TrialComparisonCertVerifierControllerTest,
OfficialBuildTrialEnabledTwoClients) {
TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_->InitAndEnableFeature(
features::kCertDualVerificationTrialFeature);
CreateController();
network::mojom::TrialComparisonCertVerifierReportClientPtr report_client_2;
network::mojom::TrialComparisonCertVerifierConfigClientPtr config_client_2;
auto config_client_2_request = mojo::MakeRequest(&config_client_2);
trial_controller().AddClient(std::move(config_client_2),
mojo::MakeRequest(&report_client_2));
StrictMock<MockTrialComparisonCertVerifierConfigClient> mock_config_client_2(
std::move(config_client_2_request));
EXPECT_FALSE(trial_controller().IsAllowed());
// Enable the SBER pref, which should trigger the OnTrialConfigUpdated
// callback.
EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(true)).Times(1);
EXPECT_CALL(mock_config_client_2, OnTrialConfigUpdated(true)).Times(1);
safe_browsing::SetExtendedReportingPref(pref_service(), true);
// Trial should now be allowed.
EXPECT_TRUE(trial_controller().IsAllowed());
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// OnTrialConfigUpdated should have been called.
Mock::VerifyAndClear(&mock_config_client());
Mock::VerifyAndClear(&mock_config_client_2);
// Report should be sent.
report_client()->SendTrialReport("127.0.0.1", leaf_cert_1_, false, false,
false, false, ok_result_, bad_result_);
report_client_2->SendTrialReport("127.0.0.2", leaf_cert_1_, false, false,
false, false, ok_result_, bad_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect a report.
std::vector<std::string> full_reports;
reporting_service_test_helper()->WaitForRequestsDestroyed(
ReportExpectation::Successful({{"127.0.0.1", RetryStatus::NOT_RETRIED},
{"127.0.0.2", RetryStatus::NOT_RETRIED}}),
&full_reports);
ASSERT_EQ(2U, full_reports.size());
chrome_browser_ssl::CertLoggerRequest report;
for (size_t i = 0; i < 2; ++i) {
ASSERT_TRUE(report.ParseFromString(full_reports[i]));
EXPECT_EQ(0, report.cert_error_size());
EXPECT_EQ(0, report.cert_status_size());
ASSERT_TRUE(report.has_features_info());
ASSERT_TRUE(report.features_info().has_trial_verification_info());
const chrome_browser_ssl::TrialVerificationInfo& trial_info =
report.features_info().trial_verification_info();
ASSERT_EQ(1, trial_info.cert_error_size());
EXPECT_EQ(chrome_browser_ssl::CertLoggerRequest::ERR_CERT_DATE_INVALID,
trial_info.cert_error()[0]);
EXPECT_EQ(0, trial_info.cert_status_size());
EXPECT_THAT(report.unverified_cert_chain(), CertChainMatches(leaf_cert_1_));
EXPECT_THAT(report.cert_chain(), CertChainMatches(cert_chain_1_));
EXPECT_THAT(trial_info.cert_chain(), CertChainMatches(cert_chain_2_));
}
// Disable the SBER pref again, which should trigger the OnTrialConfigUpdated
// callback.
EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(false)).Times(1);
EXPECT_CALL(mock_config_client_2, OnTrialConfigUpdated(false)).Times(1);
safe_browsing::SetExtendedReportingPref(pref_service(), false);
// Not allowed now.
EXPECT_FALSE(trial_controller().IsAllowed());
// Attempting to send a report should do nothing now.
report_client()->SendTrialReport("hostname", leaf_cert_1_, false, false,
false, false, ok_result_, bad_result_);
report_client_2->SendTrialReport("hostname2", leaf_cert_1_, false, false,
false, false, ok_result_, bad_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect no report since the trial is not allowed.
reporting_service_test_helper()->ExpectNoRequests(reporting_service());
}
TEST_F(TrialComparisonCertVerifierControllerTest,
OfficialBuildTrialEnabledUmaOnly) {
TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_->InitAndEnableFeatureWithParameters(
features::kCertDualVerificationTrialFeature, {{"uma_only", "true"}});
CreateController();
EXPECT_FALSE(trial_controller().IsAllowed());
// Enable the SBER pref, which should trigger the OnTrialConfigUpdated
// callback.
EXPECT_CALL(mock_config_client(), OnTrialConfigUpdated(true)).Times(1);
safe_browsing::SetExtendedReportingPref(pref_service(), true);
// Trial should now be allowed.
EXPECT_TRUE(trial_controller().IsAllowed());
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// OnTrialConfigUpdated should have been called.
Mock::VerifyAndClear(&mock_config_client());
// In uma_only mode, the network service will generate a report, but the
// trial controller will not send it to the reporting service.
report_client()->SendTrialReport("127.0.0.1", leaf_cert_1_, false, false,
false, false, ok_result_, bad_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect no reports in uma_only mode.
reporting_service_test_helper()->ExpectNoRequests(reporting_service());
}
TEST_F(TrialComparisonCertVerifierControllerTest,
IncognitoOfficialBuildTrialEnabled) {
TrialComparisonCertVerifierController::SetFakeOfficialBuildForTesting(true);
scoped_feature_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_->InitAndEnableFeature(
features::kCertDualVerificationTrialFeature);
CreateController(profile()->GetOffTheRecordProfile());
EXPECT_FALSE(trial_controller().IsAllowed());
// Enable the SBER pref, shouldn't matter since it's an incognito profile.
safe_browsing::SetExtendedReportingPref(pref_service(), true);
// Trial still not allowed, and OnTrialConfigUpdated should not be called
// either.
EXPECT_FALSE(trial_controller().IsAllowed());
// Attempting to send a report should also do nothing.
report_client()->SendTrialReport("hostname", leaf_cert_1_, false, false,
false, false, ok_result_, ok_result_);
// Ensure any in-flight mojo calls get run.
base::RunLoop().RunUntilIdle();
// Expect no report since the trial is not allowed.
reporting_service_test_helper()->ExpectNoRequests(reporting_service());
}