/* -*- 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 "cert_storage/src/cert_storage.h"
// FIXME: these two must be included before certdb.h {
#include "seccomon.h"
#include "certt.h"
// }
#include "certdb.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozpkix/pkixnss.h"
#include "NSSCertDBTrustDomain.h"
#include "nsComponentManagerUtils.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIContentSignatureVerifier.h"
#include "nsIX509CertDB.h"
#include "nsNSSCertificate.h"
#include "nsNetUtil.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"
// Content signature root certificates
#include "content-signature-dev.inc"
#include "content-signature-local.inc"
#include "content-signature-prod.inc"
#include "content-signature-stage.inc"

using namespace mozilla::pkix;

extern mozilla::LazyLogModule gPIPNSSLog;

namespace mozilla {
namespace psm {

AppTrustDomain::AppTrustDomain(nsTArray<Span<const uint8_t>>&& collectedCerts)
    : mIntermediates(std::move(collectedCerts)),
      mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)) {}

nsresult AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot) {
  switch (trustedRoot) {
    case nsIX509CertDB::AppXPCShellRoot:
      mTrustedRoot = {xpcshellRoot};
      break;

    case nsIX509CertDB::AddonsPublicRoot:
      mTrustedRoot = {addonsPublicRoot};
      break;

    case nsIX509CertDB::AddonsStageRoot:
      mTrustedRoot = {addonsStageRoot};
      break;

    case nsIContentSignatureVerifier::ContentSignatureLocalRoot:
      mTrustedRoot = {contentSignatureLocalRoot};
      break;

    case nsIContentSignatureVerifier::ContentSignatureProdRoot:
      mTrustedRoot = {contentSignatureProdRoot};
      break;

    case nsIContentSignatureVerifier::ContentSignatureStageRoot:
      mTrustedRoot = {contentSignatureStageRoot};
      break;

    case nsIContentSignatureVerifier::ContentSignatureDevRoot:
      mTrustedRoot = {contentSignatureDevRoot};
      break;

    default:
      return NS_ERROR_INVALID_ARG;
  }

  // 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.
  if (trustedRoot == nsIX509CertDB::AddonsPublicRoot) {
    mAddonsIntermediate = {addonsPublicIntermediate};
  }

  return NS_OK;
}

pkix::Result AppTrustDomain::FindIssuer(Input encodedIssuerName,
                                        IssuerChecker& checker, Time) {
  MOZ_ASSERT(!mTrustedRoot.IsEmpty());
  if (mTrustedRoot.IsEmpty()) {
    return pkix::Result::FATAL_ERROR_INVALID_STATE;
  }

  nsTArray<Input> candidates;
  Input rootInput;
  pkix::Result rv =
      rootInput.Init(mTrustedRoot.Elements(), mTrustedRoot.Length());
  // This should never fail, since the possible roots are all hard-coded and
  // they should never be too long.
  if (rv != Success) {
    return rv;
  }
  candidates.AppendElement(std::move(rootInput));
  if (!mAddonsIntermediate.IsEmpty()) {
    Input intermediateInput;
    rv = intermediateInput.Init(mAddonsIntermediate.Elements(),
                                mAddonsIntermediate.Length());
    // Again, this should never fail for the same reason as above.
    if (rv != Success) {
      return rv;
    }
    candidates.AppendElement(std::move(intermediateInput));
  }
  for (const auto& intermediate : mIntermediates) {
    Input intermediateInput;
    rv = intermediateInput.Init(intermediate.Elements(), intermediate.Length());
    // This is untrusted input, so skip any intermediates that are too large.
    if (rv != Success) {
      continue;
    }
    candidates.AppendElement(std::move(intermediateInput));
  }

  for (const auto& candidate : candidates) {
    bool keepGoing;
    rv = checker.Check(candidate, nullptr /*additionalNameConstraints*/,
                       keepGoing);
    if (rv != Success) {
      return rv;
    }
    if (!keepGoing) {
      return Success;
    }
  }

  // If the above did not succeed in building a verified certificate chain,
  // fall back to searching for candidates in NSS. This is important in case an
  // intermediate involved in add-on signing expires before it is replaced. See
  // bug 1548973.
  SECItem encodedIssuerNameSECItem = UnsafeMapInputToSECItem(encodedIssuerName);
  UniqueCERTCertList nssCandidates(CERT_CreateSubjectCertList(
      nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameSECItem, 0, false));
  if (nssCandidates) {
    for (CERTCertListNode* n = CERT_LIST_HEAD(nssCandidates);
         !CERT_LIST_END(n, nssCandidates); n = CERT_LIST_NEXT(n)) {
      Input certDER;
      pkix::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;
}

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

  nsTArray<uint8_t> issuerBytes;
  nsTArray<uint8_t> serialBytes;
  nsTArray<uint8_t> subjectBytes;
  nsTArray<uint8_t> pubKeyBytes;

  pkix::Result result =
      BuildRevocationCheckArrays(candidateCertDER, endEntityOrCA, issuerBytes,
                                 serialBytes, subjectBytes, pubKeyBytes);
  if (result != Success) {
    return result;
  }

  int16_t revocationState;
  nsresult nsrv = mCertBlocklist->GetRevocationState(
      issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState);
  if (NS_FAILED(nsrv)) {
    return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
  }

  if (revocationState == nsICertStorage::STATE_ENFORCE) {
    return pkix::Result::ERROR_REVOKED_CERTIFICATE;
  }

  // mTrustedRoot is the only trust anchor for this validation.
  Span<const uint8_t> candidateCertDERSpan = {candidateCertDER.UnsafeGetData(),
                                              candidateCertDER.GetLength()};
  if (mTrustedRoot == candidateCertDERSpan) {
    trustLevel = TrustLevel::TrustAnchor;
    return Success;
  }

  trustLevel = TrustLevel::InheritsTrust;
  return Success;
}

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

pkix::Result AppTrustDomain::CheckRevocation(EndEntityOrCA, const CertID&, Time,
                                             Duration,
                                             /*optional*/ const Input*,
                                             /*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;
}

pkix::Result AppTrustDomain::IsChainValid(const DERArray& certChain, Time time,
                                          const CertPolicyId& requiredPolicy) {
  MOZ_ASSERT(requiredPolicy.IsAnyPolicy());
  return Success;
}

pkix::Result AppTrustDomain::CheckSignatureDigestAlgorithm(
    DigestAlgorithm digestAlg, EndEntityOrCA, Time) {
  switch (digestAlg) {
    case DigestAlgorithm::sha256:  // fall through
    case DigestAlgorithm::sha384:  // fall through
    case DigestAlgorithm::sha512:
      return Success;
    case DigestAlgorithm::sha1:
      return pkix::Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
  }
  return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
}

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

pkix::Result AppTrustDomain::VerifyRSAPKCS1SignedData(
    Input data, DigestAlgorithm digestAlgorithm, Input signature,
    Input subjectPublicKeyInfo) {
  // TODO: We should restrict signatures to SHA-256 or better.
  return VerifyRSAPKCS1SignedDataNSS(data, digestAlgorithm, signature,
                                     subjectPublicKeyInfo, nullptr);
}

pkix::Result AppTrustDomain::VerifyRSAPSSSignedData(
    Input data, DigestAlgorithm digestAlgorithm, Input signature,
    Input subjectPublicKeyInfo) {
  return VerifyRSAPSSSignedDataNSS(data, digestAlgorithm, signature,
                                   subjectPublicKeyInfo, nullptr);
}

pkix::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 pkix::Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
}

pkix::Result AppTrustDomain::VerifyECDSASignedData(
    Input data, DigestAlgorithm digestAlgorithm, Input signature,
    Input subjectPublicKeyInfo) {
  return VerifyECDSASignedDataNSS(data, digestAlgorithm, signature,
                                  subjectPublicKeyInfo, nullptr);
}

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

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

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

}  // namespace psm
}  // namespace mozilla
