910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/fido/win/authenticator.h"

#include <stdint.h>

#include <memory>
#include <vector>

#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.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/features.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_test_data.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/fido_types.h"
#include "device/fido/public_key_credential_descriptor.h"
#include "device/fido/public_key_credential_rp_entity.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "device/fido/win/fake_webauthn_api.h"
#include "device/fido/win/util.h"
#include "device/fido/win/webauthn_api.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace device {
namespace {

using MakeCredentialFuture =
    base::test::TestFuture<MakeCredentialStatus,
                           std::optional<AuthenticatorMakeCredentialResponse>>;

using GetAssertionFuture =
    base::test::TestFuture<GetAssertionStatus,
                           std::vector<AuthenticatorGetAssertionResponse>>;

using GetCredentialFuture =
    base::test::TestFuture<std::vector<DiscoverableCredentialMetadata>,
                           FidoRequestHandlerBase::RecognizedCredential>;

using EnumeratePlatformCredentialsFuture =
    base::test::TestFuture<std::vector<DiscoverableCredentialMetadata>>;

const std::vector<uint8_t> kCredentialId = {1, 2, 3, 4};
const std::vector<uint8_t> kCredentialId2 = {9, 0, 1, 2};
constexpr char kRpId[] = "project-altdeus.example.com";
constexpr char kRpId2[] = "meteora.example.com";
const std::vector<uint8_t> kUserId = {5, 6, 7, 8};
constexpr char kUserName[] = "unit-aarc-noa";
constexpr char kUserDisplayName[] = "Noa";
constexpr char kProviderName[] = "Windows Provider";
const std::vector<uint8_t> kLargeBlob = {'b', 'l', 'o', 'b'};
const std::vector<uint8_t> kUserId2 = {1, 1, 1, 1};
constexpr char kUserName2[] = "chloe";
constexpr char kUserDisplayName2[] = "Chloe";

class WinAuthenticatorTest : public testing::Test,
                             WinWebAuthnApiAuthenticator::TestObserver {
 public:
  WinAuthenticatorTest() {
    scoped_feature_list_.InitWithFeatures(
        {device::kWebAuthnHelloSignal,
         device::kWebAuthenticationFixWindowsHelloRdp},
        /*disabled_features=*/{});
  }

  void SetUp() override {
    fake_webauthn_api_ = std::make_unique<FakeWinWebAuthnApi>();
    fake_webauthn_api_->set_supports_silent_discovery(true);
    authenticator_ = std::make_unique<WinWebAuthnApiAuthenticator>(
        /*current_window=*/nullptr, fake_webauthn_api_.get());
    WinWebAuthnApiAuthenticator::SetGlobalObserverForTesting(this);
  }

  void TearDown() override {
    WinWebAuthnApiAuthenticator::SetGlobalObserverForTesting(nullptr);
  }

  void SetVersion(int version) {
    fake_webauthn_api_->set_version(version);
    // `WinWebAuthnApiAuthenticator` does not expect the webauthn.dll version to
    // change during its lifetime, thus needs to be recreated for each version
    // change.
    authenticator_ = std::make_unique<WinWebAuthnApiAuthenticator>(
        /*current_window=*/nullptr, fake_webauthn_api_.get());
  }

  void WaitForSignalUnknownCredential() {
    signal_unknown_credential_run_loop_.Run();
  }

  void WaitForSignalAllAcceptedCredentials() {
    signal_all_accepted_credentials_run_loop_.Run();
  }

  // WinWebAuthnApiAuthenticator::TestObserver:
  void OnSignalUnknownCredential() override {
    signal_unknown_credential_run_loop_.Quit();
  }

  void OnSignalAllAcceptedCredentials() override {
    signal_all_accepted_credentials_run_loop_.Quit();
  }

 protected:
  std::unique_ptr<FidoAuthenticator> authenticator_;
  std::unique_ptr<FakeWinWebAuthnApi> fake_webauthn_api_;
  base::test::TaskEnvironment task_environment;
  base::RunLoop signal_unknown_credential_run_loop_;
  base::RunLoop signal_all_accepted_credentials_run_loop_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Tests getting credential information for an empty allow-list request that has
// valid credentials on a Windows version that supports silent discovery.
TEST_F(WinAuthenticatorTest,
       GetCredentialInformationForRequest_HasCredentials) {
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(kCredentialId, rp, user,
                                                   kProviderName);

  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  DiscoverableCredentialMetadata expected = DiscoverableCredentialMetadata(
      AuthenticatorType::kWinNative, kRpId, kCredentialId, user,
      /*provider_name=*/std::nullopt);
  EXPECT_EQ(std::get<0>(future.Get()),
            std::vector<DiscoverableCredentialMetadata>{expected});
  EXPECT_EQ(
      std::get<1>(future.Get()),
      FidoRequestHandlerBase::RecognizedCredential::kHasRecognizedCredential);
  EXPECT_FALSE(fake_webauthn_api_->last_get_credentials_options()
                   ->bBrowserInPrivateMode);
}

// Tests a request with the off the record flag on passes the
// bBrowserInPrivateMode option to the Windows API.
TEST_F(WinAuthenticatorTest, GetCredentialInformationForRequest_Incognito) {
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);

  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
  CtapGetAssertionOptions options;
  options.is_off_the_record_context = true;
  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), std::move(options), future.GetCallback());
  EXPECT_TRUE(future.Wait());
  EXPECT_TRUE(fake_webauthn_api_->last_get_credentials_options()
                  ->bBrowserInPrivateMode);
}

// Tests getting credential information for an empty allow-list request that
// does not have valid credentials on a Windows version that supports silent
// discovery.
TEST_F(WinAuthenticatorTest, GetCredentialInformationForRequest_NoCredentials) {
  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  EXPECT_EQ(std::get<0>(future.Get()),
            std::vector<DiscoverableCredentialMetadata>{});
  EXPECT_EQ(
      std::get<1>(future.Get()),
      FidoRequestHandlerBase::RecognizedCredential::kNoRecognizedCredential);
}

// Tests getting credential information for an empty allow-list request when
// under RDP. Windows will report that there are no credentials, but the
// Authenticator should relay this as an unknown result.
TEST_F(WinAuthenticatorTest, GetCredentialInformationForRequest_Rdp) {
  fido::win::ScopedIsRdpSessionOverride scoped_rdp_override(true);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(kCredentialId, rp, user,
                                                   kProviderName);
  fake_webauthn_api_->set_simulate_rdp(true);

  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  EXPECT_EQ(std::get<0>(future.Get()),
            std::vector<DiscoverableCredentialMetadata>{});
  EXPECT_EQ(std::get<1>(future.Get()),
            FidoRequestHandlerBase::RecognizedCredential::kUnknown);
}

// Tests the authenticator handling of an unexpected error from the Windows API.
TEST_F(WinAuthenticatorTest, GetCredentialInformationForRequest_UnknownError) {
  fake_webauthn_api_->set_hresult(ERROR_NOT_SUPPORTED);
  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  EXPECT_EQ(std::get<0>(future.Get()),
            std::vector<DiscoverableCredentialMetadata>{});
  EXPECT_EQ(std::get<1>(future.Get()),
            FidoRequestHandlerBase::RecognizedCredential::kUnknown);
}

// Tests the authenticator handling of attempting to get credential information
// for a version of the Windows API that does not support silent discovery.
TEST_F(WinAuthenticatorTest, GetCredentialInformationForRequest_Unsupported) {
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(kCredentialId, rp, user,
                                                   kProviderName);
  fake_webauthn_api_->set_supports_silent_discovery(false);

  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  DiscoverableCredentialMetadata expected = DiscoverableCredentialMetadata(
      AuthenticatorType::kWinNative, kRpId, kCredentialId, user,
      /*provider_name=*/std::nullopt);
  EXPECT_EQ(std::get<0>(future.Get()),
            std::vector<DiscoverableCredentialMetadata>{});
  EXPECT_EQ(std::get<1>(future.Get()),
            FidoRequestHandlerBase::RecognizedCredential::kUnknown);
}

// Tests that for non empty allow-list requests with a matching discoverable
// credential, the authenticator returns a credential list with only matching
// credentials.
TEST_F(WinAuthenticatorTest,
       GetCredentialInformationForRequest_NonEmptyAllowList_Found) {
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user1(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(kCredentialId, rp, user1,
                                                   kProviderName);

  PublicKeyCredentialUserEntity user2(kUserId2, kUserName2, kUserDisplayName2);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId2, rp, std::move(user2), kProviderName);

  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
  request.allow_list.emplace_back(CredentialType::kPublicKey, kCredentialId);
  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  DiscoverableCredentialMetadata expected = DiscoverableCredentialMetadata(
      AuthenticatorType::kWinNative, kRpId, kCredentialId, user1,
      /*provider_name=*/std::nullopt);
  EXPECT_THAT(std::get<0>(future.Get()), testing::ElementsAre(expected));
  EXPECT_EQ(
      std::get<1>(future.Get()),
      FidoRequestHandlerBase::RecognizedCredential::kHasRecognizedCredential);
}

// Tests that for non empty allow-list requests without a matching discoverable
// credential, the authenticator returns an empty credential list and reports no
// credential availability.
TEST_F(WinAuthenticatorTest,
       GetCredentialInformationForRequest_NonEmptyAllowList_NotMatching) {
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, rp, std::move(user), kProviderName);

  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
  request.allow_list.emplace_back(CredentialType::kPublicKey, kCredentialId2);
  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  EXPECT_EQ(std::get<0>(future.Get()),
            std::vector<DiscoverableCredentialMetadata>{});
  EXPECT_EQ(
      std::get<1>(future.Get()),
      FidoRequestHandlerBase::RecognizedCredential::kNoRecognizedCredential);
}

// Tests that for non empty allow-list requests without an internal transport
// credential, the authenticator returns an empty credential list and reports no
// credential availability, even if silent discovery is not supported.
TEST_F(WinAuthenticatorTest,
       GetCredentialInformationForRequest_NonEmptyAllowList_NoInternal) {
  fake_webauthn_api_->set_supports_silent_discovery(false);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");

  PublicKeyCredentialDescriptor credential(CredentialType::kPublicKey,
                                           kCredentialId2);
  credential.transports = {FidoTransportProtocol::kUsbHumanInterfaceDevice,
                           FidoTransportProtocol::kHybrid};
  request.allow_list.emplace_back(std::move(credential));

  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  EXPECT_EQ(std::get<0>(future.Get()),
            std::vector<DiscoverableCredentialMetadata>{});
  EXPECT_EQ(
      std::get<1>(future.Get()),
      FidoRequestHandlerBase::RecognizedCredential::kNoRecognizedCredential);
}

// Tests that for non empty allow-list requests with an internal transport
// credential, the authenticator returns an empty credential list reports
// unknown credential availability when silent discovery is not supported.
TEST_F(WinAuthenticatorTest,
       GetCredentialInformationForRequest_NonEmptyAllowList_Internal) {
  fake_webauthn_api_->set_supports_silent_discovery(false);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");

  PublicKeyCredentialDescriptor credential(CredentialType::kPublicKey,
                                           kCredentialId2);
  credential.transports = {FidoTransportProtocol::kInternal,
                           FidoTransportProtocol::kHybrid};
  request.allow_list.emplace_back(std::move(credential));

  GetCredentialFuture future;
  authenticator_->GetPlatformCredentialInfoForRequest(
      std::move(request), CtapGetAssertionOptions(), future.GetCallback());
  EXPECT_TRUE(future.Wait());

  EXPECT_EQ(std::get<0>(future.Get()),
            std::vector<DiscoverableCredentialMetadata>{});
  EXPECT_EQ(std::get<1>(future.Get()),
            FidoRequestHandlerBase::RecognizedCredential::kUnknown);
}

TEST_F(WinAuthenticatorTest, EnumeratePlatformCredentials_NotSupported) {
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(kCredentialId, rp, user,
                                                   kProviderName);
  fake_webauthn_api_->set_supports_silent_discovery(false);

  base::test::TestFuture<std::vector<DiscoverableCredentialMetadata>> future;
  WinWebAuthnApiAuthenticator::EnumeratePlatformCredentials(
      fake_webauthn_api_.get(), future.GetCallback());

  while (!future.IsReady()) {
    base::RunLoop().RunUntilIdle();
  }

  EXPECT_TRUE(future.Get().empty());
}

TEST_F(WinAuthenticatorTest, EnumeratePlatformCredentials_Supported) {
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(kCredentialId, rp, user,
                                                   kProviderName);
  fake_webauthn_api_->set_supports_silent_discovery(true);

  base::test::TestFuture<std::vector<DiscoverableCredentialMetadata>> future;
  WinWebAuthnApiAuthenticator::EnumeratePlatformCredentials(
      fake_webauthn_api_.get(), future.GetCallback());

  while (!future.IsReady()) {
    base::RunLoop().RunUntilIdle();
  }

  std::vector<DiscoverableCredentialMetadata> creds = future.Take();
  ASSERT_EQ(creds.size(), 1u);
  const DiscoverableCredentialMetadata& cred = creds[0];
  EXPECT_EQ(cred.source, AuthenticatorType::kWinNative);
  EXPECT_EQ(cred.rp_id, kRpId);
  EXPECT_EQ(cred.cred_id, kCredentialId);
  EXPECT_EQ(cred.user.name, kUserName);
  EXPECT_EQ(cred.user.display_name, kUserDisplayName);
  EXPECT_EQ(cred.provider_name, kProviderName);
}

TEST_F(WinAuthenticatorTest, MakeCredentialLargeBlob) {
  enum Availability : bool {
    kNotAvailable = false,
    kAvailable = true,
  };

  enum Result : bool {
    kDoesNotHaveLargeBlob = false,
    kHasLargeBlob = true,
  };

  struct LargeBlobTestCase {
    LargeBlobSupport requirement;
    Availability availability;
    Result result;
  };

  std::array<LargeBlobTestCase, 5> test_cases = {{
      {LargeBlobSupport::kNotRequested, kAvailable, kDoesNotHaveLargeBlob},
      {LargeBlobSupport::kNotRequested, kNotAvailable, kDoesNotHaveLargeBlob},
      {LargeBlobSupport::kPreferred, kAvailable, kHasLargeBlob},
      {LargeBlobSupport::kPreferred, kNotAvailable, kDoesNotHaveLargeBlob},
      {LargeBlobSupport::kRequired, kAvailable, kHasLargeBlob},
      // Calling the Windows API with large blob = required is not allowed if
      // it's not supported by the API version.
  }};

  for (const LargeBlobTestCase& test_case : test_cases) {
    SCOPED_TRACE(testing::Message()
                 << "requirement=" << static_cast<bool>(test_case.requirement)
                 << ", availability="
                 << static_cast<bool>(test_case.availability));

    SetVersion(test_case.availability ? WEBAUTHN_API_VERSION_3
                                      : WEBAUTHN_API_VERSION_2);
    EXPECT_EQ(authenticator_->Options().large_blob_type.has_value(),
              test_case.availability);
    PublicKeyCredentialRpEntity rp("adrestian-empire.com");
    PublicKeyCredentialUserEntity user(std::vector<uint8_t>{1, 2, 3, 4},
                                       "el@adrestian-empire.com", "Edelgard");
    CtapMakeCredentialRequest request(
        test_data::kClientDataJson, rp, user,
        PublicKeyCredentialParams({{CredentialType::kPublicKey, -257}}));
    MakeCredentialOptions options;
    options.large_blob_support = test_case.requirement;
    MakeCredentialFuture future;
    authenticator_->MakeCredential(std::move(request), options,
                                   future.GetCallback());
    EXPECT_TRUE(future.Wait());
    ASSERT_EQ(std::get<0>(future.Get()), MakeCredentialStatus::kSuccess);
    EXPECT_EQ(std::get<1>(future.Get())->large_blob_type.has_value(),
              test_case.result);
  }
}

// Tests that making a credential with attachment=undefined forces the
// attachment to cross-platform if large blob is required.
// This is because largeBlob=required is ignored by the Windows platform
// authenticator at the time of writing (Feb 2023).
TEST_F(WinAuthenticatorTest, MakeCredentialLargeBlobAttachmentUndefined) {
  SetVersion(WEBAUTHN_API_VERSION_3);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  CtapMakeCredentialRequest request(
      test_data::kClientDataJson, rp, user,
      PublicKeyCredentialParams({{CredentialType::kPublicKey, -257}}));
  request.authenticator_attachment = AuthenticatorAttachment::kAny;
  fake_webauthn_api_->set_preferred_attachment(
      WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM);
  MakeCredentialOptions options;
  options.large_blob_support = LargeBlobSupport::kRequired;
  MakeCredentialFuture future;
  authenticator_->MakeCredential(std::move(request), options,
                                 future.GetCallback());
  EXPECT_TRUE(future.Wait());
  ASSERT_EQ(std::get<0>(future.Get()), MakeCredentialStatus::kSuccess);
  EXPECT_TRUE(std::get<1>(future.Get())->large_blob_type.has_value());
  EXPECT_NE(std::get<1>(future.Get())->transport_used,
            FidoTransportProtocol::kInternal);
}

TEST_F(WinAuthenticatorTest, GetAssertionLargeBlobNotSupported) {
  SetVersion(WEBAUTHN_API_VERSION_2);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  {
    // Read large blob.
    CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
    CtapGetAssertionOptions options;
    options.large_blob_read = true;
    GetAssertionFuture future;
    authenticator_->GetAssertion(std::move(request), std::move(options),
                                 future.GetCallback());
    EXPECT_TRUE(future.Wait());
    EXPECT_EQ(std::get<0>(future.Get()), GetAssertionStatus::kSuccess);
    EXPECT_FALSE(std::get<1>(future.Get()).at(0).large_blob.has_value());
  }
  {
    // Write large blob.
    CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
    CtapGetAssertionOptions options;
    options.large_blob_write = kLargeBlob;
    GetAssertionFuture future;
    authenticator_->GetAssertion(std::move(request), std::move(options),
                                 future.GetCallback());
    EXPECT_TRUE(future.Wait());
    EXPECT_EQ(std::get<0>(future.Get()), GetAssertionStatus::kSuccess);
    EXPECT_FALSE(std::get<1>(future.Get()).at(0).large_blob_written);
  }
}

TEST_F(WinAuthenticatorTest, GetAssertionLargeBlobError) {
  SetVersion(WEBAUTHN_API_VERSION_3);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  fake_webauthn_api_->set_large_blob_result(
      WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_SUPPORTED);
  {
    // Read large blob.
    CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
    CtapGetAssertionOptions options;
    options.large_blob_read = true;
    GetAssertionFuture future;
    authenticator_->GetAssertion(std::move(request), std::move(options),
                                 future.GetCallback());
    EXPECT_TRUE(future.Wait());
    EXPECT_EQ(std::get<0>(future.Get()), GetAssertionStatus::kSuccess);
    EXPECT_FALSE(std::get<1>(future.Get()).at(0).large_blob.has_value());
  }
  {
    // Write large blob.
    CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
    CtapGetAssertionOptions options;
    options.large_blob_write = kLargeBlob;
    GetAssertionFuture future;
    authenticator_->GetAssertion(std::move(request), std::move(options),
                                 future.GetCallback());
    EXPECT_TRUE(future.Wait());
    EXPECT_EQ(std::get<0>(future.Get()), GetAssertionStatus::kSuccess);
    EXPECT_FALSE(std::get<1>(future.Get()).at(0).large_blob_written);
  }
}

TEST_F(WinAuthenticatorTest, GetAssertionLargeBlobSuccess) {
  SetVersion(WEBAUTHN_API_VERSION_3);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  {
    // Read large blob.
    CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
    CtapGetAssertionOptions options;
    options.large_blob_read = true;
    GetAssertionFuture future;
    authenticator_->GetAssertion(std::move(request), std::move(options),
                                 future.GetCallback());
    EXPECT_TRUE(future.Wait());
    EXPECT_EQ(std::get<0>(future.Get()), GetAssertionStatus::kSuccess);
    EXPECT_FALSE(std::get<1>(future.Get()).at(0).large_blob.has_value());
    EXPECT_FALSE(std::get<1>(future.Get()).at(0).large_blob_written);
  }
  {
    // Write large blob.
    CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
    CtapGetAssertionOptions options;
    options.large_blob_write = kLargeBlob;
    GetAssertionFuture future;
    authenticator_->GetAssertion(std::move(request), std::move(options),
                                 future.GetCallback());
    EXPECT_TRUE(future.Wait());
    EXPECT_EQ(std::get<0>(future.Get()), GetAssertionStatus::kSuccess);
    EXPECT_FALSE(std::get<1>(future.Get()).at(0).large_blob.has_value());
    EXPECT_TRUE(std::get<1>(future.Get()).at(0).large_blob_written);
  }
  {
    // Read the large blob that was just written.
    CtapGetAssertionRequest request(kRpId, /*client_data_json=*/"");
    CtapGetAssertionOptions options;
    options.large_blob_read = true;
    GetAssertionFuture future;
    authenticator_->GetAssertion(std::move(request), std::move(options),
                                 future.GetCallback());
    EXPECT_TRUE(future.Wait());
    EXPECT_EQ(std::get<0>(future.Get()), GetAssertionStatus::kSuccess);
    EXPECT_TRUE(std::get<1>(future.Get()).at(0).large_blob.has_value());
    EXPECT_EQ(*std::get<1>(future.Get()).at(0).large_blob, kLargeBlob);
    EXPECT_FALSE(std::get<1>(future.Get()).at(0).large_blob_written);
  }
}

// Tests signalling an unknown credential ID for a credential that is not
// present.
TEST_F(WinAuthenticatorTest, SignalUnknownCredential_NotFound) {
  SetVersion(WEBAUTHN_API_VERSION_4);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalUnknownCredential(fake_webauthn_api_.get(),
                                                       kCredentialId2, kRpId);
  WaitForSignalUnknownCredential();
  EXPECT_EQ(fake_webauthn_api_->registrations().size(), 1u);
}

// Tests signalling an unknown credential ID for a credential that exists but
// belongs to a different RP ID.
TEST_F(WinAuthenticatorTest, SignalUnknownCredential_WrongRpId) {
  SetVersion(WEBAUTHN_API_VERSION_4);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalUnknownCredential(fake_webauthn_api_.get(),
                                                       kCredentialId, kRpId2);
  WaitForSignalUnknownCredential();
  EXPECT_EQ(fake_webauthn_api_->registrations().size(), 1u);
}

// Tests signalling an unknown credential ID when the WebAuthn API does not
// support the feature.
TEST_F(WinAuthenticatorTest, SignalUnknownCredential_NotSupported) {
  SetVersion(WEBAUTHN_API_VERSION_3);
  fake_webauthn_api_->set_supports_silent_discovery(false);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalUnknownCredential(fake_webauthn_api_.get(),
                                                       kCredentialId, kRpId);
  WaitForSignalUnknownCredential();
  EXPECT_EQ(fake_webauthn_api_->registrations().size(), 1u);
}

// Tests signalling an unknown credential ID that belongs to Hello. It should be
// deleted.
TEST_F(WinAuthenticatorTest, SignalUnknownCredential) {
  SetVersion(WEBAUTHN_API_VERSION_4);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalUnknownCredential(fake_webauthn_api_.get(),
                                                       kCredentialId, kRpId);
  WaitForSignalUnknownCredential();
  EXPECT_TRUE(fake_webauthn_api_->registrations().empty());
}

// Tests passing a known credential to SignalAllAcceptedCredentials. It should
// not be deleted.
TEST_F(WinAuthenticatorTest, SignalAllAcceptedCredentials_Found) {
  SetVersion(WEBAUTHN_API_VERSION_4);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalAllAcceptedCredentials(
      fake_webauthn_api_.get(), kRpId, kUserId, {kCredentialId});
  WaitForSignalAllAcceptedCredentials();
  EXPECT_EQ(fake_webauthn_api_->registrations().size(), 1u);
}

// Tests passing a user id that doesn't match any known credentials to
// SignalAllAcceptedCredentials. The credentials should not be deleted.
TEST_F(WinAuthenticatorTest, SignalAllAcceptedCredentials_NoMatchingUserId) {
  SetVersion(WEBAUTHN_API_VERSION_4);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalAllAcceptedCredentials(
      fake_webauthn_api_.get(), kRpId, kUserId2, {});
  WaitForSignalAllAcceptedCredentials();
  EXPECT_EQ(fake_webauthn_api_->registrations().size(), 1u);
}

// Tests passing an RP id that doesn't match any known credentials to
// SignalAllAcceptedCredentials. The credentials should not be deleted.
TEST_F(WinAuthenticatorTest, SignalAllAcceptedCredentials_NoMatchingRpId) {
  SetVersion(WEBAUTHN_API_VERSION_4);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalAllAcceptedCredentials(
      fake_webauthn_api_.get(), kRpId2, kUserId, {});
  WaitForSignalAllAcceptedCredentials();
  EXPECT_EQ(fake_webauthn_api_->registrations().size(), 1u);
}

// Tests trying to call SignalAllAcceptedCredentials when not supported by
// Windows Hello.
TEST_F(WinAuthenticatorTest, SignalAllAcceptedCredentials_NotSupported) {
  SetVersion(WEBAUTHN_API_VERSION_3);
  fake_webauthn_api_->set_supports_silent_discovery(false);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalAllAcceptedCredentials(
      fake_webauthn_api_.get(), kRpId, kUserId, {kCredentialId2});
  WaitForSignalAllAcceptedCredentials();
  EXPECT_EQ(fake_webauthn_api_->registrations().size(), 1u);
}

// Tests passing the RP id and user id matching a credential to
// SignalAllAcceptedCredentials, but the list of credential IDs doesn't contain
// it. The credential should be deleted.
TEST_F(WinAuthenticatorTest, SignalAllAcceptedCredentials_NotFound) {
  SetVersion(WEBAUTHN_API_VERSION_4);
  PublicKeyCredentialRpEntity rp(kRpId);
  PublicKeyCredentialUserEntity user(kUserId, kUserName, kUserDisplayName);
  fake_webauthn_api_->InjectDiscoverableCredential(
      kCredentialId, std::move(rp), std::move(user), kProviderName);
  ASSERT_EQ(fake_webauthn_api_->registrations().size(), 1u);

  WinWebAuthnApiAuthenticator::SignalAllAcceptedCredentials(
      fake_webauthn_api_.get(), kRpId, kUserId, {kCredentialId2});
  WaitForSignalAllAcceptedCredentials();
  EXPECT_TRUE(fake_webauthn_api_->registrations().empty());
}

}  // namespace
}  // namespace device