chromium/chrome/browser/webauthn/chrome_authenticator_request_delegate_unittest.cc

// Copyright 2018 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/chrome_authenticator_request_delegate.h"

#include <algorithm>
#include <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/password_manager/chrome_webauthn_credentials_delegate_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_controller.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "chrome/browser/webauthn/passkey_model_factory.h"
#include "chrome/browser/webauthn/webauthn_pref_names.h"
#include "chrome/browser/webauthn/webauthn_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync/base/features.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/test/test_sync_service.h"
#include "components/webauthn/core/browser/passkey_model.h"
#include "components/webauthn/core/browser/test_passkey_model.h"
#include "content/public/browser/authenticator_request_client_delegate.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/web_contents_tester.h"
#include "crypto/scoped_mock_unexportable_key_provider.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/cable/v2_constants.h"
#include "device/fido/discoverable_credential_metadata.h"
#include "device/fido/features.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_discovery_factory.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_types.h"
#include "device/fido/virtual_ctap2_device.h"
#include "device/fido/virtual_fido_device_authenticator.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/permissions/permissions_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

#if BUILDFLAG(IS_WIN)
#include "device/fido/win/authenticator.h"
#include "device/fido/win/fake_webauthn_api.h"
#include "device/fido/win/webauthn_api.h"
#include "third_party/microsoft_webauthn/webauthn.h"
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_MAC)
#include "chrome/test/base/testing_profile.h"
#include "device/fido/mac/authenticator_config.h"
#endif  // BUILDFLAG(IS_MAC)

namespace {

static constexpr char kRelyingPartyID[] =;

TransportAvailabilityInfo;

class Observer : public testing::NiceMock<
                     ChromeAuthenticatorRequestDelegate::TestObserver> {};

class MockCableDiscoveryFactory : public device::FidoDiscoveryFactory {};

class ChromeAuthenticatorRequestDelegateTest
    : public ChromeRenderViewHostTestHarness {};

class TestAuthenticatorModelObserver final
    : public AuthenticatorRequestDialogModel::Observer {};

TEST_F(ChromeAuthenticatorRequestDelegateTest, IndividualAttestation) {}

TEST_F(ChromeAuthenticatorRequestDelegateTest, CableConfiguration) {}

TEST_F(ChromeAuthenticatorRequestDelegateTest, NoExtraDiscoveriesWithoutUI) {}

TEST_F(ChromeAuthenticatorRequestDelegateTest, ConditionalUI) {}

constexpr char kExtensionId[] =;
constexpr char kExtensionOrigin[] =;

PatternRpIdPair;

constexpr PatternRpIdPair kValidRelyingPartyTestCases[] =;

constexpr PatternRpIdPair kInvalidRelyingPartyTestCases[] =;

// Tests that an extension origin can claim relying party IDs it has permissions
// for.
TEST_F(ChromeAuthenticatorRequestDelegateTest,
       OverrideValidateDomainAndRelyingPartyIDTest_ExtensionValidCases) {}

// Tests that an extension origin cannot claim relying party IDs it does not
// have permissions for.
TEST_F(ChromeAuthenticatorRequestDelegateTest,
       OverrideValidateDomainAndRelyingPartyIDTest_ExtensionInvalidCases) {}

// Tests that OverrideCallerOriginAndRelyingPartyIdValidation returns false for
// chrome-extension origins that don't match an active extension.
TEST_F(ChromeAuthenticatorRequestDelegateTest,
       OverrideValidateDomainAndRelyingPartyIDTest_ExtensionNotFound) {}

// Tests that OverrideCallerOriginAndRelyingPartyIdValidation returns false for
// web origins.
TEST_F(ChromeAuthenticatorRequestDelegateTest,
       OverrideValidateDomainAndRelyingPartyIDTest_WebOrigin) {}

TEST_F(ChromeAuthenticatorRequestDelegateTest, MaybeGetRelyingPartyIdOverride) {}

// Tests that attestation is returned if the virtual environment is enabled and
// the UI is disabled.
// Regression test for crbug.com/1342458
TEST_F(ChromeAuthenticatorRequestDelegateTest, VirtualEnvironmentAttestation) {}

// Tests that synced GPM passkeys are injected in the transport availability
// info.
TEST_F(ChromeAuthenticatorRequestDelegateTest, GpmPasskeys) {}

// Tests that synced GPM passkeys are not discovered if there are no sync paired
// phones.
TEST_F(ChromeAuthenticatorRequestDelegateTest, GpmPasskeys_NoSyncPairedPhones) {}

// Tests that shadowed GPM passkeys are not discovered.
TEST_F(ChromeAuthenticatorRequestDelegateTest, GpmPasskeys_ShadowedPasskeys) {}

TEST_F(ChromeAuthenticatorRequestDelegateTest, FilterGoogleComPasskeys) {}

class EnclaveAuthenticatorRequestDelegateTest
    : public ChromeAuthenticatorRequestDelegateTest {};

// ChromeOS delegates this logic to a ChromeOS-specific service.

#if !BUILDFLAG(IS_CHROMEOS)

TEST_F(EnclaveAuthenticatorRequestDelegateTest,
       BrowserProvidedPasskeysAvailable) {}

#endif  // !BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_MAC)
std::string TouchIdMetadataSecret(ChromeWebAuthenticationDelegate& delegate,
                                  content::BrowserContext* browser_context) {
  return delegate.GetTouchIdAuthenticatorConfig(browser_context)
      ->metadata_secret;
}

TEST_F(ChromeAuthenticatorRequestDelegateTest, TouchIdMetadataSecret) {
  ChromeWebAuthenticationDelegate delegate;
  std::string secret = TouchIdMetadataSecret(delegate, GetBrowserContext());
  EXPECT_EQ(secret.size(), 32u);
  // The secret should be stable.
  EXPECT_EQ(secret, TouchIdMetadataSecret(delegate, GetBrowserContext()));
}

TEST_F(ChromeAuthenticatorRequestDelegateTest,
       TouchIdMetadataSecret_EqualForSameProfile) {
  // Different delegates on the same BrowserContext (Profile) should return
  // the same secret.
  ChromeWebAuthenticationDelegate delegate1;
  ChromeWebAuthenticationDelegate delegate2;
  EXPECT_EQ(TouchIdMetadataSecret(delegate1, GetBrowserContext()),
            TouchIdMetadataSecret(delegate2, GetBrowserContext()));
}

TEST_F(ChromeAuthenticatorRequestDelegateTest,
       TouchIdMetadataSecret_NotEqualForDifferentProfiles) {
  // Different profiles have different secrets.
  auto other_browser_context = CreateBrowserContext();
  ChromeWebAuthenticationDelegate delegate;
  EXPECT_NE(TouchIdMetadataSecret(delegate, GetBrowserContext()),
            TouchIdMetadataSecret(delegate, other_browser_context.get()));
  // Ensure this second secret is actually valid.
  EXPECT_EQ(
      32u, TouchIdMetadataSecret(delegate, other_browser_context.get()).size());
}

#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)

// Tests that ShouldReturnAttestation() returns with true if |authenticator|
// is the Windows native WebAuthn API with WEBAUTHN_API_VERSION_2 or higher,
// where Windows prompts for attestation in its own native UI.
//
// Ideally, this would also test the inverse case, i.e. that with
// WEBAUTHN_API_VERSION_1 Chrome's own attestation prompt is shown. However,
// there seems to be no good way to test AuthenticatorRequestDialogController
// UI.
TEST_F(ChromeAuthenticatorRequestDelegateTest, ShouldPromptForAttestationWin) {
  ::device::FakeWinWebAuthnApi win_webauthn_api;
  win_webauthn_api.set_version(WEBAUTHN_API_VERSION_2);
  ::device::WinWebAuthnApiAuthenticator authenticator(
      /*current_window=*/nullptr, &win_webauthn_api);

  base::test::TestFuture<bool> future;
  ChromeAuthenticatorRequestDelegate delegate(main_rfh());
  delegate.ShouldReturnAttestation(kRelyingPartyID, &authenticator,
                                   /*is_enterprise_attestation=*/false,
                                   future.GetCallback());
  EXPECT_TRUE(future.Wait());
  EXPECT_EQ(future.Get(), true);
}

#endif  // BUILDFLAG(IS_WIN)

class OriginMayUseRemoteDesktopClientOverrideTest
    : public ChromeAuthenticatorRequestDelegateTest {};

TEST_F(OriginMayUseRemoteDesktopClientOverrideTest,
       RemoteProxiedRequestsAllowedPolicy) {}

TEST_F(OriginMayUseRemoteDesktopClientOverrideTest,
       OriginMayUseRemoteDesktopClientOverrideAdditionalOriginSwitch) {}

}  // namespace

#if BUILDFLAG(IS_MAC)

// These test functions are outside of the anonymous namespace so that
// `FRIEND_TEST_ALL_PREFIXES` works to let them test private functions.

class ChromeAuthenticatorRequestDelegatePrivateTest : public testing::Test {
  // A `BrowserTaskEnvironment` needs to be in-scope in order to create a
  // `TestingProfile`.
  content::BrowserTaskEnvironment task_environment_;
};

TEST_F(ChromeAuthenticatorRequestDelegatePrivateTest, DaysSinceDate) {
  const base::Time now = base::Time::FromTimeT(1691188997);  // 2023-08-04
  const struct {
    char input[16];
    std::optional<int> expected_result;
  } kTestCases[] = {
      {"", std::nullopt},          //
      {"2023-08-", std::nullopt},  //
      {"2023-08-04", 0},           //
      {"2023-08-03", 1},           //
      {"2023-8-3", 1},             //
      {"2023-07-04", 31},          //
      {"2001-11-23", 7924},        //
  };

  for (const auto& test : kTestCases) {
    SCOPED_TRACE(test.input);
    const std::optional<int> result =
        ChromeAuthenticatorRequestDelegate::DaysSinceDate(test.input, now);
    EXPECT_EQ(result, test.expected_result);
  }
}

TEST_F(ChromeAuthenticatorRequestDelegatePrivateTest, GetICloudKeychainPref) {
  TestingProfile profile;

  // We use a boolean preference as a tristate, so it's worth checking that
  // an unset preference is recognised as such.
  EXPECT_FALSE(ChromeAuthenticatorRequestDelegate::GetICloudKeychainPref(
                   profile.GetPrefs())
                   .has_value());
  profile.GetPrefs()->SetBoolean(prefs::kCreatePasskeysInICloudKeychain, true);
  EXPECT_EQ(*ChromeAuthenticatorRequestDelegate::GetICloudKeychainPref(
                profile.GetPrefs()),
            true);
}

TEST_F(ChromeAuthenticatorRequestDelegatePrivateTest,
       ShouldCreateInICloudKeychain) {
  // Safety check that SPC requests never default to iCloud Keychain.
  EXPECT_FALSE(ChromeAuthenticatorRequestDelegate::ShouldCreateInICloudKeychain(
      ChromeAuthenticatorRequestDelegate::RequestSource::
          kSecurePaymentConfirmation,
      /*is_active_profile_authenticator_user=*/false,
      /*has_icloud_drive_enabled=*/true, /*request_is_for_google_com=*/true,
      /*preference=*/true));

  // For the valid request type, the preference should be controlling if set.
  for (const bool preference : {false, true}) {
    EXPECT_EQ(preference,
              ChromeAuthenticatorRequestDelegate::ShouldCreateInICloudKeychain(
                  ChromeAuthenticatorRequestDelegate::RequestSource::
                      kWebAuthentication,
                  /*is_active_profile_authenticator_user=*/false,
                  /*has_icloud_drive_enabled=*/true,
                  /*request_is_for_google_com=*/true,
                  /*preference=*/preference));

    // Otherwise the default is controlled by several feature flags. Testing
    // them would just be a change detector.
  }
}

#endif