// 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/flat_set.h"
#include "base/containers/span.h"
#include "base/containers/to_vector.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.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/notimplemented.h"
#include "base/notreached.h"
#include "base/stl_util.h"
#include "base/strings/string_view_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "components/webauthn/core/browser/common_utils.h"
#include "components/webauthn/core/browser/remote_validation.h"
#include "components/webauthn/core/browser/webauthn_security_utils.h"
#include "components/webauthn/json/value_conversions.h"
#include "content/browser/back_forward_cache/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_impl.h"
#include "content/browser/webauth/authenticator_request_outcome_enums.h"
#include "content/browser/webauth/client_data_json.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_impl.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/browser/webauth_request_security_checker.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/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_authenticator.h"
#include "device/fido/fido_request_handler_base.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/authenticator_selection_criteria.h"
#include "device/fido/public/features.h"
#include "device/fido/public/fido_constants.h"
#include "device/fido/public/fido_transport_protocol.h"
#include "device/fido/public/fido_types.h"
#include "device/fido/public/public_key_credential_descriptor.h"
#include "device/fido/public/public_key_credential_params.h"
#include "device/fido/public_key.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/common/features.h"
#include "third_party/blink/public/mojom/credentialmanagement/credential_type_flags.mojom.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 "base/strings/utf_string_conversions.h"
#include "device/fido/win/authenticator.h"
#include "device/fido/win/util.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,
  kCrossDeviceFallbackUrl,
  kCmtgKey,
};

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;
using RemoteDesktopParams = WebAuthRequestSecurityChecker::RemoteDesktopParams;

namespace {

const char kImmediateTimeoutWhileWaitingForUi[] =
    "WebAuthentication.GetAssertion.Immediate.TimeoutWhileWaitingForUi";

WebAuthenticationDelegate* GetWebAuthenticationDelegate() {
  return GetContentClient()->browser()->GetWebAuthenticationDelegate();
}

// Parses and validates a `clientDataJSON` string supplied by the renderer via
// the `remoteClientDataJSON` extension and returns the `origin` it carries.
std::optional<url::Origin> ExtractOriginFromClientDataJSON(
    const std::string& client_data_json,
    std::string_view expected_type) {
  std::optional<base::DictValue> dict =
      base::JSONReader::ReadDict(client_data_json, base::JSON_PARSE_RFC);
  if (!dict) {
    return std::nullopt;
  }

  const std::string* type = dict->FindString("type");
  if (!type || *type != expected_type) {
    return std::nullopt;
  }

  // Sanity-check `crossOrigin` if present.
  if (const base::Value* cross_origin = dict->Find("crossOrigin");
      cross_origin && !cross_origin->is_bool()) {
    return std::nullopt;
  }

  const std::string* origin_str = dict->FindString("origin");
  if (!origin_str) {
    return std::nullopt;
  }
  return url::Origin::Create(GURL(*origin_str));
}

// 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 && options->extensions->cross_device_fallback_url) {
    request_parameter.cross_device_fallback_url =
        options->extensions->cross_device_fallback_url->spec();
  }

  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.
  if (!timeout) {
    return device::kMaxRequestTimeout;
  }
  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(device::kMinRequestTimeout,
                  std::min(device::kMaxRequestTimeout, *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);
      transports.insert(device::FidoTransportProtocol::kSmartCard);
    }

    // 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);
  const auto [first, second] = results.split_at<32>();
  prf_values->first = base::ToVector(first);
  if (!second.empty()) {
    prf_values->second = base::ToVector(second);
  }

  return prf_values;
}

device::FidoTransportProtocol HintToTransport(blink::mojom::Hint hint) {
  switch (hint) {
    case blink::mojom::Hint::SECURITY_KEY:
      return device::FidoTransportProtocol::kUsbHumanInterfaceDevice;
    case blink::mojom::Hint::CLIENT_DEVICE:
      return device::FidoTransportProtocol::kInternal;
    case blink::mojom::Hint::HYBRID:
      return device::FidoTransportProtocol::kHybrid;
  }
}

void SetHints(AuthenticatorRequestClientDelegate* request_delegate,
              base::span<const blink::mojom::Hint> hints) {
  if (hints.empty()) {
    return;
  }
  AuthenticatorRequestClientDelegate::Hints delegate_hints;
  delegate_hints.transport = HintToTransport(hints.at(0u));
  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;
  }
}

CredentialRequestResult CredentialRequestResultFromCode(
    bool success,
    device::AuthenticatorType type) {
  switch (type) {
    case device::AuthenticatorType::kChromeOS:
      return success ? CredentialRequestResult::kChromeOSSuccess
                     : CredentialRequestResult::kChromeOSError;
    case device::AuthenticatorType::kEnclave:
      return success ? CredentialRequestResult::kEnclaveSuccess
                     : CredentialRequestResult::kEnclaveError;
    case device::AuthenticatorType::kICloudKeychain:
      return success ? CredentialRequestResult::kICloudKeychainSuccess
                     : CredentialRequestResult::kICloudKeychainError;
    case device::AuthenticatorType::kOther:
      return success ? CredentialRequestResult::kOtherSuccess
                     : CredentialRequestResult::kOtherError;
    case device::AuthenticatorType::kPhone:
      return success ? CredentialRequestResult::kPhoneSuccess
                     : CredentialRequestResult::kPhoneError;
    case device::AuthenticatorType::kTouchID:
      return success ? CredentialRequestResult::kTouchIDSuccess
                     : CredentialRequestResult::kTouchIDError;
    case device::AuthenticatorType::kWinNative:
      return success ? CredentialRequestResult::kWinNativeSuccess
                     : 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 &&
          !std::ranges::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;
      }
    }
  }
}

base::flat_set<device::FidoTransportProtocol> GetTransportsAllowedByRP(
    const device::CtapGetAssertionRequest& request) {
  const base::flat_set<device::FidoTransportProtocol> kAllTransports = {
      device::FidoTransportProtocol::kInternal,
      device::FidoTransportProtocol::kNearFieldCommunication,
      device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
      device::FidoTransportProtocol::kBluetoothLowEnergy,
      device::FidoTransportProtocol::kHybrid,
      device::FidoTransportProtocol::kSmartCard,
  };

  const auto& allowed_list = request.allow_list;
  if (allowed_list.empty()) {
    return kAllTransports;
  }

  base::flat_set<device::FidoTransportProtocol> transports;
  for (const auto& credential : allowed_list) {
    if (credential.transports.empty()) {
      return kAllTransports;
    }
    transports.insert(credential.transports.begin(),
                      credential.transports.end());
  }

  return transports;
}

void MaybeRecordBrowserAssistedLogin(CredentialRequestResult request_result) {
  using AssistedLoginType = ContentBrowserClient::AssistedLoginType;
  std::optional<AssistedLoginType> login_type;

  switch (request_result) {
    case CredentialRequestResult::kWinNativeSuccess:
      login_type = AssistedLoginType::kPasskeyStoredInWindowsHello;
      break;
    case CredentialRequestResult::kChromeOSSuccess:
    case CredentialRequestResult::kTouchIDSuccess:
      login_type = AssistedLoginType::kPasskeyStoredInChromeProfile;
      break;
    case CredentialRequestResult::kPhoneSuccess:
      login_type = AssistedLoginType::kPasskeyHybrid;
      break;
    case CredentialRequestResult::kICloudKeychainSuccess:
      login_type = AssistedLoginType::kPasskeyStoredInICloudKeychain;
      break;
    case CredentialRequestResult::kEnclaveSuccess:
      login_type = AssistedLoginType::kPasskeyStoredInGPM;
      break;
    case CredentialRequestResult::kOtherSuccess:
      login_type = AssistedLoginType::kPasskeySecurityKey;
      break;
    case CredentialRequestResult::kTimeout:
    case CredentialRequestResult::kUserCancelled:
    case CredentialRequestResult::kWinNativeError:
    case CredentialRequestResult::kTouchIDError:
    case CredentialRequestResult::kChromeOSError:
    case CredentialRequestResult::kPhoneError:
    case CredentialRequestResult::kICloudKeychainError:
    case CredentialRequestResult::kEnclaveError:
    case CredentialRequestResult::kOtherError:
          // Ignore error cases.
      break;
    case CredentialRequestResult::kAndroidFido2HybridSuccess:
    case CredentialRequestResult::kAndroidFido2Success:
    case CredentialRequestResult::kAndroidCredManSuccess:
    case CredentialRequestResult::kAndroidFido2LegacySuccess:
    case CredentialRequestResult::kAndroidFido2Error:
    case CredentialRequestResult::kAndroidCredManError:
    case CredentialRequestResult::kAndroidFido2HybridError:
    case CredentialRequestResult::kAndroidFido2LegacyError:
    case CredentialRequestResult::kAndroidIdentityCredentialsSuccess:
    case CredentialRequestResult::kAndroidIdentityCredentialsError:
      NOTREACHED();
  }
  if (login_type.has_value()) {
    GetContentClient()->browser()->RecordAssistedLogin(*login_type);
  }
}
}  // 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;
  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::unique_ptr<base::OneShotTimer> immediate_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.
  std::vector<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<webauthn::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)
                            ->GetWebAuthRequestSecurityCheckerImpl()) {}

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);
  bool discover_enclave = browser_passkeys_available_ &&
                          make_credential_options->authenticator_attachment !=
                              device::AuthenticatorAttachment::kCrossPlatform;
  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->cmtg_key,
      ctap_make_credential_request->user.name, discover_enclave,
      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::DoNothing() /* cancel_ui_timeout_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 */);
  // `StartObserving` can destroy `this`. It is not safe to refer to local
  // state after this call.
  req_state_->request_delegate->StartObserving(
      req_state_->request_handler.get());
}

void AuthenticatorCommonImpl::StartGetAssertionRequest(
    bool allow_skipping_pin_touch) {
  req_state_->request_result.reset();
  InitDiscoveryFactory();

  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);
  bool is_immediate_mediation =
      req_state_->mediation_.value_or(Mediation::MODAL) == Mediation::IMMEDIATE;
  base::flat_set<device::FidoTransportProtocol> transports =
      base::STLSetIntersection<base::flat_set<device::FidoTransportProtocol>>(
          GetWebAuthnTransports(
              GetRenderFrameHost(), discovery_factory(),
              UsesDiscoverableCreds(*ctap_get_assertion_request),
              is_uvpaa_override_, is_immediate_mediation),
          GetTransportsAllowedByRP(*ctap_get_assertion_request));
  bool discover_enclave =
      browser_passkeys_available_ &&
      transports.contains(device::FidoTransportProtocol::kInternal);
  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,
      ctap_get_assertion_request->cmtg_key,
      /*user_name=*/std::nullopt, discover_enclave, 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);

  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),
      std::move(transports), *ctap_get_assertion_request,
      *ctap_get_assertion_options, allow_skipping_pin_touch,
      base::BindOnce(&AuthenticatorCommonImpl::OnSignResponse,
                     weak_factory_.GetWeakPtr()));
  auto cancel_ui_timeout_callback =
      is_immediate_mediation
          ? base::BindOnce(&AuthenticatorCommonImpl::CancelImmediateTimeout,
                           weak_factory_.GetWeakPtr())
          : base::DoNothing();
  req_state_->request_delegate->RegisterActionCallbacks(
      base::BindOnce(&AuthenticatorCommonImpl::OnCancelFromUI,
                     weak_factory_.GetWeakPtr()) /* cancel_callback */,
      base::BindOnce(
          &AuthenticatorCommonImpl::CancelRequestForImmediateMediation,
          weak_factory_.GetWeakPtr()) /* 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 */,
      std::move(cancel_ui_timeout_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 */);

  req_state_->request_handler = std::move(request_handler);
  // `StartObserving` can destroy `this`. It is not safe to refer to local
  // state after this call.
  req_state_->request_delegate->StartObserving(
      req_state_->request_handler.get());
}

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,
    blink::mojom::PaymentOptionsPtr payment_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 = options->hints;

  if (options->is_payment_credential_creation) {
    req_state_->mode = AuthenticationRequestMode::kPayment;
  } else if (options->is_conditional) {
    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;
  }

  if (base::FeatureList::IsEnabled(device::kWebAuthnActorCheck) &&
      GetContentClient()->browser()->ShouldDisallowCredentialRequest(
          WebContents::FromRenderFrameHost(GetRenderFrameHost()))) {
    req_state_->request_outcome = MakeCredentialOutcome::kBlockedByEmbedder;
    CompleteMakeCredentialRequest(
        blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
    return;
  }

  if (options->remote_client_data_json &&
      (!base::FeatureList::IsEnabled(device::kWebAuthnRemoteClientDataJson) ||
       options->is_conditional)) {
    mojo::ReportBadMessage("invalid remoteClientDataJSON request");
    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<RemoteDesktopParams> remote_desktop_override;
  // The `remoteClientDataJSON` extension takes precedence over the older
  // `remoteDesktopClientOverride`.
  if (options->remote_client_data_json) {
    // Reject empty `rp.id` with NotAllowedError. The IDL declares `rp.id` as
    // a USVString with no presence requirement, so a missing one would
    // otherwise be silently treated as empty and only fail at later RP ID
    // validation.
    if (relying_party_id.empty()) {
      req_state_->request_outcome = MakeCredentialOutcome::kSecurityError;
      CompleteMakeCredentialRequest(
          blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
      return;
    }
    // RemoteClientDataJSON comes from the renderer process and is untrusted.
    // Extract the origin from the provided clientDataJSON for RP ID validation.
    // The origin is only used after ValidateDomainAndRelyingPartyID verifies
    // that the caller_origin is explicitly allowlisted.
    std::optional<url::Origin> extracted = ExtractOriginFromClientDataJSON(
        *options->remote_client_data_json, "webauthn.create");
    if (!extracted.has_value()) {
      req_state_->request_outcome = MakeCredentialOutcome::kOtherFailure;
      CompleteMakeCredentialRequest(
          blink::mojom::AuthenticatorStatus::REMOTE_CLIENT_DATA_JSON_INVALID);
      return;
    }
    remote_desktop_override = RemoteDesktopParams{
        .origin = *extracted, .skip_rp_id_validation = true};
  } else if (remote_desktop_client_override) {
    // RemoteDesktopClientOverride comes from the renderer process and is
    // untrusted. This `remote_desktop_override` is only used after
    // ValidateDomainAndRelyingPartyID verifies that the `caller_origin` is
    // explicitly allowlisted via enterprise policy in
    // WebAuthenticationDelegateBase::OriginMayUseRemoteDesktopClientOverride().
    remote_desktop_override =
        RemoteDesktopParams{.origin = remote_desktop_client_override->origin,
                            .skip_rp_id_validation = false};
  }
  std::unique_ptr<webauthn::RemoteValidation> remote_validation =
      security_checker_->ValidateDomainAndRelyingPartyID(
          caller_origin, relying_party_id, request_type,
          remote_desktop_override,
          base::BindOnce(
              &AuthenticatorCommonImpl::ContinueMakeCredentialAfterRpIdCheck,
              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::ContinueMakeCredentialAfterRpIdCheck(
    RequestKey request_key,
    url::Origin caller_origin,
    blink::mojom::PublicKeyCredentialCreationOptionsPtr 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 = 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 = "";
    std::optional<RemoteDesktopParams> remote_desktop_override;
    if (options->remote_client_data_json) {
      auto origin = ExtractOriginFromClientDataJSON(
          *options->remote_client_data_json, "webauthn.create");
      if (origin.has_value()) {
        remote_desktop_override = RemoteDesktopParams{
            .origin = *origin, .skip_rp_id_validation = true};
      }
    } else if (options->remote_desktop_client_override) {
      remote_desktop_override = RemoteDesktopParams{
          .origin = options->remote_desktop_client_override->origin,
          .skip_rp_id_validation = false};
    }
    auto add_id_status = security_checker_->ValidateAppIdExtension(
        *options->appid_exclude, caller_origin, remote_desktop_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 ||
        options->remote_client_data_json) {
      // 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.
  if (options->remote_client_data_json) {
    // Use the provided clientDataJSON directly instead of building one.
    req_state_->client_data_json = *options->remote_client_data_json;
  } else {
    webauthn::ClientDataJsonParams client_data_json_params(
        webauthn::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 = BuildClientDataJsonWithPayment(
        std::move(client_data_json_params), std::move(payment_options),
        /*payment_rp=*/"");
  }

  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;
  }
  if (options->cmtg_key) {
    req_state_->requested_extensions.insert(RequestExtension::kCmtgKey);
    ctap_make_credential_request->cmtg_key = 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();
  make_credential_options->hints = std::move(options->hints);

  // 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::GetCredentialOptionsPtr 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"));
  } else if (options->mediation == Mediation::IMMEDIATE) {
    base::RecordAction(base::UserMetricsAction("WebAuthn.GetCredential.Start"));
  }
  blink::mojom::PublicKeyCredentialRequestOptions* public_key_options =
      options->public_key.get();
  callback = base::BindOnce(
      &AuthenticatorCommonImpl::GetMetricsWrappedGetCredentialCallback,
      weak_factory_.GetWeakPtr(), std::move(callback));

  if (req_state_) {
    std::move(callback).Run(AuthenticatorImpl::MakeGetAssertionResponse(
        blink::mojom::AuthenticatorStatus::PENDING_REQUEST, nullptr, nullptr));
    return;
  }

  if (!public_key_options) {
    if (!options->password) {
      mojo::ReportBadMessage("At least one credential type must be requested.");
      std::move(callback).Run(AuthenticatorImpl::MakeGetAssertionResponse(
          blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR, nullptr,
          nullptr));
      return;
    }

    if (options->mediation != Mediation::IMMEDIATE) {
      mojo::ReportBadMessage(
          "Password-only credentials can only be requested with immediate "
          "mediation");
      std::move(callback).Run(AuthenticatorImpl::MakeGetAssertionResponse(
          blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR, nullptr,
          nullptr));
      return;
    }

    GetPasswordOnlyCredential(caller_origin, std::move(options),
                              std::move(payment_options), std::move(callback));
    return;
  }

  req_state_ = std::make_unique<RequestState>();
  req_state_->request_key = RequestKey(next_request_key_);

  req_state_->response_callback = std::move(callback);
  // TODO(crbug.com/358119268): Add Ambient request mode for logging.
  if (!payment_options.is_null()) {
    req_state_->mode = AuthenticationRequestMode::kPayment;
  } else if (options->mediation == Mediation::CONDITIONAL) {
    req_state_->mode = AuthenticationRequestMode::kConditional;
  } else if (options->mediation == Mediation::IMMEDIATE) {
    req_state_->mode = AuthenticationRequestMode::kImmediate;
    BeginImmediateRequestTimeout();
  } else {
    req_state_->mode = AuthenticationRequestMode::kModalWebAuthn;
  }
  req_state_->hints = public_key_options->hints;

  if (public_key_options->extensions->remote_client_data_json &&
      !base::FeatureList::IsEnabled(device::kWebAuthnRemoteClientDataJson)) {
    mojo::ReportBadMessage(
        "remoteClientDataJSON sent without the "
        "WebAuthenticationRemoteClientDataJson feature enabled");
    req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
    CompleteGetAssertionRequest(
        blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
    return;
  }

  if (options->mediation != Mediation::CONDITIONAL &&
      options->mediation != Mediation::AMBIENT) {
    BeginRequestTimeout(public_key_options->timeout);
  }

  if (options->mediation == Mediation::IMMEDIATE &&
      !public_key_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;
  }

  if ((public_key_options->extensions->remote_desktop_client_override ||
       public_key_options->extensions->remote_client_data_json) &&
      options->mediation == Mediation::IMMEDIATE) {
    mojo::ReportBadMessage(
        "Immediate mediation cannot be used with a remote desktop override "
        "request");
    req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
    CompleteGetAssertionRequest(
        blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
    return;
  }

  // remoteClientDataJSON forwards a verbatim clientDataJSON from a remote
  // desktop session. Conditional mediation (autofill UI) is incompatible with
  // that passthrough model, so the renderer should never combine them. Scoped
  // to remoteClientDataJSON only; the legacy remoteDesktopClientOverride
  // extension is unaffected.
  if (public_key_options->extensions->remote_client_data_json &&
      options->mediation == Mediation::CONDITIONAL) {
    mojo::ReportBadMessage(
        "Conditional mediation cannot be used with a remoteClientDataJSON "
        "request");
    req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
    CompleteGetAssertionRequest(
        blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
    return;
  }

  req_state_->mediation_ = options->mediation;

  WebAuthRequestSecurityChecker::RequestType request_type =
      payment_options.is_null()
          ? WebAuthRequestSecurityChecker::RequestType::kGetAssertion
          : WebAuthRequestSecurityChecker::RequestType::
                kGetPaymentCredentialAssertion;
  if (!payment_options.is_null() &&
      public_key_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(
          &public_key_options->allow_credentials)) {
    mojo::ReportBadMessage("invalid allow_credentials length");
    req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
    CompleteGetAssertionRequest(
        blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
    return;
  }

  if (base::FeatureList::IsEnabled(device::kWebAuthnActorCheck) &&
      GetContentClient()->browser()->ShouldDisallowCredentialRequest(
          WebContents::FromRenderFrameHost(GetRenderFrameHost()))) {
    req_state_->request_outcome = GetAssertionOutcome::kBlockedByEmbedder;
    CompleteGetAssertionRequest(
        blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
    return;
  }

  const std::string relying_party_id = public_key_options->relying_party_id;
  const blink::mojom::RemoteDesktopClientOverridePtr&
      remote_desktop_client_override =
          public_key_options->extensions->remote_desktop_client_override;
  std::optional<RemoteDesktopParams> remote_desktop_override;
  // The `remoteClientDataJSON` extension takes precedence over the older
  // `remoteDesktopClientOverride`.
  if (public_key_options->extensions->remote_client_data_json) {
    // Reject empty `rp.id` with NotAllowedError, mirroring the create() path.
    // The IDL declares `rp.id` as a USVString with no presence requirement, so
    // a missing one would otherwise be silently treated as empty. The spec
    // requires `rp.id` to be present for both registration and authentication.
    if (relying_party_id.empty()) {
      req_state_->request_outcome = GetAssertionOutcome::kSecurityError;
      CompleteGetAssertionRequest(
          blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
      return;
    }
    // RemoteClientDataJSON comes from the renderer process and is untrusted.
    // Extract the origin from the provided clientDataJSON for RP ID validation.
    // The origin is only used after ValidateDomainAndRelyingPartyID verifies
    // that the caller_origin is explicitly allowlisted.
    std::optional<url::Origin> extracted = ExtractOriginFromClientDataJSON(
        *public_key_options->extensions->remote_client_data_json,
        "webauthn.get");
    if (!extracted.has_value()) {
      req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
      CompleteGetAssertionRequest(
          blink::mojom::AuthenticatorStatus::REMOTE_CLIENT_DATA_JSON_INVALID);
      return;
    }
    remote_desktop_override = RemoteDesktopParams{
        .origin = *extracted, .skip_rp_id_validation = true};
  } else if (remote_desktop_client_override) {
    // RemoteDesktopClientOverride comes from the renderer process and is
    // untrusted. This `remote_desktop_override` is only used after
    // ValidateDomainAndRelyingPartyID verifies that the `caller_origin` is
    // explicitly allowlisted via enterprise policy in
    // WebAuthenticationDelegateBase::OriginMayUseRemoteDesktopClientOverride().
    remote_desktop_override =
        RemoteDesktopParams{.origin = remote_desktop_client_override->origin,
                            .skip_rp_id_validation = false};
  }
  std::unique_ptr<webauthn::RemoteValidation> remote_validation =
      security_checker_->ValidateDomainAndRelyingPartyID(
          caller_origin, relying_party_id, request_type,
          remote_desktop_override,
          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::GetPasswordOnlyCredential(
    url::Origin caller_origin,
    blink::mojom::GetCredentialOptionsPtr options,
    blink::mojom::PaymentOptionsPtr payment_options,
    GetCredentialCallback callback) {
  req_state_ = std::make_unique<RequestState>();
  req_state_->request_key = RequestKey(next_request_key_);
  req_state_->response_callback = std::move(callback);
  req_state_->mode = AuthenticationRequestMode::kImmediate;

  BeginImmediateRequestTimeout();

  bool is_cross_origin_iframe = false;
  blink::mojom::AuthenticatorStatus status =
      security_checker_->ValidateAncestorOrigins(
          caller_origin,
          WebAuthRequestSecurityChecker::RequestType::kGetAssertion,
          &is_cross_origin_iframe);
  if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
    req_state_->request_outcome = GetAssertionOutcome::kSecurityError;
    CompleteGetAssertionRequest(status);
    return;
  }

  if (base::FeatureList::IsEnabled(device::kWebAuthnActorCheck) &&
      GetContentClient()->browser()->ShouldDisallowCredentialRequest(
          WebContents::FromRenderFrameHost(GetRenderFrameHost()))) {
    req_state_->request_outcome = GetAssertionOutcome::kBlockedByEmbedder;
    CompleteGetAssertionRequest(
        blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
    return;
  }

  req_state_->request_delegate = MaybeCreateRequestDelegate();
  if (!req_state_->request_delegate) {
    req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
    CompleteGetAssertionRequest(
        blink::mojom::AuthenticatorStatus::PENDING_REQUEST);
    return;
  }

  // Passwords do not support virtual authenticators.
  req_state_->request_delegate->SetVirtualEnvironment(false);

  if (!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 = caller_origin.host();
  req_state_->request_delegate->SetRelyingPartyId(req_state_->relying_party_id);

  auto ui_presentation = UIPresentation::kModal;
  if (disable_ui_) {
    ui_presentation = UIPresentation::kDisabled;
  } else {
    ui_presentation = UIPresentation::kModalImmediate;
  }
  req_state_->request_delegate->SetUIPresentation(ui_presentation);
  req_state_->request_delegate->SetCredentialTypes(
      static_cast<int>(blink::mojom::CredentialTypeFlags::kPassword));

  auto cancel_ui_timeout_callback =
      base::BindOnce(&AuthenticatorCommonImpl::CancelImmediateTimeout,
                     weak_factory_.GetWeakPtr());

  req_state_->request_delegate->RegisterActionCallbacks(
      base::BindOnce(&AuthenticatorCommonImpl::OnCancelFromUI,
                     weak_factory_.GetWeakPtr()) /* cancel_callback */,
      base::BindOnce(
          &AuthenticatorCommonImpl::CancelRequestForImmediateMediation,
          weak_factory_.GetWeakPtr()) /* immediate_not_found_callback */,
      base::DoNothing() /* start_over_callback */,
      base::DoNothing() /* account_preselected_callback */,
      base::BindRepeating(
          &AuthenticatorCommonImpl::HandlePasswordResponse,
          weak_factory_.GetWeakPtr()) /*password_selected_callback */,
      base::DoNothing() /* request_callback */,
      std::move(cancel_ui_timeout_callback) /* cancel_ui_timeout_callback */,
      base::DoNothing() /* bluetooth_adapter_power_on_callback */,
      base::DoNothing() /* request_ble_permission_callback */);

  req_state_->request_delegate->ConfigureDiscoveries(
      req_state_->caller_origin, req_state_->relying_party_id, RequestSource(),
      device::FidoRequestType::kGetAssertion,
      /*resident_key_requirement=*/std::nullopt,
      device::UserVerificationRequirement::kDiscouraged,
      /*cmtg_key_requested=*/false,
      /*user_name=*/std::nullopt,
      /*is_enclave_authenticator_available=*/false,
      /*fido_discovery_factory=*/nullptr);
}

void AuthenticatorCommonImpl::ContinueGetAssertionAfterRpIdCheck(
    RequestKey request_key,
    url::Origin caller_origin,
    blink::mojom::GetCredentialOptionsPtr options,
    blink::mojom::PaymentOptionsPtr payment_options,
    bool is_cross_origin_iframe,
    blink::mojom::AuthenticatorStatus rp_id_validation_result) {
  if (!CheckRequestKey(request_key)) {
    return;
  }
  blink::mojom::PublicKeyCredentialRequestOptions* public_key_options =
      options->public_key.get();

  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 = public_key_options->relying_party_id;

  if (public_key_options->extensions->cross_device_fallback_url) {
    if (!base::FeatureList::IsEnabled(
            device::kWebAuthnCrossDeviceFallbackUrl)) {
      mojo::ReportBadMessage(
          "crossDeviceFallbackUrl extension sent but feature disabled");
      return;
    }
    if (!security_checker_->ValidateCrossDeviceFallbackUrl(
            public_key_options->relying_party_id,
            *public_key_options->extensions->cross_device_fallback_url)) {
      // TODO(crbug.com/509934168): Clarify if this should return an error.
      FIDO_LOG(ERROR) << "Invalid crossDeviceFallbackUrl extension value";
      public_key_options->extensions->cross_device_fallback_url = std::nullopt;
    } else {
      req_state_->requested_extensions.insert(
          RequestExtension::kCrossDeviceFallbackUrl);
    }
  }

  if (public_key_options->extensions->appid) {
    req_state_->requested_extensions.insert(RequestExtension::kAppID);
    std::string app_id;
    std::optional<RemoteDesktopParams> remote_desktop_override;
    if (public_key_options->extensions->remote_client_data_json) {
      auto origin = ExtractOriginFromClientDataJSON(
          *public_key_options->extensions->remote_client_data_json,
          "webauthn.get");
      if (origin.has_value()) {
        remote_desktop_override = RemoteDesktopParams{
            .origin = *origin, .skip_rp_id_validation = true};
      }
    } else if (public_key_options->extensions->remote_desktop_client_override) {
      remote_desktop_override = RemoteDesktopParams{
          .origin = public_key_options->extensions
                        ->remote_desktop_client_override->origin,
          .skip_rp_id_validation = false};
    }
    auto add_id_status = security_checker_->ValidateAppIdExtension(
        *public_key_options->extensions->appid, caller_origin,
        remote_desktop_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 ||
        public_key_options->extensions->remote_desktop_client_override ||
        public_key_options->extensions->remote_client_data_json) {
      // 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;
    }
    public_key_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->public_key,
        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(
          public_key_options->relying_party_id, caller_origin);
  if (rp_id_override) {
    public_key_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;
  } else if (options->mediation == Mediation::AMBIENT) {
    ui_presentation = UIPresentation::kAmbient;
  }
  req_state_->request_delegate->SetUIPresentation(ui_presentation);

  // Assemble clientDataJSON.
  if (public_key_options->extensions->remote_client_data_json) {
    req_state_->client_data_json =
        *public_key_options->extensions->remote_client_data_json;
  } else {
    webauthn::ClientDataJsonParams client_data_json_params(
        webauthn::ClientDataRequestType::kWebAuthnGet, caller_origin,
        GetRenderFrameHost()->GetOutermostMainFrame()->GetLastCommittedOrigin(),
        public_key_options->challenge, is_cross_origin_iframe);
    if (payment_options) {
      client_data_json_params.type =
          webauthn::ClientDataRequestType::kPaymentGet;
    } else if (public_key_options->extensions->remote_desktop_client_override) {
      client_data_json_params.origin =
          public_key_options->extensions->remote_desktop_client_override
              ->origin;
      client_data_json_params.is_cross_origin_iframe =
          !public_key_options->extensions->remote_desktop_client_override
               ->same_origin_with_ancestors;
    }

    req_state_->client_data_json = BuildClientDataJsonWithPayment(
        std::move(client_data_json_params), std::move(payment_options),
        req_state_->relying_party_id);
  }

  if (options->mediation == Mediation::CONDITIONAL ||
      options->mediation == Mediation::AMBIENT ||
      options->mediation == Mediation::IMMEDIATE) {
    // TODO(crbug.com/439510669) : Replace SetCredentialTypes with enums
    int requested_types = 0;
    if (public_key_options) {
      requested_types |=
          static_cast<int>(blink::mojom::CredentialTypeFlags::kPublicKey);
    }
    if (options->password) {
      requested_types |=
          static_cast<int>(blink::mojom::CredentialTypeFlags::kPassword);
    }
    req_state_->request_delegate->SetCredentialTypes(requested_types);
  }

  req_state_->request_delegate->SetCredentialIdFilter(
      public_key_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
    public_key_options->allow_credentials =
        std::vector<device::PublicKeyCredentialDescriptor>();
  }

  if (public_key_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 (public_key_options->extensions->large_blob_read &&
      public_key_options->extensions->large_blob_write) {
    req_state_->request_outcome = GetAssertionOutcome::kOtherFailure;
    CompleteGetAssertionRequest(
        blink::mojom::AuthenticatorStatus::CANNOT_READ_AND_WRITE_LARGE_BLOB);
    return;
  }

  if (public_key_options->extensions->large_blob_read) {
    req_state_->requested_extensions.insert(RequestExtension::kLargeBlobRead);
  } else if (public_key_options->extensions->large_blob_write) {
    if (public_key_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->public_key, 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->public_key));

  if (public_key_options->extensions->prf) {
    req_state_->requested_extensions.insert(RequestExtension::kPRF);

    std::optional<std::vector<device::PRFInput>> prf_inputs =
        ParsePRFInputsForGetAssertion(
            public_key_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 (public_key_options->extensions->get_cred_blob) {
    req_state_->requested_extensions.insert(RequestExtension::kGetCredBlob);
    ctap_get_assertion_request->get_cred_blob = true;
  }

  if (public_key_options->extensions->cmtg_key) {
    req_state_->requested_extensions.insert(RequestExtension::kCmtgKey);
    ctap_get_assertion_request->cmtg_key = true;
  }

  ctap_get_assertion_options->large_blob_read =
      public_key_options->extensions->large_blob_read;
  ctap_get_assertion_options->large_blob_write =
      public_key_options->extensions->large_blob_write;
  ctap_get_assertion_options->hints = std::move(public_key_options->hints);
  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 ambient_get_enabled =
      base::FeatureList::IsEnabled(device::kWebAuthnAmbientSignin);
  // 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 =
      9 + (ambient_get_enabled ? 1 : 0);
  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, true));

  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));
  barrier_callback.Run(
      MakeCapability(client_capabilities::kImmediateGet, true));
  if (ambient_get_enabled) {
    barrier_callback.Run(
        MakeCapability(client_capabilities::kAmbientGet, true));
  }

  barrier_callback.Run(
      MakeCapability(client_capabilities::kSignalAllAcceptedCredentials, true));
  barrier_callback.Run(
      MakeCapability(client_capabilities::kSignalCurrentUserDetails, true));
  barrier_callback.Run(
      MakeCapability(client_capabilities::kSignalUnknownCredential, 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;

  VirtualAuthenticatorManagerImpl* virtual_authenticator_manager =
      AuthenticatorEnvironment::GetInstance()
          ->MaybeGetVirtualAuthenticatorManager(
              static_cast<RenderFrameHostImpl*>(GetRenderFrameHost())
                  ->frame_tree_node());
  if (!virtual_authenticator_manager && !disable_tls_check_ &&
      !GetContentClient()->browser()->IsSecurityLevelAcceptableForWebAuthn(
          GetRenderFrameHost(), req_state_->caller_origin)) {
    CompleteReportRequest(blink::mojom::AuthenticatorStatus::CERTIFICATE_ERROR);
    return;
  }

  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<webauthn::RemoteValidation> remote_validation =
      security_checker_->ValidateDomainAndRelyingPartyID(
          req_state_->caller_origin, req_state_->relying_party_id,
          WebAuthRequestSecurityChecker::RequestType::kReport,
          /*remote_desktop_client_override=*/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()) {
    std::move(callback).Run(std::move(response));
    return;
  }
  if (response->is_password_response()) {
    base::RecordAction(
        base::UserMetricsAction("WebAuthn.GetCredential.SuccessWithPassword"));
    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());

  base::flat_set<device::FidoTransportProtocol> transports;
  std::optional<device::FidoTransportProtocol> authenticator_transport =
      authenticator->AuthenticatorTransport();
  if (authenticator_transport) {
    transports.emplace(*authenticator_transport);
  } else if (response_data->transports) {
    // On platforms where we delegate handling different transports to the OS,
    // use the list of transports reported by the credential instead.
    transports = *response_data->transports;
  }
  bool is_transport_used_internal =
      transports.contains(device::FidoTransportProtocol::kInternal);
  bool is_transport_used_cable =
      transports.contains(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::kCrossDeviceFallback:
      req_state_->request_outcome = GetAssertionOutcome::kCrossDeviceFallback;
      CompleteGetAssertionRequest(
          blink::mojom::AuthenticatorStatus::CROSS_DEVICE_FALLBACK);
      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()));
}

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::BeginImmediateRequestTimeout() {
  req_state_->immediate_timer->Start(
      FROM_HERE, base::Milliseconds(500),
      base::BindOnce(&AuthenticatorCommonImpl::OnImmediateTimeout,
                     weak_factory_.GetWeakPtr()));
}

void AuthenticatorCommonImpl::OnImmediateTimeout() {
  base::UmaHistogramBoolean(kImmediateTimeoutWhileWaitingForUi, true);
  base::UmaHistogramEnumeration(
      "WebAuthentication.GetAssertion.Immediate.RejectionReason",
      ImmediateMediationRejectionReason::kTimeout);
  CancelRequestForImmediateMediation();
}

void AuthenticatorCommonImpl::CancelImmediateTimeout() {
  if (!req_state_ || !req_state_->immediate_timer ||
      !req_state_->immediate_timer->IsRunning()) {
    return;
  }
  base::UmaHistogramBoolean(kImmediateTimeoutWhileWaitingForUi, false);
  req_state_->immediate_timer->Stop();
}

void AuthenticatorCommonImpl::CancelRequestForImmediateMediation() {
  // Post a task to defer the cancellation, otherwise a reentrancy may occur.
  // For example all authenticators may report no credentials; but the
  // discoveries may still be active. See crbug.com/424491613 for an example.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          [](base::WeakPtr<AuthenticatorCommonImpl> auth_ptr) {
            if (!auth_ptr) {
              return;
            }

            auth_ptr->CancelWithStatus(
                blink::mojom::AuthenticatorStatus::IMMEDIATE_NOT_FOUND);
          },
          weak_factory_.GetWeakPtr()));
}

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 = webauthn::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::kCmtgKey:
        if (response_data.cmtg_key) {
          response->cmtg_key = blink::mojom::CmtgKeyResponse::New();
          response->cmtg_key->cmtg_key = std::move(response_data.cmtg_key->key);
          response->cmtg_key->signature =
              std::move(response_data.cmtg_key->signature);
        }
        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:
      case RequestExtension::kCrossDeviceFallbackUrl:
        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 = webauthn::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::kCmtgKey:
        if (response_data.cmtg_key) {
          response_extensions->cmtg_key = blink::mojom::CmtgKeyResponse::New();
          response_extensions->cmtg_key->cmtg_key =
              std::move(response_data.cmtg_key->key);
          response_extensions->cmtg_key->signature =
              std::move(response_data.cmtg_key->signature);
        }
        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::kCrossDeviceFallbackUrl:
        response_extensions->cross_device_fallback_url = true;
        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);
    MaybeRecordBrowserAssistedLogin(*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();
  }

  std::move(get_credential_response_callback)
      .Run(AuthenticatorImpl::MakeGetAssertionResponse(
          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()` destroys the embedder request delegate which can
  // synchronously close UI which (via activation observers) may destroy the
  // hosting WebContents and therefore `this`. See https://crbug.com/521495992.
  base::WeakPtr<AuthenticatorCommonImpl> weak_this = weak_factory_.GetWeakPtr();
  req_state_.reset();
  if (!weak_this) {
    return;
  }

  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);
  device::FidoDiscoveryFactory* override_factory =
      AuthenticatorEnvironment::GetInstance()
          ->MaybeGetDiscoveryFactoryTestOverride();
  return override_factory ? override_factory
                          : req_state_->discovery_factory.get();
}

void AuthenticatorCommonImpl::InitDiscoveryFactory() {
  req_state_->discovery_factory = MakeDiscoveryFactory(GetRenderFrameHost());
}

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));
}

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;
}

bool AuthenticatorCommonImpl::IsGetMatchingCredentialIdsSupported() {
#if BUILDFLAG(IS_WIN)
  return device::WinWebAuthnApi::GetDefault() &&
         device::WinWebAuthnApi::GetDefault()->IsAvailable() &&
         device::WinWebAuthnApi::GetDefault()->SupportsSilentDiscovery() &&
         !device::fido::win::IsRemoteDesktopSession();
#else
  return false;
#endif
}

void AuthenticatorCommonImpl::GetMatchingCredentialIds(
    std::string_view relying_party_id,
    base::span<const std::vector<uint8_t>> credential_ids,
    bool require_third_party_payment_bit,
    base::OnceCallback<void(std::vector<std::vector<uint8_t>>)> callback) {
#if BUILDFLAG(IS_WIN)
  // Third-party payment bit checking in OS stores is not yet supported.
  // TODO(crbug.com/40868539): Support `require_third_party_payment_bit` by
  // checking for `bThirdPartyPayment` being present.
  CHECK(!require_third_party_payment_bit);

  if (!IsGetMatchingCredentialIdsSupported()) {
    std::move(callback).Run({});
    return;
  }

  device::WinWebAuthnApiAuthenticator::EnumeratePlatformCredentials(
      device::WinWebAuthnApi::GetDefault(), base::UTF8ToUTF16(relying_party_id),
      base::BindOnce(
          [](base::span<const std::vector<uint8_t>> credential_ids,
             base::OnceCallback<void(std::vector<std::vector<uint8_t>>)>
                 callback,
             std::vector<device::DiscoverableCredentialMetadata> credentials) {
            std::vector<std::vector<uint8_t>> matches;
            for (auto& credential : credentials) {
              if (std::ranges::contains(credential_ids, credential.cred_id)) {
                matches.push_back(std::move(credential.cred_id));
              }
            }
            std::move(callback).Run(std::move(matches));
          },
          base::ToVector(credential_ids), std::move(callback)));
#else
  std::move(callback).Run({});
#endif
}

}  // namespace content
