Index: net/base/transport_security_state.cc |
=================================================================== |
--- net/base/transport_security_state.cc (revision 150362) |
+++ net/base/transport_security_state.cc (working copy) |
@@ -33,6 +33,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" |
@@ -218,69 +219,78 @@ |
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) { |
+ HashValueVector* 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. |
+ // characters; 32 bytes base64'd is 44 characters. |
size_t size = value.size(); |
- if (size != 30 || value[0] != '"' || value[size - 1] != '"') |
+ if ((size != 30 && size != 46) || value[0] != '"' || value[size - 1] != '"') |
return false; |
std::string unquoted = HttpUtil::Unquote(value); |
std::string decoded; |
- SHA1Fingerprint fp; |
+ HashValue 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)); |
+ if (decoded.size() == 20) |
+ fp.tag = HASH_VALUE_SHA1; |
+ else if (decoded.size() == 32) |
+ fp.tag = HASH_VALUE_SHA256; |
+ else |
+ return false; |
+ |
+ memcpy(fp.data(), decoded.data(), fp.size()); |
fingerprints->push_back(fp); |
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; |
} |
@@ -288,12 +298,12 @@ |
return false; |
} |
-static bool HashesIntersect(const FingerprintVector& a, |
- const FingerprintVector& b) { |
- for (FingerprintVector::const_iterator |
+static bool HashesIntersect(const HashValueVector& a, |
+ const HashValueVector& b) { |
+ for (HashValueVector::const_iterator |
i = a.begin(); i != a.end(); ++i) { |
- FingerprintVector::const_iterator j = |
- std::find_if(b.begin(), b.end(), FingerprintsEqualPredicate(*i)); |
+ HashValueVector::const_iterator j = |
+ std::find_if(b.begin(), b.end(), HashValuesEqualPredicate(*i)); |
if (j != b.end()) |
return true; |
} |
@@ -306,17 +316,30 @@ |
// 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; |
- if (from_cert_chain.empty()) |
+ // Site operators might pin a key using either the SHA-1 or SHA-256 hash |
+ // of the SPKI. So check for success using either hash function. |
+ // |
+ // TODO(palmer): Make this generic so that it works regardless of what |
+ // HashValueTags are defined in the future. |
+ const HashValueVector& from_cert_chain_sha1 = |
+ ssl_info.public_key_hashes[HASH_VALUE_SHA1]; |
+ const HashValueVector& from_cert_chain_sha256 = |
+ ssl_info.public_key_hashes[HASH_VALUE_SHA256]; |
+ |
+ if (from_cert_chain_sha1.empty() && from_cert_chain_sha256.empty()) |
return false; |
- return IsBackupPinPresent(pins, from_cert_chain) && |
- HashesIntersect(pins, from_cert_chain); |
+ return (IsBackupPinPresent(pins, from_cert_chain_sha1) || |
+ IsBackupPinPresent(pins, from_cert_chain_sha256)) && |
+ (HashesIntersect(pins, from_cert_chain_sha1) || |
+ HashesIntersect(pins, from_cert_chain_sha256)); |
} |
// "Public-Key-Pins" ":" |
@@ -328,7 +351,7 @@ |
const SSLInfo& ssl_info) { |
bool parsed_max_age = false; |
int max_age_candidate = 0; |
- FingerprintVector pins; |
+ HashValueVector pins; |
std::string source = value; |
@@ -349,11 +372,10 @@ |
if (max_age_candidate > kMaxHSTSAgeSecs) |
max_age_candidate = kMaxHSTSAgeSecs; |
parsed_max_age = true; |
- } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1")) { |
+ } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1") || |
+ LowerCaseEqualsASCII(equals.first, "pin-sha256")) { |
if (!ParseAndAppendPin(equals.second, &pins)) |
return false; |
- } else if (LowerCaseEqualsASCII(equals.first, "pin-sha256")) { |
- // TODO(palmer) |
} else { |
// Silently ignore unknown directives for forward compatibility. |
} |
@@ -369,7 +391,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); |
} |
@@ -476,8 +498,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; |
@@ -743,13 +765,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); |
} |
@@ -767,24 +789,43 @@ |
} |
bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted( |
- const FingerprintVector& hashes) const { |
- if (HashesIntersect(bad_static_spki_hashes, hashes)) { |
- LOG(ERROR) << "Rejecting public key chain for domain " << domain |
- << ". Validated chain: " << HashesToBase64String(hashes) |
- << ", matches one or more bad hashes: " |
- << HashesToBase64String(bad_static_spki_hashes); |
+ const std::vector<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. |
+ bool empty = true; |
+ for (size_t i = 0; i < hashes.size(); ++i) { |
+ if (hashes[i].size()) { |
+ empty = false; |
+ break; |
+ } |
+ } |
+ if (empty) { |
+ LOG(ERROR) << "Rejecting empty certificate chain for public key pinned " |
+ "domain " << domain; |
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); |
+ for (size_t i = 0; i < hashes.size(); ++i) { |
+ if (HashesIntersect(bad_static_spki_hashes, hashes[i])) { |
+ LOG(ERROR) << "Rejecting public key chain for domain " << domain |
+ << ". Validated chain: " << HashesToBase64String(hashes[i]) |
+ << ", matches one or more bad hashes: " |
+ << HashesToBase64String(bad_static_spki_hashes); |
+ return false; |
+ } |
- return false; |
+ if (!(dynamic_spki_hashes.empty() && static_spki_hashes.empty()) && |
+ hashes[i].size() > 0 && |
+ !HashesIntersect(dynamic_spki_hashes, hashes[i]) && |
+ !HashesIntersect(static_spki_hashes, hashes[i])) { |
+ LOG(ERROR) << "Rejecting public key chain for domain " << domain |
+ << ". Validated chain: " << HashesToBase64String(hashes[i]) |
+ << ", expected: " << HashesToBase64String(dynamic_spki_hashes) |
+ << " or: " << HashesToBase64String(static_spki_hashes); |
+ |
+ return false; |
+ } |
} |
return true; |