Give mojo_shell a TransportSecurityPersister.
This required moving TransportSecurityPersister into net.
BUG=310293
[email protected], [email protected]
Review URL: https://codereview.chromium.org/59693008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@234667 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/http/transport_security_persister.cc b/net/http/transport_security_persister.cc
new file mode 100644
index 0000000..a7e72fd3
--- /dev/null
+++ b/net/http/transport_security_persister.cc
@@ -0,0 +1,315 @@
+// Copyright (c) 2012 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 "net/http/transport_security_persister.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task_runner_util.h"
+#include "base/values.h"
+#include "crypto/sha2.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/transport_security_state.h"
+
+using net::HashValue;
+using net::HashValueTag;
+using net::HashValueVector;
+using net::TransportSecurityState;
+
+namespace {
+
+ListValue* SPKIHashesToListValue(const HashValueVector& hashes) {
+ ListValue* pins = new ListValue;
+ for (size_t i = 0; i != hashes.size(); i++)
+ pins->Append(new StringValue(hashes[i].ToString()));
+ return pins;
+}
+
+void SPKIHashesFromListValue(const ListValue& pins, HashValueVector* hashes) {
+ size_t num_pins = pins.GetSize();
+ for (size_t i = 0; i < num_pins; ++i) {
+ std::string type_and_base64;
+ HashValue fingerprint;
+ if (pins.GetString(i, &type_and_base64) &&
+ fingerprint.FromString(type_and_base64)) {
+ hashes->push_back(fingerprint);
+ }
+ }
+}
+
+// This function converts the binary hashes to a base64 string which we can
+// include in a JSON file.
+std::string HashedDomainToExternalString(const std::string& hashed) {
+ std::string out;
+ base::Base64Encode(hashed, &out);
+ return out;
+}
+
+// This inverts |HashedDomainToExternalString|, above. It turns an external
+// string (from a JSON file) into an internal (binary) string.
+std::string ExternalStringToHashedDomain(const std::string& external) {
+ std::string out;
+ if (!base::Base64Decode(external, &out) ||
+ out.size() != crypto::kSHA256Length) {
+ return std::string();
+ }
+
+ return out;
+}
+
+const char kIncludeSubdomains[] = "include_subdomains";
+const char kStsIncludeSubdomains[] = "sts_include_subdomains";
+const char kPkpIncludeSubdomains[] = "pkp_include_subdomains";
+const char kMode[] = "mode";
+const char kExpiry[] = "expiry";
+const char kDynamicSPKIHashesExpiry[] = "dynamic_spki_hashes_expiry";
+const char kStaticSPKIHashes[] = "static_spki_hashes";
+const char kPreloadedSPKIHashes[] = "preloaded_spki_hashes";
+const char kDynamicSPKIHashes[] = "dynamic_spki_hashes";
+const char kForceHTTPS[] = "force-https";
+const char kStrict[] = "strict";
+const char kDefault[] = "default";
+const char kPinningOnly[] = "pinning-only";
+const char kCreated[] = "created";
+
+std::string LoadState(const base::FilePath& path) {
+ std::string result;
+ if (!base::ReadFileToString(path, &result)) {
+ return "";
+ }
+ return result;
+}
+
+} // namespace
+
+
+namespace net {
+
+TransportSecurityPersister::TransportSecurityPersister(
+ TransportSecurityState* state,
+ const base::FilePath& profile_path,
+ base::SequencedTaskRunner* background_runner,
+ bool readonly)
+ : transport_security_state_(state),
+ writer_(profile_path.AppendASCII("TransportSecurity"), background_runner),
+ foreground_runner_(base::MessageLoop::current()->message_loop_proxy()),
+ background_runner_(background_runner),
+ readonly_(readonly),
+ weak_ptr_factory_(this) {
+ transport_security_state_->SetDelegate(this);
+
+ base::PostTaskAndReplyWithResult(
+ background_runner_,
+ 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());
+
+ DictionaryValue toplevel;
+ base::Time now = base::Time::Now();
+ TransportSecurityState::Iterator state(*transport_security_state_);
+ for (; state.HasNext(); state.Advance()) {
+ const std::string& hostname = state.hostname();
+ const TransportSecurityState::DomainState& domain_state =
+ state.domain_state();
+
+ DictionaryValue* serialized = new DictionaryValue;
+ serialized->SetBoolean(kStsIncludeSubdomains,
+ domain_state.sts_include_subdomains);
+ serialized->SetBoolean(kPkpIncludeSubdomains,
+ domain_state.pkp_include_subdomains);
+ serialized->SetDouble(kCreated, domain_state.created.ToDoubleT());
+ serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT());
+ serialized->SetDouble(kDynamicSPKIHashesExpiry,
+ domain_state.dynamic_spki_hashes_expiry.ToDoubleT());
+
+ switch (domain_state.upgrade_mode) {
+ case TransportSecurityState::DomainState::MODE_FORCE_HTTPS:
+ serialized->SetString(kMode, kForceHTTPS);
+ break;
+ case TransportSecurityState::DomainState::MODE_DEFAULT:
+ serialized->SetString(kMode, kDefault);
+ break;
+ default:
+ NOTREACHED() << "DomainState with unknown mode";
+ delete serialized;
+ continue;
+ }
+
+ serialized->Set(kStaticSPKIHashes,
+ SPKIHashesToListValue(domain_state.static_spki_hashes));
+
+ if (now < domain_state.dynamic_spki_hashes_expiry) {
+ serialized->Set(kDynamicSPKIHashes,
+ SPKIHashesToListValue(domain_state.dynamic_spki_hashes));
+ }
+
+ toplevel.Set(HashedDomainToExternalString(hostname), serialized);
+ }
+
+ base::JSONWriter::WriteWithOptions(&toplevel,
+ base::JSONWriter::OPTIONS_PRETTY_PRINT,
+ output);
+ return true;
+}
+
+bool TransportSecurityPersister::LoadEntries(const std::string& serialized,
+ bool* dirty) {
+ DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
+
+ transport_security_state_->ClearDynamicData();
+ return Deserialize(serialized, dirty, transport_security_state_);
+}
+
+// static
+bool TransportSecurityPersister::Deserialize(const std::string& serialized,
+ bool* dirty,
+ TransportSecurityState* state) {
+ scoped_ptr<Value> value(base::JSONReader::Read(serialized));
+ DictionaryValue* dict_value = NULL;
+ if (!value.get() || !value->GetAsDictionary(&dict_value))
+ return false;
+
+ const base::Time current_time(base::Time::Now());
+ bool dirtied = false;
+
+ for (DictionaryValue::Iterator i(*dict_value); !i.IsAtEnd(); i.Advance()) {
+ const DictionaryValue* parsed = NULL;
+ if (!i.value().GetAsDictionary(&parsed)) {
+ LOG(WARNING) << "Could not parse entry " << i.key() << "; skipping entry";
+ continue;
+ }
+
+ std::string mode_string;
+ double created;
+ double expiry;
+ double dynamic_spki_hashes_expiry = 0.0;
+ TransportSecurityState::DomainState domain_state;
+
+ // kIncludeSubdomains is a legacy synonym for kStsIncludeSubdomains and
+ // kPkpIncludeSubdomains. Parse at least one of these properties,
+ // preferably the new ones.
+ bool include_subdomains = false;
+ bool parsed_include_subdomains = parsed->GetBoolean(kIncludeSubdomains,
+ &include_subdomains);
+ domain_state.sts_include_subdomains = include_subdomains;
+ domain_state.pkp_include_subdomains = include_subdomains;
+ if (parsed->GetBoolean(kStsIncludeSubdomains, &include_subdomains)) {
+ domain_state.sts_include_subdomains = include_subdomains;
+ parsed_include_subdomains = true;
+ }
+ if (parsed->GetBoolean(kPkpIncludeSubdomains, &include_subdomains)) {
+ domain_state.pkp_include_subdomains = include_subdomains;
+ parsed_include_subdomains = true;
+ }
+
+ if (!parsed_include_subdomains ||
+ !parsed->GetString(kMode, &mode_string) ||
+ !parsed->GetDouble(kExpiry, &expiry)) {
+ LOG(WARNING) << "Could not parse some elements of entry " << i.key()
+ << "; skipping entry";
+ continue;
+ }
+
+ // Don't fail if this key is not present.
+ parsed->GetDouble(kDynamicSPKIHashesExpiry,
+ &dynamic_spki_hashes_expiry);
+
+ const ListValue* pins_list = NULL;
+ // preloaded_spki_hashes is a legacy synonym for static_spki_hashes.
+ if (parsed->GetList(kStaticSPKIHashes, &pins_list))
+ SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes);
+ else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list))
+ SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes);
+
+ if (parsed->GetList(kDynamicSPKIHashes, &pins_list))
+ SPKIHashesFromListValue(*pins_list, &domain_state.dynamic_spki_hashes);
+
+ if (mode_string == kForceHTTPS || mode_string == kStrict) {
+ domain_state.upgrade_mode =
+ TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
+ } else if (mode_string == kDefault || mode_string == kPinningOnly) {
+ domain_state.upgrade_mode =
+ TransportSecurityState::DomainState::MODE_DEFAULT;
+ } else {
+ LOG(WARNING) << "Unknown TransportSecurityState mode string "
+ << mode_string << " found for entry " << i.key()
+ << "; skipping entry";
+ continue;
+ }
+
+ domain_state.upgrade_expiry = base::Time::FromDoubleT(expiry);
+ domain_state.dynamic_spki_hashes_expiry =
+ base::Time::FromDoubleT(dynamic_spki_hashes_expiry);
+ if (parsed->GetDouble(kCreated, &created)) {
+ domain_state.created = base::Time::FromDoubleT(created);
+ } else {
+ // We're migrating an old entry with no creation date. Make sure we
+ // write the new date back in a reasonable time frame.
+ dirtied = true;
+ domain_state.created = base::Time::Now();
+ }
+
+ if (domain_state.upgrade_expiry <= current_time &&
+ domain_state.dynamic_spki_hashes_expiry <= current_time) {
+ // Make sure we dirty the state if we drop an entry.
+ dirtied = true;
+ continue;
+ }
+
+ std::string hashed = ExternalStringToHashedDomain(i.key());
+ if (hashed.empty()) {
+ dirtied = true;
+ continue;
+ }
+
+ state->AddOrUpdateEnabledHosts(hashed, domain_state);
+ }
+
+ *dirty = dirtied;
+ return true;
+}
+
+void TransportSecurityPersister::CompleteLoad(const std::string& state) {
+ DCHECK(foreground_runner_->RunsTasksOnCurrentThread());
+
+ bool dirty = false;
+ if (!LoadEntries(state, &dirty)) {
+ LOG(ERROR) << "Failed to deserialize state: " << state;
+ return;
+ }
+ if (dirty)
+ StateIsDirty(transport_security_state_);
+}
+
+} // namespace net
diff --git a/net/http/transport_security_persister.h b/net/http/transport_security_persister.h
new file mode 100644
index 0000000..7725ba1
--- /dev/null
+++ b/net/http/transport_security_persister.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2012 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.
+
+// TransportSecurityState maintains an in memory database containing the
+// list of hosts that currently have transport security enabled. This
+// singleton object deals with writing that data out to disk as needed and
+// loading it at startup.
+
+// At startup we need to load the transport security state from the
+// disk. For the moment, we don't want to delay startup for this load, so we
+// let the TransportSecurityState run for a while without being loaded.
+// This means that it's possible for pages opened very quickly not to get the
+// correct transport security information.
+//
+// To load the state, we schedule a Task on file_task_runner, which
+// deserializes and configures the TransportSecurityState.
+//
+// The TransportSecurityState object supports running a callback function
+// when it changes. This object registers the callback, pointing at itself.
+//
+// TransportSecurityState calls...
+// TransportSecurityPersister::StateIsDirty
+// since the callback isn't allowed to block or reenter, we schedule a Task
+// on the file task runner after some small amount of time
+//
+// ...
+//
+// TransportSecurityPersister::SerializeState
+// copies the current state of the TransportSecurityState, serializes
+// and writes to disk.
+
+#ifndef NET_HTTP_TRANSPORT_SECURITY_PERSISTER_H_
+#define NET_HTTP_TRANSPORT_SECURITY_PERSISTER_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/important_file_writer.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/http/transport_security_state.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace net {
+
+// Reads and updates on-disk TransportSecurity state. Clients of this class
+// should create, destroy, and call into it from one thread.
+//
+// file_task_runner is the task runner this class should use internally to
+// perform file IO, and can optionally be associated with a different thread.
+class NET_EXPORT TransportSecurityPersister
+ : public TransportSecurityState::Delegate,
+ public base::ImportantFileWriter::DataSerializer {
+ public:
+ TransportSecurityPersister(TransportSecurityState* state,
+ const base::FilePath& profile_path,
+ base::SequencedTaskRunner* file_task_runner,
+ bool readonly);
+ virtual ~TransportSecurityPersister();
+
+ // Called by the TransportSecurityState when it changes its state.
+ virtual void StateIsDirty(TransportSecurityState*) OVERRIDE;
+
+ // ImportantFileWriter::DataSerializer:
+ //
+ // Serializes |transport_security_state_| into |*output|. Returns true if
+ // all DomainStates were serialized correctly.
+ //
+ // The serialization format is JSON; the JSON represents a dictionary of
+ // host:DomainState pairs (host is a string). The DomainState is
+ // represented as a dictionary containing the following keys and value
+ // types (not all keys will always be present):
+ //
+ // "sts_include_subdomains": true|false
+ // "pkp_include_subdomains": true|false
+ // "created": double
+ // "expiry": double
+ // "dynamic_spki_hashes_expiry": double
+ // "mode": "default"|"force-https"
+ // legacy value synonyms "strict" = "force-https"
+ // "pinning-only" = "default"
+ // legacy value "spdy-only" is unused and ignored
+ // "static_spki_hashes": list of strings
+ // legacy key synonym "preloaded_spki_hashes"
+ // "bad_static_spki_hashes": list of strings
+ // legacy key synonym "bad_preloaded_spki_hashes"
+ // "dynamic_spki_hashes": list of strings
+ //
+ // The JSON dictionary keys are strings containing
+ // Base64(SHA256(TransportSecurityState::CanonicalizeHost(domain))).
+ // The reason for hashing them is so that the stored state does not
+ // trivially reveal a user's browsing history to an attacker reading the
+ // serialized state on disk.
+ virtual bool SerializeData(std::string* data) OVERRIDE;
+
+ // Clears any existing non-static entries, and then re-populates
+ // |transport_security_state_|.
+ //
+ // Sets |*dirty| to true if the new state differs from the persisted
+ // state; false otherwise.
+ bool LoadEntries(const std::string& serialized, bool* dirty);
+
+ private:
+ // Populates |state| from the JSON string |serialized|. Returns true if
+ // all entries were parsed and deserialized correctly.
+ //
+ // Sets |*dirty| to true if the new state differs from the persisted
+ // state; false otherwise.
+ static bool Deserialize(const std::string& serialized,
+ bool* dirty,
+ TransportSecurityState* state);
+
+ void CompleteLoad(const std::string& state);
+
+ TransportSecurityState* transport_security_state_;
+
+ // Helper for safely writing the data.
+ base::ImportantFileWriter writer_;
+
+ scoped_refptr<base::SequencedTaskRunner> foreground_runner_;
+ scoped_refptr<base::SequencedTaskRunner> background_runner_;
+
+ // Whether or not we're in read-only mode.
+ const bool readonly_;
+
+ base::WeakPtrFactory<TransportSecurityPersister> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransportSecurityPersister);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_TRANSPORT_SECURITY_PERSISTER_H_
diff --git a/net/http/transport_security_persister_unittest.cc b/net/http/transport_security_persister_unittest.cc
new file mode 100644
index 0000000..8c41f9e
--- /dev/null
+++ b/net/http/transport_security_persister_unittest.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2012 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 "net/http/transport_security_persister.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "net/http/transport_security_state.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::TransportSecurityPersister;
+using net::TransportSecurityState;
+
+class TransportSecurityPersisterTest : public testing::Test {
+ public:
+ TransportSecurityPersisterTest() {
+ }
+
+ virtual ~TransportSecurityPersisterTest() {
+ base::MessageLoopForIO::current()->RunUntilIdle();
+ }
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ persister_.reset(new TransportSecurityPersister(
+ &state_,
+ temp_dir_.path(),
+ base::MessageLoopForIO::current()->message_loop_proxy(),
+ false));
+ }
+
+ protected:
+ base::ScopedTempDir temp_dir_;
+ TransportSecurityState state_;
+ scoped_ptr<TransportSecurityPersister> persister_;
+};
+
+TEST_F(TransportSecurityPersisterTest, SerializeData1) {
+ std::string output;
+ bool dirty;
+
+ EXPECT_TRUE(persister_->SerializeData(&output));
+ EXPECT_TRUE(persister_->LoadEntries(output, &dirty));
+ EXPECT_FALSE(dirty);
+}
+
+TEST_F(TransportSecurityPersisterTest, SerializeData2) {
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ static const char kYahooDomain[] = "yahoo.com";
+
+ EXPECT_FALSE(state_.GetDomainState(kYahooDomain, true, &domain_state));
+
+ bool include_subdomains = true;
+ state_.AddHSTS(kYahooDomain, expiry, include_subdomains);
+
+ std::string output;
+ bool dirty;
+ EXPECT_TRUE(persister_->SerializeData(&output));
+ EXPECT_TRUE(persister_->LoadEntries(output, &dirty));
+
+ EXPECT_TRUE(state_.GetDomainState(kYahooDomain, true, &domain_state));
+ EXPECT_EQ(domain_state.upgrade_mode,
+ TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
+ EXPECT_TRUE(state_.GetDomainState("foo.yahoo.com", true, &domain_state));
+ EXPECT_EQ(domain_state.upgrade_mode,
+ TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
+ EXPECT_TRUE(state_.GetDomainState("foo.bar.yahoo.com", true, &domain_state));
+ EXPECT_EQ(domain_state.upgrade_mode,
+ TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
+ EXPECT_TRUE(state_.GetDomainState("foo.bar.baz.yahoo.com", true,
+ &domain_state));
+ EXPECT_EQ(domain_state.upgrade_mode,
+ TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
+ EXPECT_FALSE(state_.GetDomainState("com", true, &domain_state));
+}
+
+TEST_F(TransportSecurityPersisterTest, SerializeData3) {
+ // Add an entry.
+ net::HashValue fp1(net::HASH_VALUE_SHA1);
+ memset(fp1.data(), 0, fp1.size());
+ net::HashValue fp2(net::HASH_VALUE_SHA1);
+ memset(fp2.data(), 1, fp2.size());
+ base::Time expiry =
+ base::Time::Now() + base::TimeDelta::FromSeconds(1000);
+ net::HashValueVector dynamic_spki_hashes;
+ dynamic_spki_hashes.push_back(fp1);
+ dynamic_spki_hashes.push_back(fp2);
+ bool include_subdomains = false;
+ state_.AddHSTS("www.example.com", expiry, include_subdomains);
+ state_.AddHPKP("www.example.com", expiry, include_subdomains,
+ dynamic_spki_hashes);
+
+ // Add another entry.
+ memset(fp1.data(), 2, fp1.size());
+ memset(fp2.data(), 3, fp2.size());
+ expiry =
+ base::Time::Now() + base::TimeDelta::FromSeconds(3000);
+ dynamic_spki_hashes.push_back(fp1);
+ dynamic_spki_hashes.push_back(fp2);
+ state_.AddHSTS("www.example.net", expiry, include_subdomains);
+ state_.AddHPKP("www.example.net", expiry, include_subdomains,
+ dynamic_spki_hashes);
+
+ // Save a copy of everything.
+ std::map<std::string, TransportSecurityState::DomainState> saved;
+ TransportSecurityState::Iterator i(state_);
+ while (i.HasNext()) {
+ saved[i.hostname()] = i.domain_state();
+ i.Advance();
+ }
+
+ std::string serialized;
+ EXPECT_TRUE(persister_->SerializeData(&serialized));
+
+ // Persist the data to the file. For the test to be fast and not flaky, we
+ // just do it directly rather than call persister_->StateIsDirty. (That uses
+ // ImportantFileWriter, which has an asynchronous commit interval rather
+ // than block.) Use a different basename just for cleanliness.
+ base::FilePath path =
+ temp_dir_.path().AppendASCII("TransportSecurityPersisterTest");
+ EXPECT_TRUE(file_util::WriteFile(path, serialized.c_str(),
+ serialized.size()));
+
+ // Read the data back.
+ std::string persisted;
+ EXPECT_TRUE(base::ReadFileToString(path, &persisted));
+ EXPECT_EQ(persisted, serialized);
+ bool dirty;
+ EXPECT_TRUE(persister_->LoadEntries(persisted, &dirty));
+ EXPECT_FALSE(dirty);
+
+ // Check that states are the same as saved.
+ size_t count = 0;
+ TransportSecurityState::Iterator j(state_);
+ while (j.HasNext()) {
+ count++;
+ j.Advance();
+ }
+ EXPECT_EQ(count, saved.size());
+}
+
+TEST_F(TransportSecurityPersisterTest, SerializeDataOld) {
+ // This is an old-style piece of transport state JSON, which has no creation
+ // date.
+ std::string output =
+ "{ "
+ "\"NiyD+3J1r6z1wjl2n1ALBu94Zj9OsEAMo0kCN8js0Uk=\": {"
+ "\"expiry\": 1266815027.983453, "
+ "\"include_subdomains\": false, "
+ "\"mode\": \"strict\" "
+ "}"
+ "}";
+ bool dirty;
+ EXPECT_TRUE(persister_->LoadEntries(output, &dirty));
+ EXPECT_TRUE(dirty);
+}
+
+TEST_F(TransportSecurityPersisterTest, PublicKeyHashes) {
+ TransportSecurityState::DomainState domain_state;
+ static const char kTestDomain[] = "example.com";
+ EXPECT_FALSE(state_.GetDomainState(kTestDomain, false, &domain_state));
+ net::HashValueVector hashes;
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(hashes));
+
+ net::HashValue sha1(net::HASH_VALUE_SHA1);
+ memset(sha1.data(), '1', sha1.size());
+ domain_state.dynamic_spki_hashes.push_back(sha1);
+
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(hashes));
+
+ hashes.push_back(sha1);
+ EXPECT_TRUE(domain_state.CheckPublicKeyPins(hashes));
+
+ hashes[0].data()[0] = '2';
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(hashes));
+
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ bool include_subdomains = false;
+ state_.AddHSTS(kTestDomain, expiry, include_subdomains);
+ state_.AddHPKP(kTestDomain, expiry, include_subdomains,
+ domain_state.dynamic_spki_hashes);
+ std::string ser;
+ EXPECT_TRUE(persister_->SerializeData(&ser));
+ bool dirty;
+ EXPECT_TRUE(persister_->LoadEntries(ser, &dirty));
+ EXPECT_TRUE(state_.GetDomainState(kTestDomain, false, &domain_state));
+ EXPECT_EQ(1u, domain_state.dynamic_spki_hashes.size());
+ EXPECT_EQ(sha1.tag, domain_state.dynamic_spki_hashes[0].tag);
+ EXPECT_EQ(0, memcmp(domain_state.dynamic_spki_hashes[0].data(), sha1.data(),
+ sha1.size()));
+}
diff --git a/net/http/transport_security_state.h b/net/http/transport_security_state.h
index ccbc53a0..97b4d7c 100644
--- a/net/http/transport_security_state.h
+++ b/net/http/transport_security_state.h
@@ -35,7 +35,7 @@
class NET_EXPORT TransportSecurityState
: NON_EXPORTED_BASE(public base::NonThreadSafe) {
public:
- class Delegate {
+ class NET_EXPORT Delegate {
public:
// This function may not block and may be called with internal locks held.
// Thus it must not reenter the TransportSecurityState object.