chromium/chrome/browser/ash/platform_keys/platform_keys_service_nss.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include <cert.h>
#include <certdb.h>
#include <cryptohi.h>
#include <keyhi.h>
#include <pk11pub.h>
#include <pkcs11t.h>
#include <secder.h>
#include <secerr.h>
#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <optional>
#include <string_view>
#include <utility>

#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/net/client_cert_store_ash.h"
#include "chrome/browser/ash/platform_keys/platform_keys_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_ash.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys.h"
#include "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h"
#include "chromeos/ash/components/chaps_util/chaps_util.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_key_util.h"
#include "crypto/openssl_util.h"
#include "crypto/scoped_nss_types.h"
#include "net/base/net_errors.h"
#include "net/cert/asn1_util.h"
#include "net/cert/cert_database.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/cert/x509_util_nss.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
#include "third_party/cros_system_api/constants/pkcs11_custom_attributes.h"

namespace ash::platform_keys {

void RunCallBackIfCallableElseRunCleanUp(base::OnceCallback<void()> callback,
                                         base::OnceCallback<void()> cleanup) {
  if (!callback.IsCancelled()) {
    return std::move(callback).Run();
  }
  if (!cleanup.IsCancelled()) {
    return std::move(cleanup).Run();
  }
  // else: TODO(b/280048774): Handle RemoveKey case when PlatformService
  // is not here.
}

namespace {

using ServiceWeakPtr = ::base::WeakPtr<PlatformKeysServiceImpl>;
using ::chromeos::platform_keys::HashAlgorithm;
using ::chromeos::platform_keys::KeyAttributeType;
using ::chromeos::platform_keys::KeyType;
using ::chromeos::platform_keys::OperationType;
using ::chromeos::platform_keys::Status;
using ::chromeos::platform_keys::SymKeyType;
using ::chromeos::platform_keys::TokenId;
using ::content::BrowserContext;
using ::content::BrowserThread;

// The current maximal RSA modulus length that ChromeOS's TPM supports for key
// generation.
const unsigned int kMaxRSAModulusLengthBits = 2048;

// Default signature length for signing with symmetric keys.
const int kDefaultSymSignatureLength = 32;

// Returns a vector containing bytes from `value` or an empty vector if `value`
// is nullptr.
std::vector<uint8_t> ScopedSECItemToBytes(const crypto::ScopedSECItem& value) {
  return value ? std::vector<uint8_t>(value->data, value->data + value->len)
               : std::vector<uint8_t>();
}

// Base class to store state that is common to all NSS database operations and
// to provide convenience methods to call back.
// Keeps track of the originating task runner.
class NSSOperationState {
 public:
  explicit NSSOperationState(ServiceWeakPtr weak_ptr)
      : service_weak_ptr_(weak_ptr) {}

  NSSOperationState(const NSSOperationState&) = delete;
  NSSOperationState& operator=(const NSSOperationState&) = delete;

  virtual ~NSSOperationState() = default;

  // Called if an error occurred during the execution of the NSS operation
  // described by this object.
  virtual void OnError(const base::Location& from, Status status) = 0;

  static void RunCallback(base::OnceClosure callback, ServiceWeakPtr weak_ptr) {
    if (weak_ptr) {
      std::move(callback).Run();
    }
  }
  crypto::ScopedPK11Slot slot_;

  // Weak pointer to the PlatformKeysServiceImpl that created this state. Used
  // to check if the callback should be still called.
  ServiceWeakPtr service_weak_ptr_;
};

using GetCertDBCallback =
    base::OnceCallback<void(net::NSSCertDatabase* cert_db)>;

// Called on the UI thread with certificate database.
void DidGetCertDbOnUiThread(std::optional<TokenId> token_id,
                            GetCertDBCallback callback,
                            NSSOperationState* state,
                            net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!cert_db) {
    LOG(ERROR) << "Couldn't get NSSCertDatabase.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  if (token_id) {
    switch (token_id.value()) {
      case TokenId::kUser:
        state->slot_ = cert_db->GetPrivateSlot();
        break;
      case TokenId::kSystem:
        state->slot_ = cert_db->GetSystemSlot();
        break;
    }

    if (!state->slot_) {
      LOG(ERROR) << "Slot for token id '" << static_cast<int>(token_id.value())
                 << "' not available.";
      state->OnError(FROM_HERE, Status::kErrorInternal);
      return;
    }
  }

  // Sets |slot_| of |state| accordingly and calls |callback| on the IO thread
  // if the database was successfully retrieved.
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), cert_db));
}

// Asynchronously fetches the NSSCertDatabase using |delegate| and, if
// |token_id| is not empty, the slot for |token_id|. Stores the slot in |state|
// and passes the database to |callback|. Will run |callback| on the IO thread.
void GetCertDatabase(std::optional<TokenId> token_id,
                     GetCertDBCallback callback,
                     PlatformKeysServiceImplDelegate* delegate,
                     NSSOperationState* state) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  delegate->GetNSSCertDatabase(base::BindOnce(&DidGetCertDbOnUiThread, token_id,
                                              std::move(callback), state));
}

class GenerateRSAKeyState : public NSSOperationState {
 public:
  GenerateRSAKeyState(ServiceWeakPtr weak_ptr,
                      unsigned int modulus_length_bits,
                      bool sw_backed,
                      TokenId token_id,
                      GenerateKeyCallback callback)
      : NSSOperationState(weak_ptr),
        modulus_length_bits_(modulus_length_bits),
        sw_backed_(sw_backed),
        token_id_(token_id),
        callback_(std::move(callback)) {}

  ~GenerateRSAKeyState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*public_key_spki_der=*/std::vector<uint8_t>(), status);
  }

  void OnSuccess(const base::Location& from,
                 std::vector<uint8_t> public_key_spki_der) {
    CallBack(from, std::move(public_key_spki_der), Status::kSuccess);
  }

  const unsigned int modulus_length_bits_;
  const bool sw_backed_;
  TokenId token_id_;

 private:
  void CallBack(const base::Location& from,
                std::vector<uint8_t> public_key_spki_der,
                Status status) {
    auto success_callback =
        base::BindOnce(std::move(callback_), public_key_spki_der, status);
    // cleanup_callback will be called in case the main callback (callback_) is
    // canceled.
    auto cleanup_callback =
        base::BindOnce(&PlatformKeysServiceImpl::RemoveKey, service_weak_ptr_,
                       token_id_, public_key_spki_der, base::DoNothing());
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&RunCallBackIfCallableElseRunCleanUp,
                             std::move(success_callback),
                             std::move(cleanup_callback)));
  }

  // Must be called on origin thread, therefore use CallBack().
  GenerateKeyCallback callback_;
};

class GenerateECKeyState : public NSSOperationState {
 public:
  GenerateECKeyState(ServiceWeakPtr weak_ptr,
                     const std::string& named_curve,
                     TokenId token_id,
                     GenerateKeyCallback callback)
      : NSSOperationState(weak_ptr),
        named_curve_(std::move(named_curve)),
        token_id_(token_id),
        callback_(std::move(callback)) {}

  ~GenerateECKeyState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*public_key_spki_der=*/std::vector<uint8_t>(), status);
  }

  void OnSuccess(const base::Location& from,
                 std::vector<uint8_t> public_key_spki_der) {
    CallBack(from, std::move(public_key_spki_der), Status::kSuccess);
  }

  const std::string named_curve_;
  TokenId token_id_;

 private:
  void CallBack(const base::Location& from,
                std::vector<uint8_t> public_key_spki_der,
                Status status) {
    auto success_callback =
        base::BindOnce(std::move(callback_), public_key_spki_der, status);
    // cleanup_callback will be called in case the main callback (callback_) is
    // canceled.
    auto cleanup_callback =
        base::BindOnce(&PlatformKeysServiceImpl::RemoveKey, service_weak_ptr_,
                       token_id_, public_key_spki_der, base::DoNothing());
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&RunCallBackIfCallableElseRunCleanUp,
                             std::move(success_callback),
                             std::move(cleanup_callback)));
  }

  // Must be called on origin thread, therefore use CallBack().
  GenerateKeyCallback callback_;
};

class GenerateSymKeyState : public NSSOperationState {
 public:
  GenerateSymKeyState(ServiceWeakPtr weak_ptr,
                      std::vector<uint8_t> key_id,
                      int key_size,
                      SymKeyType key_type,
                      GenerateKeyCallback callback)
      : NSSOperationState(weak_ptr),
        key_id_(std::move(key_id)),
        key_size_(key_size),
        key_type_(key_type),
        callback_(std::move(callback)) {}

  ~GenerateSymKeyState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*key_id=*/std::vector<uint8_t>(), status);
  }

  void OnSuccess(const base::Location& from) {
    CallBack(from, key_id_, Status::kSuccess);
  }

  // This id is assigned to the newly created symmetric key.
  const std::vector<uint8_t> key_id_;

  const int key_size_;

  // Type of the key that determines which operations are allowed for it.
  const SymKeyType key_type_;

 private:
  void CallBack(const base::Location& from,
                std::vector<uint8_t> key_id,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), std::move(key_id), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  GenerateKeyCallback callback_;
};

class EncryptDecryptState : public NSSOperationState {
 public:
  EncryptDecryptState(ServiceWeakPtr weak_ptr,
                      std::vector<uint8_t> input_data,
                      std::vector<uint8_t> key_id,
                      std::string algorithm,
                      std::vector<uint8_t> init_vector,
                      const OperationType operation_type,
                      EncryptDecryptCallback callback)
      : NSSOperationState(weak_ptr),
        input_data_(std::move(input_data)),
        key_id_(std::move(key_id)),
        algorithm_(std::move(algorithm)),
        init_vector_(std::move(init_vector)),
        operation_type_(operation_type),
        callback_(std::move(callback)) {}

  ~EncryptDecryptState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*output_data=*/std::vector<uint8_t>(), status);
  }

  void OnSuccess(const base::Location& from, std::vector<uint8_t> output_data) {
    CallBack(from, std::move(output_data), Status::kSuccess);
  }

  // The data that will be encrypted/decrypted.
  const std::vector<uint8_t> input_data_;

  // Symmetric key id.
  const std::vector<uint8_t> key_id_;

  // Determines the algorithm that is used to encrypt/decrypt.
  const std::string algorithm_;

  // Initialization vector that is required for encryption/decryption.
  // Must have a length of 16 bytes.
  const std::vector<uint8_t> init_vector_;

  // Specifies the operation, i.e. encryption or decryption.
  const OperationType operation_type_;

 private:
  void CallBack(const base::Location& from,
                std::vector<uint8_t> output_data,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), std::move(output_data), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  EncryptDecryptCallback callback_;
};

class SignState : public NSSOperationState {
 public:
  SignState(ServiceWeakPtr weak_ptr,
            std::vector<uint8_t> data,
            std::vector<uint8_t> public_key_spki_der,
            HashAlgorithm hash_algorithm,
            const KeyType key_type,
            SignCallback callback)
      : NSSOperationState(weak_ptr),
        data_(std::move(data)),
        public_key_spki_der_(std::move(public_key_spki_der)),
        hash_algorithm_(hash_algorithm),
        key_type_(key_type),
        callback_(std::move(callback)) {}

  ~SignState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*signature=*/std::vector<uint8_t>(), status);
  }

  void OnSuccess(const base::Location& from, std::vector<uint8_t> signature) {
    CallBack(from, std::move(signature), Status::kSuccess);
  }

  // The data that will be signed.
  const std::vector<uint8_t> data_;

  // Must be the DER encoding of a SubjectPublicKeyInfo.
  const std::vector<uint8_t> public_key_spki_der_;

  // Determines the hash algorithm that is used to digest |data| before signing.
  const HashAlgorithm hash_algorithm_;

  // Determines the type of the key that should be used for signing. This is
  // specified by the state creator.
  // Note: This can be different from the type of |public_key_spki_der|. In such
  // case, a runtime error should be thrown.
  const KeyType key_type_;

 private:
  void CallBack(const base::Location& from,
                std::vector<uint8_t> signature,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), std::move(signature), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  SignCallback callback_;
};

class SignSymState : public NSSOperationState {
 public:
  SignSymState(ServiceWeakPtr weak_ptr,
               std::vector<uint8_t> data,
               std::vector<uint8_t> key_id,
               SignCallback callback)
      : NSSOperationState(weak_ptr),
        data_(std::move(data)),
        key_id_(std::move(key_id)),
        callback_(std::move(callback)) {}

  ~SignSymState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*signature=*/std::vector<uint8_t>(), status);
  }

  void OnSuccess(const base::Location& from, std::vector<uint8_t> signature) {
    CallBack(from, std::move(signature), Status::kSuccess);
  }

  // The data that will be signed.
  const std::vector<uint8_t> data_;

  // Symmetric key id.
  const std::vector<uint8_t> key_id_;

 private:
  void CallBack(const base::Location& from,
                std::vector<uint8_t> signature,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), std::move(signature), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  SignCallback callback_;
};

class SelectCertificatesState : public NSSOperationState {
 public:
  SelectCertificatesState(
      ServiceWeakPtr weak_ptr,
      const scoped_refptr<net::SSLCertRequestInfo>& cert_request_info,
      SelectCertificatesCallback callback)
      : NSSOperationState(weak_ptr),
        cert_request_info_(cert_request_info),
        callback_(std::move(callback)) {}

  ~SelectCertificatesState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, std::unique_ptr<net::CertificateList>() /* no matches */,
             status);
  }

  void OnSuccess(const base::Location& from,
                 std::unique_ptr<net::CertificateList> matches) {
    CallBack(from, std::move(matches), Status::kSuccess);
  }

  scoped_refptr<net::SSLCertRequestInfo> cert_request_info_;
  std::unique_ptr<net::ClientCertStore> cert_store_;

 private:
  void CallBack(const base::Location& from,
                std::unique_ptr<net::CertificateList> matches,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), std::move(matches), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  SelectCertificatesCallback callback_;
};

class GetCertificatesState : public NSSOperationState {
 public:
  GetCertificatesState(ServiceWeakPtr weak_ptr,
                       GetCertificatesCallback callback)
      : NSSOperationState(weak_ptr), callback_(std::move(callback)) {}

  ~GetCertificatesState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from,
             std::unique_ptr<net::CertificateList>() /* no certificates */,
             status);
  }

  void OnSuccess(const base::Location& from,
                 std::unique_ptr<net::CertificateList> certs) {
    CallBack(from, std::move(certs), Status::kSuccess);
  }

  net::ScopedCERTCertificateList certs_;

 private:
  void CallBack(const base::Location& from,
                std::unique_ptr<net::CertificateList> certs,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), std::move(certs), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  GetCertificatesCallback callback_;
};

class GetAllKeysState : public NSSOperationState {
 public:
  GetAllKeysState(ServiceWeakPtr weak_ptr, GetAllKeysCallback callback)
      : NSSOperationState(weak_ptr), callback_(std::move(callback)) {}

  ~GetAllKeysState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from,
             /*public_key_spki_der_list=*/std::vector<std::vector<uint8_t>>(),
             status);
  }

  void OnSuccess(const base::Location& from,
                 std::vector<std::vector<uint8_t>> public_key_spki_der_list) {
    CallBack(from, std::move(public_key_spki_der_list), Status::kSuccess);
  }

 private:
  void CallBack(const base::Location& from,
                std::vector<std::vector<uint8_t>> public_key_spki_der_list,
                Status status) {
    auto bound_callback = base::BindOnce(
        std::move(callback_), std::move(public_key_spki_der_list), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  GetAllKeysCallback callback_;
};

class ImportCertificateState : public NSSOperationState {
 public:
  ImportCertificateState(ServiceWeakPtr weak_ptr,
                         const scoped_refptr<net::X509Certificate>& certificate,
                         ImportCertificateCallback callback)
      : NSSOperationState(weak_ptr),
        certificate_(certificate),
        callback_(std::move(callback)) {}

  ~ImportCertificateState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, status);
  }

  void OnSuccess(const base::Location& from) {
    CallBack(from, Status::kSuccess);
  }

  scoped_refptr<net::X509Certificate> certificate_;

 private:
  void CallBack(const base::Location& from, Status status) {
    auto bound_callback = base::BindOnce(std::move(callback_), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  ImportCertificateCallback callback_;
};

class RemoveCertificateState : public NSSOperationState {
 public:
  RemoveCertificateState(ServiceWeakPtr weak_ptr,
                         const scoped_refptr<net::X509Certificate>& certificate,
                         RemoveCertificateCallback callback)
      : NSSOperationState(weak_ptr),
        certificate_(certificate),
        callback_(std::move(callback)) {}

  ~RemoveCertificateState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, status);
  }

  void OnSuccess(const base::Location& from) {
    CallBack(from, Status::kSuccess);
  }

  scoped_refptr<net::X509Certificate> certificate_;

 private:
  void CallBack(const base::Location& from, Status status) {
    auto bound_callback = base::BindOnce(std::move(callback_), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  RemoveCertificateCallback callback_;
};

class RemoveKeyState : public NSSOperationState {
 public:
  RemoveKeyState(ServiceWeakPtr weak_ptr,
                 std::vector<uint8_t> public_key_spki_der,
                 RemoveKeyCallback callback)
      : NSSOperationState(weak_ptr),
        public_key_spki_der_(std::move(public_key_spki_der)),
        callback_(std::move(callback)) {}

  ~RemoveKeyState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, status);
  }

  void OnSuccess(const base::Location& from) {
    CallBack(from, Status::kSuccess);
  }

  // Must be a DER encoding of a SubjectPublicKeyInfo.
  const std::vector<uint8_t> public_key_spki_der_;

 private:
  void CallBack(const base::Location& from, Status status) {
    auto bound_callback = base::BindOnce(std::move(callback_), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  RemoveKeyCallback callback_;
};

class RemoveSymKeyState : public NSSOperationState {
 public:
  RemoveSymKeyState(ServiceWeakPtr weak_ptr,
                    std::vector<uint8_t> key_id,
                    RemoveKeyCallback callback)
      : NSSOperationState(weak_ptr),
        key_id_(std::move(key_id)),
        callback_(std::move(callback)) {}

  ~RemoveSymKeyState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, status);
  }

  void OnSuccess(const base::Location& from) {
    CallBack(from, Status::kSuccess);
  }

  // Symmetric key id.
  const std::vector<uint8_t> key_id_;

 private:
  void CallBack(const base::Location& from, Status status) {
    auto bound_callback = base::BindOnce(std::move(callback_), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  RemoveKeyCallback callback_;
};

class GetTokensState : public NSSOperationState {
 public:
  GetTokensState(ServiceWeakPtr weak_ptr, GetTokensCallback callback)
      : NSSOperationState(weak_ptr), callback_(std::move(callback)) {}

  ~GetTokensState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, std::vector<TokenId>() /* no token ids */, status);
  }

  void OnSuccess(const base::Location& from, std::vector<TokenId> token_ids) {
    CallBack(from, std::move(token_ids), Status::kSuccess);
  }

 private:
  void CallBack(const base::Location& from,
                std::vector<TokenId> token_ids,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), std::move(token_ids), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  GetTokensCallback callback_;
};

class GetKeyLocationsState : public NSSOperationState {
 public:
  GetKeyLocationsState(ServiceWeakPtr weak_ptr,
                       std::vector<uint8_t> public_key_spki_der,
                       GetKeyLocationsCallback callback)
      : NSSOperationState(weak_ptr),
        public_key_spki_der_(std::move(public_key_spki_der)),
        callback_(std::move(callback)) {}

  ~GetKeyLocationsState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*token_ids=*/std::vector<TokenId>(), status);
  }

  void OnSuccess(const base::Location& from,
                 const std::vector<TokenId>& token_ids) {
    CallBack(from, token_ids, Status::kSuccess);
  }

  // Must be a DER encoding of a SubjectPublicKeyInfo.
  const std::vector<uint8_t> public_key_spki_der_;

 private:
  void CallBack(const base::Location& from,
                const std::vector<TokenId>& token_ids,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), token_ids, status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  GetKeyLocationsCallback callback_;
};

class SetAttributeForKeyState : public NSSOperationState {
 public:
  SetAttributeForKeyState(ServiceWeakPtr weak_ptr,
                          std::vector<uint8_t> public_key_spki_der,
                          CK_ATTRIBUTE_TYPE attribute_type,
                          std::vector<uint8_t> attribute_value,
                          SetAttributeForKeyCallback callback)
      : NSSOperationState(weak_ptr),
        public_key_spki_der_(public_key_spki_der),
        attribute_type_(attribute_type),
        attribute_value_(std::move(attribute_value)),
        callback_(std::move(callback)) {}

  ~SetAttributeForKeyState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, status);
  }

  void OnSuccess(const base::Location& from) {
    CallBack(from, Status::kSuccess);
  }

  // Must be a DER encoding of a SubjectPublicKeyInfo.
  const std::vector<uint8_t> public_key_spki_der_;
  const CK_ATTRIBUTE_TYPE attribute_type_;
  const std::vector<uint8_t> attribute_value_;

 private:
  void CallBack(const base::Location& from, Status status) {
    auto bound_callback = base::BindOnce(std::move(callback_), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  SetAttributeForKeyCallback callback_;
};

class GetAttributeForKeyState : public NSSOperationState {
 public:
  GetAttributeForKeyState(ServiceWeakPtr weak_ptr,
                          std::vector<uint8_t> public_key_spki_der,
                          CK_ATTRIBUTE_TYPE attribute_type,
                          GetAttributeForKeyCallback callback)
      : NSSOperationState(weak_ptr),
        public_key_spki_der_(public_key_spki_der),
        attribute_type_(attribute_type),
        callback_(std::move(callback)) {}

  ~GetAttributeForKeyState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*attribute_value=*/std::nullopt, status);
  }

  void OnSuccess(const base::Location& from,
                 std::optional<std::vector<uint8_t>> attribute_value) {
    CallBack(from, std::move(attribute_value), Status::kSuccess);
  }

  // Must be a DER encoding of a SubjectPublicKeyInfo.
  const std::vector<uint8_t> public_key_spki_der_;
  const CK_ATTRIBUTE_TYPE attribute_type_;

 private:
  void CallBack(const base::Location& from,
                std::optional<std::vector<uint8_t>> attribute_value,
                Status status) {
    auto bound_callback = base::BindOnce(std::move(callback_),
                                         std::move(attribute_value), status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  GetAttributeForKeyCallback callback_;
};

class IsKeyOnTokenState : public NSSOperationState {
 public:
  IsKeyOnTokenState(ServiceWeakPtr weak_ptr,
                    std::vector<uint8_t> public_key_spki_der,
                    IsKeyOnTokenCallback callback)
      : NSSOperationState(weak_ptr),
        public_key_spki_der_(std::move(public_key_spki_der)),
        callback_(std::move(callback)) {}

  ~IsKeyOnTokenState() override = default;

  void OnError(const base::Location& from, Status status) override {
    CallBack(from, /*on_token=*/std::nullopt, status);
  }

  void OnSuccess(const base::Location& from, bool on_token) {
    CallBack(from, on_token, Status::kSuccess);
  }

  // Must be a DER encoding of a SubjectPublicKeyInfo.
  const std::vector<uint8_t> public_key_spki_der_;

 private:
  void CallBack(const base::Location& from,
                std::optional<bool> on_token,
                Status status) {
    auto bound_callback =
        base::BindOnce(std::move(callback_), on_token, status);
    content::GetUIThreadTaskRunner({})->PostTask(
        from, base::BindOnce(&NSSOperationState::RunCallback,
                             std::move(bound_callback), service_weak_ptr_));
  }

  // Must be called on origin thread, therefore use CallBack().
  IsKeyOnTokenCallback callback_;
};

// Returns the private key corresponding to the der-encoded
// |public_key_spki_der| if found in |slot|. If |slot| is nullptr, the private
// key will be searched in all slots.
crypto::ScopedSECKEYPrivateKey GetPrivateKey(
    const std::vector<uint8_t>& public_key_spki_der,
    PK11SlotInfo* slot) {
  if (slot) {
    return crypto::FindNSSKeyFromPublicKeyInfoInSlot(public_key_spki_der, slot);
  }
  return crypto::FindNSSKeyFromPublicKeyInfo(public_key_spki_der);
}

// Returns the symmetric key with CKA_ID equal to |key_id| found in |slot|.
// |type| specifies the type of the key.
crypto::ScopedPK11SymKey GetSymKey(
    std::vector<uint8_t> key_id,
    PK11SlotInfo* slot,
    std::optional<SymKeyType> key_type) {
  SECItem sec_key_id{siUTF8String, key_id.data(),
                     static_cast<unsigned int>(key_id.size())};
  CK_MECHANISM_TYPE mechanism_type;
  if (!key_type) {
    mechanism_type = CKM_GENERIC_SECRET_KEY_GEN;
  } else {
    switch (*key_type) {
      case SymKeyType::kAesCbc:
        mechanism_type = CKM_AES_CBC_PAD;
        break;
      case SymKeyType::kHmac:
        mechanism_type = CKM_SHA256_HMAC;
        break;
    }
  }
  return crypto::ScopedPK11SymKey(PK11_FindFixedKey(slot, mechanism_type,
                                                    &sec_key_id,
                                                    /*wincx=*/nullptr));
}

// Does the actual symmetric key generation on a worker thread. Used by
// GenerateSymKeyWithDB().
void GenerateSymKeyOnWorkerThread(std::unique_ptr<GenerateSymKeyState> state) {
  if (!state->slot_) {
    LOG(ERROR) << "No slot.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  if (state->key_size_ != 32) {
    LOG(ERROR) << "Only 32-byte keys are supported.";
    state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
    return;
  }

  crypto::ScopedPK11SymKey key =
      GetSymKey(state->key_id_, state->slot_.get(), std::nullopt);
  if (key) {
    LOG(ERROR)
        << "Cannot generate key because a key with given id already exists";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  std::vector<uint8_t> key_id = state->key_id_;
  SECItem sec_key_id{siUTF8String, key_id.data(),
                     static_cast<unsigned int>(key_id.size())};
  CK_FLAGS op_flags;
  CK_MECHANISM_TYPE key_type;

  switch (state->key_type_) {
    case SymKeyType::kAesCbc:
      op_flags = CKF_ENCRYPT | CKF_DECRYPT;
      key_type = CKM_AES_KEY_GEN;
      break;
    case SymKeyType::kHmac:
      op_flags = CKF_SIGN;
      key_type = CKM_SHA256_HMAC;
      break;
  }

  if (!PK11_TokenKeyGenWithFlags(
          state->slot_.get(), key_type,
          /*param=*/nullptr, state->key_size_, &sec_key_id, op_flags,
          /*attr_flags=*/PK11_ATTR_TOKEN | PK11_ATTR_PRIVATE,
          /*wincx=*/nullptr)) {
    LOG(ERROR) << "Couldn't generate symmetric key.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }
  state->OnSuccess(FROM_HERE);
}

// Continues generating a symmetric key with the obtained NSSCertDatabase. Used
// by GenerateSymKey().
void GenerateSymKeyWithDB(std::unique_ptr<GenerateSymKeyState> state,
                          net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
  // This task interacts with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&GenerateSymKeyOnWorkerThread, std::move(state)));
}

// Does the actual RSA key generation on a worker thread. Used by
// GenerateRSAKeyWithDB().
void GenerateRSAKeyOnWorkerThread(std::unique_ptr<GenerateRSAKeyState> state) {
  if (!state->slot_) {
    LOG(ERROR) << "No slot.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  crypto::ScopedSECKEYPublicKey public_key;
  crypto::ScopedSECKEYPrivateKey private_key;

  bool key_gen_success;
  if (state->sw_backed_) {
    auto chaps_util = chromeos::ChapsUtil::Create();
    key_gen_success = chaps_util->GenerateSoftwareBackedRSAKey(
        state->slot_.get(), state->modulus_length_bits_, &public_key,
        &private_key);
  } else {
    key_gen_success = crypto::GenerateRSAKeyPairNSS(
        state->slot_.get(), state->modulus_length_bits_, true /* permanent */,
        &public_key, &private_key);
  }
  if (!key_gen_success) {
    LOG(ERROR) << "Couldn't create key, sw_backed=" << state->sw_backed_;
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  crypto::ScopedSECItem public_key_der(
      SECKEY_EncodeDERSubjectPublicKeyInfo(public_key.get()));
  if (!public_key_der) {
    // TODO(crbug.com/40115571): Remove private_key and public_key from
    // storage.
    LOG(ERROR) << "Couldn't export public key.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }
  state->OnSuccess(FROM_HERE, ScopedSECItemToBytes(public_key_der));
}

// Does the actual EC key generation on a worker thread. Used by
// GenerateECKeyWithDB().
void GenerateECKeyOnWorkerThread(std::unique_ptr<GenerateECKeyState> state) {
  if (!state->slot_) {
    LOG(ERROR) << "No slot.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  if (state->named_curve_ != "P-256") {
    LOG(ERROR) << "Only P-256 named curve is supported.";
    state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
  }

  crypto::ScopedSECKEYPublicKey public_key;
  crypto::ScopedSECKEYPrivateKey private_key;
  if (!crypto::GenerateECKeyPairNSS(
          state->slot_.get(), SEC_OID_ANSIX962_EC_PRIME256V1,
          true /* permanent */, &public_key, &private_key)) {
    LOG(ERROR) << "Couldn't create key.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  crypto::ScopedSECItem public_key_der(
      SECKEY_EncodeDERSubjectPublicKeyInfo(public_key.get()));
  if (!public_key_der) {
    // TODO(crbug.com/40115571): Remove private_key and public_key from
    // storage.
    LOG(ERROR) << "Couldn't export public key.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }
  state->OnSuccess(FROM_HERE, ScopedSECItemToBytes(public_key_der));
}

// Continues generating a RSA key with the obtained NSSCertDatabase. Used by
// GenerateRSAKey().
void GenerateRSAKeyWithDB(std::unique_ptr<GenerateRSAKeyState> state,
                          net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
  // This task interacts with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&GenerateRSAKeyOnWorkerThread, std::move(state)));
}

// Continues generating an EC key with the obtained NSSCertDatabase. Used by
// GenerateECKey().
void GenerateECKeyWithDB(std::unique_ptr<GenerateECKeyState> state,
                         net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
  // This task interacts with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&GenerateECKeyOnWorkerThread, std::move(state)));
}

// Does the actual AES encryption/decryption on a worker thread.
// Used by EncryptDecryptAESWithDB().
void EncryptDecryptAESOnWorkerThread(
    std::unique_ptr<EncryptDecryptState> state) {
  if (!state->slot_) {
    LOG(ERROR) << "No slot.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  if (state->algorithm_ != "AES-CBC") {
    LOG(ERROR) << "Only AES-CBC encryption is supported.";
    state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
    return;
  }

  std::vector<uint8_t> iv = state->init_vector_;
  if (iv.size() != 16) {
    LOG(ERROR) << "Initialization vector's length should be equal to 16.";
    state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
    return;
  }

  crypto::ScopedPK11SymKey key =
      GetSymKey(state->key_id_, state->slot_.get(), SymKeyType::kAesCbc);
  if (!key) {
    LOG(ERROR) << "Couldn't find the symmetric key.";
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  SECItem sec_iv{siUTF8String, iv.data(), static_cast<unsigned int>(iv.size())};
  // Input string might be padded so its length would be a multiple of 16, which
  // means the encrypted string could be larger than the initial one.
  std::vector<uint8_t> result(state->input_data_.size() + 16);
  unsigned int result_len = 0;

  switch (state->operation_type_) {
    case OperationType::kEncrypt:
      if (PK11_Encrypt(key.get(), PK11_GetMechanism(key.get()), &sec_iv,
                       result.data(), &result_len, result.size(),
                       state->input_data_.data(),
                       state->input_data_.size()) != SECSuccess) {
        LOG(ERROR) << "Encryption failed.";
        state->OnError(FROM_HERE, Status::kErrorInternal);
        return;
      }
      break;
    case OperationType::kDecrypt:
      if (PK11_Decrypt(key.get(), PK11_GetMechanism(key.get()), &sec_iv,
                       result.data(), &result_len, result.size(),
                       state->input_data_.data(),
                       state->input_data_.size()) != SECSuccess) {
        LOG(ERROR) << "Decryption failed.";
        state->OnError(FROM_HERE, Status::kErrorInternal);
        return;
      }
      break;
  }

  result.resize(result_len);
  state->OnSuccess(FROM_HERE, std::move(result));
}

// Continues AES encryption/decryption with the obtained NSSCertDatabase.
// Used by EncryptDecryptAES().
void EncryptDecryptAESWithDB(std::unique_ptr<EncryptDecryptState> state,
                             net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
  // This task interacts with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&EncryptDecryptAESOnWorkerThread, std::move(state)));
}

// Checks whether |input_length| is lower or equal to the maximum input length
// for a RSA PKCS#1 v1.5 signature generated using |private_key| with PK11_Sign.
// Returns false if |input_length| is too large.
// If the maximum input length can not be determined (which is possible because
// it queries the PKCS#11 module), returns true and logs a warning.
bool CheckRSAPKCS1SignRawInputLength(SECKEYPrivateKey* private_key,
                                     size_t input_length) {
  // For RSA keys, PK11_Sign will perform PKCS#1 v1.5 padding, which needs at
  // least 11 bytes. RSA Sign can process an input of max. modulus length.
  // Thus the maximum input length for the sign operation is
  // |modulus_length - 11|.
  int modulus_length_bytes = PK11_GetPrivateModulusLen(private_key);
  if (modulus_length_bytes <= 0) {
    LOG(WARNING) << "Could not determine modulus length";
    return true;
  }
  size_t max_input_length_after_padding =
      static_cast<size_t>(modulus_length_bytes);
  // PKCS#1 v1.5 Padding needs at least this many bytes.
  size_t kMinPaddingLength = 11u;
  return input_length + kMinPaddingLength <= max_input_length_after_padding;
}

// Performs "raw" PKCS1 v1.5 padding + signing by calling PK11_Sign on
// |rsa_key|.
void SignRSAPKCS1RawOnWorkerThread(std::unique_ptr<SignState> state,
                                   crypto::ScopedSECKEYPrivateKey rsa_key) {
  static_assert(
      sizeof(*state->data_.data()) == sizeof(char),
      "Can't reinterpret data if it's characters are not 8 bit large.");
  SECItem input = {siBuffer, const_cast<unsigned char*>(state->data_.data()),
                   static_cast<unsigned int>(state->data_.size())};

  // Compute signature of hash.
  int signature_len = PK11_SignatureLen(rsa_key.get());
  if (signature_len <= 0) {
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  std::vector<unsigned char> signature(signature_len);
  SECItem signature_output = {siBuffer, signature.data(),
                              static_cast<unsigned int>(signature.size())};
  if (PK11_Sign(rsa_key.get(), &signature_output, &input) != SECSuccess) {
    // Input size is checked after a failure - obtaining max input size
    // involves extracting key modulus length which is not a free operation, so
    // don't bother if signing succeeded.
    // Note: It would be better if this could be determined from some library
    // return code (e.g. PORT_GetError), but this was not possible with
    // NSS+chaps at this point.
    if (!CheckRSAPKCS1SignRawInputLength(rsa_key.get(), state->data_.size())) {
      LOG(ERROR) << "Couldn't sign - input too long.";
      state->OnError(FROM_HERE, Status::kErrorInputTooLong);
      return;
    }
    LOG(ERROR) << "Couldn't sign.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }
  state->OnSuccess(FROM_HERE, std::move(signature));
}

// Does the actual RSA signing on a worker thread.
void SignRSAOnWorkerThread(std::unique_ptr<SignState> state) {
  crypto::ScopedSECKEYPrivateKey rsa_key =
      GetPrivateKey(state->public_key_spki_der_, state->slot_.get());

  // Fail if the key was not found or is of the wrong type.
  if (!rsa_key || SECKEY_GetPrivateKeyType(rsa_key.get()) != rsaKey) {
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  if (state->hash_algorithm_ == HashAlgorithm::HASH_ALGORITHM_NONE) {
    SignRSAPKCS1RawOnWorkerThread(std::move(state), std::move(rsa_key));
    return;
  }

  SECOidTag sign_alg_tag = SEC_OID_UNKNOWN;
  switch (state->hash_algorithm_) {
    case HashAlgorithm::HASH_ALGORITHM_SHA1:
      sign_alg_tag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION;
      break;
    case HashAlgorithm::HASH_ALGORITHM_SHA256:
      sign_alg_tag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION;
      break;
    case HashAlgorithm::HASH_ALGORITHM_SHA384:
      sign_alg_tag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION;
      break;
    case HashAlgorithm::HASH_ALGORITHM_SHA512:
      sign_alg_tag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION;
      break;
    case HashAlgorithm::HASH_ALGORITHM_NONE:
      NOTREACHED_IN_MIGRATION();
      break;
  }

  crypto::ScopedSECItem sign_result(SECITEM_AllocItem(nullptr, nullptr, 0));
  if (SEC_SignData(sign_result.get(),
                   reinterpret_cast<const unsigned char*>(state->data_.data()),
                   state->data_.size(), rsa_key.get(),
                   sign_alg_tag) != SECSuccess) {
    LOG(ERROR) << "Couldn't sign.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  state->OnSuccess(FROM_HERE, ScopedSECItemToBytes(sign_result));
}

// Does the actual EC Signing on a worker thread.
void SignECOnWorkerThread(std::unique_ptr<SignState> state) {
  crypto::ScopedSECKEYPrivateKey ec_key =
      GetPrivateKey(state->public_key_spki_der_, state->slot_.get());

  // Fail if the key was not found or is of the wrong type.
  if (!ec_key || SECKEY_GetPrivateKeyType(ec_key.get()) != ecKey) {
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  DCHECK(state->hash_algorithm_ != HashAlgorithm::HASH_ALGORITHM_NONE);

  // Only SHA-256 algorithm is supported for ECDSA.
  if (state->hash_algorithm_ != HashAlgorithm::HASH_ALGORITHM_SHA256) {
    state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
    return;
  }

  std::string signature_str;
  SECOidTag sign_alg_tag = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE;
  crypto::ScopedSECItem sign_result(SECITEM_AllocItem(nullptr, nullptr, 0));
  if (SEC_SignData(sign_result.get(),
                   reinterpret_cast<const unsigned char*>(state->data_.data()),
                   state->data_.size(), ec_key.get(),
                   sign_alg_tag) != SECSuccess) {
    LOG(ERROR) << "Couldn't sign using elliptic curve key.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  int signature_len = PK11_SignatureLen(ec_key.get());
  crypto::ScopedSECItem web_crypto_signature(
      DSAU_DecodeDerSigToLen(sign_result.get(), signature_len));

  if (!web_crypto_signature || web_crypto_signature->len == 0) {
    LOG(ERROR) << "Couldn't convert signature to WebCrypto format.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  state->OnSuccess(FROM_HERE, ScopedSECItemToBytes(web_crypto_signature));
}

// Decides which signing algorithm will be used. Used by SignWithDB().
void SignOnWorkerThread(std::unique_ptr<SignState> state) {
  crypto::ScopedSECKEYPrivateKey key =
      GetPrivateKey(state->public_key_spki_der_, state->slot_.get());

  if (!key) {
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  switch (state->key_type_) {
    case KeyType::kRsassaPkcs1V15:
      SignRSAOnWorkerThread(std::move(state));
      break;
    case KeyType::kEcdsa:
      SignECOnWorkerThread(std::move(state));
      break;
  }
}

// Continues signing with the obtained NSSCertDatabase. Used by Sign().
void SignWithDB(std::unique_ptr<SignState> state,
                net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
  // This task interacts with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&SignOnWorkerThread, std::move(state)));
}

// Does the actual SHA-256 HMAC signing on a worker thread.
// Used by SignSymWithDB().
void SignSymOnWorkerThread(std::unique_ptr<SignSymState> state) {
  if (!state->slot_) {
    LOG(ERROR) << "No slot.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  crypto::ScopedPK11SymKey key =
      GetSymKey(state->key_id_, state->slot_.get(), SymKeyType::kHmac);
  if (!key) {
    LOG(ERROR) << "Couldn't find the symmetric key.";
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  std::vector<uint8_t> signature(kDefaultSymSignatureLength);
  std::vector<uint8_t> data = state->data_;
  SECItem sec_signature{siUTF8String, signature.data(),
                        static_cast<unsigned int>(signature.size())};
  SECItem sec_data{siUTF8String, data.data(),
                   static_cast<unsigned int>(data.size())};

  if (PK11_SignWithSymKey(key.get(), PK11_GetMechanism(key.get()),
                          /*param=*/nullptr, &sec_signature,
                          &sec_data) != SECSuccess) {
    LOG(ERROR) << "Signing failed.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  state->OnSuccess(FROM_HERE, std::move(signature));
}

// Continues signing with the obtained NSSCertDatabase. Used by
// SignWithSymKey().
void SignSymWithDB(std::unique_ptr<SignSymState> state,
                   net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
  // This task interacts with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&SignSymOnWorkerThread, std::move(state)));
}

// Called when `ClientCertStoreAsh::GetClientCerts` is done. Builds the list of
// `net::CertificateList` and calls back. Used by `SelectCertificates()`.
void DidSelectCertificates(std::unique_ptr<SelectCertificatesState> state,
                           net::ClientCertIdentityList identities) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // Convert the ClientCertIdentityList to a CertificateList since returning
  // ClientCertIdentities would require changing the platformKeys extension
  // api. This assumes that the necessary keys can be found later with
  // crypto::FindNSSKeyFromPublicKeyInfo.
  auto certs = std::make_unique<net::CertificateList>();
  for (const std::unique_ptr<net::ClientCertIdentity>& identity : identities) {
    certs->push_back(identity->certificate());
  }
  // DidSelectCertificates() may be called synchronously, so run the callback on
  // a separate event loop iteration to avoid potential reentrancy bugs.
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&SelectCertificatesState::OnSuccess,
                                std::move(state), FROM_HERE, std::move(certs)));
}

// Filters the obtained certificates on a worker thread. Used by
// DidGetCertificates().
void FilterCertificatesOnWorkerThread(
    std::unique_ptr<GetCertificatesState> state) {
  std::unique_ptr<net::CertificateList> client_certs(new net::CertificateList);
  for (net::ScopedCERTCertificateList::const_iterator it =
           state->certs_.begin();
       it != state->certs_.end(); ++it) {
    CERTCertificate* cert_handle = it->get();
    crypto::ScopedPK11Slot cert_slot(PK11_KeyForCertExists(cert_handle,
                                                           nullptr,    // keyPtr
                                                           nullptr));  // wincx

    // Keep only user certificates, i.e. certs for which the private key is
    // present and stored in the queried slot.
    if (cert_slot != state->slot_) {
      continue;
    }

    // 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> cert =
        net::x509_util::CreateX509CertificateFromCERTCertificate(cert_handle,
                                                                 {}, options);
    if (!cert) {
      continue;
    }

    client_certs->push_back(std::move(cert));
  }

  state->OnSuccess(FROM_HERE, std::move(client_certs));
}

// Passes the obtained certificates to the worker thread for filtering. Used by
// GetCertificatesWithDB().
void DidGetCertificates(std::unique_ptr<GetCertificatesState> state,
                        net::ScopedCERTCertificateList all_certs) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  state->certs_ = std::move(all_certs);
  // This task interacts with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&FilterCertificatesOnWorkerThread, std::move(state)));
}

// Continues getting certificates with the obtained NSSCertDatabase. Used by
// GetCertificates().
void GetCertificatesWithDB(std::unique_ptr<GetCertificatesState> state,
                           net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Get the pointer to slot before transferring ownership of |state| to the
  // callback's bound arguments.
  PK11SlotInfo* slot = state->slot_.get();
  cert_db->ListCertsInSlot(
      base::BindOnce(&DidGetCertificates, std::move(state)), slot);
}

// Returns true if |public_key| is relevant as a "platform key" that should be
// visible to chrome extensions / subsystems.
bool ShouldIncludePublicKey(SECKEYPublicKey* public_key) {
  crypto::ScopedSECItem cka_id(SECITEM_AllocItem(/*arena=*/nullptr,
                                                 /*item=*/nullptr,
                                                 /*len=*/0));
  if (PK11_ReadRawAttribute(
          /*objType=*/PK11_TypePubKey, public_key, CKA_ID, cka_id.get()) !=
      SECSuccess) {
    return false;
  }

  std::string_view cka_id_str(reinterpret_cast<char*>(cka_id->data),
                              cka_id->len);

  // Only keys generated/stored by extensions/Chrome should be visible to
  // extensions. Oemcrypto stores its key in the TPM, but that should not
  // be exposed. Look at exposing additional attributes or changing the slot
  // that Oemcrypto stores keys, so that it can be done based on properties
  // of the key. See https://crbug/com/1136396
  if (cka_id_str == "arc-oemcrypto") {
    VLOG(0) << "Filtered out arc-oemcrypto public key.";
    return false;
  }
  return true;
}

// Does the actual retrieval of the SubjectPublicKeyInfo string on a worker
// thread. Used by GetAllKeysWithDb().
void GetAllKeysOnWorkerThread(std::unique_ptr<GetAllKeysState> state) {
  DCHECK(state->slot_.get());

  std::vector<std::vector<uint8_t>> public_key_spki_der_list;

  // This assumes that there might be a public key on a slot that
  // does not have a corresponding private key. The key is then considered
  // partially deleted and should be treated as deleted (it eventually will be).
  crypto::ScopedSECKEYPublicKeyList public_keys(
      PK11_ListPublicKeysInSlot(state->slot_.get(), /*nickname=*/nullptr));

  if (!public_keys) {
    state->OnSuccess(FROM_HERE, std::move(public_key_spki_der_list));
    return;
  }

  for (SECKEYPublicKeyListNode* node = PUBKEY_LIST_HEAD(public_keys);
       !PUBKEY_LIST_END(node, public_keys); node = PUBKEY_LIST_NEXT(node)) {
    if (!ShouldIncludePublicKey(node->key)) {
      continue;
    }

    crypto::ScopedSECItem subject_public_key_info(
        SECKEY_EncodeDERSubjectPublicKeyInfo(node->key));
    if (!subject_public_key_info) {
      LOG(WARNING) << "Could not encode subject public key info.";
      continue;
    }
    if (subject_public_key_info->len == 0) {
      continue;
    }
    const std::vector<uint8_t> pubkey =
        ScopedSECItemToBytes(subject_public_key_info);
    crypto::ScopedSECKEYPrivateKey rsa_key =
        crypto::FindNSSKeyFromPublicKeyInfoInSlot(pubkey, state->slot_.get());
    if (rsa_key) {
      public_key_spki_der_list.push_back(pubkey);
    }
  }

  state->OnSuccess(FROM_HERE, std::move(public_key_spki_der_list));
}

// Continues the retrieval of the SubjectPublicKeyInfo list with |cert_db|.
// Used by GetAllKeys().
void GetAllKeysWithDb(std::unique_ptr<GetAllKeysState> state,
                      net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&GetAllKeysOnWorkerThread, std::move(state)));
}

// Does the actual certificate importing on the IO thread. Used by
// ImportCertificate().
void ImportCertificateWithDB(std::unique_ptr<ImportCertificateState> state,
                             net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  if (!state->certificate_) {
    state->OnError(FROM_HERE, Status::kNetErrorCertificateInvalid);
    return;
  }
  if (state->certificate_->HasExpired()) {
    state->OnError(FROM_HERE, Status::kNetErrorCertificateDateInvalid);
    return;
  }

  net::ScopedCERTCertificate nss_cert =
      net::x509_util::CreateCERTCertificateFromX509Certificate(
          state->certificate_.get());
  if (!nss_cert) {
    state->OnError(FROM_HERE, Status::kNetErrorCertificateInvalid);
    return;
  }

  // Check that the private key is in the correct slot.
  crypto::ScopedPK11Slot slot(
      PK11_KeyForCertExists(nss_cert.get(), nullptr, nullptr));
  if (slot.get() != state->slot_.get()) {
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  const net::Error import_status =
      static_cast<net::Error>(cert_db->ImportUserCert(nss_cert.get()));
  if (import_status != net::OK) {
    LOG(ERROR) << "Could not import certificate: "
               << net::ErrorToString(import_status);
    state->OnError(FROM_HERE, Status::kNetErrorAddUserCertFailed);
    return;
  }

  state->OnSuccess(FROM_HERE);
}

// Called on IO thread after the certificate removal is finished.
void DidRemoveCertificate(std::unique_ptr<RemoveCertificateState> state,
                          bool certificate_found,
                          bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // CertificateNotFound error has precedence over an internal error.
  if (!certificate_found) {
    state->OnError(FROM_HERE, Status::kErrorCertificateNotFound);
    return;
  }
  if (!success) {
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  state->OnSuccess(FROM_HERE);
}

// Does the actual certificate removal on the IO thread. Used by
// RemoveCertificate().
void RemoveCertificateWithDB(std::unique_ptr<RemoveCertificateState> state,
                             net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  PRBool certificate_found;
  net::ScopedCERTCertificate nss_cert =
      net::x509_util::CreateCERTCertificateFromX509Certificate(
          state->certificate_.get());
  if (!nss_cert ||
      CERT_GetCertIsPerm(nss_cert.get(), &certificate_found) != SECSuccess) {
    state->OnError(FROM_HERE, Status::kNetErrorCertificateInvalid);
    return;
  }

  cert_db->DeleteCertAndKeyAsync(
      std::move(nss_cert),
      base::BindOnce(&DidRemoveCertificate, std::move(state),
                     certificate_found != PR_FALSE));
}

// Does the actual key pair removal on a worker thread. Used by
// RemoveKeyWithDb().
void RemoveKeyOnWorkerThread(std::unique_ptr<RemoveKeyState> state) {
  DCHECK(state->slot_.get());

  crypto::ScopedSECKEYPrivateKey private_key =
      GetPrivateKey(state->public_key_spki_der_, state->slot_.get());

  if (!private_key) {
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  crypto::ScopedSECKEYPublicKey public_key(
      SECKEY_ConvertToPublicKey(private_key.get()));

  // PK11_DeleteTokenPrivateKey function frees the privKey structure
  // unconditionally, and thus releasing the ownership of the passed private
  // key.
  // |force| is set to false so as not to delete the key if there are any
  // matching certificates.
  if (PK11_DeleteTokenPrivateKey(/*privKey=*/private_key.release(),
                                 /*force=*/false) != SECSuccess) {
    LOG(ERROR) << "Cannot delete private key";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  // PK11_DeleteTokenPublicKey function frees the pubKey structure
  // unconditionally, and thus releasing the ownership of the passed private
  // key.
  if (PK11_DeleteTokenPublicKey(/*pubKey=*/public_key.release()) !=
      SECSuccess) {
    LOG(WARNING) << "Cannot delete public key";
  }

  state->OnSuccess(FROM_HERE);
}

// Continues removing the key pair with the obtained |cert_db|. Called by
// RemoveKey().
void RemoveKeyWithDb(std::unique_ptr<RemoveKeyState> state,
                     net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&RemoveKeyOnWorkerThread, std::move(state)));
}

// Does the actual symmetric key removal on a worker thread. Used by
// RemoveSymKeyWithDb().
void RemoveSymKeyOnWorkerThread(std::unique_ptr<RemoveSymKeyState> state) {
  if (!state->slot_) {
    LOG(ERROR) << "No slot.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  crypto::ScopedPK11SymKey key =
      GetSymKey(state->key_id_, state->slot_.get(), std::nullopt);
  if (!key) {
    LOG(ERROR) << "Couldn't find the symmetric key.";
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  if (PK11_DeleteTokenSymKey(key.get()) != SECSuccess) {
    LOG(ERROR) << "Failed to delete key.";
    state->OnError(FROM_HERE, Status::kErrorInternal);
    return;
  }

  state->OnSuccess(FROM_HERE);
}

// Continues removing the symmetric key with the obtained |cert_db|. Called by
// RemoveSymKey().
void RemoveSymKeyWithDb(std::unique_ptr<RemoveSymKeyState> state,
                        net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&RemoveSymKeyOnWorkerThread, std::move(state)));
}

// Does the actual work to determine which tokens are available.
void GetTokensWithDB(std::unique_ptr<GetTokensState> state,
                     net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  std::vector<TokenId> token_ids;

  // The user token will be unavailable in case of no logged in user in this
  // profile.
  if (cert_db->GetPrivateSlot()) {
    token_ids.push_back(TokenId::kUser);
  }

  if (cert_db->GetSystemSlot()) {
    token_ids.push_back(TokenId::kSystem);
  }

  DCHECK(!token_ids.empty());

  state->OnSuccess(FROM_HERE, std::move(token_ids));
}

// Does the actual work to determine which key is on which token.
void GetKeyLocationsWithDB(std::unique_ptr<GetKeyLocationsState> state,
                           net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  std::vector<TokenId> token_ids;

  const uint8_t* public_key_uint8 =
      reinterpret_cast<const uint8_t*>(state->public_key_spki_der_.data());
  std::vector<uint8_t> public_key_vector(
      public_key_uint8, public_key_uint8 + state->public_key_spki_der_.size());

  if (cert_db->GetPrivateSlot().get()) {
    crypto::ScopedSECKEYPrivateKey rsa_key =
        crypto::FindNSSKeyFromPublicKeyInfoInSlot(
            public_key_vector, cert_db->GetPrivateSlot().get());
    if (rsa_key) {
      token_ids.push_back(TokenId::kUser);
    }
  }

  // The "system" NSSCertDatabaseChromeOS instance reuses its "system slot" as
  // "public slot", but that doesn't mean it's a user-specific slot.
  if (token_ids.empty() && cert_db->GetPublicSlot().get() &&
      cert_db->GetPublicSlot().get() != cert_db->GetSystemSlot().get()) {
    crypto::ScopedSECKEYPrivateKey rsa_key =
        crypto::FindNSSKeyFromPublicKeyInfoInSlot(
            public_key_vector, cert_db->GetPublicSlot().get());
    if (rsa_key) {
      token_ids.push_back(TokenId::kUser);
    }
  }

  if (cert_db->GetSystemSlot().get()) {
    crypto::ScopedSECKEYPrivateKey rsa_key =
        crypto::FindNSSKeyFromPublicKeyInfoInSlot(
            public_key_vector, cert_db->GetSystemSlot().get());
    if (rsa_key) {
      token_ids.push_back(TokenId::kSystem);
    }
  }

  state->OnSuccess(FROM_HERE, std::move(token_ids));
}

// Translates |type| to one of the NSS softoken module's predefined key
// attributes which are used in tests.
CK_ATTRIBUTE_TYPE TranslateKeyAttributeTypeForSoftoken(KeyAttributeType type) {
  switch (type) {
    case KeyAttributeType::kCertificateProvisioningId:
      return CKA_START_DATE;
    case KeyAttributeType::kKeyPermissions:
      return CKA_END_DATE;
  }
}

// If |map_to_softoken_attrs| is true, translates |type| to one of the softoken
// module predefined key attributes. Otherwise, applies normal translation.
CK_ATTRIBUTE_TYPE TranslateKeyAttributeType(KeyAttributeType type,
                                            bool map_to_softoken_attrs) {
  if (map_to_softoken_attrs) {
    return TranslateKeyAttributeTypeForSoftoken(type);
  }

  switch (type) {
    case KeyAttributeType::kCertificateProvisioningId:
      return pkcs11_custom_attributes::kCkaChromeOsBuiltinProvisioningProfileId;
    case KeyAttributeType::kKeyPermissions:
      return pkcs11_custom_attributes::kCkaChromeOsKeyPermissions;
  }
}

// Does the actual attribute value setting. Called by
// SetAttributeForKeyWithDb().
void SetAttributeForKeyWithDbOnWorkerThread(
    std::unique_ptr<SetAttributeForKeyState> state) {
  DCHECK(state->slot_.get());

  crypto::ScopedSECKEYPrivateKey private_key =
      GetPrivateKey(state->public_key_spki_der_, state->slot_.get());
  if (!private_key) {
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  // This SECItem will point to data owned by |state| so it is not necessary to
  // use ScopedSECItem.
  SECItem attribute_value;
  attribute_value.data = const_cast<uint8_t*>(state->attribute_value_.data());
  attribute_value.len = state->attribute_value_.size();
  if (PK11_WriteRawAttribute(
          /*objType=*/PK11_TypePrivKey, private_key.get(),
          state->attribute_type_, &attribute_value) != SECSuccess) {
    state->OnError(FROM_HERE, Status::kErrorKeyAttributeSettingFailed);
    return;
  }

  state->OnSuccess(FROM_HERE);
}

// Continues setting the attribute with the obtained NSSCertDatabase.
// Called by SetAttributeForKey().
void SetAttributeForKeyWithDb(std::unique_ptr<SetAttributeForKeyState> state,
                              net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
  // This task could interact with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&SetAttributeForKeyWithDbOnWorkerThread,
                     std::move(state)));
}

// Does the actual attribute value retrieval. Called by
// GetAttributeForKeyWithDb().
void GetAttributeForKeyWithDbOnWorkerThread(
    std::unique_ptr<GetAttributeForKeyState> state) {
  DCHECK(state->slot_.get());

  crypto::ScopedSECKEYPrivateKey private_key =
      GetPrivateKey(state->public_key_spki_der_, state->slot_.get());

  if (!private_key) {
    state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
    return;
  }

  crypto::ScopedSECItem attribute_value(SECITEM_AllocItem(/*arena=*/nullptr,
                                                          /*item=*/nullptr,
                                                          /*len=*/0));
  DCHECK(attribute_value.get());

  if (PK11_ReadRawAttribute(
          /*objType=*/PK11_TypePrivKey, private_key.get(),
          state->attribute_type_, attribute_value.get()) != SECSuccess) {
    // CKR_ATTRIBUTE_TYPE_INVALID is a cryptoki function return value which is
    // returned by Chaps if the attribute was not set before for the key. NSS
    // maps this error to SEC_ERROR_BAD_DATA. This error is captured here so as
    // not to return an |error| in cases of retrieving unset key attributes and
    // to return nullopt |attribute_value| instead.
    int error = PORT_GetError();
    if (error == SEC_ERROR_BAD_DATA) {
      state->OnSuccess(FROM_HERE, /*attribute_value=*/std::nullopt);
      return;
    }

    state->OnError(FROM_HERE, Status::kErrorKeyAttributeRetrievalFailed);
    return;
  }

  std::string attribute_value_str;
  if (attribute_value->len > 0) {
    attribute_value_str.assign(attribute_value->data,
                               attribute_value->data + attribute_value->len);
  }

  state->OnSuccess(FROM_HERE, ScopedSECItemToBytes(attribute_value));
}

// Continues retrieving the attribute with the obtained NSSCertDatabase.
// Called by GetAttributeForKey().
void GetAttributeForKeyWithDb(std::unique_ptr<GetAttributeForKeyState> state,
                              net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
  // This task could interact with the TPM, hence MayBlock().
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&GetAttributeForKeyWithDbOnWorkerThread,
                     std::move(state)));
}

void IsKeyOnTokenWithDb(std::unique_ptr<IsKeyOnTokenState> state,
                        net::NSSCertDatabase* cert_db) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(state->slot_.get());

  bool key_on_slot =
      GetPrivateKey(state->public_key_spki_der_, state->slot_.get()) != nullptr;
  state->OnSuccess(FROM_HERE, key_on_slot);
}

}  // namespace

void PlatformKeysServiceImpl::GenerateSymKey(TokenId token_id,
                                             std::vector<uint8_t> key_id,
                                             int key_size,
                                             SymKeyType key_type,
                                             GenerateKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<GenerateSymKeyState>(
      weak_factory_.GetWeakPtr(), std::move(key_id), key_size, key_type,
      std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();
  GetCertDatabase(token_id,
                  base::BindOnce(&GenerateSymKeyWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::GenerateRSAKey(TokenId token_id,
                                             unsigned int modulus_length_bits,
                                             bool sw_backed,
                                             GenerateKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<GenerateRSAKeyState>(
      weak_factory_.GetWeakPtr(), modulus_length_bits, sw_backed, token_id,
      std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }
  if (modulus_length_bits > kMaxRSAModulusLengthBits) {
    state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();
  GetCertDatabase(token_id,
                  base::BindOnce(&GenerateRSAKeyWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::GenerateECKey(TokenId token_id,
                                            const std::string named_curve,
                                            GenerateKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<GenerateECKeyState>(
      weak_factory_.GetWeakPtr(), std::move(named_curve), token_id,
      std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }
  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();
  GetCertDatabase(token_id,
                  base::BindOnce(&GenerateECKeyWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::EncryptDecryptAES(
    chromeos::platform_keys::TokenId token_id,
    std::vector<uint8_t>& input_data,
    std::vector<uint8_t>& key_id,
    std::string& algorithm,
    std::vector<uint8_t>& init_vector,
    EncryptDecryptCallback callback,
    OperationType operation_type) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<EncryptDecryptState>(
      weak_factory_.GetWeakPtr(), std::move(input_data), std::move(key_id),
      std::move(algorithm), std::move(init_vector), operation_type,
      std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes and we can double check that we
  // use a key of the correct token.
  GetCertDatabase(token_id,
                  base::BindOnce(&EncryptDecryptAESWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::DecryptAES(
    chromeos::platform_keys::TokenId token_id,
    std::vector<uint8_t> encrypted_data,
    std::vector<uint8_t> key_id,
    std::string decrypt_algorithm,
    std::vector<uint8_t> init_vector,
    EncryptDecryptCallback callback) {
  EncryptDecryptAES(token_id, encrypted_data, key_id, decrypt_algorithm,
                    init_vector, std::move(callback), OperationType::kDecrypt);
}

void PlatformKeysServiceImpl::EncryptAES(
    chromeos::platform_keys::TokenId token_id,
    std::vector<uint8_t> data,
    std::vector<uint8_t> key_id,
    std::string encrypt_algorithm,
    std::vector<uint8_t> init_vector,
    EncryptDecryptCallback callback) {
  EncryptDecryptAES(token_id, data, key_id, encrypt_algorithm, init_vector,
                    std::move(callback), OperationType::kEncrypt);
}

void PlatformKeysServiceImpl::SignRsaPkcs1(
    std::optional<TokenId> token_id,
    std::vector<uint8_t> data,
    std::vector<uint8_t> public_key_spki_der,
    HashAlgorithm hash_algorithm,
    SignCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<SignState>(
      weak_factory_.GetWeakPtr(), std::move(data),
      std::move(public_key_spki_der), hash_algorithm,
      /*key_type=*/KeyType::kRsassaPkcs1V15, std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes and we can double check that we
  // use a key of the correct token.
  GetCertDatabase(token_id, base::BindOnce(&SignWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::SignRSAPKCS1Raw(
    std::optional<TokenId> token_id,
    std::vector<uint8_t> data,
    std::vector<uint8_t> public_key_spki_der,
    SignCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<SignState>(
      weak_factory_.GetWeakPtr(), std::move(data),
      std::move(public_key_spki_der), HashAlgorithm::HASH_ALGORITHM_NONE,
      /*key_type=*/KeyType::kRsassaPkcs1V15, std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes and we can double check that we
  // use a key of the correct token.
  GetCertDatabase(token_id, base::BindOnce(&SignWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::SignEcdsa(
    std::optional<TokenId> token_id,
    std::vector<uint8_t> data,
    std::vector<uint8_t> public_key_spki_der,
    HashAlgorithm hash_algorithm,
    SignCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<SignState>(
      weak_factory_.GetWeakPtr(), std::move(data),
      std::move(public_key_spki_der), hash_algorithm,
      /*key_type=*/KeyType::kEcdsa, std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes and we can double check that we
  // use a key of the correct token.
  GetCertDatabase(token_id, base::BindOnce(&SignWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::SignWithSymKey(std::optional<TokenId> token_id,
                                             std::vector<uint8_t> data,
                                             std::vector<uint8_t> key_id,
                                             SignCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<SignSymState>(
      weak_factory_.GetWeakPtr(), std::move(data), std::move(key_id),
      std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes and we can double check that we
  // use a key of the correct token.
  GetCertDatabase(token_id, base::BindOnce(&SignSymWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::SelectClientCertificates(
    std::vector<std::string> certificate_authorities,
    SelectCertificatesCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto cert_request_info = base::MakeRefCounted<net::SSLCertRequestInfo>();

  // Currently we do not pass down the requested certificate type to the net
  // layer, as it does not support filtering certificates by type. Rather, we
  // do not constrain the certificate type here, instead the caller has to apply
  // filtering afterwards.
  cert_request_info->cert_authorities = std::move(certificate_authorities);

  auto state = std::make_unique<SelectCertificatesState>(
      weak_factory_.GetWeakPtr(), cert_request_info, std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  state->cert_store_ = delegate_->CreateClientCertStore();

  // Note DidSelectCertificates() may be called synchronously.
  SelectCertificatesState* state_ptr = state.get();
  state_ptr->cert_store_->GetClientCerts(
      state_ptr->cert_request_info_,
      base::BindOnce(&DidSelectCertificates, std::move(state)));
}

void PlatformKeysServiceImpl::GetCertificates(
    TokenId token_id,
    GetCertificatesCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<GetCertificatesState>(
      weak_factory_.GetWeakPtr(), std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }
  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();
  GetCertDatabase(token_id,
                  base::BindOnce(&GetCertificatesWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::GetAllKeys(TokenId token_id,
                                         GetAllKeysCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto state = std::make_unique<GetAllKeysState>(weak_factory_.GetWeakPtr(),
                                                 std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();
  GetCertDatabase(token_id, base::BindOnce(&GetAllKeysWithDb, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::ImportCertificate(
    TokenId token_id,
    const scoped_refptr<net::X509Certificate>& certificate,
    ImportCertificateCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<ImportCertificateState>(
      weak_factory_.GetWeakPtr(), certificate, std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }
  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes and we can double check that we
  // use a key of the correct token.
  GetCertDatabase(token_id,
                  base::BindOnce(&ImportCertificateWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::RemoveCertificate(
    TokenId token_id,
    const scoped_refptr<net::X509Certificate>& certificate,
    RemoveCertificateCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<RemoveCertificateState>(
      weak_factory_.GetWeakPtr(), certificate, std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }
  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes.
  GetCertDatabase(token_id,
                  base::BindOnce(&RemoveCertificateWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::RemoveKey(
    TokenId token_id,
    std::vector<uint8_t> public_key_spki_der,
    RemoveKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto state = std::make_unique<RemoveKeyState>(weak_factory_.GetWeakPtr(),
                                                std::move(public_key_spki_der),
                                                std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes.
  GetCertDatabase(token_id, base::BindOnce(&RemoveKeyWithDb, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::RemoveSymKey(TokenId token_id,
                                           std::vector<uint8_t> key_id,
                                           RemoveKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto state = std::make_unique<RemoveSymKeyState>(
      weak_factory_.GetWeakPtr(), std::move(key_id), std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. But in case it's not available
  // we would get more informative status codes.
  GetCertDatabase(token_id,
                  base::BindOnce(&RemoveSymKeyWithDb, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::GetTokens(GetTokensCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<GetTokensState>(weak_factory_.GetWeakPtr(),
                                                std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }
  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();
  GetCertDatabase(/*token_id=*/std::nullopt /* don't get any specific slot */,
                  base::BindOnce(&GetTokensWithDB, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::GetKeyLocations(
    std::vector<uint8_t> public_key_spki_der,
    GetKeyLocationsCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto state = std::make_unique<GetKeyLocationsState>(
      weak_factory_.GetWeakPtr(), std::move(public_key_spki_der),
      std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }
  NSSOperationState* state_ptr = state.get();

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  GetCertDatabase(
      /*token_id=*/std::nullopt /* don't get any specific slot */,
      base::BindOnce(&GetKeyLocationsWithDB, std::move(state)), delegate_.get(),
      state_ptr);
}

void PlatformKeysServiceImpl::SetAttributeForKey(
    TokenId token_id,
    std::vector<uint8_t> public_key_spki_der,
    KeyAttributeType attribute_type,
    std::vector<uint8_t> attribute_value,
    SetAttributeForKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  CK_ATTRIBUTE_TYPE ck_attribute_type = TranslateKeyAttributeType(
      attribute_type,
      /*map_to_softoken_attrs=*/IsSetMapToSoftokenAttrsForTesting());

  auto state = std::make_unique<SetAttributeForKeyState>(
      weak_factory_.GetWeakPtr(), std::move(public_key_spki_der),
      ck_attribute_type, std::move(attribute_value), std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. Only setting the state slot is
  // required.
  GetCertDatabase(token_id,
                  base::BindOnce(&SetAttributeForKeyWithDb, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::GetAttributeForKey(
    TokenId token_id,
    std::vector<uint8_t> public_key_spki_der,
    KeyAttributeType attribute_type,
    GetAttributeForKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  CK_ATTRIBUTE_TYPE ck_attribute_type = TranslateKeyAttributeType(
      attribute_type,
      /*map_to_softoken_attrs=*/IsSetMapToSoftokenAttrsForTesting());

  auto state = std::make_unique<GetAttributeForKeyState>(
      weak_factory_.GetWeakPtr(), std::move(public_key_spki_der),
      ck_attribute_type, std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. Only setting the state slot is
  // required.
  GetCertDatabase(token_id,
                  base::BindOnce(&GetAttributeForKeyWithDb, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::IsKeyOnToken(
    TokenId token_id,
    std::vector<uint8_t> public_key_spki_der,
    IsKeyOnTokenCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto state = std::make_unique<IsKeyOnTokenState>(
      weak_factory_.GetWeakPtr(), std::move(public_key_spki_der),
      std::move(callback));
  if (delegate_->IsShutDown()) {
    state->OnError(FROM_HERE, Status::kErrorShutDown);
    return;
  }

  // Get the pointer to |state| before transferring ownership of |state| to the
  // callback's bound arguments.
  NSSOperationState* state_ptr = state.get();

  // The NSSCertDatabase object is not required. Only setting the state slot is
  // required.
  GetCertDatabase(token_id,
                  base::BindOnce(&IsKeyOnTokenWithDb, std::move(state)),
                  delegate_.get(), state_ptr);
}

void PlatformKeysServiceImpl::SetMapToSoftokenAttrsForTesting(
    bool map_to_softoken_attrs_for_testing) {
  map_to_softoken_attrs_for_testing_ = map_to_softoken_attrs_for_testing;
}

bool PlatformKeysServiceImpl::IsSetMapToSoftokenAttrsForTesting() {
  return map_to_softoken_attrs_for_testing_;
}

}  // namespace ash::platform_keys