// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/cert/trial_comparison_cert_verifier_util.h"

#include "build/build_config.h"
#include "crypto/sha2.h"
#include "net/base/hash_value.h"
#include "net/base/net_errors.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/ev_root_ca_metadata.h"
#include "net/cert/pki/cert_errors.h"
#include "net/cert/pki/parsed_certificate.h"
#include "net/cert/x509_util.h"

namespace net {

namespace {

std::shared_ptr<const ParsedCertificate> ParsedCertificateFromBuffer(
    CRYPTO_BUFFER* cert_handle,
    CertErrors* errors) {
  return ParsedCertificate::Create(bssl::UpRef(cert_handle),
                                   x509_util::DefaultParseCertificateOptions(),
                                   errors);
}

ParsedCertificateList ParsedCertificateListFromX509Certificate(
    const X509Certificate* cert) {
  CertErrors parsing_errors;

  ParsedCertificateList certs;
  std::shared_ptr<const ParsedCertificate> target =
      ParsedCertificateFromBuffer(cert->cert_buffer(), &parsing_errors);
  if (!target)
    return {};
  certs.push_back(target);

  for (const auto& buf : cert->intermediate_buffers()) {
    std::shared_ptr<const ParsedCertificate> intermediate =
        ParsedCertificateFromBuffer(buf.get(), &parsing_errors);
    if (!intermediate)
      return {};
    certs.push_back(intermediate);
  }

  return certs;
}

// Tests whether cert has multiple EV policies, and at least one matches the
// root. This is not a complete test of EV, but just enough to give a possible
// explanation as to why the platform verifier did not validate as EV while
// builtin did. (Since only the builtin verifier correctly handles multiple
// candidate EV policies.)
bool CertHasMultipleEVPoliciesAndOneMatchesRoot(const X509Certificate* cert) {
  if (cert->intermediate_buffers().empty())
    return false;

  ParsedCertificateList certs = ParsedCertificateListFromX509Certificate(cert);
  if (certs.empty())
    return false;

  const ParsedCertificate* leaf = certs.front().get();
  const ParsedCertificate* root = certs.back().get();

  if (!leaf->has_policy_oids())
    return false;

  const EVRootCAMetadata* ev_metadata = EVRootCAMetadata::GetInstance();
  std::set<der::Input> candidate_oids;
  for (const der::Input& oid : leaf->policy_oids()) {
    if (ev_metadata->IsEVPolicyOID(oid)) {
      candidate_oids.insert(oid);
    }
  }

  if (candidate_oids.size() <= 1)
    return false;

  SHA256HashValue root_fingerprint;
  crypto::SHA256HashString(root->der_cert().AsStringView(),
                           root_fingerprint.data,
                           sizeof(root_fingerprint.data));

  for (const der::Input& oid : candidate_oids) {
    if (ev_metadata->HasEVPolicyOID(root_fingerprint, oid)) {
      return true;
    }
  }

  return false;
}

SHA256HashValue GetRootHash(const X509Certificate* cert) {
  SHA256HashValue sha256;
  if (cert->intermediate_buffers().empty()) {
    return sha256;
  }
  CRYPTO_BUFFER* root = cert->intermediate_buffers().back().get();
  return X509Certificate::CalculateFingerprint256(root);
}

const SHA256HashValue lets_encrypt_dst_x3_sha256_fingerprint = {
    {0x06, 0x87, 0x26, 0x03, 0x31, 0xA7, 0x24, 0x03, 0xD9, 0x09, 0xF1,
     0x05, 0xE6, 0x9B, 0xCF, 0x0D, 0x32, 0xE1, 0xBD, 0x24, 0x93, 0xFF,
     0xC6, 0xD9, 0x20, 0x6D, 0x11, 0xBC, 0xD6, 0x77, 0x07, 0x39}};

const SHA256HashValue lets_encrypt_isrg_x1_sha256_fingerprint = {
    {0x96, 0xBC, 0xEC, 0x06, 0x26, 0x49, 0x76, 0xF3, 0x74, 0x60, 0x77,
     0x9A, 0xCF, 0x28, 0xC5, 0xA7, 0xCF, 0xE8, 0xA3, 0xC0, 0xAA, 0xE1,
     0x1A, 0x8F, 0xFC, 0xEE, 0x05, 0xC0, 0xBD, 0xDF, 0x08, 0xC6}};

}  // namespace

// Note: This ignores the result of stapled OCSP (which is the same for both
// verifiers) and informational statuses about the certificate algorithms and
// the hashes, since they will be the same if the certificate chains are the
// same.
bool CertVerifyResultEqual(const CertVerifyResult& a,
                           const CertVerifyResult& b) {
  return std::tie(a.cert_status, a.is_issued_by_known_root) ==
             std::tie(b.cert_status, b.is_issued_by_known_root) &&
         (!!a.verified_cert == !!b.verified_cert) &&
         (!a.verified_cert ||
          a.verified_cert->EqualsIncludingChain(b.verified_cert.get()));
}

TrialComparisonResult IsSynchronouslyIgnorableDifference(
    int primary_error,
    const CertVerifyResult& primary_result,
    int trial_error,
    const CertVerifyResult& trial_result,
    bool sha1_local_anchors_enabled) {
  DCHECK(primary_result.verified_cert);
  DCHECK(trial_result.verified_cert);

  const bool chains_equal = primary_result.verified_cert->EqualsIncludingChain(
      trial_result.verified_cert.get());

  if (chains_equal && (trial_result.cert_status & CERT_STATUS_IS_EV) &&
      !(primary_result.cert_status & CERT_STATUS_IS_EV) &&
      (primary_error == trial_error)) {
    // The platform CertVerifyProc impls only check a single potential EV
    // policy from the leaf.  If the leaf had multiple policies, builtin
    // verifier may verify it as EV when the platform verifier did not.
    if (CertHasMultipleEVPoliciesAndOneMatchesRoot(
            trial_result.verified_cert.get())) {
      return TrialComparisonResult::kIgnoredMultipleEVPoliciesAndOneMatchesRoot;
    }
  }

  // SHA-1 signatures are not supported; ignore any results with expected SHA-1
  // errors. There are however a few cases with SHA-1 signatures where we might
  // want to see the difference:
  //
  //  * local anchors enabled, and one verifier built to a SHA-1 local root but
  //     the other built to a known root.
  //  * If a verifier returned a SHA-1 signature status but did not return an
  //    error.
  if (!(sha1_local_anchors_enabled &&
        (!primary_result.is_issued_by_known_root ||
         !trial_result.is_issued_by_known_root)) &&
      (primary_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT) &&
      (trial_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT) &&
      primary_error != OK && trial_error != OK) {
    return TrialComparisonResult::kIgnoredSHA1SignaturePresent;
  }

  // Differences in chain or errors don't matter much if both
  // return AUTHORITY_INVALID.
  if ((primary_result.cert_status & CERT_STATUS_AUTHORITY_INVALID) &&
      (trial_result.cert_status & CERT_STATUS_AUTHORITY_INVALID)) {
    return TrialComparisonResult::kIgnoredBothAuthorityInvalid;
  }

  // Due to differences in path building preferences we may end up with
  // different chains in cross-signing situations. These cases are ignorable if
  // the errors are equivalent and both chains end up at a known_root.
  if (!chains_equal && (primary_error == trial_error) &&
      primary_result.is_issued_by_known_root &&
      trial_result.is_issued_by_known_root &&
      (primary_result.cert_status == trial_result.cert_status)) {
    return TrialComparisonResult::kIgnoredBothKnownRoot;
  }

  // If the primary has an error and cert_status reports that a Symantec legacy
  // cert is present, ignore the error if trial reports
  // ERR_CERT_AUTHORITY_INVALID as trial will report AUTHORITY_INVALID and short
  // circuits other checks resulting in mismatching errors and cert status.
  if (primary_error != OK && trial_error == ERR_CERT_AUTHORITY_INVALID &&
      (primary_result.cert_status & CERT_STATUS_SYMANTEC_LEGACY)) {
    return TrialComparisonResult::
        kIgnoredBuiltinAuthorityInvalidPlatformSymantec;
  }

  // There is a fairly prevalant false positive where Windows users are getting
  // errors because the chain that is built goes to Lets Encrypt's old root
  // (https://crt.sh/?id=8395) due to the windows machine having an out of date
  // auth root, whereas CCV builds to Let's Encrypt's new root
  // (https://crt.sh/?id=9314791). This manifests itself as CCV saying OK
  // whereas platform reports DATE_INVALID. If we detect this case, ignore it.
  if (primary_error == ERR_CERT_DATE_INVALID && trial_error == OK &&
      (primary_result.cert_status & CERT_STATUS_ALL_ERRORS) ==
          CERT_STATUS_DATE_INVALID) {
    SHA256HashValue primary_root_hash =
        GetRootHash(primary_result.verified_cert.get());
    SHA256HashValue trial_root_hash =
        GetRootHash(trial_result.verified_cert.get());
    if (primary_root_hash == lets_encrypt_dst_x3_sha256_fingerprint &&
        trial_root_hash == lets_encrypt_isrg_x1_sha256_fingerprint) {
      return TrialComparisonResult::kIgnoredLetsEncryptExpiredRoot;
    }
  }

#if BUILDFLAG(IS_ANDROID)
  // In the case where a cert is expired and does not have a trusted root,
  // Android prefers ERR_CERT_DATE_INVALID whereas builtin prefers
  // ERR_CERT_AUTHORITY_INVALID.
  if (primary_error == ERR_CERT_DATE_INVALID &&
      trial_error == ERR_CERT_AUTHORITY_INVALID &&
      (primary_result.cert_status & CERT_STATUS_ALL_ERRORS) ==
          CERT_STATUS_DATE_INVALID &&
      (trial_result.cert_status & CERT_STATUS_ALL_ERRORS) ==
          CERT_STATUS_AUTHORITY_INVALID) {
    return TrialComparisonResult::kIgnoredAndroidErrorDatePriority;
  }
#endif

  return TrialComparisonResult::kInvalid;
}

}  // namespace net