chromium/chrome/browser/webauthn/enclave_manager_unittest.cc

// Copyright 2024 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/webauthn/enclave_manager.h"

#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "chrome/browser/webauthn/fake_magic_arch.h"
#include "chrome/browser/webauthn/fake_recovery_key_store.h"
#include "chrome/browser/webauthn/fake_security_domain_service.h"
#include "chrome/browser/webauthn/proto/enclave_local_state.pb.h"
#include "chrome/browser/webauthn/test_util.h"
#include "chrome/browser/webauthn/unexportable_key_utils.h"
#include "components/os_crypt/sync/os_crypt_mocker.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync/protocol/webauthn_credential_specifics.pb.h"
#include "components/trusted_vault/command_line_switches.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "crypto/scoped_fake_user_verifying_key_provider.h"
#include "crypto/scoped_mock_unexportable_key_provider.h"
#include "crypto/user_verifying_key.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/authenticator_make_credential_response.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/enclave/constants.h"
#include "device/fido/enclave/enclave_authenticator.h"
#include "device/fido/enclave/types.h"
#include "device/fido/fido_authenticator.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_types.h"
#include "device/fido/json_request.h"
#include "device/fido/public_key_credential_descriptor.h"
#include "device/fido/public_key_credential_params.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/http/http_status_code.h"
#include "services/network/network_service.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/test/fake_test_cert_verifier_params_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_MAC)
#include "crypto/scoped_fake_apple_keychain_v2.h"
#include "device/fido/enclave/icloud_recovery_key_mac.h"
#include "device/fido/mac/scoped_touch_id_test_environment.h"
#include "third_party/boringssl/src/include/openssl/hmac.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#include "base/run_loop.h"
#include "components/trusted_vault/proto/recovery_key_store.pb.h"
#include "components/trusted_vault/proto/vault.pb.h"
#include "components/trusted_vault/proto_string_bytes_conversion.h"
#include "components/trusted_vault/securebox.h"
#include "crypto/signature_verifier.h"
#endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)

// These tests are also disabled under MSAN. The enclave subprocess is written
// in Rust and FFI from Rust to C++ doesn't work in Chromium at this time
// (crbug.com/1369167).
#if !defined(MEMORY_SANITIZER)

enclave;
NoArgFuture;
BoolFuture;

namespace {

constexpr int32_t kSecretVersion =;

constexpr std::array<uint8_t, 32> kTestKey =;
constexpr uint8_t kTestProtobuf[] =;
constexpr std::string_view kTestPINPublicKey =;

#if BUILDFLAG(IS_MAC)
base::span<const uint8_t> ToSpan(std::string_view s) {
  return base::as_bytes(base::make_span(s));
}
#endif  // BUILDFLAG(IS_MAC)

std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> GetTestEntity() {}

std::string StringOfZeros(size_t len) {}

enclave::SigningCallback AlwaysFailsSigningCallback() {}

webauthn_pb::EnclaveLocalState::WrappedPIN GetTestWrappedPIN() {}

struct TempDir {};

std::unique_ptr<network::NetworkService> CreateNetwork(
    mojo::Remote<network::mojom::NetworkContext>* network_context) {}

scoped_refptr<device::JSONRequest> JSONFromString(std::string_view json_str) {}

class EnclaveManagerTest : public testing::Test, EnclaveManager::Observer {};

TEST_F(EnclaveManagerTest, TestInfrastructure) {}

TEST_F(EnclaveManagerTest, Basic) {}

TEST_F(EnclaveManagerTest, SecretsArriveBeforeRegistrationRequested) {}

TEST_F(EnclaveManagerTest, SecretsArriveBeforeRegistrationCompleted) {}

TEST_F(EnclaveManagerTest, RegistrationFailureAndRetry) {}

TEST_F(EnclaveManagerTest, PrimaryUserChange) {}

TEST_F(EnclaveManagerTest, PrimaryUserChangeDiscardsActions) {}

TEST_F(EnclaveManagerTest, AddWithExistingPIN) {}

TEST_F(EnclaveManagerTest, InvalidWrappedPIN) {}

TEST_F(EnclaveManagerTest, SetupWithPIN) {}

TEST_F(EnclaveManagerTest, SetupWithPIN_SecurityDomainFailure) {}

TEST_F(EnclaveManagerTest, SetupWithPIN_CertXMLFailure) {}

TEST_F(EnclaveManagerTest, SetupWithPIN_SigXMLFailure) {}

TEST_F(EnclaveManagerTest, AddDeviceAndPINToAccount) {}

TEST_F(EnclaveManagerTest, ChangePIN) {}

TEST_F(EnclaveManagerTest, ChangePINWithTwoDevices) {}

TEST_F(EnclaveManagerTest, EnclaveForgetsClient_SetupWithPIN) {}

TEST_F(EnclaveManagerTest, EnclaveForgetsClient_AddDeviceToAccount) {}

TEST_F(EnclaveManagerTest, EnclaveForgetsClient_AddDeviceAndPINToAccount) {}

TEST_F(EnclaveManagerTest, RenewPIN) {}

TEST_F(EnclaveManagerTest, EpochChanged) {}

TEST_F(EnclaveManagerTest, PINChanged) {}

TEST_F(EnclaveManagerTest, SigningFails) {}

#if BUILDFLAG(IS_MAC)
TEST_F(EnclaveManagerTest, AddICloudRecoveryKey) {
  ASSERT_TRUE(Register());

  BoolFuture setup_future;
  manager_.SetupWithPIN("123456", setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.is_ready());

  std::unique_ptr<device::enclave::ICloudRecoveryKey> icloud_key =
      device::enclave::ICloudRecoveryKey::CreateForTest();
  std::unique_ptr<trusted_vault::SecureBoxKeyPair> key =
      trusted_vault::SecureBoxKeyPair::CreateByPrivateKeyImport(
          icloud_key->key()->private_key().ExportToBytes());
  BoolFuture icloud_future;
  manager_.AddICloudRecoveryKey(std::move(icloud_key),
                                icloud_future.GetCallback());
  EXPECT_TRUE(icloud_future.Wait());
  EXPECT_TRUE(icloud_future.Get());

  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);

  // Find the iCloud recovery key member.
  const auto icloud_member = std::ranges::find_if(
      security_domain_service_->members(),
      [](const trusted_vault_pb::SecurityDomainMember& member) {
        return member.member_type() == trusted_vault_pb::SecurityDomainMember::
                                           MEMBER_TYPE_ICLOUD_KEYCHAIN;
      });
  ASSERT_NE(icloud_member, security_domain_service_->members().end());
  ASSERT_EQ(trusted_vault::ProtoStringToBytes(icloud_member->public_key()),
            key->public_key().ExportToBytes());

  // Use the iCloud recovery key to recover the security domain secret.
  const trusted_vault_pb::SharedMemberKey& shared_member_key =
      icloud_member->memberships().at(0).keys().at(0);
  const std::optional<std::vector<uint8_t>> security_domain_secret =
      key->private_key().Decrypt(base::span<const uint8_t>(),
                                 ToSpan("V1 shared_key"),
                                 ToSpan(shared_member_key.wrapped_key()));
  ASSERT_TRUE(security_domain_secret);
  EXPECT_EQ(manager_.TakeSecret()->second, *security_domain_secret);

  std::array<uint8_t, SHA256_DIGEST_LENGTH> expected_proof;
  unsigned expected_proof_len;
  HMAC(EVP_sha256(), security_domain_secret->data(),
       security_domain_secret->size(),
       reinterpret_cast<const uint8_t*>(icloud_member->public_key().data()),
       icloud_member->public_key().size(), expected_proof.data(),
       &expected_proof_len);
  ASSERT_EQ(expected_proof_len, expected_proof.size());
  EXPECT_EQ(base::span<const uint8_t>(expected_proof),
            ToSpan(shared_member_key.member_proof()));
}
#endif  // BUILDFLAG(IS_MAC)

TEST_F(EnclaveManagerTest, Unenroll) {}

TEST_F(EnclaveManagerTest, UnenrollRace) {}

TEST_F(EnclaveManagerTest, UnenrollWithoutRegistering) {}

TEST_F(EnclaveManagerTest, LockPINThenChange) {}

// Tests that rely on `ScopedMockUnexportableKeyProvider` only work on
// platforms where EnclaveManager uses `GetUnexportableKeyProvider`, as opposed
// to `GetSoftwareUnsecureUnexportableKeyProvider`.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
#define MAYBE_HardwareKeyLost
#else
#define MAYBE_HardwareKeyLost
#endif
TEST_F(EnclaveManagerTest, MAYBE_HardwareKeyLost) {}

class EnclaveManagerMockTimeTest : public EnclaveManagerTest {};

TEST_F(EnclaveManagerMockTimeTest, AutomaticRenewal) {}

// UV keys are only supported on Windows macOS, and ChromeOS at this time.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)

std::string ToString(base::span<const uint8_t> v) {
  return std::string(v.begin(), v.end());
}

class EnclaveUVTest : public EnclaveManagerTest {
 protected:
  void SetUp() override {
#if BUILDFLAG(IS_MAC)
    scoped_fake_apple_keychain_.SetUVMethod(
        crypto::ScopedFakeAppleKeychainV2::UVMethod::kPasswordOnly);
#endif  // BUILDFLAG(IS_MAC)
  }

  void TearDown() override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
    OverrideWebAuthnChromeosUserVerifyingKeyProviderForTesting(nullptr);
#endif
  }

  void DisableUVKeySupport() {
    fake_provider_.emplace<crypto::ScopedNullUserVerifyingKeyProvider>();
#if BUILDFLAG(IS_CHROMEOS_ASH)
    // The scoped fake provider doesn't cover ChromeOS.
    OverrideWebAuthnChromeosUserVerifyingKeyProviderForTesting([]() {
      return std::unique_ptr<crypto::UserVerifyingKeyProvider>(nullptr);
    });
#endif
  }

  void UseFailingUVKeySupport() {
    fake_provider_.emplace<crypto::ScopedFailingUserVerifyingKeyProvider>();
#if BUILDFLAG(IS_CHROMEOS_ASH)
    // The scoped fake provider doesn't cover ChromeOS.
    NOTIMPLEMENTED();
#endif
  }

  absl::variant<crypto::ScopedFakeUserVerifyingKeyProvider,
                crypto::ScopedNullUserVerifyingKeyProvider,
                crypto::ScopedFailingUserVerifyingKeyProvider>
      fake_provider_;

#if BUILDFLAG(IS_MAC)
  crypto::ScopedFakeAppleKeychainV2 scoped_fake_apple_keychain_{
      "test-keychain-access-group"};
#endif  // BUILDFLAG(IS_MAC)
};

TEST_F(EnclaveUVTest, UserVerifyingKeyAvailable) {
  security_domain_service_->pretend_there_are_members();
  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  ASSERT_FALSE(manager_.has_pending_keys());
  manager_.StoreKeys(gaia_id_, {std::move(key)},
                     /*last_key_version=*/kSecretVersion);
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.has_pending_keys());

  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(add_future.Wait());

#if BUILDFLAG(IS_WIN)
  EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
#else
  EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kUsesSystemUI);
#endif
}

TEST_F(EnclaveUVTest, UserVerifyingKeyUnavailable) {
  DisableUVKeySupport();
  security_domain_service_->pretend_there_are_members();
  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  ASSERT_FALSE(manager_.has_pending_keys());
  manager_.StoreKeys(gaia_id_, {std::move(key)},
                     /*last_key_version=*/kSecretVersion);
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.has_pending_keys());

  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(add_future.Wait());
  ASSERT_TRUE(manager_.is_registered());
  EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kNone);
}

TEST_F(EnclaveUVTest, UserVerifyingKeyLost) {
  security_domain_service_->pretend_there_are_members();
  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  ASSERT_FALSE(manager_.has_pending_keys());
  manager_.StoreKeys(gaia_id_, {std::move(key)},
                     /*last_key_version=*/kSecretVersion);
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.has_pending_keys());

  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(add_future.Wait());

  base::RepeatingClosure quit_closure;
#if BUILDFLAG(IS_WIN)
  // Windows does deferred UV key creation. This test has to trigger the actual
  // create before testing that it is later deleted.
  EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
  auto key_creation_callback = manager_.UserVerifyingKeyCreationCallback();
  quit_closure = task_env_.QuitClosure();
  std::move(key_creation_callback)
      .Run(base::BindLambdaForTesting(
          [&quit_closure](base::span<const uint8_t> uv_public_key) {
            EXPECT_FALSE(uv_public_key.empty());
            quit_closure.Run();
          }));
  task_env_.RunUntilQuit();
#else
  ASSERT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kUsesSystemUI);
#endif
  manager_.ClearCachedKeysForTesting();
  DisableUVKeySupport();
  auto signing_callback =
      manager_.UserVerifyingKeySigningCallback(/*options=*/{});
  quit_closure = task_env_.QuitClosure();
  std::move(signing_callback)
      .Run({1, 2, 3, 4},
           base::BindLambdaForTesting(
               [&quit_closure](
                   std::optional<enclave::ClientSignature> signature) {
                 EXPECT_EQ(signature, std::nullopt);
                 quit_closure.Run();
               }));
  task_env_.RunUntilQuit();
  EXPECT_FALSE(manager_.is_registered());
}

TEST_F(EnclaveUVTest, UserVerifyingKeyUseExisting) {
  security_domain_service_->pretend_there_are_members();
  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  base::test::TestFuture<
      base::expected<std::unique_ptr<crypto::UserVerifyingSigningKey>,
                     crypto::UserVerifyingKeyCreationError>>
      key_future;
  std::unique_ptr<crypto::UserVerifyingKeyProvider> key_provider =
      crypto::GetUserVerifyingKeyProvider(/*config=*/{});
  key_provider->GenerateUserVerifyingSigningKey(
      std::array{crypto::SignatureVerifier::ECDSA_SHA256},
      key_future.GetCallback());
  EXPECT_TRUE(key_future.Wait());
  manager_.local_state_for_testing()
      .mutable_users()
      ->begin()
      ->second.set_uv_public_key(
          ToString(key_future.Get().value()->GetPublicKey()));
  manager_.local_state_for_testing()
      .mutable_users()
      ->begin()
      ->second.set_wrapped_uv_private_key(
          key_future.Get().value()->GetKeyLabel());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  ASSERT_FALSE(manager_.has_pending_keys());
  manager_.StoreKeys(gaia_id_, {std::move(key)},
                     /*last_key_version=*/kSecretVersion);
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.has_pending_keys());

  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(add_future.Wait());

  ASSERT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kUsesSystemUI);
}

#if BUILDFLAG(IS_MAC)
// Tests that if biometrics are available on macOS, Chrome will handle prompting
// the user for biometrics.
TEST_F(EnclaveUVTest, ChromeHandlesBiometrics) {
  security_domain_service_->pretend_there_are_members();
  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  ASSERT_FALSE(manager_.has_pending_keys());
  manager_.StoreKeys(gaia_id_, {std::move(key)},
                     /*last_key_version=*/kSecretVersion);
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.has_pending_keys());

  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(add_future.Wait());

  scoped_fake_apple_keychain_.SetUVMethod(
      crypto::ScopedFakeAppleKeychainV2::UVMethod::kBiometrics);
  // The TouchID view is only available on macOS 12+.
  if (__builtin_available(macos 12, *)) {
    EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/true),
              EnclaveManager::UvKeyState::kUsesChromeUI);
  } else {
    EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
              EnclaveManager::UvKeyState::kUsesSystemUI);
  }

  scoped_fake_apple_keychain_.SetUVMethod(
      crypto::ScopedFakeAppleKeychainV2::UVMethod::kPasswordOnly);
  EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kUsesSystemUI);
}
#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)
TEST_F(EnclaveUVTest, DeferredUVKeyCreation) {
  security_domain_service_->pretend_there_are_members();
  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  ASSERT_FALSE(manager_.has_pending_keys());
  manager_.StoreKeys(gaia_id_, {std::move(key)},
                     /*last_key_version=*/kSecretVersion);
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.has_pending_keys());

  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(add_future.Wait());

  EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
  const auto& user_state =
      manager_.local_state_for_testing().users().find(gaia_id_)->second;
  EXPECT_TRUE(user_state.has_deferred_uv_key_creation() &&
              user_state.deferred_uv_key_creation());
  EXPECT_TRUE(user_state.wrapped_uv_private_key().empty());

  auto key_creation_callback = manager_.UserVerifyingKeyCreationCallback();
  auto quit_closure = task_env_.QuitClosure();
  std::move(key_creation_callback)
      .Run(base::BindLambdaForTesting(
          [&quit_closure](base::span<const uint8_t> uv_public_key) {
            EXPECT_FALSE(uv_public_key.empty());
            quit_closure.Run();
          }));
  task_env_.RunUntilQuit();

  EXPECT_FALSE(user_state.deferred_uv_key_creation());
  EXPECT_FALSE(user_state.wrapped_uv_private_key().empty());
}

TEST_F(EnclaveUVTest, UnregisterOnFailedDeferredUVKeyCreation) {
  security_domain_service_->pretend_there_are_members();
  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  ASSERT_FALSE(manager_.has_pending_keys());
  manager_.StoreKeys(gaia_id_, {std::move(key)},
                     /*last_key_version=*/kSecretVersion);
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.has_pending_keys());

  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(add_future.Wait());

  EXPECT_EQ(manager_.uv_key_state(/*platform_has_biometrics=*/false),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
  const auto& user_state =
      manager_.local_state_for_testing().users().find(gaia_id_)->second;
  EXPECT_TRUE(user_state.deferred_uv_key_creation());
  EXPECT_TRUE(user_state.wrapped_uv_private_key().empty());

  UseFailingUVKeySupport();
  EnclaveManager::EnableInvariantChecksForTesting(false);

  base::RunLoop run_loop;
  auto ui_request = std::make_unique<enclave::CredentialRequest>();
  ui_request->signing_callback = manager_.IdentityKeySigningCallback();
  ui_request->wrapped_secret =
      *manager_.GetWrappedSecret(/*version=*/kSecretVersion);
  ui_request->entity = GetTestEntity();
  ui_request->claimed_pin = nullptr;
  ui_request->save_passkey_callback = base::BindOnce(
      [](sync_pb::WebauthnCredentialSpecifics) { NOTREACHED(); });
  ui_request->user_verified = true;
  ui_request->uv_key_creation_callback =
      manager_.UserVerifyingKeyCreationCallback();
  ui_request->unregister_callback =
      base::BindOnce(&EnclaveManager::Unenroll, manager_.GetWeakPtr(),
                     base::BindLambdaForTesting(
                         [&run_loop](bool) { run_loop.QuitWhenIdle(); }));

  GetAssertionResponseExpectation expected_response;
  expected_response.result = device::GetAssertionStatus::kEnclaveError;
  expected_response.size = 0;
  DoAssertion(GetTestEntity(), /*claimed_pin=*/nullptr, expected_response,
              std::move(ui_request));
  run_loop.Run();

  EXPECT_FALSE(manager_.is_registered());
}

#endif  // BUILDFLAG(IS_WIN)

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)

}  // namespace

#endif  // !defined(MEMORY_SANITIZER)