Skip to content

Commit

Permalink
Parse HPKP report-uri and persist in TransportSecurityPersister
Browse files Browse the repository at this point in the history
This CL parses the report-uri attribute on HPKP headers and stores them
in TransportSecurityPersister.

This is CL #1.
CL #2: crrev.com/1212973002 (add net::CertificateReportSender)
CL #3: crrev.com/1212613004 (build and send HPKP reports)

BUG=445793

Committed: https://crrev.com/1320e36d908427d615357df1630348bfb38cb5c4
Cr-Commit-Position: refs/heads/master@{#339667}

Review URL: https://codereview.chromium.org/1211363005

Cr-Commit-Position: refs/heads/master@{#340490}
  • Loading branch information
estark authored and Anton Obzhirov committed Aug 7, 2015
1 parent ed6fa7b commit 26e8021
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 222 deletions.
2 changes: 1 addition & 1 deletion chrome/browser/ui/webui/net_internals/net_internals_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnHSTSAdd(

transport_security_state->AddHSTS(domain, expiry, sts_include_subdomains);
transport_security_state->AddHPKP(domain, expiry, pkp_include_subdomains,
hashes);
hashes, GURL());
}

void NetInternalsMessageHandler::IOThreadImpl::OnHSTSDelete(
Expand Down
122 changes: 67 additions & 55 deletions net/http/http_security_headers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include "base/base64.h"
#include "base/basictypes.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "net/http/http_security_headers.h"
#include "net/http/http_util.h"
#include "url/gurl.h"

namespace net {

Expand All @@ -23,7 +25,10 @@ static_assert(kMaxHSTSAgeSecs <= kuint32max, "kMaxHSTSAgeSecs too large");
bool MaxAgeToInt(std::string::const_iterator begin,
std::string::const_iterator end,
uint32* result) {
const std::string s(begin, end);
const base::StringPiece s(begin, end);
if (s.empty())
return false;

int64 i = 0;

// Return false on any StringToInt64 parse errors *except* for
Expand Down Expand Up @@ -92,42 +97,16 @@ bool IsPinListValid(const HashValueVector& pins,
HashesIntersect(pins, from_cert_chain);
}

std::string Strip(const std::string& source) {
if (source.empty())
return source;

std::string::const_iterator start = source.begin();
std::string::const_iterator end = source.end();
HttpUtil::TrimLWS(&start, &end);
return std::string(start, end);
}

typedef std::pair<std::string, std::string> StringPair;

StringPair Split(const std::string& source, char delimiter) {
StringPair pair;
size_t point = source.find(delimiter);

pair.first = source.substr(0, point);
if (std::string::npos != point)
pair.second = source.substr(point + 1);

return pair;
}

bool ParseAndAppendPin(const std::string& value,
bool ParseAndAppendPin(std::string::const_iterator begin,
std::string::const_iterator end,
HashValueTag tag,
HashValueVector* hashes) {
// Pins are always quoted.
if (value.empty() || !HttpUtil::IsQuote(value[0]))
const base::StringPiece value(begin, end);
if (value.empty())
return false;

std::string unquoted = HttpUtil::Unquote(value);
if (unquoted.empty())
return false;

std::string decoded;
if (!base::Base64Decode(unquoted, &decoded))
if (!base::Base64Decode(value, &decoded))
return false;

HashValue hash(tag);
Expand Down Expand Up @@ -273,51 +252,83 @@ bool ParseHSTSHeader(const std::string& value,
}
}

// "Public-Key-Pins" ":"
// "Public-Key-Pins[-Report-Only]" ":"
// "max-age" "=" delta-seconds ";"
// "pin-" algo "=" base64 [ ";" ... ]
// [ ";" "includeSubdomains" ]
// [ ";" "report-uri" "=" uri-reference ]
bool ParseHPKPHeader(const std::string& value,
const HashValueVector& chain_hashes,
base::TimeDelta* max_age,
bool* include_subdomains,
HashValueVector* hashes) {
HashValueVector* hashes,
GURL* report_uri) {
bool parsed_max_age = false;
bool include_subdomains_candidate = false;
uint32 max_age_candidate = 0;
GURL parsed_report_uri;
HashValueVector pins;

std::string source = value;

while (!source.empty()) {
StringPair semicolon = Split(source, ';');
semicolon.first = Strip(semicolon.first);
semicolon.second = Strip(semicolon.second);
StringPair equals = Split(semicolon.first, '=');
equals.first = Strip(equals.first);
equals.second = Strip(equals.second);

if (base::LowerCaseEqualsASCII(equals.first, "max-age")) {
if (equals.second.empty() ||
!MaxAgeToInt(equals.second.begin(), equals.second.end(),
&max_age_candidate)) {
HttpUtil::NameValuePairsIterator name_value_pairs(
value.begin(), value.end(), ';',
HttpUtil::NameValuePairsIterator::VALUES_OPTIONAL);

while (name_value_pairs.GetNext()) {
if (base::LowerCaseEqualsASCII(
base::StringPiece(name_value_pairs.name_begin(),
name_value_pairs.name_end()),
"max-age")) {
if (!MaxAgeToInt(name_value_pairs.value_begin(),
name_value_pairs.value_end(), &max_age_candidate)) {
return false;
}
parsed_max_age = true;
} else if (base::LowerCaseEqualsASCII(equals.first, "pin-sha1")) {
if (!ParseAndAppendPin(equals.second, HASH_VALUE_SHA1, &pins))
} else if (base::LowerCaseEqualsASCII(
base::StringPiece(name_value_pairs.name_begin(),
name_value_pairs.name_end()),
"pin-sha1")) {
// Pins are always quoted.
if (!name_value_pairs.value_is_quoted() ||
!ParseAndAppendPin(name_value_pairs.value_begin(),
name_value_pairs.value_end(), HASH_VALUE_SHA1,
&pins)) {
return false;
} else if (base::LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
if (!ParseAndAppendPin(equals.second, HASH_VALUE_SHA256, &pins))
}
} else if (base::LowerCaseEqualsASCII(
base::StringPiece(name_value_pairs.name_begin(),
name_value_pairs.name_end()),
"pin-sha256")) {
// Pins are always quoted.
if (!name_value_pairs.value_is_quoted() ||
!ParseAndAppendPin(name_value_pairs.value_begin(),
name_value_pairs.value_end(), HASH_VALUE_SHA256,
&pins)) {
return false;
} else if (base::LowerCaseEqualsASCII(equals.first, "includesubdomains")) {
}
} else if (base::LowerCaseEqualsASCII(
base::StringPiece(name_value_pairs.name_begin(),
name_value_pairs.name_end()),
"includesubdomains")) {
include_subdomains_candidate = true;
} else if (base::LowerCaseEqualsASCII(
base::StringPiece(name_value_pairs.name_begin(),
name_value_pairs.name_end()),
"report-uri")) {
// report-uris are always quoted.
if (!name_value_pairs.value_is_quoted())
return false;

parsed_report_uri = GURL(name_value_pairs.value());
if (parsed_report_uri.is_empty() || !parsed_report_uri.is_valid())
return false;
} else {
// Silently ignore unknown directives for forward compatibility.
}

source = semicolon.second;
}

if (!name_value_pairs.valid())
return false;

if (!parsed_max_age)
return false;

Expand All @@ -327,6 +338,7 @@ bool ParseHPKPHeader(const std::string& value,
*max_age = base::TimeDelta::FromSeconds(max_age_candidate);
*include_subdomains = include_subdomains_candidate;
hashes->swap(pins);
*report_uri = parsed_report_uri;

return true;
}
Expand Down
6 changes: 5 additions & 1 deletion net/http/http_security_headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "net/base/hash_value.h"
#include "net/base/net_export.h"

class GURL;

namespace net {

const int64 kMaxHSTSAgeSecs = 86400 * 365; // 1 year
Expand Down Expand Up @@ -41,6 +43,7 @@ bool NET_EXPORT_PRIVATE ParseHSTSHeader(const std::string& value,
// "max-age" "=" delta-seconds ";"
// "pin-" algo "=" base64 [ ";" ... ]
// [ ";" "includeSubdomains" ]
// [ ";" "report-uri" "=" uri-reference ]
//
// 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
Expand All @@ -52,7 +55,8 @@ bool NET_EXPORT_PRIVATE ParseHPKPHeader(const std::string& value,
const HashValueVector& chain_hashes,
base::TimeDelta* max_age,
bool* include_subdomains,
HashValueVector* hashes);
HashValueVector* hashes,
GURL* report_uri);

} // namespace net

Expand Down
Loading

0 comments on commit 26e8021

Please sign in to comment.