security/apps/AppTrustDomain.cpp
author Dana Keeler <dkeeler@mozilla.com>
Mon, 06 May 2019 23:12:36 +0000
changeset 516455 5b264ffa56e752df9a66c8a781e06fde51ada9e8
parent 508163 6f3709b3878117466168c40affa7bca0b60cf75b
child 516456 96d2576eae4baf0aa961b4f5a1dadd26bb8ee823
permissions -rw-r--r--
bug 1549249 - hard-code new add-on signing intermediate so it's always available r=jcj,kmag a=tomprince 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 848b15028562c6757748070f637e0e4f0bbb5f65 (the patch that implemented the original approach) because it should no longer be necessary. This also bumps the add-on DB schema to trigger add-on revalidation. Differential Revision: https://phabricator.services.mozilla.com/D30140

/* -*- 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