chromium/chrome/browser/chromeos/platform_keys/extension_platform_keys_service.cc

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

#include "chrome/browser/chromeos/platform_keys/extension_platform_keys_service.h"

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

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/values.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chromeos/platform_keys/extension_key_permissions_service.h"
#include "chrome/browser/chromeos/platform_keys/extension_key_permissions_service_factory.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/crosapi/cpp/keystore_service_util.h"
#include "chromeos/crosapi/mojom/keystore_error.mojom-shared.h"
#include "chromeos/crosapi/mojom/keystore_error.mojom.h"
#include "chromeos/crosapi/mojom/keystore_service.mojom.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/features/behavior_feature.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_provider.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/cert/x509_certificate.h"

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/lacros/lacros_service.h"
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/crosapi/keystore_service_ash.h"
#include "chrome/browser/ash/crosapi/keystore_service_factory_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#endif  // #if BUILDFLAG(IS_CHROMEOS_ASH)

using content::BrowserThread;
using crosapi::keystore_service_util::MakeEcKeystoreSigningAlgorithm;
using crosapi::keystore_service_util::MakeRsaKeystoreSigningAlgorithm;
using crosapi::mojom::KeystoreBinaryResult;
using crosapi::mojom::KeystoreBinaryResultPtr;
using crosapi::mojom::KeystoreECDSAParams;
using crosapi::mojom::KeystoreECDSAParamsPtr;
using crosapi::mojom::KeystoreError;
using crosapi::mojom::KeystorePKCS115Params;
using crosapi::mojom::KeystorePKCS115ParamsPtr;
using crosapi::mojom::KeystoreSelectClientCertificatesResult;
using crosapi::mojom::KeystoreSelectClientCertificatesResultPtr;
using crosapi::mojom::KeystoreService;
using crosapi::mojom::KeystoreSigningAlgorithm;
using crosapi::mojom::KeystoreSigningAlgorithmPtr;
using crosapi::mojom::KeystoreSigningScheme;
using crosapi::mojom::KeystoreType;

namespace chromeos {

namespace {

// Verify the allowlisted kKeyPermissionsInLoginScreen feature behaviors.
bool IsExtensionAllowlisted(const extensions::Extension* extension) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  // Can be nullptr if the extension is uninstalled before the SignTask is
  // completed.
  if (!extension)
    return false;

  const extensions::Feature* key_permissions_in_login_screen =
      extensions::FeatureProvider::GetBehaviorFeature(
          extensions::behavior_feature::kKeyPermissionsInLoginScreen);

  return key_permissions_in_login_screen->IsAvailableToExtension(extension)
      .is_available();
#else
  return false;
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
}

KeystoreType KeystoreTypeFromTokenId(platform_keys::TokenId token_id) {
  switch (token_id) {
    case platform_keys::TokenId::kUser:
      return KeystoreType::kUser;
    case platform_keys::TokenId::kSystem:
      return KeystoreType::kDevice;
  }
}

KeystoreSigningScheme GetKeystoreSigningScheme(
    platform_keys::KeyType key_type,
    platform_keys::HashAlgorithm hash_algorithm) {
  switch (key_type) {
    case platform_keys::KeyType::kRsassaPkcs1V15: {
      switch (hash_algorithm) {
        case platform_keys::HASH_ALGORITHM_NONE:
          return KeystoreSigningScheme::kRsassaPkcs1V15None;
        case platform_keys::HASH_ALGORITHM_SHA1:
          return KeystoreSigningScheme::kRsassaPkcs1V15Sha1;
        case platform_keys::HASH_ALGORITHM_SHA256:
          return KeystoreSigningScheme::kRsassaPkcs1V15Sha256;
        case platform_keys::HASH_ALGORITHM_SHA384:
          return KeystoreSigningScheme::kRsassaPkcs1V15Sha384;
        case platform_keys::HASH_ALGORITHM_SHA512:
          return KeystoreSigningScheme::kRsassaPkcs1V15Sha512;
      }
    }
    case platform_keys::KeyType::kEcdsa: {
      switch (hash_algorithm) {
        case platform_keys::HASH_ALGORITHM_NONE:
          // This combination is not supported.
          return KeystoreSigningScheme::kUnknown;
        case platform_keys::HASH_ALGORITHM_SHA1:
          return KeystoreSigningScheme::kEcdsaSha1;
        case platform_keys::HASH_ALGORITHM_SHA256:
          return KeystoreSigningScheme::kEcdsaSha256;
        case platform_keys::HASH_ALGORITHM_SHA384:
          return KeystoreSigningScheme::kEcdsaSha384;
        case platform_keys::HASH_ALGORITHM_SHA512:
          return KeystoreSigningScheme::kEcdsaSha512;
      }
    }
  }
  NOTREACHED_IN_MIGRATION();
  return KeystoreSigningScheme::kUnknown;
}

// Returns appropriate KeystoreService for |browser_context|.
//
// For Lacros-Chrome it returns a remote mojo implementation owned by
// LacrosService (that is created before the start of the main loop and should
// outlive ExtensionPlatformKeysService).
//
// For Ash-Chrome the factory can return:
// * an instance owned by CrosapiManager (that is created before profiles and
// should outlive ExtensionPlatformKeysService)
// * or an appropriate keyed service that will always exist
// during ExtensionPlatformKeysService lifetime (because of KeyedService
// dependencies).
crosapi::mojom::KeystoreService* GetKeystoreService(
    content::BrowserContext* browser_context) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
  // TODO(b/191958380): Lift the restriction when *.platformKeys.* APIs are
  // implemented for secondary profiles in Lacros.
  CHECK(Profile::FromBrowserContext(browser_context)->IsMainProfile())
      << "Attempted to use an incorrect profile. Please file a bug at "
         "https://bugs.chromium.org/ if this happens.";

  chromeos::LacrosService* service = chromeos::LacrosService::Get();
  if (!service || !service->IsAvailable<crosapi::mojom::KeystoreService>()) {
    return nullptr;
  }
  return service->GetRemote<crosapi::mojom::KeystoreService>().get();
#endif  // #if BUILDFLAG(IS_CHROMEOS_LACROS)

#if BUILDFLAG(IS_CHROMEOS_ASH)
  return crosapi::KeystoreServiceFactoryAsh::GetForBrowserContext(
      browser_context);
#endif  // #if BUILDFLAG(IS_CHROMEOS_LACROS)
}

}  // namespace

class ExtensionPlatformKeysService::Task {
 public:
  Task() = default;
  Task(const Task&) = delete;
  auto operator=(const Task&) = delete;
  virtual ~Task() = default;
  virtual void Start() = 0;
  virtual bool IsDone() = 0;
};

class ExtensionPlatformKeysService::GenerateKeyTask : public Task {
 public:
  enum class Step {
    GENERATE_KEY,
    GET_EXTENSION_PERMISSIONS,
    UPDATE_PERMISSIONS_AND_CALLBACK,
    DONE,
  };

  GenerateKeyTask(platform_keys::TokenId token_id,
                  extensions::ExtensionId extension_id,
                  GenerateKeyCallback callback,
                  ExtensionPlatformKeysService* service)
      : token_id_(token_id),
        extension_id_(std::move(extension_id)),
        callback_(std::move(callback)),
        service_(service) {}

  GenerateKeyTask(const GenerateKeyTask&) = delete;
  auto operator=(const GenerateKeyTask&) = delete;

  ~GenerateKeyTask() override = default;

  void Start() override {
    CHECK(next_step_ == Step::GENERATE_KEY);
    DoStep();
  }

  bool IsDone() override { return next_step_ == Step::DONE; }

 protected:
  virtual void GenerateKey(KeystoreService::GenerateKeyCallback callback) = 0;

  platform_keys::TokenId token_id_;
  std::vector<uint8_t> public_key_spki_der_;
  const extensions::ExtensionId extension_id_;
  GenerateKeyCallback callback_;
  std::unique_ptr<platform_keys::ExtensionKeyPermissionsService>
      extension_key_permissions_service_;
  const raw_ptr<ExtensionPlatformKeysService> service_;

 private:
  void DoStep() {
    switch (next_step_) {
      case Step::GENERATE_KEY:
        next_step_ = Step::GET_EXTENSION_PERMISSIONS;
        GenerateKey(base::BindOnce(&GenerateKeyTask::GeneratedKey,
                                   weak_factory_.GetWeakPtr()));
        return;
      case Step::GET_EXTENSION_PERMISSIONS:
        next_step_ = Step::UPDATE_PERMISSIONS_AND_CALLBACK;
        GetExtensionPermissions();
        return;
      case Step::UPDATE_PERMISSIONS_AND_CALLBACK:
        next_step_ = Step::DONE;
        UpdatePermissionsAndCallBack();
        return;
      case Step::DONE:
        service_->TaskFinished(this);
        // |this| might be invalid now.
        return;
    }
  }

  // Stores the generated key or in case of an error calls |callback_| with the
  // error status.
  void GeneratedKey(KeystoreBinaryResultPtr result) {
    using Tag = KeystoreBinaryResult::Tag;
    switch (result->which()) {
      case Tag::kError:
        next_step_ = Step::DONE;
        std::move(callback_).Run(std::vector<uint8_t>() /* no public key */,
                                 result->get_error());
        break;
      case Tag::kBlob:
        public_key_spki_der_ = std::move(result->get_blob());
        break;
    }
    DoStep();
  }

  // Gets the permissions for the extension with id |extension_id|.
  void GetExtensionPermissions() {
    platform_keys::ExtensionKeyPermissionsServiceFactory::
        GetForBrowserContextAndExtension(
            base::BindOnce(&GenerateKeyTask::GotPermissions,
                           weak_factory_.GetWeakPtr()),
            service_->browser_context_, extension_id_);
  }

  void GotPermissions(
      std::unique_ptr<platform_keys::ExtensionKeyPermissionsService>
          extension_key_permissions_service) {
    extension_key_permissions_service_ =
        std::move(extension_key_permissions_service);
    DoStep();
  }

  void UpdatePermissionsAndCallBack() {
    extension_key_permissions_service_->RegisterKeyForCorporateUsage(
        public_key_spki_der_,
        base::BindOnce(&GenerateKeyTask::OnKeyRegisteredForCorporateUsage,
                       weak_factory_.GetWeakPtr()));
  }

  void OnKeyRegisteredForCorporateUsage(bool is_error,
                                        crosapi::mojom::KeystoreError error) {
    if (!is_error) {
      std::move(callback_).Run(std::move(public_key_spki_der_),
                               /*error=*/std::nullopt);
      DoStep();
      return;
    }

    LOG(ERROR) << "Corporate key registration failed: "
               << platform_keys::KeystoreErrorToString(error);

    service_->keystore_service_->RemoveKey(
        KeystoreTypeFromTokenId(token_id_), std::move(public_key_spki_der_),
        base::BindOnce(&GenerateKeyTask::RemoveKeyCallback,
                       weak_factory_.GetWeakPtr(),
                       /*corporate_key_registration_error_status=*/error));
  }

  void RemoveKeyCallback(
      crosapi::mojom::KeystoreError corporate_key_registration_error,
      bool is_remove_error,
      KeystoreError remove_error) {
    if (is_remove_error) {
      LOG(ERROR)
          << "Failed to remove a dangling key with error: "
          << platform_keys::KeystoreErrorToString(remove_error)
          << ", after failing to register key for corporate usage with error: "
          << platform_keys::KeystoreErrorToString(
                 corporate_key_registration_error);
    }

    next_step_ = Step::DONE;
    std::move(callback_).Run(std::vector<uint8_t>() /* no public key */,
                             corporate_key_registration_error);
    DoStep();
  }

  Step next_step_ = Step::GENERATE_KEY;

  base::WeakPtrFactory<GenerateKeyTask> weak_factory_{this};
};

class ExtensionPlatformKeysService::GenerateRSAKeyTask
    : public GenerateKeyTask {
 public:
  // This key task generates an RSA key with the parameters |token_id| and
  // |modulus_length| and registers it for the extension with id |extension_id|.
  // The generated key will be passed to |callback|.
  GenerateRSAKeyTask(platform_keys::TokenId token_id,
                     unsigned int modulus_length,
                     bool sw_backed,
                     const std::string& extension_id,
                     GenerateKeyCallback callback,
                     ExtensionPlatformKeysService* service)
      : GenerateKeyTask(token_id, extension_id, std::move(callback), service),
        modulus_length_(modulus_length),
        sw_backed_(sw_backed) {}

  ~GenerateRSAKeyTask() override = default;

 private:
  // Generates the RSA key.
  void GenerateKey(KeystoreService::GenerateKeyCallback callback) override {
    service_->keystore_service_->GenerateKey(
        KeystoreTypeFromTokenId(token_id_),
        MakeRsaKeystoreSigningAlgorithm(modulus_length_, sw_backed_),
        std::move(callback));
  }

  const unsigned int modulus_length_;
  const bool sw_backed_;
};

class ExtensionPlatformKeysService::GenerateECKeyTask : public GenerateKeyTask {
 public:
  // This Task generates an EC key with the parameters |token_id| and
  // |named_curve| and registers it for the extension with id |extension_id|.
  // The generated key will be passed to |callback|.
  GenerateECKeyTask(platform_keys::TokenId token_id,
                    std::string named_curve,
                    std::string extension_id,
                    GenerateKeyCallback callback,
                    ExtensionPlatformKeysService* service)
      : GenerateKeyTask(token_id,
                        std::move(extension_id),
                        std::move(callback),
                        service),
        named_curve_(std::move(named_curve)) {}

  ~GenerateECKeyTask() override = default;

 private:
  // Generates the EC key.
  void GenerateKey(KeystoreService::GenerateKeyCallback callback) override {
    service_->keystore_service_->GenerateKey(
        KeystoreTypeFromTokenId(token_id_),
        MakeEcKeystoreSigningAlgorithm(named_curve_), std::move(callback));
  }

  const std::string named_curve_;
};

class ExtensionPlatformKeysService::SignTask : public Task {
 public:
  enum class Step {
    GET_EXTENSION_PERMISSIONS,
    CHECK_SIGN_PERMISSIONS,
    UPDATE_SIGN_PERMISSIONS,
    SIGN,
    DONE,
  };

  // This Task will check the permissions of the extension with |extension_id|
  // for the key of type |key_type| identified by |public_key_spki_der|. If the
  // permission check was positive, signs |data| with the key and passes the
  // signature to |callback|. If the extension is not allowed to use the key
  // multiple times, also updates the permission to prevent any future signing
  // operation of that extension using that same key. If an error occurs, an
  // error status is passed to |callback|.
  SignTask(std::optional<platform_keys::TokenId> token_id,
           std::vector<uint8_t> data,
           std::vector<uint8_t> public_key_spki_der,
           platform_keys::KeyType key_type,
           platform_keys::HashAlgorithm hash_algorithm,
           std::string extension_id,
           SignCallback callback,
           ExtensionPlatformKeysService* service)
      : token_id_(token_id),
        data_(std::move(data)),
        public_key_spki_der_(std::move(public_key_spki_der)),
        extension_id_(std::move(extension_id)),
        callback_(std::move(callback)),
        service_(service) {
    signing_scheme_ = GetKeystoreSigningScheme(key_type, hash_algorithm);
    DCHECK(signing_scheme_ != KeystoreSigningScheme::kUnknown);
  }

  SignTask(const SignTask&) = delete;
  auto operator=(const SignTask&) = delete;

  ~SignTask() override = default;

  void Start() override {
    CHECK(next_step_ == Step::GET_EXTENSION_PERMISSIONS);
    DoStep();
  }

  bool IsDone() override { return next_step_ == Step::DONE; }

 private:
  void DoStep() {
    switch (next_step_) {
      case Step::GET_EXTENSION_PERMISSIONS:
        next_step_ = Step::CHECK_SIGN_PERMISSIONS;
        GetExtensionPermissions();
        return;
      case Step::CHECK_SIGN_PERMISSIONS:
        next_step_ = Step::UPDATE_SIGN_PERMISSIONS;
        CheckSignPermissions();
        return;
      case Step::UPDATE_SIGN_PERMISSIONS:
        next_step_ = Step::SIGN;
        UpdateSignPermissions();
        return;
      case Step::SIGN:
        next_step_ = Step::DONE;
        Sign();
        return;
      case Step::DONE:
        service_->TaskFinished(this);
        // |this| might be invalid now.
        return;
    }
  }

  void GetExtensionPermissions() {
    platform_keys::ExtensionKeyPermissionsServiceFactory::
        GetForBrowserContextAndExtension(
            base::BindOnce(&SignTask::GotPermissions,
                           weak_factory_.GetWeakPtr()),
            service_->browser_context_, extension_id_);
  }

  void GotPermissions(
      std::unique_ptr<platform_keys::ExtensionKeyPermissionsService>
          extension_key_permissions_service) {
    extension_key_permissions_service_ =
        std::move(extension_key_permissions_service);
    DoStep();
  }

  void CheckSignPermissions() {
    const extensions::Extension* extension =
        extensions::ExtensionRegistry::Get(service_->browser_context_)
            ->enabled_extensions()
            .GetByID(extension_id_);
    if (service_->IsUsingSigninProfile() && IsExtensionAllowlisted(extension)) {
      DoStep();
      return;
    }

    extension_key_permissions_service_->CanUseKeyForSigning(
        public_key_spki_der_,
        base::BindOnce(&SignTask::OnCanUseKeyForSigningKnown,
                       weak_factory_.GetWeakPtr()));
  }

  void OnCanUseKeyForSigningKnown(bool allowed) {
    if (!allowed) {
      std::move(callback_).Run(std::vector<uint8_t>() /* no signature */,
                               KeystoreError::kKeyNotAllowedForSigning);
      next_step_ = Step::DONE;
      DoStep();
      return;
    }

    DoStep();
  }

  // Updates the signing permissions for |public_key_spki_der_|.
  void UpdateSignPermissions() {
    extension_key_permissions_service_->SetKeyUsedForSigning(
        public_key_spki_der_,
        base::BindOnce(&SignTask::OnSetKeyUsedForSigningDone,
                       weak_factory_.GetWeakPtr()));
  }

  void OnSetKeyUsedForSigningDone(bool is_error,
                                  crosapi::mojom::KeystoreError error) {
    if (is_error) {
      LOG(ERROR) << "Marking a key used for signing failed: "
                 << platform_keys::KeystoreErrorToString(error);
      next_step_ = Step::DONE;
      std::move(callback_).Run(std::vector<uint8_t>() /* no signature */,
                               error);
      DoStep();
      return;
    }

    DoStep();
  }

  // Starts the actual signing operation and afterwards passes the signature (or
  // error) to |callback_|.
  void Sign() {
    // TODO(crbug.com/40489779): This can be simplified when mojo supports
    // optional enums.
    bool is_keystore_provided = false;
    KeystoreType keystore = KeystoreType::kUser;
    if (token_id_.has_value()) {
      is_keystore_provided = true;
      keystore = KeystoreTypeFromTokenId(token_id_.value());
    }

    service_->keystore_service_->Sign(
        is_keystore_provided, keystore, public_key_spki_der_, signing_scheme_,
        data_, base::BindOnce(&SignTask::DidSign, weak_factory_.GetWeakPtr()));
  }

  void DidSign(KeystoreBinaryResultPtr result) {
    switch (result->which()) {
      case KeystoreBinaryResult::Tag::kError:
        std::move(callback_).Run(/*signature=*/std::vector<uint8_t>(),
                                 result->get_error());
        break;
      case KeystoreBinaryResult::Tag::kBlob:
        std::move(callback_).Run(
            /*signature=*/std::move(result->get_blob()),
            /*error=*/std::nullopt);
        break;
    }
    DoStep();
  }

  Step next_step_ = Step::GET_EXTENSION_PERMISSIONS;

  std::optional<platform_keys::TokenId> token_id_;
  const std::vector<uint8_t> data_;
  const std::vector<uint8_t> public_key_spki_der_;

  KeystoreSigningScheme signing_scheme_;
  const extensions::ExtensionId extension_id_;
  SignCallback callback_;
  std::unique_ptr<platform_keys::ExtensionKeyPermissionsService>
      extension_key_permissions_service_;
  const raw_ptr<ExtensionPlatformKeysService> service_;
  base::WeakPtrFactory<SignTask> weak_factory_{this};
};

class ExtensionPlatformKeysService::SelectTask : public Task {
 public:
  enum class Step {
    GET_EXTENSION_PERMISSIONS,
    GET_MATCHING_CERTS,
    CHECK_KEY_PERMISSIONS,
    INTERSECT_WITH_INPUT_CERTS,
    SELECT_CERTS,
    UPDATE_PERMISSION,
    PASS_RESULTING_CERTS,
    DONE,
  };

  // This task determines all known client certs matching |request| and that are
  // elements of |input_client_certificates|, if given. If |interactive| is
  // true, calls |service->select_delegate_->Select()| to select a cert from all
  // matches. The extension with |extension_id| will be granted unlimited sign
  // permission for the selected cert. Finally, either the selection or, if
  // |interactive| is false, matching certs that the extension has permission
  // for are passed to |callback|.
  SelectTask(const platform_keys::ClientCertificateRequest& request,
             std::unique_ptr<net::CertificateList> input_client_certificates,
             bool interactive,
             std::string extension_id,
             SelectCertificatesCallback callback,
             content::WebContents* web_contents,
             ExtensionPlatformKeysService* service)
      : request_(request),
        input_client_certificates_(std::move(input_client_certificates)),
        interactive_(interactive),
        extension_id_(std::move(extension_id)),
        callback_(std::move(callback)),
        web_contents_(web_contents),
        service_(service) {}

  SelectTask(const SelectTask&) = delete;
  auto operator=(const SelectTask) = delete;

  ~SelectTask() override = default;

  void Start() override {
    CHECK(next_step_ == Step::GET_EXTENSION_PERMISSIONS);
    DoStep();
  }

  bool IsDone() override { return next_step_ == Step::DONE; }

 private:
  void DoStep() {
    switch (next_step_) {
      case Step::GET_EXTENSION_PERMISSIONS:
        next_step_ = Step::GET_MATCHING_CERTS;
        GetExtensionPermissions();
        return;
      case Step::GET_MATCHING_CERTS:
        next_step_ = Step::CHECK_KEY_PERMISSIONS;
        GetMatchingCerts();
        return;
      case Step::CHECK_KEY_PERMISSIONS:
        // Don't advance to the next step yet - CheckKeyPermissions is repeated
        // for all matching certs. The next step will be selected in
        // CheckKeyPermissions.
        CheckKeyPermissions(Step::INTERSECT_WITH_INPUT_CERTS /* next_step */);
        return;
      case Step::INTERSECT_WITH_INPUT_CERTS:
        if (interactive_)
          next_step_ = Step::SELECT_CERTS;
        else  // Skip SelectCerts and UpdatePermission if not interactive.
          next_step_ = Step::PASS_RESULTING_CERTS;
        IntersectWithInputCerts();
        return;
      case Step::SELECT_CERTS:
        next_step_ = Step::UPDATE_PERMISSION;
        SelectCerts();
        return;
      case Step::UPDATE_PERMISSION:
        next_step_ = Step::PASS_RESULTING_CERTS;
        UpdatePermission();
        return;
      case Step::PASS_RESULTING_CERTS:
        next_step_ = Step::DONE;
        PassResultingCerts();
        return;
      case Step::DONE:
        service_->TaskFinished(this);
        // |this| might be invalid now.
        return;
    }
  }

  void GetExtensionPermissions() {
    platform_keys::ExtensionKeyPermissionsServiceFactory::
        GetForBrowserContextAndExtension(
            base::BindOnce(&SelectTask::GotPermissions,
                           weak_factory_.GetWeakPtr()),
            service_->browser_context_, extension_id_);
  }

  void GotPermissions(
      std::unique_ptr<platform_keys::ExtensionKeyPermissionsService>
          extension_key_permissions_service) {
    extension_key_permissions_service_ =
        std::move(extension_key_permissions_service);
    DoStep();
  }

  // Retrieves all certificates matching |request_|. Will call back to
  // |GotMatchingCerts()|.
  void GetMatchingCerts() {
    service_->keystore_service_->SelectClientCertificates(
        request_.certificate_authorities,
        base::BindOnce(&SelectTask::GotMatchingCerts,
                       weak_factory_.GetWeakPtr()));
  }

  // If the certificate request could be processed successfully, |matches| will
  // contain the list of matching certificates (maybe empty). If an error
  // occurred, |matches| will be null. Note that the order of |matches|, based
  // on the expiration/issuance date, is relevant and must be preserved in any
  // processing of the list.
  void GotMatchingCerts(KeystoreSelectClientCertificatesResultPtr result) {
    if (result->which() ==
        KeystoreSelectClientCertificatesResult::Tag::kError) {
      next_step_ = Step::DONE;
      std::move(callback_).Run(nullptr /* no certificates */,
                               result->get_error());
      DoStep();
      return;
    }

    for (const std::vector<uint8_t>& binary_cert : result->get_certificates()) {
      scoped_refptr<net::X509Certificate> certificate =
          net::X509Certificate::CreateFromBytes(binary_cert);

      // Filter the retrieved certificates returning only those whose type
      // is equal to one of the entries in the type field of the certificate
      // request.
      // If the type field does not contain any entries, certificates of all
      // types shall be returned.
      if (!request_.certificate_key_types.empty()) {
        net::X509Certificate::PublicKeyType actual_key_type =
            net::X509Certificate::kPublicKeyTypeUnknown;
        size_t unused_key_size = 0;
        net::X509Certificate::GetPublicKeyInfo(
            certificate->cert_buffer(), &unused_key_size, &actual_key_type);
        const std::vector<net::X509Certificate::PublicKeyType>& accepted_types =
            request_.certificate_key_types;
        if (!base::Contains(accepted_types, actual_key_type))
          continue;
      }

      matches_pending_permissions_check_.push_back(std::move(certificate));
    }
    DoStep();
  }

  // This is called once for each certificate in
  // |matches_pending_permissions_check_|. Each invocation processes the first
  // element and removes it from the deque. Each processed certificate is added
  // to |matches_| if it is selectable according to
  // ExtensionKeyPermissionsService. When all certificates have been processed,
  // advances the SignTask state machine to |next_step|.
  void CheckKeyPermissions(Step next_step) {
    if (matches_pending_permissions_check_.empty()) {
      next_step_ = next_step;
      DoStep();
      return;
    }

    scoped_refptr<net::X509Certificate> certificate =
        std::move(matches_pending_permissions_check_.front());
    matches_pending_permissions_check_.pop_front();
    std::vector<uint8_t> public_key_spki_der =
        platform_keys::GetSubjectPublicKeyInfoBlob(certificate);

    extension_key_permissions_service_->CanUseKeyForSigning(
        public_key_spki_der,
        base::BindOnce(&SelectTask::OnKeySigningPermissionKnown,
                       weak_factory_.GetWeakPtr(), public_key_spki_der,
                       certificate));
  }

  void OnKeySigningPermissionKnown(
      std::vector<uint8_t> public_key_spki_der,
      const scoped_refptr<net::X509Certificate>& certificate,
      bool allowed) {
    if (allowed) {
      matches_.push_back(certificate);
      DoStep();
    } else if (interactive_) {
      service_->keystore_service_->CanUserGrantPermissionForKey(
          std::move(public_key_spki_der),
          base::BindOnce(&SelectTask::OnAbilityToGrantPermissionKnown,
                         weak_factory_.GetWeakPtr(), std::move(certificate)));
    } else {
      DoStep();
    }
  }

  void OnAbilityToGrantPermissionKnown(
      const scoped_refptr<net::X509Certificate>& certificate,
      bool allowed) {
    if (allowed) {
      matches_.push_back(certificate);
    }
    DoStep();
  }

  // If |input_client_certificates_| is given, removes from |matches_| all
  // certificates that are not elements of |input_client_certificates_|.
  void IntersectWithInputCerts() {
    if (!input_client_certificates_) {
      DoStep();
      return;
    }
    platform_keys::IntersectCertificates(
        matches_, *input_client_certificates_,
        base::BindOnce(&SelectTask::GotIntersection,
                       weak_factory_.GetWeakPtr()));
  }

  void GotIntersection(std::unique_ptr<net::CertificateList> intersection) {
    matches_.swap(*intersection);
    DoStep();
  }

  // Calls |service_->select_delegate_->Select()| to select a cert from
  // |matches_|, which will be stored in |selected_cert_|.
  // Will call back to |GotSelection()|.
  void SelectCerts() {
    CHECK(interactive_);
    if (matches_.empty()) {
      // Don't show a select dialog if no certificate is matching.
      DoStep();
      return;
    }
    service_->select_delegate_->Select(
        extension_id_, matches_,
        base::BindOnce(&SelectTask::GotSelection, weak_factory_.GetWeakPtr()),
        web_contents_, service_->browser_context_);
  }

  // Will be called by |SelectCerts()| with the selected cert or null if no cert
  // was selected.
  void GotSelection(scoped_refptr<net::X509Certificate> selected_cert) {
    selected_cert_ = selected_cert;
    DoStep();
  }

  // Updates the extension's state store about unlimited sign permission for the
  // selected cert. Does nothing if no cert was selected.
  void UpdatePermission() {
    CHECK(interactive_);
    if (!selected_cert_) {
      DoStep();
      return;
    }
    extension_key_permissions_service_->SetUserGrantedPermission(
        platform_keys::GetSubjectPublicKeyInfoBlob(selected_cert_),
        base::BindOnce(&SelectTask::OnPermissionsUpdated,
                       weak_factory_.GetWeakPtr()));
  }

  void OnPermissionsUpdated(bool is_error,
                            crosapi::mojom::KeystoreError error) {
    if (is_error) {
      LOG(WARNING) << "Error while updating permissions: "
                   << platform_keys::KeystoreErrorToString(error);
    }

    DoStep();
  }

  // Passes the filtered certs to |callback_|.
  void PassResultingCerts() {
    std::unique_ptr<net::CertificateList> selection(new net::CertificateList);
    if (interactive_) {
      if (selected_cert_)
        selection->push_back(selected_cert_);
    } else {
      selection->assign(matches_.begin(), matches_.end());
    }

    std::move(callback_).Run(std::move(selection), /*error=*/std::nullopt);
    DoStep();
  }

  Step next_step_ = Step::GET_EXTENSION_PERMISSIONS;

  std::deque<scoped_refptr<net::X509Certificate>>
      matches_pending_permissions_check_;
  net::CertificateList matches_;
  scoped_refptr<net::X509Certificate> selected_cert_;
  platform_keys::ClientCertificateRequest request_;
  std::unique_ptr<net::CertificateList> input_client_certificates_;
  const bool interactive_;
  const extensions::ExtensionId extension_id_;
  SelectCertificatesCallback callback_;
  const raw_ptr<content::WebContents> web_contents_;
  std::unique_ptr<platform_keys::ExtensionKeyPermissionsService>
      extension_key_permissions_service_;
  const raw_ptr<ExtensionPlatformKeysService> service_;
  base::WeakPtrFactory<SelectTask> weak_factory_{this};
};

ExtensionPlatformKeysService::SelectDelegate::SelectDelegate() = default;

ExtensionPlatformKeysService::SelectDelegate::~SelectDelegate() = default;

ExtensionPlatformKeysService::ExtensionPlatformKeysService(
    content::BrowserContext* browser_context)
    : browser_context_(browser_context),
      keystore_service_(GetKeystoreService(browser_context_)) {
  DCHECK(browser_context);
}

ExtensionPlatformKeysService::~ExtensionPlatformKeysService() = default;

void ExtensionPlatformKeysService::SetSelectDelegate(
    std::unique_ptr<SelectDelegate> delegate) {
  select_delegate_ = std::move(delegate);
}

void ExtensionPlatformKeysService::GenerateRSAKey(
    platform_keys::TokenId token_id,
    unsigned int modulus_length,
    bool sw_backed,
    std::string extension_id,
    GenerateKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!keystore_service_) [[unlikely]] {
    std::move(callback).Run(/*public_key_spki_der=*/std::vector<uint8_t>(),
                            crosapi::mojom::KeystoreError::kMojoUnavailable);
    return;
  }

#if BUILDFLAG(IS_CHROMEOS_LACROS)
  if (sw_backed) {
    // Software-backed RSA keys are only supported starting with KeyStore
    // interface version 16.
    // TODO(crbug.com/40793151): Remove this code with M-100.
    const int kSoftwareBackedRsaMinVersion = 16;
    if (!chromeos::LacrosService::Get() ||
        (chromeos::LacrosService::Get()
             ->GetInterfaceVersion<KeystoreService>() <
         kSoftwareBackedRsaMinVersion)) {
      std::move(callback).Run(
          /*public_key_spki_der=*/std::vector<uint8_t>(),
          crosapi::mojom::KeystoreError::kUnsupportedKeyType);
      return;
    }
  }
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

  StartOrQueueTask(std::make_unique<GenerateRSAKeyTask>(
      token_id, modulus_length, sw_backed, std::move(extension_id),
      std::move(callback), this));
}

void ExtensionPlatformKeysService::GenerateECKey(
    platform_keys::TokenId token_id,
    std::string named_curve,
    std::string extension_id,
    GenerateKeyCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!keystore_service_) [[unlikely]] {
    std::move(callback).Run(/*public_key_spki_der=*/std::vector<uint8_t>(),
                            crosapi::mojom::KeystoreError::kMojoUnavailable);
    return;
  }

  StartOrQueueTask(std::make_unique<GenerateECKeyTask>(
      token_id, std::move(named_curve), std::move(extension_id),
      std::move(callback), this));
}

bool ExtensionPlatformKeysService::IsUsingSigninProfile() {
// TODO(crbug.com/40156265) Revisit this place when Lacros-Chrome starts being
// used on the login screen.
#if BUILDFLAG(IS_CHROMEOS_ASH)
  return ash::ProfileHelper::IsSigninProfile(
      Profile::FromBrowserContext(browser_context_));
#else
  return false;
#endif
}

void ExtensionPlatformKeysService::SignDigest(
    std::optional<platform_keys::TokenId> token_id,
    std::vector<uint8_t> data,
    std::vector<uint8_t> public_key_spki_der,
    platform_keys::KeyType key_type,
    platform_keys::HashAlgorithm hash_algorithm,
    std::string extension_id,
    SignCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!keystore_service_) [[unlikely]] {
    std::move(callback).Run(/*signature=*/std::vector<uint8_t>(),
                            crosapi::mojom::KeystoreError::kMojoUnavailable);
    return;
  }

  StartOrQueueTask(std::make_unique<SignTask>(
      token_id, std::move(data), std::move(public_key_spki_der), key_type,
      hash_algorithm, std::move(extension_id), std::move(callback), this));
}

void ExtensionPlatformKeysService::SignRSAPKCS1Raw(
    std::optional<platform_keys::TokenId> token_id,
    std::vector<uint8_t> data,
    std::vector<uint8_t> public_key_spki_der,
    std::string extension_id,
    SignCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!keystore_service_) [[unlikely]] {
    std::move(callback).Run(/*signature=*/std::vector<uint8_t>(),
                            crosapi::mojom::KeystoreError::kMojoUnavailable);
    return;
  }

  StartOrQueueTask(std::make_unique<SignTask>(
      token_id, std::move(data), std::move(public_key_spki_der),
      /*key_type=*/platform_keys::KeyType::kRsassaPkcs1V15,
      platform_keys::HASH_ALGORITHM_NONE, std::move(extension_id),
      std::move(callback), this));
}

void ExtensionPlatformKeysService::SelectClientCertificates(
    const platform_keys::ClientCertificateRequest& request,
    std::unique_ptr<net::CertificateList> client_certificates,
    bool interactive,
    std::string extension_id,
    SelectCertificatesCallback callback,
    content::WebContents* web_contents) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!keystore_service_) [[unlikely]] {
    std::move(callback).Run(/*matches=*/nullptr,
                            crosapi::mojom::KeystoreError::kMojoUnavailable);
    return;
  }

  StartOrQueueTask(std::make_unique<SelectTask>(
      request, std::move(client_certificates), interactive,
      std::move(extension_id), std::move(callback), web_contents, this));
}

void ExtensionPlatformKeysService::StartOrQueueTask(
    std::unique_ptr<Task> task) {
  tasks_.push(std::move(task));
  if (tasks_.size() == 1)
    tasks_.front()->Start();
}

void ExtensionPlatformKeysService::TaskFinished(Task* task) {
  DCHECK(!tasks_.empty());
  DCHECK(task == tasks_.front().get());
  // Remove all finished tasks from the queue (should be at most one).
  while (!tasks_.empty() && tasks_.front()->IsDone())
    tasks_.pop();

  // Now either the queue is empty or the next task is not finished yet and it
  // can be started.
  if (!tasks_.empty())
    tasks_.front()->Start();
}

}  // namespace chromeos