// 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 <variant>
#include <vector>

#include "base/check.h"
#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.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/notimplemented.h"
#include "base/notreached.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/task/current_thread.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.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/enclave_keys_waiter.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 "chrome/browser/webauthn/webauthn_metrics_util.h"
#include "components/cbor/reader.h"
#include "components/cbor/writer.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/sync/protocol/webauthn_credential_specifics.pb.h"
#include "components/trusted_vault/command_line_switches.h"
#include "components/trusted_vault/proto/vault.pb.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "crypto/aead.h"
#include "crypto/hash.h"
#include "crypto/hmac.h"
#include "crypto/kdf.h"
#include "crypto/scoped_fake_unexportable_key_provider.h"
#include "crypto/scoped_fake_user_verifying_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/json_request.h"
#include "device/fido/public/features.h"
#include "device/fido/public/fido_constants.h"
#include "device/fido/public/fido_types.h"
#include "device/fido/public/public_key_credential_descriptor.h"
#include "device/fido/public/public_key_credential_params.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/google_service_auth_error.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 "components/trusted_vault/icloud_recovery_key_mac.h"
#include "crypto/apple/scoped_fake_keychain_v2.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/40240570).
#if !defined(MEMORY_SANITIZER)

namespace enclave = device::enclave;
using NoArgFuture = base::test::TestFuture<void>;
using BoolFuture = base::test::TestFuture<bool>;

namespace {

constexpr std::array<uint8_t, 32> kTestKey = {
    0xc4, 0xdf, 0xa4, 0xed, 0xfc, 0xf9, 0x7c, 0xc0, 0x3a, 0xb1, 0xcb,
    0x3c, 0x03, 0x02, 0x9b, 0x5a, 0x05, 0xec, 0x88, 0x48, 0x54, 0x42,
    0xf1, 0x20, 0xb4, 0x75, 0x01, 0xde, 0x61, 0xf1, 0x39, 0x5d,
};
constexpr uint8_t kTestProtobuf[] = {
    0x0a, 0x10, 0x71, 0xfd, 0xf9, 0x65, 0xa8, 0x7c, 0x61, 0xe2, 0xff, 0x27,
    0x0c, 0x76, 0x25, 0x23, 0xe0, 0xa4, 0x12, 0x10, 0x77, 0xf2, 0x3c, 0x31,
    0x3c, 0xe8, 0x94, 0x9a, 0x9f, 0xbc, 0xdf, 0x44, 0xfc, 0xf5, 0x41, 0x97,
    0x1a, 0x0b, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x69,
    0x6f, 0x22, 0x06, 0x56, 0x47, 0x56, 0x7a, 0x64, 0x41, 0x2a, 0x10, 0x60,
    0x07, 0x19, 0x5b, 0x4e, 0x19, 0xf9, 0x6e, 0xc1, 0xfc, 0xfd, 0x0a, 0xf6,
    0x0c, 0x00, 0x7e, 0x30, 0xf9, 0xa0, 0xea, 0xf3, 0xc8, 0x31, 0x3a, 0x04,
    0x54, 0x65, 0x73, 0x74, 0x42, 0x04, 0x54, 0x65, 0x73, 0x74, 0x4a, 0xa6,
    0x01, 0xdc, 0xc5, 0x16, 0x15, 0x91, 0x24, 0xd2, 0x31, 0xfc, 0x85, 0x8b,
    0xe2, 0xec, 0x22, 0x09, 0x8f, 0x8d, 0x0f, 0xbe, 0x9b, 0x59, 0x71, 0x04,
    0xcd, 0xaa, 0x3d, 0x32, 0x23, 0xbd, 0x25, 0x46, 0x14, 0x86, 0x9c, 0xfe,
    0x74, 0xc8, 0xd3, 0x37, 0x70, 0xed, 0xb0, 0x25, 0xd4, 0x1b, 0xdd, 0xa4,
    0x3c, 0x02, 0x13, 0x8c, 0x69, 0x03, 0xff, 0xd1, 0xb0, 0x72, 0x00, 0x29,
    0xcf, 0x5f, 0x06, 0xb3, 0x94, 0xe2, 0xea, 0xca, 0x68, 0xdd, 0x0b, 0x07,
    0x98, 0x7a, 0x2c, 0x8f, 0x08, 0xee, 0x7d, 0xad, 0x16, 0x35, 0xc7, 0x10,
    0xf3, 0xa4, 0x90, 0x84, 0xd1, 0x8e, 0x2e, 0xdb, 0xb9, 0xfa, 0x72, 0x9a,
    0xcf, 0x12, 0x1b, 0x3c, 0xca, 0xfa, 0x79, 0x4a, 0x1e, 0x1b, 0xe1, 0x15,
    0xdf, 0xab, 0xee, 0x75, 0xbb, 0x5c, 0x5a, 0x94, 0x14, 0xeb, 0x72, 0xae,
    0x37, 0x97, 0x03, 0xa8, 0xe7, 0x62, 0x9d, 0x2e, 0xfd, 0x28, 0xce, 0x03,
    0x34, 0x20, 0xa7, 0xa2, 0x7b, 0x00, 0xc8, 0x12, 0x62, 0x12, 0x7f, 0x54,
    0x73, 0x8c, 0x21, 0xc8, 0x85, 0x15, 0xce, 0x36, 0x14, 0xd9, 0x41, 0x22,
    0xe8, 0xbf, 0x88, 0xf9, 0x45, 0xe4, 0x1c, 0x89, 0x7d, 0xa4, 0x23, 0x58,
    0x00, 0x68, 0x98, 0xf5, 0x81, 0xef, 0xad, 0xf4, 0xda, 0x17, 0x70, 0xab,
    0x03,
};
constexpr std::string_view kTestPINPublicKey =
    "\x04\xe4\x72\x4c\x87\xf9\x42\xbe\x2a\xd1\xe6\xac\xa3\x52\x85\xea\x08\xf7"
    "\xe9\x6d\xea\xf2\xf0\x7f\xa9\xde\x89\xe2\x9e\x69\x36\xc4\x4c\xf9\x56\xe9"
    "\xa1\x1f\x08\xfe\x55\xca\x1b\x84\xb9\xe5\x1e\xc3\x26\x69\x16\xa0\x6b\x03"
    "\xfa\x42\x08\xa8\xaf\x7d\xd9\x14\xb4\xfc\x1a";

std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> GetTestEntity() {
  auto ret = std::make_unique<sync_pb::WebauthnCredentialSpecifics>();
  CHECK(ret->ParseFromArray(kTestProtobuf, sizeof(kTestProtobuf)));
  return ret;
}

std::string StringOfZeros(size_t len) {
  return std::string(len, '0');
}

enclave::SigningCallback AlwaysFailsSigningCallback() {
  return base::BindOnce(
      [](enclave::SignedMessage,
         base::OnceCallback<void(std::optional<enclave::ClientSignature>)>
             callback) { std::move(callback).Run(std::nullopt); });
}

webauthn_pb::EnclaveLocalState::WrappedPIN GetTestWrappedPIN() {
  webauthn_pb::EnclaveLocalState::WrappedPIN wrapped_pin;
  wrapped_pin.set_wrapped_pin(StringOfZeros(30));
  wrapped_pin.set_claim_key(StringOfZeros(32));
  wrapped_pin.set_form(wrapped_pin.FORM_SIX_DIGITS);
  wrapped_pin.set_hash(wrapped_pin.HASH_SCRYPT);
  wrapped_pin.set_hash_difficulty(1 << 12);
  wrapped_pin.set_hash_salt(StringOfZeros(16));

  return wrapped_pin;
}

struct TempDir {
 public:
  TempDir() { CHECK(dir_.CreateUniqueTempDir()); }

  base::FilePath GetPath() const { return dir_.GetPath(); }

 private:
  base::ScopedTempDir dir_;
};

std::unique_ptr<network::NetworkService> CreateNetwork(
    mojo::Remote<network::mojom::NetworkContext>* network_context) {
  network::mojom::NetworkContextParamsPtr params =
      network::mojom::NetworkContextParams::New();
  params->cert_verifier_params =
      network::FakeTestCertVerifierParamsFactory::GetCertVerifierParams();

  auto service = network::NetworkService::CreateForTesting();
  service->CreateNetworkContext(network_context->BindNewPipeAndPassReceiver(),
                                std::move(params));

  return service;
}

scoped_refptr<device::JSONRequest> JSONFromString(std::string_view json_str) {
  base::Value json_request =
      base::JSONReader::Read(json_str, base::JSON_PARSE_CHROMIUM_EXTENSIONS)
          .value();
  return base::MakeRefCounted<device::JSONRequest>(std::move(json_request));
}

std::vector<uint8_t> DecryptWrappedPin(
    base::span<const uint8_t> security_domain_secret,
    base::span<const uint8_t> wrapped_pin) {
  base::span<const uint8_t> nonce = wrapped_pin.first(12u);
  base::span<const uint8_t> encrypted_pin = wrapped_pin.subspan(12u);
  // This is "KeychainApplicationKey:chrome:GPM PIN data wrapping key".
  static constexpr uint8_t kKeyPurposePinDataKey[] = {
      0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70,
      0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79,
      0x3a, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x3a, 0x47, 0x50, 0x4d,
      0x20, 0x50, 0x49, 0x4e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x77,
      0x72, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79};
  const std::array<uint8_t, 32> derived_key = crypto::kdf::Hkdf<32>(
      crypto::hash::kSha256, security_domain_secret,
      /*salt=*/base::span<const uint8_t>(), kKeyPurposePinDataKey);
  crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM, derived_key);
  std::optional<std::vector<uint8_t>> pin = aead.Open(
      encrypted_pin, nonce, /*additional_data=*/base::span<const uint8_t>());
  CHECK(pin.has_value());
  return *pin;
}

}  // namespace

class EnclaveManagerTest : public testing::Test, EnclaveManager::Observer {
 public:
  EnclaveManagerTest()
      : EnclaveManagerTest(
            base::test::TaskEnvironment::TimeSource::SYSTEM_TIME) {}

  explicit EnclaveManagerTest(
      base::test::TaskEnvironment::TimeSource time_source)
      // `IdentityTestEnvironment` wants to run on an IO thread.
      : task_env_(base::test::TaskEnvironment::MainThreadType::IO, time_source),
        temp_dir_(),
        process_and_port_(StartWebAuthnEnclave(temp_dir_.GetPath())),
        enclave_override_(
            TestWebAuthnEnclaveIdentity(process_and_port_.second)),
        network_service_(CreateNetwork(&network_context_)),
        security_domain_service_(
            FakeSecurityDomainService::New(kSecretVersion)),
        recovery_key_store_(FakeRecoveryKeyStore::New()),
        manager_(temp_dir_.GetPath(),
                 identity_test_env_.identity_manager(),
                 base::BindLambdaForTesting(
                     [&]() -> network::mojom::NetworkContext* {
                       return network_context_.get();
                     }),
                 url_loader_factory_.GetSafeWeakWrapper()) {
    identity_test_env_.MakePrimaryAccountAvailable(
        "test@gmail.com", signin::ConsentLevel::kSignin);
    gaia_id_ = identity_test_env_.identity_manager()
                   ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
                   .gaia;
    identity_test_env_.SetAutomaticIssueOfAccessTokens(true);
    manager_.AddObserver(this);

    auto security_domain_service_callback =
        security_domain_service_->GetCallback();
    auto recovery_key_store_callback = recovery_key_store_->GetCallback();
    url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
        [sds_callback = std::move(security_domain_service_callback),
         rks_callback = std::move(recovery_key_store_callback),
         this](const network::ResourceRequest& request) {
          std::optional<std::pair<net::HttpStatusCode, std::string>> response =
              sds_callback.Run(request);
          if (!response) {
            response = rks_callback.Run(request);
          }
          if (response) {
            url_loader_factory_.AddResponse(request.url.spec(),
                                            std::move(response->second),
                                            response->first);
          }
        }));
    fake_hw_provider_ =
        std::make_unique<crypto::ScopedFakeUnexportableKeyProvider>();
  }

  ~EnclaveManagerTest() override {
    if (manager_.RunWhenStoppedForTesting(task_env_.QuitClosure())) {
      task_env_.RunUntilQuit();
    }
    CHECK(process_and_port_.first.Terminate(/*exit_code=*/1, /*wait=*/true));
  }

 protected:
  base::flat_set<std::string> GaiaAccountsInState() {
    const webauthn_pb::EnclaveLocalState& state =
        manager_.local_state_for_testing();

    return base::MakeFlatSet<std::string>(
        state.users(), /*comp=*/{}, [](const auto& it) { return it.first; });
  }

  void OnKeysStored(const GaiaId& gaia_id) override {
    stored_count_++;
    last_stored_gaia_id_ = gaia_id;
  }
  void OnStateUpdated() override { notified_about_state_update_count_++; }
  void OnOutOfContextRecoveryCompletion(
      EnclaveManager::OutOfContextRecoveryOutcome outcome) override {}

  void DoCreate(
      std::unique_ptr<enclave::ClaimedPIN> claimed_pin,
      std::unique_ptr<sync_pb::WebauthnCredentialSpecifics>* out_specifics) {
    auto ui_request = std::make_unique<enclave::CredentialRequest>();
    ui_request->signing_callback = manager_.IdentityKeySigningCallback();
    int32_t secret_version;
    std::vector<uint8_t> wrapped_secret;
    std::tie(secret_version, wrapped_secret) =
        manager_.GetCurrentWrappedSecret();
    EXPECT_EQ(secret_version, kSecretVersion);
    ui_request->wrapped_secret = std::move(wrapped_secret);
    ui_request->key_version = kSecretVersion;
    ui_request->claimed_pin = std::move(claimed_pin);

    std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> specifics;
    ui_request->save_passkey_callback = base::BindLambdaForTesting(
        [&specifics](sync_pb::WebauthnCredentialSpecifics in_specifics) {
          specifics = std::make_unique<sync_pb::WebauthnCredentialSpecifics>(
              std::move(in_specifics));
        });

    enclave::EnclaveAuthenticator authenticator(
        std::move(ui_request), /*network_context_factory=*/
        base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
          return network_context_.get();
        }));

    std::vector<device::PublicKeyCredentialParams::CredentialInfo>
        pub_key_params;
    pub_key_params.emplace_back();

    device::MakeCredentialOptions ctap_options;
    ctap_options.json = JSONFromString(R"({
        "attestation": "none",
        "authenticatorSelection": {
          "residentKey": "preferred",
          "userVerification": "preferred"
        },
        "challenge": "xHyLYEorFsaL6vb",
        "extensions": { "credProps": true },
        "pubKeyCredParams": [
          { "alg": -7, "type": "public-key" },
          { "alg": -257, "type": "public-key" }
        ],
        "rp": {
          "id": "webauthn.io",
          "name": "webauthn.io"
        },
        "user": {
          "displayName": "test",
          "id": "ZEdWemRB",
          "name": "test"
        }
      })");

    auto quit_closure = task_env_.QuitClosure();
    std::optional<device::MakeCredentialStatus> status;
    std::optional<device::AuthenticatorMakeCredentialResponse> response;
    authenticator.MakeCredential(
        /*request=*/{R"({"foo": "bar"})",
                     /*rp=*/{"rpid", "rpname"},
                     /*user=*/{{'u', 'i', 'd'}, "user", "display name"},
                     device::PublicKeyCredentialParams(
                         std::move(pub_key_params))},
        std::move(ctap_options),
        base::BindLambdaForTesting(
            [&quit_closure, &status, &response](
                device::MakeCredentialStatus in_status,
                std::optional<device::AuthenticatorMakeCredentialResponse>
                    in_responses) {
              status = in_status;
              response = std::move(in_responses);
              quit_closure.Run();
            }));
    task_env_.RunUntilQuit();

    ASSERT_TRUE(status.has_value());
    ASSERT_EQ(status, device::MakeCredentialStatus::kSuccess);
    ASSERT_TRUE(response.has_value());
    ASSERT_TRUE(specifics);
    EXPECT_EQ(specifics->rp_id(), "rpid");
    EXPECT_EQ(specifics->user_id(), "uid");
    EXPECT_EQ(specifics->user_name(), "user");
    EXPECT_EQ(specifics->user_display_name(), "display name");
    EXPECT_EQ(specifics->key_version(), kSecretVersion);

    if (out_specifics) {
      *out_specifics = std::move(specifics);
    }
  }

  struct GetAssertionResponseExpectation {
    device::GetAssertionStatus result = device::GetAssertionStatus::kSuccess;
    uint32_t size = 1;
  };

  std::optional<base::Time> LastPINRenewalTime() {
    std::optional<base::Time> ret;
    webauthn_pb::EnclaveLocalState& state = manager_.local_state_for_testing();
    if (state.users().size() == 0) {
      return ret;
    }
    CHECK_EQ(state.users().size(), 1u);
    if (!state.users().begin()->second.has_last_refreshed_pin_epoch_secs()) {
      return ret;
    }
    return base::Time::FromSecondsSinceUnixEpoch(
        state.users().begin()->second.last_refreshed_pin_epoch_secs());
  }

  void DoAssertion(
      std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity,
      std::unique_ptr<enclave::ClaimedPIN> claimed_pin,
      GetAssertionResponseExpectation expected_response,
      std::unique_ptr<enclave::CredentialRequest> custom_ui_request = nullptr) {
    std::unique_ptr<enclave::CredentialRequest> ui_request;
    if (custom_ui_request) {
      ui_request = std::move(custom_ui_request);
    } else {
      ui_request = std::make_unique<enclave::CredentialRequest>();
      ui_request->signing_callback = manager_.IdentityKeySigningCallback();
      ui_request->wrapped_secret =
          *manager_.GetWrappedSecret(/*version=*/kSecretVersion);
      ui_request->entity = std::move(entity);
      ui_request->claimed_pin = std::move(claimed_pin);
      ui_request->save_passkey_callback = base::BindOnce(
          [](sync_pb::WebauthnCredentialSpecifics) { NOTREACHED(); });
    }

    enclave::EnclaveAuthenticator authenticator(
        std::move(ui_request), /*network_context_factory=*/
        base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
          return network_context_.get();
        }));

    device::CtapGetAssertionRequest ctap_request("test.com",
                                                 R"({"foo": "bar"})");
    ctap_request.allow_list.emplace_back(device::PublicKeyCredentialDescriptor(
        device::CredentialType::kPublicKey, /*id=*/{1, 2, 3, 4}));

    device::CtapGetAssertionOptions ctap_options;
    ctap_options.json = JSONFromString(R"({
        "allowCredentials": [ ],
        "challenge": "CYO8B30gOPIOVFAaU61J7PvoETG_sCZQ38Gzpu",
        "rpId": "webauthn.io",
        "userVerification": "preferred"
    })");

    auto quit_closure = task_env_.QuitClosure();
    std::optional<device::GetAssertionStatus> status;
    std::vector<device::AuthenticatorGetAssertionResponse> responses;
    authenticator.GetAssertion(
        std::move(ctap_request), std::move(ctap_options),
        base::BindLambdaForTesting(
            [&quit_closure, &status, &responses](
                device::GetAssertionStatus in_status,
                std::vector<device::AuthenticatorGetAssertionResponse>
                    in_responses) {
              status = in_status;
              responses = std::move(in_responses);
              quit_closure.Run();
            }));
    task_env_.RunUntilQuit();

    ASSERT_TRUE(status.has_value());
    ASSERT_TRUE(true);
    ASSERT_EQ(status, expected_response.result);
    ASSERT_EQ(responses.size(), expected_response.size);
  }

  bool Register() {
    BoolFuture register_future;
    manager_.RegisterIfNeeded(register_future.GetCallback());
    EXPECT_TRUE(register_future.Wait());
    return register_future.Get();
  }

  void CorruptDeviceId() {
    webauthn_pb::EnclaveLocalState& state = manager_.local_state_for_testing();
    ASSERT_EQ(state.users().size(), 1u);
    state.mutable_users()->begin()->second.set_device_id("corrupted value");
  }

  void AcquireLockAndStoreKey(EnclaveManager* manager,
                              std::vector<uint8_t> key,
                              int last_key_version) {
    auto store_keys_lock = manager->GetStoreKeysLock();
    base::HistogramTester histogram_tester;
    manager->StoreKeys(gaia_id_,
                       {trusted_vault::TrustedVaultKeyAndVersion(
                           std::move(key), last_key_version)},
                       std::nullopt);
    if (manager->IsStoringKeysFromOutOfContextRetrievalEnabled()) {
      histogram_tester.ExpectBucketCount(
          "WebAuthentication.GPM.RecoveryEvent",
          webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
              kStoreKeysFromExplicitFlowStarted,
          1);
    }
  }

  base::test::TaskEnvironment task_env_;
  unsigned stored_count_ = 0;
  GaiaId last_stored_gaia_id_;
  unsigned notified_about_state_update_count_ = 0;
  const TempDir temp_dir_;
  const std::pair<base::Process, uint16_t> process_and_port_;
  const enclave::ScopedEnclaveOverride enclave_override_;
  network::TestURLLoaderFactory url_loader_factory_;
  mojo::Remote<network::mojom::NetworkContext> network_context_;
  std::unique_ptr<network::NetworkService> network_service_;
  signin::IdentityTestEnvironment identity_test_env_;
  GaiaId gaia_id_;
  std::unique_ptr<FakeSecurityDomainService> security_domain_service_;
  std::unique_ptr<FakeRecoveryKeyStore> recovery_key_store_;
  std::unique_ptr<crypto::ScopedFakeUnexportableKeyProvider> fake_hw_provider_;
  EnclaveManager manager_;
};

TEST_F(EnclaveManagerTest, TestInfrastructure) {
  // Tests that the enclave starts up.
}

TEST_F(EnclaveManagerTest, Basic) {
  security_domain_service_->pretend_there_are_members();

  ASSERT_FALSE(manager_.IsLoaded());
  ASSERT_FALSE(manager_.IsRegistered());
  ASSERT_FALSE(manager_.IsReady());

  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.IsLoaded());
  ASSERT_FALSE(manager_.IsRegistered());
  ASSERT_FALSE(manager_.IsReady());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());
  ASSERT_TRUE(register_future.Get());
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.IsLoaded());
  ASSERT_TRUE(manager_.IsRegistered());
  ASSERT_FALSE(manager_.IsReady());
  EXPECT_TRUE(manager_.local_state_for_testing()
                  .users()
                  .find(gaia_id_.ToString())
                  ->second.identity_key_is_software_backed());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  ASSERT_FALSE(manager_.has_pending_keys());
  AcquireLockAndStoreKey(&manager_, {std::move(key)}, kSecretVersion);
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.has_pending_keys());
  EXPECT_EQ(stored_count_, 1u);
  EXPECT_EQ(last_stored_gaia_id_, gaia_id_);

  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(add_future.Get());

  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.IsLoaded());
  ASSERT_TRUE(manager_.IsRegistered());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_FALSE(manager_.has_pending_keys());
  ASSERT_TRUE(manager_.TakeSecret());
  ASSERT_FALSE(manager_.TakeSecret());
  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 0u);

  base::HistogramTester histogram_tester;
  DoCreate(/*claimed_pin=*/nullptr, /*out_specifics=*/nullptr);
  DoAssertion(GetTestEntity(), /*claimed_pin=*/nullptr,
              GetAssertionResponseExpectation());
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.EnclaveTransaction.Result",
      device::enclave::EnclaveTransactionResult::kSuccess, 2);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.EnclaveTransaction.PasskeyCreate.Result",
      device::enclave::EnclaveTransactionResult::kSuccess, 1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.EnclaveTransaction.PasskeyAssert.Result",
      device::enclave::EnclaveTransactionResult::kSuccess, 1);

  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.Latency", 2);
  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.PasskeyCreate.Latency", 1);
  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.PasskeyAssert.Latency", 1);

  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.RequestSize", 2);
  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.PasskeyCreate.RequestSize", 1);
  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.PasskeyAssert.RequestSize", 1);

  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.ResponseSize", 2);
  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.PasskeyCreate.ResponseSize", 1);
  histogram_tester.ExpectTotalCount(
      "WebAuthentication.EnclaveTransaction.PasskeyAssert.ResponseSize", 1);
}

TEST_F(EnclaveManagerTest,
       NotifiedAboutStateUpdateAfterStoringKeyAndAddingDeviceToAccount) {
  security_domain_service_->pretend_there_are_members();
  ASSERT_EQ(notified_about_state_update_count_, 0u);
  ASSERT_FALSE(manager_.IsReady());

  // Storing keys and adding device to account is supposed to notify observers
  // about enclave state update:
  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {std::move(key)}, kSecretVersion);
  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  EXPECT_TRUE(add_future.Wait());

  // `notified_about_state_update_count_` is being incremented whenever
  // `OnStateUpdated()` is called:
  EXPECT_EQ(notified_about_state_update_count_, 1u);
  ASSERT_TRUE(manager_.IsReady());
}

TEST_F(EnclaveManagerTest, SecretsArriveBeforeRegistrationRequested) {
  security_domain_service_->pretend_there_are_members();
  ASSERT_FALSE(manager_.IsRegistered());

  // If secrets are provided before `RegisterIfNeeded` is called, the state
  // machine should still trigger registration.
  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {std::move(key)},
                         /*last_key_version=*/417);
  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  EXPECT_TRUE(add_future.Wait());

  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.IsLoaded());
  ASSERT_TRUE(manager_.IsRegistered());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.TakeSecret());
}

TEST_F(EnclaveManagerTest, SecretsArriveBeforeRegistrationCompleted) {
  security_domain_service_->pretend_there_are_members();
  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  ASSERT_FALSE(manager_.IsRegistered());

  // Provide the domain secrets before the registration has completed. The
  // system should still end up in the correct state.
  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {std::move(key)},
                         /*last_key_version=*/417);
  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      /*pin_metadata=*/std::nullopt, add_future.GetCallback()));
  EXPECT_TRUE(add_future.Wait());
  EXPECT_TRUE(register_future.Wait());

  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.IsLoaded());
  ASSERT_TRUE(manager_.IsRegistered());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.TakeSecret());
}

TEST_F(EnclaveManagerTest, RegistrationFailureAndRetry) {
  const std::string gaia =
      identity_test_env_.identity_manager()
          ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
          .gaia.ToString();

  // Override the enclave with port=100, which will cause connection failures.
  {
    device::enclave::ScopedEnclaveOverride override(
        TestWebAuthnEnclaveIdentity(/*port=*/100));
    BoolFuture register_future;
    manager_.RegisterIfNeeded(register_future.GetCallback());
    EXPECT_TRUE(register_future.Wait());
    ASSERT_FALSE(register_future.Get());
  }
  ASSERT_FALSE(manager_.IsRegistered());
  const std::string public_key = manager_.local_state_for_testing()
                                     .users()
                                     .find(gaia)
                                     ->second.identity_public_key();
  ASSERT_FALSE(public_key.empty());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  EXPECT_TRUE(register_future.Wait());
  ASSERT_TRUE(manager_.IsRegistered());
  ASSERT_TRUE(register_future.Get());

  // The public key should not have changed because re-registration attempts
  // must try the same public key again in case they actually worked the first
  // time.
  ASSERT_TRUE(public_key == manager_.local_state_for_testing()
                                .users()
                                .find(gaia)
                                ->second.identity_public_key());
}

TEST_F(EnclaveManagerTest, GetAccessTokenErrorMetric_Success) {
  base::HistogramTester histogram_tester;
  ASSERT_TRUE(Register());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.GetAccessTokenError",
      GoogleServiceAuthError::State::NONE, 1);
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.RegisterIfNeeded",
      EnclaveManager::ActionOutcome::kSuccess, 1);

  // Calling RegisterIfNeeded when already registered should also record
  // kSuccess.
  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());
  EXPECT_TRUE(register_future.Wait());
  EXPECT_TRUE(register_future.Get());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.RegisterIfNeeded",
      EnclaveManager::ActionOutcome::kSuccess, 2);
}

TEST_F(EnclaveManagerTest, GetAccessTokenErrorMetric_Failure) {
  base::HistogramTester histogram_tester;
  identity_test_env_.SetAutomaticIssueOfAccessTokens(false);

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());

  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
          GoogleServiceAuthError::InvalidGaiaCredentialsReason::UNKNOWN));

  EXPECT_TRUE(register_future.Wait());
  EXPECT_FALSE(register_future.Get());

  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.GetAccessTokenError",
      GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS, 1);
}

TEST_F(EnclaveManagerTest, PrimaryUserChange) {
  const std::string gaia1 =
      identity_test_env_.identity_manager()
          ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
          .gaia.ToString();

  {
    BoolFuture register_future;
    manager_.RegisterIfNeeded(register_future.GetCallback());
    EXPECT_TRUE(register_future.Wait());
  }
  ASSERT_TRUE(manager_.IsRegistered());
  EXPECT_THAT(GaiaAccountsInState(), testing::UnorderedElementsAre(gaia1));

  identity_test_env_.MakePrimaryAccountAvailable("test2@gmail.com",
                                                 signin::ConsentLevel::kSignin);
  const std::string gaia2 =
      identity_test_env_.identity_manager()
          ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
          .gaia.ToString();
  ASSERT_FALSE(manager_.IsRegistered());
  {
    BoolFuture register_future;
    manager_.RegisterIfNeeded(register_future.GetCallback());
    EXPECT_TRUE(register_future.Wait());
  }
  ASSERT_TRUE(manager_.IsRegistered());
  EXPECT_THAT(GaiaAccountsInState(),
              testing::UnorderedElementsAre(gaia1, gaia2));

  // Remove all accounts from the cookie jar. The primary account should be
  // retained.
  identity_test_env_.SetCookieAccounts({});
  if (base::FeatureList::IsEnabled(
          device::
              kWebAuthnDoNotAlwaysTerminateStateMachineDuringIdentityChange)) {
    // Removal of accounts from cookie jar leads to scheduling enclave manager's
    // state machine operation for updating the local state. So we need to wait
    // for completion of that operation.
    base::test::RunUntil([this]() { return manager_.is_idle(); });
  }
  EXPECT_THAT(GaiaAccountsInState(), testing::UnorderedElementsAre(gaia2));

  // When the primary account changes, the second account should be dropped
  // because it was removed from the cookie jar.
  identity_test_env_.MakePrimaryAccountAvailable("test3@gmail.com",
                                                 signin::ConsentLevel::kSignin);
  const std::string gaia3 =
      identity_test_env_.identity_manager()
          ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
          .gaia.ToString();
  if (base::FeatureList::IsEnabled(
          device::
              kWebAuthnDoNotAlwaysTerminateStateMachineDuringIdentityChange)) {
    // Changing accounts leads to scheduling enclave manager's state machine
    // operation for updating the local state. So we need to wait for completion
    // of that operation.
    base::test::RunUntil([this]() { return manager_.is_idle(); });
  }
  EXPECT_THAT(GaiaAccountsInState(), testing::UnorderedElementsAre(gaia3));
}

TEST_F(EnclaveManagerTest, PrimaryUserChangeDiscardsActions) {
  security_domain_service_->pretend_there_are_members();
  const std::string gaia1 =
      identity_test_env_.identity_manager()
          ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
          .gaia.ToString();

  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  BoolFuture register_future1;
  manager_.RegisterIfNeeded(register_future1.GetCallback());
  BoolFuture register_future2;
  manager_.RegisterIfNeeded(register_future2.GetCallback());

  identity_test_env_.MakePrimaryAccountAvailable("test2@gmail.com",
                                                 signin::ConsentLevel::kSignin);
  // `MakePrimaryAccountAvailable` should have canceled any actions.
  ASSERT_TRUE(manager_.is_idle());
  ASSERT_FALSE(manager_.has_pending_keys());
  ASSERT_FALSE(manager_.IsRegistered());
  ASSERT_FALSE(manager_.IsReady());

  EXPECT_TRUE(register_future1.Wait());
  ASSERT_FALSE(register_future1.Get());
  EXPECT_TRUE(register_future2.Wait());
  ASSERT_FALSE(register_future2.Get());
}

TEST_F(EnclaveManagerTest, CookieJarChangeDoesNotDiscardActions) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      device::kWebAuthnDoNotAlwaysTerminateStateMachineDuringIdentityChange);

  security_domain_service_->pretend_there_are_members();
  const CoreAccountInfo account =
      identity_test_env_.identity_manager()->GetPrimaryAccountInfo(
          signin::ConsentLevel::kSignin);

  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  // Simulating the presence of multiple accounts in the cookie jar.
  identity_test_env_.MakePrimaryAccountAvailable("test2@gmail.com",
                                                 signin::ConsentLevel::kSignin);
  identity_test_env_.MakePrimaryAccountAvailable("test3@gmail.com",
                                                 signin::ConsentLevel::kSignin);
  identity_test_env_.MakePrimaryAccountAvailable(account.email,
                                                 signin::ConsentLevel::kSync);

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());

  // Updating the cookie jar.
  identity_test_env_.SetCookieAccounts({});
  base::test::RunUntil([this]() { return manager_.is_idle(); });
  EXPECT_EQ(manager_.local_state_for_testing().mutable_users()->size(), 1u);

  EXPECT_TRUE(register_future.Wait());
  ASSERT_TRUE(register_future.Get());
}

TEST_F(EnclaveManagerTest,
       PrimaryUserRemainsTheSame_SyncConsentChanges_DoesNotDiscardActions) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      device::kWebAuthnDoNotAlwaysTerminateStateMachineDuringIdentityChange);

  security_domain_service_->pretend_there_are_members();
  const CoreAccountInfo account =
      identity_test_env_.identity_manager()->GetPrimaryAccountInfo(
          signin::ConsentLevel::kSignin);

  NoArgFuture loaded_future;
  manager_.Load(loaded_future.GetCallback());
  EXPECT_TRUE(loaded_future.Wait());

  BoolFuture register_future;
  manager_.RegisterIfNeeded(register_future.GetCallback());

  identity_test_env_.MakePrimaryAccountAvailable(account.email,
                                                 signin::ConsentLevel::kSync);
  // Since the primary account remains the same, the on-going actions will not
  // be cancelled.
  ASSERT_FALSE(manager_.is_idle());
  EXPECT_TRUE(register_future.Wait());
  ASSERT_TRUE(register_future.Get());
}

TEST_F(EnclaveManagerTest, AddWithExistingPIN) {
  security_domain_service_->pretend_there_are_members();

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {std::move(key)},
                         /*last_key_version=*/417);
  base::HistogramTester histogram_tester;
  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      trusted_vault::GpmPinMetadata(std::string(kTestPINPublicKey),
                                    trusted_vault::UsableRecoveryPinMetadata(
                                        GetTestWrappedPIN().SerializeAsString(),
                                        /*expiry=*/base::Time())),
      add_future.GetCallback()));
  EXPECT_TRUE(add_future.Wait());

  ASSERT_TRUE(manager_.is_idle());
  ASSERT_TRUE(manager_.IsLoaded());
  ASSERT_TRUE(manager_.IsRegistered());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.TakeSecret());

  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  // The PIN should not have been added to the account. Instead this test is
  // pretending that it was already there.
  EXPECT_EQ(security_domain_service_->num_pin_members(), 0u);
  EXPECT_TRUE(manager_.has_wrapped_pin());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.AddDeviceToAccount",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(EnclaveManagerTest, InvalidWrappedPIN) {
  base::HistogramTester histogram_tester;
  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {std::move(key)},
                         /*last_key_version=*/417);

  BoolFuture add_future;
  // A wrapped PIN that isn't a valid protobuf should be rejected.
  EXPECT_FALSE(manager_.AddDeviceToAccount(
      trusted_vault::GpmPinMetadata(
          std::string(kTestPINPublicKey),
          trusted_vault::UsableRecoveryPinMetadata("nonsense wrapped PIN",
                                                   /*expiry=*/base::Time())),
      add_future.GetCallback()));
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.AddDeviceToAccount",
      EnclaveManager::ActionOutcome::
          kAddDeviceToAccountNotStartedWrappedPinParsingError,
      1);

  // A valid protobuf, but which fails invariants, should be rejected.
  webauthn_pb::EnclaveLocalState::WrappedPIN wrapped_pin = GetTestWrappedPIN();
  wrapped_pin.set_wrapped_pin("too short");
  EXPECT_FALSE(manager_.AddDeviceToAccount(
      trusted_vault::GpmPinMetadata(std::string(kTestPINPublicKey),
                                    trusted_vault::UsableRecoveryPinMetadata(
                                        wrapped_pin.SerializeAsString(),
                                        /*expiry=*/base::Time())),
      add_future.GetCallback()));
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.AddDeviceToAccount",
      EnclaveManager::ActionOutcome::
          kAddDeviceToAccountNotStartedWrappedPinParsingError,
      2);
}

TEST_F(EnclaveManagerTest, SetupWithPIN) {
  base::HistogramTester histogram_tester;
  const std::string pin = "123456";

  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  EXPECT_FALSE(manager_.wrapped_pin_is_arbitrary());
  EXPECT_TRUE(LastPINRenewalTime().has_value());

  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  const std::optional<std::vector<uint8_t>> security_domain_secret =
      FakeMagicArch::RecoverWithPIN(pin, *security_domain_service_,
                                    *recovery_key_store_);
  CHECK(security_domain_secret.has_value());
  EXPECT_EQ(manager_.TakeSecret()->second, *security_domain_secret);

  // Verify that the wrapped PIN Chrome generated contains the cohort details.
  std::vector<uint8_t> wrapped_pin = DecryptWrappedPin(
      *security_domain_secret,
      base::as_byte_span(manager_.GetWrappedPIN()->wrapped_pin()));
  std::optional<cbor::Value> cbor = cbor::Reader::Read(wrapped_pin);
  const cbor::Value::MapValue& wrapped_pin_cbor = cbor->GetMap();
  int cert_xml_serial_number =
      wrapped_pin_cbor.find(cbor::Value(6))->second.GetInteger();
  EXPECT_EQ(cert_xml_serial_number,
            recovery_key_store_->recovery_key_store_serial_number());
  const std::vector<uint8_t> cohort_public_key =
      wrapped_pin_cbor.find(cbor::Value(7))->second.GetBytestring();
  EXPECT_EQ(cohort_public_key,
            recovery_key_store_->CurrentEndpointPublicKeyBytes());

  // Verify we can use the PIN to create a passkey and assert it.
  std::unique_ptr<device::enclave::ClaimedPIN> claimed_pin =
      EnclaveManager::MakeClaimedPINSlowly(pin, manager_.GetWrappedPIN());
  std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity;
  DoCreate(/*claimed_pin=*/nullptr, &entity);
  DoAssertion(std::move(entity), std::move(claimed_pin),
              GetAssertionResponseExpectation());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.SetupWithPIN",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(EnclaveManagerTest, SetupWithPIN_SecurityDomainFailure) {
  security_domain_service_->fail_all_requests();

  BoolFuture setup_future;
  manager_.SetupWithPIN("123456", setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_FALSE(setup_future.Get());
  ASSERT_FALSE(manager_.IsReady());
}

TEST_F(EnclaveManagerTest, SetupWithPIN_CertXMLFailure) {
  recovery_key_store_->break_cert_xml_file();

  BoolFuture setup_future;
  manager_.SetupWithPIN("123456", setup_future.GetCallback());
  // This test primarily shouldn't crash or hang.
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_FALSE(setup_future.Get());
  ASSERT_FALSE(manager_.IsReady());
}

TEST_F(EnclaveManagerTest, SetupWithPIN_SigXMLFailure) {
  recovery_key_store_->break_sig_xml_file();

  BoolFuture setup_future;
  manager_.SetupWithPIN("123456", setup_future.GetCallback());
  // This test primarily shouldn't crash or hang.
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_FALSE(setup_future.Get());
  ASSERT_FALSE(manager_.IsReady());
}

TEST_F(EnclaveManagerTest, SetupWithPIN_CustomCohortFromFinch) {
  base::test::ScopedFeatureList scoped_feature_list;
  const char kCustomCertXmlUrl[] = "https://valid.example.com/cert.xml";
  const char kCustomSigXmlUrl[] = "https://valid.example.com/sig.xml";
  scoped_feature_list.InitAndEnableFeatureWithParameters(
      device::enclave::kEnclaveTrustedVaultCohort,
      {{"cert_xml", kCustomCertXmlUrl}, {"sig_xml", kCustomSigXmlUrl}});

  recovery_key_store_->set_cert_xml_url(kCustomCertXmlUrl);
  recovery_key_store_->set_sig_xml_url(kCustomSigXmlUrl);

  BoolFuture setup_future;
  manager_.SetupWithPIN("123456", setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  EXPECT_TRUE(setup_future.Get());
  EXPECT_TRUE(manager_.IsReady());
}

TEST_F(EnclaveManagerTest, SetupWithPIN_InvalidSigXmlIsIgnored) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeatureWithParameters(
      device::enclave::kEnclaveTrustedVaultCohort, {{"sig_xml", "invalid"}});

  // EnclaveManager should use the default if the Finch provided URL is invalid.
  recovery_key_store_->set_sig_xml_url(
      device::enclave::kSigXmlUrlFeature.default_value);

  BoolFuture setup_future;
  manager_.SetupWithPIN("123456", setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  EXPECT_TRUE(setup_future.Get());
  EXPECT_TRUE(manager_.IsReady());
}

TEST_F(EnclaveManagerTest, SetupWithPIN_InvalidCertXmlIsIgnored) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeatureWithParameters(
      device::enclave::kEnclaveTrustedVaultCohort, {{"cert_xml", "invalid"}});

  // EnclaveManager should use the default if the Finch provided URL is invalid.
  recovery_key_store_->set_cert_xml_url(
      device::enclave::kCertXmlUrlFeature.default_value);

  BoolFuture setup_future;
  manager_.SetupWithPIN("123456", setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  EXPECT_TRUE(setup_future.Get());
  EXPECT_TRUE(manager_.IsReady());
}

TEST_F(EnclaveManagerTest, AddDeviceAndPINToAccount) {
  security_domain_service_->pretend_there_are_members();
  const std::string pin = "pin";

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

  base::HistogramTester histogram_tester;
  BoolFuture add_future;
  manager_.AddDeviceAndPINToAccount(
      pin, /*previous_pin_public_key=*/std::nullopt, add_future.GetCallback());
  EXPECT_TRUE(add_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  EXPECT_TRUE(manager_.wrapped_pin_is_arbitrary());

  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  const std::optional<std::vector<uint8_t>> security_domain_secret =
      FakeMagicArch::RecoverWithPIN(pin, *security_domain_service_,
                                    *recovery_key_store_);
  CHECK(security_domain_secret.has_value());
  EXPECT_EQ(manager_.TakeSecret()->second, *security_domain_secret);

  // Verify that the wrapped PIN the enclave generated contains the cohort
  // details.
  std::vector<uint8_t> wrapped_pin = DecryptWrappedPin(
      *security_domain_secret,
      base::as_byte_span(manager_.GetWrappedPIN()->wrapped_pin()));
  std::optional<cbor::Value> cbor = cbor::Reader::Read(wrapped_pin);
  const cbor::Value::MapValue& wrapped_pin_cbor = cbor->GetMap();
  int cert_xml_serial_number =
      wrapped_pin_cbor.find(cbor::Value(6))->second.GetInteger();
  EXPECT_EQ(cert_xml_serial_number,
            recovery_key_store_->recovery_key_store_serial_number());
  const std::vector<uint8_t> cohort_public_key =
      wrapped_pin_cbor.find(cbor::Value(7))->second.GetBytestring();
  EXPECT_EQ(cohort_public_key,
            recovery_key_store_->CurrentEndpointPublicKeyBytes());

  std::unique_ptr<device::enclave::ClaimedPIN> claimed_pin =
      EnclaveManager::MakeClaimedPINSlowly(pin, manager_.GetWrappedPIN());
  std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity;
  DoCreate(/*claimed_pin=*/nullptr, &entity);
  DoAssertion(std::move(entity), std::move(claimed_pin),
              GetAssertionResponseExpectation());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.AddDeviceAndPINToAccount",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(EnclaveManagerTest, AddDeviceAndPINToAccountWithPreviouslyInvalidPIN) {
  // First, set up with a PIN.
  security_domain_service_->pretend_there_are_members();
  const std::string pin = "pin";

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {key},
                         /*last_key_version=*/kSecretVersion);

  {
    BoolFuture add_future;
    manager_.AddDeviceAndPINToAccount(pin,
                                      /*previous_pin_public_key=*/std::nullopt,
                                      add_future.GetCallback());
    ASSERT_TRUE(add_future.Wait());
    ASSERT_TRUE(add_future.Get());
  }
  // Then, make the PIN unusable and reset the registration.
  security_domain_service_->MakePinMemberUnusable();
  manager_.ClearRegistrationForTesting();
  AcquireLockAndStoreKey(&manager_, {key},
                         /*last_key_version=*/kSecretVersion);
  {
    // Verify that attempting to register with a PIN succeeds when the public
    // key of the obsolete PIN is set.
    BoolFuture add_future;
    manager_.AddDeviceAndPINToAccount(
        pin, security_domain_service_->GetPinMemberPublicKey(),
        add_future.GetCallback());
    ASSERT_TRUE(add_future.Wait());
    EXPECT_TRUE(add_future.Get());
  }
}

TEST_F(EnclaveManagerTest, ChangePIN) {
  security_domain_service_->pretend_there_are_members();
  const std::string pin = "pin";
  const std::string new_pin = "newpin";

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

  BoolFuture add_future;
  manager_.AddDeviceAndPINToAccount(
      pin, /*previous_pin_public_key=*/std::nullopt, add_future.GetCallback());
  EXPECT_TRUE(add_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  EXPECT_TRUE(manager_.wrapped_pin_is_arbitrary());
  const std::vector<uint8_t> security_domain_secret =
      std::move(manager_.TakeSecret()->second);

  base::HistogramTester histogram_tester;
  BoolFuture change_future;
  manager_.ChangePIN(new_pin, "rapt", change_future.GetCallback());
  EXPECT_TRUE(change_future.Wait());
  ASSERT_TRUE(change_future.Get());

  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  EXPECT_EQ(recovery_key_store_->vaults().size(), 2u);
  const std::optional<std::vector<uint8_t>> recovered_security_domain_secret =
      FakeMagicArch::RecoverWithPIN(new_pin, *security_domain_service_,
                                    *recovery_key_store_);
  CHECK(recovered_security_domain_secret.has_value());
  EXPECT_EQ(*recovered_security_domain_secret, security_domain_secret);

  std::unique_ptr<device::enclave::ClaimedPIN> claimed_pin =
      EnclaveManager::MakeClaimedPINSlowly(new_pin, manager_.GetWrappedPIN());
  std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity;
  DoCreate(/*claimed_pin=*/nullptr, &entity);
  DoAssertion(std::move(entity), std::move(claimed_pin),
              GetAssertionResponseExpectation());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.ChangePIN",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(EnclaveManagerTest, AddPINToExistingAccount) {
  security_domain_service_->pretend_there_are_members();
  const std::string new_pin = "newpin";

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

  BoolFuture add_future;
  manager_.AddDeviceToAccount(std::nullopt, add_future.GetCallback());
  EXPECT_TRUE(add_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  const std::vector<uint8_t> security_domain_secret =
      std::move(manager_.TakeSecret()->second);

  base::HistogramTester histogram_tester;
  BoolFuture set_pin_future;
  manager_.SetPIN(new_pin, "rapt", set_pin_future.GetCallback());
  EXPECT_TRUE(set_pin_future.Wait());
  ASSERT_TRUE(set_pin_future.Get());

  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  EXPECT_EQ(recovery_key_store_->vaults().size(), 1u);
  const std::optional<std::vector<uint8_t>> recovered_security_domain_secret =
      FakeMagicArch::RecoverWithPIN(new_pin, *security_domain_service_,
                                    *recovery_key_store_);
  CHECK(recovered_security_domain_secret.has_value());
  EXPECT_EQ(*recovered_security_domain_secret, security_domain_secret);

  std::unique_ptr<device::enclave::ClaimedPIN> claimed_pin =
      EnclaveManager::MakeClaimedPINSlowly(new_pin, manager_.GetWrappedPIN());
  std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity;
  DoCreate(/*claimed_pin=*/nullptr, &entity);
  DoAssertion(std::move(entity), std::move(claimed_pin),
              GetAssertionResponseExpectation());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.SetPIN",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(EnclaveManagerTest, AddPINToExistingAccountButTheresAlreadyOne) {
  security_domain_service_->pretend_there_are_members();
  const std::string pin = "pin";
  const std::string new_pin = "newpin";

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

  BoolFuture add_future;
  manager_.AddDeviceAndPINToAccount(
      pin, /*previous_pin_public_key=*/std::nullopt, add_future.GetCallback());
  EXPECT_TRUE(add_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  const std::vector<uint8_t> security_domain_secret =
      std::move(manager_.TakeSecret()->second);

  BoolFuture set_pin_future;
  manager_.SetPIN(new_pin, "rapt", set_pin_future.GetCallback());
  EXPECT_TRUE(set_pin_future.Wait());

  // This should fail because there's already a PIN set.
  ASSERT_FALSE(set_pin_future.Get());
}

TEST_F(EnclaveManagerTest, ChangePINWithTwoDevices) {
  security_domain_service_->pretend_there_are_members();
  const std::string pin = "pin";
  const std::string intermediate_pin = "intermediate_pin";
  const std::string new_pin = "newpin";

  const TempDir temp_dir_2;
  EnclaveManager second_manager(
      temp_dir_2.GetPath(), identity_test_env_.identity_manager(),
      base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
        return network_context_.get();
      }),
      url_loader_factory_.GetSafeWeakWrapper());

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {key},
                         /*last_key_version=*/kSecretVersion);
  AcquireLockAndStoreKey(&second_manager, {key},
                         /*last_key_version=*/kSecretVersion);

  {
    BoolFuture add_future;
    manager_.AddDeviceAndPINToAccount(pin,
                                      /*previous_pin_public_key=*/std::nullopt,
                                      add_future.GetCallback());
    EXPECT_TRUE(add_future.Wait());
    ASSERT_TRUE(add_future.Get());
  }
  const std::vector<uint8_t> security_domain_secret =
      std::move(manager_.TakeSecret()->second);

  {
    BoolFuture add_future;
    second_manager.AddDeviceToAccount(std::nullopt, add_future.GetCallback());
    EXPECT_TRUE(add_future.Wait());
  }

  {
    BoolFuture change_future;
    // `second_manager` must fetch PIN information from the security domain in
    // order to change it.
    second_manager.ChangePIN(intermediate_pin, "rapt",
                             change_future.GetCallback());
    EXPECT_TRUE(change_future.Wait());
    ASSERT_TRUE(change_future.Get());
  }

  {
    BoolFuture change_future;
    manager_.ChangePIN(new_pin, "rapt", change_future.GetCallback());
    EXPECT_TRUE(change_future.Wait());
    ASSERT_TRUE(change_future.Get());
  }

  EXPECT_EQ(security_domain_service_->num_physical_members(), 2u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  EXPECT_EQ(recovery_key_store_->vaults().size(), 3u);
  const std::optional<std::vector<uint8_t>> recovered_security_domain_secret =
      FakeMagicArch::RecoverWithPIN(new_pin, *security_domain_service_,
                                    *recovery_key_store_);
  CHECK(recovered_security_domain_secret.has_value());
  EXPECT_EQ(*recovered_security_domain_secret, security_domain_secret);

  std::unique_ptr<device::enclave::ClaimedPIN> claimed_pin =
      EnclaveManager::MakeClaimedPINSlowly(new_pin, manager_.GetWrappedPIN());
  std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity;
  DoCreate(/*claimed_pin=*/nullptr, &entity);
  DoAssertion(std::move(entity), std::move(claimed_pin),
              GetAssertionResponseExpectation());
}

TEST_F(EnclaveManagerTest, EnclaveForgetsClient_SetupWithPIN) {
  ASSERT_TRUE(Register());
  CorruptDeviceId();

  BoolFuture setup_future;
  manager_.SetupWithPIN("1234", setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  EXPECT_FALSE(setup_future.Get());
}

TEST_F(EnclaveManagerTest, EnclaveForgetsClient_AddDeviceToAccount) {
  ASSERT_TRUE(Register());
  CorruptDeviceId();
  security_domain_service_->pretend_there_are_members();

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {std::move(key)},
                         /*last_key_version=*/417);
  BoolFuture add_future;
  ASSERT_TRUE(manager_.AddDeviceToAccount(
      trusted_vault::GpmPinMetadata(std::string(kTestPINPublicKey),
                                    trusted_vault::UsableRecoveryPinMetadata(
                                        GetTestWrappedPIN().SerializeAsString(),
                                        /*expiry=*/base::Time())),
      add_future.GetCallback()));
  EXPECT_TRUE(add_future.Wait());
  EXPECT_FALSE(add_future.Get());
}

TEST_F(EnclaveManagerTest, EnclaveForgetsClient_AddDeviceAndPINToAccount) {
  ASSERT_TRUE(Register());
  CorruptDeviceId();

  security_domain_service_->pretend_there_are_members();

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  AcquireLockAndStoreKey(&manager_, {std::move(key)},
                         /*last_key_version=*/417);
  BoolFuture add_future;
  manager_.AddDeviceAndPINToAccount("1234",
                                    /*previous_pin_public_key=*/std::nullopt,
                                    add_future.GetCallback());
  EXPECT_TRUE(add_future.Wait());
  EXPECT_FALSE(add_future.Get());
}

TEST_F(EnclaveManagerTest, RenewPIN) {
  ASSERT_TRUE(Register());

  const std::string pin = "123456";

  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());

  ASSERT_EQ(security_domain_service_->num_physical_members(), 1u);
  ASSERT_EQ(security_domain_service_->num_pin_members(), 1u);
  ASSERT_EQ(recovery_key_store_->vaults().size(), 1u);

  const std::optional<base::Time> initial_time = LastPINRenewalTime();
  ASSERT_TRUE(initial_time.has_value());

  recovery_key_store_->UpgradeCohort();
  BoolFuture renew_future;
  manager_.RenewPIN(renew_future.GetCallback());
  EXPECT_TRUE(renew_future.Wait());
  EXPECT_TRUE(renew_future.Get());

  // The number of PIN members must not have increased because the upload
  // should have replaced the original.
  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);

  // We expect to create a new Vault.
  EXPECT_EQ(recovery_key_store_->vaults().size(), 2u);

  const std::optional<std::vector<uint8_t>> security_domain_secret =
      FakeMagicArch::RecoverWithPIN(pin, *security_domain_service_,
                                    *recovery_key_store_);
  CHECK(security_domain_secret.has_value());
  EXPECT_EQ(manager_.TakeSecret()->second, *security_domain_secret);
  EXPECT_TRUE(*LastPINRenewalTime() > *initial_time);
}

// Tests that renewing a PIN that didn't have cohort details (because it was
// wrapped on an older version of Chrome) results in the enclave re-wrapping it
// with the details.
TEST_F(EnclaveManagerTest, RenewPINAddsCohortDetails) {
  // Set up with a PIN.
  ASSERT_TRUE(Register());
  const std::string pin = "123456";
  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());

  const std::vector<uint8_t> security_domain_secret =
      manager_.TakeSecret()->second;
  {
    // Delete the wrapped PIN cohort details from the enclave manager and
    // security domain service, simulating an older version of Chrome.
    std::unique_ptr<webauthn_pb::EnclaveLocalState_WrappedPIN>
        wrapped_pin_proto = manager_.GetWrappedPIN();
    std::vector<uint8_t> wrapped_pin =
        DecryptWrappedPin(security_domain_secret,
                          base::as_byte_span(wrapped_pin_proto->wrapped_pin()));
    std::optional<cbor::Value> cbor = cbor::Reader::Read(wrapped_pin);
    cbor::Value::MapValue& wrapped_pin_cbor =
        const_cast<cbor::Value::MapValue&>(cbor->GetMap());
    wrapped_pin_cbor.erase(wrapped_pin_cbor.find(cbor::Value(6)));
    wrapped_pin_cbor.erase(wrapped_pin_cbor.find(cbor::Value(7)));
    std::vector<uint8_t> encrypted_pin = EnclaveManager::EncryptWrappedPIN(
        security_domain_secret,
        *cbor::Writer::Write(cbor::Value(wrapped_pin_cbor)));
    wrapped_pin_proto->set_wrapped_pin(
        std::string(base::as_string_view(encrypted_pin)));
    security_domain_service_->SetPinMemberWrappedPin(
        wrapped_pin_proto->SerializeAsString());
    manager_.SetWrappedPINDataForTesting(std::move(encrypted_pin));
  }

  // Renew the PIN.
  recovery_key_store_->UpgradeCohort();
  BoolFuture renew_future;
  manager_.RenewPIN(renew_future.GetCallback());
  EXPECT_TRUE(renew_future.Wait());
  EXPECT_TRUE(renew_future.Get());

  // Verify that the wrapped PIN that is now present in the security domain
  // service contains the cohort details.
  webauthn_pb::EnclaveLocalState_WrappedPIN wrapped_pin_proto;
  wrapped_pin_proto.ParseFromString(security_domain_service_->GetPinMetadata()
                                        .usable_pin_metadata->wrapped_pin);
  std::vector<uint8_t> wrapped_pin =
      DecryptWrappedPin(security_domain_secret,
                        base::as_byte_span(wrapped_pin_proto.wrapped_pin()));

  std::optional<cbor::Value> cbor = cbor::Reader::Read(wrapped_pin);
  const cbor::Value::MapValue& wrapped_pin_cbor = cbor->GetMap();

  auto cert_xml_serial_number_it = wrapped_pin_cbor.find(cbor::Value(6));
  ASSERT_NE(cert_xml_serial_number_it, wrapped_pin_cbor.end());
  ASSERT_TRUE(cert_xml_serial_number_it->second.is_integer());
  int cert_xml_serial_number = cert_xml_serial_number_it->second.GetInteger();
  EXPECT_EQ(cert_xml_serial_number,
            recovery_key_store_->recovery_key_store_serial_number());

  auto cohort_public_key_it = wrapped_pin_cbor.find(cbor::Value(7));
  ASSERT_NE(cohort_public_key_it, wrapped_pin_cbor.end());
  ASSERT_TRUE(cohort_public_key_it->second.is_bytestring());
  const std::vector<uint8_t> cohort_public_key =
      cohort_public_key_it->second.GetBytestring();
  EXPECT_EQ(cohort_public_key,
            recovery_key_store_->CurrentEndpointPublicKeyBytes());
}

// Regression test for crbug.com/403218779.
// Simulates two chrome clients by standing up a second Enclave Manager. The
// second Enclave Manager will renew a PIN. This invalidates the data from the
// first manager. Then, the first manager will attempt renewing the PIN. This
// used to be broken because the first manager would not download the updated
// PIN data, causing a public key mismatch on the join security domain query.
TEST_F(EnclaveManagerTest, RenewPINWithStaleDataFromAnotherClient) {
  const std::string kPin = "123456";

  // Set up the first manager with the PIN.
  ASSERT_TRUE(Register());
  BoolFuture setup_future;
  manager_.SetupWithPIN(kPin, setup_future.GetCallback());
  ASSERT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  ASSERT_EQ(security_domain_service_->num_physical_members(), 1u);
  ASSERT_EQ(security_domain_service_->num_pin_members(), 1u);
  std::optional<std::pair<int32_t, std::vector<uint8_t>>> secret =
      manager_.TakeSecret();
  ASSERT_TRUE(secret);
  const std::string initial_pin_key =
      security_domain_service_->GetPinMemberPublicKey();

  // Set up the second manager.
  const TempDir temp_dir_2;
  EnclaveManager second_manager(
      temp_dir_2.GetPath(), identity_test_env_.identity_manager(),
      base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
        return network_context_.get();
      }),
      url_loader_factory_.GetSafeWeakWrapper());
  AcquireLockAndStoreKey(&second_manager, {secret->second},
                         /*last_key_version=*/secret->first);
  BoolFuture add_future;
  second_manager.AddDeviceToAccount(security_domain_service_->GetPinMetadata(),
                                    add_future.GetCallback());
  ASSERT_TRUE(add_future.Wait());
  ASSERT_TRUE(add_future.Get());
  ASSERT_EQ(security_domain_service_->num_physical_members(), 2u);
  ASSERT_EQ(security_domain_service_->num_pin_members(), 1u);

  // Renew the PIN with the second manager.
  {
    recovery_key_store_->UpgradeCohort();
    BoolFuture renew_future;
    second_manager.RenewPIN(renew_future.GetCallback());
    ASSERT_TRUE(renew_future.Wait());
    ASSERT_TRUE(renew_future.Get());
  }
  const std::string second_pin_key =
      security_domain_service_->GetPinMemberPublicKey();
  ASSERT_NE(initial_pin_key, second_pin_key);

  // Attempt renewing the PIN with the first enclave.
  {
    recovery_key_store_->UpgradeCohort();
    BoolFuture renew_future;
    manager_.RenewPIN(renew_future.GetCallback());
    ASSERT_TRUE(renew_future.Wait());
    ASSERT_TRUE(renew_future.Get());
  }
  const std::string third_pin_key =
      security_domain_service_->GetPinMemberPublicKey();
  EXPECT_NE(second_pin_key, third_pin_key);
}

// Regression test for crbug.com/402425846.
// Attempts renewing a PIN from local data when the security domain indicates
// that the current PIN changed, and is also not usable for recovery.
TEST_F(EnclaveManagerTest, RenewUnusablePINFromLocalData) {
  const std::string kPin = "123456";

  // Set up the manager with the PIN.
  ASSERT_TRUE(Register());
  BoolFuture setup_future;
  manager_.SetupWithPIN(kPin, setup_future.GetCallback());
  ASSERT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  ASSERT_EQ(security_domain_service_->num_physical_members(), 1u);
  ASSERT_EQ(security_domain_service_->num_pin_members(), 1u);
  const std::string initial_pin_key =
      security_domain_service_->GetPinMemberPublicKey();

  // Update the joined PIN to one that has a different public key and is not
  // usable for recovery.
  security_domain_service_->MakePinMemberUnusable();
  security_domain_service_->SetPinMemberPublicKey("Bad PK");

  // Renew the PIN.
  recovery_key_store_->UpgradeCohort();
  BoolFuture renew_future;
  manager_.RenewPIN(renew_future.GetCallback());
  ASSERT_TRUE(renew_future.Wait());
  EXPECT_TRUE(renew_future.Get());
  EXPECT_NE(security_domain_service_->GetPinMemberPublicKey(), "Bad PK");
}

// Regression test for crbug.com/407171373.
// Attempts renewing a PIN after the security domain has been reset.
TEST_F(EnclaveManagerTest, RenewPINAfterSecurityDomainReset) {
  base::HistogramTester histogram_tester;
  const std::string kPin = "123456";

  // Set up the manager with the PIN.
  ASSERT_TRUE(Register());
  BoolFuture setup_future;
  manager_.SetupWithPIN(kPin, setup_future.GetCallback());
  ASSERT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  ASSERT_TRUE(manager_.IsRegistered());
  ASSERT_EQ(security_domain_service_->num_physical_members(), 1u);
  ASSERT_EQ(security_domain_service_->num_pin_members(), 1u);
  const std::string initial_pin_key =
      security_domain_service_->GetPinMemberPublicKey();

  // Reset the security domain.
  security_domain_service_->ResetSecurityDomain();

  // Attempt to renew the PIN. This should clear the registration data.
  recovery_key_store_->UpgradeCohort();
  BoolFuture renew_future;
  manager_.RenewPIN(renew_future.GetCallback());
  ASSERT_TRUE(renew_future.Wait());
  EXPECT_FALSE(renew_future.Get());
  EXPECT_EQ(security_domain_service_->num_pin_members(), 0u);
  EXPECT_FALSE(manager_.IsRegistered());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.PinRenewalActionOutcome",
      EnclaveManager::ActionOutcome::
          kDoSyncingWithSecurityDomainFailedSecurityDomainHasBeenReset,
      1);
}

// Regression test for crbug.com/407171373.
// Attempts renewing a PIN when the security domain reports that the user
// doesn't have a GPM PIN at all.
TEST_F(EnclaveManagerTest, RenewPINAfterSecurityDomainReportsNoPin) {
  base::HistogramTester histogram_tester;
  const std::string kPin = "123456";

  // Set up the manager with the PIN.
  ASSERT_TRUE(Register());
  BoolFuture setup_future;
  manager_.SetupWithPIN(kPin, setup_future.GetCallback());
  ASSERT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  ASSERT_EQ(security_domain_service_->num_physical_members(), 1u);
  ASSERT_EQ(security_domain_service_->num_pin_members(), 1u);
  const std::string initial_pin_key =
      security_domain_service_->GetPinMemberPublicKey();

  // Remove the PIN member from the security domain.
  security_domain_service_->RemovePinMember();

  // Try to renew the PIN. This shouldn't do anything.
  recovery_key_store_->UpgradeCohort();
  BoolFuture renew_future;
  manager_.RenewPIN(renew_future.GetCallback());
  ASSERT_TRUE(renew_future.Wait());
  EXPECT_FALSE(renew_future.Get());
  EXPECT_EQ(security_domain_service_->num_pin_members(), 0u);
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.PinRenewalActionOutcome",
      EnclaveManager::ActionOutcome::
          kDoSyncingWithSecurityDomainFailedTriedToChangePinButSdsReportsNoPin,
      1);
}

// Tests attempting to renew a PIN that's stored in a Vault cohort that hasn't
// been deprecated yet.
TEST_F(EnclaveManagerTest, NotYetDeprecated) {
  ASSERT_TRUE(Register());
  base::HistogramTester histogram_tester;

  const std::string pin = "123456";

  // Set up a new PIN.
  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  const std::optional<base::Time> initial_time = LastPINRenewalTime();
  ASSERT_TRUE(initial_time.has_value());

  // Attempt to renew without first upgrading the recovery key store.
  BoolFuture renew_future;
  manager_.RenewPIN(renew_future.GetCallback());
  EXPECT_TRUE(renew_future.Wait());
  EXPECT_FALSE(renew_future.Get());
  EXPECT_GT(*LastPINRenewalTime(), *initial_time);
  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.PinRenewalActionOutcome",
      EnclaveManager::ActionOutcome::kDoRenewingPINFailedCohortNotYetDeprecated,
      1);
}

// Tests attempting to renew a PIN with a cert.xml version that's older than the
// last one used to wrap the PIN.
TEST_F(EnclaveManagerTest, NoKeyStoreDowngrade) {
  ASSERT_TRUE(Register());
  base::HistogramTester histogram_tester;

  const std::string pin = "123456";

  // Set up a new PIN.
  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  const std::optional<base::Time> initial_time = LastPINRenewalTime();
  ASSERT_TRUE(initial_time.has_value());

  // Downgrade the recovery key store.
  recovery_key_store_->DowngradeCohort();

  // Attempting to renew the PIN should update the last renewal time to prevent
  // retries.
  BoolFuture renew_future;
  manager_.RenewPIN(renew_future.GetCallback());
  EXPECT_TRUE(renew_future.Wait());
  EXPECT_FALSE(renew_future.Get());
  EXPECT_GT(*LastPINRenewalTime(), *initial_time);
  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.PinRenewalActionOutcome",
      EnclaveManager::ActionOutcome::kDoRenewingPINFailedRecoveryStoreDowngrade,
      1);
}

// Tests that a PIN is still usable for recovery if updating the security domain
// service failed e.g. due to a network issue.
// Regression test for crbug.com/399818721.
TEST_F(EnclaveManagerTest, RenewPINInterruptSecurityDomainUpdate) {
  ASSERT_TRUE(Register());

  const std::string pin = "123456";

  // Set up a PIN.
  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());

  // Fail all *join* requests (but not *all* requests: we need downloading the
  // keys from the security domain service to succeed to get to the point we
  // create a new Vault).
  security_domain_service_->fail_join_requests_matching(base::BindRepeating(
      [](const trusted_vault_pb::JoinSecurityDomainsRequest& request) {
        return true;
      }));

  // Renewing should fail.
  recovery_key_store_->UpgradeCohort();
  BoolFuture renew_future;
  manager_.RenewPIN(renew_future.GetCallback());
  EXPECT_TRUE(renew_future.Wait());
  EXPECT_FALSE(renew_future.Get());

  // Attempt recovery.
  const std::optional<std::vector<uint8_t>> security_domain_secret =
      FakeMagicArch::RecoverWithPIN(pin, *security_domain_service_,
                                    *recovery_key_store_);
  // With the new flow, recovering should succeed.
  ASSERT_TRUE(security_domain_secret.has_value());
  EXPECT_EQ(manager_.TakeSecret()->second, *security_domain_secret);
}

TEST_F(EnclaveManagerTest, EpochChanged) {
  ASSERT_TRUE(Register());

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

  trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult state;
  state.state = trusted_vault::
      DownloadAuthenticationFactorsRegistrationStateResult::State::kRecoverable;
  state.key_version = kSecretVersion;

  EXPECT_TRUE(manager_.ConsiderSecurityDomainState(state, base::DoNothing()));
  EXPECT_TRUE(manager_.is_idle());

  base::HistogramTester histogram_tester;
  BoolFuture update_future;
  state.key_version = kSecretVersion + 1;
  EXPECT_FALSE(
      manager_.ConsiderSecurityDomainState(state, update_future.GetCallback()));
  EXPECT_TRUE(update_future.Wait());
  EXPECT_FALSE(manager_.IsReady());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.ConsiderSecurityDomainState",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(EnclaveManagerTest, PINChanged) {
  ASSERT_TRUE(Register());
  constexpr std::string_view kNewWrappedPin = "dummy wrapped pin >= 29 chars";

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

  const webauthn_pb::EnclaveLocalState::User& user =
      manager_.local_state_for_testing().users().begin()->second;
  webauthn_pb::EnclaveLocalState::WrappedPIN wrapped_pin = user.wrapped_pin();
  wrapped_pin.set_wrapped_pin(kNewWrappedPin);

  trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult state;
  state.state = trusted_vault::
      DownloadAuthenticationFactorsRegistrationStateResult::State::kRecoverable;
  state.key_version = kSecretVersion;
  state.gpm_pin_metadata = trusted_vault::GpmPinMetadata(
      "new public key", trusted_vault::UsableRecoveryPinMetadata(
                            wrapped_pin.SerializeAsString(),
                            /*expiry=*/base::Time::FromTimeT(1)));

  base::HistogramTester histogram_tester;
  BoolFuture update_future;
  EXPECT_TRUE(
      manager_.ConsiderSecurityDomainState(state, update_future.GetCallback()));
  EXPECT_TRUE(update_future.Wait());
  EXPECT_TRUE(manager_.IsReady());
  const webauthn_pb::EnclaveLocalState::User& updated_user =
      manager_.local_state_for_testing().users().begin()->second;
  EXPECT_EQ(updated_user.wrapped_pin().wrapped_pin(), kNewWrappedPin);
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.ConsiderSecurityDomainState",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(EnclaveManagerTest, ConsiderSecurityDomainState_InvalidWrappedPIN) {
  ASSERT_TRUE(Register());
  BoolFuture setup_future;
  manager_.SetupWithPIN("123456", setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  EXPECT_TRUE(manager_.IsReady());

  // Simulaing the new state with invalid wrapped pin.
  trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult state;
  state.state = trusted_vault::
      DownloadAuthenticationFactorsRegistrationStateResult::State::kRecoverable;
  state.key_version = kSecretVersion;
  state.gpm_pin_metadata = trusted_vault::GpmPinMetadata(
      "new public key", trusted_vault::UsableRecoveryPinMetadata(
                            "invalid wrapped pin",
                            /*expiry=*/base::Time::FromTimeT(1)));

  base::HistogramTester histogram_tester;
  EXPECT_TRUE(manager_.ConsiderSecurityDomainState(state, base::DoNothing()));
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.ConsiderSecurityDomainState",
      EnclaveManager::ActionOutcome::
          kConsiderSecurityDomainStateNotStartedWrappedPinParsingError,
      1);
}

TEST_F(EnclaveManagerTest, SigningFails) {
  auto ui_request = std::make_unique<enclave::CredentialRequest>();
  ui_request->signing_callback = AlwaysFailsSigningCallback();
  ui_request->wrapped_secret = {1, 2, 3};
  ui_request->key_version = 1;

  enclave::EnclaveAuthenticator authenticator(
      std::move(ui_request), /*network_context_factory=*/
      base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
        return network_context_.get();
      }));

  std::vector<device::PublicKeyCredentialParams::CredentialInfo> pub_key_params;
  pub_key_params.emplace_back();

  device::MakeCredentialOptions ctap_options;
  ctap_options.json = JSONFromString(R"({
        "attestation": "none",
        "challenge": "xHyLYEorFsaL6vb",
        "extensions": { "credProps": true }
      })");

  auto quit_closure = task_env_.QuitClosure();
  std::optional<device::MakeCredentialStatus> status;
  std::optional<device::AuthenticatorMakeCredentialResponse> response;
  authenticator.MakeCredential(
      /*request=*/{R"({"foo": "bar"})",
                   /*rp=*/{"rpid", "rpname"},
                   /*user=*/{{'u', 'i', 'd'}, "user", "display name"},
                   device::PublicKeyCredentialParams(
                       std::move(pub_key_params))},
      std::move(ctap_options),
      base::BindLambdaForTesting(
          [&quit_closure, &status, &response](
              device::MakeCredentialStatus in_status,
              std::optional<device::AuthenticatorMakeCredentialResponse>
                  in_responses) {
            status = in_status;
            response = std::move(in_responses);
            quit_closure.Run();
          }));
  task_env_.RunUntilQuit();

  ASSERT_TRUE(status.has_value());
  ASSERT_EQ(status, device::MakeCredentialStatus::kEnclaveCancel);
  ASSERT_FALSE(response.has_value());
}

#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_.IsReady());

  std::unique_ptr<trusted_vault::ICloudRecoveryKey> icloud_key =
      trusted_vault::ICloudRecoveryKey::CreateForTest();
  std::unique_ptr<trusted_vault::SecureBoxKeyPair> key =
      trusted_vault::SecureBoxKeyPair::CreateByPrivateKeyImport(
          icloud_key->key()->private_key().ExportToBytes());
  base::HistogramTester histogram_tester;
  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>(),
          base::byte_span_from_cstring("V1 shared_key"),
          base::as_byte_span(shared_member_key.wrapped_key()));
  ASSERT_TRUE(security_domain_secret);
  EXPECT_EQ(manager_.TakeSecret()->second, *security_domain_secret);

  const auto proof = base::span<const uint8_t, crypto::hash::kSha256Size>(
      base::as_byte_span(shared_member_key.member_proof()));
  EXPECT_TRUE(crypto::hmac::VerifySha256(
      *security_domain_secret, base::as_byte_span(icloud_member->public_key()),
      proof));
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.AddICloudRecoveryKey",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}
#endif  // BUILDFLAG(IS_MAC)

TEST_F(EnclaveManagerTest, Unenroll) {
  ASSERT_TRUE(Register());

  ASSERT_TRUE(manager_.IsRegistered());
  base::HistogramTester histogram_tester;
  BoolFuture unenroll_future;
  manager_.Unenroll(unenroll_future.GetCallback());
  EXPECT_TRUE(unenroll_future.Wait());
  EXPECT_TRUE(unenroll_future.Get());
  ASSERT_FALSE(manager_.IsRegistered());
  histogram_tester.ExpectUniqueSample(
      "WebAuthentication.Enclave.ActionOutcome.Unenroll",
      EnclaveManager::ActionOutcome::kSuccess, 1);

  // Things should be in a good state such that we can register again.
  ASSERT_TRUE(Register());
  ASSERT_TRUE(manager_.IsRegistered());
}

TEST_F(EnclaveManagerTest, UnenrollRace) {
  ASSERT_TRUE(Register());

  // Should be safe to race multiple unenroll requests. The ones after the first
  // will fail when pending requests are cancelled.
  ASSERT_TRUE(manager_.IsRegistered());
  BoolFuture unenroll_future1;
  BoolFuture unenroll_future2;
  BoolFuture unenroll_future3;
  manager_.Unenroll(unenroll_future1.GetCallback());
  manager_.Unenroll(unenroll_future2.GetCallback());
  manager_.Unenroll(unenroll_future3.GetCallback());
  EXPECT_TRUE(unenroll_future1.Wait());
  EXPECT_TRUE(unenroll_future2.Wait());
  EXPECT_TRUE(unenroll_future3.Wait());
  EXPECT_TRUE(unenroll_future1.Get());
  EXPECT_FALSE(unenroll_future2.Get());
  EXPECT_FALSE(unenroll_future3.Get());
  ASSERT_FALSE(manager_.IsRegistered());
}

TEST_F(EnclaveManagerTest, UnenrollWithoutRegistering) {
  ASSERT_FALSE(manager_.IsRegistered());
  BoolFuture unenroll_future;
  manager_.Unenroll(unenroll_future.GetCallback());
  EXPECT_TRUE(unenroll_future.Wait());
  EXPECT_TRUE(unenroll_future.Get());
  ASSERT_FALSE(manager_.IsRegistered());
}

TEST_F(EnclaveManagerTest, LockPINThenChange) {
  const std::string pin = "123456";
  const std::string wrong_pin = "654321";

  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());

  std::unique_ptr<sync_pb::WebauthnCredentialSpecifics> entity;
  DoCreate(/*claimed_pin=*/nullptr, &entity);

  // Use the wrong PIN until it's locked at the enclave.
  for (int i = 0; i < 5; i++) {
    std::unique_ptr<device::enclave::ClaimedPIN> wrong_claimed_pin =
        EnclaveManager::MakeClaimedPINSlowly(wrong_pin,
                                             manager_.GetWrappedPIN());
    DoAssertion(
        std::make_unique<sync_pb::WebauthnCredentialSpecifics>(*entity.get()),
        std::move(wrong_claimed_pin),
        GetAssertionResponseExpectation{
            .result = device::GetAssertionStatus::kUserConsentDenied,
            .size = 0});
  }

  // Even the correct PIN should fail now.
  std::unique_ptr<device::enclave::ClaimedPIN> correct_claimed_pin =
      EnclaveManager::MakeClaimedPINSlowly(pin, manager_.GetWrappedPIN());
  DoAssertion(
      std::make_unique<sync_pb::WebauthnCredentialSpecifics>(*entity.get()),
      std::move(correct_claimed_pin),
      GetAssertionResponseExpectation{
          .result = device::GetAssertionStatus::kUserConsentDenied, .size = 0});

  // Change the PIN.
  const std::string new_pin = "123123";
  BoolFuture change_future;
  manager_.ChangePIN(new_pin, "rapt", change_future.GetCallback());
  ASSERT_TRUE(change_future.Get());

  // The new PIN should work.
  std::unique_ptr<device::enclave::ClaimedPIN> new_correct_claimed_pin =
      EnclaveManager::MakeClaimedPINSlowly(new_pin, manager_.GetWrappedPIN());
  DoAssertion(std::move(entity), std::move(new_correct_claimed_pin),
              GetAssertionResponseExpectation());
}

// Tests that rely on `ScopedFakeUnexportableKeyProvider` only work on
// platforms where EnclaveManager uses `GetUnexportableKeyProvider`, as opposed
// to `GetSoftwareUnsecureUnexportableKeyProvider`.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
#define MAYBE_HardwareKeyLost HardwareKeyLost
#else
#define MAYBE_HardwareKeyLost DISABLED_HardwareKeyLost
#endif
TEST_F(EnclaveManagerTest, MAYBE_HardwareKeyLost) {
  crypto::ScopedFakeUserVerifyingKeyProvider scoped_uv_key_provider;
  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());
  AcquireLockAndStoreKey(&manager_, {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(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
  std::unique_ptr<EnclaveManager::UvKeyCreationLock> uv_creation_lock;
  device::enclave::UVKeyCreationCallback key_creation_callback;
  std::tie(uv_creation_lock, 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();
#endif

  fake_hw_provider_.reset();
  manager_.ClearCachedKeysForTesting();

  // Verify a UV key was created as well.
  std::string uv_key_label = manager_.local_state_for_testing()
                                 .mutable_users()
                                 ->begin()
                                 ->second.wrapped_uv_private_key();
  auto uv_key_provider = crypto::GetUserVerifyingKeyProvider(
      crypto::UserVerifyingKeyProvider::Config());
  base::test::TestFuture<
      base::expected<std::unique_ptr<crypto::UserVerifyingSigningKey>,
                     crypto::UserVerifyingKeyCreationError>>
      key_future_present;
  uv_key_provider->GetUserVerifyingSigningKey(uv_key_label,
                                              key_future_present.GetCallback());
  EXPECT_TRUE(key_future_present.Wait());
  EXPECT_TRUE(key_future_present.Get().has_value());

  crypto::ScopedNullUnexportableKeyProvider null_hw_provider;
  auto signing_callback = manager_.IdentityKeySigningCallback();
  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_.IsRegistered());

  // Verify that the UV key was deleted when the HW key was lost.
  base::test::TestFuture<
      base::expected<std::unique_ptr<crypto::UserVerifyingSigningKey>,
                     crypto::UserVerifyingKeyCreationError>>
      key_future_deleted;
  uv_key_provider->GetUserVerifyingSigningKey(uv_key_label,
                                              key_future_deleted.GetCallback());
  EXPECT_TRUE(key_future_deleted.Wait());
  EXPECT_FALSE(key_future_deleted.Get().has_value());
}

// Tests that Chrome resets the local state if joining the physical device to
// the security domain failed.
// Regression test for crbug.com/404563934.
TEST_F(EnclaveManagerTest, JoiningSecurityDomainFailed) {
  const std::string kPin = "123456";
  ASSERT_TRUE(Register());

  // Fail the request to join the physical device.
  security_domain_service_->fail_join_requests_matching(base::BindRepeating(
      [](const trusted_vault_pb::JoinSecurityDomainsRequest& request) {
        return request.security_domain_member().member_type() ==
               trusted_vault_pb::SecurityDomainMember::
                   MEMBER_TYPE_PHYSICAL_DEVICE;
      }));
  BoolFuture setup_future;
  manager_.SetupWithPIN(kPin, setup_future.GetCallback());
  ASSERT_TRUE(setup_future.Wait());
  ASSERT_FALSE(setup_future.Get());

  const auto& local_state = manager_.local_state_for_testing()
                                .mutable_users()
                                ->find(gaia_id_.ToString())
                                ->second;
  EXPECT_FALSE(local_state.has_wrapped_pin());
  EXPECT_FALSE(local_state.registered());
  EXPECT_FALSE(local_state.joined());
}

TEST_F(EnclaveManagerTest, AddDeviceToAccountMismatchedGaia) {
  ASSERT_TRUE(Register());
  security_domain_service_->pretend_there_are_members();

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  auto lock = manager_.GetStoreKeysLock();
  manager_.StoreKeys(GaiaId("Not the primary account"),
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     /*user_action_trigger=*/std::nullopt);
  EXPECT_EQ(last_stored_gaia_id_, GaiaId("Not the primary account"));

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

// Tests that attempting to renew a PIN does not make Chrome crash if joining
// the physical device to the security domain failed.
// Regression test for crbug.com/404563934.
TEST_F(EnclaveManagerTest, RenewPinAfterJoiningFailed) {
  const std::string kPin = "123456";
  ASSERT_TRUE(Register());

  // Fail the request to join the physical device. This used to leave Chrome in
  // an inconsistent state with a wrapped PIN but no wrapped security domain
  // secret.
  security_domain_service_->fail_join_requests_matching(base::BindRepeating(
      [](const trusted_vault_pb::JoinSecurityDomainsRequest& request) {
        return request.security_domain_member().member_type() ==
               trusted_vault_pb::SecurityDomainMember::
                   MEMBER_TYPE_PHYSICAL_DEVICE;
      }));
  BoolFuture setup_future;
  manager_.SetupWithPIN(kPin, setup_future.GetCallback());
  ASSERT_TRUE(setup_future.Wait());
  ASSERT_FALSE(setup_future.Get());

  // Force considering a PIN renewal.
  manager_.local_state_for_testing()
      .mutable_users()
      ->find(gaia_id_.ToString())
      ->second.set_last_refreshed_pin_epoch_secs(0);
  manager_.ConsiderPinRenewalForTesting();
  base::test::RunUntil([this]() { return manager_.is_idle(); });

  // Chrome should not have attempted to renew the PIN.
  EXPECT_EQ(manager_.local_state_for_testing()
                .mutable_users()
                ->find(gaia_id_.ToString())
                ->second.last_refreshed_pin_epoch_secs(),
            0);
}

// Tests that attempting to renew a PIN does not make Chrome crash if there is
// no wrapped security domain secret. It used to be possible to end up in this
// state, so we need to make sure clients who got into it don't crash after
// startup.
// Regression test for crbug.com/404563934.
TEST_F(EnclaveManagerTest, RenewPinWithoutWrappedSecurityDomainSecret) {
  const std::string kPin = "123456";
  ASSERT_TRUE(Register());

  BoolFuture setup_future;
  manager_.SetupWithPIN(kPin, setup_future.GetCallback());
  ASSERT_TRUE(setup_future.Wait());
  ASSERT_TRUE(setup_future.Get());

  // Remove the security domain secrets.
  manager_.local_state_for_testing()
      .mutable_users()
      ->find(gaia_id_.ToString())
      ->second.clear_wrapped_security_domain_secrets();

  // Force considering a PIN renewal.
  manager_.local_state_for_testing()
      .mutable_users()
      ->find(gaia_id_.ToString())
      ->second.set_last_refreshed_pin_epoch_secs(0);
  manager_.ConsiderPinRenewalForTesting();
  base::test::RunUntil([this]() { return manager_.is_idle(); });

  // Chrome should not have attempted to renew the PIN.
  EXPECT_EQ(manager_.local_state_for_testing()
                .mutable_users()
                ->find(gaia_id_.ToString())
                ->second.last_refreshed_pin_epoch_secs(),
            0);
}

TEST_F(EnclaveManagerTest, CheckGpmPinAvailabilityWhenPinIsAvailable) {
  const std::string pin = "123456";
  ASSERT_TRUE(Register());

  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());

  base::test::TestFuture<EnclaveManager::GpmPinAvailability> future;
  auto request = manager_.CheckGpmPinAvailability(future.GetCallback());
  EXPECT_TRUE(future.Wait());
  EXPECT_EQ(future.Get(),
            EnclaveManager::GpmPinAvailability::kGpmPinSetAndUsable);
}

TEST_F(EnclaveManagerTest, CheckGpmPinAvailabilityWhenPinIsUnusable) {
  const std::string pin = "123456";
  ASSERT_TRUE(Register());

  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  security_domain_service_->MakePinMemberUnusable();

  base::test::TestFuture<EnclaveManager::GpmPinAvailability> future;
  auto request = manager_.CheckGpmPinAvailability(future.GetCallback());
  EXPECT_TRUE(future.Wait());
  EXPECT_EQ(future.Get(),
            EnclaveManager::GpmPinAvailability::kGpmPinSetButNotUsable);
}

TEST_F(EnclaveManagerTest, CheckGpmPinAvailabilityWhenWrappedPinIsInvalid) {
  const std::string pin = "123456";
  ASSERT_TRUE(Register());

  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  security_domain_service_->SetPinMemberWrappedPin("invalid wrapped pin");

  base::test::TestFuture<EnclaveManager::GpmPinAvailability> future;
  auto request = manager_.CheckGpmPinAvailability(future.GetCallback());
  EXPECT_TRUE(future.Wait());
  EXPECT_EQ(future.Get(),
            EnclaveManager::GpmPinAvailability::kGpmPinSetButNotUsable);
}

TEST_F(EnclaveManagerTest, CheckGpmPinAvailabilityWhenPinIsNotAvailable) {
  ASSERT_TRUE(Register());

  base::test::TestFuture<EnclaveManager::GpmPinAvailability> future;
  auto request = manager_.CheckGpmPinAvailability(future.GetCallback());
  EXPECT_TRUE(future.Wait());
  EXPECT_EQ(future.Get(), EnclaveManager::GpmPinAvailability::kGpmPinUnset);
}

class OpportunisticKeyRetrievalEnclaveManagerTest : public EnclaveManagerTest {
 public:
  OpportunisticKeyRetrievalEnclaveManagerTest() {
    scoped_feature_list_.InitWithFeatures(
        {device::kWebAuthnOpportunisticRetrieval,
         device::kWebAuthnDoNotAlwaysTerminateStateMachineDuringIdentityChange},
        {});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

#if !BUILDFLAG(IS_CHROMEOS)
// This test verifies the following scenario:
// - The system UV is not available.
// - Imagine that there is an account "Account 1".
// - "Account 1" has a GPM PIN knowledge factor.
// - Imagine that the currently signed-in primary account is "Account 2".
// - We opportunistically retrieved a passkey secret of "Account 1" and
//   we are trying to store it in Enclave Manager.
// - Since the primary account is "Account 2", we can't store the passkey
//   secret at this point (we need to wait until "Account 1" becomes a
//   primary account). Because of this, Enclave Manager will temporarily
//   cache the opportunistically retrieved a passkey secret of "Account 1".
// - After "Account 1" becomes a primary account Enclave Manager will try
//   to store the cached opportunistically retrieved passkey secret of
//   "Account 1".
//  - Since "Account 1" has a GPM PIN knowledge factor, the opportunistically
//    retrieved passkey secret will be successfully stored.
TEST_F(OpportunisticKeyRetrievalEnclaveManagerTest,
       StoringOpportunisticallyRetrievedKeyAfterSignInOfMatchingAccount) {
  // Simulating the absence of system UV.
  auto disabled_uv = crypto::ScopedNullUserVerifyingKeyProvider();
  // Currently we are signed-in with some account (let's call it "Account 1").
  const CoreAccountInfo account_1 =
      identity_test_env_.identity_manager()->GetPrimaryAccountInfo(
          signin::ConsentLevel::kSignin);
  // Registering a GPM PIN knowledge factor for the account "Account 1".
  ASSERT_TRUE(Register());
  BoolFuture setup_future;
  manager_.SetupWithPIN(/*pin=*/"123456", setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  // Enforce enclave manager to be unregistered (for being able to test the
  // logic of storing the opportunistically retrieved key). After clearing
  // registration, the GPM PIN knowledge factor remains available.
  // The GPM PIN will be needed for storing opportunistically retrieved keys.
  manager_.ClearRegistrationForTesting();
  ASSERT_FALSE(manager_.IsRegistered());
  // "Account 1" should have no keys.
  EXPECT_EQ(manager_.store_keys_count(), 0u);

  // Signing-in with another primary account (let's call it "Account 2").
  identity_test_env_.MakePrimaryAccountAvailable("test2@gmail.com",
                                                 signin::ConsentLevel::kSignin);
  const CoreAccountInfo account_2 =
      identity_test_env_.identity_manager()->GetPrimaryAccountInfo(
          signin::ConsentLevel::kSignin);
  identity_test_env_.SetCookieAccounts({});
  base::test::RunUntil([this]() { return manager_.is_idle(); });
  EXPECT_THAT(GaiaAccountsInState(),
              testing::UnorderedElementsAre(account_2.gaia.ToString()));

  // While the primary account is "Account 2", we are trying to store the
  // opportunistically retrieved key for the other account ("Account 1").
  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  EnclaveKeysWaiter enclave_keys_waiter(&manager_);
  manager_.StoreKeys(account_1.gaia,
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     /*user_action_trigger=*/std::nullopt);
  // Since the account "Account 1" is not signed-in, the opportunistically
  // retrieved key can't be stored immediately (it will be cached for some
  // time). The corresponding metric is expected to be published in this case.
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowCachedKeysBecauseAccountDoesNotMatch,
      1);

  // Signing-in with the "Account 1" account again.
  // The call to `identity_test_env_.MakePrimaryAccountAvailable(...)` triggers
  // `EnclaveManager::HandleIdentityChange`, which should start storing the
  // opportunistically retrieved key.
  identity_test_env_.MakePrimaryAccountAvailable(account_1.email,
                                                 signin::ConsentLevel::kSignin);
  // Another call to `identity_test_env_.MakePrimaryAccountAvailable(...)`
  // with the same account shouldn't interrupt the logic of storing
  // opportunistically retrieved key.
  identity_test_env_.MakePrimaryAccountAvailable(account_1.email,
                                                 signin::ConsentLevel::kSync);
  base::test::RunUntil([this]() { return manager_.is_idle(); });
  EXPECT_THAT(GaiaAccountsInState(),
              testing::UnorderedElementsAre(account_1.gaia.ToString()));

  // Since the "Account 1" account is signed-in now, the cached
  // opportunistically retrieved key should be stored now.
  EXPECT_EQ(enclave_keys_waiter.Wait(),
            EnclaveManager::OutOfContextRecoveryOutcome::
                kStoreKeysFromOpportunisticFlowSucceeded);
  EXPECT_EQ(manager_.store_keys_count(), 1u);

  // The metrics indicating what happened with the cached key.
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.CachedOpportunisticallyRetrievedKeyEvent",
      webauthn::metrics::
          WebAuthenticationGPMCachedOpportunisticallyRetrievedKeyEvent::
              kStoreKeysFromOpportunisticFlowCachedKeysStoringAfterSignIn,
      1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowStarted,
      1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowSucceeded,
      1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.Enclave.OpportunisticStoreKeysOutcome",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}
#endif

class EnclaveManagerMockTimeTest : public EnclaveManagerTest {
 public:
  EnclaveManagerMockTimeTest()
      : EnclaveManagerTest(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
  }
};

class OpportunisticKeyRetrievalEnclaveManagerMockTimeTest
    : public EnclaveManagerMockTimeTest {
 public:
  OpportunisticKeyRetrievalEnclaveManagerMockTimeTest() {
    scoped_feature_list_.InitWithFeatures(
        {device::kWebAuthnOpportunisticRetrieval,
         device::kWebAuthnDoNotAlwaysTerminateStateMachineDuringIdentityChange},
        {});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

#if !BUILDFLAG(IS_CHROMEOS)
TEST_F(OpportunisticKeyRetrievalEnclaveManagerMockTimeTest,
       DiscardingOpportunisticallyRetrievedKeyAfterTimeout) {
  // Trying to store the opportunistically retrieved key of some other account.
  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  manager_.StoreKeys(GaiaId("some_other_account_id"),
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     /*user_action_trigger=*/std::nullopt);
  // Since the other account is not signed-in, the opportunistically
  // retrieved key can't be stored immediately (it will be cached for some
  // time). The corresponding metric is expected to be published in this case.
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowCachedKeysBecauseAccountDoesNotMatch,
      1);

  int ttl_seconds =
      device::kWebAuthnOpportunisticRetrievalTimeToKeepCachedKeySeconds.Get();

  // Move to the point in time before the timeout. The cached key should not be
  // discarded before the timeout.
  task_env_.FastForwardBy(base::Seconds(ttl_seconds - 1));
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.CachedOpportunisticallyRetrievedKeyEvent",
      webauthn::metrics::
          WebAuthenticationGPMCachedOpportunisticallyRetrievedKeyEvent::
              kStoreKeysFromOpportunisticFlowCachedKeysRemovedAfterTimeout,
      0);

  // Move a bit beyond the timeout. The cached key should be discarded.
  task_env_.FastForwardBy(base::Seconds(2));
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.CachedOpportunisticallyRetrievedKeyEvent",
      webauthn::metrics::
          WebAuthenticationGPMCachedOpportunisticallyRetrievedKeyEvent::
              kStoreKeysFromOpportunisticFlowCachedKeysRemovedAfterTimeout,
      1);
}

TEST_F(OpportunisticKeyRetrievalEnclaveManagerMockTimeTest,
       OverwritingAndDiscardingOpportunisticallyRetrievedKey) {
  // Trying to store the opportunistically retrieved key of some other account.
  std::vector<uint8_t> key_1(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  manager_.StoreKeys(GaiaId("some_other_account_id_1"),
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key_1),
                                                               kSecretVersion)},
                     /*user_action_trigger=*/std::nullopt);
  // Since the other account is not signed-in, the opportunistically
  // retrieved key will be cached.
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowCachedKeysBecauseAccountDoesNotMatch,
      1);

  int ttl_seconds =
      device::kWebAuthnOpportunisticRetrievalTimeToKeepCachedKeySeconds.Get();

  // Move to the point in time before the timeout, and overwrite the cached key
  // with another opportunistically retrieved key.
  task_env_.FastForwardBy(base::Seconds(ttl_seconds - 1));
  std::vector<uint8_t> key_2(kTestKey.begin(), kTestKey.end());
  manager_.StoreKeys(GaiaId("some_other_account_id_2"),
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key_2),
                                                               kSecretVersion)},
                     /*user_action_trigger=*/std::nullopt);
  // This metric is being published when the previous key is being overwritten.
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.CachedOpportunisticallyRetrievedKeyEvent",
      webauthn::metrics::
          WebAuthenticationGPMCachedOpportunisticallyRetrievedKeyEvent::
              kStoreKeysFromOpportunisticFlowCachedKeysHaveBeenOverwritten,
      1);
  // This metric is being incremented when the new key is being cached.
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowCachedKeysBecauseAccountDoesNotMatch,
      2);

  // Move to the point in time before the timeout of the newly cached key. This
  // key should not be discarded before its TTL timeout.
  task_env_.FastForwardBy(base::Seconds(ttl_seconds - 1));
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.CachedOpportunisticallyRetrievedKeyEvent",
      webauthn::metrics::
          WebAuthenticationGPMCachedOpportunisticallyRetrievedKeyEvent::
              kStoreKeysFromOpportunisticFlowCachedKeysRemovedAfterTimeout,
      0);

  // The new cached key will be discarded after its TTL timeout.
  task_env_.FastForwardBy(base::Seconds(2));
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.CachedOpportunisticallyRetrievedKeyEvent",
      webauthn::metrics::
          WebAuthenticationGPMCachedOpportunisticallyRetrievedKeyEvent::
              kStoreKeysFromOpportunisticFlowCachedKeysRemovedAfterTimeout,
      1);
}
#endif

TEST_F(EnclaveManagerMockTimeTest, AutomaticRenewal) {
  const std::string pin = "123456";

  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());

  // Because this test runs under MOCK_TIME, waiting for `setup_future` causes
  // all events to run immediately, including the timeout timer for the
  // WebSocket. Thus we have to step time forwards incrementally so that the
  // enclave process (which isn't under MOCK_TIME) gets a chance to do
  // something.
  const base::TimeDelta time_step = base::Milliseconds(1);
  while (!setup_future.IsReady()) {
    base::PlatformThread::Sleep(time_step);
    task_env_.FastForwardBy(time_step);
  }
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());

  // When using MOCK_TIME, requests to the enclave will likely timeout as noted
  // just above. But to avoid flakes, this ensures that the requests will always
  // fail.
  device::enclave::ScopedEnclaveOverride override(
      TestWebAuthnEnclaveIdentity(/*port=*/100));

  EXPECT_EQ(security_domain_service_->num_physical_members(), 1u);
  EXPECT_EQ(security_domain_service_->num_pin_members(), 1u);
  EXPECT_EQ(recovery_key_store_->vaults().size(), 1u);

  // Renewal will have been checked as soon as the state was loaded.
  EXPECT_EQ(manager_.renewal_checks_for_testing(), 1u);
  EXPECT_EQ(manager_.renewal_attempts_for_testing(), 0u);

  // After a day, another check should have been done.
  task_env_.FastForwardBy(base::Hours(24));
  EXPECT_EQ(manager_.renewal_checks_for_testing(), 2u);
  EXPECT_EQ(manager_.renewal_attempts_for_testing(), 0u);

  // After 30 days, there should be a renewal attempt.
  task_env_.FastForwardBy(base::Days(30));
  EXPECT_EQ(manager_.renewal_checks_for_testing(), 32u);
  EXPECT_EQ(manager_.renewal_attempts_for_testing(), 1u);

  // The renewal attempts will fail so an attempt should be made every day.
  task_env_.FastForwardBy(base::Days(1));
  EXPECT_EQ(manager_.renewal_checks_for_testing(), 33u);
  EXPECT_EQ(manager_.renewal_attempts_for_testing(), 2u);

  // Ensure that no operation is outstanding.
  task_env_.FastForwardBy(base::Hours(1));
}

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_keychain_.SetUVMethod(
        crypto::apple::ScopedFakeKeychainV2::UVMethod::kPasswordOnly);
#endif  // BUILDFLAG(IS_MAC)
  }

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

  void DisableUVKeySupport() {
    fake_provider_.emplace<crypto::ScopedNullUserVerifyingKeyProvider>();
#if BUILDFLAG(IS_CHROMEOS)
    // 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)
    // The scoped fake provider doesn't cover ChromeOS.
    NOTIMPLEMENTED();
#endif
  }

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

#if BUILDFLAG(IS_MAC)
  crypto::apple::ScopedFakeKeychainV2 scoped_fake_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());
  AcquireLockAndStoreKey(&manager_, {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(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
#else
  EXPECT_EQ(manager_.uv_key_state(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            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());
  AcquireLockAndStoreKey(&manager_, {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_.IsRegistered());
  EXPECT_EQ(manager_.uv_key_state(EnclaveManager::PlatformUvSupport::kNoUvKey),
            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());
  AcquireLockAndStoreKey(&manager_, {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(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
  std::unique_ptr<EnclaveManager::UvKeyCreationLock> uv_creation_lock;
  device::enclave::UVKeyCreationCallback key_creation_callback;
  std::tie(uv_creation_lock, 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(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            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_.IsRegistered());
}

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());
  AcquireLockAndStoreKey(&manager_, {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(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            EnclaveManager::UvKeyState::kUsesSystemUI);
}

class OpportunisticKeyRetrievalEnclaveUVTest : public EnclaveUVTest {
 public:
  OpportunisticKeyRetrievalEnclaveUVTest() {
    scoped_feature_list_.InitWithFeatures(
        {device::kWebAuthnOpportunisticRetrieval,
         device::kWebAuthnDoNotAlwaysTerminateStateMachineDuringIdentityChange},
        {});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(OpportunisticKeyRetrievalEnclaveUVTest, OpportunisticStoreKeys) {
  security_domain_service_->pretend_there_are_members();
  ASSERT_FALSE(manager_.IsRegistered());
  EXPECT_EQ(manager_.store_keys_count(), 0u);

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  EnclaveKeysWaiter enclave_keys_waiter(&manager_);
  manager_.StoreKeys(gaia_id_,
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     /*user_action_trigger=*/
                     trusted_vault::TrustedVaultUserActionTriggerForUMA::
                         kPasskeyUnlockProfileMenu);
  EXPECT_EQ(enclave_keys_waiter.Wait(),
            EnclaveManager::OutOfContextRecoveryOutcome::
                kStoreKeysFromOpportunisticFlowSucceeded);
  EXPECT_FALSE(manager_.has_wrapped_pin());

  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowStarted,
      1);
  EXPECT_EQ(manager_.store_keys_count(), 1u);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowSucceeded,
      1);
  histogram_tester.ExpectUniqueSample(
      "PasswordManager.UserActionTriggerThatRetrievedPasskeySecret",
      /*sample=*/
      trusted_vault::TrustedVaultUserActionTriggerForUMA::
          kPasskeyUnlockProfileMenu,
      /*expected_bucket_count=*/1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.Enclave.OpportunisticStoreKeysOutcome",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(OpportunisticKeyRetrievalEnclaveUVTest,
       OpportunisticStoreKeysFailedDueToTrustedVaultRegistrationError) {
  security_domain_service_->pretend_there_are_members();
  security_domain_service_->fail_all_requests();
  ASSERT_FALSE(manager_.IsRegistered());
  EXPECT_EQ(manager_.store_keys_count(), 0u);

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  EnclaveKeysWaiter enclave_keys_waiter(&manager_);
  manager_.StoreKeys(gaia_id_,
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     /*user_action_trigger=*/
                     trusted_vault::TrustedVaultUserActionTriggerForUMA::
                         kPasskeyUnlockProfileMenu);
  EXPECT_EQ(enclave_keys_waiter.Wait(),
            EnclaveManager::OutOfContextRecoveryOutcome::
                kStoreKeysFromOpportunisticFlowFailed);

  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowStarted,
      1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowFailed,
      1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.Enclave.OpportunisticStoreKeysOutcome",
      EnclaveManager::ActionOutcome::
          kDoJoiningDomainFailedTrustedVaultRegistrationError,
      1);
}

class NoOpportunisticKeyRetrievalEnclaveUVTest : public EnclaveUVTest {
 public:
  NoOpportunisticKeyRetrievalEnclaveUVTest() {
    scoped_feature_list_.InitAndDisableFeature(
        device::kWebAuthnOpportunisticRetrieval);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(NoOpportunisticKeyRetrievalEnclaveUVTest,
       OpportunisticStoreKeysAreIgnoredWhenFeatureIsDisabled) {
  security_domain_service_->pretend_there_are_members();
  ASSERT_FALSE(manager_.IsRegistered());
  EXPECT_EQ(manager_.store_keys_count(), 0u);

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  manager_.StoreKeys(gaia_id_,
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     /*user_action_trigger=*/std::nullopt);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowStarted,
      0);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowSucceeded,
      0);
  EXPECT_EQ(manager_.store_keys_count(), 1u);
  EXPECT_FALSE(manager_.IsRegistered());
}

TEST_F(OpportunisticKeyRetrievalEnclaveUVTest,
       OpportunisticStoreKeysRedundant) {
  ASSERT_FALSE(manager_.IsRegistered());
  EXPECT_EQ(manager_.store_keys_count(), 0u);

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

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  EnclaveKeysWaiter enclave_keys_waiter(&manager_);
  manager_.StoreKeys(gaia_id_,
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     trusted_vault::TrustedVaultUserActionTriggerForUMA::
                         kPasskeyUnlockProfileMenu);
  EXPECT_EQ(enclave_keys_waiter.Wait(),
            EnclaveManager::OutOfContextRecoveryOutcome::
                kStoreKeysFromOpportunisticFlowIgnoredRedundant);

  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowStarted,
      1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowIgnoredRedundant,
      1);
  EXPECT_EQ(manager_.store_keys_count(), 0u);

  // If the existing registration doesn't cause the keys to be discarded then
  // several things will go wrong. If nothing else, `pretend_there_are_members`
  // isn't called so the fake security domain service will CHECK since the
  // version is non-zero.
}

#if !BUILDFLAG(IS_CHROMEOS)
// On Chrome OS, `AreUserVerifyingKeysSupported` always returns true, thus this
// test cannot establish its preconditions.

TEST_F(OpportunisticKeyRetrievalEnclaveUVTest,
       OpportunisticStoreKeysNoUVButHasUsableGpmPin) {
  const std::string pin = "123456";
  BoolFuture setup_future;
  manager_.SetupWithPIN(pin, setup_future.GetCallback());
  EXPECT_TRUE(setup_future.Wait());
  ASSERT_TRUE(manager_.IsReady());
  ASSERT_TRUE(manager_.has_wrapped_pin());
  EXPECT_EQ(manager_.store_keys_count(), 0u);

  // Clear the local registration so that we can test the opportunistic flow.
  // The fake security domain service will still have the PIN available.
  manager_.ClearRegistrationForTesting();
  ASSERT_FALSE(manager_.IsRegistered());

  DisableUVKeySupport();

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  EnclaveKeysWaiter enclave_keys_waiter(&manager_);
  manager_.StoreKeys(gaia_id_,
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     trusted_vault::TrustedVaultUserActionTriggerForUMA::
                         kPasskeyUnlockProfileMenu);
  EXPECT_EQ(enclave_keys_waiter.Wait(),
            EnclaveManager::OutOfContextRecoveryOutcome::
                kStoreKeysFromOpportunisticFlowSucceeded);

  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowStarted,
      1);
  EXPECT_EQ(manager_.store_keys_count(), 1u);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowSucceeded,
      1);
  EXPECT_TRUE(manager_.has_wrapped_pin());
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.Enclave.OpportunisticStoreKeysOutcome",
      EnclaveManager::ActionOutcome::kSuccess, 1);
}

TEST_F(OpportunisticKeyRetrievalEnclaveUVTest,
       OpportunisticStoreKeysNoUVNoGpmPin) {
  ASSERT_FALSE(manager_.IsRegistered());
  EXPECT_EQ(manager_.store_keys_count(), 0u);
  DisableUVKeySupport();

  std::vector<uint8_t> key(kTestKey.begin(), kTestKey.end());
  base::HistogramTester histogram_tester;
  EnclaveKeysWaiter enclave_keys_waiter(&manager_);
  manager_.StoreKeys(gaia_id_,
                     {trusted_vault::TrustedVaultKeyAndVersion(std::move(key),
                                                               kSecretVersion)},
                     trusted_vault::TrustedVaultUserActionTriggerForUMA::
                         kPasskeyUnlockProfileMenu);
  EXPECT_EQ(enclave_keys_waiter.Wait(),
            EnclaveManager::OutOfContextRecoveryOutcome::
                kStoreKeysFromOpportunisticFlowFailed);

  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowStarted,
      1);
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.GPM.RecoveryEvent",
      webauthn::metrics::WebAuthenticationGPMRecoveryEvent::
          kStoreKeysFromOpportunisticFlowFailed,
      1);
  EXPECT_EQ(manager_.store_keys_count(), 0u);

  // If the lack of UV doesn't cause the keys to be discarded then several
  // things will go wrong. If nothing else, `pretend_there_are_members` isn't
  // called so the fake security domain service will CHECK since the version is
  // non-zero.
  EXPECT_FALSE(manager_.IsRegistered());
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.Enclave.OpportunisticStoreKeysOutcome",
      EnclaveManager::ActionOutcome::
          kDoStoringOpportunisticallyRetrievedKeyFailedNoSystemUvNoGpmPin,
      1);
}
#endif

#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());
  AcquireLockAndStoreKey(&manager_, {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_keychain_.SetUVMethod(
      crypto::apple::ScopedFakeKeychainV2::UVMethod::kBiometrics);
  EXPECT_EQ(manager_.uv_key_state(
                EnclaveManager::PlatformUvSupport::kUvKeyWithBiometrics),
            EnclaveManager::UvKeyState::kUsesChromeUI);

  scoped_fake_keychain_.SetUVMethod(
      crypto::apple::ScopedFakeKeychainV2::UVMethod::kPasswordOnly);
  EXPECT_EQ(manager_.uv_key_state(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            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());
  AcquireLockAndStoreKey(&manager_, {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(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
  const auto& user_state = manager_.local_state_for_testing()
                               .users()
                               .find(gaia_id_.ToString())
                               ->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());

  std::unique_ptr<EnclaveManager::UvKeyCreationLock> uv_creation_lock;
  device::enclave::UVKeyCreationCallback key_creation_callback;
  std::tie(uv_creation_lock, 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());
  AcquireLockAndStoreKey(&manager_, {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(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);
  const auto& user_state = manager_.local_state_for_testing()
                               .users()
                               .find(gaia_id_.ToString())
                               ->second;
  EXPECT_TRUE(user_state.deferred_uv_key_creation());
  EXPECT_TRUE(user_state.wrapped_uv_private_key().empty());

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

  base::test::TestFuture<bool> unenroll_future;
  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->up_and_uv_bits =
      device::enclave::UserPresentAndVerifiedBits::kPresentAndVerified;
  std::unique_ptr<EnclaveManager::UvKeyCreationLock> uv_creation_lock;
  std::tie(uv_creation_lock, ui_request->uv_key_creation_callback) =
      manager_.UserVerifyingKeyCreationCallback();
  ui_request->unregister_callback =
      base::BindOnce(&EnclaveManager::Unenroll, manager_.GetWeakPtr(),
                     unenroll_future.GetCallback());

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

  EXPECT_FALSE(manager_.IsRegistered());
}

// Test that signing with a key that is unknown to the service unregisters
// the local client.
TEST_F(EnclaveUVTest, UnregisterOnMissingUserVerifyingKey) {
  base::HistogramTester histogram_tester;
  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());
  AcquireLockAndStoreKey(&manager_, {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;

  EXPECT_EQ(manager_.uv_key_state(
                EnclaveManager::PlatformUvSupport::kUvKeyButNoBiometrics),
            EnclaveManager::UvKeyState::kUsesSystemUIDeferredCreation);

  // Generate a UV key and reset the deferred UV key flag, without sending a
  // public key to the service.
  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());
  manager_.local_state_for_testing()
      .mutable_users()
      ->begin()
      ->second.set_deferred_uv_key_creation(false);
  std::unique_ptr<crypto::UserVerifyingSigningKey> key_temp =
      std::move(key_future.Take().value());
  manager_.user_verifying_key_ =
      base::MakeRefCounted<crypto::RefCountedUserVerifyingSigningKey>(
          std::move(key_temp));

  base::test::TestFuture<bool> unenroll_future;
  auto ui_request = std::make_unique<enclave::CredentialRequest>();
  ui_request->signing_callback =
      manager_.UserVerifyingKeySigningCallback(/*options=*/{});
  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->up_and_uv_bits =
      device::enclave::UserPresentAndVerifiedBits::kPresentAndVerified;
  ui_request->unregister_callback =
      base::BindOnce(&EnclaveManager::Unenroll, manager_.GetWeakPtr(),
                     unenroll_future.GetCallback());

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

  EXPECT_FALSE(manager_.IsRegistered());
  histogram_tester.ExpectBucketCount(
      "WebAuthentication.EnclaveTransaction.Result",
      device::enclave::EnclaveTransactionResult::kMissingKey, 1);
}

#endif  // BUILDFLAG(IS_WIN)

#endif  // !defined(MEMORY_SANITIZER)
