// Copyright 2023 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/ash/cert_provisioning/cert_provisioning_worker_dynamic.h"

#include <stdint.h>

#include <optional>
#include <string_view>
#include <vector>

#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/syslog_logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/ash/attestation/tpm_challenge_key_result.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_client.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_common.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_invalidator.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_metrics.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_serializer.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_worker.h"
#include "chrome/browser/ash/platform_keys/key_permissions/key_permissions_manager.h"
#include "chrome/browser/ash/platform_keys/platform_keys_service.h"
#include "chrome/browser/ash/platform_keys/platform_keys_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/kcer/kcer_utils.h"
#include "chromeos/ash/components/platform_keys/platform_keys.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "content/public/browser/browser_context.h"
#include "net/cert/asn1_util.h"
#include "net/cert/x509_util.h"

// This will execute the `UpdateStateStatement` and return from the current
// function if the worker has reached a final state.
// It can only be used in void functions.
#define RETURN_ON_FINAL_STATE(UpdateStateStatement)               \
  if ((UpdateStateStatement) == UpdateStateResult::kFinalState) { \
    return;                                                       \
  }

// This will execute the `UpdateStateStatement` and expect that the worker is in
// a final state afterwards.
#define FINAL_STATE_EXPECTED(UpdateStateStatement)                \
  if ((UpdateStateStatement) != UpdateStateResult::kFinalState) { \
    NOTREACHED();                                                 \
  }

namespace em = enterprise_management;

namespace ash::cert_provisioning {

namespace {

constexpr unsigned int kNonVaKeyModulusLengthBits = 2048;
constexpr char kEcNamedCurve[] = "P-256";

constexpr base::TimeDelta kMinumumTryAgainLaterDelay = base::Seconds(5);
constexpr base::TimeDelta kInitialFetchInstructionDelay =
    kMinumumTryAgainLaterDelay;
constexpr base::TimeDelta kMaximumFetchInstructionDelay = base::Hours(8);
constexpr base::TimeDelta kOnSubscribedFetchInstructionDelay =
    base::Seconds(30);

const net::BackoffEntry::Policy kBackoffPolicy{
    /*num_errors_to_ignore=*/0,
    /*initial_delay_ms=*/
    base::Seconds(30).InMilliseconds(),
    /*multiply_factor=*/2.0,
    /*jitter_factor=*/0.15,
    /*maximum_backoff_ms=*/base::Hours(12).InMilliseconds(),
    /*entry_lifetime_ms=*/-1,
    /*always_use_initial_delay=*/false};

// Used only for the first 4 attempts, after which
// `kMaximumFetchInstructionDelay` is used. Also an additional
// kInitialFetchInstructionDelay delay is inserted at the beginning. So the
// approximate waiting times (ignoring the jitter) are: 5 sec, 5 sec, 20 sec, 80
// sec, 320 sec, 8 hours, 8 hours, ...
const net::BackoffEntry::Policy kFetchInstructionBackoffPolicy{
    /*num_errors_to_ignore=*/1,
    /*initial_delay_ms=*/
    kInitialFetchInstructionDelay.InMilliseconds(),
    /*multiply_factor=*/4.0,
    /*jitter_factor=*/0.10,
    /*maximum_backoff_ms=*/kMaximumFetchInstructionDelay.InMilliseconds(),
    /*entry_lifetime_ms=*/-1,
    /*always_use_initial_delay=*/true};

// The original message of kUserNotManagedError is misleading in case the user
// is not affiliated. In this case, the error message associated to the error
// code kUserNotManagedError is replaced.
std::string ConstructFailureMessage(
    const attestation::TpmChallengeKeyResult& challenge_result) {
  std::string failure_message = "Failed to build challenge response: ";
  if (challenge_result.result_code ==
      attestation::TpmChallengeKeyResultCode::kUserNotManagedError) {
    return (failure_message +
            "User is not affiliated. Certificate profile is not applicable.");
  }
  return base::StrCat({failure_message, challenge_result.GetErrorMessage()});
}

// TODO(b/192071491): Remove the use of this function by changing the
// dependencies.
std::vector<uint8_t> StrToBytes(std::string_view str) {
  return std::vector<uint8_t>(str.begin(), str.end());
}

// TODO(b/192071491): Remove the use of this function by changing the
// dependencies.
std::string BytesToStr(const std::vector<uint8_t>& blob) {
  return std::string(blob.begin(), blob.end());
}

bool IsInstructionReceivedState(CertProvisioningWorkerState state) {
  switch (state) {
    case CertProvisioningWorkerState::kAuthorizeInstructionReceived:
    case CertProvisioningWorkerState::kProofOfPossessionInstructionReceived:
    case CertProvisioningWorkerState::kImportCertificateInstructionReceived:
      return true;
    default:
      return false;
  }
}

bool IsStateTransitionAllowed(CertProvisioningWorkerState prev_state,
                              CertProvisioningWorkerState new_state) {
  if (prev_state == new_state) {
    return true;
  }
  if (IsFinalState(prev_state)) {
    // No transition out of final states.
    return false;
  }
  if (IsFinalState(new_state)) {
    // It's always possible to go to final states.
    return true;
  }

  switch (prev_state) {
    case CertProvisioningWorkerState::kInitState:
      return new_state == CertProvisioningWorkerState::kKeypairGenerated;
    case CertProvisioningWorkerState::kKeypairGenerated:
      return new_state == CertProvisioningWorkerState::kReadyForNextOperation;
    case CertProvisioningWorkerState::kReadyForNextOperation:
      return IsInstructionReceivedState(new_state);
    case CertProvisioningWorkerState::kAuthorizeInstructionReceived:
      return new_state == CertProvisioningWorkerState::kVaChallengeFinished;
    case CertProvisioningWorkerState::kProofOfPossessionInstructionReceived:
      return new_state == CertProvisioningWorkerState::kSignCsrFinished;
    case CertProvisioningWorkerState::kImportCertificateInstructionReceived:
      // After "Import Cert", only final states are expected.
      return false;
    case CertProvisioningWorkerState::kVaChallengeFinished:
      return new_state == CertProvisioningWorkerState::kKeyRegistered;
    case CertProvisioningWorkerState::kKeyRegistered:
      return new_state == CertProvisioningWorkerState::kKeypairMarked;
    case CertProvisioningWorkerState::kKeypairMarked:
      return new_state == CertProvisioningWorkerState::kReadyForNextOperation;
    case CertProvisioningWorkerState::kSignCsrFinished:
      return new_state == CertProvisioningWorkerState::kReadyForNextOperation;
    case CertProvisioningWorkerState::kSucceeded:
    case CertProvisioningWorkerState::kInconsistentDataError:
    case CertProvisioningWorkerState::kFailed:
    case CertProvisioningWorkerState::kCanceled:
      // These are final state, so they should already be handled above.
      NOTREACHED();
    case CertProvisioningWorkerState::kStartCsrResponseReceived:
    case CertProvisioningWorkerState::kFinishCsrResponseReceived:
      // Not used in "dynamic" flow.
      NOTREACHED();
  }
}

}  // namespace

// ===================== CertProvisioningWorkerDynamic =========================

CertProvisioningWorkerDynamic::CertProvisioningWorkerDynamic(
    std::string cert_provisioning_process_id,
    CertScope cert_scope,
    Profile* profile,
    PrefService* pref_service,
    const CertProfile& cert_profile,
    CertProvisioningClient* cert_provisioning_client,
    std::unique_ptr<CertProvisioningInvalidator> invalidator,
    base::RepeatingClosure state_change_callback,
    CertProvisioningWorkerCallback result_callback)
    : process_id_(cert_provisioning_process_id),
      cert_scope_(cert_scope),
      profile_(profile),
      pref_service_(pref_service),
      cert_profile_(cert_profile),
      state_change_callback_(std::move(state_change_callback)),
      result_callback_(std::move(result_callback)),
      request_backoff_(&kBackoffPolicy),
      fetch_instruction_backoff_(&kFetchInstructionBackoffPolicy),
      cert_provisioning_client_(cert_provisioning_client),
      invalidator_(std::move(invalidator)) {
  CHECK(profile || cert_scope == CertScope::kDevice);
  platform_keys_service_ = GetPlatformKeysService(cert_scope, profile);
  CHECK(platform_keys_service_);

  CHECK(pref_service);
  CHECK(cert_provisioning_client_);
  CHECK(invalidator_);

  // This logs an empty cppId for deserealized workers. A different log line
  // clarifies that.
  LOG(WARNING) << "Started provisioning a certificate" << GetLogInfoBlock();
}

CertProvisioningWorkerDynamic::~CertProvisioningWorkerDynamic() = default;

bool CertProvisioningWorkerDynamic::IsWaiting() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return is_waiting_;
}

bool CertProvisioningWorkerDynamic::IsWorkerMarkedForReset() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return is_schedueled_for_reset_;
}

const std::string& CertProvisioningWorkerDynamic::GetProcessId() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return process_id_;
}

const CertProfile& CertProvisioningWorkerDynamic::GetCertProfile() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return cert_profile_;
}

const std::vector<uint8_t>& CertProvisioningWorkerDynamic::GetPublicKey()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return public_key_;
}

CertProvisioningWorkerState CertProvisioningWorkerDynamic::GetState() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return state_;
}

CertProvisioningWorkerState CertProvisioningWorkerDynamic::GetPreviousState()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  return prev_state_;
}

base::Time CertProvisioningWorkerDynamic::GetLastUpdateTime() const {
  return last_update_time_;
}

const std::optional<BackendServerError>&
CertProvisioningWorkerDynamic::GetLastBackendServerError() const {
  return last_backend_server_error_;
}

std::string CertProvisioningWorkerDynamic::GetFailureMessageWithPii() const {
  return failure_message_with_pii_.value_or(failure_message_no_pii_);
}

void CertProvisioningWorkerDynamic::Stop(CertProvisioningWorkerState state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DCHECK(IsFinalState(state));

  CancelScheduledTasks();
  FINAL_STATE_EXPECTED(UpdateState(FROM_HERE, state));
}

void CertProvisioningWorkerDynamic::Pause() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  CancelScheduledTasks();
  is_waiting_ = true;
}

void CertProvisioningWorkerDynamic::DoStep() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  CancelScheduledTasks();
  is_waiting_ = false;
  switch (state_) {
    case CertProvisioningWorkerState::kInitState:
      GenerateKey();
      return;
    case CertProvisioningWorkerState::kKeypairGenerated:
      Start();
      return;
    case CertProvisioningWorkerState::kReadyForNextOperation:
      GetNextInstruction();
      return;
    case CertProvisioningWorkerState::kAuthorizeInstructionReceived:
      BuildVaChallengeResponse();
      return;
    case CertProvisioningWorkerState::kProofOfPossessionInstructionReceived:
      BuildProofOfPossession();
      return;
    case CertProvisioningWorkerState::kImportCertificateInstructionReceived:
      ImportCert();
      return;
    case CertProvisioningWorkerState::kVaChallengeFinished:
      RegisterKey();
      return;
    case CertProvisioningWorkerState::kKeyRegistered:
      MarkVaGeneratedKey();
      return;
    case CertProvisioningWorkerState::kKeypairMarked:
      UploadAuthorization();
      return;
    case CertProvisioningWorkerState::kSignCsrFinished:
      UploadProofOfPossession();
      return;
    case CertProvisioningWorkerState::kSucceeded:
    case CertProvisioningWorkerState::kInconsistentDataError:
    case CertProvisioningWorkerState::kFailed:
    case CertProvisioningWorkerState::kCanceled:
      DCHECK(false);
      return;
    case CertProvisioningWorkerState::kStartCsrResponseReceived:
    case CertProvisioningWorkerState::kFinishCsrResponseReceived:
      // Not used in "dynamic" flow.
      NOTREACHED();
  }
  NOTREACHED() << " " << static_cast<uint>(state_);
}

void CertProvisioningWorkerDynamic::MarkWorkerForReset() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  is_schedueled_for_reset_ = true;
}

CertProvisioningWorkerDynamic::UpdateStateResult
CertProvisioningWorkerDynamic::UpdateState(
    const base::Location& from_here,
    CertProvisioningWorkerState new_state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(kDynamicWorkerStates.Has(new_state)) << static_cast<int>(new_state);

  if (!IsStateTransitionAllowed(state_, new_state)) {
    failure_message_no_pii_ = base::StrCat(
        {"Invalid state transition from ", from_here.ToString(),
         " state=", CertificateProvisioningWorkerStateToString(state_),
         " new_state=", CertificateProvisioningWorkerStateToString(new_state)});
    new_state = CertProvisioningWorkerState::kFailed;
  }

  prev_state_ = state_;
  state_ = new_state;
  last_update_time_ = base::Time::NowFromSystemTime();

  if (is_continued_without_invalidation_for_uma_) {
    RecordEvent(
        cert_profile_.protocol_version, cert_scope_,
        CertProvisioningEvent::kWorkerRetrySucceededWithoutInvalidation);
    is_continued_without_invalidation_for_uma_ = false;
  }

  HandleSerialization();

  if (state_ == CertProvisioningWorkerState::kFailed) {
    LOG(ERROR) << "Failure state from " << from_here.ToString()
               << ". Details: " << failure_message_no_pii_ << GetLogInfoBlock();
  }

  state_change_callback_.Run();
  if (IsFinalState(state_)) {
    CleanUpAndRunCallback();
    return UpdateStateResult::kFinalState;
  }
  return UpdateStateResult::kNonFinalState;
}

void CertProvisioningWorkerDynamic::GenerateKey() {
  if (cert_profile_.is_va_enabled) {
    GenerateKeyForVa();
  } else {
    GenerateRegularKey();
  }
}

void CertProvisioningWorkerDynamic::GenerateRegularKey() {
  switch (cert_profile_.key_type) {
    case KeyType::kRsa:
      platform_keys_service_->GenerateRSAKey(
          GetPlatformKeysTokenId(cert_scope_), kNonVaKeyModulusLengthBits,
          /*sw_backed=*/false,
          base::BindOnce(
              &CertProvisioningWorkerDynamic::OnGenerateRegularKeyDone,
              weak_factory_.GetWeakPtr()));
      break;
    case KeyType::kEc:
      platform_keys_service_->GenerateECKey(
          GetPlatformKeysTokenId(cert_scope_), kEcNamedCurve,
          base::BindOnce(
              &CertProvisioningWorkerDynamic::OnGenerateRegularKeyDone,
              weak_factory_.GetWeakPtr()));
  }
}

void CertProvisioningWorkerDynamic::OnGenerateRegularKeyDone(
    std::vector<uint8_t> public_key_spki_der,
    chromeos::platform_keys::Status status) {
  if (status != chromeos::platform_keys::Status::kSuccess ||
      public_key_spki_der.empty()) {
    failure_message_no_pii_ =
        base::StrCat({"Failed to prepare a non-VA key: ",
                      chromeos::platform_keys::StatusToString(status)});
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  key_location_ = KeyLocation::kPkcs11Token;
  public_key_ = std::move(public_key_spki_der);
  MarkRegularKey();
}

void CertProvisioningWorkerDynamic::GenerateKeyForVa() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  tpm_challenge_key_subtle_impl_ =
      attestation::TpmChallengeKeySubtleFactory::Create();

  switch (cert_profile_.key_type) {
    case KeyType::kRsa:
      tpm_challenge_key_subtle_impl_->StartPrepareKeyStep(
          GetVaFlowType(cert_scope_),
          /*will_register_key=*/true, ::attestation::KEY_TYPE_RSA,
          GetKeyName(cert_profile_.profile_id), profile_,
          base::BindOnce(&CertProvisioningWorkerDynamic::OnGenerateKeyForVaDone,
                         weak_factory_.GetWeakPtr()),
          /*signals=*/std::nullopt);
      break;
    case KeyType::kEc:
      tpm_challenge_key_subtle_impl_->StartPrepareKeyStep(
          GetVaFlowType(cert_scope_),
          /*will_register_key=*/true, ::attestation::KEY_TYPE_ECC,
          GetKeyName(cert_profile_.profile_id), profile_,
          base::BindOnce(&CertProvisioningWorkerDynamic::OnGenerateKeyForVaDone,
                         weak_factory_.GetWeakPtr()),
          /*signals=*/std::nullopt);
  }
}

void CertProvisioningWorkerDynamic::OnGenerateKeyForVaDone(
    const attestation::TpmChallengeKeyResult& result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (result.result_code ==
      attestation::TpmChallengeKeyResultCode::kGetCertificateFailedError) {
    LOG(WARNING) << base::StrCat(
        {"Failed to get certificate for a key", GetLogInfoBlock()});
    request_backoff_.InformOfRequest(false);
    // Next DoStep will retry generating the key.
    ScheduleNextStepAndNotifyStateChange(request_backoff_.GetTimeUntilRelease(),
                                         /*try_provisioning_on_timeout=*/true);
    return;
  }

  if (!result.IsSuccess() || result.public_key.empty()) {
    failure_message_no_pii_ =
        base::StrCat({"Failed to prepare a key: ", result.GetErrorMessage()});
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  key_location_ = KeyLocation::kVaDatabase;
  public_key_ = StrToBytes(result.public_key);
  RETURN_ON_FINAL_STATE(
      UpdateState(FROM_HERE, CertProvisioningWorkerState::kKeypairGenerated));
  DoStep();
}

void CertProvisioningWorkerDynamic::Start() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  cert_provisioning_client_->Start(
      GetProvisioningProcessForClient(),
      base::BindOnce(&CertProvisioningWorkerDynamic::OnStartResponse,
                     weak_factory_.GetWeakPtr()));
}

void CertProvisioningWorkerDynamic::OnStartResponse(
    base::expected<enterprise_management::CertProvStartResponse,
                   CertProvisioningClient::Error> response) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!ProcessResponseErrors(response)) {
    return;
  }

  RegisterForInvalidations();

  RETURN_ON_FINAL_STATE(UpdateState(
      FROM_HERE, CertProvisioningWorkerState::kReadyForNextOperation));

  if (cert_profile_.is_va_enabled) {
    // If VA is enabled, then the server is expected to have the next
    // instruction available from the very beginning.
    DoStep();
  } else {
    // If VA is disabled, then the server will need to wait for an input from
    // the adapter and is expected to send an invalidation when ready.
    ScheduleNextStep(
        kInitialFetchInstructionDelay,
        /*try_provisioning_on_timeout=*/!ShouldOnlyUseInvalidations());
  }
}

void CertProvisioningWorkerDynamic::GetNextInstruction() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  cert_provisioning_client_->GetNextInstruction(
      GetProvisioningProcessForClient(),
      base::BindOnce(
          &CertProvisioningWorkerDynamic::OnGetNextInstructionResponse,
          weak_factory_.GetWeakPtr()));
}

void CertProvisioningWorkerDynamic::OnGetNextInstructionResponse(
    base::expected<enterprise_management::CertProvGetNextInstructionResponse,
                   CertProvisioningClient::Error> response) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!ProcessResponseErrors(response)) {
    return;
  }

  const em::CertProvGetNextInstructionResponse& next_instruction_response =
      response.value();
  if (next_instruction_response.has_authorize_instruction()) {
    OnAuthorizeInstructionReceived(
        next_instruction_response.authorize_instruction());
    return;
  }
  if (next_instruction_response.has_proof_of_possession_instruction()) {
    OnProofOfPossessionInstructionReceived(
        next_instruction_response.proof_of_possession_instruction());
    return;
  }
  if (next_instruction_response.has_import_certificate_instruction()) {
    OnImportCertificateInstructionReceived(
        next_instruction_response.import_certificate_instruction());
    return;
  }
  // CertProvisioningClient ensures that at least one of the instructions was
  // filled.
  NOTREACHED();
}

void CertProvisioningWorkerDynamic::OnAuthorizeInstructionReceived(
    const em::CertProvAuthorizeInstruction& authorize_instruction) {
  if (!cert_profile_.is_va_enabled) {
    failure_message_no_pii_ = "VA not enabled";
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }
  if (attempted_va_challenge_) {
    failure_message_no_pii_ = "VA only possible once";
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }
  attempted_va_challenge_ = true;

  va_challenge_ = authorize_instruction.va_challenge();
  RETURN_ON_FINAL_STATE(UpdateState(
      FROM_HERE, CertProvisioningWorkerState::kAuthorizeInstructionReceived));
  DoStep();
}

void CertProvisioningWorkerDynamic::OnProofOfPossessionInstructionReceived(
    const em::CertProvProofOfPossessionInstruction&
        proof_of_possession_instruction) {
  if (cert_profile_.is_va_enabled && !attempted_va_challenge_) {
    failure_message_no_pii_ = "Expected VA challenge";
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  if (proof_of_possession_instruction.has_signature_algorithm()) {
    signature_algorithm_ =
        proof_of_possession_instruction.signature_algorithm();
  } else {
    failure_message_no_pii_ = "No signature algorithm provided";
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  data_to_sign_ = StrToBytes(proof_of_possession_instruction.data_to_sign());
  RETURN_ON_FINAL_STATE(UpdateState(
      FROM_HERE,
      CertProvisioningWorkerState::kProofOfPossessionInstructionReceived));
  DoStep();
}

void CertProvisioningWorkerDynamic::OnImportCertificateInstructionReceived(
    const em::CertProvImportCertificateInstruction&
        import_certificate_instruction) {
  if (cert_profile_.is_va_enabled && !attempted_va_challenge_) {
    failure_message_no_pii_ = "Expected VA challenge";
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  pem_encoded_certificate_ =
      import_certificate_instruction.pem_encoded_certificate();
  RETURN_ON_FINAL_STATE(UpdateState(
      FROM_HERE,
      CertProvisioningWorkerState::kImportCertificateInstructionReceived));
  DoStep();
}

void CertProvisioningWorkerDynamic::BuildVaChallengeResponse() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  tpm_challenge_key_subtle_impl_->StartSignChallengeStep(
      std::move(va_challenge_),
      base::BindOnce(
          &CertProvisioningWorkerDynamic::OnBuildVaChallengeResponseDone,
          weak_factory_.GetWeakPtr()));
  va_challenge_.clear();
}

void CertProvisioningWorkerDynamic::OnBuildVaChallengeResponseDone(
    const attestation::TpmChallengeKeyResult& challenge_result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!challenge_result.IsSuccess()) {
    failure_message_no_pii_ = ConstructFailureMessage(challenge_result);
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  if (challenge_result.challenge_response.empty()) {
    failure_message_no_pii_ = "Challenge response is empty";
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  va_challenge_response_ = challenge_result.challenge_response;
  RETURN_ON_FINAL_STATE(UpdateState(
      FROM_HERE, CertProvisioningWorkerState::kVaChallengeFinished));
  DoStep();
}

void CertProvisioningWorkerDynamic::RegisterKey() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  tpm_challenge_key_subtle_impl_->StartRegisterKeyStep(
      base::BindOnce(&CertProvisioningWorkerDynamic::OnRegisterKeyDone,
                     weak_factory_.GetWeakPtr()));
}

void CertProvisioningWorkerDynamic::OnRegisterKeyDone(
    const attestation::TpmChallengeKeyResult& result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  key_location_ = KeyLocation::kPkcs11Token;
  tpm_challenge_key_subtle_impl_.reset();

  if (!result.IsSuccess()) {
    failure_message_no_pii_ =
        base::StrCat({"Failed to register key: ", result.GetErrorMessage()});
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  RETURN_ON_FINAL_STATE(
      UpdateState(FROM_HERE, CertProvisioningWorkerState::kKeyRegistered));
  DoStep();
}

void CertProvisioningWorkerDynamic::MarkRegularKey() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  MarkKey(CertProvisioningWorkerState::kKeypairGenerated);
}

void CertProvisioningWorkerDynamic::MarkVaGeneratedKey() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  MarkKey(CertProvisioningWorkerState::kKeypairMarked);
}

void CertProvisioningWorkerDynamic::MarkKey(
    CertProvisioningWorkerState target_state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  MarkKeyAsCorporate();

  platform_keys_service_->SetAttributeForKey(
      GetPlatformKeysTokenId(cert_scope_), public_key_,
      chromeos::platform_keys::KeyAttributeType::kCertificateProvisioningId,
      StrToBytes(cert_profile_.profile_id),
      base::BindOnce(&CertProvisioningWorkerDynamic::OnMarkKeyDone,
                     weak_factory_.GetWeakPtr(), target_state));
}

// Marks the key |public_key_spki_der| as corporate. |profile| can be nullptr if
// |scope| is CertScope::kDevice. The method is asynchronous, but the worker
// doesn't have to wait for it to finish.
void CertProvisioningWorkerDynamic::MarkKeyAsCorporate() {
  CHECK(profile_ || cert_scope_ == CertScope::kDevice);

  GetKeyPermissionsManager(cert_scope_, profile_)
      ->AllowKeyForUsage(
          base::BindOnce(&CertProvisioningWorkerDynamic::OnAllowKeyForUsageDone,
                         weak_factory_.GetWeakPtr()),
          platform_keys::KeyUsage::kCorporate, public_key_);
}

void CertProvisioningWorkerDynamic::OnAllowKeyForUsageDone(
    chromeos::platform_keys::Status status) {
  if (status != chromeos::platform_keys::Status::kSuccess) {
    LOG(ERROR) << "Cannot mark key corporate: "
               << chromeos::platform_keys::StatusToString(status)
               << GetLogInfoBlock();
  }
}

void CertProvisioningWorkerDynamic::OnMarkKeyDone(
    CertProvisioningWorkerState target_state,
    chromeos::platform_keys::Status status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (status != chromeos::platform_keys::Status::kSuccess) {
    failure_message_no_pii_ =
        base::StrCat({"Failed to mark a key: ",
                      chromeos::platform_keys::StatusToString(status)});
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  RETURN_ON_FINAL_STATE(UpdateState(FROM_HERE, target_state));
  DoStep();
}

void CertProvisioningWorkerDynamic::UploadAuthorization() {
  cert_provisioning_client_->Authorize(
      GetProvisioningProcessForClient(), va_challenge_response_,
      base::BindOnce(
          &CertProvisioningWorkerDynamic::OnUploadAuthorizationResponse,
          weak_factory_.GetWeakPtr()));
}

void CertProvisioningWorkerDynamic::OnUploadAuthorizationResponse(
    base::expected<void, CertProvisioningClient::Error> response) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!ProcessResponseErrors(response)) {
    return;
  }
  va_challenge_response_.clear();

  RETURN_ON_FINAL_STATE(UpdateState(
      FROM_HERE, CertProvisioningWorkerState::kReadyForNextOperation));
  // Wait for an invalidation or a timeout before doing the next step.
  ScheduleNextStep(
      kInitialFetchInstructionDelay,
      /*try_provisioning_on_timeout=*/!ShouldOnlyUseInvalidations());
}

void CertProvisioningWorkerDynamic::BuildProofOfPossession() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (attempted_proof_of_possession_) {
    failure_message_no_pii_ = "Proof of possession requested >1 times";
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }
  attempted_proof_of_possession_ = true;

  switch (signature_algorithm_) {
    case em::CertProvSignatureAlgorithm::
        SIGNATURE_ALGORITHM_RSA_PKCS1_V1_5_NO_HASH:
      platform_keys_service_->SignRSAPKCS1Raw(
          GetPlatformKeysTokenId(cert_scope_), std::move(data_to_sign_),
          public_key_,
          base::BindRepeating(
              &CertProvisioningWorkerDynamic::OnBuildProofOfPossessionDone,
              weak_factory_.GetWeakPtr()));
      break;
    case em::CertProvSignatureAlgorithm::SIGNATURE_ALGORITHM_ECDSA_SHA256:
      platform_keys_service_->SignEcdsa(
          GetPlatformKeysTokenId(cert_scope_), std::move(data_to_sign_),
          public_key_, chromeos::platform_keys::HASH_ALGORITHM_SHA256,
          base::BindRepeating(
              &CertProvisioningWorkerDynamic::OnBuildProofOfPossessionDone,
              weak_factory_.GetWeakPtr()));
      break;
    case em::CertProvSignatureAlgorithm::SIGNATURE_ALGORITHM_UNSPECIFIED:
      failure_message_no_pii_ = "Unknown signature algorithm";
      FINAL_STATE_EXPECTED(
          UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
      return;
  }

  data_to_sign_.clear();
}

void CertProvisioningWorkerDynamic::OnBuildProofOfPossessionDone(
    std::vector<uint8_t> signature,
    chromeos::platform_keys::Status status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (status != chromeos::platform_keys::Status::kSuccess) {
    failure_message_no_pii_ =
        base::StrCat({"Failed to sign data: ",
                      chromeos::platform_keys::StatusToString(status)});
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  if (signature_algorithm_ ==
      em::CertProvSignatureAlgorithm::SIGNATURE_ALGORITHM_ECDSA_SHA256) {
    base::expected<std::vector<uint8_t>, kcer::Error> asn1_ec_signature =
        kcer::ReencodeEcSignatureAsAsn1(signature);
    if (!asn1_ec_signature.has_value()) {
      failure_message_no_pii_ =
          base::StringPrintf("Failed to re-encode ECC signature, error: %d",
                             static_cast<int>(asn1_ec_signature.error()));
      FINAL_STATE_EXPECTED(
          UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
      return;
    }
    signature_ = std::move(asn1_ec_signature).value();
  } else {
    signature_ = std::move(signature);
  }

  RETURN_ON_FINAL_STATE(
      UpdateState(FROM_HERE, CertProvisioningWorkerState::kSignCsrFinished));
  DoStep();
}

void CertProvisioningWorkerDynamic::UploadProofOfPossession() {
  cert_provisioning_client_->UploadProofOfPossession(
      GetProvisioningProcessForClient(), BytesToStr(signature_),
      base::BindOnce(
          &CertProvisioningWorkerDynamic::OnUploadProofOfPossessionResponse,
          weak_factory_.GetWeakPtr()));
}

void CertProvisioningWorkerDynamic::OnUploadProofOfPossessionResponse(
    base::expected<void, CertProvisioningClient::Error> response) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!ProcessResponseErrors(response)) {
    return;
  }

  signature_.clear();

  RETURN_ON_FINAL_STATE(UpdateState(
      FROM_HERE, CertProvisioningWorkerState::kReadyForNextOperation));
  // Wait for an invalidation or a timeout before doing the next step.
  ScheduleNextStep(
      kInitialFetchInstructionDelay,
      /*try_provisioning_on_timeout=*/!ShouldOnlyUseInvalidations());
}

void CertProvisioningWorkerDynamic::ImportCert() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  scoped_refptr<net::X509Certificate> cert = CreateSingleCertificateFromBytes(
      pem_encoded_certificate_.data(), pem_encoded_certificate_.size());
  if (!cert) {
    failure_message_no_pii_ = "Failed to parse a certificate";
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  std::vector<uint8_t> public_key_from_cert =
      chromeos::platform_keys::GetSubjectPublicKeyInfo(cert);
  if (public_key_from_cert != public_key_) {
    failure_message_no_pii_ =
        "Downloaded certificate does not match the expected key pair.";
    failure_message_with_pii_ = base::StrCat(
        {"Downloaded certificate does not match the expected key pair. ",
         "Expected: ", base::Base64Encode(public_key_), " ",
         "Public key from cert: ", base::Base64Encode(public_key_from_cert),
         "\n", "Cert: ", pem_encoded_certificate_, GetLogInfoBlock()});
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  platform_keys_service_->ImportCertificate(
      GetPlatformKeysTokenId(cert_scope_), cert,
      base::BindRepeating(&CertProvisioningWorkerDynamic::OnImportCertDone,
                          weak_factory_.GetWeakPtr()));
}

void CertProvisioningWorkerDynamic::OnImportCertDone(
    chromeos::platform_keys::Status status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (status != chromeos::platform_keys::Status::kSuccess) {
    failure_message_no_pii_ =
        base::StrCat({"Failed to import certificate, error: ",
                      chromeos::platform_keys::StatusToString(status)});
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  FINAL_STATE_EXPECTED(
      UpdateState(FROM_HERE, CertProvisioningWorkerState::kSucceeded));
}

template <typename ResultType>
bool CertProvisioningWorkerDynamic::ProcessResponseErrors(
    const base::expected<ResultType, CertProvisioningClient::Error>& response) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (response.has_value()) {
    last_backend_server_error_ = std::nullopt;
    // Use Reset to explicitly reset a potentially non-zero error count tracked
    // by BackoffEntry to 0. This assumes that a successful response is a good
    // indicator that following responses will also be successful.
    request_backoff_.Reset();
    fetch_instruction_backoff_.Reset();
    RecordDmStatusForDynamic(policy::DeviceManagementStatus::DM_STATUS_SUCCESS);
    return true;
  }

  ProcessResponseErrors(response.error());
  return false;
}

void CertProvisioningWorkerDynamic::ProcessResponseErrors(
    const CertProvisioningClient::Error& error) {
  const policy::DeviceManagementStatus status = error.device_management_status;
  RecordDmStatusForDynamic(status);
  if ((status ==
       policy::DeviceManagementStatus::DM_STATUS_TEMPORARY_UNAVAILABLE) ||
      (status == policy::DeviceManagementStatus::DM_STATUS_REQUEST_FAILED) ||
      (status == policy::DeviceManagementStatus::DM_STATUS_HTTP_STATUS_ERROR)) {
    LOG(WARNING) << "Connection to DM Server failed, error: " << status
                 << GetLogInfoBlock();
    last_backend_server_error_ =
        BackendServerError(status, base::Time::NowFromSystemTime());
    request_backoff_.InformOfRequest(false);
    ScheduleNextStepAndNotifyStateChange(request_backoff_.GetTimeUntilRelease(),
                                         /*try_provisioning_on_timeout=*/true);
    return;
  }

  // From this point, connection to the DM Server was successful.
  last_backend_server_error_ = std::nullopt;
  if (status != policy::DeviceManagementStatus::DM_STATUS_SUCCESS) {
    failure_message_no_pii_ = base::StrCat(
        {"DM Server returned error: ", base::NumberToString(status)});
    FINAL_STATE_EXPECTED(
        UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
    return;
  }

  request_backoff_.Reset();

  const em::CertProvBackendError& backend_error = error.backend_error;
  RecordCertProvBackendErrorForDynamic(backend_error.error());
  if (backend_error.error() ==
      em::CertProvBackendError::INSTRUCTION_NOT_YET_AVAILABLE) {
    LOG(WARNING) << "No instruction available yet "
                 << GetLogInfoBlock();

    fetch_instruction_backoff_.InformOfRequest(false);
    if (fetch_instruction_backoff_.failure_count() > 4) {
      fetch_instruction_backoff_.SetCustomReleaseTime(
          base::TimeTicks::Now() + kMaximumFetchInstructionDelay);
    }

    // Don't change state, just retry the operation in this state in a delay (or
    // when an invalidation is triggered).
    ScheduleNextStepAndNotifyStateChange(
        fetch_instruction_backoff_.GetTimeUntilRelease(),
        /*try_provisioning_on_timeout=*/!ShouldOnlyUseInvalidations());
    return;
  }

  fetch_instruction_backoff_.Reset();

  if (backend_error.error() == em::CertProvBackendError::INCONSISTENT_DATA ||
      backend_error.error() == em::CertProvBackendError::PROFILE_NOT_FOUND ||
      backend_error.error() ==
          em::CertProvBackendError::IMMEDIATE_RETRY_ERROR_ZERO ||
      backend_error.error() ==
          em::CertProvBackendError::IMMEDIATE_RETRY_ERROR_ONE ||
      backend_error.error() ==
          em::CertProvBackendError::IMMEDIATE_RETRY_ERROR_TWO ||
      backend_error.error() ==
          em::CertProvBackendError::IMMEDIATE_RETRY_ERROR_THREE ||
      backend_error.error() ==
          em::CertProvBackendError::IMMEDIATE_RETRY_ERROR_FOUR) {
    // Report both INCONSISTENT_DATA and PROFILE_NOT_FOUND as
    // kInconsistentDataError because both mean that the locally-cached policy
    // does not match the server's database.
    LOG(ERROR) << "Server response contains error: " << backend_error.error()
               << ". Debug message: " << backend_error.debug_message()
               << GetLogInfoBlock();
    FINAL_STATE_EXPECTED(UpdateState(
        FROM_HERE, CertProvisioningWorkerState::kInconsistentDataError));
    return;
  }

  failure_message_no_pii_ =
      base::StrCat({"Server response contains error: ",
                    base::NumberToString(backend_error.error()),
                    ". Debug message: ", backend_error.debug_message()});
  FINAL_STATE_EXPECTED(
      UpdateState(FROM_HERE, CertProvisioningWorkerState::kFailed));
  return;
}

void CertProvisioningWorkerDynamic::ScheduleNextStep(
    base::TimeDelta delay,
    bool try_provisioning_on_timeout) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (try_provisioning_on_timeout) {
    delay = std::max(delay, kMinumumTryAgainLaterDelay);
    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&CertProvisioningWorkerDynamic::OnShouldContinue,
                       weak_factory_.GetWeakPtr(), ContinueReason::kTimeout),
        delay);
    VLOG(0) << "Next step scheduled in " << delay << GetLogInfoBlock();
  }

  is_waiting_ = true;
}

void CertProvisioningWorkerDynamic::ScheduleNextStepAndNotifyStateChange(
    base::TimeDelta delay,
    bool try_provisioning_on_timeout) {
  ScheduleNextStep(delay, try_provisioning_on_timeout);

  last_update_time_ = base::Time::NowFromSystemTime();
  state_change_callback_.Run();
}

void CertProvisioningWorkerDynamic::OnShouldContinue(ContinueReason reason) {
  switch (reason) {
    case ContinueReason::kInvalidationReceived:
      RecordEvent(cert_profile_.protocol_version, cert_scope_,
                  CertProvisioningEvent::kInvalidationReceived);
      break;
    case ContinueReason::kTimeout:
      RecordEvent(cert_profile_.protocol_version, cert_scope_,
                  CertProvisioningEvent::kWorkerRetryWithoutInvalidation);
      break;
  }

  // Worker is already doing something.
  if (!IsWaiting()) {
    return;
  }

  is_continued_without_invalidation_for_uma_ =
      (reason == ContinueReason::kTimeout);

  DoStep();
}

void CertProvisioningWorkerDynamic::CancelScheduledTasks() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  weak_factory_.InvalidateWeakPtrs();
}

// This method handles clean up.
// One of the things to be cleaned up are generated keys. It is possible that a
// worker is asked to cleanup and shutdown while a key is being generated for
// it. In that case this cleanup will miss that key and it's important to make
// sure that there is another mechanism that will eventually clean up the key.
// VA and PKS keys both are covered and the mechanism is described in separate
// comments.
void CertProvisioningWorkerDynamic::CleanUpAndRunCallback() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  UnregisterFromInvalidations();

  if (state_ == CertProvisioningWorkerState::kSucceeded) {
    // No extra clean up is necessary.
    OnCleanUpDone();
    return;
  }

  if (key_location_ == KeyLocation::kVaDatabase) {
    // if the worker is still waiting for the key right now, then it will be
    // eventually cleaned by the scheduler once it goes idle.
    DeleteVaKey(
        cert_scope_, profile_, GetKeyName(cert_profile_.profile_id),
        base::BindOnce(&CertProvisioningWorkerDynamic::OnDeleteVaKeyDone,
                       weak_factory_.GetWeakPtr()));
    return;
  } else if (key_location_ == KeyLocation::kPkcs11Token) {
    platform_keys_service_->RemoveKey(
        GetPlatformKeysTokenId(cert_scope_), public_key_,
        base::BindOnce(&CertProvisioningWorkerDynamic::OnRemoveKeyDone,
                       weak_factory_.GetWeakPtr()));
    return;
  }

  // If the worker is still waiting for a key from PlatformKeysService right
  // now, PlatformKeysService will clean up the key when the key is generated
  // and the worker is gone. No extra clean up is necessary.
  OnCleanUpDone();
}

void CertProvisioningWorkerDynamic::OnDeleteVaKeyDone(bool delete_result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!delete_result) {
    LOG(ERROR) << "Failed to delete a va key" << GetLogInfoBlock();
  }
  OnCleanUpDone();
}

void CertProvisioningWorkerDynamic::OnRemoveKeyDone(
    chromeos::platform_keys::Status status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (status != chromeos::platform_keys::Status::kSuccess) {
    LOG(ERROR) << "Failed to delete a key: "
               << chromeos::platform_keys::StatusToString(status)
               << GetLogInfoBlock();
  }

  OnCleanUpDone();
}

void CertProvisioningWorkerDynamic::OnCleanUpDone() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  RecordResult(cert_profile_.protocol_version, cert_scope_, state_,
               prev_state_);

  // The worked is likely to be deleted in `result_callback_`. Run it
  // asynchronously in case something is still interacting with it in the
  // current call stack.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(result_callback_), cert_profile_,
                                process_id_, state_));
}

CertProvisioningClient::ProvisioningProcess
CertProvisioningWorkerDynamic::GetProvisioningProcessForClient() {
  return CertProvisioningClient::ProvisioningProcess(
      process_id_, cert_scope_, cert_profile_.profile_id,
      cert_profile_.policy_version, public_key_);
}

void CertProvisioningWorkerDynamic::HandleSerialization() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  switch (state_) {
    case CertProvisioningWorkerState::kInitState:
    case CertProvisioningWorkerState::kKeypairGenerated:
      // Do not serialize in the early states, it's easier to retry from the
      // beginning. Notably, the worker registers for invalidations both after
      // deserialization and after calling the Start RPC, but it should only
      // register once.
      break;
    case CertProvisioningWorkerState::kReadyForNextOperation:
      // Serialize as we're going to wait for a server-side instruction.
    case CertProvisioningWorkerState::kSignCsrFinished:
      // Serialize as we're going to upload a signature, which (contrary to the
      // VA challenge response) does not have a limited validity time.
      CertProvisioningSerializer::SerializeWorkerToPrefs(pref_service_, *this);
      break;
    case CertProvisioningWorkerState::kAuthorizeInstructionReceived:
    case CertProvisioningWorkerState::kProofOfPossessionInstructionReceived:
    case CertProvisioningWorkerState::kImportCertificateInstructionReceived:
      // Some operations can only be performed once, and most are expected to be
      // fast. If chrome restarts in the middle of an operation it is simpler to
      // start from scratch.
      CertProvisioningSerializer::DeleteWorkerFromPrefs(pref_service_, *this);
      break;
    case CertProvisioningWorkerState::kVaChallengeFinished:
    case CertProvisioningWorkerState::kKeyRegistered:
    case CertProvisioningWorkerState::kKeypairMarked:
      break;
    case CertProvisioningWorkerState::kSucceeded:
    case CertProvisioningWorkerState::kInconsistentDataError:
    case CertProvisioningWorkerState::kFailed:
    case CertProvisioningWorkerState::kCanceled:
      CertProvisioningSerializer::DeleteWorkerFromPrefs(pref_service_, *this);
      break;
    case CertProvisioningWorkerState::kStartCsrResponseReceived:
    case CertProvisioningWorkerState::kFinishCsrResponseReceived:
      // Not used in "dynamic" flow.
      NOTREACHED();
  }
}

void CertProvisioningWorkerDynamic::InitAfterDeserialization() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  RegisterForInvalidations();

  // Only initialize TpmChallengeKeySubtle if any Verified Access operations can
  // still happen, i.e. if VA is enabled and the key has not been moved into the
  // PKCS#11 token ("registered") yet.
  if (cert_profile_.is_va_enabled &&
      key_location_ != KeyLocation::kPkcs11Token) {
    switch (cert_profile_.key_type) {
      case KeyType::kRsa:
        tpm_challenge_key_subtle_impl_ =
            attestation::TpmChallengeKeySubtleFactory::CreateForPreparedKey(
                GetVaFlowType(cert_scope_),
                /*will_register_key=*/true, ::attestation::KEY_TYPE_RSA,
                GetKeyName(cert_profile_.profile_id), BytesToStr(public_key_),
                profile_);
        break;
      case KeyType::kEc:
        tpm_challenge_key_subtle_impl_ =
            attestation::TpmChallengeKeySubtleFactory::CreateForPreparedKey(
                GetVaFlowType(cert_scope_),
                /*will_register_key=*/true, ::attestation::KEY_TYPE_ECC,
                GetKeyName(cert_profile_.profile_id), BytesToStr(public_key_),
                profile_);
    }
  }
}

void CertProvisioningWorkerDynamic::RegisterForInvalidations() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DCHECK(invalidator_);

  // Registering the callback with base::Unretained is OK because this class
  // owns |invalidator_|, and the callback will never be called after
  // |invalidator_| is destroyed.
  invalidator_->Register(
      MakeInvalidationListenerType(process_id_),
      base::BindRepeating(&CertProvisioningWorkerDynamic::OnInvalidationEvent,
                          base::Unretained(this)));

  RecordEvent(cert_profile_.protocol_version, cert_scope_,
              CertProvisioningEvent::kRegisteredToInvalidationTopic);
}

void CertProvisioningWorkerDynamic::UnregisterFromInvalidations() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DCHECK(invalidator_);

  invalidator_->Unregister();
}

void CertProvisioningWorkerDynamic::OnInvalidationEvent(
    InvalidationEvent invalidation_event) {
  // This function logs as WARNING so the messages are visible in feedback logs
  // to monitor for b/307340577 .
  switch (invalidation_event) {
    case InvalidationEvent::kSuccessfullySubscribed:
      RecordEvent(
          cert_profile_.protocol_version, cert_scope_,
          CertProvisioningEvent::kSuccessfullySubscribedToInvalidationTopic);
      LOG(WARNING) << "Successfully subscribed to invalidations"
                   << GetLogInfoBlock();
      ScheduleNextStep(kOnSubscribedFetchInstructionDelay,
                       /*try_provisioning_on_timeout=*/true);
      break;
    case InvalidationEvent::kInvalidationReceived:
      LOG(WARNING) << "Invalidation received" << GetLogInfoBlock();
      OnShouldContinue(ContinueReason::kInvalidationReceived);
      break;
  }
}

std::string CertProvisioningWorkerDynamic::GetLogInfoBlock() const {
  return base::StringPrintf(" [cppId: %s]", process_id_.c_str());
}

}  // namespace ash::cert_provisioning
