security/apps/AppTrustDomain.cpp
author Dana Keeler <dkeeler@mozilla.com>
Mon, 06 May 2019 10:42:52 -0700
changeset 531478 c52835481c084fecff479ddf35e06054c5e0ba32
parent 505383 6f3709b3878117466168c40affa7bca0b60cf75b
child 523509 c8de3e36fb00966f1412d7dfddb5bfb08d45b816
permissions -rw-r--r--
bug 1549249 - hard-code new add-on signing intermediate so it's always available r=jcj,kmag a=ryanvm Summary: Our previous approach to making this intermediate available relied on being able to add it to the user's NSS cert DB. This does work in the majority of cases, but there are some situations where it doesn't work (e.g. if the user's DB is set to read only, if they've configured Firefox to run in "nocertdb" mode, if they have a master password but forgot it, and so on). This patch compiles the intermediate in to Firefox in the same way we incorporate the root, so it should always be available. At the same time, this patch reverts the changes from 023dd959512e2cfa685187616560f91efa91183c and 1d35f8d88bdd007e01d42c4ff76c6d10d7c01a98 (the patches that implemented the original approach) because they should no longer be necessary. Reviewers: jcj!, kmag! Tags: #secure-revision Bug #: 1549249 Differential Revision: https://phabricator.services.mozilla.com/D30090

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "AppTrustDomain.h"

#include "MainThreadUtils.h"
#include "certdb.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "mozilla/Preferences.h"
#include "nsComponentManagerUtils.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsIX509CertDB.h"
#include "nsNSSCertificate.h"
#include "nsNetUtil.h"
#include "mozpkix/pkixnss.h"
#include "prerror.h"

// Generated by gen_cert_header.py, which gets called by the build system.
#include "xpcshell.inc"
// Add-on signing Certificates
#include "addons-public.inc"
#include "addons-public-intermediate.inc"
#include "addons-stage.inc"
// Privileged Package Certificates
#include "privileged-package-root.inc"

using namespace mozilla::pkix;

extern mozilla::LazyLogModule gPIPNSSLog;

static char kDevImportedDER[] = "network.http.signed-packages.developer-root";

namespace mozilla {
namespace psm {

StaticMutex AppTrustDomain::sMutex;
UniquePtr<unsigned char[]> AppTrustDomain::sDevImportedDERData;
unsigned int AppTrustDomain::sDevImportedDERLen = 0;

AppTrustDomain::AppTrustDomain(UniqueCERTCertList& certChain, void* pinArg)
    : mCertChain(certChain), mPinArg(pinArg) {}

nsresult AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot) {
  SECItem trustedDER;

  // Load the trusted certificate into the in-memory NSS database so that
  // CERT_CreateSubjectCertList can find it.

  switch (trustedRoot) {
    case nsIX509CertDB::AppXPCShellRoot:
      trustedDER.data = const_cast<uint8_t*>(xpcshellRoot);
      trustedDER.len = mozilla::ArrayLength(xpcshellRoot);
      break;

    case nsIX509CertDB::AddonsPublicRoot:
      trustedDER.data = const_cast<uint8_t*>(addonsPublicRoot);
      trustedDER.len = mozilla::ArrayLength(addonsPublicRoot);
      break;

    case nsIX509CertDB::AddonsStageRoot:
      trustedDER.data = const_cast<uint8_t*>(addonsStageRoot);
      trustedDER.len = mozilla::ArrayLength(addonsStageRoot);
      break;

    case nsIX509CertDB::PrivilegedPackageRoot:
      trustedDER.data = const_cast<uint8_t*>(privilegedPackageRoot);
      trustedDER.len = mozilla::ArrayLength(privilegedPackageRoot);
      break;

    case nsIX509CertDB::DeveloperImportedRoot: {
      StaticMutexAutoLock lock(sMutex);
      if (!sDevImportedDERData) {
        MOZ_ASSERT(!NS_IsMainThread());
        nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
        if (!file) {
          return NS_ERROR_FAILURE;
        }
        nsAutoCString path;
        Preferences::GetCString(kDevImportedDER, path);
        nsresult rv = file->InitWithNativePath(path);
        if (NS_FAILED(rv)) {
          return rv;
        }

        nsCOMPtr<nsIInputStream> inputStream;
        rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file, -1,
                                        -1, nsIFileInputStream::CLOSE_ON_EOF);
        if (NS_FAILED(rv)) {
          return rv;
        }

        uint64_t length;
        rv = inputStream->Available(&length);
        if (NS_FAILED(rv)) {
          return rv;
        }

        auto data = MakeUnique<char[]>(length);
        rv = inputStream->Read(data.get(), length, &sDevImportedDERLen);
        if (NS_FAILED(rv)) {
          return rv;
        }

        MOZ_ASSERT(length == sDevImportedDERLen);
        sDevImportedDERData.reset(
            BitwiseCast<unsigned char*, char*>(data.release()));
      }

      trustedDER.data = sDevImportedDERData.get();
      trustedDER.len = sDevImportedDERLen;
      break;
    }

    default:
      return NS_ERROR_INVALID_ARG;
  }

  mTrustedRoot.reset(CERT_NewTempCertificate(
      CERT_GetDefaultCertDB(), &trustedDER, nullptr, false, true));
  if (!mTrustedRoot) {
    return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
  }

  // If we're verifying add-ons signed by our production root, we want to make
  // sure a valid intermediate certificate is available for path building.
  // Merely holding this alive in memory makes it available for NSS to find in
  // AppTrustDomain::FindIssuer.
  if (trustedRoot == nsIX509CertDB::AddonsPublicRoot) {
    SECItem intermediateDER = {
        siBuffer,
        const_cast<uint8_t*>(addonsPublicIntermediate),
        mozilla::ArrayLength(addonsPublicIntermediate),
    };
    mAddonsIntermediate.reset(CERT_NewTempCertificate(
        CERT_GetDefaultCertDB(), &intermediateDER, nullptr, false, true));
    if (!mAddonsIntermediate) {
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
    }
  }

  return NS_OK;
}

Result AppTrustDomain::FindIssuer(Input encodedIssuerName,
                                  IssuerChecker& checker, Time)

{
  MOZ_ASSERT(mTrustedRoot);
  if (!mTrustedRoot) {
    return Result::FATAL_ERROR_INVALID_STATE;
  }

  // TODO(bug 1035418): If/when mozilla::pkix relaxes the restriction that
  // FindIssuer must only pass certificates with a matching subject name to
  // checker.Check, we can stop using CERT_CreateSubjectCertList and instead
  // use logic like this:
  //
  // 1. First, try the trusted trust anchor.
  // 2. Secondly, iterate through the certificates that were stored in the CMS
  //    message, passing each one to checker.Check.
  SECItem encodedIssuerNameSECItem = UnsafeMapInputToSECItem(encodedIssuerName);
  UniqueCERTCertList candidates(CERT_CreateSubjectCertList(
      nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameSECItem, 0, false));
  if (candidates) {
    for (CERTCertListNode* n = CERT_LIST_HEAD(candidates);
         !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
      Input certDER;
      Result rv = certDER.Init(n->cert->derCert.data, n->cert->derCert.len);
      if (rv != Success) {
        continue;  // probably too big
      }

      bool keepGoing;
      rv = checker.Check(certDER, nullptr /*additionalNameConstraints*/,
                         keepGoing);
      if (rv != Success) {
        return rv;
      }
      if (!keepGoing) {
        break;
      }
    }
  }

  return Success;
}

Result AppTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
                                    const CertPolicyId& policy,
                                    Input candidateCertDER,
                                    /*out*/ TrustLevel& trustLevel) {
  MOZ_ASSERT(policy.IsAnyPolicy());
  MOZ_ASSERT(mTrustedRoot);
  if (!policy.IsAnyPolicy()) {
    return Result::FATAL_ERROR_INVALID_ARGS;
  }
  if (!mTrustedRoot) {
    return Result::FATAL_ERROR_INVALID_STATE;
  }

  // Handle active distrust of the certificate.

  // XXX: This would be cleaner and more efficient if we could get the trust
  // information without constructing a CERTCertificate here, but NSS doesn't
  // expose it in any other easy-to-use fashion.
  SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER);
  UniqueCERTCertificate candidateCert(CERT_NewTempCertificate(
      CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, true));
  if (!candidateCert) {
    return MapPRErrorCodeToResult(PR_GetError());
  }

  CERTCertTrust trust;
  if (CERT_GetCertTrust(candidateCert.get(), &trust) == SECSuccess) {
    uint32_t flags = SEC_GET_TRUST_FLAGS(&trust, trustObjectSigning);

    // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit,
    // because we can have active distrust for either type of cert. Note that
    // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the
    // relevant trust bit isn't set then that means the cert must be considered
    // distrusted.
    uint32_t relevantTrustBit = endEntityOrCA == EndEntityOrCA::MustBeCA
                                    ? CERTDB_TRUSTED_CA
                                    : CERTDB_TRUSTED;
    if (((flags & (relevantTrustBit | CERTDB_TERMINAL_RECORD))) ==
        CERTDB_TERMINAL_RECORD) {
      trustLevel = TrustLevel::ActivelyDistrusted;
      return Success;
    }
  }

  // mTrustedRoot is the only trust anchor for this validation.
  if (CERT_CompareCerts(mTrustedRoot.get(), candidateCert.get())) {
    trustLevel = TrustLevel::TrustAnchor;
    return Success;
  }

  trustLevel = TrustLevel::InheritsTrust;
  return Success;
}

Result AppTrustDomain::DigestBuf(Input item, DigestAlgorithm digestAlg,
                                 /*out*/ uint8_t* digestBuf,
                                 size_t digestBufLen) {
  return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
}

Result AppTrustDomain::CheckRevocation(EndEntityOrCA, const CertID&, Time,
                                       Duration,
                                       /*optional*/ const Input*,
                                       /*optional*/ const Input*) {
  // We don't currently do revocation checking. If we need to distrust an Apps
  // certificate, we will use the active distrust mechanism.
  return Success;
}

Result AppTrustDomain::IsChainValid(const DERArray& certChain, Time time,
                                    const CertPolicyId& requiredPolicy) {
  MOZ_ASSERT(requiredPolicy.IsAnyPolicy());
  SECStatus srv =
      ConstructCERTCertListFromReversedDERArray(certChain, mCertChain);
  if (srv != SECSuccess) {
    return MapPRErrorCodeToResult(PR_GetError());
  }
  return Success;
}

Result AppTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm,
                                                     EndEntityOrCA, Time) {
  // TODO: We should restrict signatures to SHA-256 or better.
  return Success;
}

Result AppTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
    EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits) {
  if (modulusSizeInBits < 2048u) {
    return Result::ERROR_INADEQUATE_KEY_SIZE;
  }
  return Success;
}

Result AppTrustDomain::VerifyRSAPKCS1SignedDigest(
    const SignedDigest& signedDigest, Input subjectPublicKeyInfo) {
  // TODO: We should restrict signatures to SHA-256 or better.
  return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
                                       mPinArg);
}

Result AppTrustDomain::CheckECDSACurveIsAcceptable(
    EndEntityOrCA /*endEntityOrCA*/, NamedCurve curve) {
  switch (curve) {
    case NamedCurve::secp256r1:  // fall through
    case NamedCurve::secp384r1:  // fall through
    case NamedCurve::secp521r1:
      return Success;
  }

  return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
}

Result AppTrustDomain::VerifyECDSASignedDigest(const SignedDigest& signedDigest,
                                               Input subjectPublicKeyInfo) {
  return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo,
                                    mPinArg);
}

Result AppTrustDomain::CheckValidityIsAcceptable(
    Time /*notBefore*/, Time /*notAfter*/, EndEntityOrCA /*endEntityOrCA*/,
    KeyPurposeId /*keyPurpose*/) {
  return Success;
}

Result AppTrustDomain::NetscapeStepUpMatchesServerAuth(Time /*notBefore*/,
                                                       /*out*/ bool& matches) {
  matches = false;
  return Success;
}

void AppTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension /*extension*/,
                                            Input /*extensionData*/) {}

}  // namespace psm
}  // namespace mozilla