diff options
-rw-r--r-- | chrome/browser/transport_security_persister.cc | 2 | ||||
-rw-r--r-- | net/base/net_switches.cc | 17 | ||||
-rw-r--r-- | net/base/net_switches.h | 18 | ||||
-rw-r--r-- | net/base/transport_security_state.cc | 87 | ||||
-rw-r--r-- | net/base/transport_security_state.h | 11 | ||||
-rw-r--r-- | net/base/transport_security_state_unittest.cc | 39 | ||||
-rw-r--r-- | net/net.gyp | 2 |
7 files changed, 130 insertions, 46 deletions
diff --git a/chrome/browser/transport_security_persister.cc b/chrome/browser/transport_security_persister.cc index 7b503b3..98f4580 100644 --- a/chrome/browser/transport_security_persister.cc +++ b/chrome/browser/transport_security_persister.cc @@ -51,7 +51,7 @@ void TransportSecurityPersister::CompleteLoad(const std::string& state) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); bool dirty = false; - if (!transport_security_state_->Deserialise(state, &dirty)) { + if (!transport_security_state_->LoadEntries(state, &dirty)) { LOG(ERROR) << "Failed to deserialize state: " << state; return; } diff --git a/net/base/net_switches.cc b/net/base/net_switches.cc new file mode 100644 index 0000000..10d6fa0 --- /dev/null +++ b/net/base/net_switches.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2011 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/base/net_switches.h" + +namespace switches { + +// This switch will take the JSON-formatted HSTS specification and load it +// as if it were a preloaded HSTS entry. It will take precedence over both +// website-specified rules and built-in rules. +// The JSON format is the same as that persisted in +// <profile_dir>/Default/TransportSecurity +const char kHstsHosts[] = "hsts-hosts"; + +} // namespace switches + diff --git a/net/base/net_switches.h b/net/base/net_switches.h new file mode 100644 index 0000000..8951372 --- /dev/null +++ b/net/base/net_switches.h @@ -0,0 +1,18 @@ +// Copyright (c) 2011 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. + +// Defines all the "net" command-line switches. + +#ifndef NET_BASE_SWITCHES_H_ +#define NET_BASE_SWITCHES_H_ +#pragma once + +namespace switches { + +extern const char kHstsHosts[]; + +} // namespace switches + +#endif // NET_BASE_SWITCHES_H_ + diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc index 43690d5..4d36d350 100644 --- a/net/base/transport_security_state.cc +++ b/net/base/transport_security_state.cc @@ -5,12 +5,14 @@ #include "net/base/transport_security_state.h" #include "base/base64.h" +#include "base/command_line.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/sha1.h" #include "base/string_number_conversions.h" +#include "base/string_split.h" #include "base/string_tokenizer.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" @@ -18,6 +20,7 @@ #include "crypto/sha2.h" #include "googleurl/src/gurl.h" #include "net/base/dns_util.h" +#include "net/base/net_switches.h" namespace net { @@ -27,6 +30,12 @@ TransportSecurityState::TransportSecurityState() : delegate_(NULL) { } +static std::string HashHost(const std::string& canonicalized_host) { + char hashed[crypto::SHA256_LENGTH]; + crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); + return std::string(hashed, sizeof(hashed)); +} + void TransportSecurityState::EnableHost(const std::string& host, const DomainState& state) { const std::string canonicalized_host = CanonicalizeHost(host); @@ -36,13 +45,10 @@ void TransportSecurityState::EnableHost(const std::string& host, // TODO(cevans) -- we likely want to permit a host to override a built-in, // for at least the case where the override is stricter (i.e. includes // subdomains, or includes certificate pinning). - bool temp; + DomainState temp; if (IsPreloadedSTS(canonicalized_host, true, &temp)) return; - char hashed[crypto::SHA256_LENGTH]; - crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); - // Use the original creation date if we already have this host. DomainState state_copy(state); DomainState existing_state; @@ -53,7 +59,7 @@ void TransportSecurityState::EnableHost(const std::string& host, state_copy.preloaded = false; state_copy.domain.clear(); - enabled_hosts_[std::string(hashed, sizeof(hashed))] = state_copy; + enabled_hosts_[HashHost(canonicalized_host)] = state_copy; DirtyNotify(); } @@ -62,11 +68,8 @@ bool TransportSecurityState::DeleteHost(const std::string& host) { if (canonicalized_host.empty()) return false; - char hashed[crypto::SHA256_LENGTH]; - crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); - std::map<std::string, DomainState>::iterator i = enabled_hosts_.find( - std::string(hashed, sizeof(hashed))); + HashHost(canonicalized_host)); if (i != enabled_hosts_.end()) { enabled_hosts_.erase(i); DirtyNotify(); @@ -84,31 +87,22 @@ static std::string IncludeNUL(const char* in) { bool TransportSecurityState::IsEnabledForHost(DomainState* result, const std::string& host, bool sni_available) { - *result = DomainState(); - const std::string canonicalized_host = CanonicalizeHost(host); if (canonicalized_host.empty()) return false; - bool include_subdomains; - if (IsPreloadedSTS(canonicalized_host, sni_available, &include_subdomains)) { - result->created = result->expiry = base::Time::FromTimeT(0); - result->mode = DomainState::MODE_STRICT; - result->include_subdomains = include_subdomains; - result->preloaded = true; - return true; - } + if (IsPreloadedSTS(canonicalized_host, sni_available, result)) + return result->mode != DomainState::MODE_NONE; + + *result = DomainState(); - result->preloaded = false; base::Time current_time(base::Time::Now()); for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { - char hashed_domain[crypto::SHA256_LENGTH]; + std::string hashed_domain(HashHost(IncludeNUL(&canonicalized_host[i]))); - crypto::SHA256HashString(IncludeNUL(&canonicalized_host[i]), &hashed_domain, - sizeof(hashed_domain)); std::map<std::string, DomainState>::iterator j = - enabled_hosts_.find(std::string(hashed_domain, sizeof(hashed_domain))); + enabled_hosts_.find(hashed_domain); if (j == enabled_hosts_.end()) continue; @@ -336,10 +330,17 @@ bool TransportSecurityState::Serialise(std::string* output) { return true; } -bool TransportSecurityState::Deserialise(const std::string& input, +bool TransportSecurityState::LoadEntries(const std::string& input, bool* dirty) { enabled_hosts_.clear(); + return Deserialise(input, dirty, &enabled_hosts_); +} +// static +bool TransportSecurityState::Deserialise( + const std::string& input, + bool* dirty, + std::map<std::string, DomainState>* out) { scoped_ptr<Value> value( base::JSONReader::Read(input, false /* do not allow trailing commas */)); if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) @@ -393,6 +394,8 @@ bool TransportSecurityState::Deserialise(const std::string& input, mode = DomainState::MODE_OPPORTUNISTIC; } else if (mode_string == "spdy-only") { mode = DomainState::MODE_SPDY_ONLY; + } else if (mode_string == "none") { + mode = DomainState::MODE_NONE; } else { LOG(WARNING) << "Unknown TransportSecurityState mode string found: " << mode_string; @@ -428,7 +431,7 @@ bool TransportSecurityState::Deserialise(const std::string& input, new_state.expiry = expiry_time; new_state.include_subdomains = include_subdomains; new_state.public_key_hashes = public_key_hashes; - enabled_hosts_[hashed] = new_state; + (*out)[hashed] = new_state; } *dirty = dirtied; @@ -485,7 +488,22 @@ std::string TransportSecurityState::CanonicalizeHost(const std::string& host) { bool TransportSecurityState::IsPreloadedSTS( const std::string& canonicalized_host, bool sni_available, - bool *include_subdomains) { + DomainState* out) { + out->preloaded = true; + out->mode = DomainState::MODE_STRICT; + out->created = base::Time::FromTimeT(0); + out->expiry = out->created; + out->include_subdomains = false; + + std::map<std::string, DomainState> hosts; + std::string cmd_line_hsts = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kHstsHosts); + if (!cmd_line_hsts.empty()) { + bool dirty; + Deserialise(cmd_line_hsts, &dirty, &hosts); + } + // In the medium term this list is likely to just be hardcoded here. This, // slightly odd, form removes the need for additional relocations records. static const struct { @@ -547,13 +565,23 @@ bool TransportSecurityState::IsPreloadedSTS( static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { + std::string host_sub_chunk(&canonicalized_host[i], + canonicalized_host.size() - i); + out->domain = DNSDomainToString(host_sub_chunk); + std::string hashed_host(HashHost(host_sub_chunk)); + if (hosts.find(hashed_host) != hosts.end()) { + *out = hosts[hashed_host]; + out->domain = DNSDomainToString(host_sub_chunk); + out->preloaded = true; + return true; + } for (size_t j = 0; j < kNumPreloadedSTS; j++) { if (kPreloadedSTS[j].length == canonicalized_host.size() - i && memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i], kPreloadedSTS[j].length) == 0) { if (!kPreloadedSTS[j].include_subdomains && i != 0) return false; - *include_subdomains = kPreloadedSTS[j].include_subdomains; + out->include_subdomains = kPreloadedSTS[j].include_subdomains; return true; } } @@ -564,7 +592,7 @@ bool TransportSecurityState::IsPreloadedSTS( kPreloadedSNISTS[j].length) == 0) { if (!kPreloadedSNISTS[j].include_subdomains && i != 0) return false; - *include_subdomains = kPreloadedSNISTS[j].include_subdomains; + out->include_subdomains = kPreloadedSNISTS[j].include_subdomains; return true; } } @@ -613,7 +641,6 @@ bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted( } } - LOG(ERROR) << "Rejecting public key chain for domain " << domain << ". Validated chain: " << HashesToBase64String(hashes) << ", expected: " << HashesToBase64String(public_key_hashes); diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h index 8f0b75a..d56583a 100644 --- a/net/base/transport_security_state.h +++ b/net/base/transport_security_state.h @@ -44,6 +44,8 @@ class TransportSecurityState : // * We'll request HTTP URLs over HTTPS iff we have SPDY support. // * Certificate issues are fatal. MODE_SPDY_ONLY = 2, + // None means there is no HSTS for this domain. + MODE_NONE = 3, }; DomainState(); @@ -104,7 +106,9 @@ class TransportSecurityState : void SetDelegate(Delegate*); bool Serialise(std::string* output); - bool Deserialise(const std::string& state, bool* dirty); + // Existing non-preloaded entries are cleared and repopulated from the + // passed JSON string. + bool LoadEntries(const std::string& state, bool* dirty); // The maximum number of seconds for which we'll cache an HSTS request. static const long int kMaxHSTSAgeSecs; @@ -122,7 +126,10 @@ class TransportSecurityState : static std::string CanonicalizeHost(const std::string& host); static bool IsPreloadedSTS(const std::string& canonicalized_host, bool sni_available, - bool* out_include_subdomains); + DomainState* out); + static bool Deserialise(const std::string& state, + bool* dirty, + std::map<std::string, DomainState>* out); // The set of hosts that have enabled TransportSecurity. The keys here // are SHA256(DNSForm(domain)) where DNSForm converts from dotted form diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc index ed5df89..30377a5 100644 --- a/net/base/transport_security_state_unittest.cc +++ b/net/base/transport_security_state_unittest.cc @@ -206,7 +206,7 @@ TEST_F(TransportSecurityStateTest, Serialise1) { std::string output; bool dirty; state->Serialise(&output); - EXPECT_TRUE(state->Deserialise(output, &dirty)); + EXPECT_TRUE(state->LoadEntries(output, &dirty)); EXPECT_FALSE(dirty); } @@ -227,7 +227,7 @@ TEST_F(TransportSecurityStateTest, Serialise2) { std::string output; bool dirty; state->Serialise(&output); - EXPECT_TRUE(state->Deserialise(output, &dirty)); + EXPECT_TRUE(state->LoadEntries(output, &dirty)); EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com", true)); EXPECT_EQ(domain_state.mode, TransportSecurityState::DomainState::MODE_STRICT); @@ -260,7 +260,7 @@ TEST_F(TransportSecurityStateTest, Serialise3) { std::string output; bool dirty; state->Serialise(&output); - EXPECT_TRUE(state->Deserialise(output, &dirty)); + EXPECT_TRUE(state->LoadEntries(output, &dirty)); EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com", true)); EXPECT_EQ(domain_state.mode, @@ -318,7 +318,7 @@ TEST_F(TransportSecurityStateTest, SerialiseOld) { "}" "}"; bool dirty; - EXPECT_TRUE(state->Deserialise(output, &dirty)); + EXPECT_TRUE(state->LoadEntries(output, &dirty)); EXPECT_TRUE(dirty); } @@ -336,14 +336,20 @@ TEST_F(TransportSecurityStateTest, IsPreloaded) { const std::string aypal = TransportSecurityState::CanonicalizeHost("aypal.com"); - bool b; - EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS(paypal, true, &b)); - EXPECT_TRUE(TransportSecurityState::IsPreloadedSTS(www_paypal, true, &b)); - EXPECT_FALSE(b); - EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS(a_www_paypal, true, &b)); - EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS(abc_paypal, true, &b)); - EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS(example, true, &b)); - EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS(aypal, true, &b)); + TransportSecurityState::DomainState domain_state; + EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS( + paypal, true, &domain_state)); + EXPECT_TRUE(TransportSecurityState::IsPreloadedSTS( + www_paypal, true, &domain_state)); + EXPECT_FALSE(domain_state.include_subdomains); + EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS( + a_www_paypal, true, &domain_state)); + EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS( + abc_paypal, true, &domain_state)); + EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS( + example, true, &domain_state)); + EXPECT_FALSE(TransportSecurityState::IsPreloadedSTS( + aypal, true, &domain_state)); } TEST_F(TransportSecurityStateTest, Preloaded) { @@ -461,6 +467,13 @@ TEST_F(TransportSecurityStateTest, Preloaded) { EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "market.android.com", true)); + // The domain wasn't being set, leading to a blank string in the + // chrome://net-internals/#hsts UI. So test that. + EXPECT_EQ(domain_state.domain, "market.android.com"); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, + "sub.market.android.com", + true)); + EXPECT_EQ(domain_state.domain, "market.android.com"); EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "lastpass.com", true)); EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "www.lastpass.com", true)); @@ -533,7 +546,7 @@ TEST_F(TransportSecurityStateTest, PublicKeyHashes) { std::string ser; EXPECT_TRUE(state->Serialise(&ser)); bool dirty; - EXPECT_TRUE(state->Deserialise(ser, &dirty)); + EXPECT_TRUE(state->LoadEntries(ser, &dirty)); EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "example.com", false)); EXPECT_EQ(1u, domain_state.public_key_hashes.size()); EXPECT_TRUE(0 == memcmp(domain_state.public_key_hashes[0].data, hash.data, diff --git a/net/net.gyp b/net/net.gyp index 3290850..3254e1b 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -138,6 +138,8 @@ 'base/net_log_source_type_list.h', 'base/net_module.cc', 'base/net_module.h', + 'base/net_switches.cc', + 'base/net_switches.h', 'base/net_util.cc', 'base/net_util.h', 'base/net_util_posix.cc', |