Chromium Code Reviews| Index: chrome/browser/transport_security_persister.cc |
| =================================================================== |
| --- chrome/browser/transport_security_persister.cc (revision 133741) |
| +++ 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,194 @@ |
| transport_security_state_->SetDelegate(NULL); |
| } |
| +namespace { |
|
Ryan Sleevi
2012/04/26 19:21:12
Place unnamed namespaces at the beginning of the f
palmer
2012/04/27 23:52:34
Done.
|
| + |
| +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; |
| +} |
| + |
| +void SPKIHashesFromListValue(const ListValue& pins, FingerprintVector* hashes) { |
| + 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 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 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"; |
| + |
| +} // anonymous namespce |
| + |
| +bool TransportSecurityPersister::DeserializeFromCommandLine( |
|
Ryan Sleevi
2012/04/26 19:21:12
nit: Fix the ordering of this file so that definit
palmer
2012/04/27 23:52:34
Will do.
|
| + const std::string& serialized) { |
| + // We purposefully ignore |dirty| because we do not want to persist |
|
Ryan Sleevi
2012/04/26 19:21:12
nit: Drop the we
palmer
2012/04/27 23:52:34
Done.
|
| + // entries deserialized in this way. |
| + bool dirty; |
| + return Deserialize(serialized, &dirty, true, transport_security_state_); |
| +} |
| + |
| +// static |
| +bool TransportSecurityPersister::Deserialize(const std::string& serialized, |
| + bool* dirty, |
| + bool forced, |
| + TransportSecurityState* state) { |
| + scoped_ptr<Value> value(base::JSONReader::Read(serialized)); |
| + DictionaryValue* dict_value; |
| + if (!value.get() || !value->GetAsDictionary(&dict_value)) |
| + return false; |
| + |
| + 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(kIncludeSubdomains, &include_subdomains) || |
| + !parsed->GetString(kMode, &mode_string) || |
| + !parsed->GetDouble(kExpiry, &expiry)) { |
|
Ryan Sleevi
2012/04/26 19:21:12
So input data failures aren't fatal?
Should there
palmer
2012/04/27 23:52:34
The code as it is here is merely moved, not change
|
| + continue; |
| + } |
| + |
| + // Don't fail if this key is not present. |
| + parsed->GetDouble(kDynamicSPKIHashesExpiry, |
| + &dynamic_spki_hashes_expiry); |
| + |
| + ListValue* pins_list = NULL; |
| + FingerprintVector static_spki_hashes; |
| + // preloaded_spki_hashes is a legacy synonym for static_spki_hashes. |
| + if (parsed->GetList(kStaticSPKIHashes, &pins_list)) |
| + SPKIHashesFromListValue(*pins_list, &static_spki_hashes); |
| + else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list)) |
| + SPKIHashesFromListValue(*pins_list, &static_spki_hashes); |
| + |
| + FingerprintVector dynamic_spki_hashes; |
| + if (parsed->GetList(kDynamicSPKIHashes, &pins_list)) |
| + SPKIHashesFromListValue(*pins_list, &dynamic_spki_hashes); |
| + |
| + TransportSecurityState::DomainState::UpgradeMode mode; |
| + if (mode_string == kForceHTTPS || mode_string == kStrict) { |
| + mode = TransportSecurityState::DomainState::MODE_FORCE_HTTPS; |
| + } else if (mode_string == kDefault || mode_string == kPinningOnly) { |
| + 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(kCreated, &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; |
| + |
| + if (forced) |
| + state->GetForcedHosts()[hashed] = domain_state; |
| + else |
| + state->GetEnabledHosts()[hashed] = domain_state; |
| + } |
| + |
| + *dirty = dirtied; |
| + return true; |
| +} |
| + |
| +bool TransportSecurityPersister::LoadEntries(const std::string& serialized, |
| + bool* dirty) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + |
| + transport_security_state_->Clear(); |
| + return Deserialize(serialized, dirty, false, transport_security_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)) { |
| LOG(ERROR) << "Failed to deserialize state: " << state; |
| return; |
| } |
| @@ -93,7 +285,7 @@ |
| } |
| void TransportSecurityPersister::StateIsDirty( |
| - net::TransportSecurityState* state) { |
| + TransportSecurityState* state) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK_EQ(transport_security_state_, state); |
| @@ -101,7 +293,51 @@ |
| writer_.ScheduleWrite(this); |
| } |
| -bool TransportSecurityPersister::SerializeData(std::string* data) { |
| +bool TransportSecurityPersister::SerializeData(std::string* output) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| - return transport_security_state_->Serialise(data); |
| + |
| + 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(kIncludeSubdomains, |
| + domain_state.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; |
| } |