Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(219)

Unified Diff: net/http/transport_security_persister.cc

Issue 2751803002: Serialize and deserialize dynamic Expect-CT state (Closed)
Patch Set: add test for LoadEntries clearing Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/http/transport_security_persister.h ('k') | net/http/transport_security_persister_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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());
« no previous file with comments | « net/http/transport_security_persister.h ('k') | net/http/transport_security_persister_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698