chromium/chrome/browser/webauthn/enclave_authenticator_browsertest.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 <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/base64url.h"
#include "base/callback_list.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/process/process.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_logging_settings.h"
#include "base/test/simple_test_clock.h"
#include "base/test/test_future.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_controller.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
#include "chrome/browser/webauthn/enclave_manager.h"
#include "chrome/browser/webauthn/enclave_manager_factory.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/gpm_enclave_controller.h"
#include "chrome/browser/webauthn/passkey_model_factory.h"
#include "chrome/browser/webauthn/test_util.h"
#include "chrome/browser/webauthn/webauthn_pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/os_crypt/sync/os_crypt_mocker.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/protocol/webauthn_credential_specifics.pb.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_service_impl.h"
#include "components/sync/test/fake_server_network_resources.h"
#include "components/trusted_vault/securebox.h"
#include "components/trusted_vault/test/mock_trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "components/webauthn/core/browser/passkey_model.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "crypto/scoped_fake_user_verifying_key_provider.h"
#include "crypto/scoped_mock_unexportable_key_provider.h"
#include "crypto/unexportable_key.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/fido/enclave/constants.h"
#include "device/fido/features.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/fido_types.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_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"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(IS_WIN)
#include "device/fido/win/fake_webauthn_api.h"
#include "device/fido/win/util.h"
#include "device/fido/win/webauthn_api.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "base/test/test_future.h"
#include "chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.h"
#include "chrome/common/chrome_version.h"
#include "components/trusted_vault/proto/vault.pb.h"
#include "components/trusted_vault/proto_string_bytes_conversion.h"
#include "crypto/scoped_fake_apple_keychain_v2.h"
#include "device/fido/enclave/icloud_recovery_key_mac.h"
#include "device/fido/mac/fake_icloud_keychain.h"
#include "device/fido/mac/util.h"
#endif  // BUILDFLAG(IS_MAC)

// These tests are 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)

namespace {

MockTrustedVaultConnection;

constexpr int32_t kSecretVersion =;
constexpr uint8_t kSecurityDomainSecret[32] =;
constexpr char kEmail[] =;
constexpr char kEmailLocalPartOnly[] =;
// This value is derived by the Sync testing code from `kEmail` but is needed
// directly in these tests in order to simulate the `StoreKeys` calls to the
// `EnclaveManager`.
constexpr char kGaiaId[] =;

// Protobuf generated by printing one generated by an enclave using
// `kSecurityDomainSecret`.
constexpr uint8_t kTestProtobuf[] =;

base::span<const uint8_t, 16> TestProtobufCredId() {}

static constexpr char kIsUVPAA[] =;

static constexpr char kMakeCredentialUvDiscouraged[] =;

static constexpr char kMakeCredentialReturnId[] =;

static constexpr char kMakeCredentialWithExcludedCredential[] =;

static constexpr char kMakeCredentialCrossPlatform[] =;

static constexpr char kMakeCredentialAttachmentPlatform[] =;

static constexpr char kMakeCredentialUvRequired[] =;

static constexpr char kMakeCredentialWithPrf[] =;

static constexpr char kMakeCredentialGoogle[] =;

static constexpr char kGetAssertionWithPrf[] =;

static constexpr char kGetAssertionUvDiscouraged[] =;

static constexpr char kAbortableGetAssertion[] =;

static constexpr char kAbort[] =;

static constexpr char kGetAssertionUvDiscouragedWithCredId[] =;

static constexpr char
    kGetAssertionUvDiscouragedWithCredIdAndInternalTransport[] =;

static constexpr char kGetAssertionUvRequired[] =;

static constexpr char kGetAssertionUvPreferred[] =;

static constexpr char kGetAssertionConditionalUI[] =;

bool IsReady(GPMEnclaveController::AccountState state) {}

bool IsMechanismEnclaveCredential(
    const AuthenticatorRequestDialogModel::Mechanism& mechanism) {}

struct TempDir {};

class EnclaveAuthenticatorBrowserTest : public SyncTest {};

class EnclaveAuthenticatorWithPinBrowserTest
    : public EnclaveAuthenticatorBrowserTest {};

// Parses the string resulting from the Javascript snippets that exercise the
// PRF extension.
std::tuple<bool, std::string, std::string> ParsePrfResult(
    std::string_view result_view) {}

// Parses the output of `kMakeCredentialReturnId` and returns the credential ID
// that was created.
std::optional<std::vector<uint8_t>> ParseCredentialId(
    std::string_view result_view) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       RegisterDeviceWithGpmPin_MakeCredential_Success) {}

#if BUILDFLAG(IS_MAC)

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       RegisterICloudDriveEnabled_NoGPMDefault) {
  if (__builtin_available(macOS 13.5, *)) {
    // Override iCloud Drive to appear enabled. Because of this GPM should not
    // be the default since none of the other conditions apply.
    scoped_icloud_drive_override_.reset();
    scoped_icloud_drive_override_ = OverrideICloudDriveEnabled(true);

    trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
        registration_state_result;
    registration_state_result.state = trusted_vault::
        DownloadAuthenticationFactorsRegistrationStateResult::State::kEmpty;
    SetMockVaultConnectionOnRequestDelegate(
        std::move(registration_state_result));

    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::DOMMessageQueue message_queue(web_contents);
    content::ExecuteScriptAsync(web_contents, kMakeCredentialUvDiscouraged);
    delegate_observer()->WaitForUI();

    EXPECT_EQ(dialog_model()->step(),
              AuthenticatorRequestDialogModel::Step::kMechanismSelection);
  }
}

IN_PROC_BROWSER_TEST_F(
    EnclaveAuthenticatorWithPinBrowserTest,
    RegisterICloudDriveEnabledButAlsoPasskeyPresent_GPMDefault) {
  if (__builtin_available(macOS 13.5, *)) {
    // Override iCloud Drive to appear enabled, but also add a passkey to the
    // store. That should be sufficient for GPM to be the default.
    scoped_icloud_drive_override_.reset();
    scoped_icloud_drive_override_ = OverrideICloudDriveEnabled(true);
    AddTestPasskeyToModel();

    trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
        registration_state_result;
    registration_state_result.state = trusted_vault::
        DownloadAuthenticationFactorsRegistrationStateResult::State::kEmpty;
    SetMockVaultConnectionOnRequestDelegate(
        std::move(registration_state_result));

    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::DOMMessageQueue message_queue(web_contents);
    content::ExecuteScriptAsync(web_contents, kMakeCredentialUvDiscouraged);
    delegate_observer()->WaitForUI();

    EXPECT_EQ(dialog_model()->step(),
              AuthenticatorRequestDialogModel::Step::kGPMCreatePasskey);
  }
}

IN_PROC_BROWSER_TEST_F(
    EnclaveAuthenticatorWithPinBrowserTest,
    RegisterICloudDriveEnabledButPermissionDenied_GPMDefault) {
  // Override iCloud Drive to appear enabled, but override the iCloud Keychain
  // permission to appear as if the user denied Chrome permission. That should
  // cause GPM to be the default.
  if (__builtin_available(macOS 13.5, *)) {
    scoped_icloud_drive_override_.reset();
    scoped_icloud_drive_override_ = OverrideICloudDriveEnabled(true);
    fake_icloud_keychain_.reset();
    fake_icloud_keychain_ =
        device::fido::icloud_keychain::NewFakeWithPermission(false);

    trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
        registration_state_result;
    registration_state_result.state = trusted_vault::
        DownloadAuthenticationFactorsRegistrationStateResult::State::kEmpty;
    SetMockVaultConnectionOnRequestDelegate(
        std::move(registration_state_result));

    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::DOMMessageQueue message_queue(web_contents);
    content::ExecuteScriptAsync(web_contents, kMakeCredentialUvDiscouraged);
    delegate_observer()->WaitForUI();

    EXPECT_EQ(dialog_model()->step(),
              AuthenticatorRequestDialogModel::Step::kGPMCreatePasskey);
  }
}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       MacOs13_4_OrLess_GPMDefault) {
  if (__builtin_available(macOS 13.5, *)) {
    // __builtin_available cannot be negated thus an `else` block has to be
    // used.
  } else {
    // For versions of macOS < 13.5, it doesn't matter if iCloud Drive is
    // enabled, there's no iCloud Keychain support and so GPM should always be
    // the default.
    scoped_icloud_drive_override_.reset();
    scoped_icloud_drive_override_ = OverrideICloudDriveEnabled(true);

    trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
        registration_state_result;
    registration_state_result.state = trusted_vault::
        DownloadAuthenticationFactorsRegistrationStateResult::State::kEmpty;
    SetMockVaultConnectionOnRequestDelegate(
        std::move(registration_state_result));

    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::DOMMessageQueue message_queue(web_contents);
    content::ExecuteScriptAsync(web_contents, kMakeCredentialUvDiscouraged);
    delegate_observer()->WaitForUI();

    EXPECT_EQ(dialog_model()->step(),
              AuthenticatorRequestDialogModel::Step::kGPMCreatePasskey);
  }
}

#endif

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       MakeCredentialWithPrf) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       GetAssertionWithPrf) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       RegisterDeviceWithGpmPin_MakeCredentialWithUV_Success) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       MakeCredential_RecoverWithGPMPIN_Success) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       MakeCredential_RecoverWithLSKF_Success) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       CreatingDuplicateGivesInvalidStateError) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       RecoverWithLSKF_GetAssertion_Success) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       RegisterDeviceWithGpmPin_UVRequestsWithWrongPIN) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       GpmPinRegistrationPersistAcrossRestart) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest, UserCancelsUV) {}

// Tests that if the enclave is still loading when the user taps a passkey from
// autofill, Chrome does not jump to the modal loading UI as autofill can
// display that instead. Regression test for crbug.com/343480031.
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       ConditionalMediationLoading) {}

// Tests tapping a passkey from autofill after the trusted vault service times
// out. Regression test for crbug.com/343669719.
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       SelectPasskeyAfterTimeout) {}

// Tests a trusted vault service timeout after tapping a passkey from autofill.
// Regression test for crbug.com/343669719.
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       SelectPasskeyThenTimeout) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       GpmEnclaveNeedsReauth) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       UserResetsSecurityDomain) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       SecurityDomainCheckTimesOut) {}

class EnclaveAuthenticatorWithoutPinBrowserTest
    : public EnclaveAuthenticatorBrowserTest {};

// Without a Windows-on-ARM device we've been unable to debug why these
// tests fail in that that context.
#if BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)
#define MAYBE_NotAvailableWithoutUV
#else
#define MAYBE_NotAvailableWithoutUV
#endif
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       MAYBE_NotAvailableWithoutUV) {}

#if BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)
#define MAYBE_NotAvailableForEmptyAccounts
#else
#define MAYBE_NotAvailableForEmptyAccounts
#endif
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       MAYBE_NotAvailableForEmptyAccounts) {}

#if BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)
#define MAYBE_NoGpmCredentialsIfDeviceCannotBeEnrolled
#else
#define MAYBE_NoGpmCredentialsIfDeviceCannotBeEnrolled
#endif
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       MAYBE_NoGpmCredentialsIfDeviceCannotBeEnrolled) {}

#if BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)
#define MAYBE_NotAvailableIfLskfsAreTooOld
#else
#define MAYBE_NotAvailableIfLskfsAreTooOld
#endif
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       MAYBE_NotAvailableIfLskfsAreTooOld) {}

#if BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)
#define MAYBE_NoGpmForCrossPlatformAttachment
#else
#define MAYBE_NoGpmForCrossPlatformAttachment
#endif
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       MAYBE_NoGpmForCrossPlatformAttachment) {}

#if BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)
#define MAYBE_NoGpmCreationIfPasswordManagerDisabled
#else
#define MAYBE_NoGpmCreationIfPasswordManagerDisabled
#endif
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       MAYBE_NoGpmCreationIfPasswordManagerDisabled) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       EnrollAndCreate) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       GetAssertionWithPlatformUV) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       NotForSameGoogleAccount) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       IncognitoModeMakeCredential) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       IncognitoModeGetAssertion) {}

#if BUILDFLAG(IS_MAC)

bool MacBiometricApisAvailable() {
  if (__builtin_available(macOS 12, *)) {
    return true;
  }
  return false;
}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       BiometricsDisabledDuringRequest) {
  if (!MacBiometricApisAvailable()) {
    GTEST_SKIP() << "Need macOS >= 12";
  }

  // If Touch ID is disabled during the course of a request, the UV disposition
  // shouldn't also change. I.e. if we started with the expectation of doing
  // UV=true, the UI expects that to continue, even if we need macOS to prompt
  // for the system password.
  trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
      registration_state_result;
  registration_state_result.state = trusted_vault::
      DownloadAuthenticationFactorsRegistrationStateResult::State::kRecoverable;
  SetMockVaultConnectionOnRequestDelegate(std::move(registration_state_result));
  security_domain_service_->pretend_there_are_members();
  AddTestPasskeyToModel();
  EnableUVKeySupport();

  SetBiometricsEnabled(true);

  // The first get() request is satisfied implicitly because recovery was done.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::DOMMessageQueue message_queue(web_contents);
  content::ExecuteScriptAsync(web_contents, kGetAssertionUvPreferred);
  delegate_observer()->WaitForUI();
  model_observer()->SetStepToObserve(
      AuthenticatorRequestDialogModel::Step::kRecoverSecurityDomain);
  model_observer()->WaitForStep();

  EnclaveManagerFactory::GetAsEnclaveManagerForProfile(browser()->profile())
      ->StoreKeys(kGaiaId,
                  {std::vector<uint8_t>(std::begin(kSecurityDomainSecret),
                                        std::end(kSecurityDomainSecret))},
                  kSecretVersion);

  std::string script_result;
  ASSERT_TRUE(message_queue.WaitForMessage(&script_result));
  EXPECT_EQ(script_result, "\"webauthn: uv=true\"");

  // During this second get() request, Touch ID will be disabled.
  content::ExecuteScriptAsync(web_contents, kGetAssertionUvPreferred);
  delegate_observer()->WaitForUI();
  model_observer()->SetStepToObserve(
      AuthenticatorRequestDialogModel::Step::kGPMTouchID);
  model_observer()->WaitForStep();
  SetBiometricsEnabled(false);
  // Disable Touch ID. The request should still resolve with uv=true.
  request_delegate()->dialog_model()->OnTouchIDComplete(false);

  EnclaveManagerFactory::GetAsEnclaveManagerForProfile(browser()->profile())
      ->StoreKeys(kGaiaId,
                  {std::vector<uint8_t>(std::begin(kSecurityDomainSecret),
                                        std::end(kSecurityDomainSecret))},
                  kSecretVersion);

  ASSERT_TRUE(message_queue.WaitForMessage(&script_result));
  EXPECT_EQ(script_result, "\"webauthn: uv=true\"");
}

constexpr char kICloudKeychainRecoveryKeyAccessGroup[] =
    MAC_TEAM_IDENTIFIER_STRING ".com.google.common.folsom";

class EnclaveICloudRecoveryKeyTest
    : public EnclaveAuthenticatorWithPinBrowserTest {
 protected:
  base::test::ScopedFeatureList scoped_feature_list_{
      device::kWebAuthnICloudRecoveryKey};
  crypto::ScopedFakeAppleKeychainV2 scoped_fake_apple_keychain_{
      kICloudKeychainRecoveryKeyAccessGroup};
};

// Tests enrolling an iCloud recovery key when there are no keys already
// enrolled with the recovery service or present in iCloud keychain.
IN_PROC_BROWSER_TEST_F(EnclaveICloudRecoveryKeyTest, Enroll) {
  // Do a make credential request and enroll a PIN.
  trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
      registration_state_result;
  registration_state_result.state = trusted_vault::
      DownloadAuthenticationFactorsRegistrationStateResult::State::kEmpty;
  SetMockVaultConnectionOnRequestDelegate(std::move(registration_state_result));

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::DOMMessageQueue message_queue(web_contents);
  content::ExecuteScriptAsync(web_contents, kMakeCredentialUvDiscouraged);
  delegate_observer()->WaitForUI();

  EXPECT_EQ(dialog_model()->step(),
            AuthenticatorRequestDialogModel::Step::kGPMCreatePasskey);
  EXPECT_EQ(request_delegate()
                ->enclave_controller_for_testing()
                ->account_state_for_testing(),
            GPMEnclaveController::AccountState::kEmpty);
  dialog_model()->OnGPMCreatePasskey();
  EXPECT_EQ(dialog_model()->step(),
            AuthenticatorRequestDialogModel::Step::kGPMCreatePin);
  dialog_model()->OnGPMPinEntered(u"123456");

  std::string script_result;
  ASSERT_TRUE(message_queue.WaitForMessage(&script_result));
  EXPECT_EQ(script_result, "\"webauthn: OK\"");

  delegate_observer()->WaitForDelegateDestruction();

  // 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());

  // Find the recovery key on iCloud keychain.
  base::test::TestFuture<
      std::vector<std::unique_ptr<device::enclave::ICloudRecoveryKey>>>
      future;
  device::enclave::ICloudRecoveryKey::Retrieve(
      future.GetCallback(), kICloudKeychainRecoveryKeyAccessGroup);
  EXPECT_TRUE(future.Wait());
  std::vector<std::unique_ptr<device::enclave::ICloudRecoveryKey>>
      recovery_keys = future.Take();
  ASSERT_EQ(recovery_keys.size(), 1u);
  std::unique_ptr<device::enclave::ICloudRecoveryKey> icloud_key =
      std::move(recovery_keys.at(0));

  // Make sure they match.
  EXPECT_EQ(trusted_vault::ProtoStringToBytes(icloud_member->public_key()),
            icloud_key->key()->public_key().ExportToBytes());
}

// Tests enrolling an iCloud recovery key when there is already a recovery key
// stored in iCloud keychain. A new key should be created.
// Regression test for https://crbug.com/360321350.
IN_PROC_BROWSER_TEST_F(EnclaveICloudRecoveryKeyTest,
                       EnrollWithExistingKeyInICloud) {
  // Create an iCloud recovery key.
  base::test::TestFuture<std::unique_ptr<device::enclave::ICloudRecoveryKey>>
      future;
  device::enclave::ICloudRecoveryKey::Create(
      future.GetCallback(), kICloudKeychainRecoveryKeyAccessGroup);
  EXPECT_TRUE(future.Wait());
  std::unique_ptr<device::enclave::ICloudRecoveryKey> existing_icloud_key =
      future.Take();
  ASSERT_TRUE(existing_icloud_key);

  // Do a make credential request and enroll a PIN.
  trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
      registration_state_result;
  registration_state_result.state = trusted_vault::
      DownloadAuthenticationFactorsRegistrationStateResult::State::kEmpty;
  SetMockVaultConnectionOnRequestDelegate(std::move(registration_state_result));

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::DOMMessageQueue message_queue(web_contents);
  content::ExecuteScriptAsync(web_contents, kMakeCredentialUvDiscouraged);
  delegate_observer()->WaitForUI();

  EXPECT_EQ(dialog_model()->step(),
            AuthenticatorRequestDialogModel::Step::kGPMCreatePasskey);
  EXPECT_EQ(request_delegate()
                ->enclave_controller_for_testing()
                ->account_state_for_testing(),
            GPMEnclaveController::AccountState::kEmpty);
  dialog_model()->OnGPMCreatePasskey();
  EXPECT_EQ(dialog_model()->step(),
            AuthenticatorRequestDialogModel::Step::kGPMCreatePin);
  dialog_model()->OnGPMPinEntered(u"123456");

  std::string script_result;
  ASSERT_TRUE(message_queue.WaitForMessage(&script_result));
  EXPECT_EQ(script_result, "\"webauthn: OK\"");

  delegate_observer()->WaitForDelegateDestruction();

  // 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());

  // Make sure it does not match the existing key.
  EXPECT_NE(trusted_vault::ProtoStringToBytes(icloud_member->public_key()),
            existing_icloud_key->key()->public_key().ExportToBytes());

  // Instead, a new key should have been created.
  base::test::TestFuture<
      std::vector<std::unique_ptr<device::enclave::ICloudRecoveryKey>>>
      list_future;
  device::enclave::ICloudRecoveryKey::Retrieve(
      list_future.GetCallback(), kICloudKeychainRecoveryKeyAccessGroup);
  EXPECT_TRUE(list_future.Wait());
  std::vector<std::unique_ptr<device::enclave::ICloudRecoveryKey>>
      recovery_keys = list_future.Take();
  EXPECT_EQ(recovery_keys.size(), 2u);
}

// Tests enrolling an iCloud recovery key, then recovering from it.
IN_PROC_BROWSER_TEST_F(EnclaveICloudRecoveryKeyTest, Recovery) {
  {
    // Do a make credential request and enroll a PIN.
    trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
        registration_state_result;
    registration_state_result.state = trusted_vault::
        DownloadAuthenticationFactorsRegistrationStateResult::State::kEmpty;
    SetMockVaultConnectionOnRequestDelegate(
        std::move(registration_state_result));

    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::DOMMessageQueue message_queue(web_contents);
    content::ExecuteScriptAsync(web_contents, kMakeCredentialUvDiscouraged);
    delegate_observer()->WaitForUI();

    EXPECT_EQ(dialog_model()->step(),
              AuthenticatorRequestDialogModel::Step::kGPMCreatePasskey);
    EXPECT_EQ(request_delegate()
                  ->enclave_controller_for_testing()
                  ->account_state_for_testing(),
              GPMEnclaveController::AccountState::kEmpty);
    dialog_model()->OnGPMCreatePasskey();
    EXPECT_EQ(dialog_model()->step(),
              AuthenticatorRequestDialogModel::Step::kGPMCreatePin);
    dialog_model()->OnGPMPinEntered(u"123456");

    std::string script_result;
    ASSERT_TRUE(message_queue.WaitForMessage(&script_result));
    EXPECT_EQ(script_result, "\"webauthn: OK\"");

    delegate_observer()->WaitForDelegateDestruction();

    // Make sure a new recovery key was enrolled.
    base::test::TestFuture<
        std::vector<std::unique_ptr<device::enclave::ICloudRecoveryKey>>>
        future;
    device::enclave::ICloudRecoveryKey::Retrieve(
        future.GetCallback(), kICloudKeychainRecoveryKeyAccessGroup);
    EXPECT_TRUE(future.Wait());
    ASSERT_EQ(future.Get().size(), 1u);
  }

  // Unenroll the current device from the enclave.
  EnclaveManagerFactory::GetAsEnclaveManagerForProfile(browser()->profile())
      ->ClearRegistrationForTesting();
  EnclaveManagerFactory::GetAsEnclaveManagerForProfile(browser()->profile())
      ->ResetForTesting();
  // Expire any cache.
  clock_.Advance(base::Hours(10));

  // Do a make credential request and recover with the iCloud key.
  {
    // Set up the mock trusted vault connection to download the iCloud recovery
    // factor that should have been added.
    trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult
        registration_state_result;
    registration_state_result.gpm_pin_metadata = trusted_vault::GpmPinMetadata(
        "public key",
        EnclaveManager::MakeWrappedPINForTesting(kSecurityDomainSecret,
                                                 "123456"),
        /*expiry=*/base::Time::Now() + base::Seconds(10000));
    registration_state_result.state =
        trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult::
            State::kRecoverable;
    registration_state_result.key_version = kSecretVersion;
    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());
    std::vector<trusted_vault::MemberKeys> member_keys;
    auto member_key = icloud_member->memberships().at(0).keys().at(0);
    member_keys.emplace_back(
        member_key.epoch(),
        std::vector<uint8_t>(member_key.wrapped_key().begin(),
                             member_key.wrapped_key().end()),
        std::vector<uint8_t>(member_key.member_proof().begin(),
                             member_key.member_proof().end()));
    registration_state_result.icloud_keys.emplace_back(
        trusted_vault::SecureBoxPublicKey::CreateByImport(
            std::vector<uint8_t>(icloud_member->public_key().begin(),
                                 icloud_member->public_key().end())),
        std::move(member_keys));
    SetMockVaultConnectionOnRequestDelegate(
        std::move(registration_state_result));

    // Running the request should result in recovering automatically after the
    // "Trust this computer" screen.
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::DOMMessageQueue message_queue(web_contents);
    content::ExecuteScriptAsync(web_contents, kMakeCredentialUvDiscouraged);
    delegate_observer()->WaitForUI();

    EXPECT_EQ(
        dialog_model()->step(),
        AuthenticatorRequestDialogModel::Step::kTrustThisComputerCreation);
    EXPECT_EQ(request_delegate()
                  ->enclave_controller_for_testing()
                  ->account_state_for_testing(),
              GPMEnclaveController::AccountState::kRecoverable);
    dialog_model()->OnTrustThisComputer();

    std::string script_result;
    ASSERT_TRUE(message_queue.WaitForMessage(&script_result));
    EXPECT_EQ(script_result, "\"webauthn: OK\"");

    delegate_observer()->WaitForDelegateDestruction();

    // Make sure no new recovery key was enrolled.
    base::test::TestFuture<
        std::vector<std::unique_ptr<device::enclave::ICloudRecoveryKey>>>
        future;
    device::enclave::ICloudRecoveryKey::Retrieve(
        future.GetCallback(), kICloudKeychainRecoveryKeyAccessGroup);
    EXPECT_TRUE(future.Wait());
    ASSERT_EQ(future.Get().size(), 1u);
  }
}

#endif  // BUILDFLAG(IS_MAC)

class EnclaveAuthenticatorCachingTest
    : public EnclaveAuthenticatorWithoutPinBrowserTest {};

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorCachingTest, Caching) {}

#if BUILDFLAG(IS_MAC)
#define MAYBE_MakeCredentialDeclineGPM
#else
#define MAYBE_MakeCredentialDeclineGPM
#endif
// TODO(crbug.com/345308672): Failing on various Mac bots.
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       MAYBE_MakeCredentialDeclineGPM) {}

// Attempt a GetAssertion multiple times with GPM passkey bootstrapping
// offered, and decline each time. The default should change to hybrid after
// two times declined.
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest,
                       MultipleDeclinedBootstrappings) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       ChangedPINDetectedWhenDoingUV) {}

#if BUILDFLAG(IS_LINUX)
// These tests are run on Linux because Linux has no platform authenticator
// that can effect whether IsUVPAA returns true or not.

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithoutPinBrowserTest, IsUVPAA) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest, IsUVPAA) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       IsUVPAA_GoogleSite) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       IsUVPAA_NoUnexportableKeys) {}

#endif  // IS_LINUX

// Verify that GPM will do UV on a uv=preferred request if and only if
// biometrics are available.
IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       UserVerificationPolicy) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest, Bug_354083161) {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       NoSilentOperations) {}

// Allows a `BlockingUnexportableKeyProvider` to block inside a thread-pool
// thread so that the main test can synchronize with it on the UI thread.
class BlockingUnexportableKeyProviderRendezvous {};

BlockingUnexportableKeyProviderRendezvous&
GetBlockingUnexportableKeyProviderRendezvous() {}

// An `UnexportableKeyProvider` that blocks inside `SelectAlgorithm` and waits
// for the UI thread to synchronize with it. It doesn't implement any other
// functions.
class BlockingUnexportableKeyProvider : public crypto::UnexportableKeyProvider {};

std::unique_ptr<crypto::UnexportableKeyProvider>
BlockingUnexportableKeyProviderFactory() {}

IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorWithPinBrowserTest,
                       CancelRacesTPMCheck) {}

}  // namespace

#endif  // !defined(MEMORY_SANITIZER)