Use log list timestamp to calculate timeliness.
This CL changes CT enforcement so the log list included timestamp is
used after a CT log list is updated via component updater, instead of
the build date.
Bug: 1199877
Change-Id: I74165814fa29f7ff5d148b28ab6aad6012fa027a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2946451
Commit-Queue: Carlos IL <carlosil@chromium.org>
Reviewed-by: Yutaka Hirano <yhirano@chromium.org>
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: Ryan Sleevi <rsleevi@chromium.org>
Reviewed-by: Joshua Pawlicki <waffles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#893099}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
c482676ff2
commit
1518adb0ef
chrome/browser
components/certificate_transparency
content/shell/browser
net/http
services/network
@ -193,7 +193,11 @@ void PKIMetadataComponentInstallerPolicy::UpdateNetworkServiceOnUI(
|
||||
log_list_mojo.push_back(std::move(log_ptr));
|
||||
}
|
||||
|
||||
network_service->UpdateCtLogList(std::move(log_list_mojo));
|
||||
base::Time update_time =
|
||||
base::Time::UnixEpoch() +
|
||||
base::TimeDelta::FromSeconds(proto->log_list().timestamp().seconds()) +
|
||||
base::TimeDelta::FromNanoseconds(proto->log_list().timestamp().nanos());
|
||||
network_service->UpdateCtLogList(std::move(log_list_mojo), update_time);
|
||||
#endif // BUILDFLAG(IS_CT_SUPPORTED)
|
||||
}
|
||||
|
||||
|
@ -526,7 +526,8 @@ void SystemNetworkContextManager::OnNetworkServiceCreated(
|
||||
}
|
||||
log_list_mojo.push_back(std::move(log_info));
|
||||
}
|
||||
network_service->UpdateCtLogList(std::move(log_list_mojo));
|
||||
network_service->UpdateCtLogList(std::move(log_list_mojo),
|
||||
base::GetBuildTime());
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -680,7 +681,6 @@ void SystemNetworkContextManager::ConfigureDefaultNetworkContextParams(
|
||||
|
||||
if (g_enable_certificate_transparency) {
|
||||
network_context_params->enforce_chrome_ct_policy = true;
|
||||
network_context_params->ct_log_update_time = base::GetBuildTime();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -142,8 +142,10 @@ CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCompliance(
|
||||
}
|
||||
|
||||
void ChromeCTPolicyEnforcer::UpdateCTLogList(
|
||||
base::Time update_time,
|
||||
std::vector<std::pair<std::string, base::TimeDelta>> disqualified_logs,
|
||||
std::vector<std::string> operated_by_google_logs) {
|
||||
log_list_date_ = update_time;
|
||||
disqualified_logs_ = std::move(disqualified_logs);
|
||||
operated_by_google_logs_ = std::move(operated_by_google_logs);
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ class ChromeCTPolicyEnforcer : public net::CTPolicyEnforcer {
|
||||
// a map of log ID to disqualification date. |operated_by_google_logs| is a
|
||||
// list of log IDs operated by Google
|
||||
void UpdateCTLogList(
|
||||
base::Time update_time,
|
||||
std::vector<std::pair<std::string, base::TimeDelta>> disqualified_logs,
|
||||
std::vector<std::string> operated_by_google_logs);
|
||||
|
||||
@ -95,7 +96,7 @@ class ChromeCTPolicyEnforcer : public net::CTPolicyEnforcer {
|
||||
|
||||
// The time at which |disqualified_logs_| and |operated_by_google_logs_| were
|
||||
// generated.
|
||||
const base::Time log_list_date_;
|
||||
base::Time log_list_date_;
|
||||
};
|
||||
|
||||
} // namespace certificate_transparency
|
||||
|
@ -480,7 +480,7 @@ TEST_F(ChromeCTPolicyEnforcerTest, UpdateCTLogList) {
|
||||
|
||||
std::vector<std::pair<std::string, base::TimeDelta>> disqualified_logs;
|
||||
std::vector<std::string> operated_by_google_logs;
|
||||
chrome_policy_enforcer->UpdateCTLogList(disqualified_logs,
|
||||
chrome_policy_enforcer->UpdateCTLogList(base::Time::Now(), disqualified_logs,
|
||||
operated_by_google_logs);
|
||||
|
||||
// The check should fail since the Google Aviator log is no longer in the
|
||||
@ -492,7 +492,7 @@ TEST_F(ChromeCTPolicyEnforcerTest, UpdateCTLogList) {
|
||||
// Update the list again, this time including all the known operated by Google
|
||||
// logs.
|
||||
operated_by_google_logs = certificate_transparency::GetLogsOperatedByGoogle();
|
||||
chrome_policy_enforcer->UpdateCTLogList(disqualified_logs,
|
||||
chrome_policy_enforcer->UpdateCTLogList(base::Time::Now(), disqualified_logs,
|
||||
operated_by_google_logs);
|
||||
|
||||
// The check should now succeed.
|
||||
@ -501,6 +501,37 @@ TEST_F(ChromeCTPolicyEnforcerTest, UpdateCTLogList) {
|
||||
NetLogWithSource()));
|
||||
}
|
||||
|
||||
TEST_F(ChromeCTPolicyEnforcerTest, TimestampUpdates) {
|
||||
ChromeCTPolicyEnforcer* chrome_policy_enforcer =
|
||||
static_cast<ChromeCTPolicyEnforcer*>(policy_enforcer_.get());
|
||||
SCTList scts;
|
||||
FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
|
||||
2, &scts);
|
||||
|
||||
// Clear the log list and set the last updated time to more than 10 weeks ago.
|
||||
std::vector<std::pair<std::string, base::TimeDelta>> disqualified_logs;
|
||||
std::vector<std::string> operated_by_google_logs;
|
||||
chrome_policy_enforcer->UpdateCTLogList(
|
||||
base::Time::Now() - base::TimeDelta::FromDays(71), disqualified_logs,
|
||||
operated_by_google_logs);
|
||||
|
||||
// The check should return build not timely even though the Google Aviator log
|
||||
// is no longer in the list, since the last update time is greater than 10
|
||||
// weeks.
|
||||
EXPECT_EQ(CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
|
||||
chrome_policy_enforcer->CheckCompliance(chain_.get(), scts,
|
||||
NetLogWithSource()));
|
||||
|
||||
// Update the last update time value again, this time with a recent time.
|
||||
chrome_policy_enforcer->UpdateCTLogList(base::Time::Now(), disqualified_logs,
|
||||
operated_by_google_logs);
|
||||
|
||||
// The check should now fail
|
||||
EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
|
||||
chrome_policy_enforcer->CheckCompliance(chain_.get(), scts,
|
||||
NetLogWithSource()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace certificate_transparency
|
||||
|
@ -61,6 +61,7 @@
|
||||
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
|
||||
#include "net/ssl/client_cert_identity.h"
|
||||
#include "services/device/public/cpp/geolocation/location_system_permission_status.h"
|
||||
#include "services/network/public/mojom/ct_log_info.mojom.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "services/network/public/mojom/network_service.mojom.h"
|
||||
#include "third_party/blink/public/common/features.h"
|
||||
@ -563,7 +564,6 @@ void ShellContentBrowserClient::ConfigureNetworkContextParamsForShell(
|
||||
|
||||
if (g_enable_expect_ct_for_testing) {
|
||||
context_params->enforce_chrome_ct_policy = true;
|
||||
context_params->ct_log_update_time = base::Time::Now();
|
||||
context_params->enable_expect_ct_reporting = true;
|
||||
}
|
||||
}
|
||||
@ -653,4 +653,19 @@ void ShellContentBrowserClient::SetUpFieldTrials() {
|
||||
&safe_seed_manager, absl::nullopt);
|
||||
}
|
||||
|
||||
void ShellContentBrowserClient::OnNetworkServiceCreated(
|
||||
network::mojom::NetworkService* network_service) {
|
||||
// Explicitly configure Certificate Transparency with no logs, but with
|
||||
// a fresh enough log update time that policy enforcement will still be
|
||||
// run. This does not use base::GetBuildTime(), as that may cause certain
|
||||
// checks to be disabled if too far in the past. Callers that set
|
||||
// `g_enable_expect_ct_reporting` are expected to simulate CT verification
|
||||
// using `MockCertVerifier` (otherwise CT validation would fail due to the
|
||||
// empty log list).
|
||||
if (g_enable_expect_ct_for_testing) {
|
||||
network_service->UpdateCtLogList(
|
||||
std::vector<network::mojom::CTLogInfoPtr>(), base::Time::Now());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace content
|
||||
|
@ -122,6 +122,8 @@ class ShellContentBrowserClient : public ContentBrowserClient {
|
||||
void GetHyphenationDictionary(
|
||||
base::OnceCallback<void(const base::FilePath&)>) override;
|
||||
bool HasErrorPage(int http_status_code) override;
|
||||
void OnNetworkServiceCreated(
|
||||
network::mojom::NetworkService* network_service) override;
|
||||
|
||||
void CreateFeatureListAndFieldTrials();
|
||||
|
||||
|
@ -393,7 +393,12 @@ void SetTransportSecurityStateSourceForTesting(
|
||||
}
|
||||
|
||||
TransportSecurityState::TransportSecurityState()
|
||||
: TransportSecurityState(std::vector<std::string>()) {}
|
||||
: TransportSecurityState(std::vector<std::string>()) {
|
||||
// By default the CT log list is treated as last updated at build time (since
|
||||
// a compiled-in list is used), this is overridden if the list is dynamically
|
||||
// updated.
|
||||
ct_log_list_last_update_time_ = base::GetBuildTime();
|
||||
}
|
||||
|
||||
TransportSecurityState::TransportSecurityState(
|
||||
std::vector<std::string> hsts_host_bypass_list)
|
||||
@ -607,6 +612,10 @@ void TransportSecurityState::SetRequireCTDelegate(RequireCTDelegate* delegate) {
|
||||
require_ct_delegate_ = delegate;
|
||||
}
|
||||
|
||||
void TransportSecurityState::SetCTLogListUpdateTime(base::Time update_time) {
|
||||
ct_log_list_last_update_time_ = update_time;
|
||||
}
|
||||
|
||||
void TransportSecurityState::AddHSTSInternal(
|
||||
const std::string& host,
|
||||
TransportSecurityState::STSState::UpgradeMode upgrade_mode,
|
||||
@ -773,7 +782,7 @@ bool TransportSecurityState::GetStaticExpectCTState(
|
||||
ExpectCTState* expect_ct_state) const {
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
|
||||
if (!IsBuildTimely())
|
||||
if (!IsCTLogListTimely())
|
||||
return false;
|
||||
|
||||
PreloadResult result;
|
||||
@ -1531,4 +1540,16 @@ bool TransportSecurityState::ExpectCTPruningSorter(
|
||||
it2->second.last_observed);
|
||||
}
|
||||
|
||||
bool TransportSecurityState::IsCTLogListTimely() const {
|
||||
// Preloaded Expect-CT is enforced if the CT log list is timely. Note that
|
||||
// unlike HSTS and HPKP, the date of the preloaded list itself (i.e.
|
||||
// base::GetBuildTime()) is not directly consulted. Consulting the
|
||||
// build time would allow sites that have subsequently disabled Expect-CT
|
||||
// to opt-out. However, because as of June 2021, all unexpired certificates
|
||||
// are already expected to comply with the policies expressed by Expect-CT,
|
||||
// there's no need to offer an opt-out.
|
||||
return (base::Time::Now() - ct_log_list_last_update_time_).InDays() <
|
||||
70 /* 10 weeks */;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
|
@ -440,6 +440,8 @@ class NET_EXPORT TransportSecurityState {
|
||||
ct_emergency_disable_ = emergency_disable;
|
||||
}
|
||||
|
||||
void SetCTLogListUpdateTime(base::Time update_time);
|
||||
|
||||
// Clears all dynamic data (e.g. HSTS and HPKP data).
|
||||
//
|
||||
// Does NOT persist changes using the Delegate, as this function is only
|
||||
@ -691,6 +693,9 @@ class NET_EXPORT TransportSecurityState {
|
||||
static bool ExpectCTPruningSorter(const ExpectCTStateMap::iterator& it1,
|
||||
const ExpectCTStateMap::iterator& it2);
|
||||
|
||||
// Returns true if the CT log list has been updated in the last 10 weeks.
|
||||
bool IsCTLogListTimely() const;
|
||||
|
||||
// The sets of hosts that have enabled TransportSecurity. |domain| will always
|
||||
// be empty for a STSState, PKPState, or ExpectCTState in these maps; the
|
||||
// domain comes from the map keys instead. In addition, |upgrade_mode| in the
|
||||
@ -736,6 +741,8 @@ class NET_EXPORT TransportSecurityState {
|
||||
|
||||
bool ct_emergency_disable_ = false;
|
||||
|
||||
base::Time ct_log_list_last_update_time_;
|
||||
|
||||
THREAD_CHECKER(thread_checker_);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TransportSecurityState);
|
||||
|
@ -1804,6 +1804,34 @@ TEST_F(TransportSecurityStateTest, CTEmergencyDisable) {
|
||||
NetworkIsolationKey()));
|
||||
}
|
||||
|
||||
// Tests that the if the CT log list last update time is set, it is used for
|
||||
// enforcement decisions.
|
||||
TEST_F(TransportSecurityStateTest, CTTimestampUpdate) {
|
||||
TransportSecurityState state;
|
||||
TransportSecurityStateTest::EnableStaticExpectCT(&state);
|
||||
TransportSecurityState::ExpectCTState expect_ct_state;
|
||||
// Initially the preloaded host should require CT.
|
||||
EXPECT_TRUE(
|
||||
GetExpectCTState(&state, kExpectCTStaticHostname, &expect_ct_state));
|
||||
|
||||
// Change the last updated time to a value greater than 10 weeks.
|
||||
// We use a close value (70 days + 1 hour ago) to ensure rounding behavior is
|
||||
// working properly.
|
||||
state.SetCTLogListUpdateTime(
|
||||
base::Time::Now() -
|
||||
(base::TimeDelta::FromDays(70) + base::TimeDelta::FromHours(1)));
|
||||
// CT should no longer be required.
|
||||
EXPECT_FALSE(
|
||||
GetExpectCTState(&state, kExpectCTStaticHostname, &expect_ct_state));
|
||||
|
||||
// CT should once again be required after the log list is newer than 70 days.
|
||||
state.SetCTLogListUpdateTime(
|
||||
base::Time::Now() -
|
||||
(base::TimeDelta::FromDays(70) - base::TimeDelta::FromHours(1)));
|
||||
EXPECT_TRUE(
|
||||
GetExpectCTState(&state, kExpectCTStaticHostname, &expect_ct_state));
|
||||
}
|
||||
|
||||
// Tests that Certificate Transparency is required for Symantec-issued
|
||||
// certificates, unless the certificate was issued prior to 1 June 2016
|
||||
// or the issuing CA is permitted as independently operated.
|
||||
|
@ -1274,17 +1274,18 @@ void NetworkContext::SetSCTAuditingEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
void NetworkContext::OnCTLogListUpdated(
|
||||
const std::vector<network::mojom::CTLogInfoPtr>& log_list) {
|
||||
const std::vector<network::mojom::CTLogInfoPtr>& log_list,
|
||||
base::Time update_time) {
|
||||
if (!ct_policy_enforcer_)
|
||||
return;
|
||||
std::vector<std::pair<std::string, base::TimeDelta>> disqualified_logs;
|
||||
std::vector<std::string> operated_by_google_logs;
|
||||
GetCTPolicyConfigForCTLogInfo(log_list, &disqualified_logs,
|
||||
&operated_by_google_logs);
|
||||
ct_policy_enforcer_->UpdateCTLogList(std::move(disqualified_logs),
|
||||
ct_policy_enforcer_->UpdateCTLogList(update_time,
|
||||
std::move(disqualified_logs),
|
||||
std::move(operated_by_google_logs));
|
||||
}
|
||||
|
||||
#endif // BUILDFLAG(IS_CT_SUPPORTED)
|
||||
|
||||
void NetworkContext::CreateUDPSocket(
|
||||
@ -1995,10 +1996,9 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext(
|
||||
&disqualified_logs, &operated_by_google_logs);
|
||||
auto ct_policy_enforcer =
|
||||
std::make_unique<certificate_transparency::ChromeCTPolicyEnforcer>(
|
||||
params_->ct_log_update_time, std::move(disqualified_logs),
|
||||
std::move(operated_by_google_logs));
|
||||
network_service_->ct_log_list_update_time(),
|
||||
std::move(disqualified_logs), std::move(operated_by_google_logs));
|
||||
ct_policy_enforcer_ = ct_policy_enforcer.get();
|
||||
|
||||
builder.set_ct_policy_enforcer(std::move(ct_policy_enforcer));
|
||||
}
|
||||
|
||||
|
@ -278,7 +278,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
|
||||
signed_certificate_timestamps);
|
||||
void SetSCTAuditingEnabled(bool enabled) override;
|
||||
void OnCTLogListUpdated(
|
||||
const std::vector<network::mojom::CTLogInfoPtr>& log_list);
|
||||
const std::vector<network::mojom::CTLogInfoPtr>& log_list,
|
||||
base::Time update_time);
|
||||
bool is_sct_auditing_enabled() { return is_sct_auditing_enabled_; }
|
||||
#endif // BUILDFLAG(IS_CT_SUPPORTED)
|
||||
void CreateUDPSocket(
|
||||
|
@ -6078,12 +6078,12 @@ TEST_F(NetworkContextTest, CertificateTransparencyConfig) {
|
||||
|
||||
log_list_mojo.push_back(std::move(log_info));
|
||||
}
|
||||
network_service()->UpdateCtLogList(std::move(log_list_mojo));
|
||||
network_service()->UpdateCtLogList(std::move(log_list_mojo),
|
||||
base::Time::Now());
|
||||
|
||||
// Configure CT params in network context.
|
||||
mojom::NetworkContextParamsPtr params = CreateContextParams();
|
||||
params->enforce_chrome_ct_policy = true;
|
||||
params->ct_log_update_time = base::Time::Now();
|
||||
|
||||
std::unique_ptr<NetworkContext> network_context =
|
||||
CreateContextWithParams(std::move(params));
|
||||
|
@ -730,15 +730,20 @@ void NetworkService::ConfigureSCTAuditing(
|
||||
sct_auditing_cache_->set_url_loader_factory(std::move(factory));
|
||||
}
|
||||
|
||||
void NetworkService::UpdateCtLogList(
|
||||
std::vector<mojom::CTLogInfoPtr> log_list) {
|
||||
void NetworkService::UpdateCtLogList(std::vector<mojom::CTLogInfoPtr> log_list,
|
||||
base::Time update_time) {
|
||||
log_list_ = std::move(log_list);
|
||||
ct_log_list_update_time_ = update_time;
|
||||
|
||||
if (base::FeatureList::IsEnabled(
|
||||
certificate_transparency::features::
|
||||
kCertificateTransparencyComponentUpdater)) {
|
||||
ct_log_list_distributor_->OnNewCtConfig(log_list_);
|
||||
for (auto* context : network_contexts_) {
|
||||
context->OnCTLogListUpdated(log_list_);
|
||||
context->OnCTLogListUpdated(log_list_, update_time);
|
||||
context->url_request_context()
|
||||
->transport_security_state()
|
||||
->SetCTLogListUpdateTime(update_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,7 +192,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService
|
||||
const GURL& reporting_uri,
|
||||
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
|
||||
mojo::PendingRemote<mojom::URLLoaderFactory> factory) override;
|
||||
void UpdateCtLogList(std::vector<mojom::CTLogInfoPtr> log_list) override;
|
||||
void UpdateCtLogList(std::vector<mojom::CTLogInfoPtr> log_list,
|
||||
base::Time update_time) override;
|
||||
void SetCtEnforcementEnabled(bool enabled) override;
|
||||
#endif
|
||||
|
||||
@ -268,6 +269,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService
|
||||
SCTAuditingCache* sct_auditing_cache() { return sct_auditing_cache_.get(); }
|
||||
|
||||
const std::vector<mojom::CTLogInfoPtr>& log_list() const { return log_list_; }
|
||||
|
||||
base::Time ct_log_list_update_time() const {
|
||||
return ct_log_list_update_time_;
|
||||
}
|
||||
#endif
|
||||
|
||||
mojom::URLLoaderNetworkServiceObserver*
|
||||
@ -374,6 +379,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService
|
||||
std::vector<mojom::CTLogInfoPtr> log_list_;
|
||||
|
||||
std::unique_ptr<CtLogListDistributor> ct_log_list_distributor_;
|
||||
|
||||
base::Time ct_log_list_update_time_;
|
||||
#endif
|
||||
|
||||
// Map from a renderer process id, to the set of plugin origins embedded by
|
||||
|
@ -365,13 +365,6 @@ struct NetworkContextParams {
|
||||
[EnableIf=is_ct_supported]
|
||||
CTPolicy? ct_policy;
|
||||
|
||||
// When the built-in Certificate Transparency logs were last updated. If
|
||||
// |enforce_chrome_ct_policy| is set, and |ct_log_update_time| is not
|
||||
// sufficiently recent, enforcement of the "Certificate Transparency in
|
||||
// Chrome" policy will be disabled.
|
||||
[EnableIf=is_ct_supported]
|
||||
mojo_base.mojom.Time ct_log_update_time;
|
||||
|
||||
// Contains a pipe to a CertVerifierService.
|
||||
CertVerifierServiceRemoteParams cert_verifier_params;
|
||||
|
||||
|
@ -343,9 +343,12 @@ interface NetworkService {
|
||||
pending_remote<network.mojom.URLLoaderFactory> factory);
|
||||
|
||||
|
||||
// Updates the log list used for CT verification.
|
||||
// Updates the log list used for CT verification. `update_time` should
|
||||
// contain the timestamp that the log list was considered fresh/current, in
|
||||
// order to ensure that CT is not enforced if the log list is too out of
|
||||
// date.
|
||||
[EnableIf=is_ct_supported]
|
||||
UpdateCtLogList(array<CTLogInfo> log_list);
|
||||
UpdateCtLogList(array<CTLogInfo> log_list, mojo_base.mojom.Time update_time);
|
||||
|
||||
// Disables or enables CT Enforcement.
|
||||
[EnableIf=is_ct_supported]
|
||||
|
Reference in New Issue
Block a user