blob: d46fa8f13bb170fb87e6f02ffe14fe4ffcabf6c2 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/webauth/authenticator_test_base.h"
#include <memory>
#include <utility>
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/webauth/authenticator_environment.h"
#include "content/browser/webauth/webauth_request_security_checker.h"
#include "content/public/browser/web_authentication_delegate.h"
#include "device/fido/authenticator_selection_criteria.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_discovery_factory.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/public_key_credential_descriptor.h"
#include "device/fido/public_key_credential_params.h"
#include "device/fido/public_key_credential_rp_entity.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "device/fido/virtual_fido_device_factory.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/system/functions.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_MAC)
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/resource/resource_scale_factor.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
#include "chromeos/dbus/u2f/u2f_client.h"
#endif
namespace content {
namespace {
// TestAuthenticatorRequestDelegate is a test fake implementation of the
// AuthenticatorRequestClientDelegate embedder interface.
class TestAuthenticatorRequestDelegate
: public AuthenticatorRequestClientDelegate {
public:
TestAuthenticatorRequestDelegate(
RenderFrameHost* render_frame_host,
base::OnceClosure action_callbacks_registered_callback,
base::OnceClosure started_over_callback,
bool simulate_user_cancelled,
std::optional<bool>* enclave_authenticator_should_be_discovered,
base::flat_set<device::FidoTransportProtocol>* discovered_transports)
: action_callbacks_registered_callback_(
std::move(action_callbacks_registered_callback)),
started_over_callback_(std::move(started_over_callback)),
does_block_request_on_failure_(!started_over_callback_.is_null()),
simulate_user_cancelled_(simulate_user_cancelled),
enclave_authenticator_should_be_discovered_(
enclave_authenticator_should_be_discovered),
discovered_transports_(discovered_transports) {}
TestAuthenticatorRequestDelegate(const TestAuthenticatorRequestDelegate&) =
delete;
TestAuthenticatorRequestDelegate& operator=(
const TestAuthenticatorRequestDelegate&) = delete;
void RegisterActionCallbacks(
base::OnceClosure cancel_callback,
base::OnceClosure immediate_not_found_callback,
base::RepeatingClosure start_over_callback,
AccountPreselectedCallback account_preselected_callback,
PasswordSelectedCallback password_selected_callback,
device::FidoRequestHandlerBase::RequestCallback request_callback,
base::OnceClosure cancel_ui_timeout_callback,
base::RepeatingClosure bluetooth_adapter_power_on_callback,
base::RepeatingCallback<
void(device::FidoRequestHandlerBase::BlePermissionCallback)>
ble_status_callback) override {
ASSERT_TRUE(action_callbacks_registered_callback_)
<< "RegisterActionCallbacks called twice.";
cancel_callback_ = std::move(cancel_callback);
std::move(action_callbacks_registered_callback_).Run();
if (started_over_callback_) {
action_callbacks_registered_callback_ = std::move(started_over_callback_);
start_over_callback_ = start_over_callback;
}
}
void OnTransportAvailabilityEnumerated(
device::FidoRequestHandlerBase::TransportAvailabilityInfo transport_info)
override {
if (discovered_transports_) {
*discovered_transports_ = transport_info.available_transports;
}
// Simulate the behaviour of Chrome's |AuthenticatorRequestDialogModel|
// which shows a specific error when no transports are available and lets
// the user cancel the request.
if (transport_info.available_transports.empty() ||
simulate_user_cancelled_) {
std::move(cancel_callback_).Run();
}
}
bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override {
if (!does_block_request_on_failure_) {
return false;
}
std::move(start_over_callback_).Run();
does_block_request_on_failure_ = false;
return true;
}
void ConfigureDiscoveries(
const url::Origin& origin,
const std::string& rp_id,
RequestSource request_source,
device::FidoRequestType request_type,
std::optional<device::ResidentKeyRequirement> resident_key_requirement,
device::UserVerificationRequirement user_verification_requirement,
std::optional<std::string_view> user_name,
base::span<const device::CableDiscoveryData> pairings_from_extension,
bool is_enclave_authenticator_available,
device::FidoDiscoveryFactory* fido_discovery_factory) override {
if (enclave_authenticator_should_be_discovered_) {
*enclave_authenticator_should_be_discovered_ =
is_enclave_authenticator_available;
}
}
base::OnceClosure action_callbacks_registered_callback_;
base::OnceClosure cancel_callback_;
base::OnceClosure started_over_callback_;
base::OnceClosure start_over_callback_;
bool does_block_request_on_failure_ = false;
bool simulate_user_cancelled_ = false;
bool browser_provided_passkeys_available_ = false;
raw_ptr<std::optional<bool>> enclave_authenticator_should_be_discovered_;
raw_ptr<base::flat_set<device::FidoTransportProtocol>> discovered_transports_;
};
} // namespace
TestWebAuthenticationRequestProxy::Config::Config() = default;
TestWebAuthenticationRequestProxy::Config::~Config() = default;
TestWebAuthenticationRequestProxy::Observations::Observations() = default;
TestWebAuthenticationRequestProxy::Observations::~Observations() = default;
TestWebAuthenticationRequestProxy::TestWebAuthenticationRequestProxy() =
default;
TestWebAuthenticationRequestProxy::~TestWebAuthenticationRequestProxy() {
DCHECK(!HasPendingRequest());
}
bool TestWebAuthenticationRequestProxy::IsActive(
const url::Origin& caller_origin) {
return config_.is_active;
}
WebAuthenticationRequestProxy::RequestId
TestWebAuthenticationRequestProxy::SignalCreateRequest(
const blink::mojom::PublicKeyCredentialCreationOptionsPtr& options,
CreateCallback callback) {
DCHECK(!HasPendingRequest());
current_request_id_++;
observations_.create_requests.push_back(options->Clone());
pending_create_callback_ = std::move(callback);
if (config_.resolve_callbacks) {
RunPendingCreateCallback();
return current_request_id_;
}
return current_request_id_;
}
WebAuthenticationRequestProxy::RequestId
TestWebAuthenticationRequestProxy::SignalGetRequest(
const blink::mojom::PublicKeyCredentialRequestOptionsPtr& options,
GetCallback callback) {
current_request_id_++;
observations_.get_requests.push_back(options->Clone());
pending_get_callback_ = std::move(callback);
if (config_.resolve_callbacks) {
RunPendingGetCallback();
return current_request_id_;
}
return current_request_id_;
}
WebAuthenticationRequestProxy::RequestId
TestWebAuthenticationRequestProxy::SignalIsUvpaaRequest(
IsUvpaaCallback callback) {
DCHECK(!HasPendingRequest());
current_request_id_++;
observations_.num_isuvpaa++;
if (config_.resolve_callbacks) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), config_.is_uvpaa));
return current_request_id_;
}
DCHECK(!pending_is_uvpaa_callback_);
pending_is_uvpaa_callback_ = std::move(callback);
return current_request_id_;
}
void TestWebAuthenticationRequestProxy::CancelRequest(RequestId request_id) {
DCHECK_EQ(request_id, current_request_id_);
observations_.num_cancel++;
if (pending_create_callback_) {
pending_create_callback_.Reset();
}
if (pending_get_callback_) {
pending_get_callback_.Reset();
}
}
void TestWebAuthenticationRequestProxy::RunPendingCreateCallback() {
DCHECK(pending_create_callback_);
auto callback =
config_.request_success
? base::BindOnce(std::move(pending_create_callback_),
current_request_id_, nullptr,
config_.make_credential_response.Clone())
: base::BindOnce(std::move(pending_create_callback_),
current_request_id_,
blink::mojom::WebAuthnDOMExceptionDetails::New(
config_.request_error_name, "message"),
nullptr);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(callback));
}
void TestWebAuthenticationRequestProxy::RunPendingGetCallback() {
DCHECK(pending_get_callback_);
auto callback =
config_.request_success
? base::BindOnce(std::move(pending_get_callback_),
current_request_id_, nullptr,
config_.get_assertion_response.Clone())
: base::BindOnce(std::move(pending_create_callback_),
current_request_id_,
blink::mojom::WebAuthnDOMExceptionDetails::New(
config_.request_error_name, "message"),
nullptr);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(callback));
}
void TestWebAuthenticationRequestProxy::RunPendingIsUvpaaCallback() {
DCHECK(pending_is_uvpaa_callback_);
std::move(pending_is_uvpaa_callback_).Run(config_.is_uvpaa);
}
bool TestWebAuthenticationRequestProxy::HasPendingRequest() {
return pending_create_callback_ || pending_get_callback_ ||
pending_is_uvpaa_callback_;
}
TestWebAuthenticationDelegate::TestWebAuthenticationDelegate() = default;
TestWebAuthenticationDelegate::~TestWebAuthenticationDelegate() = default;
void TestWebAuthenticationDelegate::
IsUserVerifyingPlatformAuthenticatorAvailableOverride(
RenderFrameHost*,
base::OnceCallback<void(std::optional<bool>)> callback) {
std::move(callback).Run(is_uvpaa_override);
}
bool TestWebAuthenticationDelegate::
OverrideCallerOriginAndRelyingPartyIdValidation(
content::BrowserContext* browser_context,
const url::Origin& origin,
const std::string& rp_id) {
return permit_extensions && origin.scheme() == "chrome-extension" &&
origin.host() == rp_id;
}
std::optional<std::string>
TestWebAuthenticationDelegate::MaybeGetRelyingPartyIdOverride(
const std::string& claimed_rp_id,
const url::Origin& caller_origin) {
if (permit_extensions && caller_origin.scheme() == "chrome-extension") {
return caller_origin.Serialize();
}
return std::nullopt;
}
bool TestWebAuthenticationDelegate::ShouldPermitIndividualAttestation(
content::BrowserContext* browser_context,
const url::Origin& caller_origin,
const std::string& relying_party_id) {
return permit_individual_attestation ||
(permit_individual_attestation_for_rp_id.has_value() &&
relying_party_id == *permit_individual_attestation_for_rp_id);
}
bool TestWebAuthenticationDelegate::SupportsResidentKeys(RenderFrameHost*) {
return supports_resident_keys;
}
bool TestWebAuthenticationDelegate::IsFocused(WebContents* web_contents) {
return is_focused;
}
#if BUILDFLAG(IS_MAC)
std::optional<WebAuthenticationDelegate::TouchIdAuthenticatorConfig>
TestWebAuthenticationDelegate::GetTouchIdAuthenticatorConfig(
BrowserContext* browser_context) {
return touch_id_authenticator_config;
}
#endif
WebAuthenticationRequestProxy*
TestWebAuthenticationDelegate::MaybeGetRequestProxy(
content::BrowserContext* browser_context,
const url::Origin& caller_origin) {
return request_proxy && request_proxy->IsActive(caller_origin)
? request_proxy.get()
: nullptr;
}
bool TestWebAuthenticationDelegate::OriginMayUseRemoteDesktopClientOverride(
content::BrowserContext* browser_context,
const url::Origin& caller_origin) {
return caller_origin == remote_desktop_client_override_origin;
}
void TestWebAuthenticationDelegate::BrowserProvidedPasskeysAvailable(
BrowserContext* browser_context,
base::OnceCallback<void(bool)> callback) {
std::move(callback).Run(browser_provided_passkeys_available);
}
TestAuthenticatorContentBrowserClient::TestAuthenticatorContentBrowserClient() =
default;
TestAuthenticatorContentBrowserClient::
~TestAuthenticatorContentBrowserClient() = default;
TestWebAuthenticationDelegate*
TestAuthenticatorContentBrowserClient::GetTestWebAuthenticationDelegate() {
return &web_authentication_delegate;
}
WebAuthenticationDelegate*
TestAuthenticatorContentBrowserClient::GetWebAuthenticationDelegate() {
return &web_authentication_delegate;
}
bool TestAuthenticatorContentBrowserClient::
IsSecurityLevelAcceptableForWebAuthn(content::RenderFrameHost* rfh,
const url::Origin& origin) {
return is_webauthn_security_level_acceptable;
}
std::unique_ptr<AuthenticatorRequestClientDelegate>
TestAuthenticatorContentBrowserClient::GetWebAuthenticationRequestDelegate(
RenderFrameHost* render_frame_host) {
if (return_null_delegate) {
return nullptr;
}
return std::make_unique<TestAuthenticatorRequestDelegate>(
render_frame_host,
action_callbacks_registered_callback
? std::move(action_callbacks_registered_callback)
: base::DoNothing(),
std::move(started_over_callback_), simulate_user_cancelled_,
&enclave_authenticator_should_be_discovered_, &discovered_transports_);
}
AuthenticatorTestBase::AuthenticatorTestBase()
: RenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
AuthenticatorTestBase::~AuthenticatorTestBase() = default;
void AuthenticatorTestBase::SetUpTestSuite() {
#if BUILDFLAG(IS_MAC)
// Load fido_strings, which can be required for exercising the Touch ID
// authenticator.
base::FilePath path;
CHECK(base::PathService::Get(base::DIR_ASSETS, &path));
base::FilePath fido_test_strings =
path.Append(FILE_PATH_LITERAL("fido_test_strings.pak"));
ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
fido_test_strings, ui::kScaleFactorNone);
#endif
}
void AuthenticatorTestBase::SetUp() {
RenderViewHostTestHarness::SetUp();
WebAuthRequestSecurityChecker::UseSystemSharedURLLoaderFactoryForTesting() =
true;
mojo::SetDefaultProcessErrorHandler(base::BindRepeating(
&AuthenticatorTestBase::OnMojoError, base::Unretained(this)));
#if BUILDFLAG(IS_CHROMEOS)
chromeos::TpmManagerClient::InitializeFake();
chromeos::U2FClient::InitializeFake();
#endif
#if BUILDFLAG(IS_WIN)
// Disable the Windows WebAuthn API integration by default. Individual tests
// can modify this.
fake_win_webauthn_api_.set_available(false);
// Prevent `FidoRequestHandlerBase` from doing a system API call, which can
// cause tests to finish early since `RunUntilIdle` won't see it in the task
// queue.
biometrics_override_ =
std::make_unique<device::fido::win::ScopedBiometricsOverride>(false);
#endif
ResetVirtualDevice();
}
void AuthenticatorTestBase::TearDown() {
RenderViewHostTestHarness::TearDown();
WebAuthRequestSecurityChecker::UseSystemSharedURLLoaderFactoryForTesting() =
false;
mojo::SetDefaultProcessErrorHandler(base::NullCallback());
virtual_device_factory_ = nullptr;
AuthenticatorEnvironment::GetInstance()->Reset();
#if BUILDFLAG(IS_CHROMEOS)
chromeos::U2FClient::Shutdown();
chromeos::TpmManagerClient::Shutdown();
#endif
}
void AuthenticatorTestBase::ResetVirtualDevice() {
auto virtual_device_factory =
std::make_unique<device::test::VirtualFidoDeviceFactory>();
virtual_device_factory_ = virtual_device_factory.get();
AuthenticatorEnvironment::GetInstance()
->ReplaceDefaultDiscoveryFactoryForTesting(
std::move(virtual_device_factory));
}
void AuthenticatorTestBase::ReplaceDiscoveryFactory(
std::unique_ptr<device::FidoDiscoveryFactory> device_factory) {
virtual_device_factory_ = nullptr;
AuthenticatorEnvironment::GetInstance()
->ReplaceDefaultDiscoveryFactoryForTesting(std::move(device_factory));
}
void AuthenticatorTestBase::SetMojoErrorHandler(
base::RepeatingCallback<void(const std::string&)> callback) {
mojo_error_handler_ = callback;
}
void AuthenticatorTestBase::OnMojoError(const std::string& error) {
if (mojo_error_handler_) {
mojo_error_handler_.Run(error);
return;
}
FAIL() << "Unhandled mojo error: " << error;
}
device::PublicKeyCredentialUserEntity GetTestPublicKeyCredentialUserEntity() {
device::PublicKeyCredentialUserEntity entity;
entity.display_name = "User A. Name";
std::vector<uint8_t> id(32, 0x0A);
entity.id = id;
entity.name = "username@example.com";
return entity;
}
device::AuthenticatorSelectionCriteria GetTestAuthenticatorSelectionCriteria() {
return device::AuthenticatorSelectionCriteria(
device::AuthenticatorAttachment::kAny,
device::ResidentKeyRequirement::kDiscouraged,
device::UserVerificationRequirement::kPreferred);
}
std::vector<device::PublicKeyCredentialDescriptor> GetTestCredentials(
size_t num_credentials) {
std::vector<device::PublicKeyCredentialDescriptor> descriptors;
for (size_t i = 0; i < num_credentials; i++) {
DCHECK(i <= std::numeric_limits<uint8_t>::max());
std::vector<uint8_t> id(kTestCredentialIdLength, static_cast<uint8_t>(i));
base::flat_set<device::FidoTransportProtocol> transports{
device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
device::FidoTransportProtocol::kBluetoothLowEnergy};
descriptors.emplace_back(device::CredentialType::kPublicKey, std::move(id),
std::move(transports));
}
return descriptors;
}
std::vector<device::PublicKeyCredentialParams::CredentialInfo>
GetTestPublicKeyCredentialParameters(int32_t algorithm_identifier) {
std::vector<device::PublicKeyCredentialParams::CredentialInfo> parameters;
device::PublicKeyCredentialParams::CredentialInfo fake_parameter;
fake_parameter.type = device::CredentialType::kPublicKey;
fake_parameter.algorithm = algorithm_identifier;
parameters.push_back(std::move(fake_parameter));
return parameters;
}
device::PublicKeyCredentialRpEntity GetTestPublicKeyCredentialRPEntity() {
device::PublicKeyCredentialRpEntity entity;
entity.id = std::string(kTestRelyingPartyId);
entity.name = "TestRP@example.com";
return entity;
}
PublicKeyCredentialCreationOptionsPtr
GetTestPublicKeyCredentialCreationOptions() {
auto options = PublicKeyCredentialCreationOptions::New();
options->relying_party = GetTestPublicKeyCredentialRPEntity();
options->user = GetTestPublicKeyCredentialUserEntity();
options->public_key_parameters = GetTestPublicKeyCredentialParameters(
static_cast<int32_t>(device::CoseAlgorithmIdentifier::kEs256));
options->challenge.assign(32, 0x0A);
options->timeout = base::Minutes(1);
options->authenticator_selection = GetTestAuthenticatorSelectionCriteria();
return options;
}
PublicKeyCredentialRequestOptionsPtr
GetTestPublicKeyCredentialRequestOptions() {
auto options = PublicKeyCredentialRequestOptions::New();
options->extensions = AuthenticationExtensionsClientInputs::New();
options->relying_party_id = std::string(kTestRelyingPartyId);
options->challenge = std::vector<uint8_t>(32, 0x0A);
options->timeout = base::Minutes(1);
options->user_verification = device::UserVerificationRequirement::kPreferred;
options->allow_credentials = GetTestCredentials();
return options;
}
GetCredentialOptionsPtr GetTestGetCredentialOptions() {
auto options = GetCredentialOptions::New();
options->public_key = GetTestPublicKeyCredentialRequestOptions();
return options;
}
} // namespace content