| Index: chrome/browser/transport_security_persister.cc
|
| ===================================================================
|
| --- chrome/browser/transport_security_persister.cc (revision 128526)
|
| +++ chrome/browser/transport_security_persister.cc (working copy)
|
| @@ -4,16 +4,25 @@
|
|
|
| #include "chrome/browser/transport_security_persister.h"
|
|
|
| +#include "base/base64.h"
|
| #include "base/bind.h"
|
| #include "base/file_path.h"
|
| #include "base/file_util.h"
|
| +#include "base/json/json_reader.h"
|
| +#include "base/json/json_writer.h"
|
| #include "base/message_loop.h"
|
| #include "base/path_service.h"
|
| +#include "base/values.h"
|
| #include "chrome/common/chrome_paths.h"
|
| #include "content/public/browser/browser_thread.h"
|
| +#include "crypto/sha2.h"
|
| #include "net/base/transport_security_state.h"
|
| +#include "net/base/x509_certificate.h"
|
|
|
| using content::BrowserThread;
|
| +using net::Fingerprint;
|
| +using net::FingerprintVector;
|
| +using net::TransportSecurityState;
|
|
|
| class TransportSecurityPersister::Loader {
|
| public:
|
| @@ -52,7 +61,7 @@
|
| };
|
|
|
| TransportSecurityPersister::TransportSecurityPersister(
|
| - net::TransportSecurityState* state,
|
| + TransportSecurityState* state,
|
| const FilePath& profile_path,
|
| bool readonly)
|
| : transport_security_state_(state),
|
| @@ -80,11 +89,167 @@
|
| transport_security_state_->SetDelegate(NULL);
|
| }
|
|
|
| +static ListValue* SPKIHashesToListValue(const FingerprintVector& hashes) {
|
| + ListValue* pins = new ListValue;
|
| +
|
| + for (FingerprintVector::const_iterator i = hashes.begin();
|
| + i != hashes.end(); ++i) {
|
| + std::string hash_str(reinterpret_cast<const char*>(i->data),
|
| + sizeof(i->data));
|
| + std::string b64;
|
| + base::Base64Encode(hash_str, &b64);
|
| + pins->Append(new StringValue("sha1/" + b64));
|
| + }
|
| +
|
| + return pins;
|
| +}
|
| +
|
| +static void SPKIHashesFromListValue(FingerprintVector* hashes,
|
| + const ListValue& pins) {
|
| + size_t num_pins = pins.GetSize();
|
| + for (size_t i = 0; i < num_pins; ++i) {
|
| + std::string type_and_base64;
|
| + Fingerprint fingerprint;
|
| + if (pins.GetString(i, &type_and_base64) &&
|
| + TransportSecurityState::ParsePin(type_and_base64, &fingerprint)) {
|
| + hashes->push_back(fingerprint);
|
| + }
|
| + }
|
| +}
|
| +
|
| +// This function converts the binary hashes, which we store in
|
| +// |enabled_hosts_|, to a base64 string which we can include in a JSON file.
|
| +static std::string HashedDomainToExternalString(const std::string& hashed) {
|
| + std::string out;
|
| + CHECK(base::Base64Encode(hashed, &out));
|
| + return out;
|
| +}
|
| +
|
| +// This inverts |HashedDomainToExternalString|, above. It turns an external
|
| +// string (from a JSON file) into an internal (binary) string.
|
| +static std::string ExternalStringToHashedDomain(const std::string& external) {
|
| + std::string out;
|
| + if (!base::Base64Decode(external, &out) ||
|
| + out.size() != crypto::kSHA256Length) {
|
| + return std::string();
|
| + }
|
| +
|
| + return out;
|
| +}
|
| +
|
| +// static
|
| +bool TransportSecurityPersister::Deserialize(const std::string& serialized,
|
| + bool* dirty,
|
| + TransportSecurityState* state) {
|
| + scoped_ptr<Value> value(
|
| + base::JSONReader::Read(serialized,
|
| + false /* do not allow trailing commas */));
|
| + if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
|
| + return false;
|
| +
|
| + DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get());
|
| + const base::Time current_time(base::Time::Now());
|
| + bool dirtied = false;
|
| +
|
| + for (DictionaryValue::key_iterator i = dict_value->begin_keys();
|
| + i != dict_value->end_keys(); ++i) {
|
| + DictionaryValue* parsed;
|
| + if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &parsed))
|
| + continue;
|
| +
|
| + bool include_subdomains;
|
| + std::string mode_string;
|
| + double created;
|
| + double expiry;
|
| + double dynamic_spki_hashes_expiry = 0.0;
|
| +
|
| + if (!parsed->GetBoolean("include_subdomains", &include_subdomains) ||
|
| + !parsed->GetString("mode", &mode_string) ||
|
| + !parsed->GetDouble("expiry", &expiry)) {
|
| + continue;
|
| + }
|
| +
|
| + // Don't fail if this key is not present.
|
| + (void) parsed->GetDouble("dynamic_spki_hashes_expiry",
|
| + &dynamic_spki_hashes_expiry);
|
| +
|
| + ListValue* pins_list = NULL;
|
| + FingerprintVector static_spki_hashes;
|
| + if (parsed->GetList("preloaded_spki_hashes", &pins_list))
|
| + SPKIHashesFromListValue(&static_spki_hashes, *pins_list);
|
| +
|
| + FingerprintVector dynamic_spki_hashes;
|
| + if (parsed->GetList("dynamic_spki_hashes", &pins_list))
|
| + SPKIHashesFromListValue(&dynamic_spki_hashes, *pins_list);
|
| +
|
| + TransportSecurityState::DomainState::UpgradeMode mode;
|
| + if (mode_string == "strict") {
|
| + mode = TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
|
| + } else if (mode_string == "spdy-only") {
|
| + mode = TransportSecurityState::DomainState::MODE_DEFAULT;
|
| + } else if (mode_string == "pinning-only") {
|
| + mode = TransportSecurityState::DomainState::MODE_DEFAULT;
|
| + } else {
|
| + LOG(WARNING) << "Unknown TransportSecurityState mode string found: "
|
| + << mode_string;
|
| + continue;
|
| + }
|
| +
|
| + base::Time expiry_time = base::Time::FromDoubleT(expiry);
|
| + base::Time dynamic_spki_hashes_expiry_time =
|
| + base::Time::FromDoubleT(dynamic_spki_hashes_expiry);
|
| + base::Time created_time;
|
| + if (parsed->GetDouble("created", &created)) {
|
| + created_time = 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;
|
| + created_time = base::Time::Now();
|
| + }
|
| +
|
| + if (expiry_time <= current_time &&
|
| + dynamic_spki_hashes_expiry_time <= current_time) {
|
| + // Make sure we dirty the state if we drop an entry.
|
| + dirtied = true;
|
| + continue;
|
| + }
|
| +
|
| + std::string hashed = ExternalStringToHashedDomain(*i);
|
| + if (hashed.empty()) {
|
| + dirtied = true;
|
| + continue;
|
| + }
|
| +
|
| + TransportSecurityState::DomainState domain_state;
|
| + domain_state.upgrade_mode = mode;
|
| + domain_state.created = created_time;
|
| + domain_state.upgrade_expiry = expiry_time;
|
| + domain_state.include_subdomains = include_subdomains;
|
| + domain_state.static_spki_hashes = static_spki_hashes;
|
| + domain_state.dynamic_spki_hashes = dynamic_spki_hashes;
|
| + domain_state.dynamic_spki_hashes_expiry = dynamic_spki_hashes_expiry_time;
|
| + state->EnableHost(hashed, domain_state);
|
| + }
|
| +
|
| + *dirty = dirtied;
|
| + return true;
|
| +}
|
| +
|
| +bool TransportSecurityPersister::LoadEntries(const std::string& serialized,
|
| + bool* dirty,
|
| + TransportSecurityState* state) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| +
|
| + state->Clear();
|
| + return Deserialize(serialized, dirty, state);
|
| +}
|
| +
|
| void TransportSecurityPersister::CompleteLoad(const std::string& state) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
|
|
| bool dirty = false;
|
| - if (!transport_security_state_->LoadEntries(state, &dirty)) {
|
| + if (!LoadEntries(state, &dirty, transport_security_state_)) {
|
| LOG(ERROR) << "Failed to deserialize state: " << state;
|
| return;
|
| }
|
| @@ -93,7 +258,7 @@
|
| }
|
|
|
| void TransportSecurityPersister::StateIsDirty(
|
| - net::TransportSecurityState* state) {
|
| + TransportSecurityState* state) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| DCHECK_EQ(transport_security_state_, state);
|
|
|
| @@ -101,7 +266,56 @@
|
| writer_.ScheduleWrite(this);
|
| }
|
|
|
| +bool TransportSecurityPersister::Serialize(std::string* output) const {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| +
|
| + 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("include_subdomains",
|
| + domain_state.include_subdomains);
|
| + serialized->SetDouble("created", domain_state.created.ToDoubleT());
|
| + serialized->SetDouble("expiry", domain_state.upgrade_expiry.ToDoubleT());
|
| + serialized->SetDouble("dynamic_spki_hashes_expiry",
|
| + domain_state.dynamic_spki_hashes_expiry.ToDoubleT());
|
| +
|
| + switch (domain_state.upgrade_mode) {
|
| + case TransportSecurityState::DomainState::MODE_FORCE_HTTPS:
|
| + serialized->SetString("mode", "strict");
|
| + break;
|
| + case TransportSecurityState::DomainState::MODE_DEFAULT:
|
| + serialized->SetString("mode", "pinning-only");
|
| + break;
|
| + default:
|
| + NOTREACHED() << "DomainState with unknown mode";
|
| + delete serialized;
|
| + continue;
|
| + }
|
| +
|
| + serialized->Set("preloaded_spki_hashes",
|
| + SPKIHashesToListValue(domain_state.static_spki_hashes));
|
| +
|
| + if (now < domain_state.dynamic_spki_hashes_expiry) {
|
| + serialized->Set("dynamic_spki_hashes",
|
| + 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::SerializeData(std::string* data) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| - return transport_security_state_->Serialise(data);
|
| + return Serialize(data);
|
| }
|
|
|