summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/transport_security_persister.cc2
-rw-r--r--net/base/net_switches.cc17
-rw-r--r--net/base/net_switches.h18
-rw-r--r--net/base/transport_security_state.cc87
-rw-r--r--net/base/transport_security_state.h11
-rw-r--r--net/base/transport_security_state_unittest.cc39
-rw-r--r--net/net.gyp2
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',