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

Unified Diff: net/base/transport_security_state_static.h

Issue 9863001: net: move HSTS preloaded and pinning info out of code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: ... Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/base/transport_security_state.cc ('k') | net/base/transport_security_state_static.certs » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/base/transport_security_state_static.h
diff --git a/net/base/transport_security_state_static.h b/net/base/transport_security_state_static.h
index ce2c7138b28afffae609d8733482089a4d270642..21ac72254345d678e75b06dc9e1df39aa2a46412 100644
--- a/net/base/transport_security_state_static.h
+++ b/net/base/transport_security_state_static.h
@@ -1,14 +1,15 @@
-// Copyright (c) 2011 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.
+// This file is automatically generated by transport_security_state_static_generate.go
-#ifndef NET_BASE_PUBLIC_KEY_HASHES_
-#define NET_BASE_PUBLIC_KEY_HASHES_
+#ifndef NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_
+#define NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_
#pragma once
-// This file contains SubjectPublicKeyInfo hashes for public key pinning. The
+// These are SubjectPublicKeyInfo hashes for public key pinning. The
// hashes are base64 encoded, SHA1 digests.
+static const char kSPKIHash_TestSPKI[] =
+ "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+
#if 0
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
@@ -79,7 +80,6 @@ UUIuOss4jHg7y/j7lYe8vJD5UDI=
static const char kSPKIHash_Google1024[] =
"sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0=";
-// Not yet used publicly.
static const char kSPKIHash_Google2048[] =
"sha1/AbkhxY0L343gKf+cki7NVWp+ozk=";
@@ -323,11 +323,12 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
static const char kSPKIHash_DigiCertEVRoot[] =
"sha1/gzF+YoVCU9bXeDGQ7JGQVumRueM=";
-// Not public
static const char kSPKIHash_Tor1[] =
"sha1/juNxSTv9UANmpC9kF5GKpmWNx3Y=";
+
static const char kSPKIHash_Tor2[] =
"sha1/lia43lPolzSPVIq34Dw57uYcLD8=";
+
static const char kSPKIHash_Tor3[] =
"sha1/rzEyQIKOh77j87n5bjWUNguXF8Y=";
@@ -578,7 +579,6 @@ lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
static const char kSPKIHash_VeriSignUniversal[] =
"sha1/u8I+KQuzKHcdrT6iTb30I70GsD0=";
-// Not public
static const char kSPKIHash_Twitter1[] =
"sha1/Vv7zwhR9TtOIN/29MFI4cgHld40=";
@@ -733,25 +733,29 @@ static const char kSPKIHash_GeoTrustPrimary_G3[] =
#if 0
-----BEGIN CERTIFICATE-----
-MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
-ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
-bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
-BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
-NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
-d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
-MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
-ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
-MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
-Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
-hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
-nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
-VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E
-BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ
-KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy
-T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
-zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT
-J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e
-nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=
+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
+MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
+LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
+YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
+A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
+K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
+sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
+MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
+XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
+HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
+4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
+j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
+U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
+u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
+bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
+fF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----
#endif
static const char kSPKIHash_Entrust_2048[] =
@@ -1235,1162 +1239,23 @@ lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
static const char kSPKIHash_GTECyberTrustGlobalRoot[] =
"sha1/WXkS3mF11m/EI7d3E3THlt5viHI=";
-#endif // NET_BASE_PUBLIC_KEY_HASHES_
-// 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/transport_security_state.h"
-
-#if defined(USE_OPENSSL)
-#include <openssl/ecdsa.h>
-#include <openssl/ssl.h>
-#else // !defined(USE_OPENSSL)
-#include <cryptohi.h>
-#include <hasht.h>
-#include <keyhi.h>
-#include <pk11pub.h>
-#include <nspr.h>
-#endif
-
-#include <algorithm>
-#include <utility>
-
-#include "base/base64.h"
-#include "base/json/json_reader.h"
-#include "base/json/json_writer.h"
-#include "base/logging.h"
-#include "base/memory/scoped_ptr.h"
-#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"
-#include "base/values.h"
-#include "crypto/sha2.h"
-#include "googleurl/src/gurl.h"
-#include "net/base/asn1_util.h"
-#include "net/base/dns_util.h"
-#include "net/base/public_key_hashes.h"
-#include "net/base/ssl_info.h"
-#include "net/base/x509_certificate.h"
-#include "net/http/http_util.h"
-
-#if defined(USE_OPENSSL)
-#include "crypto/openssl_util.h"
-#endif
-
-namespace net {
-
-const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year
-
-TransportSecurityState::TransportSecurityState(const std::string& hsts_hosts)
- : delegate_(NULL) {
- if (!hsts_hosts.empty()) {
- bool dirty;
- Deserialise(hsts_hosts, &dirty, &forced_hosts_);
- }
-}
-
-static 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));
-}
-
-void TransportSecurityState::SetDelegate(
- TransportSecurityState::Delegate* delegate) {
- delegate_ = delegate;
-}
-
-void TransportSecurityState::EnableHost(const std::string& host,
- const DomainState& state) {
- DCHECK(CalledOnValidThread());
-
- const std::string canonicalized_host = CanonicalizeHost(host);
- if (canonicalized_host.empty())
- return;
-
- // Only override a preloaded state if the new state describes a more strict
- // policy. TODO(palmer): Reconsider this?
- DomainState existing_state;
- if (IsPreloadedSTS(canonicalized_host, true, &existing_state) &&
- canonicalized_host == CanonicalizeHost(existing_state.domain) &&
- existing_state.IsMoreStrict(state)) {
- return;
- }
-
- // Use the original creation date if we already have this host.
- DomainState state_copy(state);
- if (GetDomainState(&existing_state, host, true) &&
- !existing_state.created.is_null()) {
- state_copy.created = existing_state.created;
- }
-
- // We don't store these values.
- state_copy.preloaded = false;
- state_copy.domain.clear();
-
- enabled_hosts_[HashHost(canonicalized_host)] = state_copy;
- DirtyNotify();
-}
-
-bool TransportSecurityState::DeleteHost(const std::string& host) {
- DCHECK(CalledOnValidThread());
-
- const std::string canonicalized_host = CanonicalizeHost(host);
- if (canonicalized_host.empty())
- return false;
-
- std::map<std::string, DomainState>::iterator i = enabled_hosts_.find(
- HashHost(canonicalized_host));
- if (i != enabled_hosts_.end()) {
- enabled_hosts_.erase(i);
- DirtyNotify();
- return true;
- }
- return false;
-}
-
-bool TransportSecurityState::HasPinsForHost(DomainState* result,
- const std::string& host,
- bool sni_available) {
- DCHECK(CalledOnValidThread());
-
- return HasMetadata(result, host, sni_available) &&
- (!result->dynamic_spki_hashes.empty() ||
- !result->preloaded_spki_hashes.empty());
-}
-
-bool TransportSecurityState::GetDomainState(DomainState* result,
- const std::string& host,
- bool sni_available) {
- DCHECK(CalledOnValidThread());
-
- return HasMetadata(result, host, sni_available);
-}
-
-bool TransportSecurityState::HasMetadata(DomainState* result,
- const std::string& host,
- bool sni_available) {
- DCHECK(CalledOnValidThread());
-
- DomainState state;
- const std::string canonicalized_host = CanonicalizeHost(host);
- if (canonicalized_host.empty())
- return false;
-
- bool has_preload = IsPreloadedSTS(canonicalized_host, sni_available, &state);
- std::string canonicalized_preload = CanonicalizeHost(state.domain);
-
- base::Time current_time(base::Time::Now());
-
- for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
- std::string host_sub_chunk(&canonicalized_host[i],
- canonicalized_host.size() - i);
- // Exact match of a preload always wins.
- if (has_preload && host_sub_chunk == canonicalized_preload) {
- *result = state;
- return true;
- }
-
- std::map<std::string, DomainState>::iterator j =
- enabled_hosts_.find(HashHost(host_sub_chunk));
- if (j == enabled_hosts_.end())
- continue;
-
- if (current_time > j->second.expiry &&
- current_time > j->second.dynamic_spki_hashes_expiry) {
- enabled_hosts_.erase(j);
- DirtyNotify();
- continue;
- }
-
- state = j->second;
- state.domain = DNSDomainToString(host_sub_chunk);
-
- // Succeed if we matched the domain exactly or if subdomain matches are
- // allowed.
- if (i == 0 || j->second.include_subdomains) {
- *result = state;
- return true;
- }
-
- return false;
- }
-
- return false;
-}
-
-void TransportSecurityState::DeleteSince(const base::Time& time) {
- DCHECK(CalledOnValidThread());
-
- bool dirtied = false;
-
- std::map<std::string, DomainState>::iterator i = enabled_hosts_.begin();
- while (i != enabled_hosts_.end()) {
- if (i->second.created >= time) {
- dirtied = true;
- enabled_hosts_.erase(i++);
- } else {
- i++;
- }
- }
-
- if (dirtied)
- 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;
-}
-
-// TODO(palmer): Support both sha256 and sha1. This will require additional
-// infrastructure code changes and can come in a later patch.
-//
-// static
-bool TransportSecurityState::ParsePin(const std::string& value,
- SHA1Fingerprint* out) {
- StringPair slash = Split(Strip(value), '/');
- if (slash.first != "sha1")
- return false;
-
- std::string decoded;
- if (!base::Base64Decode(slash.second, &decoded) ||
- decoded.size() != arraysize(out->data)) {
- return false;
- }
-
- memcpy(out->data, decoded.data(), arraysize(out->data));
- return true;
-}
-
-static bool ParseAndAppendPin(const std::string& value,
- FingerprintVector* fingerprints) {
- // The base64'd fingerprint MUST be a quoted-string. 20 bytes base64'd is 28
- // characters; 32 bytes base64'd is 44 characters. TODO(palmer): Support
- // SHA256.
- size_t size = value.size();
- if (size != 30 || value[0] != '"' || value[size - 1] != '"')
- return false;
-
- std::string unquoted = HttpUtil::Unquote(value);
- std::string decoded;
- SHA1Fingerprint fp;
-
- if (!base::Base64Decode(unquoted, &decoded) ||
- decoded.size() != arraysize(fp.data)) {
- return false;
- }
-
- memcpy(fp.data, decoded.data(), arraysize(fp.data));
- fingerprints->push_back(fp);
- return true;
-}
-
-// static
-bool TransportSecurityState::GetPublicKeyHash(
- const X509Certificate& cert, SHA1Fingerprint* spki_hash) {
- std::string der_bytes;
- if (!X509Certificate::GetDEREncoded(cert.os_cert_handle(), &der_bytes))
- return false;
-
- base::StringPiece spki;
- if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki))
- return false;
-
- base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(spki.data()),
- spki.size(), spki_hash->data);
-
- return true;
-}
-
-struct FingerprintsEqualPredicate {
- explicit FingerprintsEqualPredicate(const SHA1Fingerprint& fingerprint) :
- fingerprint_(fingerprint) {}
-
- bool operator()(const SHA1Fingerprint& other) const {
- return fingerprint_.Equals(other);
- }
-
- const SHA1Fingerprint& fingerprint_;
-};
-
-// Returns true iff there is an item in |pins| which is not present in
-// |from_cert_chain|. Such an SPKI hash is called a "backup pin".
-static bool IsBackupPinPresent(const FingerprintVector& pins,
- const FingerprintVector& from_cert_chain) {
- for (FingerprintVector::const_iterator
- i = pins.begin(); i != pins.end(); ++i) {
- FingerprintVector::const_iterator j =
- std::find_if(from_cert_chain.begin(), from_cert_chain.end(),
- FingerprintsEqualPredicate(*i));
- if (j == from_cert_chain.end())
- return true;
- }
-
- return false;
-}
-
-static bool HashesIntersect(const FingerprintVector& a,
- const FingerprintVector& b) {
- for (FingerprintVector::const_iterator
- i = a.begin(); i != a.end(); ++i) {
- FingerprintVector::const_iterator j =
- std::find_if(b.begin(), b.end(), FingerprintsEqualPredicate(*i));
- if (j != b.end())
- return true;
- }
-
- 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 FingerprintVector& pins,
- const SSLInfo& ssl_info) {
- if (pins.size() < 2)
- return false;
-
- const FingerprintVector& 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 [ ";" ... ]
-//
-// static
-bool TransportSecurityState::ParsePinsHeader(const std::string& value,
- const SSLInfo& ssl_info,
- DomainState* state) {
- bool parsed_max_age = false;
- int max_age = 0;
- FingerprintVector 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)) {
- return false;
- }
- if (max_age > kMaxHSTSAgeSecs)
- max_age = kMaxHSTSAgeSecs;
- parsed_max_age = true;
- } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1")) {
- if (!ParseAndAppendPin(equals.second, &pins))
- return false;
- } else if (LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
- // TODO(palmer)
- } else {
- // Silently ignore unknown directives for forward compatibility.
- }
-
- source = semicolon.second;
- }
-
- if (!parsed_max_age || !IsPinListValid(pins, ssl_info))
- return false;
-
- state->max_age = max_age;
- state->dynamic_spki_hashes_expiry =
- base::Time::Now() + base::TimeDelta::FromSeconds(max_age);
-
- state->dynamic_spki_hashes.clear();
- if (max_age > 0) {
- for (FingerprintVector::const_iterator i = pins.begin();
- i != pins.end(); i++) {
- state->dynamic_spki_hashes.push_back(*i);
- }
- }
-
- return true;
-}
-
-// "Strict-Transport-Security" ":"
-// "max-age" "=" delta-seconds [ ";" "includeSubDomains" ]
-//
-// static
-bool TransportSecurityState::ParseHeader(const std::string& value,
- int* max_age,
- bool* include_subdomains) {
- DCHECK(max_age);
- DCHECK(include_subdomains);
-
- int max_age_candidate = 0;
-
- enum ParserState {
- START,
- AFTER_MAX_AGE_LABEL,
- AFTER_MAX_AGE_EQUALS,
- AFTER_MAX_AGE,
- AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER,
- AFTER_INCLUDE_SUBDOMAINS,
- } state = START;
-
- StringTokenizer tokenizer(value, " \t=;");
- tokenizer.set_options(StringTokenizer::RETURN_DELIMS);
- while (tokenizer.GetNext()) {
- DCHECK(!tokenizer.token_is_delim() || tokenizer.token().length() == 1);
- switch (state) {
- case START:
- if (IsAsciiWhitespace(*tokenizer.token_begin()))
- continue;
- if (!LowerCaseEqualsASCII(tokenizer.token(), "max-age"))
- return false;
- state = AFTER_MAX_AGE_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;
- if (!MaxAgeToInt(tokenizer.token_begin(),
- tokenizer.token_end(),
- &max_age_candidate))
- return false;
- state = AFTER_MAX_AGE;
- break;
-
- case AFTER_MAX_AGE:
- if (IsAsciiWhitespace(*tokenizer.token_begin()))
- continue;
- if (*tokenizer.token_begin() != ';')
- return false;
- state = AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER;
- break;
-
- case AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER:
- if (IsAsciiWhitespace(*tokenizer.token_begin()))
- continue;
- if (!LowerCaseEqualsASCII(tokenizer.token(), "includesubdomains"))
- return false;
- state = AFTER_INCLUDE_SUBDOMAINS;
- break;
-
- case AFTER_INCLUDE_SUBDOMAINS:
- if (!IsAsciiWhitespace(*tokenizer.token_begin()))
- return false;
- break;
-
- default:
- NOTREACHED();
- }
- }
-
- // We've consumed all the input. Let's see what state we ended up in.
- switch (state) {
- case START:
- case AFTER_MAX_AGE_LABEL:
- case AFTER_MAX_AGE_EQUALS:
- return false;
- case AFTER_MAX_AGE:
- *max_age = max_age_candidate;
- *include_subdomains = false;
- return true;
- case AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER:
- return false;
- case AFTER_INCLUDE_SUBDOMAINS:
- *max_age = max_age_candidate;
- *include_subdomains = true;
- return true;
- default:
- NOTREACHED();
- return false;
- }
-}
-
-// Side pinning and superfluous certificates:
-//
-// In SSLClientSocketNSS::DoVerifyCertComplete we look for certificates with a
-// Subject of CN=meta. When we find one we'll currently try and parse side
-// pinned key from it.
-//
-// A side pin is a key which can be pinned to, but also can be kept offline and
-// still held by the site owner. The CN=meta certificate is just a backwards
-// compatiable method of carrying a lump of bytes to the client. (We could use
-// a TLS extension just as well, but it's a lot easier for admins to add extra
-// certificates to the chain.)
-
-// A TagMap represents the simple key-value structure that we use. Keys are
-// 32-bit ints. Values are byte strings.
-typedef std::map<uint32, base::StringPiece> TagMap;
-
-// ParseTags parses a list of key-value pairs from |in| to |out| and advances
-// |in| past the data. The key-value pair data is:
-// u16le num_tags
-// u32le tag[num_tags]
-// u16le lengths[num_tags]
-// ...data...
-static bool ParseTags(base::StringPiece* in, TagMap *out) {
- // Many part of Chrome already assume little-endian. This is just to help
- // anyone who should try to port it in the future.
-#if defined(__BYTE_ORDER)
- // Linux check
- COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian);
-#elif defined(__BIG_ENDIAN__)
- // Mac check
- #error assumes little endian
-#endif
-
- uint16 num_tags_16;
- if (in->size() < sizeof(num_tags_16))
- return false;
-
- memcpy(&num_tags_16, in->data(), sizeof(num_tags_16));
- in->remove_prefix(sizeof(num_tags_16));
- unsigned num_tags = num_tags_16;
-
- if (in->size() < 6 * num_tags)
- return false;
-
- const uint32* tags = reinterpret_cast<const uint32*>(in->data());
- const uint16* lens = reinterpret_cast<const uint16*>(
- in->data() + 4*num_tags);
- in->remove_prefix(6*num_tags);
-
- uint32 prev_tag = 0;
- for (unsigned i = 0; i < num_tags; i++) {
- size_t len = lens[i];
- uint32 tag = tags[i];
-
- if (in->size() < len)
- return false;
- // tags must be in ascending order.
- if (i > 0 && prev_tag >= tag)
- return false;
- (*out)[tag] = base::StringPiece(in->data(), len);
- in->remove_prefix(len);
- prev_tag = tag;
- }
-
- return true;
-}
-
-// GetTag extracts the data associated with |tag| in |tags|.
-static bool GetTag(uint32 tag, const TagMap& tags, base::StringPiece* out) {
- TagMap::const_iterator i = tags.find(tag);
- if (i == tags.end())
- return false;
-
- *out = i->second;
- return true;
-}
-
-// kP256SubjectPublicKeyInfoPrefix can be prepended onto a P256 elliptic curve
-// point in X9.62 format in order to make a valid SubjectPublicKeyInfo. The
-// ASN.1 interpretation of these bytes is:
-//
-// 0:d=0 hl=2 l= 89 cons: SEQUENCE
-// 2:d=1 hl=2 l= 19 cons: SEQUENCE
-// 4:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
-// 13:d=2 hl=2 l= 8 prim: OBJECT :prime256v1
-// 23:d=1 hl=2 l= 66 prim: BIT STRING
-static const uint8 kP256SubjectPublicKeyInfoPrefix[] = {
- 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
- 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
- 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
- 0x42, 0x00,
-};
-
-// VerifySignature returns true iff |sig| is a valid signature of
-// |hash| by |pubkey|. The actual implementation is crypto library
-// specific.
-static bool VerifySignature(const base::StringPiece& pubkey,
- const base::StringPiece& sig,
- const base::StringPiece& hash);
-
-#if defined(USE_OPENSSL)
-
-static EVP_PKEY* DecodeX962P256PublicKey(
- const base::StringPiece& pubkey_bytes) {
- // The public key is an X9.62 encoded P256 point.
- if (pubkey_bytes.size() != 1 + 2*32)
- return NULL;
-
- std::string pubkey_spki(
- reinterpret_cast<const char*>(kP256SubjectPublicKeyInfoPrefix),
- sizeof(kP256SubjectPublicKeyInfoPrefix));
- pubkey_spki += pubkey_bytes.as_string();
-
- EVP_PKEY* ret = NULL;
- const unsigned char* der_pubkey =
- reinterpret_cast<const unsigned char*>(pubkey_spki.data());
- d2i_PUBKEY(&ret, &der_pubkey, pubkey_spki.size());
- return ret;
-}
-
-static bool VerifySignature(const base::StringPiece& pubkey,
- const base::StringPiece& sig,
- const base::StringPiece& hash) {
- crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> secpubkey(
- DecodeX962P256PublicKey(pubkey));
- if (!secpubkey.get())
- return false;
-
-
- crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ec_key(
- EVP_PKEY_get1_EC_KEY(secpubkey.get()));
- if (!ec_key.get())
- return false;
-
- return ECDSA_verify(0, reinterpret_cast<const unsigned char*>(hash.data()),
- hash.size(),
- reinterpret_cast<const unsigned char*>(sig.data()),
- sig.size(), ec_key.get()) == 1;
-}
-
-#else
-
-// DecodeX962P256PublicKey parses an uncompressed, X9.62 format, P256 elliptic
-// curve point from |pubkey_bytes| and returns it as a SECKEYPublicKey.
-static SECKEYPublicKey* DecodeX962P256PublicKey(
- const base::StringPiece& pubkey_bytes) {
- // The public key is an X9.62 encoded P256 point.
- if (pubkey_bytes.size() != 1 + 2*32)
- return NULL;
-
- std::string pubkey_spki(
- reinterpret_cast<const char*>(kP256SubjectPublicKeyInfoPrefix),
- sizeof(kP256SubjectPublicKeyInfoPrefix));
- pubkey_spki += pubkey_bytes.as_string();
-
- SECItem der;
- memset(&der, 0, sizeof(der));
- der.data = reinterpret_cast<uint8*>(const_cast<char*>(pubkey_spki.data()));
- der.len = pubkey_spki.size();
-
- CERTSubjectPublicKeyInfo* spki = SECKEY_DecodeDERSubjectPublicKeyInfo(&der);
- if (!spki)
- return NULL;
- SECKEYPublicKey* public_key = SECKEY_ExtractPublicKey(spki);
- SECKEY_DestroySubjectPublicKeyInfo(spki);
-
- return public_key;
-}
-
-static bool VerifySignature(const base::StringPiece& pubkey,
- const base::StringPiece& sig,
- const base::StringPiece& hash) {
- SECKEYPublicKey* secpubkey = DecodeX962P256PublicKey(pubkey);
- if (!secpubkey)
- return false;
-
- SECItem sigitem;
- memset(&sigitem, 0, sizeof(sigitem));
- sigitem.data = reinterpret_cast<uint8*>(const_cast<char*>(sig.data()));
- sigitem.len = sig.size();
-
- // |decoded_sigitem| is newly allocated, as is the data that it points to.
- SECItem* decoded_sigitem = DSAU_DecodeDerSigToLen(
- &sigitem, SECKEY_SignatureLen(secpubkey));
-
- if (!decoded_sigitem) {
- SECKEY_DestroyPublicKey(secpubkey);
- return false;
- }
-
- SECItem hashitem;
- memset(&hashitem, 0, sizeof(hashitem));
- hashitem.data = reinterpret_cast<unsigned char*>(
- const_cast<char*>(hash.data()));
- hashitem.len = hash.size();
-
- SECStatus rv = PK11_Verify(secpubkey, decoded_sigitem, &hashitem, NULL);
- SECKEY_DestroyPublicKey(secpubkey);
- SECITEM_FreeItem(decoded_sigitem, PR_TRUE);
- return rv == SECSuccess;
-}
-
-#endif // !defined(USE_OPENSSL)
-
-// These are the tag values that we use. Tags are little-endian on the wire and
-// these values correspond to the ASCII of the name.
-static const uint32 kTagALGO = 0x4f474c41;
-static const uint32 kTagP256 = 0x36353250;
-static const uint32 kTagPUBK = 0x4b425550;
-static const uint32 kTagSIG = 0x474953;
-static const uint32 kTagSPIN = 0x4e495053;
-
-// static
-bool TransportSecurityState::ParseSidePin(
- const base::StringPiece& leaf_spki,
- const base::StringPiece& in_side_info,
- FingerprintVector* out_pub_key_hash) {
- base::StringPiece side_info(in_side_info);
-
- TagMap outer;
- if (!ParseTags(&side_info, &outer))
- return false;
- // trailing data is not allowed
- if (side_info.size())
- return false;
-
- base::StringPiece side_pin_bytes;
- if (!GetTag(kTagSPIN, outer, &side_pin_bytes))
- return false;
-
- bool have_parsed_a_key = false;
- uint8 leaf_spki_hash[crypto::kSHA256Length];
- bool have_leaf_spki_hash = false;
-
- while (side_pin_bytes.size() > 0) {
- TagMap side_pin;
- if (!ParseTags(&side_pin_bytes, &side_pin))
- return false;
-
- base::StringPiece algo, pubkey, sig;
- if (!GetTag(kTagALGO, side_pin, &algo) ||
- !GetTag(kTagPUBK, side_pin, &pubkey) ||
- !GetTag(kTagSIG, side_pin, &sig)) {
- return false;
- }
-
- if (algo.size() != sizeof(kTagP256) ||
- 0 != memcmp(algo.data(), &kTagP256, sizeof(kTagP256))) {
- // We don't support anything but P256 at the moment.
- continue;
- }
-
- if (!have_leaf_spki_hash) {
- crypto::SHA256HashString(
- leaf_spki.as_string(), leaf_spki_hash, sizeof(leaf_spki_hash));
- have_leaf_spki_hash = true;
- }
-
- if (VerifySignature(pubkey, sig, base::StringPiece(
- reinterpret_cast<const char*>(leaf_spki_hash),
- sizeof(leaf_spki_hash)))) {
- SHA1Fingerprint fpr;
- base::SHA1HashBytes(
- reinterpret_cast<const uint8*>(pubkey.data()),
- pubkey.size(),
- fpr.data);
- out_pub_key_hash->push_back(fpr);
- have_parsed_a_key = true;
- }
- }
-
- return have_parsed_a_key;
-}
-
-// This function converts the binary hashes, which we store in
-// |enabled_hosts_|, to a base64 string which we can include in a JSON file.
-static std::string HashedDomainToExternalString(const std::string& hashed) {
- std::string out;
- CHECK(base::Base64Encode(hashed, &out));
- return out;
-}
-
-// This inverts |HashedDomainToExternalString|, above. It turns an external
-// string (from a JSON file) into an internal (binary) string.
-static std::string ExternalStringToHashedDomain(const std::string& external) {
- std::string out;
- if (!base::Base64Decode(external, &out) ||
- out.size() != crypto::kSHA256Length) {
- return std::string();
- }
-
- return out;
-}
-
-static ListValue* SPKIHashesToListValue(const FingerprintVector& hashes) {
- ListValue* pins = new ListValue;
-
- for (FingerprintVector::const_iterator i = hashes.begin();
- i != hashes.end(); ++i) {
- std::string hash_str(reinterpret_cast<const char*>(i->data),
- sizeof(i->data));
- std::string b64;
- base::Base64Encode(hash_str, &b64);
- pins->Append(new StringValue("sha1/" + b64));
- }
-
- return pins;
-}
-
-bool TransportSecurityState::Serialise(std::string* output) {
- DCHECK(CalledOnValidThread());
-
- DictionaryValue toplevel;
- base::Time now = base::Time::Now();
- for (std::map<std::string, DomainState>::const_iterator
- i = enabled_hosts_.begin(); i != enabled_hosts_.end(); ++i) {
- DictionaryValue* state = new DictionaryValue;
- state->SetBoolean("include_subdomains", i->second.include_subdomains);
- state->SetDouble("created", i->second.created.ToDoubleT());
- state->SetDouble("expiry", i->second.expiry.ToDoubleT());
- state->SetDouble("dynamic_spki_hashes_expiry",
- i->second.dynamic_spki_hashes_expiry.ToDoubleT());
-
- switch (i->second.mode) {
- case DomainState::MODE_STRICT:
- state->SetString("mode", "strict");
- break;
- case DomainState::MODE_SPDY_ONLY:
- state->SetString("mode", "spdy-only");
- break;
- case DomainState::MODE_PINNING_ONLY:
- state->SetString("mode", "pinning-only");
- break;
- default:
- NOTREACHED() << "DomainState with unknown mode";
- delete state;
- continue;
- }
-
- state->Set("preloaded_spki_hashes",
- SPKIHashesToListValue(i->second.preloaded_spki_hashes));
-
- if (now < i->second.dynamic_spki_hashes_expiry) {
- state->Set("dynamic_spki_hashes",
- SPKIHashesToListValue(i->second.dynamic_spki_hashes));
- }
-
- toplevel.Set(HashedDomainToExternalString(i->first), state);
- }
-
- base::JSONWriter::WriteWithOptions(&toplevel,
- base::JSONWriter::OPTIONS_PRETTY_PRINT,
- output);
- return true;
-}
-
-bool TransportSecurityState::LoadEntries(const std::string& input,
- bool* dirty) {
- DCHECK(CalledOnValidThread());
-
- enabled_hosts_.clear();
- return Deserialise(input, dirty, &enabled_hosts_);
-}
-
-static bool AddHash(const std::string& type_and_base64,
- FingerprintVector* out) {
- SHA1Fingerprint hash;
-
- if (!TransportSecurityState::ParsePin(type_and_base64, &hash))
- return false;
-
- out->push_back(hash);
- return true;
-}
-
-static void SPKIHashesFromListValue(FingerprintVector* hashes,
- const ListValue& pins) {
- size_t num_pins = pins.GetSize();
- for (size_t i = 0; i < num_pins; ++i) {
- std::string type_and_base64;
- if (pins.GetString(i, &type_and_base64))
- AddHash(type_and_base64, hashes);
- }
-}
-
-// static
-bool TransportSecurityState::Deserialise(
- const std::string& input,
- bool* dirty,
- std::map<std::string, DomainState>* out) {
- scoped_ptr<Value> value(
- base::JSONReader::Read(input, false /* do not allow trailing commas */));
- if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY))
- return false;
-
- DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get());
- const base::Time current_time(base::Time::Now());
- bool dirtied = false;
-
- for (DictionaryValue::key_iterator i = dict_value->begin_keys();
- i != dict_value->end_keys(); ++i) {
- DictionaryValue* state;
- if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &state))
- continue;
-
- bool include_subdomains;
- std::string mode_string;
- double created;
- double expiry;
- double dynamic_spki_hashes_expiry = 0.0;
-
- if (!state->GetBoolean("include_subdomains", &include_subdomains) ||
- !state->GetString("mode", &mode_string) ||
- !state->GetDouble("expiry", &expiry)) {
- continue;
- }
-
- // Don't fail if this key is not present.
- (void) state->GetDouble("dynamic_spki_hashes_expiry",
- &dynamic_spki_hashes_expiry);
-
- ListValue* pins_list = NULL;
- FingerprintVector preloaded_spki_hashes;
- if (state->GetList("preloaded_spki_hashes", &pins_list))
- SPKIHashesFromListValue(&preloaded_spki_hashes, *pins_list);
-
- FingerprintVector dynamic_spki_hashes;
- if (state->GetList("dynamic_spki_hashes", &pins_list))
- SPKIHashesFromListValue(&dynamic_spki_hashes, *pins_list);
-
- DomainState::Mode mode;
- if (mode_string == "strict") {
- mode = DomainState::MODE_STRICT;
- } else if (mode_string == "spdy-only") {
- mode = DomainState::MODE_SPDY_ONLY;
- } else if (mode_string == "pinning-only") {
- mode = DomainState::MODE_PINNING_ONLY;
- } else {
- LOG(WARNING) << "Unknown TransportSecurityState mode string found: "
- << mode_string;
- continue;
- }
-
- base::Time expiry_time = base::Time::FromDoubleT(expiry);
- base::Time dynamic_spki_hashes_expiry_time =
- base::Time::FromDoubleT(dynamic_spki_hashes_expiry);
- base::Time created_time;
- if (state->GetDouble("created", &created)) {
- created_time = base::Time::FromDoubleT(created);
- } else {
- // We're migrating an old entry with no creation date. Make sure we
- // write the new date back in a reasonable time frame.
- dirtied = true;
- created_time = base::Time::Now();
- }
-
- if (expiry_time <= current_time &&
- dynamic_spki_hashes_expiry_time <= current_time) {
- // Make sure we dirty the state if we drop an entry.
- dirtied = true;
- continue;
- }
-
- std::string hashed = ExternalStringToHashedDomain(*i);
- if (hashed.empty()) {
- dirtied = true;
- continue;
- }
-
- DomainState new_state;
- new_state.mode = mode;
- new_state.created = created_time;
- new_state.expiry = expiry_time;
- new_state.include_subdomains = include_subdomains;
- new_state.preloaded_spki_hashes = preloaded_spki_hashes;
- new_state.dynamic_spki_hashes = dynamic_spki_hashes;
- new_state.dynamic_spki_hashes_expiry = dynamic_spki_hashes_expiry_time;
- (*out)[hashed] = new_state;
- }
-
- *dirty = dirtied;
- return true;
-}
-
-TransportSecurityState::~TransportSecurityState() {
-}
-
-void TransportSecurityState::DirtyNotify() {
- DCHECK(CalledOnValidThread());
-
- if (delegate_)
- delegate_->StateIsDirty(this);
-}
-
-// static
-std::string TransportSecurityState::CanonicalizeHost(const std::string& host) {
- // We cannot perform the operations as detailed in the spec here as |host|
- // has already undergone IDN processing before it reached us. Thus, we check
- // that there are no invalid characters in the host and lowercase the result.
-
- std::string new_host;
- if (!DNSDomainFromDot(host, &new_host)) {
- // DNSDomainFromDot can fail if any label is > 63 bytes or if the whole
- // name is >255 bytes. However, search terms can have those properties.
- return std::string();
- }
-
- for (size_t i = 0; new_host[i]; i += new_host[i] + 1) {
- const unsigned label_length = static_cast<unsigned>(new_host[i]);
- if (!label_length)
- break;
-
- for (size_t j = 0; j < label_length; ++j) {
- // RFC 3490, 4.1, step 3
- if (!IsSTD3ASCIIValidCharacter(new_host[i + 1 + j]))
- return std::string();
-
- new_host[i + 1 + j] = tolower(new_host[i + 1 + j]);
- }
-
- // step 3(b)
- if (new_host[i + 1] == '-' ||
- new_host[i + label_length] == '-') {
- return std::string();
- }
- }
-
- return new_host;
-}
-
-// |ReportUMAOnPinFailure| uses these to report which domain was associated
-// with the public key pinning failure.
-//
-// DO NOT CHANGE THE ORDERING OF THESE NAMES OR REMOVE ANY OF THEM. Add new
-// domains at the END of the listing (but before DOMAIN_NUM_EVENTS).
-enum SecondLevelDomainName {
- DOMAIN_NOT_PINNED,
-
- DOMAIN_GOOGLE_COM,
- DOMAIN_ANDROID_COM,
- DOMAIN_GOOGLE_ANALYTICS_COM,
- DOMAIN_GOOGLEPLEX_COM,
- DOMAIN_YTIMG_COM,
- DOMAIN_GOOGLEUSERCONTENT_COM,
- DOMAIN_YOUTUBE_COM,
- DOMAIN_GOOGLEAPIS_COM,
- DOMAIN_GOOGLEADSERVICES_COM,
- DOMAIN_GOOGLECODE_COM,
- DOMAIN_APPSPOT_COM,
- DOMAIN_GOOGLESYNDICATION_COM,
- DOMAIN_DOUBLECLICK_NET,
- DOMAIN_GSTATIC_COM,
- DOMAIN_GMAIL_COM,
- DOMAIN_GOOGLEMAIL_COM,
- DOMAIN_GOOGLEGROUPS_COM,
-
- DOMAIN_TORPROJECT_ORG,
-
- DOMAIN_TWITTER_COM,
- DOMAIN_TWIMG_COM,
-
- DOMAIN_AKAMAIHD_NET,
-
- // Boundary value for UMA_HISTOGRAM_ENUMERATION:
- DOMAIN_NUM_EVENTS
-};
-
-// PublicKeyPins contains a number of SubjectPublicKeyInfo hashes for a site.
-// The validated certificate chain for the site must not include any of
-// |excluded_hashes| and must include one or more of |required_hashes|.
-struct PublicKeyPins {
- const char* const* required_hashes;
- const char* const* excluded_hashes;
-};
-
-struct HSTSPreload {
- uint8 length;
- bool include_subdomains;
- char dns_name[30];
- bool https_required;
- PublicKeyPins pins;
- SecondLevelDomainName second_level_domain_name;
-};
-
-static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries,
- const std::string& canonicalized_host, size_t i,
- TransportSecurityState::DomainState* out, bool* ret) {
- for (size_t j = 0; j < num_entries; j++) {
- if (entries[j].length == canonicalized_host.size() - i &&
- memcmp(entries[j].dns_name, &canonicalized_host[i],
- entries[j].length) == 0) {
- if (!entries[j].include_subdomains && i != 0) {
- *ret = false;
- } else {
- out->include_subdomains = entries[j].include_subdomains;
- *ret = true;
- if (!entries[j].https_required)
- out->mode = TransportSecurityState::DomainState::MODE_PINNING_ONLY;
- if (entries[j].pins.required_hashes) {
- const char* const* hash = entries[j].pins.required_hashes;
- while (*hash) {
- bool ok = AddHash(*hash, &out->preloaded_spki_hashes);
- DCHECK(ok) << " failed to parse " << *hash;
- hash++;
- }
- }
- if (entries[j].pins.excluded_hashes) {
- const char* const* hash = entries[j].pins.excluded_hashes;
- while (*hash) {
- bool ok = AddHash(*hash, &out->bad_preloaded_spki_hashes);
- DCHECK(ok) << " failed to parse " << *hash;
- hash++;
- }
- }
- }
- return true;
- }
- }
- return false;
-}
+// The following is static data describing the hosts that are hardcoded with
+// certificate pins or HSTS information.
// kNoRejectedPublicKeys is a placeholder for when no public keys are rejected.
static const char* const kNoRejectedPublicKeys[] = {
NULL,
};
+static const char* const kTestAcceptableCerts[] = {
+ kSPKIHash_TestSPKI,
+ NULL,
+};
+#define kTestPins { \
+ kTestAcceptableCerts, \
+ kNoRejectedPublicKeys, \
+}
+
static const char* const kGoogleAcceptableCerts[] = {
kSPKIHash_VeriSignClass3,
kSPKIHash_VeriSignClass3_G3,
@@ -2451,8 +1316,6 @@ static const char* const kTwitterComAcceptableCerts[] = {
kNoRejectedPublicKeys, \
}
-// kTwitterCDNAcceptableCerts are the set of public keys valid for Twitter's
-// CDNs, which includes all the keys from kTwitterComAcceptableCerts.
static const char* const kTwitterCDNAcceptableCerts[] = {
kSPKIHash_VeriSignClass1,
kSPKIHash_VeriSignClass3,
@@ -2473,7 +1336,6 @@ static const char* const kTwitterCDNAcceptableCerts[] = {
kSPKIHash_GeoTrustPrimary_G2,
kSPKIHash_GeoTrustPrimary_G3,
kSPKIHash_Twitter1,
-
kSPKIHash_Entrust_2048,
kSPKIHash_Entrust_EV,
kSPKIHash_Entrust_G2,
@@ -2498,35 +1360,14 @@ static const char* const kTwitterCDNAcceptableCerts[] = {
kNoRejectedPublicKeys, \
}
-// kTestAcceptableCerts doesn't actually match any public keys and is used
-// with "pinningtest.appspot.com", below, to test if pinning is active.
-static const char* const kTestAcceptableCerts[] = {
- "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
- NULL,
-};
-#define kTestPins { \
- kTestAcceptableCerts, \
- kNoRejectedPublicKeys, \
-}
-
-#define kNoPins { \
+#define kNoPins {\
NULL, NULL, \
}
-#if defined(OS_CHROMEOS)
- static const bool kTwitterHSTS = true;
-#else
- static const bool kTwitterHSTS = false;
-#endif
-
-// In the medium term this list is likely to just be hardcoded here. This
-// slightly odd form removes the need for additional relocations records.
static const struct HSTSPreload kPreloadedSTS[] = {
- // (*.)google.com, iff using SSL must use an acceptable certificate.
- {12, true, "\006google\003com", false, kGooglePins, DOMAIN_GOOGLE_COM },
{25, true, "\013pinningtest\007appspot\003com", false, kTestPins, DOMAIN_APPSPOT_COM },
- // Now we force HTTPS for subtrees of google.com.
- {19, true, "\006health\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {12, true, "\006google\003com", false, kGooglePins, DOMAIN_GOOGLE_COM },
+ {19, true, "\006health\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
{21, true, "\010checkout\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
{19, true, "\006chrome\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
{17, true, "\004docs\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
@@ -2541,20 +1382,13 @@ static const struct HSTSPreload kPreloadedSTS[] = {
{17, true, "\004talk\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
{29, true, "\020hostedtalkgadget\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
{17, true, "\004plus\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
- // Other Google-related domains that must use HTTPS.
{20, true, "\006market\007android\003com", true, kGooglePins, DOMAIN_ANDROID_COM },
{26, true, "\003ssl\020google-analytics\003com", true, kGooglePins, DOMAIN_GOOGLE_ANALYTICS_COM },
{18, true, "\005drive\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
{16, true, "\012googleplex\003com", true, kGooglePins, DOMAIN_GOOGLEPLEX_COM },
{19, true, "\006groups\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
{17, true, "\004apis\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
- // chart.apis.google.com is *not* HSTS because the certificate doesn't match
- // and there are lots of links out there that still use the name. The correct
- // hostname for this is chart.googleapis.com.
- {23, true, "\005chart\004apis\006google\003com", false, kGooglePins, DOMAIN_GOOGLE_COM},
-
- // Other Google-related domains that must use an acceptable certificate
- // iff using SSL.
+ {23, true, "\005chart\004apis\006google\003com", false, kGooglePins, DOMAIN_GOOGLE_COM },
{11, true, "\005ytimg\003com", false, kGooglePins, DOMAIN_YTIMG_COM },
{23, true, "\021googleusercontent\003com", false, kGooglePins, DOMAIN_GOOGLEUSERCONTENT_COM },
{13, true, "\007youtube\003com", false, kGooglePins, DOMAIN_YOUTUBE_COM },
@@ -2565,14 +1399,11 @@ static const struct HSTSPreload kPreloadedSTS[] = {
{23, true, "\021googlesyndication\003com", false, kGooglePins, DOMAIN_GOOGLESYNDICATION_COM },
{17, true, "\013doubleclick\003net", false, kGooglePins, DOMAIN_DOUBLECLICK_NET },
{17, true, "\003ssl\007gstatic\003com", false, kGooglePins, DOMAIN_GSTATIC_COM },
- // Exclude the learn.doubleclick.net subdomain because it uses a different
- // CA.
{23, true, "\005learn\013doubleclick\003net", false, kNoPins, DOMAIN_NOT_PINNED },
- // Now we force HTTPS for other sites that have requested it.
{16, false, "\003www\006paypal\003com", true, kNoPins, DOMAIN_NOT_PINNED },
{16, false, "\003www\006elanex\003biz", true, kNoPins, DOMAIN_NOT_PINNED },
- {12, true, "\006jottit\003com", true, kNoPins, DOMAIN_NOT_PINNED },
- {19, true, "\015sunshinepress\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\006jottit\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {19, true, "\015sunshinepress\003org", true, kNoPins, DOMAIN_NOT_PINNED },
{21, false, "\003www\013noisebridge\003net", true, kNoPins, DOMAIN_NOT_PINNED },
{10, false, "\004neg9\003org", true, kNoPins, DOMAIN_NOT_PINNED },
{12, true, "\006riseup\003net", true, kNoPins, DOMAIN_NOT_PINNED },
@@ -2625,23 +1456,22 @@ static const struct HSTSPreload kPreloadedSTS[] = {
{12, true, "\006ubertt\003org", true, kNoPins, DOMAIN_NOT_PINNED },
{9, true, "\004pixi\002me", true, kNoPins, DOMAIN_NOT_PINNED },
{14, true, "\010grepular\003com", true, kNoPins, DOMAIN_NOT_PINNED },
- {16, false , "\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
- {20, false , "\003www\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
- {26, false , "\011developer\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
- {30, false , "\003www\011developer\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
- {24, false , "\007sandbox\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
- {28, false , "\003www\007sandbox\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, false, "\003www\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {26, false, "\011developer\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {30, false, "\003www\011developer\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {24, false, "\007sandbox\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {28, false, "\003www\007sandbox\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
{12, true, "\006crypto\003cat", true, kNoPins, DOMAIN_NOT_PINNED },
{25, true, "\014bigshinylock\006minazo\003net", true, kNoPins, DOMAIN_NOT_PINNED },
{10, true, "\005crate\002io", true, kNoPins, DOMAIN_NOT_PINNED },
-
- {13, false, "\007twitter\003com", kTwitterHSTS, kTwitterComPins, DOMAIN_TWITTER_COM },
- {17, true, "\003www\007twitter\003com", kTwitterHSTS, kTwitterComPins, DOMAIN_TWITTER_COM },
- {17, true, "\003api\007twitter\003com", kTwitterHSTS, kTwitterCDNPins, DOMAIN_TWITTER_COM },
- {19, true, "\005oauth\007twitter\003com", kTwitterHSTS, kTwitterComPins, DOMAIN_TWITTER_COM },
- {20, true, "\006mobile\007twitter\003com", kTwitterHSTS, kTwitterComPins, DOMAIN_TWITTER_COM },
- {17, true, "\003dev\007twitter\003com", kTwitterHSTS, kTwitterComPins, DOMAIN_TWITTER_COM },
- {22, true, "\010business\007twitter\003com", kTwitterHSTS, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {13, false, "\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {17, true, "\003www\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {17, true, "\003api\007twitter\003com", false, kTwitterCDNPins, DOMAIN_TWITTER_COM },
+ {19, true, "\005oauth\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {20, true, "\006mobile\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {17, true, "\003dev\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {22, true, "\010business\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
{22, true, "\010platform\007twitter\003com", false, kTwitterCDNPins, DOMAIN_TWITTER_COM },
{15, true, "\003si0\005twimg\003com", false, kTwitterCDNPins, DOMAIN_TWIMG_COM },
{23, true, "\010twimg0-a\010akamaihd\003net", false, kTwitterCDNPins, DOMAIN_AKAMAIHD_NET },
@@ -2649,188 +1479,13 @@ static const struct HSTSPreload kPreloadedSTS[] = {
static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS);
static const struct HSTSPreload kPreloadedSNISTS[] = {
- // These SNI-only domains must always use HTTPS.
{11, false, "\005gmail\003com", true, kGooglePins, DOMAIN_GMAIL_COM },
{16, false, "\012googlemail\003com", true, kGooglePins, DOMAIN_GOOGLEMAIL_COM },
{15, false, "\003www\005gmail\003com", true, kGooglePins, DOMAIN_GMAIL_COM },
{20, false, "\003www\012googlemail\003com", true, kGooglePins, DOMAIN_GOOGLEMAIL_COM },
- // These SNI-only domains must use an acceptable certificate iff using
- // HTTPS.
{22, true, "\020google-analytics\003com", false, kGooglePins, DOMAIN_GOOGLE_ANALYTICS_COM },
- // www. requires SNI.
{18, true, "\014googlegroups\003com", false, kGooglePins, DOMAIN_GOOGLEGROUPS_COM },
};
static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS);
-// Returns the HSTSPreload entry for the |canonicalized_host| in |entries|,
-// or NULL if there is none. Prefers exact hostname matches to those that
-// match only because HSTSPreload.include_subdomains is true.
-//
-// |canonicalized_host| should be the hostname as canonicalized by
-// CanonicalizeHost.
-static const struct HSTSPreload* GetHSTSPreload(
- const std::string& canonicalized_host,
- const struct HSTSPreload* entries,
- size_t num_entries) {
- for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
- for (size_t j = 0; j < num_entries; j++) {
- const struct HSTSPreload* entry = entries + j;
-
- if (i != 0 && !entry->include_subdomains)
- continue;
-
- if (entry->length == canonicalized_host.size() - i &&
- memcmp(entry->dns_name, &canonicalized_host[i], entry->length) == 0) {
- return entry;
- }
- }
- }
-
- return NULL;
-}
-
-// static
-bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host,
- bool sni_available) {
- std::string canonicalized_host = CanonicalizeHost(host);
- const struct HSTSPreload* entry =
- GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS);
-
- if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts)
- return true;
-
- if (sni_available) {
- entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS,
- kNumPreloadedSNISTS);
- if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts)
- return true;
- }
-
- return false;
-}
-
-// static
-void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) {
- std::string canonicalized_host = CanonicalizeHost(host);
-
- const struct HSTSPreload* entry =
- GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS);
-
- if (!entry) {
- entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS,
- kNumPreloadedSNISTS);
- }
-
- DCHECK(entry);
- DCHECK(entry->pins.required_hashes);
- DCHECK(entry->second_level_domain_name != DOMAIN_NOT_PINNED);
-
- UMA_HISTOGRAM_ENUMERATION("Net.PublicKeyPinFailureDomain",
- entry->second_level_domain_name, DOMAIN_NUM_EVENTS);
-}
-
-// IsPreloadedSTS returns true if the canonicalized hostname should always be
-// considered to have STS enabled.
-bool TransportSecurityState::IsPreloadedSTS(
- const std::string& canonicalized_host,
- bool sni_available,
- DomainState* out) {
- DCHECK(CalledOnValidThread());
-
- out->preloaded = true;
- out->mode = DomainState::MODE_STRICT;
- out->include_subdomains = false;
-
- for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
- std::string host_sub_chunk(&canonicalized_host[i],
- canonicalized_host.size() - i);
- out->domain = DNSDomainToString(host_sub_chunk);
- std::string hashed_host(HashHost(host_sub_chunk));
- if (forced_hosts_.find(hashed_host) != forced_hosts_.end()) {
- *out = forced_hosts_[hashed_host];
- out->domain = DNSDomainToString(host_sub_chunk);
- out->preloaded = true;
- return true;
- }
- bool ret;
- if (HasPreload(kPreloadedSTS, kNumPreloadedSTS, canonicalized_host, i, out,
- &ret)) {
- return ret;
- }
- if (sni_available &&
- HasPreload(kPreloadedSNISTS, kNumPreloadedSNISTS, canonicalized_host, i,
- out, &ret)) {
- return ret;
- }
- }
-
- return false;
-}
-
-static std::string HashesToBase64String(
- const FingerprintVector& hashes) {
- std::vector<std::string> hashes_strs;
- for (FingerprintVector::const_iterator
- i = hashes.begin(); i != hashes.end(); i++) {
- std::string s;
- const std::string hash_str(reinterpret_cast<const char*>(i->data),
- sizeof(i->data));
- base::Base64Encode(hash_str, &s);
- hashes_strs.push_back(s);
- }
-
- return JoinString(hashes_strs, ',');
-}
-
-TransportSecurityState::DomainState::DomainState()
- : mode(MODE_STRICT),
- created(base::Time::Now()),
- include_subdomains(false),
- preloaded(false) {
-}
-
-TransportSecurityState::DomainState::~DomainState() {
-}
-
-bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted(
- const FingerprintVector& hashes) {
-
- if (HashesIntersect(bad_preloaded_spki_hashes, hashes)) {
- LOG(ERROR) << "Rejecting public key chain for domain " << domain
- << ". Validated chain: " << HashesToBase64String(hashes)
- << ", matches one or more bad hashes: "
- << HashesToBase64String(bad_preloaded_spki_hashes);
- return false;
- }
-
- if (!(dynamic_spki_hashes.empty() && preloaded_spki_hashes.empty()) &&
- !HashesIntersect(dynamic_spki_hashes, hashes) &&
- !HashesIntersect(preloaded_spki_hashes, hashes)) {
- LOG(ERROR) << "Rejecting public key chain for domain " << domain
- << ". Validated chain: " << HashesToBase64String(hashes)
- << ", expected: " << HashesToBase64String(dynamic_spki_hashes)
- << " or: " << HashesToBase64String(preloaded_spki_hashes);
-
- return false;
- }
-
- return true;
-}
-
-bool TransportSecurityState::DomainState::IsMoreStrict(
- const TransportSecurityState::DomainState& other) {
- if (this->dynamic_spki_hashes.empty() && !other.dynamic_spki_hashes.empty())
- return false;
-
- if (!this->include_subdomains && other.include_subdomains)
- return false;
-
- return true;
-}
-
-bool TransportSecurityState::DomainState::ShouldRedirectHTTPToHTTPS()
- const {
- return mode == MODE_STRICT;
-}
-
-} // namespace
+#endif // NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_
« no previous file with comments | « net/base/transport_security_state.cc ('k') | net/base/transport_security_state_static.certs » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698