/* Copyright (c) 2023, Google Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */

#include <openssl/pki/verify.h>

#include <assert.h>

#include <chrono>
#include <optional>
#include <string_view>

#include <openssl/base.h>
#include <openssl/bytestring.h>
#include <openssl/pool.h>

#include <openssl/pki/signature_verify_cache.h>

#include "cert_errors.h"
#include "cert_issuer_source_static.h"
#include "certificate_policies.h"
#include "common_cert_errors.h"
#include "encode_values.h"
#include "input.h"
#include "parse_certificate.h"
#include "parse_values.h"
#include "parsed_certificate.h"
#include "path_builder.h"
#include "simple_path_builder_delegate.h"
#include "trust_store.h"
#include "trust_store_in_memory.h"
#include "verify_certificate_chain.h"

namespace bssl {

namespace {

std::optional<std::shared_ptr<const ParsedCertificate>>
InternalParseCertificate(Span<const uint8_t> cert, std::string *out_diagnostic) {
  ParseCertificateOptions default_options{};
  // We follow Chromium in setting |allow_invalid_serial_numbers| in order to
  // not choke on 21-byte serial numbers, which are common.
  //
  // The reason for the discrepancy is that unsigned numbers with the high bit
  // otherwise set get an extra 0 byte in front to keep them positive. So if you
  // do:
  //    var num [20]byte
  //    fillWithRandom(num[:])
  //    serialNumber := new(big.Int).SetBytes(num[:])
  //    encodeASN1Integer(serialNumber)
  //
  // Then half of your serial numbers will be encoded with 21 bytes. (And
  // 1/512th will have 19 bytes instead of 20.)
  default_options.allow_invalid_serial_numbers = true;

  UniquePtr<CRYPTO_BUFFER> buffer(
      CRYPTO_BUFFER_new(cert.data(), cert.size(), nullptr));
  CertErrors errors;
  std::shared_ptr<const ParsedCertificate> parsed_cert(
      ParsedCertificate::Create(std::move(buffer), default_options, &errors));
  if (!parsed_cert) {
    *out_diagnostic = errors.ToDebugString();
    return {};
  }
  return parsed_cert;
}
}  // namespace


CertPool::CertPool() {}

CertificateVerifyOptions::CertificateVerifyOptions() {}

static std::unique_ptr<VerifyTrustStore> WrapTrustStore(
    std::unique_ptr<TrustStoreInMemory> trust_store) {
  std::unique_ptr<VerifyTrustStore> ret(new VerifyTrustStore);
  ret->trust_store = std::move(trust_store);
  return ret;
}

VerifyTrustStore::~VerifyTrustStore() {}

std::unique_ptr<VerifyTrustStore> VerifyTrustStore::FromDER(
    std::string_view der_certs, std::string *out_diagnostic) {
  auto store = std::make_unique<TrustStoreInMemory>();
  CBS cbs = StringAsBytes(der_certs);

  for (size_t cert_num = 1; CBS_len(&cbs) != 0; cert_num++) {
    CBS cert;
    if (!CBS_get_asn1_element(&cbs, &cert, CBS_ASN1_SEQUENCE)) {
      *out_diagnostic = "failed to get ASN.1 SEQUENCE from input at cert " +
                        std::to_string(cert_num);
      return {};
    }

    auto parsed_cert = InternalParseCertificate(
        Span(CBS_data(&cert), CBS_len(&cert)), out_diagnostic);
    if (!parsed_cert.has_value()) {
      return {};
    }
    store->AddTrustAnchor(parsed_cert.value());
  }

  return WrapTrustStore(std::move(store));
}

std::unique_ptr<VerifyTrustStore> VerifyTrustStore::FromDER(
    const std::vector<std::string_view> &der_roots,
    std::string *out_diagnostic) {
  auto store = std::make_unique<TrustStoreInMemory>();

  for (const std::string_view &cert : der_roots) {
    auto parsed_cert = InternalParseCertificate(StringAsBytes(cert), out_diagnostic);
    if (!parsed_cert.has_value()) {
      return {};
    }
    store->AddTrustAnchor(parsed_cert.value());
  }

  return WrapTrustStore(std::move(store));
}

CertPool::~CertPool() {}


std::unique_ptr<CertPool> CertPool::FromCerts(
    const std::vector<std::string_view> &der_certs,
    std::string *out_diagnostic) {
  auto pool = std::make_unique<CertPool>();
  pool->impl_ = std::make_unique<CertIssuerSourceStatic>();

  for (const std::string_view &cert : der_certs) {
    auto parsed_cert =
        InternalParseCertificate(StringAsBytes(cert), out_diagnostic);
    if (!parsed_cert.has_value()) {
      return {};
    }
    pool->impl_->AddCert(std::move(parsed_cert.value()));
  }

  return pool;
}

CertificateVerifyStatus::CertificateVerifyStatus() {}

size_t CertificateVerifyStatus::IterationCount() const {
  return iteration_count_;
}

size_t CertificateVerifyStatus::MaxDepthSeen() const { return max_depth_seen_; }

// PathBuilderDelegateImpl implements a deadline and allows for the
// use of a SignatureVerifyCache if an implementation is provided.
class PathBuilderDelegateImpl : public SimplePathBuilderDelegate {
 public:
  PathBuilderDelegateImpl(
      size_t min_rsa_modulus_length_bits, DigestPolicy digest_policy,
      std::chrono::time_point<std::chrono::steady_clock> deadline,
      SignatureVerifyCache *cache)
      : SimplePathBuilderDelegate(min_rsa_modulus_length_bits, digest_policy),
        deadline_(deadline),
        cache_(cache) {}

  bool IsDeadlineExpired() override {
    return (std::chrono::steady_clock::now() > deadline_);
  }

  SignatureVerifyCache *GetVerifyCache() override { return cache_; }

 private:
  const std::chrono::time_point<std::chrono::steady_clock> deadline_;
  SignatureVerifyCache *cache_;
};

std::optional<std::vector<std::vector<std::string>>> CertificateVerifyInternal(
    const CertificateVerifyOptions &opts, VerifyError *out_error,
    CertificateVerifyStatus *out_status, bool all_paths) {
  VerifyError dummy;
  if (!out_error) {
    out_error = &dummy;
  }
  if (out_status != nullptr) {
    out_status->iteration_count_ = 0;
    out_status->max_depth_seen_ = 0;
  }

  std::string diagnostic;
  std::optional<std::shared_ptr<const ParsedCertificate>> maybe_leaf =
      InternalParseCertificate(StringAsBytes(opts.leaf_cert), &diagnostic);

  if (!maybe_leaf.has_value()) {
    *out_error = {VerifyError::StatusCode::CERTIFICATE_INVALID, 0, diagnostic};
    return {};
  }
  std::shared_ptr<const ParsedCertificate> leaf_cert = maybe_leaf.value();

  int64_t now;
  if (opts.time.has_value()) {
    now = opts.time.value();
  } else {
    now = time(NULL);
  }

  der::GeneralizedTime verification_time;
  if (!der::EncodePosixTimeAsGeneralizedTime(now, &verification_time)) {
    *out_error = {VerifyError::StatusCode::VERIFICATION_FAILURE, -1,
                  "\nCould not encode verification time\n"};
    return {};
  }

  TrustStore *trust_store = nullptr;
  if (opts.trust_store) {
    trust_store = opts.trust_store->trust_store.get();
  }

  auto digest_policy = SimplePathBuilderDelegate::DigestPolicy::kStrong;
  // TODO(b/111551631): remove this
  if (opts.insecurely_allow_sha1) {
    digest_policy = SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1;
  }

  std::chrono::time_point<std::chrono::steady_clock> deadline =
      std::chrono::time_point<std::chrono::steady_clock>::max();
  if (opts.deadline.has_value()) {
    deadline = opts.deadline.value();
  }

  PathBuilderDelegateImpl path_builder_delegate(
      opts.min_rsa_modulus_length, digest_policy, deadline,
      opts.signature_verify_cache);

  KeyPurpose key_purpose = KeyPurpose::SERVER_AUTH;
  switch (opts.key_purpose) {
    case CertificateVerifyOptions::KeyPurpose::ANY_EKU:
      key_purpose = KeyPurpose::ANY_EKU;
      break;
    case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH:
      key_purpose = KeyPurpose::SERVER_AUTH;
      break;
    case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH:
      key_purpose = KeyPurpose::CLIENT_AUTH;
      break;
    case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH_STRICT:
      key_purpose = KeyPurpose::SERVER_AUTH_STRICT;
      break;
    case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH_STRICT:
      key_purpose = KeyPurpose::CLIENT_AUTH_STRICT;
      break;
    case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH_STRICT_LEAF:
      key_purpose = KeyPurpose::SERVER_AUTH_STRICT_LEAF;
      break;
    case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH_STRICT_LEAF:
      key_purpose = KeyPurpose::CLIENT_AUTH_STRICT_LEAF;
      break;
  }
  CertPathBuilder path_builder(leaf_cert, trust_store, &path_builder_delegate,
                               verification_time, key_purpose,
                               InitialExplicitPolicy::kFalse,
                               /* user_initial_policy_set= */
                               {der::Input(kAnyPolicyOid)},
                               InitialPolicyMappingInhibit::kFalse,
                               InitialAnyPolicyInhibit::kFalse);

  CertIssuerSourceStatic intermediates;
  for (const std::string_view &cert : opts.intermediates) {
    std::string diag_string;
    std::optional<std::shared_ptr<const ParsedCertificate>> parsed =
        InternalParseCertificate(StringAsBytes(cert), &diag_string);
    if (!parsed.has_value()) {
      if (path_builder_delegate.IsDebugLogEnabled()) {
        path_builder_delegate.DebugLog("skipping bad intermediate: " +
                                       diag_string);
      }
      continue;
    }
    intermediates.AddCert(std::move(parsed.value()));
  }
  path_builder.AddCertIssuerSource(&intermediates);

  if (opts.extra_intermediates != nullptr) {
    path_builder.AddCertIssuerSource(opts.extra_intermediates->impl_.get());
  }

  if (opts.max_iteration_count > 0) {
    path_builder.SetIterationLimit(opts.max_iteration_count);
  }

  if (opts.max_path_building_depth > 0) {
    path_builder.SetDepthLimit(opts.max_path_building_depth);
  }

  path_builder.SetExploreAllPaths(all_paths);

  CertPathBuilder::Result result = path_builder.Run();

  if (out_status != nullptr) {
    out_status->iteration_count_ = result.iteration_count;
    out_status->max_depth_seen_ = result.max_depth_seen;
  }

  *out_error = result.GetBestPathVerifyError();

  if (result.HasValidPath()) {
    std::vector<std::vector<std::string>> ret;
    if (!all_paths) {
      auto best_path = result.GetBestValidPath();
      ret.push_back(std::vector<std::string>());
      for (size_t i = 0; i < best_path->certs.size(); i++) {
        ret[0].emplace_back(BytesAsStringView(best_path->certs[i]->der_cert()));
      }
      return ret;
    }
    for (const auto &path : result.paths) {
      if (!path->IsValid()) {
        continue;
      }
      std::vector<std::string> ret_path;
      for (const auto &cert : path->certs) {
        ret_path.emplace_back(BytesAsStringView(cert->der_cert()));
      }
      ret.push_back(ret_path);
    }
    return ret;
  }

  return {};
}

std::optional<std::vector<std::string>> CertificateVerify(
    const CertificateVerifyOptions &opts, VerifyError *out_error,
    CertificateVerifyStatus *out_status) {
  auto single_path = CertificateVerifyInternal(opts, out_error, out_status,
                                               /*all_paths=*/false);
  if (!single_path.has_value()) {
    return {};
  }
  return single_path.value()[0];
}

std::optional<std::vector<std::vector<std::string>>> CertificateVerifyAllPaths(
    const CertificateVerifyOptions &opts) {
  return CertificateVerifyInternal(opts, nullptr, nullptr, /*all_paths=*/true);
}

}  // namespace bssl
