blob: 6ab434a42331efa80e99ade7f96470b7f53fbdb0 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/payments/content/secure_payment_confirmation_service.h"
#include "base/compiler_specific.h"
#include "base/feature_list.h"
#include "base/memory/ref_counted_memory.h"
#include "components/payments/content/browser_binding/browser_bound_key.h"
#include "components/payments/content/web_payments_web_data_service.h"
#include "components/payments/core/features.h"
#include "components/payments/core/secure_payment_confirmation_credential.h"
#include "components/webauthn/core/browser/internal_authenticator.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/secure_payment_confirmation_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "crypto/random.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
#if BUILDFLAG(IS_ANDROID)
#include "components/payments/content/browser_binding/browser_bound_key_store.h"
#include "third_party/blink/public/common/features.h"
#endif
namespace payments {
namespace {
void OnIsUserVerifyingPlatformAuthenticatorAvailable(
SecurePaymentConfirmationService::
SecurePaymentConfirmationAvailabilityCallback callback,
bool is_user_verifying_platform_authenticator_available) {
std::move(callback).Run(
is_user_verifying_platform_authenticator_available
? mojom::SecurePaymentConfirmationAvailabilityEnum::kAvailable
: mojom::SecurePaymentConfirmationAvailabilityEnum::
kUnavailableNoUserVerifyingPlatformAuthenticator);
}
} // namespace
SecurePaymentConfirmationService::SecurePaymentConfirmationService(
content::RenderFrameHost& render_frame_host,
mojo::PendingReceiver<mojom::SecurePaymentConfirmationService> receiver,
scoped_refptr<WebPaymentsWebDataService> web_data_service,
std::unique_ptr<webauthn::InternalAuthenticator> authenticator,
std::string browser_bound_key_store_keychain_access_group)
: DocumentService(render_frame_host, std::move(receiver)),
web_data_service_(web_data_service),
authenticator_(std::move(authenticator)),
browser_bound_key_store_keychain_access_group_(
std::move(browser_bound_key_store_keychain_access_group)) {}
SecurePaymentConfirmationService::~SecurePaymentConfirmationService() {
Reset();
}
void SecurePaymentConfirmationService::SecurePaymentConfirmationAvailability(
SecurePaymentConfirmationAvailabilityCallback callback) {
if (!base::FeatureList::IsEnabled(::features::kSecurePaymentConfirmation)) {
std::move(callback).Run(mojom::SecurePaymentConfirmationAvailabilityEnum::
kUnavailableFeatureNotEnabled);
return;
}
// TODO(crbug.com/40258712): This method should next check that the 'payment'
// permission policy is set. However, SecurePaymentConfirmationService is only
// installed by the factory if the policy is set, so it is not possible for
// that check to ever be false here. Instead, the renderer-side implementation
// of isSecurePaymentConfirmationAvailable checks for the permission policy
// before trying to call to the browser (or else it would hit a mojo error).
//
// This all technically works, but it would probably be cleaner to refactor
// SecurePaymentConfirmationService to always be available and to properly
// handle calls if the 'payment' policy is not present.
// The remaining checks in this method are for the underlying platform
// authenticator. If the kSecurePaymentConfirmationDebug method is enabled we
// may not have a real authenticator, so early-exit here. This is never
// expected to be hit in production, as it is a debug flag only.
if (base::FeatureList::IsEnabled(
::features::kSecurePaymentConfirmationDebug)) {
std::move(callback).Run(
mojom::SecurePaymentConfirmationAvailabilityEnum::kAvailable);
return;
}
if (!authenticator_) {
std::move(callback).Run(mojom::SecurePaymentConfirmationAvailabilityEnum::
kUnavailableUnknownReason);
return;
}
if (base::FeatureList::IsEnabled(
features::kSecurePaymentConfirmationUseCredentialStoreAPIs) &&
!authenticator_->IsGetMatchingCredentialIdsSupported()) {
std::move(callback).Run(mojom::SecurePaymentConfirmationAvailabilityEnum::
kUnavailableUnknownReason);
return;
}
authenticator_->IsUserVerifyingPlatformAuthenticatorAvailable(base::BindOnce(
&OnIsUserVerifyingPlatformAuthenticatorAvailable, std::move(callback)));
}
void SecurePaymentConfirmationService::StorePaymentCredential(
const std::vector<uint8_t>& credential_id,
const std::string& rp_id,
const std::vector<uint8_t>& user_id,
StorePaymentCredentialCallback callback) {
if (state_ != State::kIdle || !IsCurrentStateValid() ||
credential_id.empty() || rp_id.empty() || user_id.empty()) {
Reset();
std::move(callback).Run(
mojom::PaymentCredentialStorageStatus::FAILED_TO_STORE_CREDENTIAL);
return;
}
RecordFirstSystemPromptResult(
SecurePaymentConfirmationEnrollSystemPromptResult::kAccepted);
// If credential-store level APIs are available, the credential information
// will already have been stored during creation.
if (base::FeatureList::IsEnabled(
features::kSecurePaymentConfirmationUseCredentialStoreAPIs)) {
Reset();
std::move(callback).Run(mojom::PaymentCredentialStorageStatus::SUCCESS);
return;
}
storage_callback_ = std::move(callback);
state_ = State::kStoringCredential;
data_service_request_handle_ =
web_data_service_->AddSecurePaymentConfirmationCredential(
std::make_unique<SecurePaymentConfirmationCredential>(credential_id,
rp_id, user_id),
base::BindOnce(
&SecurePaymentConfirmationService::OnStorePaymentCredential,
weak_ptr_factory_.GetWeakPtr()));
}
void SecurePaymentConfirmationService::MakePaymentCredential(
blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
MakePaymentCredentialCallback callback) {
std::string relying_party_id;
std::optional<PasskeyBrowserBinder::UnboundKey> browser_bound_key;
#if BUILDFLAG(IS_ANDROID)
if (options &&
base::FeatureList::IsEnabled(
blink::features::kSecurePaymentConfirmationBrowserBoundKeys)) {
relying_party_id = options->relying_party.id;
if (!passkey_browser_binder_) {
if (scoped_refptr<BrowserBoundKeyStore> key_store =
GetBrowserBoundKeyStoreInstance(BrowserBoundKeyStore::Config {
#if BUILDFLAG(IS_MAC)
.keychain_access_group =
browser_bound_key_store_keychain_access_group_;
#endif // BUILDFLAG(IS_MAC)
})) {
passkey_browser_binder_ = std::make_unique<PasskeyBrowserBinder>(
key_store, web_data_service_);
}
}
if (passkey_browser_binder_ &&
!render_frame_host().GetBrowserContext()->IsOffTheRecord()) {
// TODO(crbug.com/384940850): Regenerate the browser bound key identifier
// if a browser bound key with the same identifier already exists.
// TODO(crbug.com/377278827): Provide the browser bound public key
// credential parameters from the payment extensions to the key store.
browser_bound_key = passkey_browser_binder_->CreateUnboundKey(
options->payment_browser_bound_key_parameters.value_or(
options->public_key_parameters));
}
auto payment_options = ::blink::mojom::PaymentOptions::New();
payment_options->total = mojom::PaymentCurrencyAmount::New();
payment_options->instrument =
::blink::mojom::PaymentCredentialInstrument::New();
if (browser_bound_key) {
payment_options->browser_bound_public_key =
browser_bound_key->Get().GetPublicKeyAsCoseKey();
}
authenticator_->SetPaymentOptions(std::move(payment_options));
}
#endif // BUILDFLAG(IS_ANDROID)
authenticator_->MakeCredential(
std::move(options),
base::BindOnce(
&SecurePaymentConfirmationService::OnAuthenticatorMakeCredential,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(relying_party_id), std::move(browser_bound_key)));
}
#if BUILDFLAG(IS_ANDROID)
void SecurePaymentConfirmationService::SetPasskeyBrowserBinderForTesting(
std::unique_ptr<PasskeyBrowserBinder> passkey_browser_binder) {
passkey_browser_binder_ = std::move(passkey_browser_binder);
}
#endif // BUILDFLAG(IS_ANDROID)
void SecurePaymentConfirmationService::OnStorePaymentCredential(
WebDataServiceBase::Handle h,
std::unique_ptr<WDTypedResult> result) {
if (state_ != State::kStoringCredential || !IsCurrentStateValid() ||
data_service_request_handle_ != h) {
Reset();
return;
}
auto callback = std::move(storage_callback_);
Reset();
std::move(callback).Run(
result && static_cast<WDResult<bool>*>(result.get())->GetValue()
? mojom::PaymentCredentialStorageStatus::SUCCESS
: mojom::PaymentCredentialStorageStatus::FAILED_TO_STORE_CREDENTIAL);
}
// Handles the authenticator make credential callback by adding the browser
// bound signature, then running the callback.
void SecurePaymentConfirmationService::OnAuthenticatorMakeCredential(
SecurePaymentConfirmationService::MakePaymentCredentialCallback callback,
std::string relying_party,
std::optional<PasskeyBrowserBinder::UnboundKey> browser_bound_key,
::blink::mojom::AuthenticatorStatus authenticator_status,
::blink::mojom::MakeCredentialAuthenticatorResponsePtr response,
::blink::mojom::WebAuthnDOMExceptionDetailsPtr maybe_exception_details) {
#if BUILDFLAG(IS_ANDROID)
if (response &&
base::FeatureList::IsEnabled(
blink::features::kSecurePaymentConfirmationBrowserBoundKeys)) {
if (browser_bound_key) {
std::vector<uint8_t> signature_output =
browser_bound_key->Get().Sign(response->info->client_data_json);
response->payment =
blink::mojom::AuthenticationExtensionsPaymentResponse::New();
response->payment->browser_bound_signature = std::move(signature_output);
passkey_browser_binder_->BindKey(
std::move(*browser_bound_key), response->info->raw_id,
std::move(relying_party), /*last_used=*/std::nullopt);
}
}
#endif
std::move(callback).Run(authenticator_status, std::move(response),
std::move(maybe_exception_details));
}
bool SecurePaymentConfirmationService::IsCurrentStateValid() const {
if (!content::IsFrameAllowedToUseSecurePaymentConfirmation(
&render_frame_host()) ||
!web_data_service_) {
return false;
}
switch (state_) {
case State::kIdle:
return !storage_callback_ && !data_service_request_handle_;
case State::kStoringCredential:
return storage_callback_ && data_service_request_handle_;
}
}
void SecurePaymentConfirmationService::RecordFirstSystemPromptResult(
SecurePaymentConfirmationEnrollSystemPromptResult result) {
if (!is_system_prompt_result_recorded_) {
is_system_prompt_result_recorded_ = true;
RecordEnrollSystemPromptResult(result);
}
}
void SecurePaymentConfirmationService::Reset() {
// Callbacks must either be run or disconnected before being destroyed, so
// run them if they are still connected.
if (storage_callback_) {
std::move(storage_callback_)
.Run(mojom::PaymentCredentialStorageStatus::FAILED_TO_STORE_CREDENTIAL);
}
if (web_data_service_ && data_service_request_handle_) {
web_data_service_->CancelRequest(data_service_request_handle_.value());
}
data_service_request_handle_.reset();
is_system_prompt_result_recorded_ = false;
state_ = State::kIdle;
}
} // namespace payments