/*
 * NSS utility functions
 *
 * 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 "net/third_party/nss/ssl/cmpcert.h"

#include <secder.h>
#include <secitem.h>

#include "base/strings/string_piece.h"
#include "net/cert/pki/parse_certificate.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/der/parser.h"
#include "third_party/boringssl/src/include/openssl/pool.h"

namespace net {

namespace {

bool GetIssuerAndSubject(CERTCertificate* cert,
                         der::Input* issuer,
                         der::Input* subject) {
  *issuer = der::Input(cert->derIssuer.data, cert->derIssuer.len);
  *subject = der::Input(cert->derSubject.data, cert->derSubject.len);
  return true;
}

bool GetIssuerAndSubject(X509Certificate* cert,
                         der::Input* issuer,
                         der::Input* subject) {
  der::Input tbs_certificate_tlv;
  der::Input signature_algorithm_tlv;
  der::BitString signature_value;
  if (!ParseCertificate(der::Input(CRYPTO_BUFFER_data(cert->cert_buffer()),
                                   CRYPTO_BUFFER_len(cert->cert_buffer())),
                        &tbs_certificate_tlv, &signature_algorithm_tlv,
                        &signature_value, nullptr)) {
    return false;
  }
  ParsedTbsCertificate tbs;
  if (!ParseTbsCertificate(tbs_certificate_tlv,
                           x509_util::DefaultParseCertificateOptions(), &tbs,
                           nullptr)) {
    return false;
  }

  *issuer = tbs.issuer_tlv;
  *subject = tbs.subject_tlv;
  return true;
}

}  // namespace

bool MatchClientCertificateIssuers(
    X509Certificate* cert,
    const std::vector<std::string>& cert_authorities,
    ScopedCERTCertificateList* intermediates) {
  // Bound how many iterations to try.
  static const int kMaxDepth = 20;

  intermediates->clear();

  // If no authorities are supplied, everything matches.
  if (cert_authorities.empty())
    return true;

  // DER encoded issuer and subject name of current certificate.
  der::Input issuer;
  der::Input subject;

  if (!GetIssuerAndSubject(cert, &issuer, &subject))
    return false;

  while (intermediates->size() < kMaxDepth) {
    // Check if current cert is issued by a valid CA.
    for (const std::string& ca : cert_authorities) {
      if (issuer == der::Input(&ca))
        return true;
    }

    // Stop at self-issued certificates.
    if (issuer == subject)
      return false;

    // Look the parent up in the database and keep searching.
    SECItem issuer_item;
    issuer_item.len = issuer.Length();
    issuer_item.data = const_cast<unsigned char*>(issuer.UnsafeData());
    ScopedCERTCertificate nextcert(
        CERT_FindCertByName(CERT_GetDefaultCertDB(), &issuer_item));
    if (!nextcert)
      return false;

    if (!GetIssuerAndSubject(nextcert.get(), &issuer, &subject))
      return false;

    intermediates->push_back(std::move(nextcert));
  }

  return false;
}

}  // namespace net
