blob: a4744cc4f8842025273bb317cfc2671f5aa9862e [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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_app.h"
#include <sstream>
#include <utility>
#include "base/base64.h"
#include "base/base64url.h"
#include "base/check.h"
#include "base/containers/flat_tree.h"
#include "base/feature_list.h"
#include "base/json/json_writer.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/payments/content/payment_request_spec.h"
#include "components/payments/core/method_strings.h"
#include "components/payments/core/payer_data.h"
#include "components/webauthn/core/browser/internal_authenticator.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "crypto/sha2.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/fido_types.h"
#include "device/fido/public_key_credential_descriptor.h"
#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
#include "url/url_constants.h"
namespace payments {
namespace {
static constexpr int kDefaultTimeoutMinutes = 3;
std::string EncodeSecurePaymentConfirmationString(
const std::vector<uint8_t>& vector_to_encode) {
if (base::FeatureList::IsEnabled(features::kSecurePaymentConfirmationAPIV2)) {
std::string encoded_string;
base::Base64UrlEncode(
std::string(vector_to_encode.begin(), vector_to_encode.end()),
base::Base64UrlEncodePolicy::OMIT_PADDING, &encoded_string);
return encoded_string;
} else {
return base::Base64Encode(vector_to_encode);
}
}
// Creates a SHA-256 hash over the Secure Payment Confirmation bundle, which is
// a JSON string (without whitespace) with the following structure:
// {
// "merchantData" {
// "merchantOrigin": "https://merchant.example",
// "total": {
// "currency": "CAD",
// "value": "1.25",
// },
// },
// "networkData": "YW=",
// }
// where "networkData" is the base64 encoding of the `networkData` specified in
// the SecurePaymentConfirmationRequest. Sets the `challenge` out-param value to
// this JSON string.
std::vector<uint8_t> GetSecurePaymentConfirmationChallenge(
const std::vector<uint8_t>& network_data,
const url::Origin& merchant_origin,
const mojom::PaymentCurrencyAmountPtr& amount,
std::string* challenge) {
DCHECK(
!base::FeatureList::IsEnabled(features::kSecurePaymentConfirmationAPIV2));
base::Value total(base::Value::Type::DICTIONARY);
total.SetKey("currency", base::Value(amount->currency));
total.SetKey("value", base::Value(amount->value));
base::Value merchant_data(base::Value::Type::DICTIONARY);
merchant_data.SetKey("merchantOrigin",
base::Value(merchant_origin.Serialize()));
merchant_data.SetKey("total", std::move(total));
base::Value transaction_data(base::Value::Type::DICTIONARY);
transaction_data.SetKey(
"networkData",
base::Value(EncodeSecurePaymentConfirmationString(network_data)));
transaction_data.SetKey("merchantData", std::move(merchant_data));
bool success = base::JSONWriter::Write(transaction_data, challenge);
DCHECK(success) << "Failed to write JSON for " << transaction_data;
std::string sha256_hash = crypto::SHA256HashString(*challenge);
std::vector<uint8_t> output_bytes(sha256_hash.begin(), sha256_hash.end());
return output_bytes;
}
// Records UMA metric for the system prompt result.
void RecordSystemPromptResult(
const SecurePaymentConfirmationSystemPromptResult result) {
base::UmaHistogramEnumeration(
"PaymentRequest.SecurePaymentConfirmation.Funnel.SystemPromptResult",
result);
}
} // namespace
SecurePaymentConfirmationApp::SecurePaymentConfirmationApp(
content::WebContents* web_contents_to_observe,
const std::string& effective_relying_party_identity,
std::unique_ptr<SkBitmap> icon,
const std::u16string& label,
std::vector<uint8_t> credential_id,
const url::Origin& merchant_origin,
base::WeakPtr<PaymentRequestSpec> spec,
mojom::SecurePaymentConfirmationRequestPtr request,
std::unique_ptr<autofill::InternalAuthenticator> authenticator)
: PaymentApp(/*icon_resource_id=*/0, PaymentApp::Type::INTERNAL),
content::WebContentsObserver(web_contents_to_observe),
authenticator_frame_routing_id_(
authenticator->GetRenderFrameHost()->GetGlobalId()),
effective_relying_party_identity_(effective_relying_party_identity),
icon_(std::move(icon)),
label_(label),
credential_id_(std::move(credential_id)),
encoded_credential_id_(
EncodeSecurePaymentConfirmationString(credential_id_)),
merchant_origin_(merchant_origin),
spec_(spec),
request_(std::move(request)),
authenticator_(std::move(authenticator)) {
DCHECK(web_contents_to_observe->GetMainFrame()->GetGlobalId() ==
authenticator_frame_routing_id_);
DCHECK(!credential_id_.empty());
app_method_names_.insert(methods::kSecurePaymentConfirmation);
}
SecurePaymentConfirmationApp::~SecurePaymentConfirmationApp() = default;
void SecurePaymentConfirmationApp::InvokePaymentApp(
base::WeakPtr<Delegate> delegate) {
if (!authenticator_ || !spec_)
return;
DCHECK(spec_->IsInitialized());
auto options = blink::mojom::PublicKeyCredentialRequestOptions::New();
options->relying_party_id = effective_relying_party_identity_;
options->timeout = request_->timeout.has_value()
? request_->timeout.value()
: base::TimeDelta::FromMinutes(kDefaultTimeoutMinutes);
options->user_verification = device::UserVerificationRequirement::kRequired;
std::vector<device::PublicKeyCredentialDescriptor> credentials;
if (base::FeatureList::IsEnabled(features::kSecurePaymentConfirmationDebug)) {
options->user_verification =
device::UserVerificationRequirement::kPreferred;
// The `device::PublicKeyCredentialDescriptor` constructor with 2 parameters
// enables authentication through all protocols.
credentials.emplace_back(device::CredentialType::kPublicKey,
credential_id_);
} else {
// Enable authentication only through internal authenticators by default.
credentials.emplace_back(device::CredentialType::kPublicKey, credential_id_,
base::flat_set<device::FidoTransportProtocol>{
device::FidoTransportProtocol::kInternal});
}
options->allow_credentials = std::move(credentials);
if (base::FeatureList::IsEnabled(features::kSecurePaymentConfirmationAPIV2)) {
options->challenge = request_->challenge;
options->payment = blink::mojom::PaymentOptions::New(
spec_->GetTotal(/*selected_app=*/this)->amount.Clone(),
request_->instrument.Clone());
} else {
// Create a new challenge that is a hash of the transaction data.
options->challenge = GetSecurePaymentConfirmationChallenge(
request_->challenge, merchant_origin_,
spec_->GetTotal(/*selected_app=*/this)->amount, &challenge_);
}
// We are nullifying the security check by design, and the origin that created
// the credential isn't saved anywhere.
authenticator_->SetEffectiveOrigin(url::Origin::Create(
GURL(base::StrCat({url::kHttpsScheme, url::kStandardSchemeSeparator,
effective_relying_party_identity_}))));
authenticator_->GetAssertion(
std::move(options),
base::BindOnce(&SecurePaymentConfirmationApp::OnGetAssertion,
weak_ptr_factory_.GetWeakPtr(), delegate));
}
bool SecurePaymentConfirmationApp::IsCompleteForPayment() const {
return true;
}
uint32_t SecurePaymentConfirmationApp::GetCompletenessScore() const {
// This value is used for sorting multiple apps, but this app always appears
// on its own.
return 0;
}
bool SecurePaymentConfirmationApp::CanPreselect() const {
return true;
}
std::u16string SecurePaymentConfirmationApp::GetMissingInfoLabel() const {
NOTREACHED();
return std::u16string();
}
bool SecurePaymentConfirmationApp::HasEnrolledInstrument() const {
// If there's no platform authenticator, then the factory should not create
// this app. Therefore, this function can always return true.
return true;
}
void SecurePaymentConfirmationApp::RecordUse() {
NOTIMPLEMENTED();
}
bool SecurePaymentConfirmationApp::NeedsInstallation() const {
return false;
}
std::string SecurePaymentConfirmationApp::GetId() const {
return encoded_credential_id_;
}
std::u16string SecurePaymentConfirmationApp::GetLabel() const {
return label_;
}
std::u16string SecurePaymentConfirmationApp::GetSublabel() const {
return std::u16string();
}
const SkBitmap* SecurePaymentConfirmationApp::icon_bitmap() const {
return icon_.get();
}
bool SecurePaymentConfirmationApp::IsValidForModifier(
const std::string& method,
bool supported_networks_specified,
const std::set<std::string>& supported_networks) const {
bool is_valid = false;
IsValidForPaymentMethodIdentifier(method, &is_valid);
return is_valid;
}
base::WeakPtr<PaymentApp> SecurePaymentConfirmationApp::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
bool SecurePaymentConfirmationApp::HandlesShippingAddress() const {
return false;
}
bool SecurePaymentConfirmationApp::HandlesPayerName() const {
return false;
}
bool SecurePaymentConfirmationApp::HandlesPayerEmail() const {
return false;
}
bool SecurePaymentConfirmationApp::HandlesPayerPhone() const {
return false;
}
bool SecurePaymentConfirmationApp::IsWaitingForPaymentDetailsUpdate() const {
return false;
}
void SecurePaymentConfirmationApp::UpdateWith(
mojom::PaymentRequestDetailsUpdatePtr details_update) {
NOTREACHED();
}
void SecurePaymentConfirmationApp::OnPaymentDetailsNotUpdated() {
NOTREACHED();
}
void SecurePaymentConfirmationApp::AbortPaymentApp(
base::OnceCallback<void(bool)> abort_callback) {
std::move(abort_callback).Run(/*abort_success=*/false);
}
mojom::PaymentResponsePtr
SecurePaymentConfirmationApp::SetAppSpecificResponseFields(
mojom::PaymentResponsePtr response) const {
if (base::FeatureList::IsEnabled(features::kSecurePaymentConfirmationAPIV2)) {
response->secure_payment_confirmation =
mojom::SecurePaymentConfirmationResponse::New(response_->info.Clone(),
response_->signature,
response_->user_handle);
}
return response;
}
void SecurePaymentConfirmationApp::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (content::RenderFrameHost::FromID(authenticator_frame_routing_id_) ==
render_frame_host) {
// The authenticator requires to be deleted before the render frame.
authenticator_.reset();
}
}
void SecurePaymentConfirmationApp::OnGetAssertion(
base::WeakPtr<Delegate> delegate,
blink::mojom::AuthenticatorStatus status,
blink::mojom::GetAssertionAuthenticatorResponsePtr response) {
if (!delegate)
return;
if (status != blink::mojom::AuthenticatorStatus::SUCCESS || !response) {
std::stringstream status_string_stream;
status_string_stream << status;
delegate->OnInstrumentDetailsError(base::StringPrintf(
"Authenticator returned %s.", status_string_stream.str().c_str()));
RecordSystemPromptResult(
SecurePaymentConfirmationSystemPromptResult::kCanceled);
return;
}
RecordSystemPromptResult(
SecurePaymentConfirmationSystemPromptResult::kAccepted);
base::DictionaryValue json;
if (base::FeatureList::IsEnabled(features::kSecurePaymentConfirmationAPIV2)) {
response_ = std::move(response);
} else {
// Serialize response into a JSON string. Browser will pass this string over
// Mojo IPC into Blink, which will parse it into a JavaScript object for the
// merchant.
base::DictionaryValue info_json;
if (response->info) {
info_json.SetString("id", response->info->id);
info_json.SetString("client_data_json",
EncodeSecurePaymentConfirmationString(
response->info->client_data_json));
info_json.SetString("authenticator_data",
EncodeSecurePaymentConfirmationString(
response->info->authenticator_data));
}
base::DictionaryValue prf_results_json;
if (response->prf_results) {
DCHECK(!response->prf_results->id.has_value());
prf_results_json.SetString("first", EncodeSecurePaymentConfirmationString(
response->prf_results->first));
if (response->prf_results->second) {
prf_results_json.SetString("second",
EncodeSecurePaymentConfirmationString(
*response->prf_results->second));
}
}
json.SetKey("info", std::move(info_json));
if (!base::FeatureList::IsEnabled(
features::kSecurePaymentConfirmationAPIV2)) {
json.SetString("challenge", challenge_);
}
json.SetString("signature",
EncodeSecurePaymentConfirmationString(response->signature));
if (response->user_handle.has_value()) {
json.SetString("user_handle", EncodeSecurePaymentConfirmationString(
response->user_handle.value()));
}
json.SetBoolean("echo_appid_extension", response->echo_appid_extension);
json.SetBoolean("appid_extension", response->appid_extension);
json.SetBoolean("echo_prf", response->echo_prf);
json.SetKey("prf_results", std::move(prf_results_json));
json.SetBoolean("prf_not_evaluated", response->echo_prf);
}
std::string json_serialized_response;
base::JSONWriter::Write(json, &json_serialized_response);
delegate->OnInstrumentDetailsReady(methods::kSecurePaymentConfirmation,
json_serialized_response, PayerData());
}
} // namespace payments