Index: net/http/transport_security_persister.cc |
diff --git a/net/http/transport_security_persister.cc b/net/http/transport_security_persister.cc |
index a37d2379db2993f847c9461dc42e8ef8d49fd30f..8938b27daf53dfb9dc34b37cfa38bf22093bedef 100644 |
--- a/net/http/transport_security_persister.cc |
+++ b/net/http/transport_security_persister.cc |
@@ -9,6 +9,7 @@ |
#include "base/base64.h" |
#include "base/bind.h" |
+#include "base/feature_list.h" |
#include "base/files/file_path.h" |
#include "base/files/file_util.h" |
#include "base/json/json_reader.h" |
@@ -81,6 +82,13 @@ const char kCreated[] = "created"; |
const char kStsObserved[] = "sts_observed"; |
const char kPkpObserved[] = "pkp_observed"; |
const char kReportUri[] = "report-uri"; |
+// The keys below are contained in a subdictionary keyed as |
+// |kExpectCTSubdictionary|. |
+const char kExpectCTSubdictionary[] = "expect_ct"; |
+const char kExpectCTExpiry[] = "expect_ct_expiry"; |
+const char kExpectCTObserved[] = "expect_ct_observed"; |
+const char kExpectCTEnforce[] = "expect_ct_enforce"; |
+const char kExpectCTReportUri[] = "expect_ct_report_uri"; |
std::string LoadState(const base::FilePath& path) { |
std::string result; |
@@ -90,56 +98,37 @@ std::string LoadState(const base::FilePath& path) { |
return result; |
} |
-} // namespace |
- |
-TransportSecurityPersister::TransportSecurityPersister( |
- TransportSecurityState* state, |
- const base::FilePath& profile_path, |
- const scoped_refptr<base::SequencedTaskRunner>& background_runner, |
- bool readonly) |
- : transport_security_state_(state), |
- writer_(profile_path.AppendASCII("TransportSecurity"), background_runner), |
- foreground_runner_(base::ThreadTaskRunnerHandle::Get()), |
- background_runner_(background_runner), |
- readonly_(readonly), |
- weak_ptr_factory_(this) { |
- transport_security_state_->SetDelegate(this); |
- |
- base::PostTaskAndReplyWithResult( |
- background_runner_.get(), FROM_HERE, |
- base::Bind(&LoadState, writer_.path()), |
- base::Bind(&TransportSecurityPersister::CompleteLoad, |
- weak_ptr_factory_.GetWeakPtr())); |
+bool IsDynamicExpectCTEnabled() { |
+ return base::FeatureList::IsEnabled( |
+ TransportSecurityState::kDynamicExpectCTFeature); |
} |
-TransportSecurityPersister::~TransportSecurityPersister() { |
- DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); |
- |
- if (writer_.HasPendingWrite()) |
- writer_.DoScheduledWrite(); |
- |
- transport_security_state_->SetDelegate(NULL); |
-} |
+// Populates |host| with default values for the STS and PKP states. |
+// These default values represent "null" states and are only useful to keep |
+// the entries in the resulting JSON consistent. The deserializer will ignore |
+// "null" states. |
+// TODO(davidben): This can be removed when the STS and PKP states are stored |
+// independently on disk. https://crbug.com/470295 |
+void PopulateEntryWithDefaults(base::DictionaryValue* host) { |
+ host->Clear(); |
-void TransportSecurityPersister::StateIsDirty( |
- TransportSecurityState* state) { |
- DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); |
- DCHECK_EQ(transport_security_state_, state); |
+ // STS default values. |
+ host->SetBoolean(kStsIncludeSubdomains, false); |
+ host->SetDouble(kStsObserved, 0.0); |
+ host->SetDouble(kExpiry, 0.0); |
+ host->SetString(kMode, kDefault); |
- if (!readonly_) |
- writer_.ScheduleWrite(this); |
+ // PKP default values. |
+ host->SetBoolean(kPkpIncludeSubdomains, false); |
+ host->SetDouble(kPkpObserved, 0.0); |
+ host->SetDouble(kDynamicSPKIHashesExpiry, 0.0); |
} |
-bool TransportSecurityPersister::SerializeData(std::string* output) { |
- DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); |
- |
- base::DictionaryValue toplevel; |
- base::Time now = base::Time::Now(); |
- |
- // TODO(davidben): Fix the serialization format by splitting the on-disk |
- // representation of the STS and PKP states. https://crbug.com/470295. |
- TransportSecurityState::STSStateIterator sts_iterator( |
- *transport_security_state_); |
+// Serializes STS data from |state| into |toplevel|. Any existing state in |
+// |toplevel| for each item is overwritten. |
+void SerializeSTSData(TransportSecurityState* state, |
+ base::DictionaryValue* toplevel) { |
+ TransportSecurityState::STSStateIterator sts_iterator(*state); |
for (; sts_iterator.HasNext(); sts_iterator.Advance()) { |
const std::string& hostname = sts_iterator.hostname(); |
const TransportSecurityState::STSState& sts_state = |
@@ -166,11 +155,17 @@ bool TransportSecurityPersister::SerializeData(std::string* output) { |
continue; |
} |
- toplevel.Set(key, std::move(serialized)); |
+ toplevel->Set(key, std::move(serialized)); |
} |
+} |
- TransportSecurityState::PKPStateIterator pkp_iterator( |
- *transport_security_state_); |
+// Serializes PKP data from |state| into |toplevel|. For each PKP item in |
+// |state|, if |toplevel| already contains an item for that hostname, the item |
+// is updated with the PKP data. |
+void SerializePKPData(TransportSecurityState* state, |
+ base::DictionaryValue* toplevel) { |
+ base::Time now = base::Time::Now(); |
+ TransportSecurityState::PKPStateIterator pkp_iterator(*state); |
for (; pkp_iterator.HasNext(); pkp_iterator.Advance()) { |
const std::string& hostname = pkp_iterator.hostname(); |
const TransportSecurityState::PKPState& pkp_state = |
@@ -180,12 +175,12 @@ bool TransportSecurityPersister::SerializeData(std::string* output) { |
// that entry. |
const std::string key = HashedDomainToExternalString(hostname); |
base::DictionaryValue* serialized = nullptr; |
- if (!toplevel.GetDictionary(key, &serialized)) { |
+ if (!toplevel->GetDictionary(key, &serialized)) { |
std::unique_ptr<base::DictionaryValue> serialized_scoped( |
new base::DictionaryValue); |
serialized = serialized_scoped.get(); |
PopulateEntryWithDefaults(serialized); |
- toplevel.Set(key, std::move(serialized_scoped)); |
+ toplevel->Set(key, std::move(serialized_scoped)); |
} |
serialized->SetBoolean(kPkpIncludeSubdomains, pkp_state.include_subdomains); |
@@ -205,6 +200,138 @@ bool TransportSecurityPersister::SerializeData(std::string* output) { |
serialized->SetString(kReportUri, pkp_state.report_uri.spec()); |
} |
+} |
+ |
+// Serializes Expect-CT data from |state| into |toplevel|. For each Expect-CT |
+// item in |state|, if |toplevel| already contains an item for that hostname, |
+// the item is updated to include a subdictionary with key |
+// |kExpectCTSubdictionary|; otherwise an item is created for that hostname with |
+// a |kExpectCTSubdictionary| subdictionary. |
+void SerializeExpectCTData(TransportSecurityState* state, |
+ base::DictionaryValue* toplevel) { |
+ if (!IsDynamicExpectCTEnabled()) |
+ return; |
+ TransportSecurityState::ExpectCTStateIterator expect_ct_iterator(*state); |
+ for (; expect_ct_iterator.HasNext(); expect_ct_iterator.Advance()) { |
+ const std::string& hostname = expect_ct_iterator.hostname(); |
+ const TransportSecurityState::ExpectCTState& expect_ct_state = |
+ expect_ct_iterator.domain_state(); |
+ |
+ // See if the current |hostname| already has STS/PKP state and, if so, |
+ // update that entry. |
+ const std::string key = HashedDomainToExternalString(hostname); |
+ base::DictionaryValue* serialized = nullptr; |
+ if (!toplevel->GetDictionary(key, &serialized)) { |
+ std::unique_ptr<base::DictionaryValue> serialized_scoped( |
+ new base::DictionaryValue); |
+ serialized = serialized_scoped.get(); |
+ PopulateEntryWithDefaults(serialized); |
+ toplevel->Set(key, std::move(serialized_scoped)); |
+ } |
+ |
+ std::unique_ptr<base::DictionaryValue> expect_ct_subdictionary( |
+ new base::DictionaryValue()); |
+ expect_ct_subdictionary->SetDouble( |
+ kExpectCTObserved, expect_ct_state.last_observed.ToDoubleT()); |
+ expect_ct_subdictionary->SetDouble(kExpectCTExpiry, |
+ expect_ct_state.expiry.ToDoubleT()); |
+ expect_ct_subdictionary->SetBoolean(kExpectCTEnforce, |
+ expect_ct_state.enforce); |
+ expect_ct_subdictionary->SetString(kExpectCTReportUri, |
+ expect_ct_state.report_uri.spec()); |
+ serialized->Set(kExpectCTSubdictionary, std::move(expect_ct_subdictionary)); |
+ } |
+} |
+ |
+// Populates |state| with the values in the |kExpectCTSubdictionary| |
+// subdictionary in |parsed|. Returns false if |parsed| is malformed |
+// (e.g. missing a required Expect-CT key) and true otherwise. Note that true |
+// does not necessarily mean that Expect-CT state was present in |parsed|. |
+bool DeserializeExpectCTState(const base::DictionaryValue* parsed, |
+ TransportSecurityState::ExpectCTState* state) { |
+ const base::DictionaryValue* expect_ct_subdictionary; |
+ if (!parsed->GetDictionary(kExpectCTSubdictionary, |
+ &expect_ct_subdictionary)) { |
+ // Expect-CT data is not required, so this item is not malformed. |
+ return true; |
+ } |
+ double observed; |
+ bool has_observed = |
+ expect_ct_subdictionary->GetDouble(kExpectCTObserved, &observed); |
+ double expiry; |
+ bool has_expiry = |
+ expect_ct_subdictionary->GetDouble(kExpectCTExpiry, &expiry); |
+ bool enforce; |
+ bool has_enforce = |
+ expect_ct_subdictionary->GetBoolean(kExpectCTEnforce, &enforce); |
+ std::string report_uri_str; |
+ bool has_report_uri = |
+ expect_ct_subdictionary->GetString(kExpectCTReportUri, &report_uri_str); |
+ |
+ // If an Expect-CT subdictionary is present, it must have the required keys. |
+ if (!has_observed || !has_expiry || !has_enforce) |
+ return false; |
+ |
+ state->last_observed = base::Time::FromDoubleT(observed); |
+ state->expiry = base::Time::FromDoubleT(expiry); |
+ state->enforce = enforce; |
+ if (has_report_uri) { |
+ GURL report_uri(report_uri_str); |
+ if (report_uri.is_valid()) |
+ state->report_uri = report_uri; |
+ } |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+TransportSecurityPersister::TransportSecurityPersister( |
+ TransportSecurityState* state, |
+ const base::FilePath& profile_path, |
+ const scoped_refptr<base::SequencedTaskRunner>& background_runner, |
+ bool readonly) |
+ : transport_security_state_(state), |
+ writer_(profile_path.AppendASCII("TransportSecurity"), background_runner), |
+ foreground_runner_(base::ThreadTaskRunnerHandle::Get()), |
+ background_runner_(background_runner), |
+ readonly_(readonly), |
+ weak_ptr_factory_(this) { |
+ transport_security_state_->SetDelegate(this); |
+ |
+ base::PostTaskAndReplyWithResult( |
+ background_runner_.get(), FROM_HERE, |
+ base::Bind(&LoadState, writer_.path()), |
+ base::Bind(&TransportSecurityPersister::CompleteLoad, |
+ weak_ptr_factory_.GetWeakPtr())); |
+} |
+ |
+TransportSecurityPersister::~TransportSecurityPersister() { |
+ DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); |
+ |
+ if (writer_.HasPendingWrite()) |
+ writer_.DoScheduledWrite(); |
+ |
+ transport_security_state_->SetDelegate(NULL); |
+} |
+ |
+void TransportSecurityPersister::StateIsDirty(TransportSecurityState* state) { |
+ DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); |
+ DCHECK_EQ(transport_security_state_, state); |
+ |
+ if (!readonly_) |
+ writer_.ScheduleWrite(this); |
+} |
+ |
+bool TransportSecurityPersister::SerializeData(std::string* output) { |
+ DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); |
+ |
+ base::DictionaryValue toplevel; |
+ |
+ // TODO(davidben): Fix the serialization format by splitting the on-disk |
+ // representation of the STS and PKP states. https://crbug.com/470295. |
+ SerializeSTSData(transport_security_state_, &toplevel); |
+ SerializePKPData(transport_security_state_, &toplevel); |
+ SerializeExpectCTData(transport_security_state_, &toplevel); |
base::JSONWriter::WriteWithOptions( |
toplevel, base::JSONWriter::OPTIONS_PRETTY_PRINT, output); |
@@ -241,6 +368,7 @@ bool TransportSecurityPersister::Deserialize(const std::string& serialized, |
TransportSecurityState::STSState sts_state; |
TransportSecurityState::PKPState pkp_state; |
+ TransportSecurityState::ExpectCTState expect_ct_state; |
// kIncludeSubdomains is a legacy synonym for kStsIncludeSubdomains and |
// kPkpIncludeSubdomains. Parse at least one of these properties, |
@@ -323,13 +451,21 @@ bool TransportSecurityPersister::Deserialize(const std::string& serialized, |
pkp_state.last_observed = base::Time::Now(); |
} |
+ if (!DeserializeExpectCTState(parsed, &expect_ct_state)) { |
+ continue; |
+ } |
+ |
bool has_sts = |
sts_state.expiry > current_time && sts_state.ShouldUpgradeToSSL(); |
bool has_pkp = |
pkp_state.expiry > current_time && pkp_state.HasPublicKeyPins(); |
- if (!has_sts && !has_pkp) { |
+ bool has_expect_ct = |
+ expect_ct_state.expiry > current_time && |
+ (expect_ct_state.enforce || !expect_ct_state.report_uri.is_empty()); |
+ if (!has_sts && !has_pkp && !has_expect_ct) { |
// Make sure we dirty the state if we drop an entry. The entries can only |
- // be dropped when both the STS and PKP states are expired or invalid. |
+ // be dropped when all the STS, PKP, and Expect-CT states are expired or |
+ // invalid. |
dirtied = true; |
continue; |
} |
@@ -346,28 +482,14 @@ bool TransportSecurityPersister::Deserialize(const std::string& serialized, |
state->AddOrUpdateEnabledSTSHosts(hashed, sts_state); |
if (has_pkp) |
state->AddOrUpdateEnabledPKPHosts(hashed, pkp_state); |
+ if (has_expect_ct) |
+ state->AddOrUpdateEnabledExpectCTHosts(hashed, expect_ct_state); |
} |
*dirty = dirtied; |
return true; |
} |
-void TransportSecurityPersister::PopulateEntryWithDefaults( |
- base::DictionaryValue* host) { |
- host->Clear(); |
- |
- // STS default values. |
- host->SetBoolean(kStsIncludeSubdomains, false); |
- host->SetDouble(kStsObserved, 0.0); |
- host->SetDouble(kExpiry, 0.0); |
- host->SetString(kMode, kDefault); |
- |
- // PKP default values. |
- host->SetBoolean(kPkpIncludeSubdomains, false); |
- host->SetDouble(kPkpObserved, 0.0); |
- host->SetDouble(kDynamicSPKIHashesExpiry, 0.0); |
-} |
- |
void TransportSecurityPersister::CompleteLoad(const std::string& state) { |
DCHECK(foreground_runner_->RunsTasksOnCurrentThread()); |