summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorunsafe@trevp.net <unsafe@trevp.net@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-08 22:07:33 +0000
committerunsafe@trevp.net <unsafe@trevp.net@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-08 22:07:33 +0000
commit6ed72be9850bc59df02b0ec19ce44cfabc805656 (patch)
tree9e278061bdcd61bec8b9a591780dd201a8ce2f65
parent0807387aeac97df0e1140e7de2c5cfa99c968175 (diff)
downloadchromium_src-6ed72be9850bc59df02b0ec19ce44cfabc805656.zip
chromium_src-6ed72be9850bc59df02b0ec19ce44cfabc805656.tar.gz
chromium_src-6ed72be9850bc59df02b0ec19ce44cfabc805656.tar.bz2
This is the first in an intended sequence of CLs to refactor
TransportSecurityState, fix some book-keeping bugs, and hopefully add TACK. This sequence of CLs will be derived from the original, overly-large CL #11191005. This CL does a few things: - Adds a high-level API for processing HSTS/HPKP - Move the code for handling HSTS/HPKP headers out of transport_security_state - Move HashValue out of x509_cert_types - Addresses several HSTS/HPKP parsing bugs identified during review of the cleanup - Ignore unknown HSTS/HPKP directives - Ignore unknown hash algorithms - Handle overly-large (> int64) expirations without parsing issues - Reject invalid pins entered by users Review URL: https://chromiumcodereview.appspot.com/11274032 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@175595 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--AUTHORS1
-rw-r--r--chrome/browser/net/transport_security_persister.cc14
-rw-r--r--chrome/browser/ui/webui/net_internals/net_internals_ui.cc76
-rw-r--r--net/base/hash_value.cc123
-rw-r--r--net/base/hash_value.h125
-rw-r--r--net/base/transport_security_state.cc486
-rw-r--r--net/base/transport_security_state.h29
-rw-r--r--net/base/transport_security_state_static.h158
-rw-r--r--net/base/transport_security_state_unittest.cc402
-rw-r--r--net/base/x509_cert_types.cc65
-rw-r--r--net/base/x509_cert_types.h83
-rw-r--r--net/http/http_security_headers.cc330
-rw-r--r--net/http/http_security_headers.h60
-rw-r--r--net/http/http_security_headers_unittest.cc424
-rw-r--r--net/net.gyp5
-rw-r--r--net/url_request/url_request_http_job.cc83
16 files changed, 1327 insertions, 1137 deletions
diff --git a/AUTHORS b/AUTHORS
index a0352b6..bbf96f3 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -215,3 +215,4 @@ Sungguk Lim <limasdf@gmail.com>
Martin Bednorz <m.s.bednorz@gmail.com>
Kamil Jiwa <kamil.jiwa@gmail.com>
Keene Pan <keenepan@linpus.com>
+Trevor Perrin <unsafe@trevp.net>
diff --git a/chrome/browser/net/transport_security_persister.cc b/chrome/browser/net/transport_security_persister.cc
index 7e80742..b288e21 100644
--- a/chrome/browser/net/transport_security_persister.cc
+++ b/chrome/browser/net/transport_security_persister.cc
@@ -29,16 +29,8 @@ namespace {
ListValue* SPKIHashesToListValue(const HashValueVector& hashes) {
ListValue* pins = new ListValue;
-
- for (HashValueVector::const_iterator i = hashes.begin();
- i != hashes.end(); ++i) {
- std::string hash_str(reinterpret_cast<const char*>(i->data()), i->size());
- std::string b64;
- if (base::Base64Encode(hash_str, &b64))
- pins->Append(new StringValue(TransportSecurityState::HashValueLabel(*i) +
- b64));
- }
-
+ for (size_t i = 0; i != hashes.size(); i++)
+ pins->Append(new StringValue(hashes[i].ToString()));
return pins;
}
@@ -48,7 +40,7 @@ void SPKIHashesFromListValue(const ListValue& pins, HashValueVector* hashes) {
std::string type_and_base64;
HashValue fingerprint;
if (pins.GetString(i, &type_and_base64) &&
- TransportSecurityState::ParsePin(type_and_base64, &fingerprint)) {
+ fingerprint.FromString(type_and_base64)) {
hashes->push_back(fingerprint);
}
}
diff --git a/chrome/browser/ui/webui/net_internals/net_internals_ui.cc b/chrome/browser/ui/webui/net_internals/net_internals_ui.cc
index 2cb52ad..9b5e742 100644
--- a/chrome/browser/ui/webui/net_internals/net_internals_ui.cc
+++ b/chrome/browser/ui/webui/net_internals/net_internals_ui.cc
@@ -110,6 +110,41 @@ net::HostCache* GetHostResolverCache(net::URLRequestContext* context) {
return context->host_resolver()->GetHostCache();
}
+std::string HashesToBase64String(const net::HashValueVector& hashes) {
+ std::string str;
+ for (size_t i = 0; i != hashes.size(); ++i) {
+ if (i != 0)
+ str += ",";
+ str += hashes[i].ToString();
+ }
+ return str;
+}
+
+bool Base64StringToHashes(const std::string& hashes_str,
+ net::HashValueVector* hashes) {
+ hashes->clear();
+ std::vector<std::string> vector_hash_str;
+ base::SplitString(hashes_str, ',', &vector_hash_str);
+
+ for (size_t i = 0; i != vector_hash_str.size(); ++i) {
+ std::string hash_str;
+ RemoveChars(vector_hash_str[i], " \t\r\n", &hash_str);
+ net::HashValue hash;
+ // Skip past unrecognized hash algos
+ // But return false on malformatted input
+ if (hash_str.empty())
+ return false;
+ if (hash_str.compare(0, 5, "sha1/") != 0 &&
+ hash_str.compare(0, 7, "sha256/") != 0) {
+ continue;
+ }
+ if (!hash.FromString(hash_str))
+ return false;
+ hashes->push_back(hash);
+ }
+ return true;
+}
+
// Returns a Value representing the state of a pre-existing URLRequest when
// net-internals was opened.
Value* RequestStateToValue(const net::URLRequest* request,
@@ -1176,21 +1211,6 @@ void NetInternalsMessageHandler::IOThreadImpl::OnStartConnectionTests(
connection_tester_->RunAllTests(url);
}
-void SPKIHashesToString(const net::HashValueVector& hashes,
- std::string* string) {
- for (net::HashValueVector::const_iterator
- i = hashes.begin(); i != hashes.end(); ++i) {
- base::StringPiece hash_str(reinterpret_cast<const char*>(i->data()),
- i->size());
- std::string encoded;
- base::Base64Encode(hash_str, &encoded);
-
- if (i != hashes.begin())
- *string += ",";
- *string += net::TransportSecurityState::HashValueLabel(*i) + encoded;
- }
-}
-
void NetInternalsMessageHandler::IOThreadImpl::OnHSTSQuery(
const ListValue* list) {
// |list| should be: [<domain to query>].
@@ -1219,13 +1239,10 @@ void NetInternalsMessageHandler::IOThreadImpl::OnHSTSQuery(
result->SetDouble("dynamic_spki_hashes_expiry",
state.dynamic_spki_hashes_expiry.ToDoubleT());
- std::string hashes;
- SPKIHashesToString(state.static_spki_hashes, &hashes);
- result->SetString("static_spki_hashes", hashes);
-
- hashes.clear();
- SPKIHashesToString(state.dynamic_spki_hashes, &hashes);
- result->SetString("dynamic_spki_hashes", hashes);
+ result->SetString("static_spki_hashes",
+ HashesToBase64String(state.static_spki_hashes));
+ result->SetString("dynamic_spki_hashes",
+ HashesToBase64String(state.dynamic_spki_hashes));
}
}
}
@@ -1257,20 +1274,9 @@ void NetInternalsMessageHandler::IOThreadImpl::OnHSTSAdd(
state.upgrade_expiry = state.created + base::TimeDelta::FromDays(1000);
state.include_subdomains = include_subdomains;
if (!hashes_str.empty()) {
- std::vector<std::string> type_and_b64s;
- base::SplitString(hashes_str, ',', &type_and_b64s);
- for (std::vector<std::string>::const_iterator
- i = type_and_b64s.begin(); i != type_and_b64s.end(); ++i) {
- std::string type_and_b64;
- RemoveChars(*i, " \t\r\n", &type_and_b64);
- net::HashValue hash;
- if (!net::TransportSecurityState::ParsePin(type_and_b64, &hash))
- continue;
-
- state.dynamic_spki_hashes.push_back(hash);
- }
+ if (!Base64StringToHashes(hashes_str, &state.dynamic_spki_hashes))
+ return;
}
-
transport_security_state->EnableHost(domain, state);
}
diff --git a/net/base/hash_value.cc b/net/base/hash_value.cc
new file mode 100644
index 0000000..1ab3a51
--- /dev/null
+++ b/net/base/hash_value.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/base/hash_value.h"
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/sha1.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+
+namespace net {
+
+namespace {
+
+// CompareSHA1Hashes is a helper function for using bsearch() with an array of
+// SHA1 hashes.
+int CompareSHA1Hashes(const void* a, const void* b) {
+ return memcmp(a, b, base::kSHA1Length);
+}
+
+} // namespace
+
+
+bool SHA1HashValue::Equals(const SHA1HashValue& other) const {
+ return memcmp(data, other.data, sizeof(data)) == 0;
+}
+
+bool SHA256HashValue::Equals(const SHA256HashValue& other) const {
+ return memcmp(data, other.data, sizeof(data)) == 0;
+}
+
+bool HashValue::Equals(const HashValue& other) const {
+ if (tag != other.tag)
+ return false;
+ switch (tag) {
+ case HASH_VALUE_SHA1:
+ return fingerprint.sha1.Equals(other.fingerprint.sha1);
+ case HASH_VALUE_SHA256:
+ return fingerprint.sha256.Equals(other.fingerprint.sha256);
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << tag;
+ return false;
+ }
+}
+
+bool HashValue::FromString(const base::StringPiece value) {
+ base::StringPiece base64_str;
+ if (value.starts_with("sha1/")) {
+ tag = HASH_VALUE_SHA1;
+ base64_str = value.substr(5);
+ } else if (value.starts_with("sha256/")) {
+ tag = HASH_VALUE_SHA256;
+ base64_str = value.substr(7);
+ } else {
+ return false;
+ }
+
+ std::string decoded;
+ if (!base::Base64Decode(base64_str, &decoded) || decoded.size() != size())
+ return false;
+
+ memcpy(data(), decoded.data(), size());
+ return true;
+}
+
+std::string HashValue::ToString() const {
+ std::string base64_str;
+ base::Base64Encode(base::StringPiece(reinterpret_cast<const char*>(data()),
+ size()), &base64_str);
+ switch (tag) {
+ case HASH_VALUE_SHA1:
+ return std::string("sha1/") + base64_str;
+ case HASH_VALUE_SHA256:
+ return std::string("sha256/") + base64_str;
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << tag;
+ return std::string("unknown/" + base64_str);
+ }
+}
+
+size_t HashValue::size() const {
+ switch (tag) {
+ case HASH_VALUE_SHA1:
+ return sizeof(fingerprint.sha1.data);
+ case HASH_VALUE_SHA256:
+ return sizeof(fingerprint.sha256.data);
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << tag;
+ // While an invalid tag should not happen, return a non-zero length
+ // to avoid compiler warnings when the result of size() is
+ // used with functions like memset.
+ return sizeof(fingerprint.sha1.data);
+ }
+}
+
+unsigned char* HashValue::data() {
+ return const_cast<unsigned char*>(const_cast<const HashValue*>(this)->data());
+}
+
+const unsigned char* HashValue::data() const {
+ switch (tag) {
+ case HASH_VALUE_SHA1:
+ return fingerprint.sha1.data;
+ case HASH_VALUE_SHA256:
+ return fingerprint.sha256.data;
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << tag;
+ return NULL;
+ }
+}
+
+bool IsSHA1HashInSortedArray(const SHA1HashValue& hash,
+ const uint8* array,
+ size_t array_byte_len) {
+ DCHECK_EQ(0u, array_byte_len % base::kSHA1Length);
+ const size_t arraylen = array_byte_len / base::kSHA1Length;
+ return NULL != bsearch(hash.data, array, arraylen, base::kSHA1Length,
+ CompareSHA1Hashes);
+}
+
+} // namespace net
diff --git a/net/base/hash_value.h b/net/base/hash_value.h
new file mode 100644
index 0000000..fc8d163
--- /dev/null
+++ b/net/base/hash_value.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_BASE_HASH_VALUE_H_
+#define NET_BASE_HASH_VALUE_H_
+
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/string_piece.h"
+#include "build/build_config.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct NET_EXPORT SHA1HashValue {
+ bool Equals(const SHA1HashValue& other) const;
+
+ unsigned char data[20];
+};
+
+struct NET_EXPORT SHA256HashValue {
+ bool Equals(const SHA256HashValue& other) const;
+
+ unsigned char data[32];
+};
+
+enum HashValueTag {
+ HASH_VALUE_SHA1,
+ HASH_VALUE_SHA256,
+
+ // This must always be last.
+ HASH_VALUE_TAGS_COUNT
+};
+
+class NET_EXPORT HashValue {
+ public:
+ explicit HashValue(HashValueTag tag) : tag(tag) {}
+ HashValue() : tag(HASH_VALUE_SHA1) {}
+
+ // Check for equality of hash values
+ // This function may have VARIABLE timing which leaks information
+ // about its inputs. For example it may exit early once a
+ // nonequal character is discovered. Thus, for security reasons
+ // this function MUST NOT be used with secret values (such as
+ // password hashes, MAC tags, etc.)
+ bool Equals(const HashValue& other) const;
+
+ // Serializes/Deserializes hashes in the form of
+ // <hash-name>"/"<base64-hash-value>
+ // (eg: "sha1/...")
+ // This format may be persisted to permanent storage, so
+ // care should be taken before changing the serialization.
+ //
+ // This format is used for:
+ // - net_internals display/setting public-key pins
+ // - logging public-key pins
+ // - serializing public-key pins
+
+ // Deserializes a HashValue from a string. On error, returns
+ // false and MAY change the contents of HashValue to contain invalid data.
+ bool FromString(const base::StringPiece input);
+
+ // Serializes the HashValue to a string. If an invalid HashValue
+ // is supplied (eg: an unknown hash tag), returns "unknown"/<base64>
+ std::string ToString() const;
+
+ size_t size() const;
+ unsigned char* data();
+ const unsigned char* data() const;
+
+ HashValueTag tag;
+
+ private:
+ union {
+ SHA1HashValue sha1;
+ SHA256HashValue sha256;
+ } fingerprint;
+};
+
+typedef std::vector<HashValue> HashValueVector;
+
+
+class SHA1HashValueLessThan {
+ public:
+ bool operator()(const SHA1HashValue& lhs,
+ const SHA1HashValue& rhs) const {
+ return memcmp(lhs.data, rhs.data, sizeof(lhs.data)) < 0;
+ }
+};
+
+class SHA256HashValueLessThan {
+ public:
+ bool operator()(const SHA256HashValue& lhs,
+ const SHA256HashValue& rhs) const {
+ return memcmp(lhs.data, rhs.data, sizeof(lhs.data)) < 0;
+ }
+};
+
+class HashValuesEqual {
+ public:
+ explicit HashValuesEqual(const HashValue& fingerprint) :
+ fingerprint_(fingerprint) {}
+
+ bool operator()(const HashValue& other) const {
+ return fingerprint_.Equals(other);
+ }
+
+ const HashValue& fingerprint_;
+};
+
+
+// IsSHA1HashInSortedArray returns true iff |hash| is in |array|, a sorted
+// array of SHA1 hashes.
+bool IsSHA1HashInSortedArray(const SHA1HashValue& hash,
+ const uint8* array,
+ size_t array_byte_len);
+
+} // namespace net
+
+#endif // NET_BASE_HASH_VALUE_H_
diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc
index 75d26cb..0fc9a68 100644
--- a/net/base/transport_security_state.cc
+++ b/net/base/transport_security_state.cc
@@ -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 @@ void TransportSecurityState::DeleteSince(const base::Time& time) {
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 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries,
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 @@ static const struct HSTSPreload* GetHSTSPreload(
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 @@ void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) {
}
// 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 @@ void TransportSecurityState::AddOrUpdateForcedHosts(
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()),
diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h
index 5f73f74..06fee49 100644
--- a/net/base/transport_security_state.h
+++ b/net/base/transport_security_state.h
@@ -61,20 +61,6 @@ class NET_EXPORT TransportSecurityState
DomainState();
~DomainState();
- // Parses |value| as a Public-Key-Pins header. If successful, returns true
- // and updates the |dynamic_spki_hashes| and |dynamic_spki_hashes_expiry|
- // fields; otherwise, returns false without updating any fields.
- // Interprets the max-age directive relative to |now|.
- bool ParsePinsHeader(const base::Time& now,
- const std::string& value,
- const SSLInfo& ssl_info);
-
- // Parses |value| as a Strict-Transport-Security header. If successful,
- // returns true and updates the |upgrade_mode|, |upgrade_expiry| and
- // |include_subdomains| fields; otherwise, returns false without updating
- // any fields. Interprets the max-age directive relative to |now|.
- bool ParseSTSHeader(const base::Time& now, const std::string& value);
-
// Takes a set of SubjectPublicKeyInfo |hashes| and returns true if:
// 1) |bad_static_spki_hashes| does not intersect |hashes|; AND
// 2) Both |static_spki_hashes| and |dynamic_spki_hashes| are empty
@@ -237,6 +223,16 @@ class NET_EXPORT TransportSecurityState
void AddOrUpdateForcedHosts(const std::string& hashed_host,
const DomainState& state);
+ // Processes an HSTS header value from the host, adding entries to
+ // dynamic state if necessary.
+ bool AddHSTSHeader(const std::string& host, const std::string& value);
+
+ // Processes an HPKP header value from the host, adding entries to
+ // dynamic state if necessary. ssl_info is used to check that
+ // the specified pins overlap with the certificate chain.
+ bool AddHPKPHeader(const std::string& host, const std::string& value,
+ 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.
@@ -249,11 +245,6 @@ class NET_EXPORT TransportSecurityState
static bool IsGooglePinnedProperty(const std::string& host,
bool sni_enabled);
- // Decodes a pin string |value| (e.g. "sha1/hvfkN/qlp/zhXR3cuerq6jd2Z7g=").
- // If parsing succeeded, updates |*out| and returns true; otherwise returns
- // false without updating |*out|.
- static bool ParsePin(const std::string& value, HashValue* out);
-
// The maximum number of seconds for which we'll cache an HSTS request.
static const long int kMaxHSTSAgeSecs;
diff --git a/net/base/transport_security_state_static.h b/net/base/transport_security_state_static.h
index 48710e5..cbfa06c 100644
--- a/net/base/transport_security_state_static.h
+++ b/net/base/transport_security_state_static.h
@@ -8,163 +8,215 @@
#define NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_
// These are SubjectPublicKeyInfo hashes for public key pinning. The
-// hashes are base64 encoded, SHA1 digests.
+// hashes are SHA1 digests.
static const char kSPKIHash_TestSPKI[] =
- "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
static const char kSPKIHash_VeriSignClass3[] =
- "sha1/4n972HfV354KP560yw4uqe/baXc=";
+ "\xe2\x7f\x7b\xd8\x77\xd5\xdf\x9e\x0a\x3f"
+ "\x9e\xb4\xcb\x0e\x2e\xa9\xef\xdb\x69\x77";
static const char kSPKIHash_VeriSignClass3_G3[] =
- "sha1/IvGeLsbqzPxdI0b0wuj2xVTdXgc=";
+ "\x22\xf1\x9e\x2e\xc6\xea\xcc\xfc\x5d\x23"
+ "\x46\xf4\xc2\xe8\xf6\xc5\x54\xdd\x5e\x07";
static const char kSPKIHash_Google1024[] =
- "sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0=";
+ "\x40\xc5\x40\x1d\x6f\x8c\xba\xf0\x8b\x00"
+ "\xed\xef\xb1\xee\x87\xd0\x05\xb3\xb9\xcd";
static const char kSPKIHash_Google2048[] =
- "sha1/AbkhxY0L343gKf+cki7NVWp+ozk=";
+ "\x01\xb9\x21\xc5\x8d\x0b\xdf\x8d\xe0\x29"
+ "\xff\x9c\x92\x2e\xcd\x55\x6a\x7e\xa3\x39";
static const char kSPKIHash_EquifaxSecureCA[] =
- "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=";
+ "\x48\xe6\x68\xf9\x2b\xd2\xb2\x95\xd7\x47"
+ "\xd8\x23\x20\x10\x4f\x33\x98\x90\x9f\xd4";
static const char kSPKIHash_Aetna[] =
- "sha1/klKqFN6/gK4wqtlOYDhwJKVDLxo=";
+ "\x92\x52\xaa\x14\xde\xbf\x80\xae\x30\xaa"
+ "\xd9\x4e\x60\x38\x70\x24\xa5\x43\x2f\x1a";
static const char kSPKIHash_GeoTrustGlobal[] =
- "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4=";
+ "\xc0\x7a\x98\x68\x8d\x89\xfb\xab\x05\x64"
+ "\x0c\x11\x7d\xaa\x7d\x65\xb8\xca\xcc\x4e";
static const char kSPKIHash_GeoTrustPrimary[] =
- "sha1/sBmJ5+/7Sq/LFI9YRjl2IkFQ4bo=";
+ "\xb0\x19\x89\xe7\xef\xfb\x4a\xaf\xcb\x14"
+ "\x8f\x58\x46\x39\x76\x22\x41\x50\xe1\xba";
static const char kSPKIHash_Intel[] =
- "sha1/DsYq91myCBCQJW/D3f2KZjEwK8U=";
+ "\x0e\xc6\x2a\xf7\x59\xb2\x08\x10\x90\x25"
+ "\x6f\xc3\xdd\xfd\x8a\x66\x31\x30\x2b\xc5";
static const char kSPKIHash_TCTrustCenter[] =
- "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=";
+ "\x83\x3b\x84\x10\x00\x7f\x6e\x4a\x9d\x41"
+ "\x2d\xc4\x22\x39\x36\x6f\x2e\xe5\x5b\xe9";
static const char kSPKIHash_Vodafone[] =
- "sha1/DX/hXFUUNmiZ/EDWIgjvIuvRFRw=";
+ "\x0d\x7f\xe1\x5c\x55\x14\x36\x68\x99\xfc"
+ "\x40\xd6\x22\x08\xef\x22\xeb\xd1\x15\x1c";
static const char kSPKIHash_RapidSSL[] =
- "sha1/o5OZxATDsgmwgcIfIWIneMJ0jkw=";
+ "\xa3\x93\x99\xc4\x04\xc3\xb2\x09\xb0\x81"
+ "\xc2\x1f\x21\x62\x27\x78\xc2\x74\x8e\x4c";
static const char kSPKIHash_DigiCertEVRoot[] =
- "sha1/gzF+YoVCU9bXeDGQ7JGQVumRueM=";
+ "\x83\x31\x7e\x62\x85\x42\x53\xd6\xd7\x78"
+ "\x31\x90\xec\x91\x90\x56\xe9\x91\xb9\xe3";
static const char kSPKIHash_Tor1[] =
- "sha1/juNxSTv9UANmpC9kF5GKpmWNx3Y=";
+ "\x8e\xe3\x71\x49\x3b\xfd\x50\x03\x66\xa4"
+ "\x2f\x64\x17\x91\x8a\xa6\x65\x8d\xc7\x76";
static const char kSPKIHash_Tor2[] =
- "sha1/lia43lPolzSPVIq34Dw57uYcLD8=";
+ "\x96\x26\xb8\xde\x53\xe8\x97\x34\x8f\x54"
+ "\x8a\xb7\xe0\x3c\x39\xee\xe6\x1c\x2c\x3f";
static const char kSPKIHash_Tor3[] =
- "sha1/rzEyQIKOh77j87n5bjWUNguXF8Y=";
+ "\xaf\x31\x32\x40\x82\x8e\x87\xbe\xe3\xf3"
+ "\xb9\xf9\x6e\x35\x94\x36\x0b\x97\x17\xc6";
static const char kSPKIHash_VeriSignClass1[] =
- "sha1/I0PRSKJViZuUfUYaeX7ATP7RcLc=";
+ "\x23\x43\xd1\x48\xa2\x55\x89\x9b\x94\x7d"
+ "\x46\x1a\x79\x7e\xc0\x4c\xfe\xd1\x70\xb7";
static const char kSPKIHash_VeriSignClass3_G4[] =
- "sha1/7WYxNdMb1OymFMQp4xkGn5TBJlA=";
+ "\xed\x66\x31\x35\xd3\x1b\xd4\xec\xa6\x14"
+ "\xc4\x29\xe3\x19\x06\x9f\x94\xc1\x26\x50";
static const char kSPKIHash_VeriSignClass4_G3[] =
- "sha1/PANDaGiVHPNpKri0Jtq6j+ki5b0=";
+ "\x3c\x03\x43\x68\x68\x95\x1c\xf3\x69\x2a"
+ "\xb8\xb4\x26\xda\xba\x8f\xe9\x22\xe5\xbd";
static const char kSPKIHash_VeriSignClass1_G3[] =
- "sha1/VRmyeKyygdftp6vBg5nDu2kEJLU=";
+ "\x55\x19\xb2\x78\xac\xb2\x81\xd7\xed\xa7"
+ "\xab\xc1\x83\x99\xc3\xbb\x69\x04\x24\xb5";
static const char kSPKIHash_VeriSignClass2_G3[] =
- "sha1/Wr7Fddyu87COJxlD/H8lDD32YeM=";
+ "\x5a\xbe\xc5\x75\xdc\xae\xf3\xb0\x8e\x27"
+ "\x19\x43\xfc\x7f\x25\x0c\x3d\xf6\x61\xe3";
static const char kSPKIHash_VeriSignClass3_G2[] =
- "sha1/GiG0lStik84Ys2XsnA6TTLOB5tQ=";
+ "\x1a\x21\xb4\x95\x2b\x62\x93\xce\x18\xb3"
+ "\x65\xec\x9c\x0e\x93\x4c\xb3\x81\xe6\xd4";
static const char kSPKIHash_VeriSignClass2_G2[] =
- "sha1/Eje6RRfurSkm/cHN/r7t8t7ZFFw=";
+ "\x12\x37\xba\x45\x17\xee\xad\x29\x26\xfd"
+ "\xc1\xcd\xfe\xbe\xed\xf2\xde\xd9\x14\x5c";
static const char kSPKIHash_VeriSignClass3_G5[] =
- "sha1/sYEIGhmkwJQf+uiVKMEkyZs0rMc=";
+ "\xb1\x81\x08\x1a\x19\xa4\xc0\x94\x1f\xfa"
+ "\xe8\x95\x28\xc1\x24\xc9\x9b\x34\xac\xc7";
static const char kSPKIHash_VeriSignUniversal[] =
- "sha1/u8I+KQuzKHcdrT6iTb30I70GsD0=";
+ "\xbb\xc2\x3e\x29\x0b\xb3\x28\x77\x1d\xad"
+ "\x3e\xa2\x4d\xbd\xf4\x23\xbd\x06\xb0\x3d";
static const char kSPKIHash_Twitter1[] =
- "sha1/Vv7zwhR9TtOIN/29MFI4cgHld40=";
+ "\x56\xfe\xf3\xc2\x14\x7d\x4e\xd3\x88\x37"
+ "\xfd\xbd\x30\x52\x38\x72\x01\xe5\x77\x8d";
static const char kSPKIHash_GeoTrustGlobal2[] =
- "sha1/cTg28gIxU0crbrplRqkQFVggBQk=";
+ "\x71\x38\x36\xf2\x02\x31\x53\x47\x2b\x6e"
+ "\xba\x65\x46\xa9\x10\x15\x58\x20\x05\x09";
static const char kSPKIHash_GeoTrustUniversal[] =
- "sha1/h+hbY1PGI6MSjLD/u/VR/lmADiI=";
+ "\x87\xe8\x5b\x63\x53\xc6\x23\xa3\x12\x8c"
+ "\xb0\xff\xbb\xf5\x51\xfe\x59\x80\x0e\x22";
static const char kSPKIHash_GeoTrustUniversal2[] =
- "sha1/Xk9ThoXdT57KX9wNRW99UbHcm3s=";
+ "\x5e\x4f\x53\x86\x85\xdd\x4f\x9e\xca\x5f"
+ "\xdc\x0d\x45\x6f\x7d\x51\xb1\xdc\x9b\x7b";
static const char kSPKIHash_GeoTrustPrimary_G2[] =
- "sha1/vb6nG6txV/nkddlU0rcngBqCJoI=";
+ "\xbd\xbe\xa7\x1b\xab\x71\x57\xf9\xe4\x75"
+ "\xd9\x54\xd2\xb7\x27\x80\x1a\x82\x26\x82";
static const char kSPKIHash_GeoTrustPrimary_G3[] =
- "sha1/nKmNAK90Dd2BgNITRaWLjy6UONY=";
+ "\x9c\xa9\x8d\x00\xaf\x74\x0d\xdd\x81\x80"
+ "\xd2\x13\x45\xa5\x8b\x8f\x2e\x94\x38\xd6";
static const char kSPKIHash_Entrust_2048[] =
- "sha1/VeSB0RGAvtiJuQijMfmhJAkWuXA=";
+ "\x55\xe4\x81\xd1\x11\x80\xbe\xd8\x89\xb9"
+ "\x08\xa3\x31\xf9\xa1\x24\x09\x16\xb9\x70";
static const char kSPKIHash_Entrust_EV[] =
- "sha1/ukKwgYhTiB2GY71MwF4I/upuu3c=";
+ "\xba\x42\xb0\x81\x88\x53\x88\x1d\x86\x63"
+ "\xbd\x4c\xc0\x5e\x08\xfe\xea\x6e\xbb\x77";
static const char kSPKIHash_Entrust_G2[] =
- "sha1/qzDTr0vY8WtYae5FaSnahLhzlIg=";
+ "\xab\x30\xd3\xaf\x4b\xd8\xf1\x6b\x58\x69"
+ "\xee\x45\x69\x29\xda\x84\xb8\x73\x94\x88";
static const char kSPKIHash_Entrust_SSL[] =
- "sha1/8BdiE1U9s/8KAGv7UISX8+1i0Bo=";
+ "\xf0\x17\x62\x13\x55\x3d\xb3\xff\x0a\x00"
+ "\x6b\xfb\x50\x84\x97\xf3\xed\x62\xd0\x1a";
static const char kSPKIHash_AAACertificateServices[] =
- "sha1/xDAoxdPjCAwQRIssd7okU5dgu/k=";
+ "\xc4\x30\x28\xc5\xd3\xe3\x08\x0c\x10\x44"
+ "\x8b\x2c\x77\xba\x24\x53\x97\x60\xbb\xf9";
static const char kSPKIHash_AddTrustClass1CARoot[] =
- "sha1/i9vXzKBoU0IW9MErJUT8Apyli0c=";
+ "\x8b\xdb\xd7\xcc\xa0\x68\x53\x42\x16\xf4"
+ "\xc1\x2b\x25\x44\xfc\x02\x9c\xa5\x8b\x47";
static const char kSPKIHash_AddTrustExternalCARoot[] =
- "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=";
+ "\x4f\x9c\x7d\x21\x79\x9c\xad\x0e\xd8\xb9"
+ "\x0c\x57\x9f\x1a\x02\x99\xe7\x90\xf3\x87";
static const char kSPKIHash_AddTrustPublicCARoot[] =
- "sha1/qFdl1ugyyMUZY3Namhd0OoHf7i4=";
+ "\xa8\x57\x65\xd6\xe8\x32\xc8\xc5\x19\x63"
+ "\x73\x5a\x9a\x17\x74\x3a\x81\xdf\xee\x2e";
static const char kSPKIHash_AddTrustQualifiedCARoot[] =
- "sha1/vOS3IxJVmOVjQRkcUOS2R8J2Bdc=";
+ "\xbc\xe4\xb7\x23\x12\x55\x98\xe5\x63\x41"
+ "\x19\x1c\x50\xe4\xb6\x47\xc2\x76\x05\xd7";
static const char kSPKIHash_COMODOCertificationAuthority[] =
- "sha1/EeSR0cnkwOuazs9zVF3h8agwPsM=";
+ "\x11\xe4\x91\xd1\xc9\xe4\xc0\xeb\x9a\xce"
+ "\xcf\x73\x54\x5d\xe1\xf1\xa8\x30\x3e\xc3";
static const char kSPKIHash_SecureCertificateServices[] =
- "sha1/PLQahC71XPIaPaVKyNG+OQh2N7w=";
+ "\x3c\xb4\x1a\x84\x2e\xf5\x5c\xf2\x1a\x3d"
+ "\xa5\x4a\xc8\xd1\xbe\x39\x08\x76\x37\xbc";
static const char kSPKIHash_TrustedCertificateServices[] =
- "sha1//nLI678ML7sOJhOTkzwsqY3cJJQ=";
+ "\xfe\x72\xc8\xeb\xbf\x0c\x2f\xbb\x0e\x26"
+ "\x13\x93\x93\x3c\x2c\xa9\x8d\xdc\x24\x94";
static const char kSPKIHash_UTNDATACorpSGC[] =
- "sha1/UzLRs89/+uDxoF2FTpLSnkUdtE8=";
+ "\x53\x32\xd1\xb3\xcf\x7f\xfa\xe0\xf1\xa0"
+ "\x5d\x85\x4e\x92\xd2\x9e\x45\x1d\xb4\x4f";
static const char kSPKIHash_UTNUSERFirstClientAuthenticationandEmail[] =
- "sha1/iYJnfcSdJnAAS7RQSHzePa4Ebn0=";
+ "\x89\x82\x67\x7d\xc4\x9d\x26\x70\x00\x4b"
+ "\xb4\x50\x48\x7c\xde\x3d\xae\x04\x6e\x7d";
static const char kSPKIHash_UTNUSERFirstHardware[] =
- "sha1/oXJfJhsomEOVXQc31YWWnUvSw0U=";
+ "\xa1\x72\x5f\x26\x1b\x28\x98\x43\x95\x5d"
+ "\x07\x37\xd5\x85\x96\x9d\x4b\xd2\xc3\x45";
static const char kSPKIHash_UTNUSERFirstObject[] =
- "sha1/2u1kdBScFDyr3ZmpvVsoTYs8ydg=";
+ "\xda\xed\x64\x74\x14\x9c\x14\x3c\xab\xdd"
+ "\x99\xa9\xbd\x5b\x28\x4d\x8b\x3c\xc9\xd8";
static const char kSPKIHash_GTECyberTrustGlobalRoot[] =
- "sha1/WXkS3mF11m/EI7d3E3THlt5viHI=";
+ "\x59\x79\x12\xde\x61\x75\xd6\x6f\xc4\x23"
+ "\xb7\x77\x13\x74\xc7\x96\xde\x6f\x88\x72";
static const char kSPKIHash_Tor2web[] =
- "sha1/GeW1hxvUgy7I9ZSX/sZe+0jjM7E=";
+ "\x19\xe5\xb5\x87\x1b\xd4\x83\x2e\xc8\xf5"
+ "\x94\x97\xfe\xc6\x5e\xfb\x48\xe3\x33\xb1";
static const char kSPKIHash_AlphaSSL_G2[] =
- "sha1/5STpjjF9yPytkFN8kecNpHCTkF8=";
+ "\xe5\x24\xe9\x8e\x31\x7d\xc8\xfc\xad\x90"
+ "\x53\x7c\x91\xe7\x0d\xa4\x70\x93\x90\x5f";
static const char kSPKIHash_CryptoCat1[] =
- "sha1/TIfOhSz0wE1nqeDsUQx/OxSz6ck=";
+ "\x4c\x87\xce\x85\x2c\xf4\xc0\x4d\x67\xa9"
+ "\xe0\xec\x51\x0c\x7f\x3b\x14\xb3\xe9\xc9";
// The following is static data describing the hosts that are hardcoded with
// certificate pins or HSTS information.
diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc
index 3192f01..b68f083 100644
--- a/net/base/transport_security_state_unittest.cc
+++ b/net/base/transport_security_state_unittest.cc
@@ -46,405 +46,6 @@ class TransportSecurityStateTest : public testing::Test {
}
};
-TEST_F(TransportSecurityStateTest, BogusHeaders) {
- TransportSecurityState::DomainState state;
- base::Time now = base::Time::Now();
-
- EXPECT_FALSE(state.ParseSTSHeader(now, ""));
- EXPECT_FALSE(state.ParseSTSHeader(now, " "));
- EXPECT_FALSE(state.ParseSTSHeader(now, "abc"));
- EXPECT_FALSE(state.ParseSTSHeader(now, " abc"));
- EXPECT_FALSE(state.ParseSTSHeader(now, " abc "));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age"));
- EXPECT_FALSE(state.ParseSTSHeader(now, " max-age"));
- EXPECT_FALSE(state.ParseSTSHeader(now, " max-age "));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age="));
- EXPECT_FALSE(state.ParseSTSHeader(now, " max-age="));
- EXPECT_FALSE(state.ParseSTSHeader(now, " max-age ="));
- EXPECT_FALSE(state.ParseSTSHeader(now, " max-age= "));
- EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = "));
- EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = xy"));
- EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = 3488a923"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488a923 "));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-ag=3488923"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-aged=3488923"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age==3488923"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "amax-age=3488923"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=-3488923"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923;"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 e"));
- EXPECT_FALSE(state.ParseSTSHeader(
- now, "max-age=3488923 includesubdomain"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923includesubdomains"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923=includesubdomains"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomainx"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomain="));
- EXPECT_FALSE(state.ParseSTSHeader(
- now, "max-age=3488923 includesubdomain=true"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomainsx"));
- EXPECT_FALSE(state.ParseSTSHeader(
- now, "max-age=3488923 includesubdomains x"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=34889.23 includesubdomains"));
- EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=34889 includesubdomains"));
-
- // Check that |state| was not updated by expecting the default
- // values for its predictable fields.
- EXPECT_EQ(state.upgrade_mode,
- TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
- EXPECT_FALSE(state.include_subdomains);
-}
-
-static bool GetPublicKeyHash(const net::X509Certificate::OSCertHandle& cert,
- HashValue* hash) {
- std::string der_bytes;
- if (!net::X509Certificate::GetDEREncoded(cert, &der_bytes))
- return false;
-
- base::StringPiece spki;
- if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki))
- return false;
-
- switch (hash->tag) {
- case HASH_VALUE_SHA1:
- base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(spki.data()),
- spki.size(), hash->data());
- break;
- case HASH_VALUE_SHA256:
- crypto::SHA256HashString(spki, hash->data(), crypto::kSHA256Length);
- break;
- default:
- NOTREACHED() << "Unknown HashValueTag " << hash->tag;
- }
-
- return true;
-}
-
-static std::string GetPinFromCert(X509Certificate* cert, HashValueTag tag) {
- HashValue spki_hash(tag);
- EXPECT_TRUE(GetPublicKeyHash(cert->os_cert_handle(), &spki_hash));
-
- std::string base64;
- base::Base64Encode(base::StringPiece(
- reinterpret_cast<char*>(spki_hash.data()), spki_hash.size()), &base64);
-
- std::string label;
- switch (tag) {
- case HASH_VALUE_SHA1:
- label = "pin-sha1=";
- break;
- case HASH_VALUE_SHA256:
- label = "pin-sha256=";
- break;
- default:
- NOTREACHED() << "Unknown HashValueTag " << tag;
- }
-
- return label + HttpUtil::Quote(base64);
-}
-
-static void TestBogusPinsHeaders(HashValueTag tag) {
- TransportSecurityState::DomainState state;
- SSLInfo ssl_info;
- ssl_info.cert =
- ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
- std::string good_pin = GetPinFromCert(ssl_info.cert, tag);
- base::Time now = base::Time::Now();
-
- // The backup pin is fake --- it just has to not be in the chain.
- std::string backup_pin = "pin-sha1=" +
- HttpUtil::Quote("6dcfXufJLW3J6S/9rRe4vUlBj5g=");
-
- EXPECT_FALSE(state.ParsePinsHeader(now, "", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " ", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "abc", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " abc", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " abc ", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-age", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " max-age", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " max-age ", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " max-age=", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " max-age =", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " max-age= ", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " max-age = ", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, " max-age = xy", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(
- now,
- " max-age = 3488a923",
- ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488a923 ", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now,
- "max-ag=3488923pins=" + good_pin + "," + backup_pin,
- ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923" + backup_pin,
- ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923; " + backup_pin,
- ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now,
- "max-aged=3488923; " + backup_pin + ";" + backup_pin,
- ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now,
- "max-aged=3488923; " + good_pin + ";" + good_pin,
- ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923; " + good_pin,
- ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-age==3488923", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "amax-age=3488923", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=-3488923", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488923;", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488923 e", ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(
- now,
- "max-age=3488923 includesubdomain",
- ssl_info));
- EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=34889.23", ssl_info));
-
- // Check that |state| was not updated by expecting the default
- // values for its predictable fields.
- EXPECT_EQ(state.upgrade_mode,
- TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
- EXPECT_FALSE(state.include_subdomains);
-}
-
-TEST_F(TransportSecurityStateTest, BogusPinsHeadersSHA1) {
- TestBogusPinsHeaders(HASH_VALUE_SHA1);
-}
-
-TEST_F(TransportSecurityStateTest, BogusPinsHeadersSHA256) {
- TestBogusPinsHeaders(HASH_VALUE_SHA256);
-}
-
-TEST_F(TransportSecurityStateTest, ValidSTSHeaders) {
- TransportSecurityState::DomainState state;
- base::Time expiry;
- base::Time now = base::Time::Now();
-
- EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=243"));
- expiry = now + base::TimeDelta::FromSeconds(243);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_FALSE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now, " Max-agE = 567"));
- expiry = now + base::TimeDelta::FromSeconds(567);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_FALSE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now, " mAx-aGe = 890 "));
- expiry = now + base::TimeDelta::FromSeconds(890);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_FALSE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=123;incLudesUbdOmains"));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now, "incLudesUbdOmains; max-age=123"));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now, " incLudesUbdOmains; max-age=123"));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now,
- " incLudesUbdOmains; max-age=123; pumpkin=kitten"));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now,
- " pumpkin=894; incLudesUbdOmains; max-age=123 "));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now,
- " pumpkin; incLudesUbdOmains; max-age=123 "));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now,
- " pumpkin; incLudesUbdOmains; max-age=\"123\" "));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now,
- "animal=\"squirrel; distinguished\"; incLudesUbdOmains; max-age=123"));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=394082; incLudesUbdOmains"));
- expiry = now + base::TimeDelta::FromSeconds(394082);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(
- now, "max-age=39408299 ;incLudesUbdOmains"));
- expiry = now + base::TimeDelta::FromSeconds(
- std::min(TransportSecurityState::kMaxHSTSAgeSecs, 39408299l));
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(
- now, "max-age=394082038 ; incLudesUbdOmains"));
- expiry = now + base::TimeDelta::FromSeconds(
- std::min(TransportSecurityState::kMaxHSTSAgeSecs, 394082038l));
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-
- EXPECT_TRUE(state.ParseSTSHeader(
- now, " max-age=0 ; incLudesUbdOmains "));
- expiry = now + base::TimeDelta::FromSeconds(0);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
- // When max-age == 0, we downgrade to MODE_DEFAULT rather than deleting
- // the entire DomainState. (That is because we currently overload
- // DomainState to also include pins, and we don't want to invalidate any
- // opportunistic pins that may be in place.)
- EXPECT_EQ(TransportSecurityState::DomainState::MODE_DEFAULT,
- state.upgrade_mode);
-
- EXPECT_TRUE(state.ParseSTSHeader(
- now,
- " max-age=999999999999999999999999999999999999999999999 ;"
- " incLudesUbdOmains "));
- expiry = now + base::TimeDelta::FromSeconds(
- TransportSecurityState::kMaxHSTSAgeSecs);
- EXPECT_EQ(expiry, state.upgrade_expiry);
- EXPECT_TRUE(state.include_subdomains);
-}
-
-static void TestValidPinsHeaders(HashValueTag tag) {
- TransportSecurityState::DomainState state;
- base::Time expiry;
- base::Time now = base::Time::Now();
-
- // Set up a realistic SSLInfo with a realistic cert chain.
- FilePath certs_dir = GetTestCertsDirectory();
- scoped_refptr<X509Certificate> ee_cert =
- ImportCertFromFile(certs_dir, "2048-rsa-ee-by-2048-rsa-intermediate.pem");
- ASSERT_NE(static_cast<X509Certificate*>(NULL), ee_cert);
- scoped_refptr<X509Certificate> intermediate =
- ImportCertFromFile(certs_dir, "2048-rsa-intermediate.pem");
- ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate);
- X509Certificate::OSCertHandles intermediates;
- intermediates.push_back(intermediate->os_cert_handle());
- SSLInfo ssl_info;
- ssl_info.cert = X509Certificate::CreateFromHandle(ee_cert->os_cert_handle(),
- intermediates);
-
- // Add the root that signed the intermediate for this test.
- scoped_refptr<X509Certificate> root_cert =
- ImportCertFromFile(certs_dir, "2048-rsa-root.pem");
- ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert);
- ScopedTestRoot scoped_root(root_cert);
-
- // Verify has the side-effect of populating public_key_hashes, which
- // ParsePinsHeader needs. (It wants to check pins against the validated
- // chain, not just the presented chain.)
- int rv = ERR_FAILED;
- CertVerifyResult result;
- scoped_ptr<CertVerifier> verifier(CertVerifier::CreateDefault());
- TestCompletionCallback callback;
- CertVerifier::RequestHandle handle = NULL;
- rv = verifier->Verify(ssl_info.cert, "127.0.0.1", 0, NULL, &result,
- callback.callback(), &handle, BoundNetLog());
- rv = callback.GetResult(rv);
- ASSERT_EQ(OK, rv);
- // Normally, ssl_client_socket_nss would do this, but for a unit test we
- // fake it.
- ssl_info.public_key_hashes = result.public_key_hashes;
- std::string good_pin = GetPinFromCert(ssl_info.cert, /*tag*/HASH_VALUE_SHA1);
- DLOG(WARNING) << "good pin: " << good_pin;
-
- // The backup pin is fake --- we just need an SPKI hash that does not match
- // the hash of any SPKI in the certificate chain.
- std::string backup_pin = "pin-sha1=" +
- HttpUtil::Quote("6dcfXufJLW3J6S/9rRe4vUlBj5g=");
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- "max-age=243; " + good_pin + ";" + backup_pin,
- ssl_info));
- expiry = now + base::TimeDelta::FromSeconds(243);
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- " " + good_pin + "; " + backup_pin + " ; Max-agE = 567",
- ssl_info));
- expiry = now + base::TimeDelta::FromSeconds(567);
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- good_pin + ";" + backup_pin + " ; mAx-aGe = 890 ",
- ssl_info));
- expiry = now + base::TimeDelta::FromSeconds(890);
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- good_pin + ";" + backup_pin + "; max-age=123;IGNORED;",
- ssl_info));
- expiry = now + base::TimeDelta::FromSeconds(123);
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- "max-age=394082;" + backup_pin + ";" + good_pin + "; ",
- ssl_info));
- expiry = now + base::TimeDelta::FromSeconds(394082);
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- "max-age=39408299 ;" + backup_pin + ";" + good_pin + "; ",
- ssl_info));
- expiry = now + base::TimeDelta::FromSeconds(
- std::min(TransportSecurityState::kMaxHSTSAgeSecs, 39408299l));
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- "max-age=39408038 ; cybers=39408038 ; " +
- good_pin + ";" + backup_pin + "; ",
- ssl_info));
- expiry = now + base::TimeDelta::FromSeconds(
- std::min(TransportSecurityState::kMaxHSTSAgeSecs, 394082038l));
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- " max-age=0 ; " + good_pin + ";" + backup_pin,
- ssl_info));
- expiry = now + base::TimeDelta::FromSeconds(0);
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-
- EXPECT_TRUE(state.ParsePinsHeader(
- now,
- " max-age=999999999999999999999999999999999999999999999 ; " +
- backup_pin + ";" + good_pin + "; ",
- ssl_info));
- expiry = now +
- base::TimeDelta::FromSeconds(TransportSecurityState::kMaxHSTSAgeSecs);
- EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
-}
-
-TEST_F(TransportSecurityStateTest, ValidPinsHeadersSHA1) {
- TestValidPinsHeaders(HASH_VALUE_SHA1);
-}
-
-TEST_F(TransportSecurityStateTest, ValidPinsHeadersSHA256) {
- TestValidPinsHeaders(HASH_VALUE_SHA256);
-}
-
TEST_F(TransportSecurityStateTest, SimpleMatches) {
TransportSecurityState state;
TransportSecurityState::DomainState domain_state;
@@ -920,8 +521,7 @@ TEST_F(TransportSecurityStateTest, BuiltinCertPins) {
static bool AddHash(const std::string& type_and_base64,
HashValueVector* out) {
HashValue hash;
-
- if (!TransportSecurityState::ParsePin(type_and_base64, &hash))
+ if (!hash.FromString(type_and_base64))
return false;
out->push_back(hash);
diff --git a/net/base/x509_cert_types.cc b/net/base/x509_cert_types.cc
index 5e3c3d1..643454f 100644
--- a/net/base/x509_cert_types.cc
+++ b/net/base/x509_cert_types.cc
@@ -8,7 +8,6 @@
#include <cstring>
#include "base/logging.h"
-#include "base/sha1.h"
#include "base/string_number_conversions.h"
#include "base/string_piece.h"
#include "base/time.h"
@@ -29,22 +28,6 @@ int ParseIntAndAdvance(const char** field, size_t field_len, bool* ok) {
return result;
}
-// CompareSHA1Hashes is a helper function for using bsearch() with an array of
-// SHA1 hashes.
-int CompareSHA1Hashes(const void* a, const void* b) {
- return memcmp(a, b, base::kSHA1Length);
-}
-
-} // namespace
-
-// static
-bool IsSHA1HashInSortedArray(const SHA1HashValue& hash,
- const uint8* array,
- size_t array_byte_len) {
- DCHECK_EQ(0u, array_byte_len % base::kSHA1Length);
- const size_t arraylen = array_byte_len / base::kSHA1Length;
- return NULL != bsearch(hash.data, array, arraylen, base::kSHA1Length,
- CompareSHA1Hashes);
}
CertPrincipal::CertPrincipal() {
@@ -143,52 +126,4 @@ bool ParseCertificateDate(const base::StringPiece& raw_date,
return true;
}
-bool HashValue::Equals(const HashValue& other) const {
- if (tag != other.tag)
- return false;
- switch (tag) {
- case HASH_VALUE_SHA1:
- return fingerprint.sha1.Equals(other.fingerprint.sha1);
- case HASH_VALUE_SHA256:
- return fingerprint.sha256.Equals(other.fingerprint.sha256);
- default:
- NOTREACHED() << "Unknown HashValueTag " << tag;
- return false;
- }
-}
-
-size_t HashValue::size() const {
- switch (tag) {
- case HASH_VALUE_SHA1:
- return sizeof(fingerprint.sha1.data);
- case HASH_VALUE_SHA256:
- return sizeof(fingerprint.sha256.data);
- default:
- NOTREACHED() << "Unknown HashValueTag " << tag;
- // Although this is NOTREACHED, this function might be inlined and its
- // return value can be passed to memset as the length argument. If we
- // returned 0 here, it might result in what appears (in some stages of
- // compilation) to be a call to to memset with a length argument of 0,
- // which results in a warning. Therefore, we return a dummy value
- // here.
- return sizeof(fingerprint.sha1.data);
- }
-}
-
-unsigned char* HashValue::data() {
- return const_cast<unsigned char*>(const_cast<const HashValue*>(this)->data());
-}
-
-const unsigned char* HashValue::data() const {
- switch (tag) {
- case HASH_VALUE_SHA1:
- return fingerprint.sha1.data;
- case HASH_VALUE_SHA256:
- return fingerprint.sha256.data;
- default:
- NOTREACHED() << "Unknown HashValueTag " << tag;
- return NULL;
- }
-}
-
} // namespace net
diff --git a/net/base/x509_cert_types.h b/net/base/x509_cert_types.h
index cebcbce..8ebc477 100644
--- a/net/base/x509_cert_types.h
+++ b/net/base/x509_cert_types.h
@@ -14,6 +14,7 @@
#include "base/logging.h"
#include "base/string_piece.h"
#include "build/build_config.h"
+#include "net/base/hash_value.h"
#include "net/base/net_export.h"
#if defined(OS_MACOSX) && !defined(OS_IOS)
@@ -28,88 +29,6 @@ namespace net {
class X509Certificate;
-// SHA-1 fingerprint (160 bits) of a certificate.
-struct NET_EXPORT SHA1HashValue {
- bool Equals(const SHA1HashValue& other) const {
- return memcmp(data, other.data, sizeof(data)) == 0;
- }
-
- unsigned char data[20];
-};
-
-class NET_EXPORT SHA1HashValueLessThan {
- public:
- bool operator()(const SHA1HashValue& lhs,
- const SHA1HashValue& rhs) const {
- return memcmp(lhs.data, rhs.data, sizeof(lhs.data)) < 0;
- }
-};
-
-struct NET_EXPORT SHA256HashValue {
- bool Equals(const SHA256HashValue& other) const {
- return memcmp(data, other.data, sizeof(data)) == 0;
- }
-
- unsigned char data[32];
-};
-
-class NET_EXPORT SHA256HashValueLessThan {
- public:
- bool operator()(const SHA256HashValue& lhs,
- const SHA256HashValue& rhs) const {
- return memcmp(lhs.data, rhs.data, sizeof(lhs.data)) < 0;
- }
-};
-
-enum HashValueTag {
- HASH_VALUE_SHA1,
- HASH_VALUE_SHA256,
-
- // This must always be last.
- HASH_VALUE_TAGS_COUNT
-};
-
-class NET_EXPORT HashValue {
- public:
- explicit HashValue(HashValueTag tag) : tag(tag) {}
- HashValue() : tag(HASH_VALUE_SHA1) {}
-
- bool Equals(const HashValue& other) const;
- size_t size() const;
- unsigned char* data();
- const unsigned char* data() const;
-
- HashValueTag tag;
-
- private:
- union {
- SHA1HashValue sha1;
- SHA256HashValue sha256;
- } fingerprint;
-};
-
-class NET_EXPORT HashValueLessThan {
- public:
- bool operator()(const HashValue& lhs,
- const HashValue& rhs) const {
- size_t lhs_size = lhs.size();
- size_t rhs_size = rhs.size();
-
- if (lhs_size != rhs_size)
- return lhs_size < rhs_size;
-
- return memcmp(lhs.data(), rhs.data(), lhs_size) < 0;
- }
-};
-
-typedef std::vector<HashValue> HashValueVector;
-
-// IsSHA1HashInSortedArray returns true iff |hash| is in |array|, a sorted
-// array of SHA1 hashes.
-bool NET_EXPORT IsSHA1HashInSortedArray(const SHA1HashValue& hash,
- const uint8* array,
- size_t array_byte_len);
-
// CertPrincipal represents the issuer or subject field of an X.509 certificate.
struct NET_EXPORT CertPrincipal {
CertPrincipal();
diff --git a/net/http/http_security_headers.cc b/net/http/http_security_headers.cc
new file mode 100644
index 0000000..8018927
--- /dev/null
+++ b/net/http/http_security_headers.cc
@@ -0,0 +1,330 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/string_number_conversions.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "net/http/http_security_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+namespace {
+
+COMPILE_ASSERT(kMaxHSTSAgeSecs <= kuint32max, kMaxHSTSAgeSecsTooLarge);
+
+// MaxAgeToInt converts a string representation of a "whole number" of
+// seconds into a uint32. The string may contain an arbitrarily large number,
+// which will be clipped to kMaxHSTSAgeSecs and which is guaranteed to fit
+// within a 32-bit unsigned integer. False is returned on any parse error.
+bool MaxAgeToInt(std::string::const_iterator begin,
+ std::string::const_iterator end,
+ uint32* result) {
+ const std::string s(begin, end);
+ int64 i = 0;
+
+ // Return false on any StringToInt64 parse errors *except* for
+ // int64 overflow. StringToInt64 is used, rather than StringToUint64,
+ // in order to properly handle and reject negative numbers
+ // (StringToUint64 does not return false on negative numbers).
+ // For values too large to be stored in an int64, StringToInt64 will
+ // return false with i set to kint64max, so this case is detected
+ // by the immediately following if-statement and allowed to fall
+ // through so that i gets clipped to kMaxHSTSAgeSecs.
+ if (!base::StringToInt64(s, &i) && i != kint64max)
+ return false;
+ if (i < 0)
+ return false;
+ if (i > kMaxHSTSAgeSecs)
+ i = kMaxHSTSAgeSecs;
+ *result = (uint32)i;
+ return true;
+}
+
+// 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".
+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(),
+ HashValuesEqual(*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.
+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;
+}
+
+// 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.
+bool IsPinListValid(const HashValueVector& pins,
+ const HashValueVector& from_cert_chain) {
+ // Fast fail: 1 live + 1 backup = at least 2 pins. (Check for actual
+ // liveness and backupness below.)
+ if (pins.size() < 2)
+ return false;
+
+ if (from_cert_chain.empty())
+ return false;
+
+ return IsBackupPinPresent(pins, from_cert_chain) &&
+ 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,
+ HashValueTag tag,
+ HashValueVector* hashes) {
+ std::string unquoted = HttpUtil::Unquote(value);
+ std::string decoded;
+
+ if (unquoted.empty())
+ return false;
+
+ 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;
+}
+
+} // namespace
+
+// 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 ParseHSTSHeader(const base::Time& now, const std::string& value,
+ base::Time* expiry, // OUT
+ bool* include_subdomains) { // OUT
+ uint32 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:
+ *expiry = now + base::TimeDelta::FromSeconds(max_age_candidate);
+ *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;
+ }
+}
+
+// "Public-Key-Pins" ":"
+// "max-age" "=" delta-seconds ";"
+// "pin-" algo "=" base64 [ ";" ... ]
+bool ParseHPKPHeader(const base::Time& now,
+ const std::string& value,
+ const HashValueVector& chain_hashes,
+ base::Time* expiry,
+ HashValueVector* hashes) {
+ bool parsed_max_age = false;
+ uint32 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;
+ }
+ parsed_max_age = true;
+ } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1")) {
+ if (!ParseAndAppendPin(equals.second, HASH_VALUE_SHA1, &pins))
+ return false;
+ } else if (LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
+ if (!ParseAndAppendPin(equals.second, HASH_VALUE_SHA256, &pins))
+ return false;
+ } else {
+ // Silently ignore unknown directives for forward compatibility.
+ }
+
+ source = semicolon.second;
+ }
+
+ if (!parsed_max_age)
+ return false;
+
+ if (!IsPinListValid(pins, chain_hashes))
+ return false;
+
+ *expiry = now + base::TimeDelta::FromSeconds(max_age_candidate);
+ for (HashValueVector::const_iterator i = pins.begin();
+ i != pins.end(); ++i) {
+ hashes->push_back(*i);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/net/http/http_security_headers.h b/net/http/http_security_headers.h
new file mode 100644
index 0000000..bc465e9
--- /dev/null
+++ b/net/http/http_security_headers.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_HTTP_HTTP_SECURITY_HEADERS_H_
+#define NET_HTTP_HTTP_SECURITY_HEADERS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/time.h"
+#include "base/values.h"
+#include "net/base/hash_value.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+const int64 kMaxHSTSAgeSecs = 86400 * 365; // 1 year
+
+// Parses |value| as a Strict-Transport-Security header value. If successful,
+// returns true and sets |*expiry| and |*include_subdomains|.
+// Otherwise returns false and leaves the output parameters unchanged.
+// Interprets the max-age directive relative to |now|.
+//
+// value is the right-hand side of:
+//
+// "Strict-Transport-Security" ":"
+// [ directive ] *( ";" [ directive ] )
+bool NET_EXPORT_PRIVATE ParseHSTSHeader(const base::Time& now,
+ const std::string& value,
+ base::Time* expiry,
+ bool* include_subdomains);
+
+// Parses |value| as a Public-Key-Pins header value. If successful,
+// returns true and populates the expiry and hashes values.
+// Otherwise returns false and leaves the output parameters unchanged.
+// Interprets the max-age directive relative to |now|.
+//
+// value is the right-hand side of:
+//
+// "Public-Key-Pins" ":"
+// "max-age" "=" delta-seconds ";"
+// "pin-" algo "=" base64 [ ";" ... ]
+//
+// 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 hash which matches the SSL certificate chain of the current site
+// (as specified by the chain_hashes) parameter. In addition, there MUST
+// be at least one key hash which does NOT match the site's SSL certificate
+// chain (this is the "backup pin").
+bool NET_EXPORT_PRIVATE ParseHPKPHeader(const base::Time& now,
+ const std::string& value,
+ const HashValueVector& chain_hashes,
+ base::Time* expiry,
+ HashValueVector* hashes);
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_SECURITY_HEADERS_H_
diff --git a/net/http/http_security_headers_unittest.cc b/net/http/http_security_headers_unittest.cc
new file mode 100644
index 0000000..a142486
--- /dev/null
+++ b/net/http/http_security_headers_unittest.cc
@@ -0,0 +1,424 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base64.h"
+#include "base/sha1.h"
+#include "base/string_piece.h"
+#include "crypto/sha2.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_security_headers.h"
+#include "net/http/http_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+HashValue GetTestHashValue(uint8 label, HashValueTag tag) {
+ HashValue hash_value(tag);
+ memset(hash_value.data(), label, hash_value.size());
+ return hash_value;
+}
+
+std::string GetTestPin(uint8 label, HashValueTag tag) {
+ HashValue hash_value = GetTestHashValue(label, tag);
+ std::string base64;
+ base::Base64Encode(base::StringPiece(
+ reinterpret_cast<char*>(hash_value.data()), hash_value.size()), &base64);
+
+ switch (hash_value.tag) {
+ case HASH_VALUE_SHA1:
+ return std::string("pin-sha1=\"") + base64 + "\"";
+ case HASH_VALUE_SHA256:
+ return std::string("pin-sha256=\"") + base64 + "\"";
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << hash_value.tag;
+ return std::string("ERROR");
+ }
+}
+
+};
+
+
+class HttpSecurityHeadersTest : public testing::Test {
+};
+
+
+TEST_F(HttpSecurityHeadersTest, BogusHeaders) {
+ base::Time now = base::Time::Now();
+ base::Time expiry = now;
+ bool include_subdomains = false;
+
+ EXPECT_FALSE(ParseHSTSHeader(now, "", &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " ", &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "abc", &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " abc", &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " abc ", &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age", &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " max-age", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " max-age ", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=", &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " max-age=", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " max-age =", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " max-age= ", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " max-age = ", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " max-age = xy", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, " max-age = 3488a923", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=3488a923 ", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-ag=3488923", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-aged=3488923", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age==3488923", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "amax-age=3488923", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=-3488923", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=3488923;", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=3488923 e", &expiry,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now,
+ "max-age=3488923 includesubdomain",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=3488923includesubdomains",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=3488923=includesubdomains",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=3488923 includesubdomainx",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=3488923 includesubdomain=",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now,
+ "max-age=3488923 includesubdomain=true",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=3488923 includesubdomainsx",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now,
+ "max-age=3488923 includesubdomains x",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=34889.23 includesubdomains",
+ &expiry, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(now, "max-age=34889 includesubdomains",
+ &expiry, &include_subdomains));
+
+ // Check the out args were not updated by checking the default
+ // values for its predictable fields.
+ EXPECT_EQ(now, expiry);
+ EXPECT_FALSE(include_subdomains);
+}
+
+static void TestBogusPinsHeaders(HashValueTag tag) {
+ base::Time now = base::Time::Now();
+ base::Time expiry = now;
+ HashValueVector hashes;
+ HashValueVector chain_hashes;
+
+ // Set some fake "chain" hashes
+ chain_hashes.push_back(GetTestHashValue(1, tag));
+ chain_hashes.push_back(GetTestHashValue(2, tag));
+ chain_hashes.push_back(GetTestHashValue(3, tag));
+
+ // The good pin must be in the chain, the backup pin must not be
+ std::string good_pin = GetTestPin(2, tag);
+ std::string backup_pin = GetTestPin(4, tag);
+
+ EXPECT_FALSE(ParseHPKPHeader(now, "", chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " ", chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "abc", chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " abc", chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " abc ", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-age", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " max-age", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " max-age ", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-age=", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " max-age=", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " max-age =", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " max-age= ", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " max-age = ", chain_hashes,
+ &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, " max-age = xy", chain_hashes,
+ &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now,
+ " max-age = 3488a923",
+ chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-age=3488a923 ", chain_hashes,
+ &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now,
+ "max-ag=3488923pins=" + good_pin + "," +
+ backup_pin,
+ chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-aged=3488923" + backup_pin,
+ chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-aged=3488923; " + backup_pin,
+ chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now,
+ "max-aged=3488923; " + backup_pin + ";" +
+ backup_pin,
+ chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now,
+ "max-aged=3488923; " + good_pin + ";" +
+ good_pin,
+ chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-aged=3488923; " + good_pin,
+ chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-age==3488923", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "amax-age=3488923", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-age=-3488923", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-age=3488923;", chain_hashes, &expiry,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-age=3488923 e", chain_hashes,
+ &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now,
+ "max-age=3488923 includesubdomain",
+ chain_hashes, &expiry, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(now, "max-age=34889.23", chain_hashes, &expiry,
+ &hashes));
+
+ // Check the out args were not updated by checking the default
+ // values for its predictable fields.
+ EXPECT_EQ(now, expiry);
+ EXPECT_EQ(hashes.size(), (size_t)0);
+}
+
+TEST_F(HttpSecurityHeadersTest, ValidSTSHeaders) {
+ base::Time now = base::Time::Now();
+ base::Time expiry = now;
+ base::Time expect_expiry = now;
+ bool include_subdomains = false;
+
+ EXPECT_TRUE(ParseHSTSHeader(now, "max-age=243", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(243);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now, " Max-agE = 567", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(567);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now, " mAx-aGe = 890 ", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(890);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now, "max-age=123;incLudesUbdOmains", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now, "incLudesUbdOmains; max-age=123", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now, " incLudesUbdOmains; max-age=123",
+ &expiry, &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now,
+ " incLudesUbdOmains; max-age=123; pumpkin=kitten", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now,
+ " pumpkin=894; incLudesUbdOmains; max-age=123 ", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now,
+ " pumpkin; incLudesUbdOmains; max-age=123 ", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now,
+ " pumpkin; incLudesUbdOmains; max-age=\"123\" ", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now,
+ "animal=\"squirrel; distinguished\"; incLudesUbdOmains; max-age=123",
+ &expiry, &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(now, "max-age=394082; incLudesUbdOmains",
+ &expiry, &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(394082);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ now, "max-age=39408299 ;incLudesUbdOmains", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(
+ std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(39408299))));
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ now, "max-age=394082038 ; incLudesUbdOmains", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(
+ std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(394082038))));
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ now, " max-age=0 ; incLudesUbdOmains ", &expiry,
+ &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(0);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ now,
+ " max-age=999999999999999999999999999999999999999999999 ;"
+ " incLudesUbdOmains ", &expiry, &include_subdomains));
+ expect_expiry = now + base::TimeDelta::FromSeconds(
+ kMaxHSTSAgeSecs);
+ EXPECT_EQ(expect_expiry, expiry);
+ EXPECT_TRUE(include_subdomains);
+}
+
+static void TestValidPinsHeaders(HashValueTag tag) {
+ base::Time now = base::Time::Now();
+ base::Time expiry = now;
+ base::Time expect_expiry = now;
+ HashValueVector hashes;
+ HashValueVector chain_hashes;
+
+ // Set some fake "chain" hashes into chain_hashes
+ chain_hashes.push_back(GetTestHashValue(1, tag));
+ chain_hashes.push_back(GetTestHashValue(2, tag));
+ chain_hashes.push_back(GetTestHashValue(3, tag));
+
+ // The good pin must be in the chain, the backup pin must not be
+ std::string good_pin = GetTestPin(2, tag);
+ std::string backup_pin = GetTestPin(4, tag);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ "max-age=243; " + good_pin + ";" + backup_pin,
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now + base::TimeDelta::FromSeconds(243);
+ EXPECT_EQ(expect_expiry, expiry);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ " " + good_pin + "; " + backup_pin + " ; Max-agE = 567",
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now + base::TimeDelta::FromSeconds(567);
+ EXPECT_EQ(expect_expiry, expiry);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ good_pin + ";" + backup_pin + " ; mAx-aGe = 890 ",
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now + base::TimeDelta::FromSeconds(890);
+ EXPECT_EQ(expect_expiry, expiry);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ good_pin + ";" + backup_pin + "; max-age=123;IGNORED;",
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now + base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_expiry, expiry);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ "max-age=394082;" + backup_pin + ";" + good_pin + "; ",
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now + base::TimeDelta::FromSeconds(394082);
+ EXPECT_EQ(expect_expiry, expiry);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ "max-age=39408299 ;" + backup_pin + ";" + good_pin + "; ",
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now + base::TimeDelta::FromSeconds(
+ std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(39408299))));
+ EXPECT_EQ(expect_expiry, expiry);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ "max-age=39408038 ; cybers=39408038 ; " +
+ good_pin + ";" + backup_pin + "; ",
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now + base::TimeDelta::FromSeconds(
+ std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(394082038))));
+ EXPECT_EQ(expect_expiry, expiry);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ " max-age=0 ; " + good_pin + ";" + backup_pin,
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now + base::TimeDelta::FromSeconds(0);
+ EXPECT_EQ(expect_expiry, expiry);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ now,
+ " max-age=999999999999999999999999999999999999999999999 ; " +
+ backup_pin + ";" + good_pin + "; ",
+ chain_hashes, &expiry, &hashes));
+ expect_expiry = now +
+ base::TimeDelta::FromSeconds(kMaxHSTSAgeSecs);
+ EXPECT_EQ(expect_expiry, expiry);
+}
+
+TEST_F(HttpSecurityHeadersTest, BogusPinsHeadersSHA1) {
+ TestBogusPinsHeaders(HASH_VALUE_SHA1);
+}
+
+TEST_F(HttpSecurityHeadersTest, BogusPinsHeadersSHA256) {
+ TestBogusPinsHeaders(HASH_VALUE_SHA256);
+}
+
+TEST_F(HttpSecurityHeadersTest, ValidPinsHeadersSHA1) {
+ TestValidPinsHeaders(HASH_VALUE_SHA1);
+}
+
+TEST_F(HttpSecurityHeadersTest, ValidPinsHeadersSHA256) {
+ TestValidPinsHeaders(HASH_VALUE_SHA256);
+}
+};
+
diff --git a/net/net.gyp b/net/net.gyp
index d2d9a7f..5c1e43c 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -154,6 +154,8 @@
'base/gzip_filter.h',
'base/gzip_header.cc',
'base/gzip_header.h',
+ 'base/hash_value.cc',
+ 'base/hash_value.h',
'base/host_cache.cc',
'base/host_cache.h',
'base/host_mapping_rules.cc',
@@ -543,6 +545,8 @@
'http/http_response_headers.h',
'http/http_response_info.cc',
'http/http_response_info.h',
+ 'http/http_security_headers.cc',
+ 'http/http_security_headers.h',
'http/http_server_properties.cc',
'http/http_server_properties.h',
'http/http_server_properties_impl.cc',
@@ -1421,6 +1425,7 @@
'http/http_request_headers_unittest.cc',
'http/http_response_body_drainer_unittest.cc',
'http/http_response_headers_unittest.cc',
+ 'http/http_security_headers_unittest.cc',
'http/http_server_properties_impl_unittest.cc',
'http/http_stream_factory_impl_unittest.cc',
'http/http_stream_parser_unittest.cc',
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 982e76f..fe5f325 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -685,93 +685,50 @@ void URLRequestHttpJob::FetchResponseCookies(
// NOTE: |ProcessStrictTransportSecurityHeader| and
// |ProcessPublicKeyPinsHeader| have very similar structures, by design.
-// They manipulate different parts of |TransportSecurityState::DomainState|,
-// and they must remain complementary. If, in future changes here, there is
-// any conflict between their policies (such as in |domain_state.mode|), you
-// should resolve the conflict in favor of the more strict policy.
void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() {
DCHECK(response_info_);
-
- const URLRequestContext* ctx = request_->context();
+ TransportSecurityState* security_state =
+ request_->context()->transport_security_state();
const SSLInfo& ssl_info = response_info_->ssl_info;
- // Only accept strict transport security headers on HTTPS connections that
- // have no certificate errors.
+ // Only accept HSTS headers on HTTPS connections that have no
+ // certificate errors.
if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status) ||
- !ctx->transport_security_state()) {
+ !security_state)
return;
- }
-
- TransportSecurityState* security_state = ctx->transport_security_state();
- TransportSecurityState::DomainState domain_state;
- const std::string& host = request_info_.url.host();
-
- bool sni_available =
- SSLConfigService::IsSNIAvailable(ctx->ssl_config_service());
- if (!security_state->GetDomainState(host, sni_available, &domain_state))
- // |GetDomainState| may have altered |domain_state| while searching. If
- // not found, start with a fresh state.
- domain_state.upgrade_mode =
- TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
-
- HttpResponseHeaders* headers = GetResponseHeaders();
- std::string value;
- void* iter = NULL;
- base::Time now = base::Time::Now();
// http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec:
//
// If a UA receives more than one STS header field in a HTTP response
// message over secure transport, then the UA MUST process only the
// first such header field.
- bool seen_sts = false;
- while (headers->EnumerateHeader(&iter, "Strict-Transport-Security", &value)) {
- if (seen_sts)
- return;
- seen_sts = true;
- TransportSecurityState::DomainState domain_state;
- if (domain_state.ParseSTSHeader(now, value))
- security_state->EnableHost(host, domain_state);
- }
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ std::string value;
+ if (headers->EnumerateHeader(NULL, "Strict-Transport-Security", &value))
+ security_state->AddHSTSHeader(request_info_.url.host(), value);
}
void URLRequestHttpJob::ProcessPublicKeyPinsHeader() {
DCHECK(response_info_);
-
- const URLRequestContext* ctx = request_->context();
+ TransportSecurityState* security_state =
+ request_->context()->transport_security_state();
const SSLInfo& ssl_info = response_info_->ssl_info;
- // Only accept public key pins headers on HTTPS connections that have no
+ // Only accept HPKP headers on HTTPS connections that have no
// certificate errors.
if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status) ||
- !ctx->transport_security_state()) {
+ !security_state)
return;
- }
-
- TransportSecurityState* security_state = ctx->transport_security_state();
- TransportSecurityState::DomainState domain_state;
- const std::string& host = request_info_.url.host();
-
- bool sni_available =
- SSLConfigService::IsSNIAvailable(ctx->ssl_config_service());
- if (!security_state->GetDomainState(host, sni_available, &domain_state))
- // |GetDomainState| may have altered |domain_state| while searching. If
- // not found, start with a fresh state.
- domain_state.upgrade_mode =
- TransportSecurityState::DomainState::MODE_DEFAULT;
+ // http://tools.ietf.org/html/draft-ietf-websec-key-pinning:
+ //
+ // 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();
- void* iter = NULL;
std::string value;
- base::Time now = base::Time::Now();
-
- while (headers->EnumerateHeader(&iter, "Public-Key-Pins", &value)) {
- // Note that ParsePinsHeader updates |domain_state| (iff the header parses
- // correctly), but does not completely overwrite it. It just updates the
- // dynamic pinning metadata.
- if (domain_state.ParsePinsHeader(now, value, ssl_info))
- security_state->EnableHost(host, domain_state);
- }
+ if (headers->EnumerateHeader(NULL, "Public-Key-Pins", &value))
+ security_state->AddHPKPHeader(request_info_.url.host(), value, ssl_info);
}
void URLRequestHttpJob::OnStartCompleted(int result) {