Commit 650a9668 authored by Camilo Viecco's avatar Camilo Viecco Committed by Mike Perry
Browse files

Bug #11955 Backport certificate pinning

Includes the following Mozilla patches, some modified for Tor Browser:

Bug 744204 - Allow Key pining part 1 - Built-in Pinning Service. r=keeler

Bug 744204 - Allow Certificate key pinning Part 2 - Certverifier Interface. r=keeler

  --HG--
  extra : rebase_source : 2f9748ba0b241c697e22b7ff72f2f5a0fad4a2ca

Bug 998057: Add test pinset to the pin generator (r=cviecco)

  --HG--
  rename : security/manager/ssl/tests/unit/tlsserver/default-ee.der => security/manager/boot/src/default-ee.der

Bug 998057: Add tests for certificate pinning (r=cviecco,dkeeler)

Bug 1002696 - Minimum set of changes to make genHPKPStaticPins.js productionizable. r=cviecco, dkeeler

  --HG--
  rename : security/manager/boot/src/PreloadedHPKPins.json => security/manager/tools/PreloadedHPKPins.json
  rename : security/manager/boot/src/genHPKPStaticPins.js => security/manager/tools/genHPKPStaticPins.js

Bug 951315 - Add telemetry to PK pinning. r=dkeeler

Bug 1006107 - Disable pining by default, setup pinning for *.addons.mozilla.org. r=dkeeler

  Tor project: only patching two files:
  security/manager/ssl/src/nsNSSComponent.cpp
  netwerk/base/public/security-prefs.js

  --HG--
  extra : rebase_source : 93b1dbd5dc31490424060729a3941deffa8ee1d5

Bug 772756: Implement sha1 support, import Chrome's pinsets wholesale, add test mode (r=cviecco,keeler)

  Tor project, we only patch:
  security/manager/ssl/tests/unit/test_pinning.js
  security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp
  security/manager/ssl/tests/unit/tlsserver/default-ee.der
  security/manager/ssl/tests/unit/tlsserver/generate_certs.sh
  security/manager/ssl/tests/unit/tlsserver/other-test-ca.der
  security/manager/ssl/tests/unit/tlsserver/test-ca.der

Bug 1009720: Telemetry for CERT_PINNING_TEST_RESULTS (r=keeler)

Bug 1007844: Implement per-host telemetry for pin violations for AMO and aus4 (r=keeler)

  Only patching toolkit/components/telemetry/Histograms.json

Bug 1011269: Add CertVerifier::pinningEnforceTestMode (r=keeler)

  Tor project, only commit:
  security/certverifier/CertVerifier.cpp
  security/certverifier/CertVerifier.h
  security/manager/ssl/src/nsNSSComponent.cpp

Bug 1012882: Restrict pinning to desktop (r=keeler)

Bug 1066190 (see: Tor Bug #13684)

Tor Bug #11955: Backport certificate pinning

  Bring the following files up to date:

  security/manager/boot/src/PublicKeyPinningService.cpp
  security/manager/boot/src/PublicKeyPinningService.h
  security/manager/boot/src/StaticHPKPins.h
  security/manager/ssl/tests/unit/test_pinning.js
  security/manager/tools/PreloadedHPKPins.json
  security/manager/tools/genHPKPStaticPins.js
  security/pkix/include/pkix/Time.h
  security/pkix/lib/pkixtime.cpp
parent 95a7b314
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ ID
.*.sw[a-z]

# User files that may appear at the root
/.mozconfig*
#/.mozconfig*
/mozconfig
/configure
/config.cache
+3 −0
Original line number Diff line number Diff line
@@ -1484,6 +1484,9 @@ pref("security.csp.speccompliant", true);
// Block insecure active content on https pages
pref("security.mixed_content.block_active_content", true);

// 1 = allow MITM for certificate pinning checks.
pref("security.cert_pinning.enforcement_level", 1);

// Override the Gecko-default value of false for Firefox.
pref("plain_text.wrap_long_lines", true);

+3 −0
Original line number Diff line number Diff line
@@ -1566,6 +1566,9 @@ pref("security.csp.experimentalEnabled", false);
pref("security.mixed_content.block_active_content", false);
pref("security.mixed_content.block_display_content", false);

// Disable pinning checks by default.
pref("security.cert_pinning.enforcement_level", 0);

// Modifier key prefs: default to Windows settings,
// menu access key = alt, accelerator key = control.
// Use 17 for Ctrl, 18 for Alt, 224 for Meta, 91 for Win, 0 for none. Mac settings in macprefs.js
+2 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ public:
                            /*const*/ CERTCertificate* issuerCertToDup,
                            PRTime time,
                            /*optional*/ const SECItem* stapledOCSPresponse);
  SECStatus IsChainValid(const CERTCertList* certChain) { return SECSuccess; }

private:
  void* mPinArg; // non-owning!
  mozilla::pkix::ScopedCERTCertificate mTrustedRoot;
+219 −54
Original line number Diff line number Diff line
@@ -11,9 +11,11 @@
#include "pkix/pkix.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "PublicKeyPinningService.h"
#include "cert.h"
#include "ocsp.h"
#include "secerr.h"
#include "pk11pub.h"
#include "prerror.h"
#include "sslerr.h"

@@ -38,7 +40,8 @@ CertVerifier::CertVerifier(implementation_config ic,
#endif
                           ocsp_download_config odc,
                           ocsp_strict_config osc,
                           ocsp_get_config ogc)
                           ocsp_get_config ogc,
                           pinning_enforcement_config pel)
  : mImplementation(ic)
#ifndef NSS_NO_LIBPKIX
  , mMissingCertDownloadEnabled(mcdc == missing_cert_download_on)
@@ -47,6 +50,7 @@ CertVerifier::CertVerifier(implementation_config ic,
  , mOCSPDownloadEnabled(odc == ocsp_on)
  , mOCSPStrict(osc == ocsp_strict)
  , mOCSPGETEnabled(ogc == ocsp_get_enabled)
  , mPinningEnforcementLevel(pel)
{
}

@@ -64,7 +68,6 @@ InitCertVerifierLog()
#endif
}

#if 0
// Once we migrate to mozilla::pkix or change the overridable error
// logic this will become unnecesary.
static SECStatus
@@ -95,37 +98,146 @@ insertErrorIntoVerifyLog(CERTCertificate* cert, const PRErrorCode err,

  return SECSuccess;
}
#endif

SECStatus
IsCertBuiltInRoot(CERTCertificate* cert, bool& result) {
  result = false;
  ScopedPtr<PK11SlotList, PK11_FreeSlotList> slots;
  slots = PK11_GetAllSlotsForCert(cert, nullptr);
  if (!slots) {
    if (PORT_GetError() == SEC_ERROR_NO_TOKEN) {
      // no list
      return SECSuccess;
    }
    return SECFailure;
  }
  for (PK11SlotListElement* le = slots->head; le; le = le->next) {
    char* token = PK11_GetTokenName(le->slot);
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("BuiltInRoot? subject=%s token=%s",cert->subjectName, token));
    if (strcmp("Builtin Object Token", token) == 0) {
      result = true;
      return SECSuccess;
    }
  }
  return SECSuccess;
}

struct ChainValidationCallbackState
{
  const char* hostname;
  const CertVerifier::pinning_enforcement_config pinningEnforcementLevel;
  const SECCertificateUsage usage;
  const PRTime time;
};

SECStatus chainValidationCallback(void* state, const CERTCertList* certList,
                                  PRBool* chainOK)
{
  ChainValidationCallbackState* callbackState =
    reinterpret_cast<ChainValidationCallbackState*>(state);

  *chainOK = PR_FALSE;

  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("verifycert: Inside the Callback \n"));
  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
         ("verifycert: Inside the Callback \n"));

  // On sanity failure we fail closed.
  if (!certList) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("verifycert: Short circuit, callback, "
                                            "sanity check failed \n"));
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("verifycert: Short circuit, callback, sanity check failed \n"));
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    return SECFailure;
  }
  if (!callbackState) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("verifycert: Short circuit, callback, no state! \n"));
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    return SECFailure;
  }

  if (callbackState->usage != certificateUsageSSLServer ||
      callbackState->pinningEnforcementLevel == CertVerifier::pinningDisabled) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("verifycert: Callback shortcut pel=%d \n",
            callbackState->pinningEnforcementLevel));
    *chainOK = PR_TRUE;
    return SECSuccess;
  }

  for (CERTCertListNode* node = CERT_LIST_HEAD(certList);
       !CERT_LIST_END(node, certList);
       node = CERT_LIST_NEXT(node)) {
    CERTCertificate* currentCert = node->cert;
    if (CERT_LIST_END(CERT_LIST_NEXT(node), certList)) {
      bool isBuiltInRoot = false;
      SECStatus srv = IsCertBuiltInRoot(currentCert, isBuiltInRoot);
      if (srv != SECSuccess) {
        PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Is BuiltInRoot failure"));
        return srv;
      }
      // If desired, the user can enable "allow user CA MITM mode", in which
      // case key pinning is not enforced for certificates that chain to trust
      // anchors that are not in Mozilla's root program
      if (!isBuiltInRoot &&
          (callbackState->pinningEnforcementLevel ==
             CertVerifier::pinningAllowUserCAMITM)) {
        *chainOK = PR_TRUE;
        return SECSuccess;
      }
    }
  }

  const bool enforceTestMode = (callbackState->pinningEnforcementLevel ==
                                CertVerifier::pinningEnforceTestMode);
  *chainOK = PublicKeyPinningService::
    ChainHasValidPins(certList, callbackState->hostname, callbackState->time,
                      enforceTestMode);

  return SECSuccess;
}

// This always returns secfailure but its objective is to replate
// the PR_Error
static void
tryWorsenPRErrorInCallback(CERTCertificate* cert,
                           ChainValidationCallbackState* callbackState) {
  ScopedCERTCertificate certCopy(CERT_DupCertificate(cert));
  if (!certCopy) {
    return;
  }
  ScopedCERTCertList certList(CERT_NewCertList());
  if (!certList) {
    return;
  }
  SECStatus srv = CERT_AddCertToListTail(certList.get(), certCopy.get());
  if (srv != SECSuccess) {
    return;
  }
  certCopy.release(); // now owned by certList
  PRBool chainOK = false;
  srv = chainValidationCallback(&callbackState, certList.get(), &chainOK);
  if (srv != SECSuccess) {
    return;
  }
  if (!chainOK) {
    PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); // same as libpkix
    return ;
  }
  return; // no change in PR_error
}

static SECStatus
ClassicVerifyCert(CERTCertificate* cert,
                  const SECCertificateUsage usage,
                  const PRTime time,
                  void* pinArg,
                  ChainValidationCallbackState* callbackState,
                  /*optional out*/ ScopedCERTCertList* validationChain,
                  /*optional out*/ CERTVerifyLog* verifyLog)
{
  SECStatus rv;
  SECCertUsage enumUsage;
  if (validationChain) {
  switch (usage) {
    case certificateUsageSSLClient:
      enumUsage = certUsageSSLClient;
@@ -156,7 +268,6 @@ ClassicVerifyCert(CERTCertificate* cert,
      PORT_SetError(SEC_ERROR_INVALID_ARGS);
      return SECFailure;
  }
  }
  if (usage == certificateUsageSSLServer) {
    // SSL server cert verification has always used CERT_VerifyCert, so we
    // continue to use it for SSL cert verification to minimize the risk of
@@ -168,13 +279,44 @@ ClassicVerifyCert(CERTCertificate* cert,
    rv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), cert, true,
                                usage, time, pinArg, verifyLog, nullptr);
  }

  if (rv == SECSuccess &&
      (validationChain || usage == certificateUsageSSLServer)) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
           ("VerifyCert: getting chain in 'classic' \n"));
    ScopedCERTCertList certChain(CERT_GetCertChainFromCert(cert, time,
                                                           enumUsage));
    if (!certChain) {
      return SECFailure;
    }
    if (usage == certificateUsageSSLServer) {
      PRBool chainOK = PR_FALSE;
      SECStatus srv = chainValidationCallback(callbackState, certChain.get(),
                                              &chainOK);
      if (srv != SECSuccess) {
        return srv;
      }
      if (chainOK != PR_TRUE) {
        if (verifyLog) {
          insertErrorIntoVerifyLog(cert,
                                   SEC_ERROR_APPLICATION_CALLBACK_ERROR,
                                   verifyLog);
        }
        PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); // same as libpkix
        return SECFailure;
      }
    }

    // If there is an error we may need to worsen to error to be a pinning failure
    if (rv != SECSuccess && usage == certificateUsageSSLServer) {
      tryWorsenPRErrorInCallback(cert, callbackState);
    }

    if (rv == SECSuccess && validationChain) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: getting chain in 'classic' \n"));
    *validationChain = CERT_GetCertChainFromCert(cert, time, enumUsage);
    if (!*validationChain) {
      rv = SECFailure;
      *validationChain = certChain.release();
    }
  }

  return rv;
}

@@ -227,6 +369,7 @@ CertVerifier::MozillaPKIXVerifyCert(
                   const PRTime time,
                   void* pinArg,
                   const Flags flags,
                   ChainValidationCallbackState* callbackState,
      /*optional*/ const SECItem* stapledOCSPResponse,
  /*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain,
  /*optional out*/ SECOidTag* evOidPolicy)
@@ -249,6 +392,10 @@ CertVerifier::MozillaPKIXVerifyCert(
    return SECFailure;
  }

  CERTChainVerifyCallback callbackContainer;
  callbackContainer.isChainValid = chainValidationCallback;
  callbackContainer.isChainValidArg = callbackState;

  NSSCertDBTrustDomain::OCSPFetching ocspFetching
    = !mOCSPDownloadEnabled ||
      (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::NeverFetchOCSP
@@ -295,7 +442,7 @@ CertVerifier::MozillaPKIXVerifyCert(
                      ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP
                        ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
                        : NSSCertDBTrustDomain::FetchOCSPForEV,
                      mOCSPCache, pinArg);
                      mOCSPCache, pinArg, &callbackContainer);
        rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                          KeyUsage::digitalSignature, // ECDHE/DHE
                                          KeyUsage::keyEncipherment, // RSA
@@ -321,7 +468,7 @@ CertVerifier::MozillaPKIXVerifyCert(

      // Now try non-EV.
      NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
                                       pinArg);
                                       pinArg, &callbackContainer);
      rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                        KeyUsage::digitalSignature, // (EC)DHE
                                        KeyUsage::keyEncipherment, // RSA
@@ -434,6 +581,12 @@ CertVerifier::MozillaPKIXVerifyCert(
      return SECFailure;
  }

  // If there is an error we may need to worsen to error to be a pinning failure
  if (rv != SECSuccess && usage == certificateUsageSSLServer &&
      PR_GetError() != SEC_ERROR_APPLICATION_CALLBACK_ERROR) {
    tryWorsenPRErrorInCallback(cert, callbackState);
  }

  if (validationChain && rv == SECSuccess) {
    *validationChain = builtChain.release();
  }
@@ -443,19 +596,25 @@ CertVerifier::MozillaPKIXVerifyCert(

SECStatus
CertVerifier::VerifyCert(CERTCertificate* cert,
            /*optional*/ const SECItem* stapledOCSPResponse,
                         const SECCertificateUsage usage,
                         const PRTime time,
                         void* pinArg,
                         const char* hostname,
                         const Flags flags,
                         /*optional in*/ const SECItem* stapledOCSPResponse,
                         /*optional out*/ ScopedCERTCertList* validationChain,
                         /*optional out*/ SECOidTag* evOidPolicy,
                         /*optional out*/ CERTVerifyLog* verifyLog)
{
  ChainValidationCallbackState callbackState = { hostname,
                                                 mPinningEnforcementLevel,
                                                 usage,
                                                 time };

  if (mImplementation == mozillapkix) {
    return MozillaPKIXVerifyCert(cert, usage, time, pinArg, flags,
                                 stapledOCSPResponse, validationChain,
                                 evOidPolicy);
                                 &callbackState, stapledOCSPResponse,
                                 validationChain, evOidPolicy);
  }

  if (!cert)
@@ -581,7 +740,7 @@ CertVerifier::VerifyCert(CERTCertificate* cert,
  CERTChainVerifyCallback callbackContainer;
  if (usage == certificateUsageSSLServer) {
    callbackContainer.isChainValid = chainValidationCallback;
    callbackContainer.isChainValidArg = nullptr;
    callbackContainer.isChainValidArg = &callbackState;
    cvin[i].type = cert_pi_chainVerifyCallback;
    cvin[i].value.pointer.chainVerifyCallback = &callbackContainer;
    ++i;
@@ -685,8 +844,8 @@ CertVerifier::VerifyCert(CERTCertificate* cert,
  if (mImplementation == classic) {
    // XXX: we do not care about the localOnly flag (currently) as the
    // caller that wants localOnly should disable and reenable the fetching.
    return ClassicVerifyCert(cert, usage, time, pinArg, validationChain,
                             verifyLog);
    return ClassicVerifyCert(cert, usage, time, pinArg, &callbackState,
                             validationChain, verifyLog);
  }

#ifdef NSS_NO_LIBPKIX
@@ -759,6 +918,12 @@ CertVerifier::VerifyCert(CERTCertificate* cert,
  rv = CERT_PKIXVerifyCert(cert, usage, cvin, cvout, pinArg);

pkix_done:
  // If there is an error we may need to worsen to error to be a pinning failure
  if (rv != SECSuccess && usage == certificateUsageSSLServer &&
      PR_GetError() != SEC_ERROR_APPLICATION_CALLBACK_ERROR) {
    tryWorsenPRErrorInCallback(cert, &callbackState);
  }

  if (validationChain) {
    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: validation chain requested\n"));
    ScopedCERTCertificate trustAnchor(cvout[validationTrustAnchorLocation].value.pointer.cert);
@@ -826,9 +991,9 @@ CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert,
  // CreateCertErrorRunnable assumes that CERT_VerifyCertName is only called
  // if VerifyCert succeeded.
  ScopedCERTCertList validationChain;
  SECStatus rv = VerifyCert(peerCert, stapledOCSPResponse,
                            certificateUsageSSLServer, time,
                            pinarg, 0, &validationChain, evOidPolicy);
  SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg,
                            hostname, 0, stapledOCSPResponse, &validationChain,
                            evOidPolicy, nullptr);
  if (rv != SECSuccess) {
    return rv;
  }
Loading