summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome_frame/test/net/fake_external_tab.cc2
-rw-r--r--net/data/url_request_unittest/hpkp-headers.html1
-rw-r--r--net/data/url_request_unittest/hpkp-headers.html.mock-http-headers6
-rw-r--r--net/data/url_request_unittest/hsts-and-hpkp-headers2.html1
-rw-r--r--net/data/url_request_unittest/hsts-and-hpkp-headers2.html.mock-http-headers8
-rw-r--r--net/http/http_security_headers.cc5
-rw-r--r--net/http/http_security_headers.h19
-rw-r--r--net/http/http_security_headers_unittest.cc194
-rw-r--r--net/http/transport_security_state.cc65
-rw-r--r--net/http/transport_security_state.h19
-rw-r--r--net/url_request/url_request_unittest.cc85
11 files changed, 336 insertions, 69 deletions
diff --git a/chrome_frame/test/net/fake_external_tab.cc b/chrome_frame/test/net/fake_external_tab.cc
index 8ec91be..90ef166 100644
--- a/chrome_frame/test/net/fake_external_tab.cc
+++ b/chrome_frame/test/net/fake_external_tab.cc
@@ -257,9 +257,11 @@ void FilterDisabledTests() {
// certs. So these tests time out waiting for user input. The
// functionality they test (HTTP Strict Transport Security and
// HTTP-based Public Key Pinning) does not work in Chrome Frame anyway.
+ "URLRequestTestHTTP.ProcessPKP",
"URLRequestTestHTTP.ProcessSTS",
"URLRequestTestHTTP.ProcessSTSOnce",
"URLRequestTestHTTP.ProcessSTSAndPKP",
+ "URLRequestTestHTTP.ProcessSTSAndPKP2",
// These tests have been disabled as the Chrome cookie policies don't make
// sense or have not been implemented for the host network stack.
diff --git a/net/data/url_request_unittest/hpkp-headers.html b/net/data/url_request_unittest/hpkp-headers.html
new file mode 100644
index 0000000..364322d
--- /dev/null
+++ b/net/data/url_request_unittest/hpkp-headers.html
@@ -0,0 +1 @@
+This file is boring; all the action's in the .mock-http-headers.
diff --git a/net/data/url_request_unittest/hpkp-headers.html.mock-http-headers b/net/data/url_request_unittest/hpkp-headers.html.mock-http-headers
new file mode 100644
index 0000000..f491840
--- /dev/null
+++ b/net/data/url_request_unittest/hpkp-headers.html.mock-http-headers
@@ -0,0 +1,6 @@
+HTTP/1.1 200 OK
+Cache-Control: private
+Content-Type: text/html; charset=ISO-8859-1
+X-Multiple-Entries: a
+X-Multiple-Entries: b
+Public-Key-Pins: max-age=50000; pin-sha1="K9e3/nFL5j90GuVJOJBv6WXpvcs="; pin-sha256="kd16uBd5KFa9IJjF0X+8B+BXdAWkYYRZruNKDZ0M9Zw="; pin-sha1="Wws2/Z7YhKlX73v3rYHBBxO4OLE="
diff --git a/net/data/url_request_unittest/hsts-and-hpkp-headers2.html b/net/data/url_request_unittest/hsts-and-hpkp-headers2.html
new file mode 100644
index 0000000..364322d
--- /dev/null
+++ b/net/data/url_request_unittest/hsts-and-hpkp-headers2.html
@@ -0,0 +1 @@
+This file is boring; all the action's in the .mock-http-headers.
diff --git a/net/data/url_request_unittest/hsts-and-hpkp-headers2.html.mock-http-headers b/net/data/url_request_unittest/hsts-and-hpkp-headers2.html.mock-http-headers
new file mode 100644
index 0000000..9c0feda
--- /dev/null
+++ b/net/data/url_request_unittest/hsts-and-hpkp-headers2.html.mock-http-headers
@@ -0,0 +1,8 @@
+HTTP/1.1 200 OK
+Cache-Control: private
+Content-Type: text/html; charset=ISO-8859-1
+X-Multiple-Entries: a
+X-Multiple-Entries: b
+Strict-Transport-Security: max-age=12300; includeSubdomains
+Public-Key-Pins: max-age=50000; pin-sha1="K9e3/nFL5j90GuVJOJBv6WXpvcs="; pin-sha256="kd16uBd5KFa9IJjF0X+8B+BXdAWkYYRZruNKDZ0M9Zw="; pin-sha1="Wws2/Z7YhKlX73v3rYHBBxO4OLE="
+Public-Key-Pins: max-age=50000; pin-sha1="K9e3/nFL5j90GuVJOJBv6WXpvcs="; pin-sha256="kd16uBd5KFa9IJjF0X+8B+BXdAWkYYRZruNKDZ0M9Zw="; pin-sha1="Wws2/Z7YhKlX73v3rYHBBxO4OLE="; includeSubdomains
diff --git a/net/http/http_security_headers.cc b/net/http/http_security_headers.cc
index 0540bc7..9fc7627 100644
--- a/net/http/http_security_headers.cc
+++ b/net/http/http_security_headers.cc
@@ -276,8 +276,10 @@ bool ParseHSTSHeader(const std::string& value,
bool ParseHPKPHeader(const std::string& value,
const HashValueVector& chain_hashes,
base::TimeDelta* max_age,
+ bool* include_subdomains,
HashValueVector* hashes) {
bool parsed_max_age = false;
+ bool include_subdomains_candidate = false;
uint32 max_age_candidate = 0;
HashValueVector pins;
@@ -304,6 +306,8 @@ bool ParseHPKPHeader(const std::string& value,
} else if (LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
if (!ParseAndAppendPin(equals.second, HASH_VALUE_SHA256, &pins))
return false;
+ } else if (LowerCaseEqualsASCII(equals.first, "includesubdomains")) {
+ include_subdomains_candidate = true;
} else {
// Silently ignore unknown directives for forward compatibility.
}
@@ -318,6 +322,7 @@ bool ParseHPKPHeader(const std::string& value,
return false;
*max_age = base::TimeDelta::FromSeconds(max_age_candidate);
+ *include_subdomains = include_subdomains_candidate;
for (HashValueVector::const_iterator i = pins.begin();
i != pins.end(); ++i) {
hashes->push_back(*i);
diff --git a/net/http/http_security_headers.h b/net/http/http_security_headers.h
index b000971..12e6be9 100644
--- a/net/http/http_security_headers.h
+++ b/net/http/http_security_headers.h
@@ -30,25 +30,28 @@ bool NET_EXPORT_PRIVATE ParseHSTSHeader(const std::string& value,
base::TimeDelta* max_age,
bool* include_subdomains);
-// Parses |value| as a Public-Key-Pins header value. If successful,
-// returns true and populates the |*max_age| and hashes values.
-// Otherwise returns false and leaves the output parameters unchanged.
+// Parses |value| as a Public-Key-Pins header value. If successful, returns
+// true and populates the |*max_age|, |*include_subdomains|, and |*hashes|
+// values. Otherwise returns false and leaves the output parameters
+// unchanged.
//
// value is the right-hand side of:
//
// "Public-Key-Pins" ":"
// "max-age" "=" delta-seconds ";"
// "pin-" algo "=" base64 [ ";" ... ]
+// [ ";" "includeSubdomains" ]
//
// For this function to return true, the key hashes specified by the HPKP
-// header must pass two additional checks. There MUST be at least one
-// key hash which matches the SSL certificate chain of the current site
-// (as specified by the chain_hashes) parameter. In addition, there MUST
-// be at least one key hash which does NOT match the site's SSL certificate
-// chain (this is the "backup pin").
+// header must pass two additional checks. There MUST be at least one key
+// hash which matches the SSL certificate chain of the current site (as
+// specified by the chain_hashes) parameter. In addition, there MUST be at
+// least one key hash which does NOT match the site's SSL certificate chain
+// (this is the "backup pin").
bool NET_EXPORT_PRIVATE ParseHPKPHeader(const std::string& value,
const HashValueVector& chain_hashes,
base::TimeDelta* max_age,
+ bool* include_subdomains,
HashValueVector* hashes);
} // namespace net
diff --git a/net/http/http_security_headers_unittest.cc b/net/http/http_security_headers_unittest.cc
index 0dd286b..0cc81b5 100644
--- a/net/http/http_security_headers_unittest.cc
+++ b/net/http/http_security_headers_unittest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <algorithm>
+
#include "base/base64.h"
#include "base/sha1.h"
#include "base/strings/string_piece.h"
@@ -10,6 +12,8 @@
#include "net/base/test_completion_callback.h"
#include "net/http/http_security_headers.h"
#include "net/http/http_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/ssl/ssl_info.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
@@ -119,6 +123,7 @@ TEST_F(HttpSecurityHeadersTest, BogusHeaders) {
static void TestBogusPinsHeaders(HashValueTag tag) {
base::TimeDelta max_age;
+ bool include_subdomains;
HashValueVector hashes;
HashValueVector chain_hashes;
@@ -131,64 +136,75 @@ static void TestBogusPinsHeaders(HashValueTag tag) {
std::string good_pin = GetTestPin(2, tag);
std::string backup_pin = GetTestPin(4, tag);
- EXPECT_FALSE(
- ParseHPKPHeader(std::string(), chain_hashes, &max_age, &hashes));
- EXPECT_FALSE(ParseHPKPHeader(" ", chain_hashes, &max_age, &hashes));
- EXPECT_FALSE(ParseHPKPHeader("abc", chain_hashes, &max_age, &hashes));
- EXPECT_FALSE(ParseHPKPHeader(" abc", chain_hashes, &max_age, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(std::string(), chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" ", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("abc", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" abc", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" abc ", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-age", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" max-age", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" max-age ", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-age=", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" max-age=", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" max-age =", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" max-age= ", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" max-age = ", chain_hashes,
- &max_age, &hashes));
+ &max_age, &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" max-age = xy", chain_hashes,
- &max_age, &hashes));
+ &max_age, &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader(" max-age = 3488a923",
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-age=3488a923 ", chain_hashes,
- &max_age, &hashes));
+ &max_age, &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-ag=3488923pins=" + good_pin + "," +
backup_pin,
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923" + backup_pin,
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923; " + backup_pin,
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923; " + backup_pin + ";" +
backup_pin,
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923; " + good_pin + ";" +
good_pin,
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923; " + good_pin,
- chain_hashes, &max_age, &hashes));
- EXPECT_FALSE(ParseHPKPHeader("max-age==3488923", chain_hashes, &max_age,
+ chain_hashes, &max_age, &include_subdomains,
&hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age==3488923", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader("amax-age=3488923", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-age=-3488923", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-age=3488923;", chain_hashes, &max_age,
- &hashes));
+ &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-age=3488923 e", chain_hashes,
- &max_age, &hashes));
+ &max_age, &include_subdomains, &hashes));
EXPECT_FALSE(ParseHPKPHeader("max-age=3488923 includesubdomain",
- chain_hashes, &max_age, &hashes));
- EXPECT_FALSE(ParseHPKPHeader("max-age=34889.23", chain_hashes, &max_age,
+ chain_hashes, &max_age, &include_subdomains,
&hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age=34889.23", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
// Check the out args were not updated by checking the default
// values for its predictable fields.
@@ -310,9 +326,10 @@ TEST_F(HttpSecurityHeadersTest, ValidSTSHeaders) {
EXPECT_TRUE(include_subdomains);
}
-static void TestValidPinsHeaders(HashValueTag tag) {
+static void TestValidPKPHeaders(HashValueTag tag) {
base::TimeDelta max_age;
base::TimeDelta expect_max_age;
+ bool include_subdomains;
HashValueVector hashes;
HashValueVector chain_hashes;
@@ -327,61 +344,78 @@ static void TestValidPinsHeaders(HashValueTag tag) {
EXPECT_TRUE(ParseHPKPHeader(
"max-age=243; " + good_pin + ";" + backup_pin,
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(243);
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
EXPECT_TRUE(ParseHPKPHeader(
" " + good_pin + "; " + backup_pin + " ; Max-agE = 567",
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(567);
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
EXPECT_TRUE(ParseHPKPHeader(
- good_pin + ";" + backup_pin + " ; mAx-aGe = 890 ",
- chain_hashes, &max_age, &hashes));
+ "includeSubDOMAINS;" + good_pin + ";" + backup_pin +
+ " ; mAx-aGe = 890 ",
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(890);
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
EXPECT_TRUE(ParseHPKPHeader(
good_pin + ";" + backup_pin + "; max-age=123;IGNORED;",
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(123);
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
EXPECT_TRUE(ParseHPKPHeader(
"max-age=394082;" + backup_pin + ";" + good_pin + "; ",
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(394082);
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
EXPECT_TRUE(ParseHPKPHeader(
"max-age=39408299 ;" + backup_pin + ";" + good_pin + "; ",
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(
std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(39408299))));
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
EXPECT_TRUE(ParseHPKPHeader(
- "max-age=39408038 ; cybers=39408038 ; " +
+ "max-age=39408038 ; cybers=39408038 ; includeSubdomains; " +
good_pin + ";" + backup_pin + "; ",
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(
std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(394082038))));
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
EXPECT_TRUE(ParseHPKPHeader(
" max-age=0 ; " + good_pin + ";" + backup_pin,
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(0);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ " max-age=0 ; includeSubdomains; " + good_pin + ";" + backup_pin,
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(0);
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
EXPECT_TRUE(ParseHPKPHeader(
" max-age=999999999999999999999999999999999999999999999 ; " +
backup_pin + ";" + good_pin + "; ",
- chain_hashes, &max_age, &hashes));
+ chain_hashes, &max_age, &include_subdomains, &hashes));
expect_max_age = base::TimeDelta::FromSeconds(kMaxHSTSAgeSecs);
EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
}
TEST_F(HttpSecurityHeadersTest, BogusPinsHeadersSHA1) {
@@ -392,12 +426,80 @@ TEST_F(HttpSecurityHeadersTest, BogusPinsHeadersSHA256) {
TestBogusPinsHeaders(HASH_VALUE_SHA256);
}
-TEST_F(HttpSecurityHeadersTest, ValidPinsHeadersSHA1) {
- TestValidPinsHeaders(HASH_VALUE_SHA1);
+TEST_F(HttpSecurityHeadersTest, ValidPKPHeadersSHA1) {
+ TestValidPKPHeaders(HASH_VALUE_SHA1);
+}
+
+TEST_F(HttpSecurityHeadersTest, ValidPKPHeadersSHA256) {
+ TestValidPKPHeaders(HASH_VALUE_SHA256);
}
-TEST_F(HttpSecurityHeadersTest, ValidPinsHeadersSHA256) {
- TestValidPinsHeaders(HASH_VALUE_SHA256);
+TEST_F(HttpSecurityHeadersTest, UpdateDynamicPKPOnly) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+
+ // docs.google.com has preloaded pins.
+ std::string domain = "docs.google.com";
+ EXPECT_TRUE(state.GetDomainState(domain, true, &domain_state));
+ EXPECT_GT(domain_state.static_spki_hashes.size(), 1UL);
+ HashValueVector saved_hashes = domain_state.static_spki_hashes;
+
+ // Add a header, which should only update the dynamic state.
+ HashValue good_hash = GetTestHashValue(1, HASH_VALUE_SHA1);
+ HashValue backup_hash = GetTestHashValue(2, HASH_VALUE_SHA1);
+ std::string good_pin = GetTestPin(1, HASH_VALUE_SHA1);
+ std::string backup_pin = GetTestPin(2, HASH_VALUE_SHA1);
+ std::string header = "max-age = 10000; " + good_pin + "; " + backup_pin;
+
+ // Construct a fake SSLInfo that will pass AddHPKPHeader's checks.
+ SSLInfo ssl_info;
+ ssl_info.public_key_hashes.push_back(good_hash);
+ ssl_info.public_key_hashes.push_back(saved_hashes[0]);
+ EXPECT_TRUE(state.AddHPKPHeader(domain, header, ssl_info));
+
+ // Expect the preloaded state to remain unchanged.
+ std::string canonicalized_host = TransportSecurityState::CanonicalizeHost(
+ domain);
+ TransportSecurityState::DomainState static_domain_state;
+ EXPECT_TRUE(state.GetStaticDomainState(canonicalized_host,
+ true,
+ &static_domain_state));
+ for (size_t i = 0; i < saved_hashes.size(); ++i) {
+ EXPECT_TRUE(HashValuesEqual(
+ saved_hashes[i])(static_domain_state.static_spki_hashes[i]));
+ }
+
+ // Expect the dynamic state to reflect the header.
+ TransportSecurityState::DomainState dynamic_domain_state;
+ EXPECT_TRUE(state.GetDynamicDomainState(domain, &dynamic_domain_state));
+ EXPECT_EQ(2UL, dynamic_domain_state.dynamic_spki_hashes.size());
+
+ HashValueVector::const_iterator hash = std::find_if(
+ dynamic_domain_state.dynamic_spki_hashes.begin(),
+ dynamic_domain_state.dynamic_spki_hashes.end(),
+ HashValuesEqual(good_hash));
+ EXPECT_NE(dynamic_domain_state.dynamic_spki_hashes.end(), hash);
+
+ hash = std::find_if(
+ dynamic_domain_state.dynamic_spki_hashes.begin(),
+ dynamic_domain_state.dynamic_spki_hashes.end(),
+ HashValuesEqual(backup_hash));
+ EXPECT_NE(dynamic_domain_state.dynamic_spki_hashes.end(), hash);
+
+ // Expect the overall state to reflect the header, too.
+ EXPECT_TRUE(state.GetDomainState(domain, true, &domain_state));
+ EXPECT_EQ(2UL, domain_state.dynamic_spki_hashes.size());
+
+ hash = std::find_if(domain_state.dynamic_spki_hashes.begin(),
+ domain_state.dynamic_spki_hashes.end(),
+ HashValuesEqual(good_hash));
+ EXPECT_NE(domain_state.dynamic_spki_hashes.end(), hash);
+
+ hash = std::find_if(
+ domain_state.dynamic_spki_hashes.begin(),
+ domain_state.dynamic_spki_hashes.end(),
+ HashValuesEqual(backup_hash));
+ EXPECT_NE(domain_state.dynamic_spki_hashes.end(), hash);
}
}; // namespace net
diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc
index 92a223c..f2282ed 100644
--- a/net/http/transport_security_state.cc
+++ b/net/http/transport_security_state.cc
@@ -109,17 +109,7 @@ void TransportSecurityState::EnableHost(const std::string& host,
if (canonicalized_host.empty())
return;
- DomainState existing_state;
-
- // Use the original creation date if we already have this host. (But note
- // that statically-defined states have no |created| date. Therefore, we do
- // not bother to search the SNI-only static states.)
DomainState state_copy(state);
- if (GetDomainState(host, false /* sni_enabled */, &existing_state) &&
- !existing_state.created.is_null()) {
- state_copy.created = existing_state.created;
- }
-
// No need to store this value since it is redundant. (|canonicalized_host|
// is the map key.)
state_copy.domain.clear();
@@ -158,6 +148,7 @@ bool TransportSecurityState::GetDomainState(const std::string& host,
bool has_preload = GetStaticDomainState(canonicalized_host, sni_enabled,
&state);
std::string canonicalized_preload = CanonicalizeHost(state.domain);
+ GetDynamicDomainState(host, &state);
base::Time current_time(base::Time::Now());
@@ -625,6 +616,7 @@ bool TransportSecurityState::AddHSTSHeader(const std::string& host,
base::Time now = base::Time::Now();
base::TimeDelta max_age;
TransportSecurityState::DomainState domain_state;
+ GetDynamicDomainState(host, &domain_state);
if (ParseHSTSHeader(value, &max_age, &domain_state.sts_include_subdomains)) {
// Handle max-age == 0
if (max_age.InSeconds() == 0)
@@ -647,10 +639,11 @@ bool TransportSecurityState::AddHPKPHeader(const std::string& host,
base::Time now = base::Time::Now();
base::TimeDelta max_age;
TransportSecurityState::DomainState domain_state;
+ GetDynamicDomainState(host, &domain_state);
if (ParseHPKPHeader(value, ssl_info.public_key_hashes,
- &max_age, &domain_state.dynamic_spki_hashes)) {
- // TODO(palmer): http://crbug.com/243865 handle max-age == 0
- // and includeSubdomains.
+ &max_age, &domain_state.pkp_include_subdomains,
+ &domain_state.dynamic_spki_hashes)) {
+ // TODO(palmer): http://crbug.com/243865 handle max-age == 0.
domain_state.created = now;
domain_state.dynamic_spki_hashes_expiry = now + max_age;
EnableHost(host, domain_state);
@@ -789,6 +782,50 @@ bool TransportSecurityState::GetStaticDomainState(
return false;
}
+bool TransportSecurityState::GetDynamicDomainState(const std::string& host,
+ DomainState* result) {
+ DCHECK(CalledOnValidThread());
+
+ DomainState state;
+ const std::string canonicalized_host = CanonicalizeHost(host);
+ if (canonicalized_host.empty())
+ return false;
+
+ base::Time current_time(base::Time::Now());
+
+ 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);
+ DomainStateMap::iterator j =
+ enabled_hosts_.find(HashHost(host_sub_chunk));
+ if (j == enabled_hosts_.end())
+ continue;
+
+ if (current_time > j->second.upgrade_expiry &&
+ current_time > j->second.dynamic_spki_hashes_expiry) {
+ enabled_hosts_.erase(j);
+ DirtyNotify();
+ continue;
+ }
+
+ state = j->second;
+ state.domain = DNSDomainToString(host_sub_chunk);
+
+ // Succeed if we matched the domain exactly or if subdomain matches are
+ // allowed.
+ if (i == 0 || j->second.sts_include_subdomains ||
+ j->second.pkp_include_subdomains) {
+ *result = state;
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+
void TransportSecurityState::AddOrUpdateEnabledHosts(
const std::string& hashed_host, const DomainState& state) {
DCHECK(CalledOnValidThread());
@@ -796,7 +833,7 @@ void TransportSecurityState::AddOrUpdateEnabledHosts(
}
TransportSecurityState::DomainState::DomainState()
- : upgrade_mode(MODE_FORCE_HTTPS),
+ : upgrade_mode(MODE_DEFAULT),
created(base::Time::Now()),
sts_include_subdomains(false),
pkp_include_subdomains(false) {
diff --git a/net/http/transport_security_state.h b/net/http/transport_security_state.h
index 60df04a..ccbc53a 100644
--- a/net/http/transport_security_state.h
+++ b/net/http/transport_security_state.h
@@ -136,7 +136,8 @@ class NET_EXPORT TransportSecurityState
// The following members are not valid when stored in |enabled_hosts_|:
// The domain which matched during a search for this DomainState entry.
- // Updated by |GetDomainState| and |GetStaticDomainState|.
+ // Updated by |GetDomainState|, |GetDynamicDomainState|, and
+ // |GetStaticDomainState|.
std::string domain;
};
@@ -261,6 +262,8 @@ class NET_EXPORT TransportSecurityState
private:
friend class TransportSecurityStateTest;
+ FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest,
+ UpdateDynamicPKPOnly);
typedef std::map<std::string, DomainState> DomainStateMap;
@@ -298,6 +301,20 @@ class NET_EXPORT TransportSecurityState
bool sni_enabled,
DomainState* result);
+ // Returns true and updates |*result| iff there is a dynamic DomainState for
+ // |host|.
+ //
+ // |GetDynamicDomainState| is identical to |GetDomainState| except that it
+ // searches only the dynamically-added transport security state, ignoring
+ // all statically-defined DomainStates.
+ //
+ // If |host| matches both an exact entry and is a subdomain of another
+ // entry, the exact match determines the return value.
+ //
+ // Note that this method is not const because it opportunistically removes
+ // entries that have expired.
+ bool GetDynamicDomainState(const std::string& host, DomainState* result);
+
// The set of hosts that have enabled TransportSecurity.
DomainStateMap enabled_hosts_;
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index ece4f61..099c5fc 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -3933,6 +3933,53 @@ TEST_F(URLRequestTestHTTP, ProcessSTS) {
domain_state.upgrade_mode);
EXPECT_TRUE(domain_state.sts_include_subdomains);
EXPECT_FALSE(domain_state.pkp_include_subdomains);
+#if defined(OS_ANDROID)
+ // Android's CertVerifyProc does not (yet) handle pins.
+#else
+ EXPECT_FALSE(domain_state.HasPublicKeyPins());
+#endif
+}
+
+// Android's CertVerifyProc does not (yet) handle pins. Therefore, it will
+// reject HPKP headers, and a test setting only HPKP headers will fail (no
+// DomainState present because header rejected).
+#if defined(OS_ANDROID)
+#define MAYBE_ProcessPKP DISABLED_ProcessPKP
+#else
+#define MAYBE_ProcessPKP ProcessPKP
+#endif
+
+// Tests that enabling HPKP on a domain does not affect the HSTS
+// validity/expiration.
+TEST_F(URLRequestTestHTTP, MAYBE_ProcessPKP) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ SpawnedTestServer https_test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hpkp-headers.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ base::MessageLoop::current()->Run();
+
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ SpawnedTestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_DEFAULT,
+ domain_state.upgrade_mode);
+ EXPECT_FALSE(domain_state.sts_include_subdomains);
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+ EXPECT_TRUE(domain_state.HasPublicKeyPins());
+ EXPECT_NE(domain_state.upgrade_expiry,
+ domain_state.dynamic_spki_hashes_expiry);
}
TEST_F(URLRequestTestHTTP, ProcessSTSOnce) {
@@ -4004,6 +4051,44 @@ TEST_F(URLRequestTestHTTP, ProcessSTSAndPKP) {
EXPECT_FALSE(domain_state.pkp_include_subdomains);
}
+// Tests that when multiple HPKP headers are present, asserting different
+// policies, that only the first such policy is processed.
+TEST_F(URLRequestTestHTTP, ProcessSTSAndPKP2) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ SpawnedTestServer https_test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hsts-and-hpkp-headers2.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ base::MessageLoop::current()->Run();
+
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ SpawnedTestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_FORCE_HTTPS,
+ domain_state.upgrade_mode);
+#if defined(OS_ANDROID)
+ // Android's CertVerifyProc does not (yet) handle pins.
+#else
+ EXPECT_TRUE(domain_state.HasPublicKeyPins());
+#endif
+ EXPECT_NE(domain_state.upgrade_expiry,
+ domain_state.dynamic_spki_hashes_expiry);
+
+ EXPECT_TRUE(domain_state.sts_include_subdomains);
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+}
+
TEST_F(URLRequestTestHTTP, ContentTypeNormalizationTest) {
ASSERT_TRUE(test_server_.Start());