| Index: net/base/transport_security_state.cc
|
| ===================================================================
|
| --- net/base/transport_security_state.cc (revision 155029)
|
| +++ net/base/transport_security_state.cc (working copy)
|
| @@ -16,7 +16,6 @@
|
| #endif
|
|
|
| #include <algorithm>
|
| -#include <utility>
|
|
|
| #include "base/base64.h"
|
| #include "base/logging.h"
|
| @@ -33,6 +32,7 @@
|
| #include "googleurl/src/gurl.h"
|
| #include "net/base/dns_util.h"
|
| #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"
|
|
|
| @@ -225,69 +225,69 @@
|
| return pair;
|
| }
|
|
|
| -// TODO(palmer): Support both sha256 and sha1. This will require additional
|
| -// infrastructure code changes and can come in a later patch.
|
| -//
|
| // static
|
| bool TransportSecurityState::ParsePin(const std::string& value,
|
| - SHA1Fingerprint* out) {
|
| + HashValue* out) {
|
| StringPair slash = Split(Strip(value), '/');
|
| - if (slash.first != "sha1")
|
| +
|
| + 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() != arraysize(out->data)) {
|
| + decoded.size() != out->size()) {
|
| return false;
|
| }
|
|
|
| - memcpy(out->data, decoded.data(), arraysize(out->data));
|
| + memcpy(out->data(), decoded.data(), out->size());
|
| return true;
|
| }
|
|
|
| static bool ParseAndAppendPin(const std::string& value,
|
| - FingerprintVector* fingerprints) {
|
| - // The base64'd fingerprint MUST be a quoted-string. 20 bytes base64'd is 28
|
| - // characters; 32 bytes base64'd is 44 characters. TODO(palmer): Support
|
| - // SHA256.
|
| - size_t size = value.size();
|
| - if (size != 30 || value[0] != '"' || value[size - 1] != '"')
|
| - return false;
|
| -
|
| + HashValueTag tag,
|
| + HashValueVector* hashes) {
|
| std::string unquoted = HttpUtil::Unquote(value);
|
| std::string decoded;
|
| - SHA1Fingerprint fp;
|
|
|
| - if (!base::Base64Decode(unquoted, &decoded) ||
|
| - decoded.size() != arraysize(fp.data)) {
|
| + // 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;
|
| - }
|
|
|
| - memcpy(fp.data, decoded.data(), arraysize(fp.data));
|
| - fingerprints->push_back(fp);
|
| + HashValue hash(tag);
|
| + if (decoded.size() != hash.size())
|
| + return false;
|
| +
|
| + memcpy(hash.data(), decoded.data(), hash.size());
|
| + hashes->push_back(hash);
|
| return true;
|
| }
|
|
|
| -struct FingerprintsEqualPredicate {
|
| - explicit FingerprintsEqualPredicate(const SHA1Fingerprint& fingerprint) :
|
| +struct HashValuesEqualPredicate {
|
| + explicit HashValuesEqualPredicate(const HashValue& fingerprint) :
|
| fingerprint_(fingerprint) {}
|
|
|
| - bool operator()(const SHA1Fingerprint& other) const {
|
| + bool operator()(const HashValue& other) const {
|
| return fingerprint_.Equals(other);
|
| }
|
|
|
| - const SHA1Fingerprint& fingerprint_;
|
| + 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 FingerprintVector& pins,
|
| - const FingerprintVector& from_cert_chain) {
|
| - for (FingerprintVector::const_iterator
|
| +static bool IsBackupPinPresent(const HashValueVector& pins,
|
| + const HashValueVector& from_cert_chain) {
|
| + for (HashValueVector::const_iterator
|
| i = pins.begin(); i != pins.end(); ++i) {
|
| - FingerprintVector::const_iterator j =
|
| + HashValueVector::const_iterator j =
|
| std::find_if(from_cert_chain.begin(), from_cert_chain.end(),
|
| - FingerprintsEqualPredicate(*i));
|
| + HashValuesEqualPredicate(*i));
|
| if (j == from_cert_chain.end())
|
| return true;
|
| }
|
| @@ -295,14 +295,15 @@
|
| return false;
|
| }
|
|
|
| -static bool HashesIntersect(const FingerprintVector& a,
|
| - const FingerprintVector& b) {
|
| - for (FingerprintVector::const_iterator
|
| - i = a.begin(); i != a.end(); ++i) {
|
| - FingerprintVector::const_iterator j =
|
| - std::find_if(b.begin(), b.end(), FingerprintsEqualPredicate(*i));
|
| - if (j != b.end())
|
| - return true;
|
| +// 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;
|
| @@ -313,17 +314,19 @@
|
| // 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 FingerprintVector& pins,
|
| +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 FingerprintVector& from_cert_chain = ssl_info.public_key_hashes;
|
| + 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);
|
| + HashesIntersect(pins, from_cert_chain);
|
| }
|
|
|
| // "Public-Key-Pins" ":"
|
| @@ -335,7 +338,7 @@
|
| const SSLInfo& ssl_info) {
|
| bool parsed_max_age = false;
|
| int max_age_candidate = 0;
|
| - FingerprintVector pins;
|
| + HashValueVector pins;
|
|
|
| std::string source = value;
|
|
|
| @@ -356,11 +359,18 @@
|
| if (max_age_candidate > kMaxHSTSAgeSecs)
|
| max_age_candidate = kMaxHSTSAgeSecs;
|
| parsed_max_age = true;
|
| - } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1")) {
|
| - if (!ParseAndAppendPin(equals.second, &pins))
|
| + } 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;
|
| - } else if (LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
|
| - // TODO(palmer)
|
| + }
|
| + if (!ParseAndAppendPin(equals.second, tag, &pins))
|
| + return false;
|
| } else {
|
| // Silently ignore unknown directives for forward compatibility.
|
| }
|
| @@ -376,7 +386,7 @@
|
|
|
| dynamic_spki_hashes.clear();
|
| if (max_age_candidate > 0) {
|
| - for (FingerprintVector::const_iterator i = pins.begin();
|
| + for (HashValueVector::const_iterator i = pins.begin();
|
| i != pins.end(); ++i) {
|
| dynamic_spki_hashes.push_back(*i);
|
| }
|
| @@ -483,8 +493,8 @@
|
| }
|
|
|
| static bool AddHash(const std::string& type_and_base64,
|
| - FingerprintVector* out) {
|
| - SHA1Fingerprint hash;
|
| + HashValueVector* out) {
|
| + HashValue hash;
|
|
|
| if (!TransportSecurityState::ParsePin(type_and_base64, &hash))
|
| return false;
|
| @@ -705,6 +715,21 @@
|
| entry->second_level_domain_name, DOMAIN_NUM_EVENTS);
|
| }
|
|
|
| +// 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/";
|
| + }
|
| +}
|
| +
|
| bool TransportSecurityState::GetStaticDomainState(
|
| const std::string& canonicalized_host,
|
| bool sni_enabled,
|
| @@ -750,13 +775,13 @@
|
| }
|
|
|
| static std::string HashesToBase64String(
|
| - const FingerprintVector& hashes) {
|
| + const HashValueVector& hashes) {
|
| std::vector<std::string> hashes_strs;
|
| - for (FingerprintVector::const_iterator
|
| + 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),
|
| - sizeof(i->data));
|
| + const std::string hash_str(reinterpret_cast<const char*>(i->data()),
|
| + i->size());
|
| base::Base64Encode(hash_str, &s);
|
| hashes_strs.push_back(s);
|
| }
|
| @@ -774,7 +799,16 @@
|
| }
|
|
|
| bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted(
|
| - const FingerprintVector& hashes) const {
|
| + const HashValueVector& hashes) const {
|
| + // Validate that hashes is not empty. By the time this code is called (in
|
| + // production), that should never happen, but it's good to be defensive.
|
| + // And, hashes *can* be empty in some test scenarios.
|
| + if (hashes.empty()) {
|
| + LOG(ERROR) << "Rejecting empty public key chain for public-key-pinned "
|
| + "domain " << domain;
|
| + return false;
|
| + }
|
| +
|
| if (HashesIntersect(bad_static_spki_hashes, hashes)) {
|
| LOG(ERROR) << "Rejecting public key chain for domain " << domain
|
| << ". Validated chain: " << HashesToBase64String(hashes)
|
| @@ -783,18 +817,20 @@
|
| return false;
|
| }
|
|
|
| - if (!(dynamic_spki_hashes.empty() && static_spki_hashes.empty()) &&
|
| - !HashesIntersect(dynamic_spki_hashes, hashes) &&
|
| - !HashesIntersect(static_spki_hashes, hashes)) {
|
| - LOG(ERROR) << "Rejecting public key chain for domain " << domain
|
| - << ". Validated chain: " << HashesToBase64String(hashes)
|
| - << ", expected: " << HashesToBase64String(dynamic_spki_hashes)
|
| - << " or: " << HashesToBase64String(static_spki_hashes);
|
| + // If there are no pins, then any valid chain is acceptable.
|
| + if (dynamic_spki_hashes.empty() && static_spki_hashes.empty())
|
| + return true;
|
|
|
| - return false;
|
| + if (HashesIntersect(dynamic_spki_hashes, hashes) ||
|
| + HashesIntersect(static_spki_hashes, hashes)) {
|
| + return true;
|
| }
|
|
|
| - return true;
|
| + LOG(ERROR) << "Rejecting public key chain for domain " << domain
|
| + << ". Validated chain: " << HashesToBase64String(hashes)
|
| + << ", expected: " << HashesToBase64String(dynamic_spki_hashes)
|
| + << " or: " << HashesToBase64String(static_spki_hashes);
|
| + return false;
|
| }
|
|
|
| bool TransportSecurityState::DomainState::ShouldRedirectHTTPToHTTPS() const {
|
|
|