blob: 65072304accdea7b7f6f97e5d9c9293210c8b08a [file] [log] [blame]
// Copyright 2019 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_common_impl.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
#include "base/barrier_callback.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "components/webauthn/json/value_conversions.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/webauth/authenticator_environment.h"
#include "content/browser/webauth/authenticator_request_outcome_enums.h"
#include "content/browser/webauth/client_data_json.h"
#include "content/browser/webauth/common_utils.h"
#include "content/browser/webauth/virtual_authenticator.h"
#include "content/browser/webauth/virtual_authenticator_manager_impl.h"
#include "content/browser/webauth/virtual_fido_discovery_factory.h"
#include "content/browser/webauth/webauth_request_security_checker.h"
#include "content/public/browser/authenticator_common.h"
#include "content/public/browser/authenticator_request_client_delegate.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_authentication_delegate.h"
#include "content/public/browser/web_authentication_request_proxy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "crypto/hash.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/fido/attestation_object.h"
#include "device/fido/attestation_statement.h"
#include "device/fido/authenticator_data.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/authenticator_make_credential_response.h"
#include "device/fido/authenticator_selection_criteria.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/features.h"
#include "device/fido/fido_authenticator.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/fido_types.h"
#include "device/fido/filter.h"
#include "device/fido/get_assertion_request_handler.h"
#include "device/fido/json_request.h"
#include "device/fido/make_credential_request_handler.h"
#include "device/fido/prf_input.h"
#include "device/fido/public_key.h"
#include "device/fido/public_key_credential_descriptor.h"
#include "device/fido/public_key_credential_params.h"
#include "mojo/public/cpp/bindings/message.h"
#include "net/cert/asn1_util.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
#include "third_party/boringssl/src/pki/input.h"
#include "third_party/boringssl/src/pki/parse_values.h"
#include "third_party/boringssl/src/pki/parser.h"
#if BUILDFLAG(IS_MAC)
#include "device/fido/mac/credential_metadata.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "device/fido/cros/authenticator.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "device/fido/win/authenticator.h"
#include "device/fido/win/webauthn_api.h"
#endif
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
#include "content/browser/webauth/is_uvpaa.h"
#endif
namespace content {
// RequestExtension is a type of extension in a WebAuthn request that might
// yield an extension output in the response.
enum class RequestExtension {
kAppID,
kHMACSecret,
kPRF,
kCredProps,
kLargeBlobEnable,
kLargeBlobRead,
kLargeBlobWrite,
kCredBlob,
kGetCredBlob,
kMinPINLength,
};
enum class AttestationErasureOption {
kIncludeAttestation,
kEraseAttestationButIncludeAaguid,
kEraseAttestationAndAaguid,
};
using MakeCredentialCallback =
blink::mojom::Authenticator::MakeCredentialCallback;
using GetCredentialCallback =
blink::mojom::Authenticator::GetCredentialCallback;
using ReportCallback = blink::mojom::Authenticator::ReportCallback;
using UIPresentation = AuthenticatorRequestClientDelegate::UIPresentation;
using Mediation = blink::mojom::Mediation;
namespace {
WebAuthenticationDelegate* GetWebAuthenticationDelegate() {
return GetContentClient()->browser()->GetWebAuthenticationDelegate();
}
// The application parameter is the SHA-256 hash of the UTF-8 encoding of
// the application identity (i.e. relying_party_id) of the application
// requesting the registration.
std::array<uint8_t, crypto::hash::kSha256Size> CreateApplicationParameter(
const std::string& relying_party_id) {
return crypto::hash::Sha256(relying_party_id);
}
device::CtapGetAssertionRequest CreateCtapGetAssertionRequest(
const std::string& client_data_json,
const blink::mojom::PublicKeyCredentialRequestOptionsPtr& options,
std::optional<std::string> app_id) {
device::CtapGetAssertionRequest request_parameter(options->relying_party_id,
client_data_json);
request_parameter.allow_list = options->allow_credentials;
request_parameter.user_verification = options->user_verification;
if (app_id) {
request_parameter.alternative_application_parameter =
CreateApplicationParameter(*app_id);
request_parameter.app_id = std::move(*app_id);
}
if (!options->extensions->cable_authentication_data.empty()) {
request_parameter.cable_extension =
options->extensions->cable_authentication_data;
}
return request_parameter;
}
// Parses the FIDO transport types extension from the DER-encoded, X.509
// certificate in |der_cert| and adds any transport types found to
// |out_transports|. Returns true if any transports were added.
bool AddTransportsFromCertificate(
base::span<const uint8_t> der_cert,
base::flat_set<device::FidoTransportProtocol>* out_transports) {
// See
// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-authenticator-transports-extension-v1.2-ps-20170411.html#fido-u2f-certificate-transports-extension
static constexpr std::array<uint8_t, 11> kTransportTypesOID = {
0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01};
bool present, critical;
std::string_view contents;
if (!net::asn1::ExtractExtensionFromDERCert(
base::as_string_view(der_cert),
base::as_string_view(kTransportTypesOID), &present, &critical,
&contents) ||
!present) {
return false;
}
const bssl::der::Input contents_der(contents);
bssl::der::Parser contents_parser(contents_der);
std::optional<bssl::der::BitString> transport_bits =
contents_parser.ReadBitString();
if (!transport_bits) {
return false;
}
// The certificate extension contains a BIT STRING where different bits
// indicate support for different transports. The following array maps
// between these bit indexes and the FidoTransportProtocol enum.
static constexpr struct {
uint8_t bit_index;
device::FidoTransportProtocol transport;
} kTransportMapping[] = {
// Bit 0 is "Bluetooth Classic", not BLE. Since webauthn doesn't define a
// transport type for this we ignore it.
{1, device::FidoTransportProtocol::kBluetoothLowEnergy},
{2, device::FidoTransportProtocol::kUsbHumanInterfaceDevice},
{3, device::FidoTransportProtocol::kNearFieldCommunication},
{4, device::FidoTransportProtocol::kInternal},
};
bool ret = false;
for (const auto& mapping : kTransportMapping) {
if (transport_bits->AssertsBit(mapping.bit_index)) {
out_transports->insert(mapping.transport);
ret |= true;
}
}
return ret;
}
base::TimeDelta AdjustTimeout(std::optional<base::TimeDelta> timeout,
RenderFrameHost* render_frame_host) {
// Time to wait for an authenticator to successfully complete an operation.
base::TimeDelta adjusted_timeout_lower = base::Minutes(3);
base::TimeDelta adjusted_timeout_upper = base::Hours(20);
if (!timeout) {
return adjusted_timeout_upper;
}
const bool testing_api_enabled =
AuthenticatorEnvironment::GetInstance()->IsVirtualAuthenticatorEnabledFor(
static_cast<RenderFrameHostImpl*>(render_frame_host)
->frame_tree_node()) ||
AuthenticatorEnvironment::GetInstance()
->MaybeGetDiscoveryFactoryTestOverride();
if (testing_api_enabled) {
return *timeout;
}
return std::max(adjusted_timeout_lower,
std::min(adjusted_timeout_upper, *timeout));
}
bool UsesDiscoverableCreds(const device::MakeCredentialOptions& options) {
return options.resident_key == device::ResidentKeyRequirement::kRequired;
}
bool UsesDiscoverableCreds(const device::CtapGetAssertionRequest& request) {
return request.allow_list.empty();
}
// GetWebAuthnTransports returns the set of transports that should be passed to
// a FidoRequestHandler for a WebAuthn request. This determines for which
// transports the request handler will attempt to obtain FidoDiscovery
// instances.
base::flat_set<device::FidoTransportProtocol> GetWebAuthnTransports(
RenderFrameHost* render_frame_host,
device::FidoDiscoveryFactory* discovery_factory,
bool uses_discoverable_creds,
std::optional<bool> is_uvpaa_override,
bool is_immediate_mediation = false) {
base::flat_set<device::FidoTransportProtocol> transports;
if (!is_immediate_mediation) {
transports.insert(device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
transports.insert(device::FidoTransportProtocol::kHybrid);
}
// Only instantiate platform discovery if the embedder hasn't chosen to
// override IsUserVerifyingPlatformAuthenticatorAvailable() to be false.
// Chrome disables platform authenticators in Guest modes this way.
if (is_uvpaa_override.value_or(true)) {
transports.insert(device::FidoTransportProtocol::kInternal);
}
if (discovery_factory->IsTestOverride()) {
if (!is_immediate_mediation) {
// The desktop implementation does not support BLE or NFC, but we emulate
// them if the testing API is enabled.
transports.insert(device::FidoTransportProtocol::kBluetoothLowEnergy);
transports.insert(device::FidoTransportProtocol::kNearFieldCommunication);
}
// Ensure virtual platform authenticators can be instantiated even if they
// are not-user-verifying, i.e. IsUVPAA() returns false.
transports.insert(device::FidoTransportProtocol::kInternal);
}
return transports;
}
// Returns a new FidoDiscoveryFactory for the current request. This may be
// a factory for virtual authenticators if the testing API is enabled for the
// given frame.
std::unique_ptr<device::FidoDiscoveryFactory> MakeDiscoveryFactory(
RenderFrameHost* render_frame_host) {
VirtualAuthenticatorManagerImpl* virtual_authenticator_manager =
AuthenticatorEnvironment::GetInstance()
->MaybeGetVirtualAuthenticatorManager(
static_cast<RenderFrameHostImpl*>(render_frame_host)
->frame_tree_node());
if (virtual_authenticator_manager) {
return virtual_authenticator_manager->MakeDiscoveryFactory();
}
auto discovery_factory = std::make_unique<device::FidoDiscoveryFactory>();
#if BUILDFLAG(IS_MAC)
discovery_factory->set_mac_touch_id_info(
GetWebAuthenticationDelegate()->GetTouchIdAuthenticatorConfig(
render_frame_host->GetBrowserContext()));
#endif // BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_CHROMEOS)
// Ignore the ChromeOS u2fd virtual U2F HID device so that it doesn't collide
// with the ChromeOS platform authenticator, also implemented in u2fd.
// There are two possible PIDs the virtual U2F HID device could use, with or
// without corp protocol functionality.
constexpr device::VidPid kChromeOsU2fdVidPid{0x18d1, 0x502c};
constexpr device::VidPid kChromeOsU2fdCorpVidPid{0x18d1, 0x5212};
discovery_factory->set_hid_ignore_list(
{kChromeOsU2fdVidPid, kChromeOsU2fdCorpVidPid});
discovery_factory->set_generate_request_id_callback(
GetWebAuthenticationDelegate()->GetGenerateRequestIdCallback(
render_frame_host));
#endif // BUILDFLAG(IS_CHROMEOS)
return discovery_factory;
}
std::optional<device::CredProtectRequest> ProtectionPolicyToCredProtect(
blink::mojom::ProtectionPolicy protection_policy,
const device::MakeCredentialOptions& make_credential_options) {
switch (protection_policy) {
case blink::mojom::ProtectionPolicy::UNSPECIFIED:
// Some platform authenticators have the behaviour that uv=required
// demands a local reauthentication but uv=preferred can be satisfied by
// just clicking a button. Since the device has to be unlocked by the
// user, this seems to balance the demands of uv=required against the
// fact that quite a number of (non-mobile) devices lack biometrics and
// thus full UV requires entering the local password. Since password
// autofill doesn't demand entering the local password all the time, it
// would be sad if WebAuthn was much worse in that respect.
//
// Also, some sites have (or will) implement a sign-in flow where the
// user enters their username and then the site makes a WebAuthn
// request, with an allowlist, where completing that request is
// sufficient to sign-in. I.e. there's no additional password challenge.
// Since these sites are trying to replace passwords, we expect them to
// set uv=preferred in order to work well with the platform behaviour
// detailed in the first paragraph.
//
// If such sites remembered the UV flag from the registration and enforced
// it at assertion time, that would break situations where closing a
// laptop lid covers the biometric sensor and makes entering a password
// preferable. But without any enforcement of the UV flag, someone could
// pick a security key off the ground and do a uv=false request to get a
// sufficient assertion.
//
// Thus if rk=required and uv=preferred, credProtect level three is set
// to tell security keys to only create an assertion after UV for this
// credential. (Sites can still override this by setting a specific
// credProtect level.)
//
// If a site sets rk=preferred then we assume that they're doing something
// unusual and will only set credProtect level two.
//
// See also
// https://chromium.googlesource.com/chromium/src/+/main/content/browser/webauth/cred_protect.md
if (make_credential_options.resident_key ==
device::ResidentKeyRequirement::kRequired &&
make_credential_options.user_verification ==
device::UserVerificationRequirement::kPreferred) {
return device::CredProtectRequest::kUVRequired;
}
#if BUILDFLAG(IS_WIN)
// On Windows, if webauthn.dll is version two or below, rk=preferred
// cannot be expressed and will be mapped to rk=false. Some security keys
// have a bug where they'll return credProtect=1 when credProtect=2 is
// requested for non-discoverable credentials. Thus, for these versions
// of webauthn.dll, treat rk=preferred as rk=discouraged for the purposes
// of credProtect, because that's what will ultimately be sent to the
// security key.
//
// If a site explicitly requests a credProtect level, we'll still respect
// that because they are presumably going to check the response.
if (make_credential_options.resident_key ==
device::ResidentKeyRequirement::kPreferred &&
device::WinWebAuthnApi::GetDefault() &&
device::WinWebAuthnApi::GetDefault()->Version() < 3) {
return std::nullopt;
}
#endif
if (make_credential_options.resident_key !=
device::ResidentKeyRequirement::kDiscouraged) {
// Otherwise, kUVOrCredIDRequired is made the default unless
// the authenticator defaults to something better.
return device::CredProtectRequest::kUVOrCredIDRequiredOrBetter;
}
return std::nullopt;
case blink::mojom::ProtectionPolicy::NONE:
return device::CredProtectRequest::kUVOptional;
case blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED:
return device::CredProtectRequest::kUVOrCredIDRequired;
case blink::mojom::ProtectionPolicy::UV_REQUIRED:
return device::CredProtectRequest::kUVRequired;
}
}
std::optional<device::PRFInput> ParsePRFInputForMakeCredential(
const blink::mojom::PRFValuesPtr& prf_input_from_renderer) {
// The input cannot be credential-specific because we haven't created the
// credential yet.
if (prf_input_from_renderer->id) {
return std::nullopt;
}
device::PRFInput prf_input;
prf_input.input1 = prf_input_from_renderer->first;
if (prf_input_from_renderer->second) {
prf_input.input2 = prf_input_from_renderer->second;
}
prf_input.HashInputsIntoSalts();
return prf_input;
}
std::optional<std::vector<device::PRFInput>> ParsePRFInputsForGetAssertion(
base::span<const blink::mojom::PRFValuesPtr> inputs) {
std::vector<device::PRFInput> ret;
bool is_first = true;
std::optional<std::vector<uint8_t>> last_id;
// TODO(agl): should match the credential IDs from the allow list, which
// will also limit the size to the size of the allow list.
for (const auto& prf_input_from_renderer : inputs) {
device::PRFInput prf_input;
// This statement enforces invariants that should be established by the
// renderer.
if (
// Only the first element in the vector may be the default.
(!is_first && !prf_input_from_renderer->id) ||
// The PRF inputs must be sorted by credential ID to show that there
// are no duplicates.
(last_id.has_value() && prf_input_from_renderer->id.has_value() &&
*last_id >= *prf_input_from_renderer->id)) {
return std::nullopt;
}
is_first = false;
last_id = prf_input_from_renderer->id;
if (prf_input_from_renderer->id) {
prf_input.credential_id = std::move(*prf_input_from_renderer->id);
}
prf_input.input1 = prf_input_from_renderer->first;
if (prf_input_from_renderer->second) {
prf_input.input2 = prf_input_from_renderer->second;
}
prf_input.HashInputsIntoSalts();
ret.emplace_back(std::move(prf_input));
}
return ret;
}
blink::mojom::PRFValuesPtr PRFResultsToValues(
base::span<const uint8_t> results) {
auto prf_values = blink::mojom::PRFValues::New();
DCHECK(results.size() == 32 || results.size() == 64);
// Using `.split_at<32>()` would cause the subsequent `Materialize()` call to
// return a `std::array`, resulting in an extra copy.
const auto [first, second] = results.split_at(32u);
prf_values->first = device::fido_parsing_utils::Materialize(first);
if (!second.empty()) {
prf_values->second = device::fido_parsing_utils::Materialize(second);
}
return prf_values;
}
void SetHints(AuthenticatorRequestClientDelegate* request_delegate,
const base::flat_set<blink::mojom::Hint>& hints) {
// The first recognised transport takes priority.
std::optional<device::FidoTransportProtocol> transport;
for (const auto hint : hints) {
switch (hint) {
case blink::mojom::Hint::SECURITY_KEY:
transport = transport.value_or(
device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
break;
case blink::mojom::Hint::CLIENT_DEVICE:
transport =
transport.value_or(device::FidoTransportProtocol::kInternal);
break;
case blink::mojom::Hint::HYBRID:
transport = transport.value_or(device::FidoTransportProtocol::kHybrid);
break;
}
}
if (transport) {
AuthenticatorRequestClientDelegate::Hints delegate_hints;
delegate_hints.transport = transport;
request_delegate->SetHints(delegate_hints);
}
}
bool IsPlatformAuthenticatorForInvalidStateError(
const device::FidoAuthenticator* authenticator) {
switch (authenticator->GetType()) {
case device::AuthenticatorType::kTouchID:
case device::AuthenticatorType::kChromeOS:
case device::AuthenticatorType::kICloudKeychain:
case device::AuthenticatorType::kEnclave:
return true;
// kWinNative can be a platform authenticator but, in the context where this
// function is used, Windows returns a specific error when InvalidStateError
// should be returned. Thus, if it didn't return that, then we shouldn't
// consider it a platform authenticator.
case device::AuthenticatorType::kWinNative:
case device::AuthenticatorType::kOther:
case device::AuthenticatorType::kPhone:
return false;
}
}
AuthenticatorCommonImpl::CredentialRequestResult
CredentialRequestResultFromCode(bool success, device::AuthenticatorType type) {
switch (type) {
case device::AuthenticatorType::kChromeOS:
return success ? AuthenticatorCommonImpl::CredentialRequestResult::
kChromeOSSuccess
: AuthenticatorCommonImpl::CredentialRequestResult::
kChromeOSError;
case device::AuthenticatorType::kEnclave:
return success ? AuthenticatorCommonImpl::CredentialRequestResult::
kEnclaveSuccess
: AuthenticatorCommonImpl::CredentialRequestResult::
kEnclaveError;
case device::AuthenticatorType::kICloudKeychain:
return success ? AuthenticatorCommonImpl::CredentialRequestResult::
kICloudKeychainSuccess
: AuthenticatorCommonImpl::CredentialRequestResult::
kICloudKeychainError;
case device::AuthenticatorType::kOther:
return success ? AuthenticatorCommonImpl::CredentialRequestResult::
kOtherSuccess
: AuthenticatorCommonImpl::CredentialRequestResult::
kOtherError;
case device::AuthenticatorType::kPhone:
return success ? AuthenticatorCommonImpl::CredentialRequestResult::
kPhoneSuccess
: AuthenticatorCommonImpl::CredentialRequestResult::
kPhoneError;
case device::AuthenticatorType::kTouchID:
return success ? AuthenticatorCommonImpl::CredentialRequestResult::
kTouchIDSuccess
: AuthenticatorCommonImpl::CredentialRequestResult::
kTouchIDError;
case device::AuthenticatorType::kWinNative:
return success ? AuthenticatorCommonImpl::CredentialRequestResult::
kWinNativeSuccess
: AuthenticatorCommonImpl::CredentialRequestResult::
kWinNativeError;
}
}
void RecordRegisterOutcomeMetric(std::optional<AuthenticationRequestMode> mode,
ukm::SourceId source_id,
MakeCredentialOutcome outcome) {
CHECK(mode.has_value());
CHECK(*mode != AuthenticationRequestMode::kConditional);
ukm::builders::WebAuthn_RegisterCompletion(source_id)
.SetRegisterCompletionResult(static_cast<int>(outcome))
.SetRequestMode(static_cast<int>(*mode))
.Record(ukm::UkmRecorder::Get());
}
void RecordSignOutcomeMetric(std::optional<AuthenticationRequestMode> mode,
ukm::SourceId source_id,
GetAssertionOutcome outcome) {
CHECK(mode.has_value());
ukm::builders::WebAuthn_SignCompletion(source_id)
.SetSignCompletionResult(static_cast<int>(outcome))
.SetRequestMode(static_cast<int>(*mode))
.Record(ukm::UkmRecorder::Get());
}
blink::mojom::WebAuthnClientCapabilityPtr MakeCapability(std::string name,
bool available) {
return blink::mojom::WebAuthnClientCapability::New(std::move(name),
available);
}
inline bool HasSupportedCapability(
const std::vector<blink::mojom::WebAuthnClientCapabilityPtr>& capabilities,
std::string_view capability_name) {
auto capability_it =
std::find_if(capabilities.begin(), capabilities.end(),
[&capability_name](const auto& capability) {
return capability->name == capability_name;
});
CHECK(capability_it != capabilities.end())
<< "Capability " << capability_name << " not found.";
return (*capability_it)->supported;
}
std::vector<blink::mojom::WebAuthnClientCapabilityPtr> InsertIsPPAACapability(
std::vector<blink::mojom::WebAuthnClientCapabilityPtr> capabilities) {
bool isUVPAA = HasSupportedCapability(
capabilities, client_capabilities::kUserVerifyingPlatformAuthenticator);
bool hybridTransport = HasSupportedCapability(
capabilities, client_capabilities::kHybridTransport);
capabilities.push_back(
MakeCapability(client_capabilities::kPasskeyPlatformAuthenticator,
isUVPAA || hybridTransport));
return capabilities;
}
void DeleteUnacceptedVirtualAuthenticatorCreds(
RenderFrameHost* render_frame_host,
std::string_view relying_party_id,
base::span<uint8_t> user_id,
base::span<std::vector<uint8_t>> all_accepted_credentials_ids) {
FrameTreeNode* frame_tree_node =
static_cast<RenderFrameHostImpl*>(render_frame_host)->frame_tree_node();
VirtualAuthenticatorManagerImpl* virtual_authenticator_manager =
AuthenticatorEnvironment::GetInstance()
->MaybeGetVirtualAuthenticatorManager(frame_tree_node);
if (!virtual_authenticator_manager) {
return;
}
for (VirtualAuthenticator* authenticator :
virtual_authenticator_manager->GetAuthenticators()) {
std::vector<std::vector<uint8_t>> credential_ids_to_remove;
for (const auto& registration : authenticator->registrations()) {
if (registration.second.user && registration.second.rp &&
registration.second.rp->id == relying_party_id &&
registration.second.user->id == user_id &&
!base::Contains(all_accepted_credentials_ids, registration.first)) {
credential_ids_to_remove.push_back(registration.first);
}
}
for (const std::vector<uint8_t>& credential_id : credential_ids_to_remove) {
authenticator->RemoveRegistration(credential_id);
}
}
}
void UpdateVirtualAuthenticatorUserCreds(RenderFrameHost* render_frame_host,
std::string_view relying_party_id,
base::span<uint8_t> user_id,
std::string_view name,
std::string_view display_name) {
FrameTreeNode* frame_tree_node =
static_cast<RenderFrameHostImpl*>(render_frame_host)->frame_tree_node();
VirtualAuthenticatorManagerImpl* virtual_authenticator_manager =
AuthenticatorEnvironment::GetInstance()
->MaybeGetVirtualAuthenticatorManager(frame_tree_node);
if (!virtual_authenticator_manager) {
return;
}
for (VirtualAuthenticator* authenticator :
virtual_authenticator_manager->GetAuthenticators()) {
authenticator->UpdateUserDetails(relying_party_id, user_id, name,
display_name);
}
}
void DeleteVirtualAuthenticatorCreds(
RenderFrameHost* render_frame_host,
const std::vector<uint8_t>& passkey_credential_id,
std::string_view relying_party_id) {
FrameTreeNode* frame_tree_node =
static_cast<RenderFrameHostImpl*>(render_frame_host)->frame_tree_node();
VirtualAuthenticatorManagerImpl* virtual_authenticator_manager =
AuthenticatorEnvironment::GetInstance()
->MaybeGetVirtualAuthenticatorManager(frame_tree_node);
if (!virtual_authenticator_manager) {
return;
}
for (VirtualAuthenticator* authenticator :
virtual_authenticator_manager->GetAuthenticators()) {
for (const auto& registration : authenticator->registrations()) {
if (registration.second.rp &&
registration.second.rp->id == relying_party_id &&
registration.first == passkey_credential_id) {
authenticator->RemoveRegistration(passkey_credential_id);
return;
}
}
}
}
auto GetCallbackForAssertion(GetCredentialCallback callback) {
return base::BindOnce(
[](blink::mojom::Authenticator::GetCredentialCallback callback,
blink::mojom::AuthenticatorStatus status,
blink::mojom::GetAssertionAuthenticatorResponsePtr credential,
blink::mojom::WebAuthnDOMExceptionDetailsPtr dom_exception_details) {
blink::mojom::GetAssertionResponsePtr assertion_response =
blink::mojom::GetAssertionResponse::New(
status, std::move(credential),
std::move(dom_exception_details));
blink::mojom::GetCredentialResponsePtr response =
blink::mojom::GetCredentialResponse::NewGetAssertionResponse(
std::move(assertion_response));
std::move(callback).Run(std::move(response));
},
std::move(callback));
}
} // namespace
// RequestState contains all state that is specific to a single WebAuthn call.
// Since `AuthenticatorCommonImpl` can service multiple calls, it's important
// that this state be reset after processing each one and collecting it into
// this structure makes that easier to enforce.
struct AuthenticatorCommonImpl::RequestState {
// Uniquely identifies this request in the scope of its owning
// `AuthenticatorCommonImpl`.
RequestKey request_key;
std::unique_ptr<AuthenticatorRequestClientDelegate> request_delegate;
std::unique_ptr<device::FidoRequestHandlerBase> request_handler;
std::unique_ptr<device::FidoDiscoveryFactory> discovery_factory;
// This dangling raw_ptr occurred in:
// interactive_ui_tests:
// WebAuthnDevtoolsAutofillIntegrationTest.SelectAccountWithAllowCredentials
// https://ci.chromium.org/ui/p/chromium/builders/try/mac-rel/1357012/test-results?q=ExactID%3Aninja%3A%2F%2Fchrome%2Ftest%3Ainteractive_ui_tests%2FWebAuthnDevtoolsAutofillIntegrationTest.SelectAccountWithAllowCredentials+VHash%3A81d118f1ad0b63a6
raw_ptr<device::FidoDiscoveryFactory,
FlakyDanglingUntriaged | AcrossTasksDanglingUntriaged>
discovery_factory_testing_override = nullptr;
std::variant<std::monostate,
MakeCredentialCallback,
GetCredentialCallback,
ReportCallback>
response_callback;
std::string client_data_json;
url::Origin caller_origin;
std::string relying_party_id;
std::unique_ptr<base::OneShotTimer> timer =
std::make_unique<base::OneShotTimer>();
std::optional<std::string> app_id;
std::variant<std::monostate,
device::CtapMakeCredentialRequest,
device::CtapGetAssertionRequest>
ctap_request;
std::variant<std::monostate,
device::MakeCredentialOptions,
device::CtapGetAssertionOptions>
request_options;
blink::mojom::AuthenticatorStatus error_awaiting_user_acknowledgement =
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR;
bool discoverable_credential_request = false;
// Indicates whether the current request is a modal WebAuthn call, a
// conditional UI WebAuthn call, or a payment-related request.
std::optional<AuthenticationRequestMode> mode;
// The hints set by the request, if any.
base::flat_set<blink::mojom::Hint> hints;
std::optional<CredentialRequestResult> request_result;
std::variant<std::monostate, MakeCredentialOutcome, GetAssertionOutcome>
request_outcome;
base::flat_set<RequestExtension> requested_extensions;
// The request ID of a pending proxied MakeCredential or GetAssertion request.
std::optional<WebAuthenticationRequestProxy::RequestId>
pending_proxied_request_id;
// A pending remote validation of an RP ID.
std::unique_ptr<WebAuthRequestSecurityChecker::RemoteValidation>
remote_rp_id_validation;
std::optional<Mediation> mediation_;
};
// static
std::unique_ptr<AuthenticatorCommon> AuthenticatorCommon::Create(
RenderFrameHost* render_frame_host) {
return std::make_unique<AuthenticatorCommonImpl>(
render_frame_host,
AuthenticatorCommonImpl::ServingRequestsFor::kInternalUses);
}
AuthenticatorCommonImpl::AuthenticatorCommonImpl(
RenderFrameHost* render_frame_host,
ServingRequestsFor serving_requests_for)
: render_frame_host_id_(render_frame_host->GetGlobalId()),
serving_requests_for_(serving_requests_for),
security_checker_(static_cast<RenderFrameHostImpl*>(render_frame_host)
->GetWebAuthRequestSecurityChecker()) {
// Disable the back-forward cache for any document that makes WebAuthn
// requests. Pages using privacy-sensitive APIs are generally exempt from
// back-forward cache for now as a precaution.
if (!base::FeatureList::IsEnabled(device::kWebAuthnNewBfCacheHandling)) {
BackForwardCache::DisableForRenderFrameHost(
render_frame_host,
BackForwardCacheDisable::DisabledReason(
BackForwardCacheDisable::DisabledReasonId::kWebAuthenticationAPI));
}
}
AuthenticatorCommonImpl::~AuthenticatorCommonImpl() = default;
std::unique_ptr<AuthenticatorRequestClientDelegate>
AuthenticatorCommonImpl::MaybeCreateRequestDelegate() {
RenderFrameHostImpl* const render_frame_host_impl =
static_cast<RenderFrameHostImpl*>(GetRenderFrameHost());
std::unique_ptr<AuthenticatorRequestClientDelegate> delegate =
GetContentClient()->browser()->GetWebAuthenticationRequestDelegate(
render_frame_host_impl);
if (!delegate) {
return nullptr;
}
VirtualAuthenticatorManagerImpl* virtual_authenticator_manager =
AuthenticatorEnvironment::GetInstance()
->MaybeGetVirtualAuthenticatorManager(
render_frame_host_impl->frame_tree_node());
if (virtual_authenticator_manager) {
delegate->SetVirtualEnvironment(true);
if (!virtual_authenticator_manager->is_ui_enabled()) {
DisableUI();
}
}
return delegate;
}
void AuthenticatorCommonImpl::StartMakeCredentialRequest(
bool allow_skipping_pin_touch) {
req_state_->request_result.reset();
InitDiscoveryFactory();
auto* ctap_make_credential_request =
&std::get<device::CtapMakeCredentialRequest>(req_state_->ctap_request);
auto* make_credential_options =
&std::get<device::MakeCredentialOptions>(req_state_->request_options);
req_state_->request_delegate->ConfigureDiscoveries(
req_state_->caller_origin, req_state_->relying_party_id, RequestSource(),
device::FidoRequestType::kMakeCredential,
make_credential_options->resident_key,
make_credential_options->user_verification,
ctap_make_credential_request->user.name,
base::span<const device::CableDiscoveryData>(),
browser_passkeys_available_, discovery_factory());
SetHints(req_state_->request_delegate.get(), req_state_->hints);
make_credential_options->allow_skipping_pin_touch = allow_skipping_pin_touch;
base::flat_set<device::FidoTransportProtocol> transports =
GetWebAuthnTransports(GetRenderFrameHost(), discovery_factory(),
UsesDiscoverableCreds(*make_credential_options),
is_uvpaa_override_);
auto platform_discoveries =
discovery_factory()->IsTestOverride()
? std::vector<std::unique_ptr<device::FidoDiscoveryBase>>()
: req_state_->request_delegate->CreatePlatformDiscoveries();
req_state_->request_handler =
std::make_unique<device::MakeCredentialRequestHandler>(
discovery_factory(), std::move(platform_discoveries), transports,
*ctap_make_credential_request, *make_credential_options,
base::BindOnce(&AuthenticatorCommonImpl::OnRegisterResponse,
weak_factory_.GetWeakPtr()));
req_state_->request_delegate->RegisterActionCallbacks(
base::BindOnce(&AuthenticatorCommonImpl::OnCancelFromUI,
weak_factory_.GetWeakPtr()) /* cancel_callback */,
base::DoNothing() /* immediate_not_found_callback */,
base::BindRepeating(
&AuthenticatorCommonImpl::StartMakeCredentialRequest,
weak_factory_.GetWeakPtr(),
/*allow_skipping_pin_touch=*/false) /* start_over_callback */,
base::DoNothing() /* account_preselected_callback */,
base::DoNothing() /*password_selected_callback */,
base::BindRepeating(
&device::FidoRequestHandlerBase::StartAuthenticatorRequest,
req_state_->request_handler->GetWeakPtr()) /* request_callback */,
base::BindRepeating(
&device::FidoRequestHandlerBase::PowerOnBluetoothAdapter,
req_state_->request_handler
->GetWeakPtr()) /* bluetooth_adapter_power_on_callback */,
base::BindRepeating(
&device::FidoRequestHandlerBase::RequestBluetoothPermission,
req_state_->request_handler
->GetWeakPtr()) /* request_ble_permission_callback */);
req_state_->request_handler->set_observer(req_state_->request_delegate.get());
}
void AuthenticatorCommonImpl::StartGetAssertionRequest(
bool allow_skipping_pin_touch) {
req_state_->request_result.reset();
InitDiscoveryFactory();
base::span<const device::CableDiscoveryData> cable_pairings;
auto* ctap_get_assertion_request =
&std::get<device::CtapGetAssertionRequest>(req_state_->ctap_request);
auto* ctap_get_assertion_options =
&std::get<device::CtapGetAssertionOptions>(req_state_->request_options);
if (ctap_get_assertion_request->cable_extension && IsFocused()) {
cable_pairings = *ctap_get_assertion_request->cable_extension;
}
req_state_->request_delegate->ConfigureDiscoveries(
req_state_->caller_origin, req_state_->relying_party_id, RequestSource(),
device::FidoRequestType::kGetAssertion,
/*resident_key_requirement=*/std::nullopt,
ctap_get_assertion_request->user_verification,
/*user_name=*/std::nullopt, cable_pairings, browser_passkeys_available_,
discovery_factory());
#if BUILDFLAG(IS_CHROMEOS)
discovery_factory()->set_get_assertion_request_for_legacy_credential_check(
*ctap_get_assertion_request);
#endif
SetHints(req_state_->request_delegate.get(), req_state_->hints);
bool is_immediate_mediation =
req_state_->mediation_.value_or(Mediation::MODAL) == Mediation::IMMEDIATE;
base::flat_set<device::FidoTransportProtocol> transports =
GetWebAuthnTransports(GetRenderFrameHost(), discovery_factory(),
UsesDiscoverableCreds(*ctap_get_assertion_request),
is_uvpaa_override_, is_immediate_mediation);
auto platform_discoveries =
discovery_factory()->IsTestOverride()
? std::vector<std::unique_ptr<device::FidoDiscoveryBase>>()
: req_state_->request_delegate->CreatePlatformDiscoveries();
auto request_handler = std::make_unique<device::GetAssertionRequestHandler>(
discovery_factory(), std::move(platform_discoveries), transports,
*ctap_get_assertion_request, *ctap_get_assertion_options,
allow_skipping_pin_touch,
base::BindOnce(&AuthenticatorCommonImpl::OnSignResponse,
weak_factory_.GetWeakPtr()));
req_state_->request_delegate->RegisterActionCallbacks(
base::BindOnce(&AuthenticatorCommonImpl::OnCancelFromUI,
weak_factory_.GetWeakPtr()) /* cancel_callback */,
base::BindOnce(
&AuthenticatorCommonImpl::CancelWithStatus,
weak_factory_.GetWeakPtr(),
blink::mojom::AuthenticatorStatus::
IMMEDIATE_NOT_FOUND) /* immediate_not_found_callback */,
base::BindRepeating(
&AuthenticatorCommonImpl::StartGetAssertionRequest,
weak_factory_.GetWeakPtr(),
/*allow_skipping_pin_touch=*/false) /* start_over_callback */,
base::BindRepeating(
&device::GetAssertionRequestHandler::PreselectAccount,
request_handler->GetWeakPtr()) /* account_preselected_callback */,
base::BindRepeating(
&AuthenticatorCommonImpl::HandlePasswordResponse,
weak_factory_.GetWeakPtr()) /*password_selected_callback */,
base::BindRepeating(
&device::GetAssertionRequestHandler::StartAuthenticatorRequest,
request_handler->GetWeakPtr()) /* request_callback */,
base::BindRepeating(
&device::FidoRequestHandlerBase::PowerOnBluetoothAdapter,
request_handler
->GetWeakPtr()) /* bluetooth_adapter_power_on_callback */,
base::BindRepeating(
&device::FidoRequestHandlerBase::RequestBluetoothPermission,
request_handler->GetWeakPtr()) /* request_ble_permission_callback */);
request_handler->set_observer(req_state_->request_delegate.get());
req_state_->request_handler = std::move(request_handler);
}
bool AuthenticatorCommonImpl::IsFocused() const {
return GetRenderFrameHost()->IsActive() &&
GetWebAuthenticationDelegate()->IsFocused(
WebContents::FromRenderFrameHost(GetRenderFrameHost()));
}
// mojom::Authenticator
void AuthenticatorCommonImpl::MakeCredential(
url::Origin caller_origin,
blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
MakeCredentialCallback callback) {
base::RecordAction(base::UserMetricsAction("WebAuthn.MakeCredential.Start"));
callback = base::BindOnce(
&AuthenticatorCommonImpl::GetMetricsWrappedMakeCredentialCallback,
weak_factory_.GetWeakPtr(), std::move(callback));
if (req_state_) {
std::move(callback).Run(blink::mojom::AuthenticatorStatus::PENDING_REQUEST,
nullptr, nullptr);
return;
}
req_state_ = std::make_unique<RequestState>();
req_state_->request_key = RequestKey(next_request_key_);
req_state_->response_callback = std::move(callback);
req_state_->hints.insert(options->hints.begin(), options->hints.end());
if (options->is_payment_credential_creation) {
req_state_->mode = AuthenticationRequestMode::kPayment;
} else if (options->is_conditional) {
if (!base::FeatureList::IsEnabled(device::kWebAuthnPasskeyUpgrade)) {
// The renderer runtime flag should enforce this.
mojo::ReportBadMessage("kWebAuthnPasskeyUpgrade flag must be enabled");
return;
}
req_state_->mode = AuthenticationRequestMode::kPasskeyUpgrade;
} else {
req_state_->mode = AuthenticationRequestMode::kModalWebAuthn;
}
BeginRequestTimeout(options->timeout);
WebAuthRequestSecurityChecker::RequestType request_type =
options->is_payment_credential_creation
? WebAuthRequestSecurityChecker::RequestType::kMakePaymentCredential
: WebAuthRequestSecurityChecker::RequestType::kMakeCredential;
bool is_cross_origin_iframe = false;
blink::mojom::AuthenticatorStatus status =
security_checker_->ValidateAncestorOrigins(caller_origin, request_type,
&is_cross_origin_iframe);
if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
req_state_->request_outcome = MakeCredentialOutcome::kSecurityError;
CompleteMakeCredentialRequest(status);
return;
}
if (!security_checker_->DeduplicateCredentialDescriptorListAndValidateLength(
&options->exclude_credentials)) {
mojo::ReportBadMessage("invalid exclude_credentials length");
req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
const std::string relying_party_id = options->relying_party.id;
const blink::mojom::RemoteDesktopClientOverridePtr&
remote_desktop_client_override = options->remote_desktop_client_override;
std::optional<url::Origin> remote_desktop_override_origin;
if (remote_desktop_client_override) {
// SECURITY: RemoteDesktopClientOverride comes from the renderer process and
// is untrusted. This `remote_desktop_override_origin` is only used after
// ValidateDomainAndRelyingPartyID verifies that the `caller_origin` is
// explicitly allowlisted via enterprise policy in
// WebAuthenticationDelegateBase::OriginMayUseRemoteDesktopClientOverride().
remote_desktop_override_origin = remote_desktop_client_override->origin;
}
std::unique_ptr<WebAuthRequestSecurityChecker::RemoteValidation>
remote_validation = security_checker_->ValidateDomainAndRelyingPartyID(
caller_origin, relying_party_id, request_type,
remote_desktop_override_origin,
base::BindOnce(
&AuthenticatorCommonImpl::ContinueMakeCredentialAfterRpIdCheck,
weak_factory_.GetWeakPtr(), GetRequestKey(), caller_origin,
std::move(options), is_cross_origin_iframe));
// If `remote_validation` is nullptr then the request may already have
// completed.
if (remote_validation) {
req_state_->remote_rp_id_validation = std::move(remote_validation);
}
}
void AuthenticatorCommonImpl::ContinueMakeCredentialAfterRpIdCheck(
RequestKey request_key,
url::Origin caller_origin,
blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
bool is_cross_origin_iframe,
blink::mojom::AuthenticatorStatus rp_id_validation_result) {
if (!CheckRequestKey(request_key)) {
return;
}
req_state_->remote_rp_id_validation.reset();
if (rp_id_validation_result != blink::mojom::AuthenticatorStatus::SUCCESS) {
req_state_->request_outcome = MakeCredentialOutcome::kSecurityError;
CompleteMakeCredentialRequest(rp_id_validation_result);
return;
}
req_state_->request_delegate = MaybeCreateRequestDelegate();
if (!req_state_->request_delegate) {
req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::PENDING_REQUEST);
return;
}
if (!req_state_->request_delegate->IsVirtualEnvironmentEnabled() &&
!disable_tls_check_ &&
!GetContentClient()->browser()->IsSecurityLevelAcceptableForWebAuthn(
GetRenderFrameHost(), caller_origin)) {
req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::CERTIFICATE_ERROR);
return;
}
req_state_->caller_origin = caller_origin;
req_state_->relying_party_id = options->relying_party.id;
std::optional<std::string> appid_exclude;
if (options->appid_exclude) {
appid_exclude = "";
auto add_id_status = security_checker_->ValidateAppIdExtension(
*options->appid_exclude, caller_origin,
options->remote_desktop_client_override, &appid_exclude.value());
if (add_id_status != blink::mojom::AuthenticatorStatus::SUCCESS) {
req_state_->request_outcome = MakeCredentialOutcome::kSecurityError;
CompleteMakeCredentialRequest(add_id_status);
return;
}
// `ValidateAppidExtension` must have set a value to use. If not, it would
// be a security bug, so crashing seems appropriate here.
CHECK(!appid_exclude->empty());
}
// If there is an active webAuthenticationProxy extension, let it handle the
// request.
WebAuthenticationRequestProxy* proxy =
GetWebAuthnRequestProxyIfActive(caller_origin);
if (proxy) {
if (options->remote_desktop_client_override) {
// Don't allow proxying of an already proxied request.
req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
options->remote_desktop_client_override =
blink::mojom::RemoteDesktopClientOverride::New(
/*origin=*/req_state_->caller_origin,
/*same_origin_with_ancestors=*/!is_cross_origin_iframe);
req_state_->pending_proxied_request_id = proxy->SignalCreateRequest(
options,
base::BindOnce(&AuthenticatorCommonImpl::OnMakeCredentialProxyResponse,
weak_factory_.GetWeakPtr(), GetRequestKey()));
return;
}
// Let the embedder override the RP ID to use for the request. In practice
// this rewrites the RP ID that Chrome extensions use.
std::optional<std::string> rp_id_override =
GetWebAuthenticationDelegate()->MaybeGetRelyingPartyIdOverride(
options->relying_party.id, caller_origin);
if (rp_id_override) {
options->relying_party.id = *rp_id_override;
req_state_->relying_party_id = *rp_id_override;
}
req_state_->request_delegate->SetRelyingPartyId(req_state_->relying_party_id);
req_state_->request_delegate->SetUserEntityForMakeCredentialRequest(
options->user);
device::fido_filter::MaybeInitialize();
switch (device::fido_filter::Evaluate(
device::fido_filter::Operation::MAKE_CREDENTIAL,
req_state_->relying_party_id,
/*device=*/std::nullopt,
/*id=*/std::nullopt)) {
case device::fido_filter::Action::ALLOW:
break;
case device::fido_filter::Action::NO_ATTESTATION:
// This will be handled by the request handler.
break;
case device::fido_filter::Action::BLOCK:
req_state_->request_outcome = MakeCredentialOutcome::kFilterBlock;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
if (!IsFocused()) {
req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::NOT_FOCUSED);
return;
}
const device::AuthenticatorSelectionCriteria
authenticator_selection_criteria =
options->authenticator_selection
? *options->authenticator_selection
: device::AuthenticatorSelectionCriteria();
req_state_->request_options =
device::MakeCredentialOptions(authenticator_selection_criteria);
auto* make_credential_options =
&std::get<device::MakeCredentialOptions>(req_state_->request_options);
make_credential_options->json =
base::MakeRefCounted<device::JSONRequest>(webauthn::ToValue(options));
make_credential_options->is_passkey_upgrade_request = options->is_conditional;
const bool might_create_resident_key =
make_credential_options->resident_key !=
device::ResidentKeyRequirement::kDiscouraged;
if (might_create_resident_key &&
!GetWebAuthenticationDelegate()->SupportsResidentKeys(
GetRenderFrameHost())) {
if (make_credential_options->resident_key ==
device::ResidentKeyRequirement::kRequired) {
req_state_->request_outcome = MakeCredentialOutcome::kRkNotSupported;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
return;
}
// Downgrade 'preferred' to 'discouraged'.
make_credential_options->resident_key =
device::ResidentKeyRequirement::kDiscouraged;
}
// Reject any non-sensical credProtect extension values.
if ( // Can't require the default policy (or no policy).
(options->enforce_protection_policy &&
(options->protection_policy ==
blink::mojom::ProtectionPolicy::UNSPECIFIED ||
options->protection_policy == blink::mojom::ProtectionPolicy::NONE)) ||
// For non-resident keys, NONE doesn't make sense. (UV_OR_CRED_ID_REQUIRED
// does because, with CTAP 2.0, just because a resident key isn't
// _required_ doesn't mean that one won't be created and an RP might want
// credProtect to take effect if that happens.)
(!might_create_resident_key &&
options->protection_policy == blink::mojom::ProtectionPolicy::NONE) ||
// UV_REQUIRED only makes sense if UV is required overall.
(options->protection_policy ==
blink::mojom::ProtectionPolicy::UV_REQUIRED &&
authenticator_selection_criteria.user_verification_requirement !=
device::UserVerificationRequirement::kRequired)) {
req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::PROTECTION_POLICY_INCONSISTENT);
return;
}
std::optional<device::CredProtectRequest> cred_protect_request =
ProtectionPolicyToCredProtect(options->protection_policy,
*make_credential_options);
if (cred_protect_request) {
make_credential_options->cred_protect_request = {
{*cred_protect_request, options->enforce_protection_policy}};
}
auto ui_presentation = UIPresentation::kModal;
if (disable_ui_) {
ui_presentation = UIPresentation::kDisabled;
} else if (options->is_conditional) {
ui_presentation = UIPresentation::kPasskeyUpgrade;
}
req_state_->request_delegate->SetUIPresentation(ui_presentation);
// Assemble clientDataJSON.
ClientDataJsonParams client_data_json_params(
ClientDataRequestType::kWebAuthnCreate, req_state_->caller_origin,
GetRenderFrameHost()->GetOutermostMainFrame()->GetLastCommittedOrigin(),
options->challenge, is_cross_origin_iframe);
if (options->remote_desktop_client_override) {
client_data_json_params.origin =
options->remote_desktop_client_override->origin;
client_data_json_params.is_cross_origin_iframe =
!options->remote_desktop_client_override->same_origin_with_ancestors;
}
req_state_->client_data_json =
BuildClientDataJson(std::move(client_data_json_params));
req_state_->ctap_request = device::CtapMakeCredentialRequest(
req_state_->client_data_json, options->relying_party, options->user,
device::PublicKeyCredentialParams(options->public_key_parameters));
auto* ctap_make_credential_request =
&std::get<device::CtapMakeCredentialRequest>(req_state_->ctap_request);
ctap_make_credential_request->exclude_list = options->exclude_credentials;
if (options->prf_enable) {
req_state_->requested_extensions.insert(RequestExtension::kPRF);
ctap_make_credential_request->hmac_secret = true;
if (options->prf_input) {
std::optional<device::PRFInput> prf_input =
ParsePRFInputForMakeCredential(options->prf_input);
if (!prf_input) {
mojo::ReportBadMessage("invalid PRF inputs");
req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
ctap_make_credential_request->prf_input = std::move(*prf_input);
}
}
if (options->hmac_create_secret) {
req_state_->requested_extensions.insert(RequestExtension::kHMACSecret);
ctap_make_credential_request->hmac_secret = true;
}
if (options->cred_props) {
req_state_->requested_extensions.insert(RequestExtension::kCredProps);
}
if (options->large_blob_enable != device::LargeBlobSupport::kNotRequested) {
req_state_->requested_extensions.insert(RequestExtension::kLargeBlobEnable);
}
if (options->cred_blob) {
req_state_->requested_extensions.insert(RequestExtension::kCredBlob);
ctap_make_credential_request->cred_blob = *options->cred_blob;
}
if (options->min_pin_length_requested) {
req_state_->requested_extensions.insert(RequestExtension::kMinPINLength);
ctap_make_credential_request->min_pin_length_requested = true;
}
make_credential_options->large_blob_support = options->large_blob_enable;
ctap_make_credential_request->app_id_exclude = std::move(appid_exclude);
make_credential_options->is_off_the_record_context =
GetBrowserContext()->IsOffTheRecord();
// Compute the effective attestation conveyance preference.
device::AttestationConveyancePreference attestation = options->attestation;
// Enterprise attestation should not have been approved by this point.
DCHECK_NE(
attestation,
device::AttestationConveyancePreference::kEnterpriseApprovedByBrowser);
if (attestation == device::AttestationConveyancePreference::
kEnterpriseIfRPListedOnAuthenticator &&
GetWebAuthenticationDelegate()->ShouldPermitIndividualAttestation(
GetBrowserContext(), caller_origin, req_state_->relying_party_id)) {
attestation =
device::AttestationConveyancePreference::kEnterpriseApprovedByBrowser;
}
ctap_make_credential_request->attestation_preference = attestation;
GetWebAuthenticationDelegate()->BrowserProvidedPasskeysAvailable(
GetBrowserContext(),
base::BindOnce(
&AuthenticatorCommonImpl::
ContinueMakeCredentialAfterBrowserPasskeysAvailabilityCheck,
weak_factory_.GetWeakPtr(), GetRequestKey()));
}
void AuthenticatorCommonImpl::
ContinueMakeCredentialAfterBrowserPasskeysAvailabilityCheck(
RequestKey request_key,
bool available) {
if (!CheckRequestKey(request_key)) {
return;
}
browser_passkeys_available_ = available;
GetWebAuthenticationDelegate()
->IsUserVerifyingPlatformAuthenticatorAvailableOverride(
GetRenderFrameHost(),
base::BindOnce(&AuthenticatorCommonImpl::
ContinueMakeCredentialAfterIsUvpaaOverrideCheck,
weak_factory_.GetWeakPtr(), GetRequestKey()));
}
void AuthenticatorCommonImpl::ContinueMakeCredentialAfterIsUvpaaOverrideCheck(
RequestKey request_key,
std::optional<bool> is_uvpaa_override) {
if (!CheckRequestKey(request_key)) {
return;
}
is_uvpaa_override_ = is_uvpaa_override;
StartMakeCredentialRequest(/*allow_skipping_pin_touch=*/true);
}
void AuthenticatorCommonImpl::GetCredential(
url::Origin caller_origin,
blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
blink::mojom::PaymentOptionsPtr payment_options,
GetCredentialCallback callback) {
if (options->mediation == Mediation::CONDITIONAL) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.GetAssertion.Conditional.Start"));
} else if (options->mediation == Mediation::MODAL) {
base::RecordAction(base::UserMetricsAction("WebAuthn.GetAssertion.Start"));
}
callback = base::BindOnce(
&AuthenticatorCommonImpl::GetMetricsWrappedGetCredentialCallback,
weak_factory_.GetWeakPtr(), std::move(callback));
if (req_state_) {
GetCallbackForAssertion(std::move(callback))
.Run(blink::mojom::AuthenticatorStatus::PENDING_REQUEST, nullptr,
nullptr);
return;
}
req_state_ = std::make_unique<RequestState>();
req_state_->request_key = RequestKey(next_request_key_);
req_state_->response_callback = std::move(callback);
if (!payment_options.is_null()) {
req_state_->mode = AuthenticationRequestMode::kPayment;
} else if (options->mediation == Mediation::CONDITIONAL) {
req_state_->mode = AuthenticationRequestMode::kConditional;
} else {
req_state_->mode = AuthenticationRequestMode::kModalWebAuthn;
}
req_state_->hints.insert(options->hints.begin(), options->hints.end());
if (options->mediation != Mediation::CONDITIONAL) {
BeginRequestTimeout(options->timeout);
}
if (options->challenge.has_value() == options->challenge_url.has_value()) {
mojo::ReportBadMessage(
"Exactly one of challenge and challenge_url must be provided");
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
if (options->mediation == Mediation::IMMEDIATE &&
!options->allow_credentials.empty()) {
mojo::ReportBadMessage(
"Immediate mediation cannot be used with an allow credential list");
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
req_state_->mediation_ = options->mediation;
if (options->challenge_url.has_value() &&
!options->challenge_url->is_valid()) {
mojo::ReportBadMessage("challenge_url must contain a valid URL");
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
WebAuthRequestSecurityChecker::RequestType request_type =
payment_options.is_null()
? WebAuthRequestSecurityChecker::RequestType::kGetAssertion
: WebAuthRequestSecurityChecker::RequestType::
kGetPaymentCredentialAssertion;
if (!payment_options.is_null() && options->allow_credentials.empty()) {
mojo::ReportBadMessage(
"PaymentOptions with empty allow_credentials is invalid");
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
bool is_cross_origin_iframe = false;
blink::mojom::AuthenticatorStatus status =
security_checker_->ValidateAncestorOrigins(caller_origin, request_type,
&is_cross_origin_iframe);
if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
req_state_->request_outcome = GetAssertionOutcome::kSecurityError;
CompleteGetAssertionRequest(status);
return;
}
if (!security_checker_->DeduplicateCredentialDescriptorListAndValidateLength(
&options->allow_credentials)) {
mojo::ReportBadMessage("invalid allow_credentials length");
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
const std::string relying_party_id = options->relying_party_id;
const blink::mojom::RemoteDesktopClientOverridePtr&
remote_desktop_client_override =
options->extensions->remote_desktop_client_override;
std::optional<url::Origin> remote_desktop_override_origin;
if (remote_desktop_client_override) {
// SECURITY: RemoteDesktopClientOverride comes from the renderer process and
// is untrusted. This `remote_desktop_override_origin` is only used after
// ValidateDomainAndRelyingPartyID verifies that the `caller_origin` is
// explicitly allowlisted via enterprise policy in
// WebAuthenticationDelegateBase::OriginMayUseRemoteDesktopClientOverride().
remote_desktop_override_origin = remote_desktop_client_override->origin;
}
std::unique_ptr<WebAuthRequestSecurityChecker::RemoteValidation>
remote_validation = security_checker_->ValidateDomainAndRelyingPartyID(
caller_origin, relying_party_id, request_type,
remote_desktop_override_origin,
base::BindOnce(
&AuthenticatorCommonImpl::ContinueGetAssertionAfterRpIdCheck,
weak_factory_.GetWeakPtr(), GetRequestKey(), caller_origin,
std::move(options), std::move(payment_options),
is_cross_origin_iframe));
// If `remote_validation` is nullptr then the request may already have
// completed.
if (remote_validation) {
req_state_->remote_rp_id_validation = std::move(remote_validation);
}
}
void AuthenticatorCommonImpl::ContinueGetAssertionAfterRpIdCheck(
RequestKey request_key,
url::Origin caller_origin,
blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
blink::mojom::PaymentOptionsPtr payment_options,
bool is_cross_origin_iframe,
blink::mojom::AuthenticatorStatus rp_id_validation_result) {
if (!CheckRequestKey(request_key)) {
return;
}
req_state_->remote_rp_id_validation.reset();
if (rp_id_validation_result != blink::mojom::AuthenticatorStatus::SUCCESS) {
req_state_->request_outcome = GetAssertionOutcome::kSecurityError;
CompleteGetAssertionRequest(rp_id_validation_result);
return;
}
req_state_->request_delegate = MaybeCreateRequestDelegate();
if (!req_state_->request_delegate) {
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::PENDING_REQUEST);
return;
}
if (!req_state_->request_delegate->IsVirtualEnvironmentEnabled() &&
!disable_tls_check_ &&
!GetContentClient()->browser()->IsSecurityLevelAcceptableForWebAuthn(
GetRenderFrameHost(), caller_origin)) {
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::CERTIFICATE_ERROR);
return;
}
req_state_->caller_origin = caller_origin;
req_state_->relying_party_id = options->relying_party_id;
if (options->extensions->appid) {
req_state_->requested_extensions.insert(RequestExtension::kAppID);
std::string app_id;
auto add_id_status = security_checker_->ValidateAppIdExtension(
*options->extensions->appid, caller_origin,
options->extensions->remote_desktop_client_override, &app_id);
if (add_id_status != blink::mojom::AuthenticatorStatus::SUCCESS) {
req_state_->request_outcome = GetAssertionOutcome::kSecurityError;
CompleteGetAssertionRequest(add_id_status);
return;
}
// `ValidateAppidExtension` must have set a value to use. If not, it would
// be a security bug, so crashing seems appropriate here.
CHECK(!app_id.empty());
req_state_->app_id = app_id;
}
WebAuthenticationRequestProxy* proxy =
GetWebAuthnRequestProxyIfActive(caller_origin);
if (proxy) {
if (options->mediation == Mediation::CONDITIONAL ||
(options->extensions->remote_desktop_client_override)) {
// Don't allow proxying of an already proxied or conditional request.
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
options->extensions->remote_desktop_client_override =
blink::mojom::RemoteDesktopClientOverride::New(
/*origin=*/req_state_->caller_origin,
/*same_origin_with_ancestors=*/!is_cross_origin_iframe);
req_state_->pending_proxied_request_id = proxy->SignalGetRequest(
options,
base::BindOnce(&AuthenticatorCommonImpl::OnGetAssertionProxyResponse,
weak_factory_.GetWeakPtr(), GetRequestKey()));
return;
}
// Let the embedder override the RP ID to use for the request. In practice
// this rewrites the RP ID that Chrome extension use.
std::optional<std::string> rp_id_override =
GetWebAuthenticationDelegate()->MaybeGetRelyingPartyIdOverride(
options->relying_party_id, caller_origin);
if (rp_id_override) {
options->relying_party_id = *rp_id_override;
req_state_->relying_party_id = *rp_id_override;
}
req_state_->request_delegate->SetRelyingPartyId(req_state_->relying_party_id);
device::fido_filter::MaybeInitialize();
if (device::fido_filter::Evaluate(
device::fido_filter::Operation::GET_ASSERTION,
req_state_->relying_party_id,
/*device=*/std::nullopt,
/*id=*/std::nullopt) == device::fido_filter::Action::BLOCK) {
req_state_->request_outcome = GetAssertionOutcome::kFilterBlock;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
auto ui_presentation = UIPresentation::kModal;
if (disable_ui_) {
ui_presentation = UIPresentation::kDisabled;
} else if (options->mediation == Mediation::CONDITIONAL) {
ui_presentation = UIPresentation::kAutofill;
} else if (options->mediation == Mediation::IMMEDIATE) {
ui_presentation = UIPresentation::kModalImmediate;
}
req_state_->request_delegate->SetUIPresentation(ui_presentation);
// Assemble clientDataJSON.
ClientDataJsonParams client_data_json_params(
ClientDataRequestType::kWebAuthnGet, caller_origin,
GetRenderFrameHost()->GetOutermostMainFrame()->GetLastCommittedOrigin(),
options->challenge, is_cross_origin_iframe);
if (payment_options) {
client_data_json_params.type = ClientDataRequestType::kPaymentGet;
client_data_json_params.payment_options = std::move(payment_options);
client_data_json_params.payment_rp = req_state_->relying_party_id;
} else if (options->extensions->remote_desktop_client_override) {
client_data_json_params.origin =
options->extensions->remote_desktop_client_override->origin;
client_data_json_params.is_cross_origin_iframe =
!options->extensions->remote_desktop_client_override
->same_origin_with_ancestors;
}
if (options->challenge.has_value()) {
req_state_->client_data_json =
BuildClientDataJson(std::move(client_data_json_params));
} else {
req_state_->request_delegate->ProvideChallengeUrl(
*options->challenge_url,
base::BindOnce(&AuthenticatorCommonImpl::UpdateChallengeFromUrl,
weak_factory_.GetWeakPtr(),
std::move(client_data_json_params)));
}
if (options->mediation == Mediation::CONDITIONAL ||
options->mediation == Mediation::IMMEDIATE) {
req_state_->request_delegate->SetCredentialTypes(
options->requested_credential_type_flags);
}
req_state_->request_delegate->SetCredentialIdFilter(
options->allow_credentials);
if (options->mediation == Mediation::CONDITIONAL) {
// Conditional mediation requests can only be fulfilled by discoverable
// credentials. The provided allowCredentials list is stripped and will be
// used to filter returned passkeys
options->allow_credentials =
std::vector<device::PublicKeyCredentialDescriptor>();
}
if (options->allow_credentials.empty()) {
if (!GetWebAuthenticationDelegate()->SupportsResidentKeys(
GetRenderFrameHost())) {
req_state_->request_outcome = GetAssertionOutcome::kRkNotSupported;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
return;
}
req_state_->discoverable_credential_request = true;
}
if (options->extensions->large_blob_read &&
options->extensions->large_blob_write) {
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::CANNOT_READ_AND_WRITE_LARGE_BLOB);
return;
}
if (options->extensions->large_blob_read) {
req_state_->requested_extensions.insert(RequestExtension::kLargeBlobRead);
} else if (options->extensions->large_blob_write) {
if (options->allow_credentials.size() != 1) {
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(blink::mojom::AuthenticatorStatus::
INVALID_ALLOW_CREDENTIALS_FOR_LARGE_BLOB);
return;
}
req_state_->requested_extensions.insert(RequestExtension::kLargeBlobWrite);
}
req_state_->ctap_request = CreateCtapGetAssertionRequest(
req_state_->client_data_json, options, req_state_->app_id);
auto* ctap_get_assertion_request =
&std::get<device::CtapGetAssertionRequest>(req_state_->ctap_request);
req_state_->request_options.emplace<device::CtapGetAssertionOptions>();
auto* ctap_get_assertion_options =
&std::get<device::CtapGetAssertionOptions>(req_state_->request_options);
ctap_get_assertion_options->is_off_the_record_context =
GetBrowserContext()->IsOffTheRecord();
ctap_get_assertion_options->json =
base::MakeRefCounted<device::JSONRequest>(webauthn::ToValue(options));
if (options->extensions->prf) {
req_state_->requested_extensions.insert(RequestExtension::kPRF);
std::optional<std::vector<device::PRFInput>> prf_inputs =
ParsePRFInputsForGetAssertion(options->extensions->prf_inputs);
// This should never happen for inputs from the renderer, which should sort
// the values itself.
if (!prf_inputs) {
mojo::ReportBadMessage("invalid PRF inputs");
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
ctap_get_assertion_options->prf_inputs = std::move(*prf_inputs);
}
if (options->extensions->get_cred_blob) {
req_state_->requested_extensions.insert(RequestExtension::kGetCredBlob);
ctap_get_assertion_request->get_cred_blob = true;
}
ctap_get_assertion_options->large_blob_read =
options->extensions->large_blob_read;
ctap_get_assertion_options->large_blob_write =
options->extensions->large_blob_write;
GetWebAuthenticationDelegate()->BrowserProvidedPasskeysAvailable(
GetBrowserContext(),
base::BindOnce(
&AuthenticatorCommonImpl::
ContinueGetAssertionAfterBrowserPasskeysAvailabilityCheck,
weak_factory_.GetWeakPtr(), GetRequestKey()));
}
void AuthenticatorCommonImpl::
ContinueGetAssertionAfterBrowserPasskeysAvailabilityCheck(
RequestKey request_key,
bool available) {
if (!CheckRequestKey(request_key)) {
return;
}
browser_passkeys_available_ = available;
GetWebAuthenticationDelegate()
->IsUserVerifyingPlatformAuthenticatorAvailableOverride(
GetRenderFrameHost(),
base::BindOnce(&AuthenticatorCommonImpl::
ContinueGetAssertionAfterIsUvpaaOverrideCheck,
weak_factory_.GetWeakPtr(), GetRequestKey()));
}
void AuthenticatorCommonImpl::ContinueGetAssertionAfterIsUvpaaOverrideCheck(
RequestKey request_key,
std::optional<bool> is_uvpaa_override) {
if (!CheckRequestKey(request_key)) {
return;
}
is_uvpaa_override_ = is_uvpaa_override;
StartGetAssertionRequest(/*allow_skipping_pin_touch=*/true);
}
void AuthenticatorCommonImpl::GetClientCapabilities(
url::Origin caller_origin,
blink::mojom::Authenticator::GetClientCapabilitiesCallback callback) {
// IsPPAA is computed based on the results of IsUVPAA and HybridTransport.
auto completion_callback =
base::BindOnce(&InsertIsPPAACapability).Then(std::move(callback));
bool immediate_get_enabled =
base::FeatureList::IsEnabled(device::kWebAuthnImmediateGet);
// IMPORTANT: If you add or remove a capability check below (and expect to
// collect the results of the check with the `BarrierCallback`), update this
// constant to match the number of `barrier_callback.Run()` calls. Otherwise,
// the `GetClientCapabilities()` call will crash or timeout.
const size_t kNumberOfComputedCapabilities = immediate_get_enabled ? 6 : 5;
auto barrier_callback =
base::BarrierCallback<blink::mojom::WebAuthnClientCapabilityPtr>(
kNumberOfComputedCapabilities, std::move(completion_callback));
barrier_callback.Run(
MakeCapability(client_capabilities::kRelatedOrigins, true));
barrier_callback.Run(MakeCapability(
client_capabilities::kConditionalCreate,
base::FeatureList::IsEnabled(device::kWebAuthnPasskeyUpgrade)));
IsHybridTransportSupported(
base::BindOnce(&MakeCapability, client_capabilities::kHybridTransport)
.Then(barrier_callback));
IsUvpaaAvailableInternal(
caller_origin,
base::BindOnce(&MakeCapability,
client_capabilities::kUserVerifyingPlatformAuthenticator)
.Then(barrier_callback),
/*is_get_client_capabilities_call=*/true);
IsConditionalMediationAvailable(
caller_origin,
base::BindOnce(&MakeCapability, client_capabilities::kConditionalGet)
.Then(barrier_callback));
if (immediate_get_enabled) {
barrier_callback.Run(
MakeCapability(client_capabilities::kImmediateGet, true));
}
}
void AuthenticatorCommonImpl::IsHybridTransportSupported(
base::OnceCallback<void(bool)> callback) {
if (!device::BluetoothAdapterFactory::Get()->IsLowEnergySupported()) {
std::move(callback).Run(false);
return;
}
device::BluetoothAdapterFactory::Get()->GetAdapter(
base::BindOnce([](scoped_refptr<device::BluetoothAdapter> adapter) {
return adapter && adapter->IsPresent();
}).Then(std::move(callback)));
}
void AuthenticatorCommonImpl::IsUvpaaAvailableInternal(
url::Origin caller_origin,
blink::mojom::Authenticator::
IsUserVerifyingPlatformAuthenticatorAvailableCallback callback,
bool is_get_client_capabilities_call) {
WebAuthenticationRequestProxy* proxy =
GetWebAuthnRequestProxyIfActive(caller_origin);
if (proxy) {
// Note that IsUvpaa requests can interleave with MakeCredential or
// GetAssertion, and cannot be cancelled. Thus, we do not set
// `req_state_->pending_proxied_request_id` here.
proxy->SignalIsUvpaaRequest(std::move(callback));
return;
}
// Check for a delegate override. Chrome overrides IsUVPAA() in Guest mode.
GetWebAuthenticationDelegate()
->IsUserVerifyingPlatformAuthenticatorAvailableOverride(
GetRenderFrameHost(),
base::BindOnce(
&AuthenticatorCommonImpl::ContinueIsUvpaaAfterOverrideCheck,
weak_factory_.GetWeakPtr(), std::move(callback),
is_get_client_capabilities_call));
}
void AuthenticatorCommonImpl::IsUserVerifyingPlatformAuthenticatorAvailable(
url::Origin caller_origin,
blink::mojom::Authenticator::
IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) {
IsUvpaaAvailableInternal(caller_origin, std::move(callback),
/*is_get_client_capabilities_call=*/false);
}
void AuthenticatorCommonImpl::ContinueIsUvpaaAfterOverrideCheck(
blink::mojom::Authenticator::
IsUserVerifyingPlatformAuthenticatorAvailableCallback callback,
bool is_get_client_capabilities_call,
std::optional<bool> is_uvpaa_override) {
if (is_uvpaa_override) {
std::move(callback).Run(*is_uvpaa_override);
return;
}
// Record IsUVPAA result in a UMA metric, but only if they're not the
// WebAuthenticationDelegate override value, so that results from the testing
// API and disabling in Guest/Off-The-Record profiles aren't counted.
auto uma_decorated_callback =
is_get_client_capabilities_call
? std::move(callback)
: base::BindOnce([](bool available) {
base::UmaHistogramBoolean(
"WebAuthentication.IsUVPlatformAuthenticatorAvailable2",
available);
return available;
}).Then(std::move(callback));
#if BUILDFLAG(IS_MAC)
IsUVPlatformAuthenticatorAvailable(GetBrowserContext(),
std::move(uma_decorated_callback));
#elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
IsUVPlatformAuthenticatorAvailable(std::move(uma_decorated_callback));
#else
std::move(uma_decorated_callback).Run(false);
#endif
}
void AuthenticatorCommonImpl::IsConditionalMediationAvailable(
url::Origin caller_origin,
blink::mojom::Authenticator::IsConditionalMediationAvailableCallback
callback) {
// Conditional mediation is always supported if the virtual environment is
// providing a platform authenticator.
GetWebAuthenticationDelegate()
->IsUserVerifyingPlatformAuthenticatorAvailableOverride(
GetRenderFrameHost(),
base::BindOnce(
&AuthenticatorCommonImpl::
ContinueIsConditionalMediationAvailableAfterOverrideCheck,
weak_factory_.GetWeakPtr(), std::move(caller_origin),
std::move(callback)));
}
void AuthenticatorCommonImpl::
ContinueIsConditionalMediationAvailableAfterOverrideCheck(
url::Origin caller_origin,
blink::mojom::Authenticator::IsConditionalMediationAvailableCallback
callback,
std::optional<bool> is_uvpaa_override) {
if (is_uvpaa_override.has_value()) {
std::move(callback).Run(*is_uvpaa_override);
return;
}
// Conditional requests cannot be proxied, signal the feature as unavailable.
if (GetWebAuthnRequestProxyIfActive(caller_origin)) {
std::move(callback).Run(false);
return;
}
// Desktop Chrome can always show GPM passkeys through conditional mediation.
std::move(callback).Run(true);
}
void AuthenticatorCommonImpl::Report(
url::Origin caller_origin,
blink::mojom::PublicKeyCredentialReportOptionsPtr options,
blink::mojom::Authenticator::ReportCallback callback) {
if (req_state_) {
std::move(callback).Run(blink::mojom::AuthenticatorStatus::PENDING_REQUEST,
nullptr);
return;
}
req_state_ = std::make_unique<RequestState>();
req_state_->request_key = RequestKey(next_request_key_);
req_state_->response_callback = std::move(callback);
req_state_->caller_origin = std::move(caller_origin);
req_state_->relying_party_id = options->relying_party_id;
bool is_cross_origin_iframe = false;
blink::mojom::AuthenticatorStatus status =
security_checker_->ValidateAncestorOrigins(
req_state_->caller_origin,
WebAuthRequestSecurityChecker::RequestType::kReport,
&is_cross_origin_iframe);
// TODO(crbug.com/347727501): Add test for ValidateAncestorOrigins's status.
if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
CompleteReportRequest(status);
return;
}
std::unique_ptr<WebAuthRequestSecurityChecker::RemoteValidation>
remote_validation = security_checker_->ValidateDomainAndRelyingPartyID(
req_state_->caller_origin, req_state_->relying_party_id,
WebAuthRequestSecurityChecker::RequestType::kReport,
/*remote_desktop_client_override_origin=*/std::nullopt,
base::BindOnce(&AuthenticatorCommonImpl::ContinueReportAfterRpIdCheck,
weak_factory_.GetWeakPtr(), GetRequestKey(),
std::move(options)));
// TODO(crbug.com/347727501): Add a test to cover the case when
// remote_validation is not null. If `remote_validation` is nullptr then the
// request may already have completed.
if (remote_validation) {
req_state_->remote_rp_id_validation = std::move(remote_validation);
}
}
void AuthenticatorCommonImpl::ContinueReportAfterRpIdCheck(
RequestKey request_key,
blink::mojom::PublicKeyCredentialReportOptionsPtr options,
blink::mojom::AuthenticatorStatus rp_id_validation_result) {
if (!CheckRequestKey(request_key)) {
return;
}
req_state_->remote_rp_id_validation.reset();
if (rp_id_validation_result != blink::mojom::AuthenticatorStatus::SUCCESS) {
CompleteReportRequest(rp_id_validation_result);
return;
}
RenderFrameHost* render_frame_host = GetRenderFrameHost();
if (options->all_accepted_credentials) {
DeleteUnacceptedVirtualAuthenticatorCreds(
render_frame_host, req_state_->relying_party_id,
options->all_accepted_credentials->user_id,
options->all_accepted_credentials->all_accepted_credentials_ids);
GetWebAuthenticationDelegate()->SignalAllAcceptedCredentials(
WebContents::FromRenderFrameHost(render_frame_host),
req_state_->caller_origin, req_state_->relying_party_id,
options->all_accepted_credentials->user_id,
options->all_accepted_credentials->all_accepted_credentials_ids);
#if BUILDFLAG(IS_WIN)
device::WinWebAuthnApiAuthenticator::SignalAllAcceptedCredentials(
device::WinWebAuthnApi::GetDefault(), req_state_->relying_party_id,
options->all_accepted_credentials->user_id,
options->all_accepted_credentials->all_accepted_credentials_ids);
#endif // BUILDFLAG(IS_WIN)
} else if (options->current_user_details) {
UpdateVirtualAuthenticatorUserCreds(
render_frame_host, req_state_->relying_party_id,
options->current_user_details->user_id,
options->current_user_details->name,
options->current_user_details->display_name);
GetWebAuthenticationDelegate()->UpdateUserPasskeys(
WebContents::FromRenderFrameHost(render_frame_host),
req_state_->caller_origin, req_state_->relying_party_id,
options->current_user_details->user_id,
options->current_user_details->name,
options->current_user_details->display_name);
} else if (options->unknown_credential_id) {
#if BUILDFLAG(IS_WIN)
device::WinWebAuthnApiAuthenticator::SignalUnknownCredential(
device::WinWebAuthnApi::GetDefault(), *options->unknown_credential_id,
req_state_->relying_party_id);
#endif // BUILDFLAG(IS_WIN)
DeleteVirtualAuthenticatorCreds(render_frame_host,
*options->unknown_credential_id,
req_state_->relying_party_id);
GetWebAuthenticationDelegate()->PasskeyUnrecognized(
WebContents::FromRenderFrameHost(render_frame_host),
req_state_->caller_origin, *options->unknown_credential_id,
req_state_->relying_party_id);
}
CompleteReportRequest(blink::mojom::AuthenticatorStatus::SUCCESS, nullptr);
}
void AuthenticatorCommonImpl::GetMetricsWrappedMakeCredentialCallback(
MakeCredentialCallback original_callback,
blink::mojom::AuthenticatorStatus status,
blink::mojom::MakeCredentialAuthenticatorResponsePtr authenticator_response,
blink::mojom::WebAuthnDOMExceptionDetailsPtr dom_exception_details) {
if (req_state_ &&
req_state_->request_result == CredentialRequestResult::kTimeout) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.MakeCredential.Timeout"));
} else if (req_state_ && req_state_->request_result ==
CredentialRequestResult::kUserCancelled) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.MakeCredential.Cancelled"));
} else if (status == blink::mojom::AuthenticatorStatus::SUCCESS) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.MakeCredential.Success"));
} else if (status == blink::mojom::AuthenticatorStatus::ABORT_ERROR) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.MakeCredential.Aborted"));
} else {
base::RecordAction(
base::UserMetricsAction("WebAuthn.MakeCredential.Failure"));
}
std::move(original_callback)
.Run(status, std::move(authenticator_response),
std::move(dom_exception_details));
}
void AuthenticatorCommonImpl::GetMetricsWrappedGetCredentialCallback(
blink::mojom::Authenticator::GetCredentialCallback callback,
blink::mojom::GetCredentialResponsePtr response) {
if (response.is_null() || response->is_password_response()) {
// TODO(crbug.com/392549444): add metrics for passwords.
std::move(callback).Run(std::move(response));
return;
}
const auto status = response->get_get_assertion_response()->status;
if (req_state_ &&
req_state_->request_result == CredentialRequestResult::kTimeout) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.GetAssertion.Timeout"));
} else if (req_state_ && req_state_->request_result ==
CredentialRequestResult::kUserCancelled) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.GetAssertion.Cancelled"));
} else if (status == blink::mojom::AuthenticatorStatus::SUCCESS) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.GetAssertion.Success"));
} else if (status == blink::mojom::AuthenticatorStatus::ABORT_ERROR) {
base::RecordAction(
base::UserMetricsAction("WebAuthn.GetAssertion.Aborted"));
} else {
base::RecordAction(
base::UserMetricsAction("WebAuthn.GetAssertion.Failure"));
}
std::move(callback).Run(std::move(response));
}
void AuthenticatorCommonImpl::Cancel() {
CancelWithStatus(blink::mojom::AuthenticatorStatus::ABORT_ERROR);
}
void AuthenticatorCommonImpl::OnRegisterResponse(
device::MakeCredentialStatus status_code,
std::optional<device::AuthenticatorMakeCredentialResponse> response_data,
const device::FidoAuthenticator* authenticator) {
CHECK(authenticator);
if (!req_state_->request_handler) {
// Either the callback was called immediately and
// |req_state_->request_handler| has not yet been assigned (this is a bug),
// or a navigation caused the request to be canceled while a callback was
// enqueued.
return;
}
req_state_->request_result = CredentialRequestResultFromCode(
status_code == device::MakeCredentialStatus::kSuccess,
authenticator->GetType());
switch (status_code) {
case device::MakeCredentialStatus::kUserConsentButCredentialExcluded:
case device::MakeCredentialStatus::kWinInvalidStateError:
// Duplicate registration: the new credential would be created on an
// authenticator that already contains one of the credentials in
// |exclude_credentials|. If the target was a platform authenticator then
// the RP learns of the result via the distinctive InvalidStateError
// result. This tells them that the platform authenticator is already
// registered with one of the credential IDs that they already know about.
//
// Windows already behaves like this and so its representation of
// InvalidStateError is handled this way too.
req_state_->request_outcome = MakeCredentialOutcome::kCredentialExcluded;
if ((authenticator &&
IsPlatformAuthenticatorForInvalidStateError(authenticator)) ||
status_code == device::MakeCredentialStatus::kWinInvalidStateError) {
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::CREDENTIAL_EXCLUDED, nullptr,
nullptr, Focus::kDoCheck);
} else {
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kKeyAlreadyRegistered,
blink::mojom::AuthenticatorStatus::CREDENTIAL_EXCLUDED);
}
return;
case device::MakeCredentialStatus::kAuthenticatorResponseInvalid:
req_state_->request_outcome =
MakeCredentialOutcome::kUnknownResponseFromAuthenticator;
// The response from the authenticator was corrupted.
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR, nullptr,
nullptr, Focus::kDoCheck);
return;
case device::MakeCredentialStatus::kHybridTransportError:
req_state_->request_outcome =
MakeCredentialOutcome::kHybridTransportError;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kHybridTransportError,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kEnclaveError:
req_state_->request_outcome = MakeCredentialOutcome::kEnclaveError;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kEnclaveError,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kUserConsentDenied:
req_state_->request_outcome = MakeCredentialOutcome::kUserCancellation;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kUserConsentDenied,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kSoftPINBlock:
req_state_->request_outcome = MakeCredentialOutcome::kSoftPinBlock;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kSoftPINBlock,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kHardPINBlock:
req_state_->request_outcome = MakeCredentialOutcome::kHardPinBlock;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kHardPINBlock,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kAuthenticatorRemovedDuringPINEntry:
req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kAuthenticatorRemovedDuringPINEntry,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kAuthenticatorMissingResidentKeys:
req_state_->request_outcome = MakeCredentialOutcome::kRkNotSupported;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kAuthenticatorMissingResidentKeys,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kAuthenticatorMissingUserVerification:
req_state_->request_outcome = MakeCredentialOutcome::kUvNotSupported;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kAuthenticatorMissingUserVerification,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kAuthenticatorMissingLargeBlob:
req_state_->request_outcome =
MakeCredentialOutcome::kLargeBlobNotSupported;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kAuthenticatorMissingLargeBlob,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kNoCommonAlgorithms:
req_state_->request_outcome =
MakeCredentialOutcome::kAlgorithmNotSupported;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kNoCommonAlgorithms,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kStorageFull:
req_state_->request_outcome = MakeCredentialOutcome::kStorageFull;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kStorageFull,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kWinNotAllowedError:
req_state_->request_outcome = MakeCredentialOutcome::kPlatformNotAllowed;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kWinUserCancelled,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kEnclaveCancel:
req_state_->request_outcome = MakeCredentialOutcome::kUserCancellation;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kEnclaveCancel,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::MakeCredentialStatus::kSuccess:
break;
}
DCHECK(response_data.has_value());
DCHECK(authenticator);
req_state_->request_delegate->OnTransactionSuccessful(
RequestSource(), device::FidoRequestType::kMakeCredential,
authenticator->GetType());
std::optional<device::FidoTransportProtocol> transport =
authenticator->AuthenticatorTransport();
bool is_transport_used_internal = false;
bool is_transport_used_cable = false;
if (transport) {
is_transport_used_internal =
*transport == device::FidoTransportProtocol::kInternal;
is_transport_used_cable =
*transport == device::FidoTransportProtocol::kHybrid;
}
const auto attestation =
std::get<device::CtapMakeCredentialRequest>(req_state_->ctap_request)
.attestation_preference;
std::optional<AttestationErasureOption> attestation_erasure;
if (response_data->attestation_should_be_filtered &&
!GetWebAuthenticationDelegate()->ShouldPermitIndividualAttestation(
GetBrowserContext(), req_state_->caller_origin,
req_state_->relying_party_id)) {
attestation_erasure = AttestationErasureOption::kEraseAttestationAndAaguid;
} else if (response_data->attestation_object
.IsAttestationCertificateInappropriatelyIdentifying() &&
!GetWebAuthenticationDelegate()->ShouldPermitIndividualAttestation(
GetBrowserContext(), req_state_->caller_origin,
req_state_->relying_party_id)) {
// If the RP sees a "none" attestation with a zero AAGUID after requesting
// "direct" attestation then they can reasonably conclude that it was one of
// the tokens with inappropriate certs. But this is better than disclosing
// the certificate itself, and these tokens are vanishingly rare now.
attestation_erasure = AttestationErasureOption::kEraseAttestationAndAaguid;
} else if (attestation == device::AttestationConveyancePreference::
kEnterpriseApprovedByBrowser) {
// If enterprise attestation was approved by policy then it can be
// returned immediately.
attestation_erasure = AttestationErasureOption::kIncludeAttestation;
} else if (response_data->attestation_object.IsSelfAttestation()) {
// Self attestation is just a self-signature and carries no identifying
// information.
attestation_erasure = AttestationErasureOption::kIncludeAttestation;
} else if (is_transport_used_internal || is_transport_used_cable) {
// Direct attestation from platform authenticators is known to be
// privacy preserving, so we always return it when requested. We follow the
// same rule when the platform authenticator is used over hybrid so that
// sites see a consistent experience between syncing and using the phone.
// Also, counter to what the WebAuthn spec says, we do not erase the AAGUID
// even when attestation wasn't requested.
attestation_erasure =
attestation != device::AttestationConveyancePreference::kNone
? AttestationErasureOption::kIncludeAttestation
: AttestationErasureOption::kEraseAttestationButIncludeAaguid;
} else if (attestation == device::AttestationConveyancePreference::kNone) {
attestation_erasure = AttestationErasureOption::kEraseAttestationAndAaguid;
} else {
// The UI will have shown a notification that attestation was requested.
attestation_erasure = AttestationErasureOption::kIncludeAttestation;
}
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::SUCCESS,
CreateMakeCredentialResponse(std::move(*response_data),
*attestation_erasure),
nullptr, Focus::kDoCheck);
}
void AuthenticatorCommonImpl::OnSignResponse(
device::GetAssertionStatus status_code,
std::optional<std::vector<device::AuthenticatorGetAssertionResponse>>
response_data,
device::FidoAuthenticator* authenticator) {
CHECK(authenticator);
DCHECK(!response_data || !response_data->empty()); // empty vector is invalid
if (!req_state_->request_handler) {
// Either the callback was called immediately and
// |req_state_->request_handler| has not yet been assigned (this is a bug),
// or a navigation caused the request to be canceled while a callback was
// enqueued.
return;
}
req_state_->request_result = CredentialRequestResultFromCode(
status_code == device::GetAssertionStatus::kSuccess,
authenticator->GetType());
switch (status_code) {
case device::GetAssertionStatus::kUserConsentButCredentialNotRecognized:
req_state_->request_outcome =
GetAssertionOutcome::kCredentialNotRecognized;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kKeyNotRegistered,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kAuthenticatorResponseInvalid:
req_state_->request_outcome =
GetAssertionOutcome::kUnknownResponseFromAuthenticator;
// The response from the authenticator was corrupted.
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kUserConsentDenied:
req_state_->request_outcome = GetAssertionOutcome::kUserCancellation;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kUserConsentDenied,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kSoftPINBlock:
req_state_->request_outcome = GetAssertionOutcome::kSoftPinBlock;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kSoftPINBlock,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kHardPINBlock:
req_state_->request_outcome = GetAssertionOutcome::kHardPinBlock;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kHardPINBlock,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kAuthenticatorRemovedDuringPINEntry:
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kAuthenticatorRemovedDuringPINEntry,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kAuthenticatorMissingResidentKeys:
req_state_->request_outcome = GetAssertionOutcome::kRkNotSupported;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kAuthenticatorMissingResidentKeys,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kAuthenticatorMissingUserVerification:
req_state_->request_outcome = GetAssertionOutcome::kUvNotSupported;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kAuthenticatorMissingUserVerification,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kWinNotAllowedError:
req_state_->request_outcome = GetAssertionOutcome::kPlatformNotAllowed;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kWinUserCancelled,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kHybridTransportError:
req_state_->request_outcome = GetAssertionOutcome::kHybridTransportError;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kHybridTransportError,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kICloudKeychainNoCredentials:
req_state_->request_outcome =
GetAssertionOutcome::kCredentialNotRecognized;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kNoPasskeys,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kEnclaveError:
req_state_->request_outcome = GetAssertionOutcome::kEnclaveError;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kEnclaveError,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kEnclaveCancel:
req_state_->request_outcome = GetAssertionOutcome::kUserCancellation;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kEnclaveCancel,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
case device::GetAssertionStatus::kSuccess:
break;
}
DCHECK_EQ(status_code, device::GetAssertionStatus::kSuccess);
DCHECK(response_data.has_value());
req_state_->request_delegate->OnTransactionSuccessful(
RequestSource(), device::FidoRequestType::kGetAssertion,
authenticator->GetType());
// Show an account picker for discoverable credential requests (empty allow
// lists). Responses with a single credential are considered pre-selected if
// one of the following is true:
// - The authenticator omitted user entity information because only one
// credential matched (only valid in CTAP 2.0).
// - The `userSelected` flag is set, because the user chose an account on an
// integrated authenticator UI (CTAP 2.1).
// - The user already pre-selected a platform authenticator credential from
// browser UI prior to the actual GetAssertion request. (The request handler
// set the `userSelected` flag in this case.)
if (response_data->size() == 1) {
const device::AuthenticatorGetAssertionResponse& response =
response_data->at(0);
if (!req_state_->discoverable_credential_request ||
response.user_selected || !response.user_entity ||
!response.user_entity->name || !response.user_entity->display_name) {
OnAccountSelected(std::move(response_data->at(0)));
return;
}
}
// Discoverable credential request without preselection UI. Show an account
// picker.
std::vector<device::PublicKeyCredentialUserEntity> users_list;
users_list.reserve(response_data->size());
for (const auto& response : *response_data) {
if (response.user_entity) {
users_list.push_back(*response.user_entity);
}
}
req_state_->request_delegate->SelectAccount(
std::move(*response_data),
base::BindOnce(&AuthenticatorCommonImpl::OnAccountSelected,
weak_factory_.GetWeakPtr()));
}
void AuthenticatorCommonImpl::OnAccountSelected(
device::AuthenticatorGetAssertionResponse response) {
CompleteGetAssertionRequest(blink::mojom::AuthenticatorStatus::SUCCESS,
CreateGetAssertionResponse(std::move(response)));
}
void AuthenticatorCommonImpl::SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason reason,
blink::mojom::AuthenticatorStatus status) {
req_state_->error_awaiting_user_acknowledgement = status;
// The UI may decide to end the request immediately, or after user
// confirmation. Either way stop discoveries and authenticators now.
if (req_state_->request_handler) {
req_state_->request_handler->StopDiscoveries();
req_state_->request_handler->CancelActiveAuthenticators();
}
if (req_state_->request_delegate->DoesBlockRequestOnFailure(reason)) {
// The UI may have decided to start the request over. Thus do not assume
// anything about the state here.
return;
}
// The UI wishes the end the request immediately.
CancelWithStatus(req_state_->error_awaiting_user_acknowledgement);
}
void AuthenticatorCommonImpl::BeginRequestTimeout(
std::optional<base::TimeDelta> timeout) {
req_state_->timer->Start(FROM_HERE,
AdjustTimeout(timeout, GetRenderFrameHost()),
base::BindOnce(&AuthenticatorCommonImpl::OnTimeout,
weak_factory_.GetWeakPtr()));
}
// TODO(crbug.com/41371792): Add web tests to verify timeouts are
// indistinguishable from NOT_ALLOWED_ERROR cases.
void AuthenticatorCommonImpl::OnTimeout() {
if (!req_state_->request_delegate) {
// If no UI has been shown yet (likely because we timed out waiting for RP
// ID validation) then simply cancel the request.
CancelWithStatus(blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
req_state_->request_result = CredentialRequestResult::kTimeout;
if (std::holds_alternative<GetCredentialCallback>(
req_state_->response_callback)) {
req_state_->request_outcome = GetAssertionOutcome::kUiTimeout;
} else {
req_state_->request_outcome = MakeCredentialOutcome::kUiTimeout;
}
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::kTimeout,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
}
void AuthenticatorCommonImpl::CancelWithStatus(
blink::mojom::AuthenticatorStatus status) {
// Callers may attempt to cancel whether there is a request or not.
if (!req_state_) {
return;
}
if (req_state_->pending_proxied_request_id) {
WebAuthenticationRequestProxy* proxy =
GetWebAuthenticationDelegate()->MaybeGetRequestProxy(
GetBrowserContext(), req_state_->caller_origin);
// As long as `req_state_->pending_proxied_request_id` is set, there should
// be an active request proxy. Deactivation of the proxy would have invoked
// `OnMakeCredentialProxyResponse()` or `OnGetAssertionProxyResponse()`, and
// cleared `req_state_->pending_proxied_request_id`
DCHECK(proxy);
proxy->CancelRequest(*req_state_->pending_proxied_request_id);
}
DCHECK(
!std::holds_alternative<std::monostate>(req_state_->response_callback));
if (std::holds_alternative<MakeCredentialCallback>(
req_state_->response_callback) &&
std::get<MakeCredentialCallback>(req_state_->response_callback)) {
CompleteMakeCredentialRequest(status);
return;
}
if (std::holds_alternative<GetCredentialCallback>(
req_state_->response_callback) &&
std::get<GetCredentialCallback>(req_state_->response_callback)) {
CompleteGetAssertionRequest(status);
}
}
void AuthenticatorCommonImpl::OnCancelFromUI() {
if (!req_state_->request_result &&
std::holds_alternative<GetCredentialCallback>(
req_state_->response_callback) &&
std::get<GetCredentialCallback>(req_state_->response_callback)) {
// The user cancelled before the request finished.
req_state_->request_result = CredentialRequestResult::kUserCancelled;
req_state_->request_outcome = GetAssertionOutcome::kUserCancellation;
} else if (!req_state_->request_result &&
std::holds_alternative<MakeCredentialCallback>(
req_state_->response_callback) &&
std::get<MakeCredentialCallback>(req_state_->response_callback)) {
req_state_->request_result = CredentialRequestResult::kUserCancelled;
req_state_->request_outcome = MakeCredentialOutcome::kUserCancellation;
}
CancelWithStatus(req_state_->error_awaiting_user_acknowledgement);
}
blink::mojom::MakeCredentialAuthenticatorResponsePtr
AuthenticatorCommonImpl::CreateMakeCredentialResponse(
device::AuthenticatorMakeCredentialResponse response_data,
AttestationErasureOption attestation_erasure) {
auto response = blink::mojom::MakeCredentialAuthenticatorResponse::New();
auto common_info = blink::mojom::CommonCredentialInfo::New();
common_info->client_data_json.assign(req_state_->client_data_json.begin(),
req_state_->client_data_json.end());
common_info->raw_id = response_data.attestation_object.GetCredentialId();
common_info->id = Base64UrlEncodeOmitPadding(common_info->raw_id);
response->authenticator_attachment =
response_data.transport_used
? device::AuthenticatorAttachmentFromTransport(
*response_data.transport_used)
: device::AuthenticatorAttachment::kAny;
base::flat_set<device::FidoTransportProtocol> transports;
// transports_authoritative tracks whether the contents of `transports` are
// considered to be sufficient complete to report back to the website.
bool transports_authoritative = false;
if (response_data.transport_used) {
transports.insert(*response_data.transport_used);
}
if (response_data.transports) {
transports.insert(response_data.transports->begin(),
response_data.transports->end());
transports_authoritative = true;
}
// Also include any transports from the attestation certificate.
std::optional<base::span<const uint8_t>> leaf_cert =
response_data.attestation_object.attestation_statement()
.GetLeafCertificate();
if (leaf_cert) {
transports_authoritative |=
AddTransportsFromCertificate(*leaf_cert, &transports);
}
if (!transports_authoritative &&
response_data.transport_used == device::FidoTransportProtocol::kHybrid) {
// Windows doesn't provide transport data, but can provide the transport
// used. If the transport was hybrid then we assume that ['hybrid',
// 'internal'] is a reasonable set of transports.
transports.insert(device::FidoTransportProtocol::kHybrid);
transports.insert(device::FidoTransportProtocol::kInternal);
transports_authoritative = true;
}
// The order of transports doesn't matter because Blink will sort the
// resulting strings before returning them.
if (transports_authoritative) {
response->transports.assign(transports.begin(), transports.end());
}
switch (attestation_erasure) {
case AttestationErasureOption::kIncludeAttestation:
break;
case AttestationErasureOption::kEraseAttestationButIncludeAaguid:
response_data.attestation_object.EraseAttestationStatement(
device::AttestationObject::AAGUID::kInclude);
break;
case AttestationErasureOption::kEraseAttestationAndAaguid:
response_data.attestation_object.EraseAttestationStatement(
device::AttestationObject::AAGUID::kErase);
break;
}
bool did_create_hmac_secret = response_data.prf_enabled;
bool did_store_cred_blob = false;
const std::optional<cbor::Value>& maybe_extensions =
response_data.attestation_object.authenticator_data().extensions();
if (maybe_extensions) {
DCHECK(maybe_extensions->is_map());
const cbor::Value::MapValue& extensions = maybe_extensions->GetMap();
if (!did_create_hmac_secret) {
const auto hmac_secret_it =
extensions.find(cbor::Value(device::kExtensionHmacSecret));
if (hmac_secret_it != extensions.end() &&
hmac_secret_it->second.is_bool() &&
hmac_secret_it->second.GetBool()) {
did_create_hmac_secret = true;
}
}
const auto cred_blob_it =
extensions.find(cbor::Value(device::kExtensionCredBlob));
if (cred_blob_it != extensions.end() && cred_blob_it->second.is_bool() &&
cred_blob_it->second.GetBool()) {
did_store_cred_blob = true;
}
}
for (const RequestExtension ext : req_state_->requested_extensions) {
switch (ext) {
case RequestExtension::kPRF:
response->echo_prf = true;
response->prf = did_create_hmac_secret;
if (response_data.prf_results) {
response->prf_results =
PRFResultsToValues(*response_data.prf_results);
}
break;
case RequestExtension::kHMACSecret:
response->echo_hmac_create_secret = true;
response->hmac_create_secret = did_create_hmac_secret;
break;
case RequestExtension::kCredProps:
response->echo_cred_props = true;
if (response_data.is_resident_key) {
response->has_cred_props_rk = true;
response->cred_props_rk = *response_data.is_resident_key;
}
break;
case RequestExtension::kLargeBlobEnable:
response->echo_large_blob = true;
response->supports_large_blob =
response_data.large_blob_type.has_value();
break;
case RequestExtension::kCredBlob:
response->echo_cred_blob = true;
response->cred_blob = did_store_cred_blob;
break;
case RequestExtension::kMinPINLength:
// Ignore. The spec says[1] that there's no client (i.e. browser)
// extension output (as opposed to the output in the returned
// authenticator data). This may have been a mistake but it can always
// be added later.
// [1]
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#sctn-minpinlength-extension
break;
case RequestExtension::kAppID:
case RequestExtension::kLargeBlobRead:
case RequestExtension::kLargeBlobWrite:
case RequestExtension::kGetCredBlob:
NOTREACHED();
}
}
response->attestation_object =
response_data.GetCBOREncodedAttestationObject();
common_info->authenticator_data =
response_data.attestation_object.authenticator_data()
.SerializeToByteArray();
response->info = std::move(common_info);
const device::PublicKey* public_key =
response_data.attestation_object.authenticator_data()
.attested_data()
->public_key();
response->public_key_algo = public_key->algorithm;
const std::optional<std::vector<uint8_t>>& public_key_der =
public_key->der_bytes;
if (public_key_der) {
response->public_key_der.emplace(public_key_der.value());
}
return response;
}
void AuthenticatorCommonImpl::CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus status,
blink::mojom::MakeCredentialAuthenticatorResponsePtr response,
blink::mojom::WebAuthnDOMExceptionDetailsPtr dom_exception_details,
Focus check_focus) {
DCHECK(std::holds_alternative<MakeCredentialCallback>(
req_state_->response_callback) &&
std::get<MakeCredentialCallback>(req_state_->response_callback));
auto make_credential_response_callback = std::move(
std::get<MakeCredentialCallback>(req_state_->response_callback));
if (req_state_->request_result) {
UMA_HISTOGRAM_ENUMERATION("WebAuthentication.MakeCredential.Result",
*req_state_->request_result);
}
if (std::holds_alternative<MakeCredentialOutcome>(
req_state_->request_outcome)) {
RecordRegisterOutcomeMetric(
req_state_->mode, GetRenderFrameHost()->GetPageUkmSourceId(),
std::get<MakeCredentialOutcome>(req_state_->request_outcome));
} else if (status == blink::mojom::AuthenticatorStatus::SUCCESS) {
RecordRegisterOutcomeMetric(req_state_->mode,
GetRenderFrameHost()->GetPageUkmSourceId(),
MakeCredentialOutcome::kSuccess);
}
if (check_focus != Focus::kDontCheck &&
!(req_state_->request_delegate && IsFocused())) {
std::move(make_credential_response_callback)
.Run(blink::mojom::AuthenticatorStatus::NOT_FOCUSED, nullptr, nullptr);
} else {
std::move(make_credential_response_callback)
.Run(status, std::move(response), std::move(dom_exception_details));
}
Cleanup();
}
blink::mojom::GetAssertionAuthenticatorResponsePtr
AuthenticatorCommonImpl::CreateGetAssertionResponse(
device::AuthenticatorGetAssertionResponse response_data) {
auto response = blink::mojom::GetAssertionAuthenticatorResponse::New();
auto common_info = blink::mojom::CommonCredentialInfo::New();
auto response_extensions =
blink::mojom::AuthenticationExtensionsClientOutputs::New();
common_info->client_data_json.assign(req_state_->client_data_json.begin(),
req_state_->client_data_json.end());
common_info->raw_id = response_data.credential->id;
common_info->id = Base64UrlEncodeOmitPadding(common_info->raw_id);
response->info = std::move(common_info);
response->info->authenticator_data =
response_data.authenticator_data.SerializeToByteArray();
response->signature = response_data.signature;
response->authenticator_attachment =
response_data.transport_used
? device::AuthenticatorAttachmentFromTransport(
*response_data.transport_used)
: device::AuthenticatorAttachment::kAny;
response_data.user_entity
? response->user_handle.emplace(response_data.user_entity->id)
: response->user_handle.emplace();
for (RequestExtension ext : req_state_->requested_extensions) {
switch (ext) {
case RequestExtension::kAppID:
DCHECK(req_state_->app_id);
response_extensions->echo_appid_extension = true;
if (response_data.authenticator_data.application_parameter() ==
CreateApplicationParameter(*req_state_->app_id)) {
response_extensions->appid_extension = true;
}
break;
case RequestExtension::kPRF: {
response_extensions->echo_prf = true;
if (response_data.hmac_secret) {
response_extensions->prf_results =
PRFResultsToValues(*response_data.hmac_secret);
} else {
response_extensions->prf_not_evaluated =
response_data.hmac_secret_not_evaluated;
}
break;
}
case RequestExtension::kLargeBlobRead:
response_extensions->echo_large_blob = true;
response_extensions->large_blob = response_data.large_blob;
break;
case RequestExtension::kLargeBlobWrite:
response_extensions->echo_large_blob = true;
response_extensions->echo_large_blob_written = true;
response_extensions->large_blob_written =
response_data.large_blob_written;
break;
case RequestExtension::kGetCredBlob: {
const std::optional<cbor::Value>& extensions =
response_data.authenticator_data.extensions();
if (extensions) {
const cbor::Value::MapValue& map = extensions->GetMap();
const auto& it = map.find(cbor::Value(device::kExtensionCredBlob));
if (it != map.end() && it->second.is_bytestring()) {
response_extensions->get_cred_blob = it->second.GetBytestring();
}
}
if (!response_extensions->get_cred_blob.has_value()) {
// The authenticator is supposed to return an empty byte string if it
// does not have a credBlob for the credential. But in case it
// doesn't, we return one to the caller anyway.
response_extensions->get_cred_blob = std::vector<uint8_t>();
}
break;
}
case RequestExtension::kHMACSecret:
case RequestExtension::kCredProps:
case RequestExtension::kLargeBlobEnable:
case RequestExtension::kCredBlob:
case RequestExtension::kMinPINLength:
NOTREACHED();
}
}
response->extensions = std::move(response_extensions);
return response;
}
void AuthenticatorCommonImpl::CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus status,
blink::mojom::GetAssertionAuthenticatorResponsePtr response,
blink::mojom::WebAuthnDOMExceptionDetailsPtr dom_exception_details) {
CHECK(std::holds_alternative<GetCredentialCallback>(
req_state_->response_callback) &&
std::get<GetCredentialCallback>(req_state_->response_callback));
auto get_credential_response_callback =
std::move(std::get<GetCredentialCallback>(req_state_->response_callback));
if (req_state_->request_result) {
UMA_HISTOGRAM_ENUMERATION("WebAuthentication.GetAssertion.Result",
*req_state_->request_result);
}
if (std::holds_alternative<GetAssertionOutcome>(
req_state_->request_outcome)) {
RecordSignOutcomeMetric(
req_state_->mode, GetRenderFrameHost()->GetPageUkmSourceId(),
std::get<GetAssertionOutcome>(req_state_->request_outcome));
} else if (status == blink::mojom::AuthenticatorStatus::SUCCESS) {
RecordSignOutcomeMetric(req_state_->mode,
GetRenderFrameHost()->GetPageUkmSourceId(),
GetAssertionOutcome::kSuccess);
}
if (status == blink::mojom::AuthenticatorStatus::SUCCESS) {
static_cast<RenderFrameHostImpl*>(GetRenderFrameHost())
->WebAuthnAssertionRequestSucceeded();
}
GetCallbackForAssertion(std::move(get_credential_response_callback))
.Run(status, std::move(response), std::move(dom_exception_details));
Cleanup();
}
void AuthenticatorCommonImpl::HandlePasswordResponse(
password_manager::CredentialInfo credential) {
CHECK(std::holds_alternative<GetCredentialCallback>(
req_state_->response_callback) &&
std::get<GetCredentialCallback>(req_state_->response_callback));
// TODO(crbug.com/393055190): Define the required metrics and emit them.
static_cast<RenderFrameHostImpl*>(GetRenderFrameHost())
->WebAuthnAssertionRequestSucceeded();
auto credential_response =
blink::mojom::GetCredentialResponse::NewPasswordResponse(
std::move(credential));
std::move(std::get<GetCredentialCallback>(req_state_->response_callback))
.Run(std::move(credential_response));
Cleanup();
}
void AuthenticatorCommonImpl::CompleteReportRequest(
blink::mojom::AuthenticatorStatus status,
blink::mojom::WebAuthnDOMExceptionDetailsPtr dom_exception_details) {
DCHECK(
std::holds_alternative<ReportCallback>(req_state_->response_callback) &&
std::get<ReportCallback>(req_state_->response_callback));
std::move(std::get<ReportCallback>(req_state_->response_callback))
.Run(status, std::move(dom_exception_details));
Cleanup();
}
void AuthenticatorCommonImpl::Cleanup() {
CHECK(!req_state_ || req_state_->request_key.value() == next_request_key_);
req_state_.reset();
next_request_key_++;
CHECK(next_request_key_); // crash on overflow. Only 2^64 WebAuthn requests
// per instance of this object are supported.
}
void AuthenticatorCommonImpl::DisableUI() {
// DisableUI() must be invoked before the request delegate is created, because
// the delegate needs know what type of UI, if any, is shown for the request.
CHECK(!req_state_ || !req_state_->request_delegate);
disable_ui_ = true;
}
void AuthenticatorCommonImpl::DisableTLSCheck() {
disable_tls_check_ = true;
}
RenderFrameHost* AuthenticatorCommonImpl::GetRenderFrameHost() const {
RenderFrameHost* ret = RenderFrameHost::FromID(render_frame_host_id_);
DCHECK(ret);
return ret;
}
AuthenticatorRequestClientDelegate::RequestSource
AuthenticatorCommonImpl::RequestSource() const {
if (serving_requests_for_ == ServingRequestsFor::kInternalUses) {
return AuthenticatorRequestClientDelegate::RequestSource::kInternal;
}
if (req_state_->mode == AuthenticationRequestMode::kPayment) {
return AuthenticatorRequestClientDelegate::RequestSource::
kSecurePaymentConfirmation;
}
return AuthenticatorRequestClientDelegate::RequestSource::kWebAuthentication;
}
BrowserContext* AuthenticatorCommonImpl::GetBrowserContext() const {
return GetRenderFrameHost()->GetBrowserContext();
}
device::FidoDiscoveryFactory* AuthenticatorCommonImpl::discovery_factory() {
DCHECK(req_state_->discovery_factory);
return req_state_->discovery_factory_testing_override
? req_state_->discovery_factory_testing_override.get()
: req_state_->discovery_factory.get();
}
void AuthenticatorCommonImpl::InitDiscoveryFactory() {
req_state_->discovery_factory = MakeDiscoveryFactory(GetRenderFrameHost());
// TODO(martinkr): |discovery_factory_testing_override_| is a long-lived
// VirtualFidoDeviceDiscovery so that tests can maintain and alter virtual
// authenticator state in between requests. We should extract a longer-lived
// configuration object from VirtualFidoDeviceDiscovery, so we can simply
// stick a short-lived instance into |discovery_factory_| and eliminate
// |discovery_factory_testing_override_|.
req_state_->discovery_factory_testing_override =
AuthenticatorEnvironment::GetInstance()
->MaybeGetDiscoveryFactoryTestOverride();
}
void AuthenticatorCommonImpl::EnableRequestProxyExtensionsAPISupport() {
enable_request_proxy_api_ = true;
}
WebAuthenticationRequestProxy*
AuthenticatorCommonImpl::GetWebAuthnRequestProxyIfActive(
const url::Origin& caller_origin) {
DCHECK(!caller_origin.opaque());
// The Virtual Authenticator, which can be activated via Dev Tools UI or
// ChromeDriver, should take precedence over request proxying. Otherwise
// attaching a remote desktop session would interfere with automated or manual
// testing.
const bool virtual_authenticator_active =
AuthenticatorEnvironment::GetInstance()
->MaybeGetVirtualAuthenticatorManager(
static_cast<RenderFrameHostImpl*>(GetRenderFrameHost())
->frame_tree_node()) != nullptr;
if (!enable_request_proxy_api_ || virtual_authenticator_active) {
return nullptr;
}
return GetWebAuthenticationDelegate()->MaybeGetRequestProxy(
GetBrowserContext(), caller_origin);
}
void AuthenticatorCommonImpl::OnMakeCredentialProxyResponse(
RequestKey request_key,
WebAuthenticationRequestProxy::RequestId request_id,
blink::mojom::WebAuthnDOMExceptionDetailsPtr error,
blink::mojom::MakeCredentialAuthenticatorResponsePtr response) {
if (!CheckRequestKey(request_key)) {
return;
}
DCHECK_EQ(*req_state_->pending_proxied_request_id, request_id);
DCHECK(std::holds_alternative<MakeCredentialCallback>(
req_state_->response_callback) &&
std::get<MakeCredentialCallback>(req_state_->response_callback));
req_state_->pending_proxied_request_id.reset();
if (error) {
DCHECK(!response);
CompleteMakeCredentialRequest(
blink::mojom::AuthenticatorStatus::ERROR_WITH_DOM_EXCEPTION_DETAILS,
nullptr, std::move(error), Focus::kDoCheck);
return;
}
CompleteMakeCredentialRequest(blink::mojom::AuthenticatorStatus::SUCCESS,
std::move(response), nullptr, Focus::kDoCheck);
}
void AuthenticatorCommonImpl::OnGetAssertionProxyResponse(
RequestKey request_key,
WebAuthenticationRequestProxy::RequestId request_id,
blink::mojom::WebAuthnDOMExceptionDetailsPtr error,
blink::mojom::GetAssertionAuthenticatorResponsePtr response) {
if (!CheckRequestKey(request_key)) {
return;
}
DCHECK_EQ(*req_state_->pending_proxied_request_id, request_id);
DCHECK(std::holds_alternative<GetCredentialCallback>(
req_state_->response_callback));
req_state_->pending_proxied_request_id.reset();
if (error) {
DCHECK(!response);
CompleteGetAssertionRequest(
blink::mojom::AuthenticatorStatus::ERROR_WITH_DOM_EXCEPTION_DETAILS,
nullptr, std::move(error));
return;
}
CompleteGetAssertionRequest(blink::mojom::AuthenticatorStatus::SUCCESS,
std::move(response));
}
void AuthenticatorCommonImpl::UpdateChallengeFromUrl(
ClientDataJsonParams params,
std::optional<base::span<const uint8_t>> challenge) {
// ChallengeUrl is only valid for GetAssertion requests.
CHECK(std::holds_alternative<device::CtapGetAssertionRequest>(
req_state_->ctap_request));
if (!challenge) {
// TODO(https://crbug.com/381219428): This might warrant a more specific
// error being returned to the RP. Also this should have its own logging
// value when it is no longer a prototype.
req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kChallengeUrlFailure,
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
return;
}
params.challenge = device::fido_parsing_utils::Materialize(*challenge);
req_state_->client_data_json = BuildClientDataJson(std::move(params));
std::get<device::CtapGetAssertionRequest>(req_state_->ctap_request)
.SetClientDataJson(req_state_->client_data_json);
reinterpret_cast<device::GetAssertionRequestHandler*>(
req_state_->request_handler.get())
->ProvideClientDataJson(req_state_->client_data_json);
}
AuthenticatorCommonImpl::RequestKey AuthenticatorCommonImpl::GetRequestKey() {
return req_state_->request_key;
}
bool AuthenticatorCommonImpl::CheckRequestKey(RequestKey request_key) {
return req_state_.get() && req_state_->request_key == request_key;
}
} // namespace content