security/manager/boot/src/PublicKeyPinningService.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/security/manager/boot/src/PublicKeyPinningService.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,306 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +#include "PublicKeyPinningService.h"
     1.9 +#include "pkix/nullptr.h"
    1.10 +#include "StaticHPKPins.h" // autogenerated by genHPKPStaticpins.js
    1.11 +
    1.12 +#include "cert.h"
    1.13 +#include "mozilla/Base64.h"
    1.14 +#include "mozilla/Telemetry.h"
    1.15 +#include "nsString.h"
    1.16 +#include "nssb64.h"
    1.17 +#include "pkix/pkixtypes.h"
    1.18 +#include "prlog.h"
    1.19 +#include "ScopedNSSTypes.h"
    1.20 +#include "seccomon.h"
    1.21 +#include "sechash.h"
    1.22 +
    1.23 +using namespace mozilla;
    1.24 +using namespace mozilla::pkix;
    1.25 +using namespace mozilla::psm;
    1.26 +
    1.27 +#if defined(PR_LOGGING)
    1.28 +PRLogModuleInfo* gPublicKeyPinningLog =
    1.29 +  PR_NewLogModule("PublicKeyPinningService");
    1.30 +#endif
    1.31 +
    1.32 +/**
    1.33 + Computes in the location specified by base64Out the SHA256 digest
    1.34 + of the DER Encoded subject Public Key Info for the given cert
    1.35 +*/
    1.36 +static SECStatus
    1.37 +GetBase64HashSPKI(const CERTCertificate* cert, SECOidTag hashType,
    1.38 +                  nsACString& hashSPKIDigest)
    1.39 +{
    1.40 +  hashSPKIDigest.Truncate();
    1.41 +  Digest digest;
    1.42 +  nsresult rv = digest.DigestBuf(hashType, cert->derPublicKey.data,
    1.43 +                                 cert->derPublicKey.len);
    1.44 +  if (NS_WARN_IF(NS_FAILED(rv))) {
    1.45 +    return SECFailure;
    1.46 +  }
    1.47 +  rv = Base64Encode(nsDependentCSubstring(
    1.48 +                      reinterpret_cast<const char*>(digest.get().data),
    1.49 +                      digest.get().len),
    1.50 +                      hashSPKIDigest);
    1.51 +  if (NS_WARN_IF(NS_FAILED(rv))) {
    1.52 +    return SECFailure;
    1.53 +  }
    1.54 +  return SECSuccess;
    1.55 +}
    1.56 +
    1.57 +/*
    1.58 + * Returns true if a given cert matches any hashType fingerprints from the
    1.59 + * given pinset, false otherwise.
    1.60 + */
    1.61 +static bool
    1.62 +EvalCertWithHashType(const CERTCertificate* cert, SECOidTag hashType,
    1.63 +                     const StaticFingerprints* fingerprints)
    1.64 +{
    1.65 +  if (!fingerprints) {
    1.66 +    PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
    1.67 +           ("pkpin: No hashes found for hash type: %d\n", hashType));
    1.68 +    return false;
    1.69 +  }
    1.70 +
    1.71 +  nsAutoCString base64Out;
    1.72 +  SECStatus srv = GetBase64HashSPKI(cert, hashType, base64Out);
    1.73 +  if (srv != SECSuccess) {
    1.74 +    PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
    1.75 +           ("pkpin: GetBase64HashSPKI failed!\n"));
    1.76 +    return false;
    1.77 +  }
    1.78 +
    1.79 +  for (size_t i = 0; i < fingerprints->size; i++) {
    1.80 +    if (base64Out.Equals(fingerprints->data[i])) {
    1.81 +      PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
    1.82 +             ("pkpin: found pin base_64 ='%s'\n", base64Out.get()));
    1.83 +      return true;
    1.84 +    }
    1.85 +  }
    1.86 +  return false;
    1.87 +}
    1.88 +
    1.89 +/*
    1.90 + * Returns true if a given chain matches any hashType fingerprints from the
    1.91 + * given pinset, false otherwise.
    1.92 + */
    1.93 +static bool
    1.94 +EvalChainWithHashType(const CERTCertList* certList, SECOidTag hashType,
    1.95 +                      const StaticPinset* pinset)
    1.96 +{
    1.97 +  CERTCertificate* currentCert;
    1.98 +
    1.99 +  const StaticFingerprints* fingerprints = nullptr;
   1.100 +  if (hashType == SEC_OID_SHA256) {
   1.101 +    fingerprints = pinset->sha256;
   1.102 +  } else if (hashType == SEC_OID_SHA1) {
   1.103 +    fingerprints = pinset->sha1;
   1.104 +  }
   1.105 +  if (!fingerprints) {
   1.106 +    return false;
   1.107 +  }
   1.108 +
   1.109 +  CERTCertListNode* node;
   1.110 +  for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList);
   1.111 +       node = CERT_LIST_NEXT(node)) {
   1.112 +    currentCert = node->cert;
   1.113 +    PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
   1.114 +           ("pkpin: certArray subject: '%s'\n",
   1.115 +            currentCert->subjectName));
   1.116 +    PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
   1.117 +           ("pkpin: certArray common_name: '%s'\n",
   1.118 +            CERT_GetCommonName(&(currentCert->issuer))));
   1.119 +    if (EvalCertWithHashType(currentCert, hashType, fingerprints)) {
   1.120 +      return true;
   1.121 +    }
   1.122 +  }
   1.123 +  PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, ("pkpin: no matches found\n"));
   1.124 +  return false;
   1.125 +}
   1.126 +
   1.127 +/**
   1.128 + * Given a pinset and certlist, return true if one of the certificates on
   1.129 + * the list matches a fingerprint in the pinset, false otherwise.
   1.130 + */
   1.131 +static bool
   1.132 +EvalChainWithPinset(const CERTCertList* certList,
   1.133 +                    const StaticPinset* pinset) {
   1.134 +  // SHA256 is more trustworthy, try that first.
   1.135 +  if (EvalChainWithHashType(certList, SEC_OID_SHA256, pinset)) {
   1.136 +    return true;
   1.137 +  }
   1.138 +  return EvalChainWithHashType(certList, SEC_OID_SHA1, pinset);
   1.139 +}
   1.140 +
   1.141 +/**
   1.142 +  Comparator for the is public key pinned host.
   1.143 +*/
   1.144 +static int
   1.145 +TransportSecurityPreloadCompare(const void *key, const void *entry) {
   1.146 +  const char *keyStr = reinterpret_cast<const char *>(key);
   1.147 +  const TransportSecurityPreload *preloadEntry =
   1.148 +    reinterpret_cast<const TransportSecurityPreload *>(entry);
   1.149 +
   1.150 +  return strcmp(keyStr, preloadEntry->mHost);
   1.151 +}
   1.152 +
   1.153 +/**
   1.154 + * Check PKPins on the given certlist against the specified hostname
   1.155 + */
   1.156 +static bool
   1.157 +CheckPinsForHostname(const CERTCertList *certList, const char *hostname,
   1.158 +                     bool enforceTestMode)
   1.159 +{
   1.160 +  if (!certList) {
   1.161 +    return false;
   1.162 +  }
   1.163 +  if (!hostname || hostname[0] == 0) {
   1.164 +    return false;
   1.165 +  }
   1.166 +
   1.167 +  TransportSecurityPreload *foundEntry = nullptr;
   1.168 +  char *evalHost = const_cast<char*>(hostname);
   1.169 +  char *evalPart;
   1.170 +  // Notice how the (xx = strchr) prevents pins for unqualified domain names.
   1.171 +  while (!foundEntry && (evalPart = strchr(evalHost, '.'))) {
   1.172 +    PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
   1.173 +           ("pkpin: Querying pinsets for host: '%s'\n", evalHost));
   1.174 +    foundEntry = (TransportSecurityPreload *)bsearch(evalHost,
   1.175 +      kPublicKeyPinningPreloadList,
   1.176 +      sizeof(kPublicKeyPinningPreloadList) / sizeof(TransportSecurityPreload),
   1.177 +      sizeof(TransportSecurityPreload),
   1.178 +      TransportSecurityPreloadCompare);
   1.179 +    if (foundEntry) {
   1.180 +      PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
   1.181 +             ("pkpin: Found pinset for host: '%s'\n", evalHost));
   1.182 +      if (evalHost != hostname) {
   1.183 +        if (!foundEntry->mIncludeSubdomains) {
   1.184 +          // Does not apply to this host, continue iterating
   1.185 +          foundEntry = nullptr;
   1.186 +        }
   1.187 +      }
   1.188 +    } else {
   1.189 +      PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
   1.190 +             ("pkpin: Didn't find pinset for host: '%s'\n", evalHost));
   1.191 +    }
   1.192 +    // Add one for '.'
   1.193 +    evalHost = evalPart + 1;
   1.194 +  }
   1.195 +
   1.196 +  if (foundEntry && foundEntry->pinset) {
   1.197 +    bool result = EvalChainWithPinset(certList, foundEntry->pinset);
   1.198 +    bool retval = result;
   1.199 +    Telemetry::ID histogram = foundEntry->mIsMoz
   1.200 +      ? Telemetry::CERT_PINNING_MOZ_RESULTS
   1.201 +      : Telemetry::CERT_PINNING_RESULTS;
   1.202 +    if (foundEntry->mTestMode) {
   1.203 +      histogram = foundEntry->mIsMoz
   1.204 +        ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS
   1.205 +        : Telemetry::CERT_PINNING_TEST_RESULTS;
   1.206 +      if (!enforceTestMode) {
   1.207 +        retval = true;
   1.208 +      }
   1.209 +    }
   1.210 +    // We can collect per-host pinning violations for this host because it is
   1.211 +    // operationally critical to Firefox.
   1.212 +    if (foundEntry->mId != kUnknownId) {
   1.213 +      int32_t bucket = foundEntry->mId * 2 + (result ? 1 : 0);
   1.214 +      histogram = foundEntry->mTestMode
   1.215 +        ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS_BY_HOST
   1.216 +        : Telemetry::CERT_PINNING_MOZ_RESULTS_BY_HOST;
   1.217 +      Telemetry::Accumulate(histogram, bucket);
   1.218 +    } else {
   1.219 +      Telemetry::Accumulate(histogram, result ? 1 : 0);
   1.220 +    }
   1.221 +    PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
   1.222 +           ("pkpin: Pin check %s for %s host '%s' (mode=%s)\n",
   1.223 +            result ? "passed" : "failed",
   1.224 +            foundEntry->mIsMoz ? "mozilla" : "non-mozilla",
   1.225 +            hostname, foundEntry->mTestMode ? "test" : "production"));
   1.226 +    return retval;
   1.227 +  }
   1.228 +  return true; // No pinning information for this hostname
   1.229 +}
   1.230 +
   1.231 +/**
   1.232 + * Extract all the DNS names for a host (including CN) and evaluate the
   1.233 + * certifiate pins against all of them (Currently is an OR so we stop
   1.234 + * evaluating at the first OK pin).
   1.235 + */
   1.236 +static bool
   1.237 +CheckChainAgainstAllNames(const CERTCertList* certList, bool enforceTestMode)
   1.238 +{
   1.239 +  PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
   1.240 +         ("pkpin: top of checkChainAgainstAllNames"));
   1.241 +  CERTCertListNode* node = CERT_LIST_HEAD(certList);
   1.242 +  if (!node) {
   1.243 +    return false;
   1.244 +  }
   1.245 +  CERTCertificate* cert = node->cert;
   1.246 +  if (!cert) {
   1.247 +    return false;
   1.248 +  }
   1.249 +
   1.250 +  ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
   1.251 +  if (!arena) {
   1.252 +    return false;
   1.253 +  }
   1.254 +
   1.255 +  bool hasValidPins = false;
   1.256 +  CERTGeneralName* nameList;
   1.257 +  CERTGeneralName* currentName;
   1.258 +  nameList = CERT_GetConstrainedCertificateNames(cert, arena.get(), PR_TRUE);
   1.259 +  if (!nameList) {
   1.260 +    return false;
   1.261 +  }
   1.262 +
   1.263 +  currentName = nameList;
   1.264 +  do {
   1.265 +    if (currentName->type == certDNSName
   1.266 +        && currentName->name.other.data[0] != 0) {
   1.267 +      // no need to cleaup, as the arena cleanup will do
   1.268 +      char *hostName = (char *)PORT_ArenaAlloc(arena.get(),
   1.269 +                                               currentName->name.other.len + 1);
   1.270 +      if (!hostName) {
   1.271 +        break;
   1.272 +      }
   1.273 +      // We use a temporary buffer as the hostname as returned might not be
   1.274 +      // null terminated.
   1.275 +      hostName[currentName->name.other.len] = 0;
   1.276 +      memcpy(hostName, currentName->name.other.data,
   1.277 +             currentName->name.other.len);
   1.278 +      if (!hostName[0]) {
   1.279 +        // cannot call CheckPinsForHostname on empty or null hostname
   1.280 +        break;
   1.281 +      }
   1.282 +      if (CheckPinsForHostname(certList, hostName, enforceTestMode)) {
   1.283 +        hasValidPins = true;
   1.284 +        break;
   1.285 +      }
   1.286 +    }
   1.287 +    currentName = CERT_GetNextGeneralName(currentName);
   1.288 +  } while (currentName != nameList);
   1.289 +
   1.290 +  return hasValidPins;
   1.291 +}
   1.292 +
   1.293 +bool
   1.294 +PublicKeyPinningService::ChainHasValidPins(const CERTCertList* certList,
   1.295 +                                           const char* hostname,
   1.296 +                                           const PRTime time,
   1.297 +                                           bool enforceTestMode)
   1.298 +{
   1.299 +  if (!certList) {
   1.300 +    return false;
   1.301 +  }
   1.302 +  if (time > kPreloadPKPinsExpirationTime) {
   1.303 +    return true;
   1.304 +  }
   1.305 +  if (!hostname || hostname[0] == 0) {
   1.306 +    return CheckChainAgainstAllNames(certList, enforceTestMode);
   1.307 +  }
   1.308 +  return CheckPinsForHostname(certList, hostname, enforceTestMode);
   1.309 +}

mercurial