LCOV - code coverage report
Current view: top level - security/manager/ssl - PublicKeyPinningService.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 6 169 3.6 %
Date: 2017-07-14 16:53:18 Functions: 1 11 9.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* This Source Code Form is subject to the terms of the Mozilla Public
       2             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       3             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       4             : 
       5             : #include "PublicKeyPinningService.h"
       6             : 
       7             : #include "RootCertificateTelemetryUtils.h"
       8             : #include "mozilla/ArrayUtils.h"
       9             : #include "mozilla/Base64.h"
      10             : #include "mozilla/BinarySearch.h"
      11             : #include "mozilla/Casting.h"
      12             : #include "mozilla/Logging.h"
      13             : #include "mozilla/Telemetry.h"
      14             : #include "nsDependentString.h"
      15             : #include "nsISiteSecurityService.h"
      16             : #include "nsServiceManagerUtils.h"
      17             : #include "nsSiteSecurityService.h"
      18             : #include "pkix/pkixtypes.h"
      19             : #include "seccomon.h"
      20             : #include "sechash.h"
      21             : 
      22             : #include "StaticHPKPins.h" // autogenerated by genHPKPStaticpins.js
      23             : 
      24             : using namespace mozilla;
      25             : using namespace mozilla::pkix;
      26             : using namespace mozilla::psm;
      27             : 
      28             : LazyLogModule gPublicKeyPinningLog("PublicKeyPinningService");
      29             : 
      30             : /**
      31             :  Computes in the location specified by base64Out the SHA256 digest
      32             :  of the DER Encoded subject Public Key Info for the given cert
      33             : */
      34             : static nsresult
      35           0 : GetBase64HashSPKI(const CERTCertificate* cert, nsACString& hashSPKIDigest)
      36             : {
      37           0 :   hashSPKIDigest.Truncate();
      38           0 :   Digest digest;
      39           0 :   nsresult rv = digest.DigestBuf(SEC_OID_SHA256, cert->derPublicKey.data,
      40           0 :                                  cert->derPublicKey.len);
      41           0 :   if (NS_FAILED(rv)) {
      42           0 :     return rv;
      43             :   }
      44           0 :   return Base64Encode(nsDependentCSubstring(
      45           0 :                         BitwiseCast<char*, unsigned char*>(digest.get().data),
      46           0 :                         digest.get().len),
      47           0 :                       hashSPKIDigest);
      48             : }
      49             : 
      50             : /*
      51             :  * Sets certMatchesPinset to true if a given cert matches any fingerprints from
      52             :  * the given pinset or the dynamicFingerprints array, or to false otherwise.
      53             :  */
      54             : static nsresult
      55           0 : EvalCert(const CERTCertificate* cert, const StaticFingerprints* fingerprints,
      56             :          const nsTArray<nsCString>* dynamicFingerprints,
      57             :  /*out*/ bool& certMatchesPinset)
      58             : {
      59           0 :   certMatchesPinset = false;
      60           0 :   if (!fingerprints && !dynamicFingerprints) {
      61           0 :     MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
      62             :            ("pkpin: No hashes found\n"));
      63           0 :     return NS_ERROR_INVALID_ARG;
      64             :   }
      65             : 
      66           0 :   nsAutoCString base64Out;
      67           0 :   nsresult rv = GetBase64HashSPKI(cert, base64Out);
      68           0 :   if (NS_FAILED(rv)) {
      69           0 :     MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
      70             :            ("pkpin: GetBase64HashSPKI failed!\n"));
      71           0 :     return rv;
      72             :   }
      73             : 
      74           0 :   if (fingerprints) {
      75           0 :     for (size_t i = 0; i < fingerprints->size; i++) {
      76           0 :       if (base64Out.Equals(fingerprints->data[i])) {
      77           0 :         MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
      78             :                ("pkpin: found pin base_64 ='%s'\n", base64Out.get()));
      79           0 :         certMatchesPinset = true;
      80           0 :         return NS_OK;
      81             :       }
      82             :     }
      83             :   }
      84           0 :   if (dynamicFingerprints) {
      85           0 :     for (size_t i = 0; i < dynamicFingerprints->Length(); i++) {
      86           0 :       if (base64Out.Equals((*dynamicFingerprints)[i])) {
      87           0 :         MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
      88             :                ("pkpin: found pin base_64 ='%s'\n", base64Out.get()));
      89           0 :         certMatchesPinset = true;
      90           0 :         return NS_OK;
      91             :       }
      92             :     }
      93             :   }
      94           0 :   return NS_OK;
      95             : }
      96             : 
      97             : /*
      98             :  * Sets certListIntersectsPinset to true if a given chain matches any
      99             :  * fingerprints from the given static fingerprints or the
     100             :  * dynamicFingerprints array, or to false otherwise.
     101             :  */
     102             : static nsresult
     103           0 : EvalChain(const UniqueCERTCertList& certList,
     104             :           const StaticFingerprints* fingerprints,
     105             :           const nsTArray<nsCString>* dynamicFingerprints,
     106             :   /*out*/ bool& certListIntersectsPinset)
     107             : {
     108           0 :   certListIntersectsPinset = false;
     109             :   CERTCertificate* currentCert;
     110             : 
     111           0 :   if (!fingerprints && !dynamicFingerprints) {
     112           0 :     MOZ_ASSERT(false, "Must pass in at least one type of pinset");
     113             :     return NS_ERROR_FAILURE;
     114             :   }
     115             : 
     116             :   CERTCertListNode* node;
     117           0 :   for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList);
     118           0 :        node = CERT_LIST_NEXT(node)) {
     119           0 :     currentCert = node->cert;
     120           0 :     MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
     121             :            ("pkpin: certArray subject: '%s'\n", currentCert->subjectName));
     122           0 :     MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
     123             :            ("pkpin: certArray issuer: '%s'\n", currentCert->issuerName));
     124             :     nsresult rv = EvalCert(currentCert, fingerprints, dynamicFingerprints,
     125           0 :                            certListIntersectsPinset);
     126           0 :     if (NS_FAILED(rv)) {
     127           0 :       return rv;
     128             :     }
     129           0 :     if (certListIntersectsPinset) {
     130           0 :       return NS_OK;
     131             :     }
     132             :   }
     133           0 :   MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug, ("pkpin: no matches found\n"));
     134           0 :   return NS_OK;
     135             : }
     136             : 
     137             : class TransportSecurityPreloadBinarySearchComparator
     138             : {
     139             : public:
     140           0 :   explicit TransportSecurityPreloadBinarySearchComparator(
     141             :     const char* aTargetHost)
     142           0 :     : mTargetHost(aTargetHost) { }
     143             : 
     144           0 :   int operator()(const TransportSecurityPreload& val) const
     145             :   {
     146           0 :     return strcmp(mTargetHost, val.mHost);
     147             :   }
     148             : 
     149             : private:
     150             :   const char* mTargetHost; // non-owning
     151             : };
     152             : 
     153             : nsresult
     154           0 : PublicKeyPinningService::ChainMatchesPinset(const UniqueCERTCertList& certList,
     155             :                                             const nsTArray<nsCString>& aSHA256keys,
     156             :                                     /*out*/ bool& chainMatchesPinset)
     157             : {
     158           0 :   return EvalChain(certList, nullptr, &aSHA256keys, chainMatchesPinset);
     159             : }
     160             : 
     161             : // Returns via one of the output parameters the most relevant pinning
     162             : // information that is valid for the given host at the given time.
     163             : // Dynamic pins are prioritized over static pins.
     164             : static nsresult
     165           0 : FindPinningInformation(const char* hostname, mozilla::pkix::Time time,
     166             :                        const OriginAttributes& originAttributes,
     167             :                /*out*/ nsTArray<nsCString>& dynamicFingerprints,
     168             :                /*out*/ const TransportSecurityPreload*& staticFingerprints)
     169             : {
     170           0 :   if (!hostname || hostname[0] == 0) {
     171           0 :     return NS_ERROR_INVALID_ARG;
     172             :   }
     173           0 :   staticFingerprints = nullptr;
     174           0 :   dynamicFingerprints.Clear();
     175             :   nsCOMPtr<nsISiteSecurityService> sssService =
     176           0 :     do_GetService(NS_SSSERVICE_CONTRACTID);
     177           0 :   if (!sssService) {
     178           0 :     return NS_ERROR_FAILURE;
     179             :   }
     180           0 :   const TransportSecurityPreload* foundEntry = nullptr;
     181           0 :   const char* evalHost = hostname;
     182             :   const char* evalPart;
     183             :   // Notice how the (xx = strchr) prevents pins for unqualified domain names.
     184           0 :   while (!foundEntry && (evalPart = strchr(evalHost, '.'))) {
     185           0 :     MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
     186             :            ("pkpin: Querying pinsets for host: '%s'\n", evalHost));
     187             :     // Attempt dynamic pins first
     188             :     nsresult rv;
     189             :     bool found;
     190             :     bool includeSubdomains;
     191           0 :     nsTArray<nsCString> pinArray;
     192           0 :     rv = sssService->GetKeyPinsForHostname(nsDependentCString(evalHost), time,
     193             :                                            originAttributes, pinArray,
     194           0 :                                            &includeSubdomains, &found);
     195           0 :     if (NS_FAILED(rv)) {
     196           0 :       return rv;
     197             :     }
     198           0 :     if (found && (evalHost == hostname || includeSubdomains)) {
     199           0 :       MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
     200             :              ("pkpin: Found dyn match for host: '%s'\n", evalHost));
     201           0 :       dynamicFingerprints = pinArray;
     202           0 :       return NS_OK;
     203             :     }
     204             : 
     205             :     size_t foundEntryIndex;
     206           0 :     if (BinarySearchIf(kPublicKeyPinningPreloadList, 0,
     207             :                        ArrayLength(kPublicKeyPinningPreloadList),
     208           0 :                        TransportSecurityPreloadBinarySearchComparator(evalHost),
     209             :                        &foundEntryIndex)) {
     210           0 :       foundEntry = &kPublicKeyPinningPreloadList[foundEntryIndex];
     211           0 :       MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
     212             :              ("pkpin: Found pinset for host: '%s'\n", evalHost));
     213           0 :       if (evalHost != hostname) {
     214           0 :         if (!foundEntry->mIncludeSubdomains) {
     215             :           // Does not apply to this host, continue iterating
     216           0 :           foundEntry = nullptr;
     217             :         }
     218             :       }
     219             :     } else {
     220           0 :       MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
     221             :              ("pkpin: Didn't find pinset for host: '%s'\n", evalHost));
     222             :     }
     223             :     // Add one for '.'
     224           0 :     evalHost = evalPart + 1;
     225             :   }
     226             : 
     227           0 :   if (foundEntry && foundEntry->pinset) {
     228           0 :     if (time > TimeFromEpochInSeconds(kPreloadPKPinsExpirationTime /
     229             :                                       PR_USEC_PER_SEC)) {
     230           0 :       return NS_OK;
     231             :     }
     232           0 :     staticFingerprints = foundEntry;
     233             :   }
     234           0 :   return NS_OK;
     235             : }
     236             : 
     237             : // Returns true via the output parameter if the given certificate list meets
     238             : // pinning requirements for the given host at the given time. It must be the
     239             : // case that either there is an intersection between the set of hashes of
     240             : // subject public key info data in the list and the most relevant non-expired
     241             : // pinset for the host or there is no pinning information for the host.
     242             : static nsresult
     243           0 : CheckPinsForHostname(const UniqueCERTCertList& certList, const char* hostname,
     244             :                      bool enforceTestMode, mozilla::pkix::Time time,
     245             :                      const OriginAttributes& originAttributes,
     246             :              /*out*/ bool& chainHasValidPins,
     247             :     /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
     248             : {
     249           0 :   chainHasValidPins = false;
     250           0 :   if (!certList) {
     251           0 :     return NS_ERROR_INVALID_ARG;
     252             :   }
     253           0 :   if (!hostname || hostname[0] == 0) {
     254           0 :     return NS_ERROR_INVALID_ARG;
     255             :   }
     256             : 
     257           0 :   nsTArray<nsCString> dynamicFingerprints;
     258           0 :   const TransportSecurityPreload* staticFingerprints = nullptr;
     259             :   nsresult rv = FindPinningInformation(hostname, time, originAttributes,
     260           0 :                                        dynamicFingerprints, staticFingerprints);
     261             :   // If we have no pinning information, the certificate chain trivially
     262             :   // validates with respect to pinning.
     263           0 :   if (dynamicFingerprints.Length() == 0 && !staticFingerprints) {
     264           0 :     chainHasValidPins = true;
     265           0 :     return NS_OK;
     266             :   }
     267           0 :   if (dynamicFingerprints.Length() > 0) {
     268           0 :     return EvalChain(certList, nullptr, &dynamicFingerprints, chainHasValidPins);
     269             :   }
     270           0 :   if (staticFingerprints) {
     271             :     bool enforceTestModeResult;
     272           0 :     rv = EvalChain(certList, staticFingerprints->pinset, nullptr,
     273           0 :                    enforceTestModeResult);
     274           0 :     if (NS_FAILED(rv)) {
     275           0 :       return rv;
     276             :     }
     277           0 :     chainHasValidPins = enforceTestModeResult;
     278           0 :     Telemetry::HistogramID histogram = staticFingerprints->mIsMoz
     279           0 :       ? Telemetry::CERT_PINNING_MOZ_RESULTS
     280           0 :       : Telemetry::CERT_PINNING_RESULTS;
     281           0 :     if (staticFingerprints->mTestMode) {
     282           0 :       histogram = staticFingerprints->mIsMoz
     283           0 :         ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS
     284             :         : Telemetry::CERT_PINNING_TEST_RESULTS;
     285           0 :       if (!enforceTestMode) {
     286           0 :         chainHasValidPins = true;
     287             :       }
     288             :     }
     289             :     // We can collect per-host pinning violations for this host because it is
     290             :     // operationally critical to Firefox.
     291           0 :     if (pinningTelemetryInfo) {
     292           0 :       if (staticFingerprints->mId != kUnknownId) {
     293           0 :         int32_t bucket = staticFingerprints->mId * 2
     294           0 :                          + (enforceTestModeResult ? 1 : 0);
     295           0 :         histogram = staticFingerprints->mTestMode
     296           0 :           ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS_BY_HOST
     297             :           : Telemetry::CERT_PINNING_MOZ_RESULTS_BY_HOST;
     298           0 :         pinningTelemetryInfo->certPinningResultBucket = bucket;
     299             :       } else {
     300           0 :         pinningTelemetryInfo->certPinningResultBucket =
     301           0 :             enforceTestModeResult ? 1 : 0;
     302             :       }
     303           0 :       pinningTelemetryInfo->accumulateResult = true;
     304           0 :       pinningTelemetryInfo->certPinningResultHistogram = histogram;
     305             :     }
     306             : 
     307             :     // We only collect per-CA pinning statistics upon failures.
     308           0 :     CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
     309             :     // Only log telemetry if the certificate list is non-empty.
     310           0 :     if (!CERT_LIST_END(rootNode, certList)) {
     311           0 :       if (!enforceTestModeResult && pinningTelemetryInfo) {
     312           0 :         int32_t binNumber = RootCABinNumber(&rootNode->cert->derCert);
     313           0 :         if (binNumber != ROOT_CERTIFICATE_UNKNOWN ) {
     314           0 :           pinningTelemetryInfo->accumulateForRoot = true;
     315           0 :           pinningTelemetryInfo->rootBucket = binNumber;
     316             :         }
     317             :       }
     318             :     }
     319             : 
     320           0 :     MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
     321             :            ("pkpin: Pin check %s for %s host '%s' (mode=%s)\n",
     322             :             enforceTestModeResult ? "passed" : "failed",
     323             :             staticFingerprints->mIsMoz ? "mozilla" : "non-mozilla",
     324             :             hostname, staticFingerprints->mTestMode ? "test" : "production"));
     325             :   }
     326             : 
     327           0 :   return NS_OK;
     328             : }
     329             : 
     330             : nsresult
     331           0 : PublicKeyPinningService::ChainHasValidPins(
     332             :   const UniqueCERTCertList& certList,
     333             :   const char* hostname,
     334             :   mozilla::pkix::Time time,
     335             :   bool enforceTestMode,
     336             :   const OriginAttributes& originAttributes,
     337             :   /*out*/ bool& chainHasValidPins,
     338             :   /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
     339             : {
     340           0 :   chainHasValidPins = false;
     341           0 :   if (!certList) {
     342           0 :     return NS_ERROR_INVALID_ARG;
     343             :   }
     344           0 :   if (!hostname || hostname[0] == 0) {
     345           0 :     return NS_ERROR_INVALID_ARG;
     346             :   }
     347           0 :   nsAutoCString canonicalizedHostname(CanonicalizeHostname(hostname));
     348           0 :   return CheckPinsForHostname(certList, canonicalizedHostname.get(),
     349             :                               enforceTestMode, time, originAttributes,
     350           0 :                               chainHasValidPins, pinningTelemetryInfo);
     351             : }
     352             : 
     353             : nsresult
     354           0 : PublicKeyPinningService::HostHasPins(const char* hostname,
     355             :                                      mozilla::pkix::Time time,
     356             :                                      bool enforceTestMode,
     357             :                                      const OriginAttributes& originAttributes,
     358             :                                      /*out*/ bool& hostHasPins)
     359             : {
     360           0 :   hostHasPins = false;
     361           0 :   nsAutoCString canonicalizedHostname(CanonicalizeHostname(hostname));
     362           0 :   nsTArray<nsCString> dynamicFingerprints;
     363           0 :   const TransportSecurityPreload* staticFingerprints = nullptr;
     364           0 :   nsresult rv = FindPinningInformation(canonicalizedHostname.get(), time,
     365             :                                        originAttributes, dynamicFingerprints,
     366           0 :                                        staticFingerprints);
     367           0 :   if (NS_FAILED(rv)) {
     368           0 :     return rv;
     369             :   }
     370           0 :   if (dynamicFingerprints.Length() > 0) {
     371           0 :     hostHasPins = true;
     372           0 :   } else if (staticFingerprints) {
     373           0 :     hostHasPins = !staticFingerprints->mTestMode || enforceTestMode;
     374             :   }
     375           0 :   return NS_OK;
     376             : }
     377             : 
     378             : nsAutoCString
     379          17 : PublicKeyPinningService::CanonicalizeHostname(const char* hostname)
     380             : {
     381          17 :   nsAutoCString canonicalizedHostname(hostname);
     382          17 :   ToLowerCase(canonicalizedHostname);
     383          34 :   while (canonicalizedHostname.Length() > 0 &&
     384          17 :          canonicalizedHostname.Last() == '.') {
     385           0 :     canonicalizedHostname.Truncate(canonicalizedHostname.Length() - 1);
     386             :   }
     387          17 :   return canonicalizedHostname;
     388             : }

Generated by: LCOV version 1.13