chromium/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc

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

#include "chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/values.h"
#include "chrome/browser/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/certificate_provider/pin_dialog_manager.h"
#include "chrome/browser/certificate_provider/security_token_pin_dialog_host.h"
#include "chrome/common/extensions/api/certificate_provider.h"
#include "chrome/common/extensions/api/certificate_provider_internal.h"
#include "chromeos/components/security_token_pin/constants.h"
#include "extensions/browser/quota_service.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/ssl_private_key.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"

namespace {

namespace api_cp = ::extensions::api::certificate_provider;
namespace api_cpi = ::extensions::api::certificate_provider_internal;
using PinCodeType = ::chromeos::security_token_pin::CodeType;
using PinErrorLabel = ::chromeos::security_token_pin::ErrorLabel;
using RequestPinResult = ::chromeos::PinDialogManager::RequestPinResult;
using StopPinRequestResult = ::chromeos::PinDialogManager::StopPinRequestResult;

PinErrorLabel GetErrorLabelForDialog(api_cp::PinRequestErrorType error_type) {
  switch (error_type) {
    case api_cp::PinRequestErrorType::kInvalidPin:
      return PinErrorLabel::kInvalidPin;
    case api_cp::PinRequestErrorType::kInvalidPuk:
      return PinErrorLabel::kInvalidPuk;
    case api_cp::PinRequestErrorType::kMaxAttemptsExceeded:
      return PinErrorLabel::kMaxAttemptsExceeded;
    case api_cp::PinRequestErrorType::kUnknownError:
      return PinErrorLabel::kUnknown;
    case api_cp::PinRequestErrorType::kNone:
      return PinErrorLabel::kNone;
  }

  NOTREACHED_IN_MIGRATION();
  return PinErrorLabel::kNone;
}

}  // namespace

namespace extensions {

namespace {

const char kCertificateProviderErrorEmptyChain[] =
    "Certificate chain is empty.";
const char kCertificateProviderErrorChainTooLong[] =
    "Certificate chain should contain exactly one item.";
const char kCertificateProviderErrorInvalidX509Cert[] =
    "Certificate is not a valid X.509 certificate.";
const char kCertificateProviderErrorECDSANotSupported[] =
    "Key type ECDSA not supported.";
const char kCertificateProviderErrorUnknownKeyType[] = "Key type unknown.";
const char kCertificateProviderErrorAborted[] = "Request was aborted.";
const char kCertificateProviderErrorTimeout[] =
    "Request timed out, reply rejected.";
const char kCertificateProviderErrorInvalidId[] = "Invalid requestId";
const char kCertificateProviderErrorUnexpectedError[] =
    "Error supplied with non-empty data.";
const char kCertificateProviderErrorNeitherResultNorError[] =
    "Neither the result nor an error supplied.";
const char kCertificateProviderErrorNoAlgorithms[] = "Algorithm list is empty.";

// requestPin constants.
const char kCertificateProviderNoActiveDialog[] =
    "No active dialog from extension.";
const char kCertificateProviderInvalidSignId[] = "Invalid signRequestId";
const char kCertificateProviderInvalidAttemptsLeft[] = "Invalid attemptsLeft";
const char kCertificateProviderOtherFlowInProgress[] = "Other flow in progress";
const char kCertificateProviderPreviousDialogActive[] =
    "Previous request not finished";
const char kCertificateProviderNoUserInput[] = "No user input received";

// The BucketMapper implementation for the requestPin API that avoids using the
// quota when the current request uses the requestId that is strictly greater
// than all previous ones.
class RequestPinExceptFirstQuotaBucketMapper final
    : public QuotaLimitHeuristic::BucketMapper {
 public:
  RequestPinExceptFirstQuotaBucketMapper() = default;
  RequestPinExceptFirstQuotaBucketMapper(
      const RequestPinExceptFirstQuotaBucketMapper&) = delete;
  RequestPinExceptFirstQuotaBucketMapper& operator=(
      const RequestPinExceptFirstQuotaBucketMapper&) = delete;
  ~RequestPinExceptFirstQuotaBucketMapper() override = default;

  void GetBucketsForArgs(const base::Value::List& args,
                         QuotaLimitHeuristic::BucketList* buckets) override {
    if (args.empty())
      return;
    const base::Value::Dict* details = args.front().GetIfDict();
    if (!details)
      return;
    std::optional<int> sign_request_id = details->FindInt("signRequestId");
    if (!sign_request_id.has_value())
      return;
    if (*sign_request_id > biggest_request_id_) {
      // Either it's the first request with the newly issued requestId, or it's
      // an invalid requestId (bigger than the real one). Return a new bucket in
      // order to apply no quota for the former case; for the latter case the
      // quota doesn't matter much, except that we're maybe making it stricter
      // for future requests (which is bearable).
      biggest_request_id_ = *sign_request_id;
      new_request_bucket_ = std::make_unique<QuotaLimitHeuristic::Bucket>();
      buckets->push_back(new_request_bucket_.get());
      return;
    }
    // Either it's a repeatitive request for the given requestId, or the
    // extension reordered the requests. Fall back to the default bucket (shared
    // between all requests) in that case.
    buckets->push_back(&default_bucket_);
  }

 private:
  int biggest_request_id_ = -1;
  QuotaLimitHeuristic::Bucket default_bucket_;
  std::unique_ptr<QuotaLimitHeuristic::Bucket> new_request_bucket_;
};

scoped_refptr<net::X509Certificate> ParseCertificateDer(
    const std::vector<uint8_t>& cert_der,
    std::string* out_error_message) {
  if (cert_der.empty()) {
    *out_error_message = kCertificateProviderErrorInvalidX509Cert;
    return nullptr;
  }

  // Allow UTF-8 inside PrintableStrings in client certificates. See
  // crbug.com/770323 and crbug.com/788655.
  net::X509Certificate::UnsafeCreateOptions options;
  options.printable_string_is_utf8 = true;
  scoped_refptr<net::X509Certificate> certificate =
      net::X509Certificate::CreateFromBytesUnsafeOptions(cert_der, options);
  if (!certificate) {
    *out_error_message = kCertificateProviderErrorInvalidX509Cert;
    return nullptr;
  }

  size_t public_key_length_in_bits = 0;
  net::X509Certificate::PublicKeyType type =
      net::X509Certificate::kPublicKeyTypeUnknown;
  net::X509Certificate::GetPublicKeyInfo(certificate->cert_buffer(),
                                         &public_key_length_in_bits, &type);

  switch (type) {
    case net::X509Certificate::kPublicKeyTypeRSA:
      break;
    case net::X509Certificate::kPublicKeyTypeECDSA:
      *out_error_message = kCertificateProviderErrorECDSANotSupported;
      return nullptr;
    case net::X509Certificate::kPublicKeyTypeUnknown:
      *out_error_message = kCertificateProviderErrorUnknownKeyType;
      return nullptr;
  }
  return certificate;
}

bool ParseCertificateInfo(
    const api_cp::CertificateInfo& info,
    chromeos::certificate_provider::CertificateInfo* out_info,
    std::string* out_error_message) {
  out_info->certificate =
      ParseCertificateDer(info.certificate, out_error_message);
  if (!out_info->certificate)
    return false;

  out_info->supported_algorithms.reserve(info.supported_hashes.size());
  for (const api_cp::Hash hash : info.supported_hashes) {
    switch (hash) {
      case api_cp::Hash::kMd5Sha1:
        // Ignore `HASH_MD5_SHA1`. This is only used in TLS 1.0 and 1.1, which
        // we no longer support.
        break;
      case api_cp::Hash::kSha1:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA1);
        break;
      case api_cp::Hash::kSha256:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA256);
        break;
      case api_cp::Hash::kSha384:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA384);
        break;
      case api_cp::Hash::kSha512:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA512);
        break;
      case api_cp::Hash::kNone:
        NOTREACHED_IN_MIGRATION();
        return false;
    }
  }
  if (out_info->supported_algorithms.empty()) {
    *out_error_message = kCertificateProviderErrorNoAlgorithms;
    return false;
  }
  return true;
}

bool ParseClientCertificateInfo(
    const api_cp::ClientCertificateInfo& info,
    chromeos::certificate_provider::CertificateInfo* out_info,
    std::string* out_error_message) {
  if (info.certificate_chain.empty()) {
    *out_error_message = kCertificateProviderErrorEmptyChain;
    return false;
  }
  if (info.certificate_chain.size() > 1) {
    // TODO(crbug.com/40703788): Support passing certificate chains.
    *out_error_message = kCertificateProviderErrorChainTooLong;
    return false;
  }
  out_info->certificate =
      ParseCertificateDer(info.certificate_chain[0], out_error_message);
  if (!out_info->certificate)
    return false;

  out_info->supported_algorithms.reserve(info.supported_algorithms.size());
  for (const api_cp::Algorithm algorithm : info.supported_algorithms) {
    switch (algorithm) {
      case api_cp::Algorithm::kRsassaPkcs1V1_5Md5Sha1:
        // Ignore `ALGORITHM_RSASSA_PKCS1_V1_5_MD5_SHA1`. This is only used in
        // TLS 1.0 and 1.1, which we no longer support.
        break;
      case api_cp::Algorithm::kRsassaPkcs1V1_5Sha1:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA1);
        break;
      case api_cp::Algorithm::kRsassaPkcs1V1_5Sha256:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA256);
        break;
      case api_cp::Algorithm::kRsassaPkcs1V1_5Sha384:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA384);
        break;
      case api_cp::Algorithm::kRsassaPkcs1V1_5Sha512:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA512);
        break;
      case api_cp::Algorithm::kRsassaPssSha256:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PSS_RSAE_SHA256);
        break;
      case api_cp::Algorithm::kRsassaPssSha384:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PSS_RSAE_SHA384);
        break;
      case api_cp::Algorithm::kRsassaPssSha512:
        out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PSS_RSAE_SHA512);
        break;
      case api_cp::Algorithm::kNone:
        NOTREACHED_IN_MIGRATION();
        return false;
    }
  }
  if (out_info->supported_algorithms.empty()) {
    *out_error_message = kCertificateProviderErrorNoAlgorithms;
    return false;
  }
  return true;
}

}  // namespace

const int api::certificate_provider::kMaxClosedDialogsPerMinute = 10;
const int api::certificate_provider::kMaxClosedDialogsPer10Minutes = 30;

CertificateProviderInternalReportCertificatesFunction::
    ~CertificateProviderInternalReportCertificatesFunction() {}

ExtensionFunction::ResponseAction
CertificateProviderInternalReportCertificatesFunction::Run() {
  std::optional<api_cpi::ReportCertificates::Params> params =
      api_cpi::ReportCertificates::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  chromeos::CertificateProviderService* const service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          browser_context());
  DCHECK(service);

  if (!params->certificates) {
    // In the public API, the certificates parameter is mandatory. We only run
    // into this case, if the custom binding rejected the reply by the
    // extension.
    return RespondNow(Error(kCertificateProviderErrorAborted));
  }

  chromeos::certificate_provider::CertificateInfoList cert_infos;
  std::vector<std::vector<uint8_t>> rejected_certificates;
  for (const api_cp::CertificateInfo& input_cert_info : *params->certificates) {
    chromeos::certificate_provider::CertificateInfo parsed_cert_info;
    std::string error_message;
    if (ParseCertificateInfo(input_cert_info, &parsed_cert_info,
                             &error_message)) {
      cert_infos.push_back(parsed_cert_info);
    } else {
      rejected_certificates.push_back(input_cert_info.certificate);
      WriteToConsole(blink::mojom::ConsoleMessageLevel::kError, error_message);
    }
  }

  // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
  LOG(WARNING) << "Certificates provided by extension " << extension()->id()
               << ": " << cert_infos.size() << ", rejected "
               << rejected_certificates.size();

  service->SetCertificatesProvidedByExtension(extension_id(), cert_infos);

  if (service->SetExtensionCertificateReplyReceived(extension_id(),
                                                    params->request_id))
    return RespondNow(ArgumentList(
        api_cpi::ReportCertificates::Results::Create(rejected_certificates)));

  // The custom binding already checks for multiple reports to the same
  // request. The only remaining case, why this reply can fail is that the
  // request timed out.
  return RespondNow(Error(kCertificateProviderErrorTimeout));
}

CertificateProviderStopPinRequestFunction::
    ~CertificateProviderStopPinRequestFunction() = default;

ExtensionFunction::ResponseAction
CertificateProviderStopPinRequestFunction::Run() {
  std::optional<api_cp::StopPinRequest::Params> params =
      api_cp::StopPinRequest::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
  LOG(WARNING) << "Handling PIN stop request from extension "
               << extension()->id() << " error "
               << api_cp::ToString(params->details.error_type);

  chromeos::CertificateProviderService* const service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          browser_context());
  DCHECK(service);
  if (params->details.error_type == api_cp::PinRequestErrorType::kNone) {
    bool dialog_closed =
        service->pin_dialog_manager()->CloseDialog(extension_id());
    if (!dialog_closed) {
      // This might happen if the user closed the dialog while extension was
      // processing the input.
      // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
      LOG(WARNING) << "PIN stop request failed: "
                   << kCertificateProviderNoActiveDialog;
      return RespondNow(Error(kCertificateProviderNoActiveDialog));
    }

    // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
    LOG(WARNING) << "PIN stop request succeeded";
    return RespondNow(NoArguments());
  }

  // Extension provided an error, which means it intends to notify the user with
  // the error and not allow any more input.
  const PinErrorLabel error_label =
      GetErrorLabelForDialog(params->details.error_type);
  const StopPinRequestResult stop_request_result =
      service->pin_dialog_manager()->StopPinRequestWithError(
          extension()->id(), error_label,
          base::BindOnce(
              &CertificateProviderStopPinRequestFunction::OnPinRequestStopped,
              this));
  std::string error_result;
  switch (stop_request_result) {
    case StopPinRequestResult::kNoActiveDialog:
      error_result = kCertificateProviderNoActiveDialog;
      break;
    case StopPinRequestResult::kNoUserInput:
      error_result = kCertificateProviderNoUserInput;
      break;
    case StopPinRequestResult::kSuccess:
      return RespondLater();
  }
  // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
  LOG(WARNING) << "PIN stop request failed: " << error_result;
  return RespondNow(Error(std::move(error_result)));
}

void CertificateProviderStopPinRequestFunction::OnPinRequestStopped() {
  // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
  LOG(WARNING) << "PIN stop request succeeded";
  Respond(NoArguments());
}

CertificateProviderRequestPinFunction::
    ~CertificateProviderRequestPinFunction() {}

bool CertificateProviderRequestPinFunction::ShouldSkipQuotaLimiting() const {
  chromeos::CertificateProviderService* const service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          browser_context());
  DCHECK(service);

  return !service->pin_dialog_manager()->LastPinDialogClosed(extension_id());
}

void CertificateProviderRequestPinFunction::GetQuotaLimitHeuristics(
    QuotaLimitHeuristics* heuristics) const {
  // Apply a 1-minute and a 10-minute quotas. A special bucket mapper is used in
  // order to, approximately, skip applying quotas to the first request for each
  // requestId (such logic cannot be done in ShouldSkipQuotaLimiting(), since
  // it's not called with the request's parameters). The limitation constants
  // are decremented below to account the first request.

  QuotaLimitHeuristic::Config short_limit_config = {
      api::certificate_provider::kMaxClosedDialogsPerMinute - 1,
      base::Minutes(1)};
  heuristics->push_back(std::make_unique<QuotaService::TimedLimit>(
      short_limit_config,
      std::make_unique<RequestPinExceptFirstQuotaBucketMapper>(),
      "MAX_PIN_DIALOGS_CLOSED_PER_MINUTE"));

  QuotaLimitHeuristic::Config long_limit_config = {
      api::certificate_provider::kMaxClosedDialogsPer10Minutes - 1,
      base::Minutes(10)};
  heuristics->push_back(std::make_unique<QuotaService::TimedLimit>(
      long_limit_config,
      std::make_unique<RequestPinExceptFirstQuotaBucketMapper>(),
      "MAX_PIN_DIALOGS_CLOSED_PER_10_MINUTES"));
}

ExtensionFunction::ResponseAction CertificateProviderRequestPinFunction::Run() {
  std::optional<api_cp::RequestPin::Params> params =
      api_cp::RequestPin::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  const api_cp::PinRequestType pin_request_type =
      params->details.request_type == api_cp::PinRequestType::kNone
          ? api_cp::PinRequestType::kPin
          : params->details.request_type;

  const PinErrorLabel error_label =
      GetErrorLabelForDialog(params->details.error_type);

  const PinCodeType code_type =
      (pin_request_type == api_cp::PinRequestType::kPin) ? PinCodeType::kPin
                                                         : PinCodeType::kPuk;

  chromeos::CertificateProviderService* const service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          browser_context());
  DCHECK(service);

  int attempts_left = -1;
  if (params->details.attempts_left) {
    if (*params->details.attempts_left < 0)
      return RespondNow(Error(kCertificateProviderInvalidAttemptsLeft));
    attempts_left = *params->details.attempts_left;
  }

  // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
  LOG(WARNING) << "Starting PIN request from extension " << extension()->id()
               << " signRequestId " << params->details.sign_request_id
               << " type " << api_cp::ToString(params->details.request_type)
               << " error " << api_cp::ToString(params->details.error_type)
               << " attempts " << attempts_left;

  const RequestPinResult result = service->pin_dialog_manager()->RequestPin(
      extension()->id(), extension()->name(), params->details.sign_request_id,
      code_type, error_label, attempts_left,
      base::BindOnce(&CertificateProviderRequestPinFunction::OnInputReceived,
                     this));
  std::string error_result;
  switch (result) {
    case RequestPinResult::kSuccess:
      return RespondLater();
    case RequestPinResult::kInvalidId:
      error_result = kCertificateProviderInvalidSignId;
      break;
    case RequestPinResult::kOtherFlowInProgress:
      error_result = kCertificateProviderOtherFlowInProgress;
      break;
    case RequestPinResult::kDialogDisplayedAlready:
      error_result = kCertificateProviderPreviousDialogActive;
      break;
  }
  // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
  LOG(WARNING) << "PIN request failed: " << error_result;
  return RespondNow(Error(std::move(error_result)));
}

void CertificateProviderRequestPinFunction::OnInputReceived(
    const std::string& value) {
  base::Value::List create_results;
  chromeos::CertificateProviderService* const service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          browser_context());
  DCHECK(service);
  if (!value.empty()) {
    // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
    LOG(WARNING) << "PIN request succeeded";
    api::certificate_provider::PinResponseDetails details;
    details.user_input = value;
    create_results.Append(details.ToValue());
  } else {
    // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
    LOG(WARNING) << "PIN request canceled";
  }

  Respond(ArgumentList(std::move(create_results)));
}

CertificateProviderSetCertificatesFunction::
    ~CertificateProviderSetCertificatesFunction() = default;

ExtensionFunction::ResponseAction
CertificateProviderSetCertificatesFunction::Run() {
  std::optional<api_cp::SetCertificates::Params> params =
      api_cp::SetCertificates::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  if (!params->details.client_certificates.empty() &&
      params->details.error != api_cp::Error::kNone) {
    return RespondNow(Error(kCertificateProviderErrorUnexpectedError));
  }

  chromeos::certificate_provider::CertificateInfoList accepted_certificates;
  uint32_t rejected_certificates_count = 0;
  for (const api_cp::ClientCertificateInfo& input_cert_info :
       params->details.client_certificates) {
    chromeos::certificate_provider::CertificateInfo parsed_cert_info;
    std::string parsing_error_message;
    if (ParseClientCertificateInfo(input_cert_info, &parsed_cert_info,
                                   &parsing_error_message)) {
      accepted_certificates.push_back(parsed_cert_info);
    } else {
      rejected_certificates_count++;
      WriteToConsole(blink::mojom::ConsoleMessageLevel::kError,
                     parsing_error_message);
    }
  }

  // TODO(crbug.com/40671053): Remove logging after stabilizing the feature.
  LOG(WARNING) << "Certificates provided by extension " << extension()->id()
               << ": " << accepted_certificates.size() << ", rejected "
               << rejected_certificates_count;

  chromeos::CertificateProviderService* const service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          browser_context());
  DCHECK(service);
  service->SetCertificatesProvidedByExtension(extension_id(),
                                              accepted_certificates);

  if (params->details.certificates_request_id &&
      !service->SetExtensionCertificateReplyReceived(
          extension_id(), *params->details.certificates_request_id)) {
    // The extension supplied invalid request ID: it could be an unknown value,
    // or a value that was already reported before, or the request timed out.
    return RespondNow(Error(kCertificateProviderErrorInvalidId));
  }

  return RespondNow(NoArguments());
}

CertificateProviderInternalReportSignatureFunction::
    ~CertificateProviderInternalReportSignatureFunction() {}

ExtensionFunction::ResponseAction
CertificateProviderInternalReportSignatureFunction::Run() {
  std::optional<api_cpi::ReportSignature::Params> params =
      api_cpi::ReportSignature::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  chromeos::CertificateProviderService* const service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          browser_context());
  DCHECK(service);

  std::vector<uint8_t> signature;
  // If an error occurred, |signature| will not be set.
  if (params->signature)
    signature.assign(params->signature->begin(), params->signature->end());

  if (!service->ReplyToSignRequest(extension_id(), params->request_id,
                                   signature)) {
    // The request was aborted before, or the extension managed to bypass the
    // checks in the API bindings and specified a bad or an already used id.
    DLOG(WARNING) << "Unexpected reply of extension " << extension_id()
                  << " to sign request " << params->request_id;
  }
  return RespondNow(NoArguments());
}

CertificateProviderReportSignatureFunction::
    ~CertificateProviderReportSignatureFunction() = default;

ExtensionFunction::ResponseAction
CertificateProviderReportSignatureFunction::Run() {
  std::optional<api_cp::ReportSignature::Params> params =
      api_cp::ReportSignature::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  if (params->details.signature && !params->details.signature->empty() &&
      params->details.error != api_cp::Error::kNone) {
    return RespondNow(Error(kCertificateProviderErrorUnexpectedError));
  }
  if ((!params->details.signature || params->details.signature->empty()) &&
      params->details.error == api_cp::Error::kNone) {
    // It's not allowed to supply empty result without an error code.
    return RespondNow(Error(kCertificateProviderErrorNeitherResultNorError));
  }

  chromeos::CertificateProviderService* const service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          browser_context());
  DCHECK(service);

  std::vector<uint8_t> signature;
  // If an error occurred, |signature| will not be set.
  if (params->details.signature) {
    signature.assign(params->details.signature->begin(),
                     params->details.signature->end());
  }

  if (!service->ReplyToSignRequest(
          extension_id(), params->details.sign_request_id, signature)) {
    return RespondNow(Error(kCertificateProviderInvalidSignId));
  }
  return RespondNow(NoArguments());
}

}  // namespace extensions