| Index: net/base/transport_security_state_static.h
|
| ===================================================================
|
| --- net/base/transport_security_state_static.h (revision 132015)
|
| +++ net/base/transport_security_state_static.h (working copy)
|
| @@ -1,19 +1,14 @@
|
| -// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// 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_TRANSPORT_SECURITY_STATE_STATIC_H_
|
| -#define NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_
|
| +#ifndef NET_BASE_PUBLIC_KEY_HASHES_
|
| +#define NET_BASE_PUBLIC_KEY_HASHES_
|
| #pragma once
|
|
|
| -// These are SubjectPublicKeyInfo hashes for public key pinning. The
|
| +// This file contains 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
|
| @@ -84,6 +79,7 @@
|
| static const char kSPKIHash_Google1024[] =
|
| "sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0=";
|
|
|
| +// Not yet used publicly.
|
| static const char kSPKIHash_Google2048[] =
|
| "sha1/AbkhxY0L343gKf+cki7NVWp+ozk=";
|
|
|
| @@ -327,12 +323,11 @@
|
| 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=";
|
|
|
| @@ -583,6 +578,7 @@
|
| static const char kSPKIHash_VeriSignUniversal[] =
|
| "sha1/u8I+KQuzKHcdrT6iTb30I70GsD0=";
|
|
|
| +// Not public
|
| static const char kSPKIHash_Twitter1[] =
|
| "sha1/Vv7zwhR9TtOIN/29MFI4cgHld40=";
|
|
|
| @@ -737,29 +733,25 @@
|
|
|
| #if 0
|
| -----BEGIN CERTIFICATE-----
|
| -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=
|
| +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=
|
| -----END CERTIFICATE-----
|
| #endif
|
| static const char kSPKIHash_Entrust_2048[] =
|
| @@ -1243,23 +1235,1162 @@
|
| static const char kSPKIHash_GTECyberTrustGlobalRoot[] =
|
| "sha1/WXkS3mF11m/EI7d3E3THlt5viHI=";
|
|
|
| -// The following is static data describing the hosts that are hardcoded with
|
| -// certificate pins or HSTS information.
|
| +#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;
|
| +}
|
| +
|
| // 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,
|
| @@ -1320,6 +2451,8 @@
|
| 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,
|
| @@ -1340,6 +2473,7 @@
|
| kSPKIHash_GeoTrustPrimary_G2,
|
| kSPKIHash_GeoTrustPrimary_G3,
|
| kSPKIHash_Twitter1,
|
| +
|
| kSPKIHash_Entrust_2048,
|
| kSPKIHash_Entrust_EV,
|
| kSPKIHash_Entrust_G2,
|
| @@ -1364,14 +2498,35 @@
|
| kNoRejectedPublicKeys, \
|
| }
|
|
|
| -#define kNoPins {\
|
| +// 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 { \
|
| 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 },
|
| - {12, true, "\006google\003com", false, kGooglePins, DOMAIN_GOOGLE_COM },
|
| - {19, true, "\006health\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
|
| + // Now we force HTTPS for subtrees of 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 },
|
| @@ -1386,13 +2541,20 @@
|
| {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 },
|
| - {23, true, "\005chart\004apis\006google\003com", false, 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.
|
| {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 },
|
| @@ -1403,11 +2565,14 @@
|
| {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 },
|
| @@ -1460,22 +2625,23 @@
|
| {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", 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 },
|
| +
|
| + {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 },
|
| {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 },
|
| @@ -1483,13 +2649,188 @@
|
| 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);
|
|
|
| -#endif // NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_
|
| +// 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
|
|
|