Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(365)

Unified Diff: net/base/transport_security_state.cc

Issue 11274032: Separate http_security_headers from transport_security_state (Closed) Base URL: https://src.chromium.org/chrome/trunk/src/
Patch Set: Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/base/transport_security_state.h ('k') | net/base/transport_security_state_static.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/base/transport_security_state.cc
===================================================================
--- net/base/transport_security_state.cc (revision 175486)
+++ net/base/transport_security_state.cc (working copy)
@@ -24,7 +24,6 @@
#include "base/metrics/histogram.h"
#include "base/sha1.h"
#include "base/string_number_conversions.h"
-#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
@@ -35,7 +34,7 @@
#include "net/base/ssl_info.h"
#include "net/base/x509_cert_types.h"
#include "net/base/x509_certificate.h"
-#include "net/http/http_util.h"
+#include "net/http/http_security_headers.h"
#if defined(USE_OPENSSL)
#include "crypto/openssl_util.h"
@@ -43,14 +42,47 @@
namespace net {
-const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year
+namespace {
-static std::string HashHost(const std::string& canonicalized_host) {
+std::string HashesToBase64String(const HashValueVector& hashes) {
+ std::string str;
+ for (size_t i = 0; i != hashes.size(); ++i) {
+ if (i != 0)
+ str += ",";
+ str += hashes[i].ToString();
+ }
+ return str;
+}
+
+std::string HashHost(const std::string& canonicalized_host) {
char hashed[crypto::kSHA256Length];
crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed));
return std::string(hashed, sizeof(hashed));
}
+// Returns true if the intersection of |a| and |b| is not empty. If either
+// |a| or |b| is empty, returns false.
+bool HashesIntersect(const HashValueVector& a,
+ const HashValueVector& b) {
+ for (HashValueVector::const_iterator i = a.begin(); i != a.end(); ++i) {
+ HashValueVector::const_iterator j =
+ std::find_if(b.begin(), b.end(), HashValuesEqual(*i));
+ if (j != b.end())
+ return true;
+ }
+ return false;
+}
+
+bool AddHash(const char* sha1_hash,
+ HashValueVector* out) {
+ HashValue hash(HASH_VALUE_SHA1);
+ memcpy(hash.data(), sha1_hash, hash.size());
+ out->push_back(hash);
+ return true;
+}
+
+} // namespace
+
TransportSecurityState::TransportSecurityState()
: delegate_(NULL) {
}
@@ -183,370 +215,6 @@
DirtyNotify();
}
-// MaxAgeToInt converts a string representation of a number of seconds into a
-// int. We use strtol in order to handle overflow correctly. The string may
-// contain an arbitary number which we should truncate correctly rather than
-// throwing a parse failure.
-static bool MaxAgeToInt(std::string::const_iterator begin,
- std::string::const_iterator end,
- int* result) {
- const std::string s(begin, end);
- char* endptr;
- long int i = strtol(s.data(), &endptr, 10 /* base */);
- if (*endptr || i < 0)
- return false;
- if (i > TransportSecurityState::kMaxHSTSAgeSecs)
- i = TransportSecurityState::kMaxHSTSAgeSecs;
- *result = i;
- return true;
-}
-
-// Strip, Split, StringPair, and ParsePins are private implementation details
-// of ParsePinsHeader(std::string&, DomainState&).
-static 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;
-
-static 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;
-}
-
-// static
-bool TransportSecurityState::ParsePin(const std::string& value,
- HashValue* out) {
- StringPair slash = Split(Strip(value), '/');
-
- if (slash.first == "sha1")
- out->tag = HASH_VALUE_SHA1;
- else if (slash.first == "sha256")
- out->tag = HASH_VALUE_SHA256;
- else
- return false;
-
- std::string decoded;
- if (!base::Base64Decode(slash.second, &decoded) ||
- decoded.size() != out->size()) {
- return false;
- }
-
- memcpy(out->data(), decoded.data(), out->size());
- return true;
-}
-
-static bool ParseAndAppendPin(const std::string& value,
- HashValueTag tag,
- HashValueVector* hashes) {
- std::string unquoted = HttpUtil::Unquote(value);
- std::string decoded;
-
- // This code has to assume that 32 bytes is SHA-256 and 20 bytes is SHA-1.
- // Currently, those are the only two possibilities, so the assumption is
- // valid.
- if (!base::Base64Decode(unquoted, &decoded))
- return false;
-
- HashValue hash(tag);
- if (decoded.size() != hash.size())
- return false;
-
- memcpy(hash.data(), decoded.data(), hash.size());
- hashes->push_back(hash);
- return true;
-}
-
-struct HashValuesEqualPredicate {
- explicit HashValuesEqualPredicate(const HashValue& fingerprint) :
- fingerprint_(fingerprint) {}
-
- bool operator()(const HashValue& other) const {
- return fingerprint_.Equals(other);
- }
-
- const HashValue& fingerprint_;
-};
-
-// Returns true iff there is an item in |pins| which is not present in
-// |from_cert_chain|. Such an SPKI hash is called a "backup pin".
-static bool IsBackupPinPresent(const HashValueVector& pins,
- const HashValueVector& from_cert_chain) {
- for (HashValueVector::const_iterator
- i = pins.begin(); i != pins.end(); ++i) {
- HashValueVector::const_iterator j =
- std::find_if(from_cert_chain.begin(), from_cert_chain.end(),
- HashValuesEqualPredicate(*i));
- if (j == from_cert_chain.end())
- return true;
- }
-
- return false;
-}
-
-// Returns true if the intersection of |a| and |b| is not empty. If either
-// |a| or |b| is empty, returns false.
-static bool HashesIntersect(const HashValueVector& a,
- const HashValueVector& b) {
- for (HashValueVector::const_iterator i = a.begin(); i != a.end(); ++i) {
- HashValueVector::const_iterator j =
- std::find_if(b.begin(), b.end(), HashValuesEqualPredicate(*i));
- if (j != b.end())
- return true;
- }
-
- return false;
-}
-
-// Returns true iff |pins| contains both a live and a backup pin. A live pin
-// is a pin whose SPKI is present in the certificate chain in |ssl_info|. A
-// backup pin is a pin intended for disaster recovery, not day-to-day use, and
-// thus must be absent from the certificate chain. The Public-Key-Pins header
-// specification requires both.
-static bool IsPinListValid(const HashValueVector& pins,
- const SSLInfo& ssl_info) {
- // Fast fail: 1 live + 1 backup = at least 2 pins. (Check for actual
- // liveness and backupness below.)
- if (pins.size() < 2)
- return false;
-
- const HashValueVector& from_cert_chain = ssl_info.public_key_hashes;
- if (from_cert_chain.empty())
- return false;
-
- return IsBackupPinPresent(pins, from_cert_chain) &&
- HashesIntersect(pins, from_cert_chain);
-}
-
-// "Public-Key-Pins" ":"
-// "max-age" "=" delta-seconds ";"
-// "pin-" algo "=" base64 [ ";" ... ]
-bool TransportSecurityState::DomainState::ParsePinsHeader(
- const base::Time& now,
- const std::string& value,
- const SSLInfo& ssl_info) {
- bool parsed_max_age = false;
- int max_age_candidate = 0;
- 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 (LowerCaseEqualsASCII(equals.first, "max-age")) {
- if (equals.second.empty() ||
- !MaxAgeToInt(equals.second.begin(), equals.second.end(),
- &max_age_candidate)) {
- return false;
- }
- if (max_age_candidate > kMaxHSTSAgeSecs)
- max_age_candidate = kMaxHSTSAgeSecs;
- parsed_max_age = true;
- } else if (StartsWithASCII(equals.first, "pin-", false)) {
- HashValueTag tag;
- if (LowerCaseEqualsASCII(equals.first, "pin-sha1")) {
- tag = HASH_VALUE_SHA1;
- } else if (LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
- tag = HASH_VALUE_SHA256;
- } else {
- LOG(WARNING) << "Ignoring pin of unknown type: " << equals.first;
- return false;
- }
- if (!ParseAndAppendPin(equals.second, tag, &pins))
- return false;
- } else {
- // Silently ignore unknown directives for forward compatibility.
- }
-
- source = semicolon.second;
- }
-
- if (!parsed_max_age || !IsPinListValid(pins, ssl_info))
- return false;
-
- dynamic_spki_hashes_expiry =
- now + base::TimeDelta::FromSeconds(max_age_candidate);
-
- dynamic_spki_hashes.clear();
- if (max_age_candidate > 0) {
- for (HashValueVector::const_iterator i = pins.begin();
- i != pins.end(); ++i) {
- dynamic_spki_hashes.push_back(*i);
- }
- }
-
- return true;
-}
-
-// Parse the Strict-Transport-Security header, as currently defined in
-// http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14:
-//
-// Strict-Transport-Security = "Strict-Transport-Security" ":"
-// [ directive ] *( ";" [ directive ] )
-//
-// directive = directive-name [ "=" directive-value ]
-// directive-name = token
-// directive-value = token | quoted-string
-//
-// 1. The order of appearance of directives is not significant.
-//
-// 2. All directives MUST appear only once in an STS header field.
-// Directives are either optional or required, as stipulated in
-// their definitions.
-//
-// 3. Directive names are case-insensitive.
-//
-// 4. UAs MUST ignore any STS header fields containing directives, or
-// other header field value data, that does not conform to the
-// syntax defined in this specification.
-//
-// 5. If an STS header field contains directive(s) not recognized by
-// the UA, the UA MUST ignore the unrecognized directives and if the
-// STS header field otherwise satisfies the above requirements (1
-// through 4), the UA MUST process the recognized directives.
-bool TransportSecurityState::DomainState::ParseSTSHeader(
- const base::Time& now,
- const std::string& value) {
- int max_age_candidate = 0;
- bool include_subdomains_candidate = false;
-
- // We must see max-age exactly once.
- int max_age_observed = 0;
- // We must see includeSubdomains exactly 0 or 1 times.
- int include_subdomains_observed = 0;
-
- enum ParserState {
- START,
- AFTER_MAX_AGE_LABEL,
- AFTER_MAX_AGE_EQUALS,
- AFTER_MAX_AGE,
- AFTER_INCLUDE_SUBDOMAINS,
- AFTER_UNKNOWN_LABEL,
- DIRECTIVE_END
- } state = START;
-
- StringTokenizer tokenizer(value, " \t=;");
- tokenizer.set_options(StringTokenizer::RETURN_DELIMS);
- tokenizer.set_quote_chars("\"");
- std::string unquoted;
- while (tokenizer.GetNext()) {
- DCHECK(!tokenizer.token_is_delim() || tokenizer.token().length() == 1);
- switch (state) {
- case START:
- case DIRECTIVE_END:
- if (IsAsciiWhitespace(*tokenizer.token_begin()))
- continue;
- if (LowerCaseEqualsASCII(tokenizer.token(), "max-age")) {
- state = AFTER_MAX_AGE_LABEL;
- max_age_observed++;
- } else if (LowerCaseEqualsASCII(tokenizer.token(),
- "includesubdomains")) {
- state = AFTER_INCLUDE_SUBDOMAINS;
- include_subdomains_observed++;
- include_subdomains_candidate = true;
- } else {
- state = AFTER_UNKNOWN_LABEL;
- }
- break;
-
- case AFTER_MAX_AGE_LABEL:
- if (IsAsciiWhitespace(*tokenizer.token_begin()))
- continue;
- if (*tokenizer.token_begin() != '=')
- return false;
- DCHECK_EQ(tokenizer.token().length(), 1U);
- state = AFTER_MAX_AGE_EQUALS;
- break;
-
- case AFTER_MAX_AGE_EQUALS:
- if (IsAsciiWhitespace(*tokenizer.token_begin()))
- continue;
- unquoted = HttpUtil::Unquote(tokenizer.token());
- if (!MaxAgeToInt(unquoted.begin(),
- unquoted.end(),
- &max_age_candidate))
- return false;
- state = AFTER_MAX_AGE;
- break;
-
- case AFTER_MAX_AGE:
- case AFTER_INCLUDE_SUBDOMAINS:
- if (IsAsciiWhitespace(*tokenizer.token_begin()))
- continue;
- else if (*tokenizer.token_begin() == ';')
- state = DIRECTIVE_END;
- else
- return false;
- break;
-
- case AFTER_UNKNOWN_LABEL:
- // Consume and ignore the post-label contents (if any).
- if (*tokenizer.token_begin() != ';')
- continue;
- state = DIRECTIVE_END;
- break;
- }
- }
-
- // We've consumed all the input. Let's see what state we ended up in.
- if (max_age_observed != 1 ||
- (include_subdomains_observed != 0 && include_subdomains_observed != 1)) {
- return false;
- }
-
- switch (state) {
- case AFTER_MAX_AGE:
- case AFTER_INCLUDE_SUBDOMAINS:
- case AFTER_UNKNOWN_LABEL:
- if (max_age_candidate > 0) {
- upgrade_expiry = now + base::TimeDelta::FromSeconds(max_age_candidate);
- upgrade_mode = MODE_FORCE_HTTPS;
- } else {
- upgrade_expiry = now;
- upgrade_mode = MODE_DEFAULT;
- }
- include_subdomains = include_subdomains_candidate;
- return true;
- case START:
- case DIRECTIVE_END:
- case AFTER_MAX_AGE_LABEL:
- case AFTER_MAX_AGE_EQUALS:
- return false;
- default:
- NOTREACHED();
- return false;
- }
-}
-
-static bool AddHash(const std::string& type_and_base64,
- HashValueVector* out) {
- HashValue hash;
-
- if (!TransportSecurityState::ParsePin(type_and_base64, &hash))
- return false;
-
- out->push_back(hash);
- return true;
-}
-
TransportSecurityState::~TransportSecurityState() {}
void TransportSecurityState::DirtyNotify() {
@@ -890,19 +558,17 @@
if (!entries[j].https_required)
out->upgrade_mode = TransportSecurityState::DomainState::MODE_DEFAULT;
if (entries[j].pins.required_hashes) {
- const char* const* hash = entries[j].pins.required_hashes;
- while (*hash) {
- bool ok = AddHash(*hash, &out->static_spki_hashes);
- DCHECK(ok) << " failed to parse " << *hash;
- hash++;
+ const char* const* sha1_hash = entries[j].pins.required_hashes;
+ while (*sha1_hash) {
+ AddHash(*sha1_hash, &out->static_spki_hashes);
+ sha1_hash++;
}
}
if (entries[j].pins.excluded_hashes) {
- const char* const* hash = entries[j].pins.excluded_hashes;
- while (*hash) {
- bool ok = AddHash(*hash, &out->bad_static_spki_hashes);
- DCHECK(ok) << " failed to parse " << *hash;
- hash++;
+ const char* const* sha1_hash = entries[j].pins.excluded_hashes;
+ while (*sha1_hash) {
+ AddHash(*sha1_hash, &out->bad_static_spki_hashes);
+ sha1_hash++;
}
}
}
@@ -941,6 +607,40 @@
return NULL;
}
+bool TransportSecurityState::AddHSTSHeader(const std::string& host,
+ const std::string& value) {
+ base::Time now = base::Time::Now();
+ TransportSecurityState::DomainState domain_state;
+ if (ParseHSTSHeader(now, value, &domain_state.upgrade_expiry,
+ &domain_state.include_subdomains)) {
+ // Handle max-age == 0
+ if (now == domain_state.upgrade_expiry)
+ domain_state.upgrade_mode = DomainState::MODE_DEFAULT;
+ else
+ domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS;
+ domain_state.created = now;
+ EnableHost(host, domain_state);
+ return true;
+ }
+ return false;
+}
+
+bool TransportSecurityState::AddHPKPHeader(const std::string& host,
+ const std::string& value,
+ const SSLInfo& ssl_info) {
+ base::Time now = base::Time::Now();
+ TransportSecurityState::DomainState domain_state;
+ if (ParseHPKPHeader(now, value, ssl_info.public_key_hashes,
+ &domain_state.dynamic_spki_hashes_expiry,
+ &domain_state.dynamic_spki_hashes)) {
+ domain_state.upgrade_mode = DomainState::MODE_DEFAULT;
+ domain_state.created = now;
+ EnableHost(host, domain_state);
+ return true;
+ }
+ return false;
+}
+
// static
bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host,
bool sni_enabled) {
@@ -987,21 +687,6 @@
}
// static
-const char* TransportSecurityState::HashValueLabel(
- const HashValue& hash_value) {
- switch (hash_value.tag) {
- case HASH_VALUE_SHA1:
- return "sha1/";
- case HASH_VALUE_SHA256:
- return "sha256/";
- default:
- NOTREACHED();
- LOG(WARNING) << "Invalid fingerprint of unknown type " << hash_value.tag;
- return "unknown/";
- }
-}
-
-// static
bool TransportSecurityState::IsBuildTimely() {
const base::Time build_time = base::GetBuildTime();
// We consider built-in information to be timely for 10 weeks.
@@ -1056,21 +741,6 @@
forced_hosts_[hashed_host] = state;
}
-static std::string HashesToBase64String(
- const HashValueVector& hashes) {
- std::vector<std::string> hashes_strs;
- for (HashValueVector::const_iterator
- i = hashes.begin(); i != hashes.end(); i++) {
- std::string s;
- const std::string hash_str(reinterpret_cast<const char*>(i->data()),
- i->size());
- base::Base64Encode(hash_str, &s);
- hashes_strs.push_back(s);
- }
-
- return JoinString(hashes_strs, ',');
-}
-
TransportSecurityState::DomainState::DomainState()
: upgrade_mode(MODE_FORCE_HTTPS),
created(base::Time::Now()),
« no previous file with comments | « net/base/transport_security_state.h ('k') | net/base/transport_security_state_static.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698