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_ |