blob: c0de69c8f01669de296a75c8bccbdd51def6855a [file] [log] [blame]
// Copyright 2020 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 <memory>
#include "base/callback.h"
#include "base/ignore_result.h"
#include "base/synchronization/lock.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/cert_verifier_browser_test.h"
#include "chrome/browser/ssl/sct_reporting_service.h"
#include "chrome/browser/ssl/sct_reporting_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/network_service_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/network_service_test_helper.h"
#include "net/cert/cert_verify_result.h"
#include "net/cert/sct_status_flags.h"
#include "net/cert/signed_certificate_timestamp_and_status.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/simple_connection_listener.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/proto/sct_audit_report.pb.h"
#include "services/network/test/test_url_loader_factory.h"
namespace {
// These LogId constants allow test cases to specify SCTs from both Google and
// non-Google logs, allowing tests to vary how they meet (or don't meet) the
// Chrome CT policy. To be compliant, the cert used by the embedded test server
// currently requires three embedded SCTs, including at least one from a Google
// log and one from a non-Google log.
//
// Google's "Argon2023" log ("6D7Q2j71BjUy51covIlryQPTy9ERa+zraeF3fW0GvW4="):
const uint8_t kTestGoogleLogId[] = {
0xe8, 0x3e, 0xd0, 0xda, 0x3e, 0xf5, 0x06, 0x35, 0x32, 0xe7, 0x57,
0x28, 0xbc, 0x89, 0x6b, 0xc9, 0x03, 0xd3, 0xcb, 0xd1, 0x11, 0x6b,
0xec, 0xeb, 0x69, 0xe1, 0x77, 0x7d, 0x6d, 0x06, 0xbd, 0x6e};
// Cloudflare's "Nimbus2023" log
// ("ejKMVNi3LbYg6jjgUh7phBZwMhOFTTvSK8E6V6NS61I="):
const uint8_t kTestNonGoogleLogId1[] = {
0x7a, 0x32, 0x8c, 0x54, 0xd8, 0xb7, 0x2d, 0xb6, 0x20, 0xea, 0x38,
0xe0, 0x52, 0x1e, 0xe9, 0x84, 0x16, 0x70, 0x32, 0x13, 0x85, 0x4d,
0x3b, 0xd2, 0x2b, 0xc1, 0x3a, 0x57, 0xa3, 0x52, 0xeb, 0x52};
// DigiCert's "Yeti2023" log ("Nc8ZG7+xbFe/D61MbULLu7YnICZR6j/hKu+oA8M71kw="):
const uint8_t kTestNonGoogleLogId2[] = {
0x35, 0xcf, 0x19, 0x1b, 0xbf, 0xb1, 0x6c, 0x57, 0xbf, 0x0f, 0xad,
0x4c, 0x6d, 0x42, 0xcb, 0xbb, 0xb6, 0x27, 0x20, 0x26, 0x51, 0xea,
0x3f, 0xe1, 0x2a, 0xef, 0xa8, 0x03, 0xc3, 0x3b, 0xd6, 0x4c};
// Constructs a net::SignedCertificateTimestampAndStatus with the given
// information and appends it to |sct_list|.
void MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::Origin origin,
const std::string& extensions,
const std::string& signature_data,
const base::Time& timestamp,
const std::string& log_id,
net::ct::SCTVerifyStatus status,
net::SignedCertificateTimestampAndStatusList* sct_list) {
scoped_refptr<net::ct::SignedCertificateTimestamp> sct(
new net::ct::SignedCertificateTimestamp());
sct->version = net::ct::SignedCertificateTimestamp::V1;
sct->log_id = log_id;
sct->extensions = extensions;
sct->timestamp = timestamp;
sct->signature.signature_data = signature_data;
sct->origin = origin;
sct_list->push_back(net::SignedCertificateTimestampAndStatus(sct, status));
}
} // namespace
class SCTReportingServiceBrowserTest : public CertVerifierBrowserTest {
public:
SCTReportingServiceBrowserTest() {
// Set sampling rate to 1.0 to ensure deterministic behavior.
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kSCTAuditing,
{{features::kSCTAuditingSamplingRate.name, "1.0"}}}},
{});
SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
true);
// The report server must be initialized here so the reporting URL can be
// set before the network service is initialized.
ignore_result(report_server()->InitializeAndListen());
SCTReportingService::GetReportURLInstance() = report_server()->GetURL("/");
}
~SCTReportingServiceBrowserTest() override {
SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
absl::nullopt);
}
SCTReportingServiceBrowserTest(const SCTReportingServiceBrowserTest&) =
delete;
const SCTReportingServiceBrowserTest& operator=(
const SCTReportingServiceBrowserTest&) = delete;
void SetUpOnMainThread() override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
host_resolver()->AddRule("*", "127.0.0.1");
https_server()->AddDefaultHandlers(GetChromeTestDataDir());
report_server()->RegisterRequestHandler(base::BindRepeating(
&SCTReportingServiceBrowserTest::HandleReportRequest,
base::Unretained(this)));
report_server()->StartAcceptingConnections();
ASSERT_TRUE(https_server()->Start());
mock_cert_verifier()->set_default_result(net::OK);
// Mock the cert verify results so that it has valid CT verification
// results.
net::CertVerifyResult verify_result;
verify_result.verified_cert = https_server()->GetCertificate().get();
verify_result.is_issued_by_known_root = true;
// Add three "valid" SCTs and mark the certificate as compliant.
// The default test set up is embedded SCTs where one SCT is from a Google
// log and two are from non-Google logs (to meet the Chrome CT policy).
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions1",
"signature1", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestGoogleLogId),
base::size(kTestGoogleLogId)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions2",
"signature2", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId1),
base::size(kTestNonGoogleLogId1)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions3",
"signature3", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId2),
base::size(kTestNonGoogleLogId2)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
// Set up two test hosts as using publicly-issued certificates for testing.
mock_cert_verifier()->AddResultForCertAndHost(
https_server()->GetCertificate().get(), "a.test", verify_result,
net::OK);
mock_cert_verifier()->AddResultForCertAndHost(
https_server()->GetCertificate().get(), "b.test", verify_result,
net::OK);
// Set up a third (internal) test host for FlushAndCheckZeroReports().
mock_cert_verifier()->AddResultForCertAndHost(
https_server()->GetCertificate().get(),
"flush-and-check-zero-reports.test", verify_result, net::OK);
CertVerifierBrowserTest::SetUpOnMainThread();
}
protected:
void SetExtendedReportingEnabled(bool enabled) {
browser()->profile()->GetPrefs()->SetBoolean(
prefs::kSafeBrowsingScoutReportingEnabled, enabled);
}
void SetSafeBrowsingEnabled(bool enabled) {
browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled,
enabled);
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
net::EmbeddedTestServer* report_server() { return &report_server_; }
void WaitForRequests(size_t num_requests) {
// Each loop iteration will account for one request being processed. (This
// simplifies the request handler code below, and reduces the state that
// must be tracked and handled under locks.)
while (true) {
base::RunLoop run_loop;
{
base::AutoLock auto_lock(requests_lock_);
if (requests_seen_ >= num_requests)
return;
requests_closure_ = run_loop.QuitClosure();
}
run_loop.Run();
}
}
size_t requests_seen() {
base::AutoLock auto_lock(requests_lock_);
return requests_seen_;
}
sct_auditing::SCTClientReport GetLastSeenReport() {
base::AutoLock auto_lock(requests_lock_);
sct_auditing::SCTClientReport auditing_report;
if (last_seen_request_.has_content)
auditing_report.ParseFromString(last_seen_request_.content);
return auditing_report;
}
// Checks that no reports have been sent. To do this, opt-in the profile,
// make a new navigation, and check that there is only a single report and it
// was for this new navigation specifically. This should be used at the end of
// any negative tests to reduce the chance of false successes.
bool FlushAndCheckZeroReports() {
SetSafeBrowsingEnabled(true);
SetExtendedReportingEnabled(true);
EXPECT_TRUE(ui_test_utils::NavigateToURL(
browser(),
https_server()->GetURL("flush-and-check-zero-reports.test", "/")));
WaitForRequests(1);
return (1u == requests_seen() &&
"flush-and-check-zero-reports.test" == GetLastSeenReport()
.certificate_report(0)
.context()
.origin()
.hostname());
}
void set_error_count(int error_count) { error_count_ = error_count; }
private:
std::unique_ptr<net::test_server::HttpResponse> HandleReportRequest(
const net::test_server::HttpRequest& request) {
base::AutoLock auto_lock(requests_lock_);
last_seen_request_ = request;
++requests_seen_;
if (requests_closure_)
std::move(requests_closure_).Run();
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
if (error_count_ > 0) {
http_response->set_code(net::HTTP_TOO_MANY_REQUESTS);
--error_count_;
} else {
http_response->set_code(net::HTTP_OK);
}
return http_response;
}
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
net::EmbeddedTestServer report_server_{net::EmbeddedTestServer::TYPE_HTTPS};
base::test::ScopedFeatureList scoped_feature_list_;
// `requests_lock_` is used to force sequential access to these variables to
// avoid races that can cause test flakes.
base::Lock requests_lock_;
net::test_server::HttpRequest last_seen_request_;
size_t requests_seen_ = 0;
base::OnceClosure requests_closure_;
// How many times the report server should return an error before succeeding.
size_t error_count_ = 0;
};
// Tests that reports should not be sent when extended reporting is not opted
// in.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
NotOptedIn_ShouldNotEnqueueReport) {
SetExtendedReportingEnabled(false);
// Visit an HTTPS page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
// Check that no reports are sent.
EXPECT_EQ(0u, requests_seen());
EXPECT_TRUE(FlushAndCheckZeroReports());
}
// Tests that reports should be sent when extended reporting is opted in.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
OptedIn_ShouldEnqueueReport) {
SetExtendedReportingEnabled(true);
// Visit an HTTPS page and wait for the report to be sent.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
WaitForRequests(1);
// Check that one report was sent and contains the expected details.
EXPECT_EQ(1u, requests_seen());
EXPECT_EQ(
"a.test",
GetLastSeenReport().certificate_report(0).context().origin().hostname());
}
// Tests that disabling Safe Browsing entirely should cause reports to not get
// sent.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest, DisableSafebrowsing) {
SetSafeBrowsingEnabled(false);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
EXPECT_EQ(0u, requests_seen());
EXPECT_TRUE(FlushAndCheckZeroReports());
}
// Tests that we don't send a report for a navigation with a cert error.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
CertErrorDoesNotEnqueueReport) {
SetExtendedReportingEnabled(true);
// Visit a page with an invalid cert.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("invalid.test", "/")));
EXPECT_EQ(0u, requests_seen());
EXPECT_TRUE(FlushAndCheckZeroReports());
}
// Tests that reports aren't sent for Incognito windows.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
IncognitoWindow_ShouldNotEnqueueReport) {
// Enable SBER in the main profile.
SetExtendedReportingEnabled(true);
// Create a new Incognito window.
auto* incognito = CreateIncognitoBrowser();
ASSERT_TRUE(
ui_test_utils::NavigateToURL(incognito, https_server()->GetURL("/")));
EXPECT_EQ(0u, requests_seen());
EXPECT_TRUE(FlushAndCheckZeroReports());
}
// Tests that disabling Extended Reporting causes the cache to be cleared.
// TODO(crbug.com/1179504): Reenable. Flakes heavily on Linux, Win, and CrOS.
#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS)
#define MAYBE_OptingOutClearsSCTAuditingCache \
DISABLED_OptingOutClearsSCTAuditingCache
#else
#define MAYBE_OptingOutClearsSCTAuditingCache OptingOutClearsSCTAuditingCache
#endif
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
MAYBE_OptingOutClearsSCTAuditingCache) {
// Enable SCT auditing and enqueue a report.
SetExtendedReportingEnabled(true);
// Visit an HTTPS page and wait for a report to be sent.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
WaitForRequests(1);
// Check that one report was sent.
EXPECT_EQ(1u, requests_seen());
EXPECT_EQ(
"a.test",
GetLastSeenReport().certificate_report(0).context().origin().hostname());
// Disable Extended Reporting which should clear the underlying cache.
SetExtendedReportingEnabled(false);
// We can check that the same report gets cached again instead of being
// deduplicated (i.e., another report should be sent).
SetExtendedReportingEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
WaitForRequests(2);
EXPECT_EQ(2u, requests_seen());
EXPECT_EQ(
"a.test",
GetLastSeenReport().certificate_report(0).context().origin().hostname());
}
// Tests that reports are still sent for opted-in profiles after the network
// service crashes and is restarted.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
ReportsSentAfterNetworkServiceRestart) {
// This test is only applicable to out-of-process network service because it
// tests what happens when the network service crashes and restarts.
if (content::IsInProcessNetworkService()) {
return;
}
SetExtendedReportingEnabled(true);
// Crash the NetworkService to force it to restart.
SimulateNetworkServiceCrash();
// Flush the network interface to make sure it notices the crash.
browser()
->profile()
->GetDefaultStoragePartition()
->FlushNetworkInterfaceForTesting();
g_browser_process->system_network_context_manager()
->FlushNetworkInterfaceForTesting();
// The mock cert verify result will be lost when the network service restarts,
// so set back up the necessary rules.
mock_cert_verifier()->set_default_result(net::OK);
net::CertVerifyResult verify_result;
verify_result.verified_cert = https_server()->GetCertificate().get();
verify_result.is_issued_by_known_root = true;
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions1",
"signature1", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestGoogleLogId),
base::size(kTestGoogleLogId)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions2",
"signature2", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId1),
base::size(kTestNonGoogleLogId1)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions3",
"signature3", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId2),
base::size(kTestNonGoogleLogId2)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
mock_cert_verifier()->AddResultForCertAndHost(
https_server()->GetCertificate().get(), "a.test", verify_result, net::OK);
// Visit an HTTPS page and wait for the report to be sent.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
WaitForRequests(1);
// Check that one report was enqueued.
EXPECT_EQ(1u, requests_seen());
EXPECT_EQ(
"a.test",
GetLastSeenReport().certificate_report(0).context().origin().hostname());
}
// Tests that invalid SCTs don't get reported when the overall result is
// compliant with CT policy.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
CTCompliantInvalidSCTsNotReported) {
// Set up a mocked CertVerifyResult that includes both valid and invalid SCTs.
net::CertVerifyResult verify_result;
verify_result.verified_cert = https_server()->GetCertificate().get();
verify_result.is_issued_by_known_root = true;
// Add three valid SCTs and one invalid SCT. The three valid SCTs meet the
// Chrome CT policy.
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions1",
"signature1", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestGoogleLogId),
sizeof(kTestGoogleLogId)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions2",
"signature2", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId1),
sizeof(kTestNonGoogleLogId1)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions3",
"signature3", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId2),
sizeof(kTestNonGoogleLogId2)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions4",
"signature4", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId2),
sizeof(kTestNonGoogleLogId2)),
net::ct::SCT_STATUS_INVALID_SIGNATURE, &verify_result.scts);
mock_cert_verifier()->AddResultForCertAndHost(
https_server()->GetCertificate().get(), "mixed-scts.test", verify_result,
net::OK);
SetExtendedReportingEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("mixed-scts.test", "/")));
WaitForRequests(1);
EXPECT_EQ(1u, requests_seen());
auto report = GetLastSeenReport();
EXPECT_EQ(3, report.certificate_report(0).included_sct_size());
}
// Tests that invalid SCTs don't get included when the overall result is
// non-compliant with CT policy. Valid SCTs should still be reported.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest,
CTNonCompliantInvalidSCTsNotReported) {
// Set up a mocked CertVerifyResult that includes both valid and invalid SCTs.
net::CertVerifyResult verify_result;
verify_result.verified_cert = https_server()->GetCertificate().get();
verify_result.is_issued_by_known_root = true;
// Add one valid SCT and two invalid SCTs. These SCTs will not meet the Chrome
// CT policy requirements.
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions1",
"signature1", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId1),
sizeof(kTestNonGoogleLogId1)),
net::ct::SCT_STATUS_OK, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions2",
"signature2", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId1),
sizeof(kTestNonGoogleLogId1)),
net::ct::SCT_STATUS_INVALID_SIGNATURE, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions3",
"signature3", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId2),
sizeof(kTestNonGoogleLogId2)),
net::ct::SCT_STATUS_INVALID_SIGNATURE, &verify_result.scts);
mock_cert_verifier()->AddResultForCertAndHost(
https_server()->GetCertificate().get(), "mixed-scts.test", verify_result,
net::OK);
SetExtendedReportingEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("mixed-scts.test", "/")));
WaitForRequests(1);
EXPECT_EQ(1u, requests_seen());
auto report = GetLastSeenReport();
EXPECT_EQ(1, report.certificate_report(0).included_sct_size());
}
IN_PROC_BROWSER_TEST_F(SCTReportingServiceBrowserTest, NoValidSCTsNoReport) {
// Set up a mocked CertVerifyResult with only invalid SCTs.
net::CertVerifyResult verify_result;
verify_result.verified_cert = https_server()->GetCertificate().get();
verify_result.is_issued_by_known_root = true;
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions1",
"signature1", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId1),
sizeof(kTestNonGoogleLogId1)),
net::ct::SCT_STATUS_INVALID_TIMESTAMP, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions2",
"signature2", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId1),
sizeof(kTestNonGoogleLogId1)),
net::ct::SCT_STATUS_INVALID_SIGNATURE, &verify_result.scts);
MakeTestSCTAndStatus(
net::ct::SignedCertificateTimestamp::SCT_EMBEDDED, "extensions3",
"signature3", base::Time::Now(),
std::string(reinterpret_cast<const char*>(kTestNonGoogleLogId1),
sizeof(kTestNonGoogleLogId1)),
net::ct::SCT_STATUS_INVALID_SIGNATURE, &verify_result.scts);
mock_cert_verifier()->AddResultForCertAndHost(
https_server()->GetCertificate().get(), "invalid-scts.test",
verify_result, net::OK);
SetExtendedReportingEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("invalid-scts.test", "/")));
EXPECT_EQ(0u, requests_seen());
EXPECT_TRUE(FlushAndCheckZeroReports());
}
class SCTReportingServiceZeroSamplingRateBrowserTest
: public SCTReportingServiceBrowserTest {
public:
SCTReportingServiceZeroSamplingRateBrowserTest() {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kSCTAuditing,
{{features::kSCTAuditingSamplingRate.name, "0.0"}}}},
{});
}
SCTReportingServiceZeroSamplingRateBrowserTest(
const SCTReportingServiceZeroSamplingRateBrowserTest&) = delete;
const SCTReportingServiceZeroSamplingRateBrowserTest& operator=(
const SCTReportingServiceZeroSamplingRateBrowserTest&) = delete;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that the embedder is not notified when the sampling rate is zero.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceZeroSamplingRateBrowserTest,
EmbedderNotNotified) {
SetExtendedReportingEnabled(true);
// Visit an HTTPS page.
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
// Check that no reports are observed.
EXPECT_EQ(0u, requests_seen());
}
// Test fixture with SCT auditing and retry/persist enabled.
class SCTReportingServiceWithRetryAndPersistBrowserTest
: public SCTReportingServiceBrowserTest {
public:
SCTReportingServiceWithRetryAndPersistBrowserTest() {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kSCTAuditing,
{{features::kSCTAuditingSamplingRate.name, "1.0"}}},
{network::features::kSCTAuditingRetryAndPersistReports, {}}},
{});
}
~SCTReportingServiceWithRetryAndPersistBrowserTest() override = default;
SCTReportingServiceWithRetryAndPersistBrowserTest(
const SCTReportingServiceWithRetryAndPersistBrowserTest&) = delete;
const SCTReportingServiceWithRetryAndPersistBrowserTest& operator=(
const SCTReportingServiceWithRetryAndPersistBrowserTest&) = delete;
void SetUpOnMainThread() override {
// ConnectionListener must be set before the report server is started. Lets
// tests wait for one connection to be made to the report server (e.g. a
// failed connection due to the cert error that won't trigger the
// WaitForRequests() helper from the parent class).
report_connection_listener_ =
std::make_unique<net::test_server::SimpleConnectionListener>(
1, net::test_server::SimpleConnectionListener::
ALLOW_ADDITIONAL_CONNECTIONS);
report_server()->SetConnectionListener(report_connection_listener());
// Parent test fixture setup will start the report server.
SCTReportingServiceBrowserTest::SetUpOnMainThread();
// Set up NetworkServiceTest once.
content::GetNetworkService()->BindTestInterface(
network_service_test_.BindNewPipeAndPassReceiver());
// Override the retry delay to 0 so that retries happen immediately.
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
network_service_test()->SetSCTAuditingRetryDelay(base::TimeDelta());
}
void TearDownOnMainThread() override {
// Reset the retry delay override.
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
network_service_test()->SetSCTAuditingRetryDelay(absl::nullopt);
SCTReportingServiceBrowserTest::TearDownOnMainThread();
}
net::test_server::SimpleConnectionListener* report_connection_listener() {
return report_connection_listener_.get();
}
network::mojom::NetworkServiceTest* network_service_test() {
return network_service_test_.get();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<net::test_server::SimpleConnectionListener>
report_connection_listener_;
mojo::Remote<network::mojom::NetworkServiceTest> network_service_test_;
};
// Tests the simple case where a report succeeds on the first try.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceWithRetryAndPersistBrowserTest,
SucceedOnFirstTry) {
// Succeed on the first try.
set_error_count(0);
SetExtendedReportingEnabled(true);
// Visit an HTTPS page and wait for the report to be sent.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
WaitForRequests(1);
// Check that one report was sent and contains the expected details.
EXPECT_EQ(1u, requests_seen());
EXPECT_EQ(
"a.test",
GetLastSeenReport().certificate_report(0).context().origin().hostname());
}
IN_PROC_BROWSER_TEST_F(SCTReportingServiceWithRetryAndPersistBrowserTest,
RetryOnceAndSucceed) {
// Succeed on the second try.
set_error_count(1);
SetExtendedReportingEnabled(true);
// Visit an HTTPS page and wait for the report to be sent twice.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
WaitForRequests(2);
// Check that the report was sent twice and contains the expected details.
EXPECT_EQ(2u, requests_seen());
EXPECT_EQ(
"a.test",
GetLastSeenReport().certificate_report(0).context().origin().hostname());
}
IN_PROC_BROWSER_TEST_F(SCTReportingServiceWithRetryAndPersistBrowserTest,
FailAfterMaxRetries) {
// Don't succeed for max_retries+1.
set_error_count(16);
SetExtendedReportingEnabled(true);
// Visit an HTTPS page and wait for the report to be sent.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
// Wait until the reporter completes 16 requests.
WaitForRequests(16);
// Check that the report was sent 16x and contains the expected details.
EXPECT_EQ(16u, requests_seen());
EXPECT_EQ(
"a.test",
GetLastSeenReport().certificate_report(0).context().origin().hostname());
}
// Test that a cert error on the first attempt to send a report will trigger
// retries that succeed if the server starts using a good cert.
IN_PROC_BROWSER_TEST_F(SCTReportingServiceWithRetryAndPersistBrowserTest,
CertificateErrorTriggersRetry) {
{
// Override the retry delay to 1s so that the retries don't all happen
// immediately and the test can reset the default verifier result in
// between retry attempts.
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
network_service_test()->SetSCTAuditingRetryDelay(base::Seconds(1));
// Default test fixture teardown will reset the delay back to the default.
}
// The first request to the report server will trigger a certificate error via
// the mock cert verifier.
mock_cert_verifier()->set_default_result(net::ERR_CERT_COMMON_NAME_INVALID);
SetExtendedReportingEnabled(true);
// Visit an HTTPS page, which will trigger a report being sent to the report
// server but that report request will result in a cert error.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("a.test", "/")));
report_connection_listener()->WaitForConnections();
// After seeing one connection, replace the mock cert verifier result with a
// successful result.
mock_cert_verifier()->set_default_result(net::OK);
WaitForRequests(1);
// The second try should have resulted in the first successful report being
// seen by the HandleRequest() handler.
EXPECT_EQ(1u, requests_seen());
EXPECT_EQ(
"a.test",
GetLastSeenReport().certificate_report(0).context().origin().hostname());
}