chromium/chromeos/ash/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc

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

#include "chromeos/ash/services/device_sync/cryptauth_v2_enroller_impl.h"

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

#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/services/device_sync/cryptauth_enrollment_constants.h"
#include "chromeos/ash/services/device_sync/cryptauth_enrollment_result.h"
#include "chromeos/ash/services/device_sync/cryptauth_key_creator_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_key_proof_computer_impl.h"
#include "chromeos/ash/services/device_sync/cryptauth_key_registry_impl.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_key_creator.h"
#include "chromeos/ash/services/device_sync/fake_cryptauth_key_proof_computer.h"
#include "chromeos/ash/services/device_sync/mock_cryptauth_client.h"
#include "chromeos/ash/services/device_sync/network_request_error.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_better_together_feature_metadata.pb.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_client_app_metadata.pb.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_common.pb.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_directive.pb.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_enrollment.pb.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_v2_test_util.h"
#include "chromeos/ash/services/device_sync/public/cpp/gcm_constants.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace device_sync {

using cryptauthv2::SyncKeysRequest;
using SyncSingleKeyRequest = cryptauthv2::SyncKeysRequest::SyncSingleKeyRequest;

using cryptauthv2::SyncKeysResponse;
using SyncSingleKeyResponse =
    cryptauthv2::SyncKeysResponse::SyncSingleKeyResponse;
using KeyAction =
    cryptauthv2::SyncKeysResponse::SyncSingleKeyResponse::KeyAction;
using KeyCreation =
    cryptauthv2::SyncKeysResponse::SyncSingleKeyResponse::KeyCreation;

using cryptauthv2::EnrollKeysRequest;
using EnrollSingleKeyRequest =
    cryptauthv2::EnrollKeysRequest::EnrollSingleKeyRequest;

using cryptauthv2::EnrollKeysResponse;
using EnrollSingleKeyResponse =
    cryptauthv2::EnrollKeysResponse::EnrollSingleKeyResponse;

using cryptauthv2::ApplicationSpecificMetadata;
using cryptauthv2::BetterTogetherFeatureMetadata;
using cryptauthv2::ClientAppMetadata;
using cryptauthv2::ClientDirective;
using cryptauthv2::ClientMetadata;
using cryptauthv2::FeatureMetadata;
using cryptauthv2::InvokeNext;
using cryptauthv2::KeyDirective;
using cryptauthv2::KeyType;
using cryptauthv2::PolicyReference;
using cryptauthv2::TargetService;

namespace {

const char kAccessTokenUsed[] = "access token used by CryptAuthClient";

const char kRandomSessionId[] = "random_session_id";

const char kOldActivePublicKey[] = "old_active_public_key";
const char kOldActivePrivateKey[] = "old_active_private_key";

// User key pair active handle must be kCryptAuthFixedUserKeyPairHandle.
const CryptAuthKey kOldActiveAsymmetricKey(kOldActivePublicKey,
                                           kOldActivePrivateKey,
                                           CryptAuthKey::Status::kActive,
                                           KeyType::P256,
                                           kCryptAuthFixedUserKeyPairHandle);

const char kOldActiveSymmetricKeyMaterial[] = "old_active_symmetric_key";
const char kOldActiveSymmetricKeyHandle[] = "old_active_symmetric_key_handle";
CryptAuthKey kOldActiveSymmetricKey(kOldActiveSymmetricKeyMaterial,
                                    CryptAuthKey::Status::kActive,
                                    KeyType::RAW128,
                                    kOldActiveSymmetricKeyHandle);

const char kOldInactiveSymmetricKeyMaterial[] = "old_inactive_symmetric_key";
const char kOldInactiveSymmetricKeyHandle[] =
    "old_inactive_symmetric_key_handle";
CryptAuthKey kOldInactiveSymmetricKey(kOldInactiveSymmetricKeyMaterial,
                                      CryptAuthKey::Status::kInactive,
                                      KeyType::RAW256,
                                      kOldInactiveSymmetricKeyHandle);

const char kNewPublicKey[] = "new_public_key";
const char kNewPrivateKey[] = "new_private_key";

const char kNewSymmetricKey[] = "new_symmetric_key";
const char kNewSymmetricKeyHandle[] = "new_symmetric_key_handle";

const char kServerEphemeralDh[] = "server_ephemeral_dh";
const char kClientDhPublicKey[] = "client_ephemeral_dh_public_key";
const char kClientDhPrivateKey[] = "client_ephemeral_dh_private_key";
const CryptAuthKey kClientEphemeralDh(kClientDhPublicKey,
                                      kClientDhPrivateKey,
                                      CryptAuthKey::Status::kActive,
                                      KeyType::P256);

class FakeCryptAuthKeyProofComputerFactory
    : public CryptAuthKeyProofComputerImpl::Factory {
 public:
  FakeCryptAuthKeyProofComputerFactory() = default;

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

  ~FakeCryptAuthKeyProofComputerFactory() override = default;

  void set_should_return_null_key_proof(bool should_return_null_key_proof) {
    should_return_null_key_proof_ = should_return_null_key_proof;
  }

 private:
  // CryptAuthKeyProofComputerImpl::Factory:
  std::unique_ptr<CryptAuthKeyProofComputer> CreateInstance() override {
    auto instance = std::make_unique<FakeCryptAuthKeyProofComputer>();
    instance->set_should_return_null(should_return_null_key_proof_);
    return instance;
  }

  bool should_return_null_key_proof_ = false;
};

class SyncSingleKeyResponseData {
 public:
  SyncSingleKeyResponseData(
      const CryptAuthKeyBundle::Name& bundle_name,
      const CryptAuthKeyRegistry* key_registry,
      const base::flat_map<std::string, KeyAction>& handle_to_action_map,
      const KeyCreation& new_key_creation,
      const std::optional<KeyType>& new_key_type,
      const std::optional<KeyDirective>& new_key_directive)
      : bundle_name(bundle_name),
        single_response(GenerateResponse(key_registry,
                                         handle_to_action_map,
                                         new_key_creation,
                                         new_key_type,
                                         new_key_directive)) {}

  CryptAuthKeyBundle::Name bundle_name;
  SyncSingleKeyResponse single_response;

 private:
  SyncSingleKeyResponse GenerateResponse(
      const CryptAuthKeyRegistry* key_registry,
      const base::flat_map<std::string, KeyAction>& handle_to_action_map,
      const KeyCreation& new_key_creation,
      const std::optional<KeyType>& new_key_type,
      const std::optional<KeyDirective>& new_key_directive) {
    SyncSingleKeyResponse generated_response;
    generated_response.set_key_creation(new_key_creation);
    if (new_key_type)
      generated_response.set_key_type(*new_key_type);
    if (new_key_directive)
      generated_response.mutable_key_directive()->CopyFrom(*new_key_directive);

    // If there are no keys, we don't need to add key actions.
    const CryptAuthKeyBundle* bundle = key_registry->GetKeyBundle(bundle_name);
    if (!bundle || handle_to_action_map.empty())
      return generated_response;

    // We assume the enroller populated SyncSingleKeyRequest::key_handles in
    // the same order as the key bundle's handle-to-key map. Populate
    // SyncSingleKeyResponse::key_actions with the same ordering. If a key
    // action for a handle is not specified in |handle_to_action| map, use
    // KEY_ACTION_UNSPECIFIED.
    for (const std::pair<std::string, CryptAuthKey>& handle_key_pair :
         bundle->handle_to_key_map()) {
      auto it = handle_to_action_map.find(handle_key_pair.first);
      KeyAction key_action = it == handle_to_action_map.end()
                                 ? SyncSingleKeyResponse::KEY_ACTION_UNSPECIFIED
                                 : it->second;
      generated_response.add_key_actions(key_action);
    }

    return generated_response;
  }
};

ClientMetadata GetClientMetadataForTest() {
  return cryptauthv2::BuildClientMetadata(
      2 /* retry_count */, ClientMetadata::PERIODIC /* invocation_reason */);
}

PolicyReference GetPreviousClientDirectivePolicyReferenceForTest() {
  return cryptauthv2::BuildPolicyReference(
      "Previous Client Directive Policy Reference", 1 /* version */);
}

ClientDirective GetNewClientDirectiveForTest() {
  return cryptauthv2::GetClientDirectiveForTest();
}

KeyDirective GetOldKeyDirectiveForTest() {
  return cryptauthv2::BuildKeyDirective(
      cryptauthv2::BuildPolicyReference("Old Key Policy Name",
                                        10 /* version */),
      100 /* enroll_time_millis */);
}

KeyDirective GetNewKeyDirectiveForTest() {
  return cryptauthv2::BuildKeyDirective(
      cryptauthv2::BuildPolicyReference("New Key Policy Name",
                                        20 /* version */),
      100 /* enroll_time_millis */);
}

// Note: Copied from the implementation file.
const std::vector<CryptAuthKeyBundle::Name>& GetKeyBundleOrder() {
  static const base::NoDestructor<std::vector<CryptAuthKeyBundle::Name>> order(
      [] {
        std::vector<CryptAuthKeyBundle::Name> order;
        for (const CryptAuthKeyBundle::Name& bundle_name :
             CryptAuthKeyBundle::AllEnrollableNames())
          order.push_back(bundle_name);
        return order;
      }());

  return *order;
}

// Returns the index of SyncKeysRequest.sync_single_key_requests or
// SyncKeysResponse.sync_single_key_responses that contains information about
// the key bundle |bundle_name|.
size_t GetKeyBundleIndex(const CryptAuthKeyBundle::Name& bundle_name) {
  for (size_t i = 0; i < GetKeyBundleOrder().size(); ++i) {
    if (GetKeyBundleOrder()[i] == bundle_name)
      return i;
  }

  return GetKeyBundleOrder().size();
}

// Builds a SyncKeysResponse, ensuring that the SyncSingleKeyResponses ordering
// aligns with GetKeyBundleOrder().
SyncKeysResponse BuildSyncKeysResponse(
    std::vector<SyncSingleKeyResponseData> sync_single_key_responses_data = {},
    const std::string& session_id = kRandomSessionId,
    const std::string& server_ephemeral_dh = kServerEphemeralDh,
    const ClientDirective& client_directive = GetNewClientDirectiveForTest()) {
  SyncKeysResponse sync_keys_response;
  sync_keys_response.set_random_session_id(session_id);
  sync_keys_response.set_server_ephemeral_dh(server_ephemeral_dh);
  sync_keys_response.mutable_client_directive()->CopyFrom(client_directive);

  // Make sure there are at least as many SyncSingleKeyResponses as key bundles.
  while (
      static_cast<size_t>(sync_keys_response.sync_single_key_responses_size()) <
      GetKeyBundleOrder().size()) {
    sync_keys_response.add_sync_single_key_responses();
  }

  // Populate the relevant SyncSingleKeyResponse for each key bundle with data
  // from the input |sync_single_key_responses_map|.
  for (const SyncSingleKeyResponseData& data : sync_single_key_responses_data) {
    sync_keys_response
        .mutable_sync_single_key_responses(GetKeyBundleIndex(data.bundle_name))
        ->CopyFrom(data.single_response);
  }

  return sync_keys_response;
}

}  // namespace

class DeviceSyncCryptAuthV2EnrollerImplTest
    : public testing::Test,
      public MockCryptAuthClientFactory::Observer {
 public:
  DeviceSyncCryptAuthV2EnrollerImplTest(
      const DeviceSyncCryptAuthV2EnrollerImplTest&) = delete;
  DeviceSyncCryptAuthV2EnrollerImplTest& operator=(
      const DeviceSyncCryptAuthV2EnrollerImplTest&) = delete;

 protected:
  DeviceSyncCryptAuthV2EnrollerImplTest()
      : client_factory_(std::make_unique<MockCryptAuthClientFactory>(
            MockCryptAuthClientFactory::MockType::MAKE_NICE_MOCKS)),
        fake_cryptauth_key_creator_factory_(
            std::make_unique<FakeCryptAuthKeyCreatorFactory>()),
        fake_cryptauth_key_proof_computer_factory_(
            std::make_unique<FakeCryptAuthKeyProofComputerFactory>()) {
    CryptAuthKeyRegistryImpl::RegisterPrefs(pref_service_.registry());
    key_registry_ = CryptAuthKeyRegistryImpl::Factory::Create(&pref_service_);

    client_factory_->AddObserver(this);
  }

  ~DeviceSyncCryptAuthV2EnrollerImplTest() override {
    client_factory_->RemoveObserver(this);
  }

  // testing::Test:
  void SetUp() override {
    CryptAuthKeyCreatorImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_key_creator_factory_.get());
    CryptAuthKeyProofComputerImpl::Factory::SetFactoryForTesting(
        fake_cryptauth_key_proof_computer_factory_.get());

    auto mock_timer = std::make_unique<base::MockOneShotTimer>();
    timer_ = mock_timer.get();

    enroller_ = CryptAuthV2EnrollerImpl::Factory::Create(
        key_registry(), client_factory(), std::move(mock_timer));
  }

  // testing::Test:
  void TearDown() override {
    CryptAuthKeyCreatorImpl::Factory::SetFactoryForTesting(nullptr);
    CryptAuthKeyProofComputerImpl::Factory::SetFactoryForTesting(nullptr);
  }

  // MockCryptAuthClientFactory::Observer:
  void OnCryptAuthClientCreated(MockCryptAuthClient* client) override {
    ON_CALL(*client, SyncKeys(testing::_, testing::_, testing::_))
        .WillByDefault(
            Invoke(this, &DeviceSyncCryptAuthV2EnrollerImplTest::OnSyncKeys));

    ON_CALL(*client, EnrollKeys(testing::_, testing::_, testing::_))
        .WillByDefault(
            Invoke(this, &DeviceSyncCryptAuthV2EnrollerImplTest::OnEnrollKeys));

    ON_CALL(*client, GetAccessTokenUsed())
        .WillByDefault(testing::Return(kAccessTokenUsed));
  }

  void CallEnroll(const cryptauthv2::ClientMetadata& client_metadata,
                  const cryptauthv2::ClientAppMetadata& client_app_metadata,
                  const std::optional<cryptauthv2::PolicyReference>&
                      client_directive_policy_reference) {
    enroller()->Enroll(
        client_metadata, client_app_metadata, client_directive_policy_reference,
        base::BindOnce(
            &DeviceSyncCryptAuthV2EnrollerImplTest::OnEnrollmentComplete,
            base::Unretained(this)));
  }

  void OnSyncKeys(const SyncKeysRequest& request,
                  CryptAuthClient::SyncKeysCallback callback,
                  CryptAuthClient::ErrorCallback error_callback) {
    // Check that SyncKeys is called before EnrollKeys.
    EXPECT_FALSE(sync_keys_request_);
    EXPECT_FALSE(enroll_keys_request_);
    EXPECT_TRUE(sync_keys_success_callback_.is_null());
    EXPECT_TRUE(enroll_keys_success_callback_.is_null());
    EXPECT_TRUE(sync_keys_failure_callback_.is_null());
    EXPECT_TRUE(enroll_keys_failure_callback_.is_null());

    sync_keys_request_ = request;
    sync_keys_success_callback_ = std::move(callback);
    sync_keys_failure_callback_ = std::move(error_callback);
  }

  void SendSyncKeysResponse(const SyncKeysResponse& sync_keys_response) {
    std::move(sync_keys_success_callback_).Run(sync_keys_response);
  }

  void FailSyncKeysRequest(const NetworkRequestError& network_request_error) {
    std::move(sync_keys_failure_callback_).Run(network_request_error);
  }

  void RunKeyCreator(
      const base::flat_map<CryptAuthKeyBundle::Name,
                           std::optional<CryptAuthKey>>& new_keys_output,
      const CryptAuthKey& client_ephemeral_dh_output) {
    std::move(key_creator()->create_keys_callback())
        .Run(new_keys_output, client_ephemeral_dh_output);
  }

  void SendEnrollKeysResponse(const EnrollKeysResponse& enroll_keys_response) {
    std::move(enroll_keys_success_callback_).Run(enroll_keys_response);
  }

  void FailEnrollKeysRequest(const NetworkRequestError& network_request_error) {
    std::move(enroll_keys_failure_callback_).Run(network_request_error);
  }

  void OnEnrollKeys(const EnrollKeysRequest& request,
                    CryptAuthClient::EnrollKeysCallback callback,
                    CryptAuthClient::ErrorCallback error_callback) {
    // Check that EnrollKeys is called after a successful SyncKeys call.
    EXPECT_TRUE(sync_keys_request_);
    EXPECT_FALSE(enroll_keys_request_);
    EXPECT_TRUE(sync_keys_success_callback_.is_null());
    EXPECT_TRUE(enroll_keys_success_callback_.is_null());
    EXPECT_FALSE(sync_keys_failure_callback_.is_null());
    EXPECT_TRUE(enroll_keys_failure_callback_.is_null());

    enroll_keys_request_ = request;
    enroll_keys_success_callback_ = std::move(callback);
    enroll_keys_failure_callback_ = std::move(error_callback);
  }

  void VerifyKeyCreatorInputs(
      const base::flat_map<CryptAuthKeyBundle::Name,
                           std::optional<CryptAuthKey>>& expected_new_keys,
      const std::string& expected_server_ephemeral_dh_public_key) {
    ASSERT_EQ(expected_new_keys.size(), key_creator()->keys_to_create().size());
    for (const std::pair<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>&
             name_key_pair : expected_new_keys) {
      const CryptAuthKeyBundle::Name& bundle_name = name_key_pair.first;
      const CryptAuthKey& key = *name_key_pair.second;
      ASSERT_TRUE(base::Contains(key_creator()->keys_to_create(), bundle_name));
      const CryptAuthKeyCreator::CreateKeyData& create_key_data =
          key_creator()->keys_to_create().find(bundle_name)->second;

      EXPECT_EQ(key.status(), create_key_data.status);
      EXPECT_EQ(key.type(), create_key_data.type);

      // Special handling for user key pair.
      if (bundle_name == CryptAuthKeyBundle::Name::kUserKeyPair) {
        EXPECT_EQ(key.handle(), create_key_data.handle);
        const CryptAuthKey* current_active_user_key_pair =
            key_registry()->GetActiveKey(
                CryptAuthKeyBundle::Name::kUserKeyPair);
        if (current_active_user_key_pair) {
          EXPECT_EQ(key.public_key(), create_key_data.public_key);
          EXPECT_EQ(current_active_user_key_pair->public_key(),
                    create_key_data.public_key);

          EXPECT_EQ(key.private_key(), create_key_data.private_key);
          EXPECT_EQ(current_active_user_key_pair->private_key(),
                    create_key_data.private_key);

          EXPECT_EQ(KeyType::P256, create_key_data.type);
          EXPECT_EQ(CryptAuthKey::Status::kActive, create_key_data.status);
        }
      }
    }

    ASSERT_TRUE(key_creator()->server_ephemeral_dh()->IsAsymmetricKey());
    EXPECT_EQ(expected_server_ephemeral_dh_public_key,
              key_creator()->server_ephemeral_dh()->public_key());
    EXPECT_EQ(KeyType::P256, key_creator()->server_ephemeral_dh()->type());
  }

  void VerifyEnrollSingleKeyRequest(const CryptAuthKeyBundle::Name& bundle_name,
                                    const CryptAuthKey& new_key) {
    const EnrollSingleKeyRequest& single_request_user_key_pair =
        enroll_keys_request()->enroll_single_key_requests(
            GetKeyBundleIndex(bundle_name));

    std::string bundle_name_str =
        CryptAuthKeyBundle::KeyBundleNameEnumToString(bundle_name);

    EXPECT_EQ(bundle_name_str, single_request_user_key_pair.key_name());

    EXPECT_EQ(new_key.handle(), single_request_user_key_pair.new_key_handle());

    // No private or symmetric keys should be sent to CryptAuth, so key_material
    // should only ever be populated with a public key.
    EXPECT_EQ(new_key.IsAsymmetricKey() ? new_key.public_key() : std::string(),
              single_request_user_key_pair.key_material());

    EXPECT_EQ(
        CryptAuthKeyProofComputerImpl::Factory::Create()->ComputeKeyProof(
            new_key, kRandomSessionId, kCryptAuthKeyProofSalt, bundle_name_str),
        single_request_user_key_pair.key_proof());
  }

  CryptAuthV2Enroller* enroller() { return enroller_.get(); }

  CryptAuthKeyRegistry* key_registry() { return key_registry_.get(); }

  CryptAuthClientFactory* client_factory() { return client_factory_.get(); }

  FakeCryptAuthKeyProofComputerFactory* key_proof_computer_factory() {
    return fake_cryptauth_key_proof_computer_factory_.get();
  }

  base::MockOneShotTimer* timer() { return timer_; }

  const std::optional<SyncKeysRequest>& sync_keys_request() {
    return sync_keys_request_;
  }

  const std::optional<EnrollKeysRequest>& enroll_keys_request() {
    return enroll_keys_request_;
  }

  const std::optional<CryptAuthEnrollmentResult>& enrollment_result() {
    return enrollment_result_;
  }

 private:
  void OnEnrollmentComplete(
      const CryptAuthEnrollmentResult& enrollment_result) {
    enrollment_result_ = enrollment_result;
  }

  FakeCryptAuthKeyCreator* key_creator() {
    return fake_cryptauth_key_creator_factory_->instance();
  }

  TestingPrefServiceSimple pref_service_;
  std::unique_ptr<CryptAuthKeyRegistry> key_registry_;
  std::unique_ptr<MockCryptAuthClientFactory> client_factory_;
  raw_ptr<base::MockOneShotTimer, DanglingUntriaged> timer_;

  std::unique_ptr<FakeCryptAuthKeyCreatorFactory>
      fake_cryptauth_key_creator_factory_;
  std::unique_ptr<FakeCryptAuthKeyProofComputerFactory>
      fake_cryptauth_key_proof_computer_factory_;

  // Parameters passed to the CryptAuthClient functions {Sync,Enroll}Keys().
  std::optional<SyncKeysRequest> sync_keys_request_;
  std::optional<EnrollKeysRequest> enroll_keys_request_;
  CryptAuthClient::SyncKeysCallback sync_keys_success_callback_;
  CryptAuthClient::EnrollKeysCallback enroll_keys_success_callback_;
  CryptAuthClient::ErrorCallback sync_keys_failure_callback_;
  CryptAuthClient::ErrorCallback enroll_keys_failure_callback_;

  std::optional<CryptAuthEnrollmentResult> enrollment_result_;

  std::unique_ptr<CryptAuthV2Enroller> enroller_;
};

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, SuccessfulEnrollment) {
  // Seed key registry.
  key_registry()->AddKey(CryptAuthKeyBundle::Name::kUserKeyPair,
                         kOldActiveAsymmetricKey);
  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kUserKeyPair,
                                  GetOldKeyDirectiveForTest());
  CryptAuthKeyBundle expected_key_bundle_user_key_pair(
      *key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kUserKeyPair));

  key_registry()->AddKey(CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
                         kOldActiveSymmetricKey);
  key_registry()->AddKey(CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
                         kOldInactiveSymmetricKey);
  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
                                  GetOldKeyDirectiveForTest());
  CryptAuthKeyBundle expected_key_bundle_legacy_authzen_key(
      *key_registry()->GetKeyBundle(
          CryptAuthKeyBundle::Name::kLegacyAuthzenKey));

  // Start the enrollment flow.
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  ClientDirective expected_new_client_directive =
      GetNewClientDirectiveForTest();
  KeyDirective expected_new_key_directive = GetNewKeyDirectiveForTest();

  // For kUserKeyPair (special case):
  //   - active --> temporarily active during key creation
  //   - new --> same handle so overwrites active key with same material
  // For kLegacyAuthzenKey:
  //   - active --> deleted
  //   - inactive --> temporarily active during key creation
  //   - new --> active after created
  std::vector<SyncSingleKeyResponseData> sync_single_key_responses_data = {
      SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {{kCryptAuthFixedUserKeyPairHandle,
            SyncSingleKeyResponse::ACTIVATE}} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          expected_new_key_directive /* new_key_directive */),
      SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kLegacyAuthzenKey, key_registry(),
          {{kOldActiveSymmetricKeyHandle, SyncSingleKeyResponse::DELETE},
           {kOldInactiveSymmetricKeyHandle,
            SyncSingleKeyResponse::ACTIVATE}} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::RAW256 /* new_key_type */,
          expected_new_key_directive /* new_key_directive */)};

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse(sync_single_key_responses_data, kRandomSessionId,
                            kServerEphemeralDh, expected_new_client_directive);

  // Assume a successful SyncKeys() call.
  SendSyncKeysResponse(sync_keys_response);

  // Verify that the key actions were applied. (Note: New keys not created yet.)
  // No key actions expected for kUserKeyPair.
  EXPECT_EQ(
      expected_key_bundle_user_key_pair,
      *key_registry()->GetKeyBundle(CryptAuthKeyBundle::Name::kUserKeyPair));

  // In kLegacyAuthzenKey bundle, former active key should have been deleted and
  // former inactive key should now be active.
  expected_key_bundle_legacy_authzen_key.DeleteKey(
      kOldActiveSymmetricKeyHandle);
  expected_key_bundle_legacy_authzen_key.SetActiveKey(
      kOldInactiveSymmetricKeyHandle);
  EXPECT_EQ(expected_key_bundle_legacy_authzen_key,
            *key_registry()->GetKeyBundle(
                CryptAuthKeyBundle::Name::kLegacyAuthzenKey));

  // Verify the key creation data, and assume successful key creation.
  // Note: Since an active user key pair already exists, the same key material
  // should re-used.
  base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>
      expected_new_keys = {{CryptAuthKeyBundle::Name::kUserKeyPair,
                            std::make_optional(CryptAuthKey(
                                kOldActivePublicKey, kOldActivePrivateKey,
                                CryptAuthKey::Status::kActive, KeyType::P256,
                                kCryptAuthFixedUserKeyPairHandle))},
                           {CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
                            std::make_optional(CryptAuthKey(
                                kNewSymmetricKey, CryptAuthKey::Status::kActive,
                                KeyType::RAW256, kNewSymmetricKeyHandle))}};

  VerifyKeyCreatorInputs(
      expected_new_keys,
      kServerEphemeralDh /* expected_server_ephemeral_dh_public_key */);

  RunKeyCreator(expected_new_keys, kClientEphemeralDh);

  // Verify EnrollKeysRequest.
  EXPECT_EQ(kRandomSessionId, enroll_keys_request()->random_session_id());
  EXPECT_EQ(kClientDhPublicKey, enroll_keys_request()->client_ephemeral_dh());
  EXPECT_EQ(2, enroll_keys_request()->enroll_single_key_requests_size());
  VerifyEnrollSingleKeyRequest(
      CryptAuthKeyBundle::Name::kUserKeyPair,
      *expected_new_keys.find(CryptAuthKeyBundle::Name::kUserKeyPair)->second);
  VerifyEnrollSingleKeyRequest(
      CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
      *expected_new_keys.find(CryptAuthKeyBundle::Name::kLegacyAuthzenKey)
           ->second);

  // Assume a successful EnrollKeys() call.
  // Note: No parameters in EnrollKeysResponse are processed by the enroller
  // (yet), so send a trivial response.
  SendEnrollKeysResponse(EnrollKeysResponse());

  // Verify enrollment result.
  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::kSuccessNewKeysEnrolled,
                expected_new_client_directive),
            enrollment_result());

  // Verify that the key registry is updated with the newly enrolled keys
  // and new key directives.
  CryptAuthKeyBundle::Name bundle_name = CryptAuthKeyBundle::Name::kUserKeyPair;
  expected_key_bundle_user_key_pair.AddKey(
      *expected_new_keys.find(bundle_name)->second);
  expected_key_bundle_user_key_pair.set_key_directive(
      expected_new_key_directive);
  EXPECT_EQ(expected_key_bundle_user_key_pair,
            *key_registry()->GetKeyBundle(bundle_name));

  bundle_name = CryptAuthKeyBundle::Name::kLegacyAuthzenKey;
  expected_key_bundle_legacy_authzen_key.AddKey(
      *expected_new_keys.find(bundle_name)->second);
  expected_key_bundle_legacy_authzen_key.set_key_directive(
      expected_new_key_directive);
  EXPECT_EQ(expected_key_bundle_legacy_authzen_key,
            *key_registry()->GetKeyBundle(bundle_name));
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       SuccessfulEnrollment_CreateUserKeyPair_NoKeyInRegistry) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  ClientDirective expected_new_client_directive =
      GetNewClientDirectiveForTest();
  KeyDirective expected_new_key_directive = GetNewKeyDirectiveForTest();

  SyncKeysResponse sync_keys_response = BuildSyncKeysResponse(
      {SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          expected_new_key_directive /* new_key_directive */)},
      kRandomSessionId, kServerEphemeralDh, expected_new_client_directive);

  SendSyncKeysResponse(sync_keys_response);

  // Note: Because there is not an existing kUserKeyPair key in registry, a new
  // key should be generated. (If there was an existing key, its key material
  // would be reused because the kUserKeyPair key should not be rotated.)
  base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>
      expected_new_keys = {
          {CryptAuthKeyBundle::Name::kUserKeyPair,
           std::make_optional(CryptAuthKey(
               kNewPublicKey, kNewPrivateKey, CryptAuthKey::Status::kActive,
               KeyType::P256, kCryptAuthFixedUserKeyPairHandle))}};

  VerifyKeyCreatorInputs(
      expected_new_keys,
      kServerEphemeralDh /* expected_server_ephemeral_dh_public_key */);

  RunKeyCreator(expected_new_keys, kClientEphemeralDh);

  EXPECT_EQ(1, enroll_keys_request()->enroll_single_key_requests_size());
  VerifyEnrollSingleKeyRequest(
      CryptAuthKeyBundle::Name::kUserKeyPair,
      *expected_new_keys.find(CryptAuthKeyBundle::Name::kUserKeyPair)->second);

  SendEnrollKeysResponse(EnrollKeysResponse());

  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::kSuccessNewKeysEnrolled,
                expected_new_client_directive),
            enrollment_result());

  CryptAuthKeyBundle expected_key_bundle(
      CryptAuthKeyBundle::Name::kUserKeyPair);
  expected_key_bundle.AddKey(
      *expected_new_keys.find(CryptAuthKeyBundle::Name::kUserKeyPair)->second);
  expected_key_bundle.set_key_directive(expected_new_key_directive);
  EXPECT_EQ(expected_key_bundle, *key_registry()->GetKeyBundle(
                                     CryptAuthKeyBundle::Name::kUserKeyPair));
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       SuccessfulEnrollment_NoKeysCreated) {
  key_registry()->AddKey(CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
                         kOldActiveSymmetricKey);
  key_registry()->AddKey(CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
                         kOldInactiveSymmetricKey);
  key_registry()->SetKeyDirective(CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
                                  GetOldKeyDirectiveForTest());
  CryptAuthKeyBundle expected_key_bundle(*key_registry()->GetKeyBundle(
      CryptAuthKeyBundle::Name::kLegacyAuthzenKey));

  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  // Simulate CryptAuth instructing us to swap active and inactive key states
  // but not create any new keys.
  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kLegacyAuthzenKey, key_registry(),
          {{kOldActiveSymmetricKeyHandle, SyncSingleKeyResponse::DEACTIVATE},
           {kOldInactiveSymmetricKeyHandle,
            SyncSingleKeyResponse::ACTIVATE}} /* handle_to_action_map */,
          SyncSingleKeyResponse::NONE /* new_key_creation */,
          std::nullopt /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  expected_key_bundle.SetActiveKey(kOldInactiveSymmetricKeyHandle);
  EXPECT_EQ(expected_key_bundle,
            *key_registry()->GetKeyBundle(
                CryptAuthKeyBundle::Name::kLegacyAuthzenKey));

  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::kSuccessNoNewKeysNeeded,
                sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_ServerOverloaded) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response = BuildSyncKeysResponse();
  sync_keys_response.set_server_status(SyncKeysResponse::SERVER_OVERLOADED);

  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                          kErrorCryptAuthServerOverloaded,
                                      std::nullopt /* client_directive */),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_MissingSessionId) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response = BuildSyncKeysResponse();
  sync_keys_response.clear_random_session_id();

  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::
                    kErrorSyncKeysResponseMissingRandomSessionId,
                std::nullopt /* client_directive */),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_MissingClientDirective) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response = BuildSyncKeysResponse();
  sync_keys_response.clear_client_directive();

  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::
                    kErrorSyncKeysResponseInvalidClientDirective,
                std::nullopt /* client_directive */),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_InvalidSyncSingleKeyResponsesSize) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response = BuildSyncKeysResponse();
  sync_keys_response.clear_sync_single_key_responses();

  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(
      CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                    kErrorWrongNumberOfSyncSingleKeyResponses,
                                std::nullopt /* client_directive */),
      enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_InvalidKeyActions_Size) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response = BuildSyncKeysResponse();

  // Add a key action for a bundle that has no keys.
  sync_keys_response.mutable_sync_single_key_responses(0)->add_key_actions(
      SyncSingleKeyResponse::ACTIVATE);

  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(
      CryptAuthEnrollmentResult(
          CryptAuthEnrollmentResult::ResultCode::kErrorWrongNumberOfKeyActions,
          sync_keys_response.client_directive()),
      enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_InvalidKeyActions_NoActiveKey) {
  key_registry()->AddKey(CryptAuthKeyBundle::Name::kLegacyAuthzenKey,
                         kOldActiveAsymmetricKey);

  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  // Try to deactivate the only active key.
  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kLegacyAuthzenKey, key_registry(),
          {{kOldActiveSymmetricKeyHandle,
            SyncSingleKeyResponse::DEACTIVATE}} /* handle_to_action_map */,
          SyncSingleKeyResponse::NONE /* new_key_creation */,
          std::nullopt /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(
      CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                    kErrorKeyActionsDoNotSpecifyAnActiveKey,
                                sync_keys_response.client_directive()),
      enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_InvalidKeyCreationInstructions_UnsupportedKeyType) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  // Instruct client to create an unsupported key type, CURVE25519.
  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::CURVE25519 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                          kErrorKeyCreationKeyTypeNotSupported,
                                      sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_InvalidKeyCreationInstructions_UnsupportedUserKeyPairKeyType) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  // Instruct client to create a symmetric user key pair. The user key pair is
  // heavily protected against anything other than P256.
  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::RAW256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::
                    kErrorUserKeyPairCreationInstructionsInvalid,
                sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_InvalidKeyCreationInstructions_NewUserKeyPairMustBeActive) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  // Instruct client to create a new, inactive user key pair. Since there can
  // only be one user key pair in the bundle, a new one must be active.
  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::INACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::
                    kErrorUserKeyPairCreationInstructionsInvalid,
                sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_InvalidKeyCreationInstructions_NoServerDiffieHellman) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kLegacyAuthzenKey, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::RAW256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  sync_keys_response.clear_server_ephemeral_dh();

  SendSyncKeysResponse(sync_keys_response);

  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::
                    kErrorSymmetricKeyCreationMissingServerDiffieHellman,
                sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_KeyCreation_UserKeyPair) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>
      expected_new_keys = {
          {CryptAuthKeyBundle::Name::kUserKeyPair, std::nullopt}};
  RunKeyCreator(expected_new_keys, kClientEphemeralDh);

  EXPECT_EQ(CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                          kErrorUserKeyPairCreationFailed,
                                      sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_KeyCreation_LegacyAuthzenKey) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kLegacyAuthzenKey, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::RAW256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>
      expected_new_keys = {
          {CryptAuthKeyBundle::Name::kLegacyAuthzenKey, std::nullopt}};
  RunKeyCreator(expected_new_keys, kClientEphemeralDh);

  EXPECT_EQ(CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                          kErrorLegacyAuthzenKeyCreationFailed,
                                      sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_KeyCreation_DeviceSyncBetterTogether) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kDeviceSyncBetterTogether, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>
      expected_new_keys = {
          {CryptAuthKeyBundle::Name::kDeviceSyncBetterTogether, std::nullopt}};
  RunKeyCreator(expected_new_keys, kClientEphemeralDh);

  EXPECT_EQ(CryptAuthEnrollmentResult(
                CryptAuthEnrollmentResult::ResultCode::
                    kErrorDeviceSyncBetterTogetherKeyCreationFailed,
                sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_KeyProofComputationFailed) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  key_proof_computer_factory()->set_should_return_null_key_proof(true);

  base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>
      expected_new_keys = {
          {CryptAuthKeyBundle::Name::kUserKeyPair,
           std::make_optional(CryptAuthKey(
               kNewPublicKey, kNewPrivateKey, CryptAuthKey::Status::kActive,
               KeyType::P256, kCryptAuthFixedUserKeyPairHandle))}};
  RunKeyCreator(expected_new_keys, kClientEphemeralDh);

  EXPECT_EQ(CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                          kErrorKeyProofComputationFailed,
                                      sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_SyncKeysApiCall) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  FailSyncKeysRequest(NetworkRequestError::kAuthenticationError);

  EXPECT_EQ(
      CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                    kErrorSyncKeysApiCallAuthenticationError,
                                std::nullopt /* client_directive */),
      enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_EnrollKeysApiCall) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>
      expected_new_keys = {
          {CryptAuthKeyBundle::Name::kUserKeyPair,
           std::make_optional(CryptAuthKey(
               kNewPublicKey, kNewPrivateKey, CryptAuthKey::Status::kActive,
               KeyType::P256, kCryptAuthFixedUserKeyPairHandle))}};
  RunKeyCreator(expected_new_keys, kClientEphemeralDh);

  FailEnrollKeysRequest(NetworkRequestError::kBadRequest);

  EXPECT_EQ(CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                          kErrorEnrollKeysApiCallBadRequest,
                                      sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_Timeout_WaitingForSyncKeysResponse) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  // Timeout waiting for SyncKeysResponse.
  EXPECT_TRUE(timer()->IsRunning());
  timer()->Fire();

  EXPECT_EQ(
      CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                    kErrorTimeoutWaitingForSyncKeysResponse,
                                std::nullopt /* client_directive */),
      enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_Timeout_WaitingForKeyCreation) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  // Timeout waiting for key creation.
  EXPECT_TRUE(timer()->IsRunning());
  timer()->Fire();

  EXPECT_EQ(CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                          kErrorTimeoutWaitingForKeyCreation,
                                      sync_keys_response.client_directive()),
            enrollment_result());
}

TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
       Failure_Timeout_WaitingForEnrollKeysResponse) {
  CallEnroll(GetClientMetadataForTest(),
             cryptauthv2::GetClientAppMetadataForTest(),
             GetPreviousClientDirectivePolicyReferenceForTest());

  SyncKeysResponse sync_keys_response =
      BuildSyncKeysResponse({SyncSingleKeyResponseData(
          CryptAuthKeyBundle::Name::kUserKeyPair, key_registry(),
          {} /* handle_to_action_map */,
          SyncSingleKeyResponse::ACTIVE /* new_key_creation */,
          KeyType::P256 /* new_key_type */,
          std::nullopt /* new_key_directive */)});
  SendSyncKeysResponse(sync_keys_response);

  base::flat_map<CryptAuthKeyBundle::Name, std::optional<CryptAuthKey>>
      expected_new_keys = {
          {CryptAuthKeyBundle::Name::kUserKeyPair,
           std::make_optional(CryptAuthKey(
               kNewPublicKey, kNewPrivateKey, CryptAuthKey::Status::kActive,
               KeyType::P256, kCryptAuthFixedUserKeyPairHandle))}};
  RunKeyCreator(expected_new_keys, kClientEphemeralDh);

  // Timeout waiting for EnrollKeysResponse.
  EXPECT_TRUE(timer()->IsRunning());
  timer()->Fire();

  EXPECT_EQ(
      CryptAuthEnrollmentResult(CryptAuthEnrollmentResult::ResultCode::
                                    kErrorTimeoutWaitingForEnrollKeysResponse,
                                sync_keys_response.client_directive()),
      enrollment_result());
}

}  // namespace device_sync

}  // namespace ash