summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/base/dns_util.cc71
-rw-r--r--net/base/dns_util.h25
-rw-r--r--net/base/dns_util_unittest.cc60
-rw-r--r--net/base/strict_transport_security_state.cc123
-rw-r--r--net/base/strict_transport_security_state.h6
-rw-r--r--net/base/strict_transport_security_state_unittest.cc79
-rw-r--r--net/net.gyp3
7 files changed, 343 insertions, 24 deletions
diff --git a/net/base/dns_util.cc b/net/base/dns_util.cc
new file mode 100644
index 0000000..9c7e35a
--- /dev/null
+++ b/net/base/dns_util.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2009 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/dns_util.h"
+
+namespace net {
+
+// Based on DJB's public domain code.
+bool DNSDomainFromDot(const std::string& dotted, std::string* out) {
+ const char* buf = dotted.data();
+ unsigned n = dotted.size();
+ char label[63];
+ unsigned int labellen = 0; /* <= sizeof label */
+ char name[255];
+ unsigned int namelen = 0; /* <= sizeof name */
+ char ch;
+
+ for (;;) {
+ if (!n)
+ break;
+ ch = *buf++;
+ --n;
+ if (ch == '.') {
+ if (labellen) {
+ if (namelen + labellen + 1 > sizeof name)
+ return false;
+ name[namelen++] = labellen;
+ memcpy(name + namelen, label, labellen);
+ namelen += labellen;
+ labellen = 0;
+ }
+ continue;
+ }
+ if (labellen >= sizeof label)
+ return false;
+ label[labellen++] = ch;
+ }
+
+ if (labellen) {
+ if (namelen + labellen + 1 > sizeof name)
+ return false;
+ name[namelen++] = labellen;
+ memcpy(name + namelen, label, labellen);
+ namelen += labellen;
+ labellen = 0;
+ }
+
+ if (namelen + 1 > sizeof name)
+ return false;
+ name[namelen++] = 0;
+
+ *out = name;
+ return true;
+}
+
+bool IsSTD3ASCIIValidCharacter(char c) {
+ if (c <= 0x2c)
+ return false;
+ if (c >= 0x7b)
+ return false;
+ if (c >= 0x2e && c <= 0x2f)
+ return false;
+ if (c >= 0x3a && c <= 0x40)
+ return false;
+ if (c >= 0x5b && c <= 0x60)
+ return false;
+ return true;
+}
+
+} // namespace net
diff --git a/net/base/dns_util.h b/net/base/dns_util.h
new file mode 100644
index 0000000..8eb98f2
--- /dev/null
+++ b/net/base/dns_util.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2009 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.
+
+#ifndef NET_BASE_DNS_UTIL_H_
+#define NET_BASE_DNS_UTIL_H_
+
+#include <string>
+
+namespace net {
+
+// DNSDomainFromDot - convert a domain string to DNS format. From DJB's
+// public domain DNS library.
+//
+// dotted: a string in dotted form: "www.google.com"
+// out: a result in DNS form: "\x03www\x06google\x03com\x00"
+bool DNSDomainFromDot(const std::string& dotted, std::string* out);
+
+// Returns true iff the given character is in the set of valid DNS label
+// characters as given in RFC 3490, 4.1, 3(a)
+bool IsSTD3ASCIIValidCharacter(char c);
+
+} // namespace net
+
+#endif // NET_BASE_DNS_UTIL_H_
diff --git a/net/base/dns_util_unittest.cc b/net/base/dns_util_unittest.cc
new file mode 100644
index 0000000..7995b92
--- /dev/null
+++ b/net/base/dns_util_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2009 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/dns_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class DNSUtilTest : public testing::Test {
+};
+
+TEST_F(DNSUtilTest, DNSDomainFromDot) {
+ std::string out;
+
+ EXPECT_TRUE(DNSDomainFromDot("", &out));
+ EXPECT_EQ(out, "");
+ EXPECT_TRUE(DNSDomainFromDot("com", &out));
+ EXPECT_EQ(out, "\003com");
+ EXPECT_TRUE(DNSDomainFromDot("google.com", &out));
+ EXPECT_EQ(out, "\x006google\003com");
+ EXPECT_TRUE(DNSDomainFromDot("www.google.com", &out));
+ EXPECT_EQ(out, "\003www\006google\003com");
+
+ // Label is 63 chars: still valid
+ EXPECT_TRUE(DNSDomainFromDot("123456789a123456789a123456789a123456789a123456789a123456789a123", &out));
+ EXPECT_EQ(out, "\077123456789a123456789a123456789a123456789a123456789a123456789a123");
+
+ // Label is too long: invalid
+ EXPECT_FALSE(DNSDomainFromDot("123456789a123456789a123456789a123456789a123456789a123456789a1234", &out));
+
+ // 253 characters in the name: still valid
+ EXPECT_TRUE(DNSDomainFromDot("123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123", &out));
+ EXPECT_EQ(out, "\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\003123");
+
+ // 254 characters in the name: invalid
+ EXPECT_FALSE(DNSDomainFromDot("123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.1234", &out));
+
+ // Zero length labels should be dropped.
+ EXPECT_TRUE(DNSDomainFromDot("www.google.com.", &out));
+ EXPECT_EQ(out, "\003www\006google\003com");
+
+ EXPECT_TRUE(DNSDomainFromDot(".google.com", &out));
+ EXPECT_EQ(out, "\006google\003com");
+}
+
+TEST_F(DNSUtilTest, STD3ASCII) {
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('a'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('b'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('c'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('1'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('2'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('3'));
+
+ EXPECT_FALSE(IsSTD3ASCIIValidCharacter('.'));
+ EXPECT_FALSE(IsSTD3ASCIIValidCharacter('/'));
+ EXPECT_FALSE(IsSTD3ASCIIValidCharacter('\x00'));
+}
+
+} // namespace net
diff --git a/net/base/strict_transport_security_state.cc b/net/base/strict_transport_security_state.cc
index ac0b9fe..fc267c5 100644
--- a/net/base/strict_transport_security_state.cc
+++ b/net/base/strict_transport_security_state.cc
@@ -8,11 +8,13 @@
#include "base/json_writer.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
+#include "base/sha2.h"
#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "base/values.h"
#include "googleurl/src/gurl.h"
-#include "net/base/registry_controlled_domain.h"
+#include "net/base/base64.h"
+#include "net/base/dns_util.h"
namespace net {
@@ -36,33 +38,54 @@ void StrictTransportSecurityState::DidReceiveHeader(const GURL& url,
}
void StrictTransportSecurityState::EnableHost(const std::string& host,
- base::Time expiry,
- bool include_subdomains) {
- // TODO(abarth): Canonicalize host.
+ base::Time expiry,
+ bool include_subdomains) {
+ const std::string canonicalised_host = CanonicaliseHost(host);
+ if (canonicalised_host.empty())
+ return;
+ char hashed[base::SHA256_LENGTH];
+ base::SHA256HashString(canonicalised_host, hashed, sizeof(hashed));
+
AutoLock lock(lock_);
State state = {expiry, include_subdomains};
- enabled_hosts_[host] = state;
+ enabled_hosts_[std::string(hashed, sizeof(hashed))] = state;
DirtyNotify();
}
bool StrictTransportSecurityState::IsEnabledForHost(const std::string& host) {
- // TODO(abarth): Canonicalize host.
- // TODO: check for subdomains too.
-
- AutoLock lock(lock_);
- std::map<std::string, State>::iterator i = enabled_hosts_.find(host);
- if (i == enabled_hosts_.end())
+ const std::string canonicalised_host = CanonicaliseHost(host);
+ if (canonicalised_host.empty())
return false;
base::Time current_time(base::Time::Now());
- if (current_time > i->second.expiry) {
- enabled_hosts_.erase(i);
- DirtyNotify();
- return false;
+ AutoLock lock(lock_);
+
+ for (size_t i = 0; canonicalised_host[i]; i += canonicalised_host[i] + 1) {
+ char hashed_domain[base::SHA256_LENGTH];
+
+ base::SHA256HashString(&canonicalised_host[i], &hashed_domain,
+ sizeof(hashed_domain));
+ std::map<std::string, State>::iterator j =
+ enabled_hosts_.find(std::string(hashed_domain, sizeof(hashed_domain)));
+ if (j == enabled_hosts_.end())
+ continue;
+
+ if (current_time > j->second.expiry) {
+ enabled_hosts_.erase(j);
+ DirtyNotify();
+ continue;
+ }
+
+ // If we matched the domain exactly, it doesn't matter what the value of
+ // include_subdomains is.
+ if (i == 0)
+ return true;
+
+ return j->second.include_subdomains;
}
- return true;
+ return false;
}
// "Strict-Transport-Security" ":"
@@ -171,6 +194,27 @@ void StrictTransportSecurityState::SetDelegate(
delegate_ = delegate;
}
+// 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::wstring HashedDomainToExternalString(const std::string& hashed) {
+ std::string out;
+ CHECK(Base64Encode(hashed, &out));
+ return ASCIIToWide(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::wstring& external) {
+ std::string external_ascii = WideToASCII(external);
+ std::string out;
+ if (!Base64Decode(external_ascii, &out) ||
+ out.size() != base::SHA256_LENGTH) {
+ return std::string();
+ }
+
+ return out;
+}
+
bool StrictTransportSecurityState::Serialise(std::string* output) {
AutoLock lock(lock_);
@@ -181,7 +225,7 @@ bool StrictTransportSecurityState::Serialise(std::string* output) {
state->SetBoolean(L"include_subdomains", i->second.include_subdomains);
state->SetReal(L"expiry", i->second.expiry.ToDoubleT());
- toplevel.Set(ASCIIToWide(i->first), state);
+ toplevel.Set(HashedDomainToExternalString(i->first), state);
}
JSONWriter::Write(&toplevel, true /* pretty print */, output);
@@ -207,7 +251,6 @@ bool StrictTransportSecurityState::Deserialise(const std::string& input) {
if (!dict_value->GetDictionary(*i, &state))
continue;
- const std::string host = WideToASCII(*i);
bool include_subdomains;
double expiry;
@@ -220,11 +263,15 @@ bool StrictTransportSecurityState::Deserialise(const std::string& input) {
if (expiry_time <= current_time)
continue;
+ std::string hashed = ExternalStringToHashedDomain(*i);
+ if (hashed.empty())
+ continue;
+
State new_state = { expiry_time, include_subdomains };
- enabled_hosts_[host] = new_state;
+ enabled_hosts_[hashed] = new_state;
}
- return enabled_hosts_.size() > 0;
+ return true;
}
void StrictTransportSecurityState::DirtyNotify() {
@@ -232,4 +279,40 @@ void StrictTransportSecurityState::DirtyNotify() {
delegate_->StateIsDirty(this);
}
+// static
+std::string StrictTransportSecurityState::CanonicaliseHost(
+ const std::string& host) {
+ // We cannot perform the operations as detailed in the spec here as |host|
+ // has already undergone IDN processing before it reached us. Thus, we check
+ // that there are no invalid characters in the host and lowercase the result.
+
+ std::string new_host;
+ if (!DNSDomainFromDot(host, &new_host)) {
+ NOTREACHED();
+ return std::string();
+ }
+
+ for (size_t i = 0; new_host[i]; i += new_host[i] + 1) {
+ const unsigned label_length = static_cast<unsigned>(new_host[i]);
+ if (!label_length)
+ break;
+
+ for (size_t j = 0; j < label_length; ++j) {
+ // RFC 3490, 4.1, step 3
+ if (!IsSTD3ASCIIValidCharacter(new_host[i + 1 + j]))
+ return std::string();
+
+ new_host[i + 1 + j] = tolower(new_host[i + 1 + j]);
+ }
+
+ // step 3(b)
+ if (new_host[i + 1] == '-' ||
+ new_host[i + label_length] == '-') {
+ return std::string();
+ }
+ }
+
+ return new_host;
+}
+
} // namespace
diff --git a/net/base/strict_transport_security_state.h b/net/base/strict_transport_security_state.h
index b41be1e..463382c 100644
--- a/net/base/strict_transport_security_state.h
+++ b/net/base/strict_transport_security_state.h
@@ -70,7 +70,9 @@ class StrictTransportSecurityState :
// our state is dirty.
void DirtyNotify();
- // The set of hosts that have enabled StrictTransportSecurity.
+ // The set of hosts that have enabled StrictTransportSecurity. The keys here
+ // are SHA256(DNSForm(domain)) where DNSForm converts from dotted form
+ // ('www.google.com') to the form used in DNS: "\x03www\x06google\x03com"
std::map<std::string, State> enabled_hosts_;
// Protect access to our data members with this lock.
@@ -79,6 +81,8 @@ class StrictTransportSecurityState :
// Our delegate who gets notified when we are dirtied, or NULL.
Delegate* delegate_;
+ static std::string CanonicaliseHost(const std::string& host);
+
DISALLOW_COPY_AND_ASSIGN(StrictTransportSecurityState);
};
diff --git a/net/base/strict_transport_security_state_unittest.cc b/net/base/strict_transport_security_state_unittest.cc
index 0077a8c..5ebd358 100644
--- a/net/base/strict_transport_security_state_unittest.cc
+++ b/net/base/strict_transport_security_state_unittest.cc
@@ -5,8 +5,6 @@
#include "net/base/strict_transport_security_state.h"
#include "testing/gtest/include/gtest/gtest.h"
-namespace {
-
class StrictTransportSecurityStateTest : public testing::Test {
};
@@ -130,4 +128,79 @@ TEST_F(StrictTransportSecurityStateTest, ValidHeaders) {
EXPECT_TRUE(include_subdomains);
}
-} // namespace
+TEST_F(StrictTransportSecurityStateTest, SimpleMatches) {
+ scoped_refptr<net::StrictTransportSecurityState> state(
+ new net::StrictTransportSecurityState);
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state->IsEnabledForHost("google.com"));
+ state->EnableHost("google.com", expiry, false);
+ EXPECT_TRUE(state->IsEnabledForHost("google.com"));
+}
+
+TEST_F(StrictTransportSecurityStateTest, MatchesCase1) {
+ scoped_refptr<net::StrictTransportSecurityState> state(
+ new net::StrictTransportSecurityState);
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state->IsEnabledForHost("google.com"));
+ state->EnableHost("GOOgle.coM", expiry, false);
+ EXPECT_TRUE(state->IsEnabledForHost("google.com"));
+}
+
+TEST_F(StrictTransportSecurityStateTest, MatchesCase2) {
+ scoped_refptr<net::StrictTransportSecurityState> state(
+ new net::StrictTransportSecurityState);
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state->IsEnabledForHost("GOOgle.coM"));
+ state->EnableHost("google.com", expiry, false);
+ EXPECT_TRUE(state->IsEnabledForHost("GOOgle.coM"));
+}
+
+TEST_F(StrictTransportSecurityStateTest, SubdomainMatches) {
+ scoped_refptr<net::StrictTransportSecurityState> state(
+ new net::StrictTransportSecurityState);
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state->IsEnabledForHost("google.com"));
+ state->EnableHost("google.com", expiry, true);
+ EXPECT_TRUE(state->IsEnabledForHost("google.com"));
+ EXPECT_TRUE(state->IsEnabledForHost("foo.google.com"));
+ EXPECT_TRUE(state->IsEnabledForHost("foo.bar.google.com"));
+ EXPECT_TRUE(state->IsEnabledForHost("foo.bar.baz.google.com"));
+ EXPECT_FALSE(state->IsEnabledForHost("com"));
+}
+
+TEST_F(StrictTransportSecurityStateTest, Serialise1) {
+ scoped_refptr<net::StrictTransportSecurityState> state(
+ new net::StrictTransportSecurityState);
+ std::string output;
+ state->Serialise(&output);
+ EXPECT_TRUE(state->Deserialise(output));
+}
+
+TEST_F(StrictTransportSecurityStateTest, Serialise2) {
+ scoped_refptr<net::StrictTransportSecurityState> state(
+ new net::StrictTransportSecurityState);
+
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state->IsEnabledForHost("google.com"));
+ state->EnableHost("google.com", expiry, true);
+
+ std::string output;
+ state->Serialise(&output);
+ EXPECT_TRUE(state->Deserialise(output));
+
+ EXPECT_TRUE(state->IsEnabledForHost("google.com"));
+ EXPECT_TRUE(state->IsEnabledForHost("foo.google.com"));
+ EXPECT_TRUE(state->IsEnabledForHost("foo.bar.google.com"));
+ EXPECT_TRUE(state->IsEnabledForHost("foo.bar.baz.google.com"));
+ EXPECT_FALSE(state->IsEnabledForHost("com"));
+}
diff --git a/net/net.gyp b/net/net.gyp
index fb48e11..837cd13 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -52,6 +52,8 @@
'base/data_url.h',
'base/directory_lister.cc',
'base/directory_lister.h',
+ 'base/dns_util.cc',
+ 'base/dns_util.h',
'base/effective_tld_names.cc',
'base/effective_tld_names.dat',
'base/escape.cc',
@@ -450,6 +452,7 @@
'base/cookie_policy_unittest.cc',
'base/data_url_unittest.cc',
'base/directory_lister_unittest.cc',
+ 'base/dns_util_unittest.cc',
'base/escape_unittest.cc',
'base/file_stream_unittest.cc',
'base/filter_unittest.cc',