blob: b8389448061fb5a0220dbc86bbc7c37916f93ace [file] [log] [blame]
// 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/ui/webauthn/passkey_upgrade_request_controller.h"
#include "chrome/browser/password_manager/account_password_store_factory.h"
#include "chrome/browser/password_manager/profile_password_store_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/passwords/passwords_client_ui_delegate.h"
#include "chrome/browser/webauthn/enclave_manager_factory.h"
#include "chrome/browser/webauthn/gpm_enclave_controller.h"
#include "chrome/browser/webauthn/passkey_model_factory.h"
#include "components/device_event_log/device_event_log.h"
#include "components/password_manager/core/browser/form_parsing/form_data_parser.h"
#include "components/password_manager/core/browser/password_store/password_store.h"
#include "components/password_manager/core/browser/password_store/password_store_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "device/fido/fido_discovery_factory.h"
using RenderFrameHost = content::RenderFrameHost;
DOCUMENT_USER_DATA_KEY_IMPL(PasskeyUpgradeRequestController);
PasskeyUpgradeRequestController::~PasskeyUpgradeRequestController() = default;
void PasskeyUpgradeRequestController::InitializeEnclaveRequestCallback(
device::FidoDiscoveryFactory* discovery_factory) {
using EnclaveEventStream = device::FidoDiscoveryBase::EventStream<
std::unique_ptr<device::enclave::CredentialRequest>>;
std::unique_ptr<EnclaveEventStream> event_stream;
std::tie(enclave_request_callback_, event_stream) = EnclaveEventStream::New();
discovery_factory->set_enclave_ui_request_stream(std::move(event_stream));
}
void PasskeyUpgradeRequestController::TryUpgradePasswordToPasskey(
std::string rp_id,
const std::string& user_name,
base::OnceCallback<void(bool success)> callback) {
FIDO_LOG(EVENT) << "Passkey upgrade request started";
CHECK(enclave_request_callback_);
CHECK(!pending_callback_);
pending_callback_ = std::move(callback);
rp_id_ = std::move(rp_id);
user_name_ = base::UTF8ToUTF16(user_name);
switch (enclave_state_) {
case EnclaveState::kUnknown:
// EnclaveLoaded() will invoke ContinuePendingUpgradeRequest().
pending_upgrade_request_ = true;
break;
case EnclaveState::kNotReady:
FIDO_LOG(EVENT) << "Passkey upgrade request failed because the enclave "
"isn't initialized.";
std::move(pending_callback_).Run(false);
break;
case EnclaveState::kReady:
pending_upgrade_request_ = true;
ContinuePendingUpgradeRequest();
break;
}
}
void PasskeyUpgradeRequestController::ContinuePendingUpgradeRequest() {
CHECK_EQ(enclave_state_, EnclaveState::kReady);
CHECK(pending_upgrade_request_);
pending_upgrade_request_ = false;
// TODO(crbug.com/377758786): The profile password store is probably wrong in
// some cases. Find out how to query GPM specifically.
scoped_refptr<password_manager::PasswordStoreInterface> password_store =
ProfilePasswordStoreFactory::GetForProfile(
profile(), ServiceAccessType::EXPLICIT_ACCESS);
GURL url = origin().GetURL();
password_manager::PasswordFormDigest form_digest(
password_manager::PasswordForm::Scheme::kHtml,
password_manager::GetSignonRealm(url), url);
password_store->GetLogins(form_digest, weak_factory_.GetWeakPtr());
}
void PasskeyUpgradeRequestController::OnGetPasswordStoreResultsOrErrorFrom(
password_manager::PasswordStoreInterface* store,
password_manager::LoginsResultOrError results_or_error) {
if (absl::holds_alternative<password_manager::PasswordStoreBackendError>(
results_or_error)) {
FIDO_LOG(EVENT) << "Passkey upgrade failed due to password store error";
}
CHECK(pending_callback_);
password_manager::LoginsResult result =
password_manager::GetLoginsOrEmptyListOnFailure(results_or_error);
bool found = false;
// Passwords must have been used within the last 90 days in order to be
// eligible.
const auto min_last_used = base::Time::Now() - base::Days(90);
for (const password_manager::PasswordForm& password_form : result) {
if (password_form.username_value == user_name_ &&
password_form.date_last_used >= min_last_used) {
found = true;
break;
}
}
if (!found) {
FIDO_LOG(EVENT) << "Passkey upgrade request failed, no matching password";
std::move(pending_callback_).Run(false);
return;
}
CHECK(enclave_request_callback_);
// TODO(crbug.com/377758786): Make the request up=0.
enclave_transaction_ = std::make_unique<GPMEnclaveTransaction>(
/*delegate=*/this, PasskeyModelFactory::GetForProfile(profile()),
device::FidoRequestType::kMakeCredential, rp_id_,
EnclaveUserVerificationMethod::kNoUserVerificationAndNoUserPresence,
EnclaveManagerFactory::GetAsEnclaveManagerForProfile(profile()),
/*pin=*/std::nullopt, /*selected_credential_id=*/std::nullopt,
enclave_request_callback_);
enclave_transaction_->Start();
}
void PasskeyUpgradeRequestController::HandleEnclaveTransactionError() {
if (!pending_callback_) {
return;
}
FIDO_LOG(ERROR) << "Passkey upgrade failed on enclave error";
std::move(pending_callback_).Run(false);
}
void PasskeyUpgradeRequestController::BuildUVKeyOptions(
EnclaveManager::UVKeyOptions&) {
// Upgrade requests don't perform user verification.
NOTIMPLEMENTED();
}
void PasskeyUpgradeRequestController::HandlePINValidationResult(
device::enclave::PINValidationResult) {
// Upgrade requests don't perform user verification.
NOTIMPLEMENTED();
}
void PasskeyUpgradeRequestController::OnPasskeyCreated(
const sync_pb::WebauthnCredentialSpecifics& passkey) {
PasswordsClientUIDelegate* manage_passwords_ui_controller =
PasswordsClientUIDelegateFromWebContents(
content::WebContents::FromRenderFrameHost(&render_frame_host()));
FIDO_LOG(EVENT) << "Passkey upgrade request succeeded";
if (manage_passwords_ui_controller) {
manage_passwords_ui_controller->OnPasskeyUpgrade(rp_id_);
}
CHECK(pending_callback_);
std::move(pending_callback_).Run(true);
}
Profile* PasskeyUpgradeRequestController::profile() const {
return Profile::FromBrowserContext(render_frame_host().GetBrowserContext());
}
void PasskeyUpgradeRequestController::OnEnclaveLoaded() {
CHECK(enclave_manager_->is_loaded());
CHECK_EQ(enclave_state_, EnclaveState::kUnknown);
enclave_state_ = enclave_manager_->is_ready() ? EnclaveState::kReady
: EnclaveState::kNotReady;
if (pending_upgrade_request_) {
ContinuePendingUpgradeRequest();
}
}
PasskeyUpgradeRequestController::PasskeyUpgradeRequestController(
RenderFrameHost* rfh)
: content::DocumentUserData<PasskeyUpgradeRequestController>(rfh),
enclave_manager_(
EnclaveManagerFactory::GetAsEnclaveManagerForProfile(profile())) {
if (enclave_manager_->is_loaded()) {
OnEnclaveLoaded();
return;
}
enclave_manager_->Load(
base::BindOnce(&PasskeyUpgradeRequestController::OnEnclaveLoaded,
weak_factory_.GetWeakPtr()));
}