summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorestark <estark@chromium.org>2015-08-03 11:31:51 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-03 18:32:37 +0000
commit53fee7cfec8bf6426d2ed3d8117ddc08f42e9d87 (patch)
treeee2fc289d558ededff5df60eac27362ab37eaab1
parenta90e6c95e36bc873fb2c276938e88b16611ce1d9 (diff)
downloadchromium_src-53fee7cfec8bf6426d2ed3d8117ddc08f42e9d87.zip
chromium_src-53fee7cfec8bf6426d2ed3d8117ddc08f42e9d87.tar.gz
chromium_src-53fee7cfec8bf6426d2ed3d8117ddc08f42e9d87.tar.bz2
Process Public-Key-Pin-Report-Only headers
This CL adds a call to parse and process HPKP-RO headers in URLRequestHTTPJob. This is based on top of crrev.com/1267513003 BUG=445793 Review URL: https://codereview.chromium.org/1266723003 Cr-Commit-Position: refs/heads/master@{#341563}
-rw-r--r--net/http/transport_security_state.cc91
-rw-r--r--net/http/transport_security_state.h8
-rw-r--r--net/http/transport_security_state_unittest.cc207
-rw-r--r--net/url_request/url_request_http_job.cc9
4 files changed, 251 insertions, 64 deletions
diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc
index 50e9c39..b343223 100644
--- a/net/http/transport_security_state.cc
+++ b/net/http/transport_security_state.cc
@@ -135,6 +135,38 @@ bool GetHPKPReport(const HostPortPair& host_port_pair,
return true;
}
+bool CheckPinsAndMaybeSendReport(
+ const HostPortPair& host_port_pair,
+ const TransportSecurityState::PKPState& pkp_state,
+ const HashValueVector& hashes,
+ const X509Certificate* served_certificate_chain,
+ const X509Certificate* validated_certificate_chain,
+ const TransportSecurityState::PublicKeyPinReportStatus report_status,
+ TransportSecurityState::ReportSender* report_sender,
+ std::string* failure_log) {
+ if (pkp_state.CheckPublicKeyPins(hashes, failure_log))
+ return true;
+
+ if (!report_sender ||
+ report_status != TransportSecurityState::ENABLE_PIN_REPORTS ||
+ pkp_state.report_uri.is_empty()) {
+ return false;
+ }
+
+ DCHECK(pkp_state.report_uri.is_valid());
+
+ std::string serialized_report;
+
+ if (!GetHPKPReport(host_port_pair, pkp_state, served_certificate_chain,
+ validated_certificate_chain, &serialized_report)) {
+ return false;
+ }
+
+ report_sender->Send(pkp_state.report_uri, serialized_report);
+
+ return false;
+}
+
std::string HashesToBase64String(const HashValueVector& hashes) {
std::string str;
for (size_t i = 0; i != hashes.size(); ++i) {
@@ -873,6 +905,42 @@ void TransportSecurityState::AddHPKP(const std::string& host,
report_uri);
}
+bool TransportSecurityState::ProcessHPKPReportOnlyHeader(
+ const std::string& value,
+ const HostPortPair& host_port_pair,
+ const SSLInfo& ssl_info) {
+ DCHECK(CalledOnValidThread());
+
+ base::Time now = base::Time::Now();
+ bool include_subdomains;
+ HashValueVector spki_hashes;
+ GURL report_uri;
+ std::string unused_failure_log;
+
+ if (!ParseHPKPReportOnlyHeader(value, &include_subdomains, &spki_hashes,
+ &report_uri) ||
+ !report_uri.is_valid() || report_uri.is_empty())
+ return false;
+
+ PKPState pkp_state;
+ pkp_state.last_observed = now;
+ pkp_state.expiry = now;
+ pkp_state.include_subdomains = include_subdomains;
+ pkp_state.spki_hashes = spki_hashes;
+ pkp_state.report_uri = report_uri;
+ pkp_state.domain = DNSDomainToString(CanonicalizeHost(host_port_pair.host()));
+
+ // Only perform pin validation if the cert chains up to a known root.
+ if (!ssl_info.is_issued_by_known_root)
+ return true;
+
+ CheckPinsAndMaybeSendReport(
+ host_port_pair, pkp_state, ssl_info.public_key_hashes,
+ ssl_info.unverified_cert.get(), ssl_info.cert.get(), ENABLE_PIN_REPORTS,
+ report_sender_, &unused_failure_log);
+ return true;
+}
+
// static
bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host) {
PreloadResult result;
@@ -927,26 +995,9 @@ bool TransportSecurityState::CheckPublicKeyPinsImpl(
return false;
}
- if (pkp_state.CheckPublicKeyPins(hashes, failure_log))
- return true;
-
- if (!report_sender_ || report_status != ENABLE_PIN_REPORTS ||
- pkp_state.report_uri.is_empty()) {
- return false;
- }
-
- DCHECK(pkp_state.report_uri.is_valid());
-
- std::string serialized_report;
-
- if (!GetHPKPReport(host_port_pair, pkp_state, served_certificate_chain,
- validated_certificate_chain, &serialized_report)) {
- return false;
- }
-
- report_sender_->Send(pkp_state.report_uri, serialized_report);
-
- return false;
+ return CheckPinsAndMaybeSendReport(
+ host_port_pair, pkp_state, hashes, served_certificate_chain,
+ validated_certificate_chain, report_status, report_sender_, failure_log);
}
bool TransportSecurityState::GetStaticDomainState(const std::string& host,
diff --git a/net/http/transport_security_state.h b/net/http/transport_security_state.h
index 1c5658a..d7befb7 100644
--- a/net/http/transport_security_state.h
+++ b/net/http/transport_security_state.h
@@ -299,6 +299,14 @@ class NET_EXPORT TransportSecurityState
const HashValueVector& hashes,
const GURL& report_uri);
+ // Parses |value| as a Public-Key-Pins-Report-Only header value and
+ // sends a HPKP report for |host_port_pair| if |ssl_info| violates the
+ // pin. Returns true if |value| parses and includes a valid
+ // report-uri, and false otherwise.
+ bool ProcessHPKPReportOnlyHeader(const std::string& value,
+ const HostPortPair& host_port_pair,
+ const SSLInfo& ssl_info);
+
// Returns true iff we have any static public key pins for the |host| and
// iff its set of required pins is the set we expect for Google
// properties.
diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc
index 31c4062..4c63c06 100644
--- a/net/http/transport_security_state_unittest.cc
+++ b/net/http/transport_security_state_unittest.cc
@@ -42,8 +42,28 @@ namespace net {
namespace {
+const char kHost[] = "example.test";
+const char kSubdomain[] = "foo.example.test";
+const uint16_t kPort = 443;
const char kReportUri[] = "http://example.test/test";
+// kGoodPath is blog.torproject.org.
+const char* const kGoodPath[] = {
+ "sha1/m9lHYJYke9k0GtVZ+bXSQYE8nDI=", "sha1/o5OZxATDsgmwgcIfIWIneMJ0jkw=",
+ "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4=", NULL,
+};
+
+const char kGoodPin1[] = "m9lHYJYke9k0GtVZ+bXSQYE8nDI=";
+const char kGoodPin2[] = "o5OZxATDsgmwgcIfIWIneMJ0jkw=";
+const char kGoodPin3[] = "wHqYaI2J+6sFZAwRfap9ZbjKzE4=";
+
+// kBadPath is plus.google.com via Trustcenter, which is utterly wrong for
+// torproject.org.
+const char* const kBadPath[] = {
+ "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=", "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=",
+ "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=", NULL,
+};
+
// A mock ReportSender that just remembers the latest report
// URI and report to be sent.
class MockCertificateReportSender
@@ -57,6 +77,11 @@ class MockCertificateReportSender
latest_report_ = report;
}
+ void Clear() {
+ latest_report_uri_ = GURL();
+ latest_report_ = std::string();
+ }
+
const GURL& latest_report_uri() { return latest_report_uri_; }
const std::string& latest_report() { return latest_report_; }
@@ -83,14 +108,11 @@ void CompareCertificateChainWithList(
void CheckHPKPReport(
const std::string& report,
const HostPortPair& host_port_pair,
- const base::Time& expiry,
bool include_subdomains,
const std::string& noted_hostname,
const scoped_refptr<X509Certificate>& served_certificate_chain,
const scoped_refptr<X509Certificate>& validated_certificate_chain,
const HashValueVector& known_pins) {
- // TODO(estark): check time in RFC3339 format.
-
scoped_ptr<base::Value> value(base::JSONReader::Read(report));
ASSERT_TRUE(value);
ASSERT_TRUE(value->IsType(base::Value::TYPE_DICTIONARY));
@@ -115,6 +137,17 @@ void CheckHPKPReport(
EXPECT_TRUE(report_dict->GetString("noted-hostname", &report_noted_hostname));
EXPECT_EQ(noted_hostname, report_noted_hostname);
+ // TODO(estark): check times in RFC3339 format.
+
+ std::string report_expiration;
+ EXPECT_TRUE(
+ report_dict->GetString("effective-expiration-date", &report_expiration));
+ EXPECT_FALSE(report_expiration.empty());
+
+ std::string report_date;
+ EXPECT_TRUE(report_dict->GetString("date-time", &report_date));
+ EXPECT_FALSE(report_date.empty());
+
base::ListValue* report_served_certificate_chain;
EXPECT_TRUE(report_dict->GetList("served-certificate-chain",
&report_served_certificate_chain));
@@ -1057,23 +1090,6 @@ static bool AddHash(const std::string& type_and_base64,
}
TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) {
- // kGoodPath is blog.torproject.org.
- static const char* const kGoodPath[] = {
- "sha1/m9lHYJYke9k0GtVZ+bXSQYE8nDI=",
- "sha1/o5OZxATDsgmwgcIfIWIneMJ0jkw=",
- "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4=",
- NULL,
- };
-
- // kBadPath is plus.google.com via Trustcenter, which is utterly wrong for
- // torproject.org.
- static const char* const kBadPath[] = {
- "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=",
- "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=",
- "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=",
- NULL,
- };
-
HashValueVector good_hashes, bad_hashes;
for (size_t i = 0; kGoodPath[i]; i++) {
@@ -1200,12 +1216,9 @@ TEST_F(TransportSecurityStateTest, GooglePinnedProperties) {
}
TEST_F(TransportSecurityStateTest, HPKPReporting) {
- const char kHost[] = "example.test";
- const char kSubdomain[] = "foo.example.test";
- static const uint16_t kPort = 443;
HostPortPair host_port_pair(kHost, kPort);
HostPortPair subdomain_host_port_pair(kSubdomain, kPort);
- GURL report_uri("http://www.example.test/report");
+ GURL report_uri(kReportUri);
// Two dummy certs to use as the server-sent and validated chains. The
// contents don't matter.
scoped_refptr<X509Certificate> cert1 =
@@ -1215,19 +1228,6 @@ TEST_F(TransportSecurityStateTest, HPKPReporting) {
ASSERT_TRUE(cert1);
ASSERT_TRUE(cert2);
- // kGoodPath is blog.torproject.org.
- static const char* const kGoodPath[] = {
- "sha1/m9lHYJYke9k0GtVZ+bXSQYE8nDI=", "sha1/o5OZxATDsgmwgcIfIWIneMJ0jkw=",
- "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4=", NULL,
- };
-
- // kBadPath is plus.google.com via Trustcenter, which is utterly wrong for
- // torproject.org.
- static const char* const kBadPath[] = {
- "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=", "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=",
- "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=", NULL,
- };
-
HashValueVector good_hashes, bad_hashes;
for (size_t i = 0; kGoodPath[i]; i++)
@@ -1273,10 +1273,10 @@ TEST_F(TransportSecurityStateTest, HPKPReporting) {
EXPECT_EQ(report_uri, mock_report_sender.latest_report_uri());
std::string report = mock_report_sender.latest_report();
ASSERT_FALSE(report.empty());
- ASSERT_NO_FATAL_FAILURE(CheckHPKPReport(report, host_port_pair, expiry, true,
- kHost, cert1.get(), cert2.get(),
+ ASSERT_NO_FATAL_FAILURE(CheckHPKPReport(report, host_port_pair, true, kHost,
+ cert1.get(), cert2.get(),
good_hashes));
-
+ mock_report_sender.Clear();
EXPECT_FALSE(state.CheckPublicKeyPins(
subdomain_host_port_pair, true, bad_hashes, cert1.get(), cert2.get(),
TransportSecurityState::ENABLE_PIN_REPORTS, &failure_log));
@@ -1287,8 +1287,131 @@ TEST_F(TransportSecurityStateTest, HPKPReporting) {
report = mock_report_sender.latest_report();
ASSERT_FALSE(report.empty());
ASSERT_NO_FATAL_FAILURE(CheckHPKPReport(report, subdomain_host_port_pair,
- expiry, true, kHost, cert1.get(),
- cert2.get(), good_hashes));
+ true, kHost, cert1.get(), cert2.get(),
+ good_hashes));
+}
+
+TEST_F(TransportSecurityStateTest, HPKPReportOnly) {
+ HostPortPair host_port_pair(kHost, kPort);
+ GURL report_uri(kReportUri);
+ // Two dummy certs to use as the server-sent and validated chains. The
+ // contents don't matter.
+ scoped_refptr<X509Certificate> cert1 =
+ ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
+ scoped_refptr<X509Certificate> cert2 =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ ASSERT_TRUE(cert1);
+ ASSERT_TRUE(cert2);
+
+ TransportSecurityState state;
+ MockCertificateReportSender mock_report_sender;
+ state.SetReportSender(&mock_report_sender);
+
+ // Check that a report is not sent for a Report-Only header with no
+ // violation.
+ std::string header =
+ "pin-sha1=\"" + std::string(kGoodPin1) + "\";pin-sha1=\"" +
+ std::string(kGoodPin2) + "\";pin-sha1=\"" + std::string(kGoodPin3) +
+ "\";report-uri=\"" + report_uri.spec() + "\";includeSubdomains";
+ SSLInfo ssl_info;
+ ssl_info.is_issued_by_known_root = true;
+ ssl_info.unverified_cert = cert1;
+ ssl_info.cert = cert2;
+ for (size_t i = 0; kGoodPath[i]; i++)
+ EXPECT_TRUE(AddHash(kGoodPath[i], &ssl_info.public_key_hashes));
+
+ EXPECT_TRUE(
+ state.ProcessHPKPReportOnlyHeader(header, host_port_pair, ssl_info));
+ EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri());
+ EXPECT_EQ(std::string(), mock_report_sender.latest_report());
+
+ // Check that a report is sent for a Report-Only header with a
+ // violation.
+ ssl_info.public_key_hashes.clear();
+ for (size_t i = 0; kBadPath[i]; i++)
+ EXPECT_TRUE(AddHash(kBadPath[i], &ssl_info.public_key_hashes));
+
+ EXPECT_TRUE(
+ state.ProcessHPKPReportOnlyHeader(header, host_port_pair, ssl_info));
+ EXPECT_EQ(report_uri, mock_report_sender.latest_report_uri());
+ std::string report = mock_report_sender.latest_report();
+ ASSERT_FALSE(report.empty());
+ ASSERT_NO_FATAL_FAILURE(CheckHPKPReport(report, host_port_pair, true, kHost,
+ cert1.get(), cert2.get(),
+ ssl_info.public_key_hashes));
+}
+
+// Test that Report-Only reports are not sent on certs that chain to
+// local roots.
+TEST_F(TransportSecurityStateTest, HPKPReportOnlyOnLocalRoot) {
+ HostPortPair host_port_pair(kHost, kPort);
+ GURL report_uri(kReportUri);
+ // Two dummy certs to use as the server-sent and validated chains. The
+ // contents don't matter.
+ scoped_refptr<X509Certificate> cert1 =
+ ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
+ scoped_refptr<X509Certificate> cert2 =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ ASSERT_TRUE(cert1);
+ ASSERT_TRUE(cert2);
+
+ std::string header =
+ "pin-sha1=\"" + std::string(kGoodPin1) + "\";pin-sha1=\"" +
+ std::string(kGoodPin2) + "\";pin-sha1=\"" + std::string(kGoodPin3) +
+ "\";report-uri=\"" + report_uri.spec() + "\";includeSubdomains";
+
+ TransportSecurityState state;
+ MockCertificateReportSender mock_report_sender;
+ state.SetReportSender(&mock_report_sender);
+
+ SSLInfo ssl_info;
+ ssl_info.is_issued_by_known_root = true;
+ ssl_info.unverified_cert = cert1;
+ ssl_info.cert = cert2;
+ for (size_t i = 0; kGoodPath[i]; i++)
+ EXPECT_TRUE(AddHash(kGoodPath[i], &ssl_info.public_key_hashes));
+ ssl_info.is_issued_by_known_root = false;
+
+ EXPECT_TRUE(
+ state.ProcessHPKPReportOnlyHeader(header, host_port_pair, ssl_info));
+ EXPECT_EQ(GURL(), mock_report_sender.latest_report_uri());
+ EXPECT_EQ(std::string(), mock_report_sender.latest_report());
+}
+
+// Test that ProcessHPKPReportOnlyHeader() returns false if a report-uri
+// wasn't specified or if the header fails to parse.
+TEST_F(TransportSecurityStateTest, HPKPReportOnlyParseErrors) {
+ HostPortPair host_port_pair(kHost, kPort);
+ GURL report_uri(kReportUri);
+ // Two dummy certs to use as the server-sent and validated chains. The
+ // contents don't matter.
+ scoped_refptr<X509Certificate> cert1 =
+ ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
+ scoped_refptr<X509Certificate> cert2 =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ ASSERT_TRUE(cert1);
+ ASSERT_TRUE(cert2);
+
+ std::string header = "pin-sha1=\"" + std::string(kGoodPin1) +
+ "\";pin-sha1=\"" + std::string(kGoodPin2) +
+ "\";pin-sha1=\"" + std::string(kGoodPin3) + "\"";
+
+ TransportSecurityState state;
+ MockCertificateReportSender mock_report_sender;
+ state.SetReportSender(&mock_report_sender);
+
+ SSLInfo ssl_info;
+ ssl_info.is_issued_by_known_root = true;
+ ssl_info.unverified_cert = cert1;
+ ssl_info.cert = cert2;
+ for (size_t i = 0; kGoodPath[i]; i++)
+ EXPECT_TRUE(AddHash(kGoodPath[i], &ssl_info.public_key_hashes));
+
+ EXPECT_FALSE(
+ state.ProcessHPKPReportOnlyHeader(header, host_port_pair, ssl_info));
+ header += ";report-uri=\"";
+ EXPECT_FALSE(
+ state.ProcessHPKPReportOnlyHeader(header, host_port_pair, ssl_info));
}
} // namespace net
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 019a0a7..318aa4f 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -883,15 +883,20 @@ void URLRequestHttpJob::ProcessPublicKeyPinsHeader() {
if (request_info_.url.HostIsIPAddress())
return;
- // http://tools.ietf.org/html/draft-ietf-websec-key-pinning:
+ // http://tools.ietf.org/html/rfc7469:
//
// If a UA receives more than one PKP header field in an HTTP
// response message over secure transport, then the UA MUST process
// only the first such header field.
HttpResponseHeaders* headers = GetResponseHeaders();
std::string value;
- if (headers->EnumerateHeader(NULL, "Public-Key-Pins", &value))
+ if (headers->EnumerateHeader(nullptr, "Public-Key-Pins", &value))
security_state->AddHPKPHeader(request_info_.url.host(), value, ssl_info);
+ if (headers->EnumerateHeader(nullptr, "Public-Key-Pins-Report-Only",
+ &value)) {
+ security_state->ProcessHPKPReportOnlyHeader(
+ value, HostPortPair::FromURL(request_info_.url), ssl_info);
+ }
}
void URLRequestHttpJob::OnStartCompleted(int result) {