blob: 41f0cf253616e7fc9f5c069297040745cfab00d9 [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 "chrome/browser/webauthn/enclave_authenticator_browsertest_base.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/test_mock_time_task_runner.h"
#include "build/buildflag.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/webauthn/enclave_manager_factory.h"
#include "chrome/browser/webauthn/fake_recovery_key_store.h"
#include "chrome/browser/webauthn/fake_security_domain_service.h"
#include "chrome/browser/webauthn/gpm_enclave_controller.h"
#include "chrome/browser/webauthn/passkey_model_factory.h"
#include "chrome/browser/webauthn/test_util.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/os_crypt/sync/os_crypt_mocker.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_service_impl.h"
#include "components/trusted_vault/test/mock_trusted_vault_throttling_connection.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "components/webauthn/core/browser/passkey_model.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test_utils.h"
#include "crypto/scoped_fake_unexportable_key_provider.h"
#include "crypto/scoped_fake_user_verifying_key_provider.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/fido/enclave/enclave_protocol_utils.h"
#include "device/fido/features.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.h"
#include "device/fido/mac/fake_icloud_keychain.h"
#include "device/fido/mac/util.h"
#endif // BUILDFLAG(IS_MAC)
namespace {
static constexpr char kIsUVPAA[] = R"((() => {
window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().
then(result => window.domAutomationController.send('IsUVPAA: ' + result),
error => window.domAutomationController.send('error ' + error));
})())";
} // namespace
// Helper struct for managing a temporary directory.
struct TempDir {
public:
TempDir() { CHECK(dir_.CreateUniqueTempDir()); }
base::FilePath GetPath() const { return dir_.GetPath(); }
private:
base::ScopedTempDir dir_;
};
EnclaveAuthenticatorTestBase::EnclaveAuthenticatorTestBase()
: SyncTest(SINGLE_CLIENT),
timer_task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>()),
temp_dir_(std::make_unique<TempDir>()),
process_and_port_(StartWebAuthnEnclave(temp_dir_->GetPath())),
enclave_override_(TestWebAuthnEnclaveIdentity(process_and_port_.second)),
security_domain_service_(FakeSecurityDomainService::New(kSecretVersion)),
#if BUILDFLAG(IS_WIN)
fake_webauthn_dll_(std::make_unique<device::FakeWinWebAuthnApi>()),
webauthn_dll_override_(
std::make_unique<device::WinWebAuthnApi::ScopedOverride>(
fake_webauthn_dll_.get())),
#endif
recovery_key_store_(FakeRecoveryKeyStore::New()),
fake_hw_provider_(
std::make_unique<crypto::ScopedFakeUnexportableKeyProvider>()) {
#if BUILDFLAG(IS_WIN)
fake_webauthn_dll_->set_available(false);
biometrics_override_ =
std::make_unique<device::fido::win::ScopedBiometricsOverride>(false);
#elif BUILDFLAG(IS_MAC)
biometrics_override_ =
std::make_unique<device::fido::mac::ScopedBiometricsOverride>(false);
if (__builtin_available(macOS 13.5, *)) {
fake_icloud_keychain_ = device::fido::icloud_keychain::NewFake();
}
scoped_icloud_drive_override_ = OverrideICloudDriveEnabled(false);
#endif
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{device::kWebAuthnLargeBlobForGPM,
device::kWebAuthnSignalApiHidePasskeys,
device::kWebAuthnWrapCohortData},
/*disabled_features=*/{});
OSCryptMocker::SetUp();
scoped_vmodule_.InitWithSwitches("device_event_log_impl=2");
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_uv_provider_.emplace<crypto::ScopedNullUserVerifyingKeyProvider>();
bluetooth_values_for_testing_ =
device::BluetoothAdapterFactory::Get()->InitGlobalOverrideValues();
bluetooth_values_for_testing_->SetLESupported(false);
}
EnclaveAuthenticatorTestBase::~EnclaveAuthenticatorTestBase() {
EnclaveManagerFactory::SetUrlLoaderFactoryForTesting(nullptr);
CHECK(process_and_port_.first.Terminate(/*exit_code=*/1, /*wait=*/true));
OSCryptMocker::TearDown();
}
base::FilePath EnclaveAuthenticatorTestBase::GetTempDirPath() {
return temp_dir_->GetPath();
}
void EnclaveAuthenticatorTestBase::SetUpCommandLine(
base::CommandLine* command_line) {
SyncTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
void EnclaveAuthenticatorTestBase::SetUp() {
ASSERT_TRUE(https_server_.InitializeAndListen());
EnclaveManagerFactory::SetUrlLoaderFactoryForTesting(
url_loader_factory_.GetSafeWeakWrapper().get());
SyncTest::SetUp();
}
void EnclaveAuthenticatorTestBase::SetUpInProcessBrowserTestFixture() {
SyncTest::SetUpInProcessBrowserTestFixture();
subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(
base::BindRepeating([](content::BrowserContext* context) {
IdentityTestEnvironmentProfileAdaptor::
SetIdentityTestEnvironmentFactoriesOnBrowserContext(
context);
}));
}
void EnclaveAuthenticatorTestBase::SetUpOnMainThread() {
SyncTest::SetUpOnMainThread();
identity_test_env_adaptor_ =
std::make_unique<IdentityTestEnvironmentProfileAdaptor>(
browser()->profile());
identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
sync_harness_ = SyncServiceImplHarness::Create(
browser()->profile(), SyncServiceImplHarness::SigninType::FAKE_SIGNIN);
if (sync_feature_enabled_) {
ASSERT_TRUE(sync_harness_->SetupSync());
} else {
ASSERT_TRUE(sync_harness_->SignInPrimaryAccount());
}
syncer::SyncServiceImpl* sync_service =
SyncServiceFactory::GetAsSyncServiceImplForProfileForTesting(
browser()->profile());
ASSERT_EQ(kSyncEmail, sync_service->GetAccountInfo().email);
sync_service->GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/{syncer::UserSelectableType::kPasswords});
https_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
https_server_.StartAcceptingConnections();
host_resolver()->AddRule("*", "127.0.0.1");
}
void EnclaveAuthenticatorTestBase::TearDownOnMainThread() {
identity_test_env_adaptor_.reset();
SyncTest::TearDownOnMainThread();
}
signin::IdentityTestEnvironment*
EnclaveAuthenticatorTestBase::identity_test_env() {
return identity_test_env_adaptor_->identity_test_env();
}
webauthn::PasskeyModel* EnclaveAuthenticatorTestBase::passkey_model() {
return PasskeyModelFactory::GetInstance()->GetForProfile(
browser()->profile());
}
void EnclaveAuthenticatorTestBase::EnableUVKeySupport(
bool fake_hardware_backing) {
fake_uv_provider_.emplace<crypto::ScopedFakeUserVerifyingKeyProvider>(
fake_hardware_backing);
}
bool EnclaveAuthenticatorTestBase::IsUVPAA() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::DOMMessageQueue message_queue(web_contents);
content::ExecuteScriptAsync(web_contents, kIsUVPAA);
std::string script_result;
CHECK(message_queue.WaitForMessage(&script_result));
if (script_result == "\"IsUVPAA: true\"") {
return true;
} else if (script_result == "\"IsUVPAA: false\"") {
return false;
}
NOTREACHED() << "unexpected IsUVPAA result: " << script_result;
}
void EnclaveAuthenticatorTestBase::SetBiometricsEnabled(bool enabled) {
#if BUILDFLAG(IS_MAC)
biometrics_override_.reset();
biometrics_override_ =
std::make_unique<device::fido::mac::ScopedBiometricsOverride>(enabled);
#elif BUILDFLAG(IS_WIN)
biometrics_override_.reset();
biometrics_override_ =
std::make_unique<device::fido::win::ScopedBiometricsOverride>(enabled);
#endif
}
void EnclaveAuthenticatorTestBase::AddTestPasskeyToModel() {
sync_pb::WebauthnCredentialSpecifics passkey;
CHECK(passkey.ParseFromArray(kTestProtobuf, sizeof(kTestProtobuf)));
passkey_model()->AddNewPasskeyForTesting(passkey);
}
void EnclaveAuthenticatorTestBase::SetMockVaultConnectionOnRequestDelegate(
AuthenticationFactorsResult result,
content::RenderFrameHost* rfh) {
auto connection = std::make_unique<
testing::NiceMock<trusted_vault::MockTrustedVaultThrottlingConnection>>();
EXPECT_CALL(*connection, DownloadAuthenticationFactorsRegistrationState(
testing::_, testing::_, testing::_))
.WillOnce(
[result = std::move(result)](
const CoreAccountInfo&,
base::OnceCallback<void(AuthenticationFactorsResult)> callback,
base::RepeatingClosure _) mutable {
std::move(callback).Run(std::move(result));
return std::make_unique<
trusted_vault::TrustedVaultConnection::Request>();
});
if (rfh == nullptr) {
rfh = browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame();
}
GpmTrustedVaultConnectionProvider::SetOverrideForFrame(rfh,
std::move(connection));
}
void EnclaveAuthenticatorTestBase::SetTrustedVaultEmpty() {
AuthenticationFactorsResult registration_state_result;
registration_state_result.state = AuthenticationFactorsResult::State::kEmpty;
SetMockVaultConnectionOnRequestDelegate(std::move(registration_state_result));
}
void EnclaveAuthenticatorTestBase::SetTrustedVaultRecoverable(
int32_t key_version,
content::RenderFrameHost* rfh) {
AuthenticationFactorsResult registration_state_result;
registration_state_result.state =
AuthenticationFactorsResult::State::kRecoverable;
registration_state_result.key_version = key_version;
SetMockVaultConnectionOnRequestDelegate(std::move(registration_state_result),
rfh);
security_domain_service_->pretend_there_are_members();
}
void EnclaveAuthenticatorTestBase::SetTrustedVaultSlowAndCacheCallback() {
auto connection_callback = [this](const CoreAccountInfo&,
base::OnceCallback<void(
AuthenticationFactorsResult)> callback,
base::RepeatingClosure) {
cached_connection_cb_ = std::move(callback);
return std::make_unique<trusted_vault::TrustedVaultConnection::Request>();
};
auto connection = std::make_unique<
testing::NiceMock<trusted_vault::MockTrustedVaultThrottlingConnection>>();
EXPECT_CALL(*connection, DownloadAuthenticationFactorsRegistrationState(
testing::_, testing::_, testing::_))
.WillOnce(connection_callback);
GpmTrustedVaultConnectionProvider::SetOverrideForFrame(
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame(),
std::move(connection));
}
void EnclaveAuthenticatorTestBase::SimulateSuccessfulGpmPinCreation(
const std::string& pin_value) {
WaitForEnclaveLoaded();
EnclaveManager* enclave_manager =
EnclaveManagerFactory::GetAsEnclaveManagerForProfile(
browser()->profile());
ASSERT_TRUE(enclave_manager);
enclave_manager->StoreKeys(
kSyncGaiaId,
{std::vector<uint8_t>(std::begin(kSecurityDomainSecret),
std::end(kSecurityDomainSecret))},
/*last_key_version=*/0);
base::test::TestFuture<bool> add_device_future;
enclave_manager->AddDeviceAndPINToAccount(
"123456",
/*previous_pin_public_key=*/std::nullopt,
add_device_future.GetCallback());
ASSERT_TRUE(add_device_future.Wait()) << "AddDeviceAndPINToAccount timed out";
ASSERT_TRUE(add_device_future.Get()) << "AddDeviceAndPINToAccount failed";
ASSERT_TRUE(enclave_manager->is_ready());
ASSERT_TRUE(enclave_manager->has_wrapped_pin());
}
void EnclaveAuthenticatorTestBase::WaitForEnclaveLoaded() {
EnclaveManager* enclave_manager =
EnclaveManagerFactory::GetAsEnclaveManagerForProfile(
browser()->profile());
ASSERT_TRUE(enclave_manager);
if (!enclave_manager->is_loaded()) {
base::test::TestFuture<void> load_future;
enclave_manager->Load(load_future.GetCallback());
ASSERT_TRUE(load_future.Wait());
}
}