chromium/device/fido/win/webauthn_api.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/fido/win/webauthn_api.h"

#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util_win.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/scoped_thread_priority.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/features.h"
#include "device/fido/fido_types.h"
#include "device/fido/win/logging.h"
#include "device/fido/win/type_conversions.h"
#include "third_party/microsoft_webauthn/webauthn.h"

namespace device {

namespace {

WinWebAuthnApi* g_api_override = nullptr;

// Time out all Windows API requests after 5 minutes. We maintain our own
// timeout and cancel the operation when it expires, so this value simply needs
// to be larger than the largest internal request timeout.
constexpr uint32_t kWinWebAuthnTimeoutMilliseconds = 1000 * 60 * 5;

std::string HresultToHex(HRESULT hr) {
  return base::StringPrintf("0x%0lX", hr);
}

// FillHMACSalts converts `input` to the Windows representation of a pair of
// HMAC salt values, using `salts_storage` to own the returned pointer.
WEBAUTHN_HMAC_SECRET_SALT* FillHMACSalts(
    std::vector<WEBAUTHN_HMAC_SECRET_SALT>* salts_storage,
    const PRFInput& input) {
  const WEBAUTHN_HMAC_SECRET_SALT salts{
      base::checked_cast<DWORD>(input.salt1.size()),
      const_cast<PBYTE>(input.salt1.data()),
      input.salt2.has_value() ? base::checked_cast<DWORD>(input.salt2->size())
                              : 0,
      input.salt2.has_value() ? const_cast<PBYTE>(input.salt2->data())
                              : nullptr,
  };
  salts_storage->push_back(salts);
  return &salts_storage->back();
}

// FillHMACSaltValues converts `inputs` to the Windows representation of the
// PRF inputs and uses the `*_storage` arguments to own the returned structures.
WEBAUTHN_HMAC_SECRET_SALT_VALUES* FillHMACSaltValues(
    WEBAUTHN_HMAC_SECRET_SALT_VALUES* values_storage,
    std::vector<WEBAUTHN_HMAC_SECRET_SALT>* salts_storage,
    std::vector<WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT>* cred_salts_storage,
    const std::vector<PRFInput>& inputs) {
  if (inputs.empty()) {
    return nullptr;
  }

  memset(values_storage, 0, sizeof(*values_storage));
  // These vectors must not reallocate because the Windows structures will have
  // pointers into their elements.
  salts_storage->reserve(inputs.size());
  cred_salts_storage->reserve(inputs.size());

  for (const auto& input : inputs) {
    if (!input.credential_id.has_value()) {
      // Only the first input may omit the credential ID.
      DCHECK(cred_salts_storage->empty());
      values_storage->pGlobalHmacSalt = FillHMACSalts(salts_storage, input);
    } else {
      const WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT cred_salt{
          base::checked_cast<DWORD>(input.credential_id->size()),
          const_cast<PBYTE>(input.credential_id->data()),
          FillHMACSalts(salts_storage, input),
      };
      cred_salts_storage->push_back(cred_salt);
    }
  }

  if (!cred_salts_storage->empty()) {
    values_storage->cCredWithHmacSecretSaltList =
        base::checked_cast<DWORD>(cred_salts_storage->size());
    values_storage->pCredWithHmacSecretSaltList = cred_salts_storage->data();
  }

  return values_storage;
}

}  // namespace

class WinWebAuthnApiImpl : public WinWebAuthnApi {
 public:
  WinWebAuthnApiImpl() {
    if (!base::FeatureList::IsEnabled(device::kWebAuthUseNativeWinApi)) {
      FIDO_LOG(DEBUG) << "Windows WebAuthn API deactivated via feature flag";
      return;
    }
    {
      // Mitigate the issues caused by loading DLLs on a background thread
      // (http://crbug/973868).
      SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
      webauthn_dll_ =
          LoadLibraryExA("webauthn.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
    }
    if (!webauthn_dll_) {
      FIDO_LOG(ERROR) << "Windows WebAuthn API failed to load";
      return;
    }

#define BIND_FN(fn_pointer, lib_handle, fn_name)       \
  DCHECK(!fn_pointer);                                 \
  fn_pointer = reinterpret_cast<decltype(fn_pointer)>( \
      GetProcAddress(lib_handle, fn_name));

#define BIND_FN_OR_RETURN(fn_pointer, lib_handle, fn_name) \
  BIND_FN(fn_pointer, lib_handle, fn_name);                \
  if (!fn_pointer) {                                       \
    DLOG(ERROR) << "failed to bind " << fn_name;           \
    return;                                                \
  }

    BIND_FN_OR_RETURN(is_user_verifying_platform_authenticator_available_,
                      webauthn_dll_,
                      "WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable");
    BIND_FN_OR_RETURN(authenticator_make_credential_, webauthn_dll_,
                      "WebAuthNAuthenticatorMakeCredential");
    BIND_FN_OR_RETURN(authenticator_get_assertion_, webauthn_dll_,
                      "WebAuthNAuthenticatorGetAssertion");
    BIND_FN_OR_RETURN(cancel_current_operation_, webauthn_dll_,
                      "WebAuthNCancelCurrentOperation");
    BIND_FN_OR_RETURN(get_error_name_, webauthn_dll_, "WebAuthNGetErrorName");
    BIND_FN_OR_RETURN(free_credential_attestation_, webauthn_dll_,
                      "WebAuthNFreeCredentialAttestation");
    BIND_FN_OR_RETURN(free_assertion_, webauthn_dll_, "WebAuthNFreeAssertion");

    // The platform credential list set of functions was added in version 4.
    BIND_FN(get_platform_credential_list_, webauthn_dll_,
            "WebAuthNGetPlatformCredentialList");
    if (get_platform_credential_list_) {
      BIND_FN_OR_RETURN(free_platform_credential_list_, webauthn_dll_,
                        "WebAuthNFreePlatformCredentialList");
      BIND_FN_OR_RETURN(delete_platform_credential_, webauthn_dll_,
                        "WebAuthNDeletePlatformCredential");
    }

    is_bound_ = true;

    // Determine the API version of webauthn.dll. There is a version currently
    // shipping with Windows RS5 from before WebAuthNGetApiVersionNumber was
    // added (i.e., before WEBAUTHN_API_VERSION_1). Therefore we allow this
    // function to be missing.
    BIND_FN(get_api_version_number_, webauthn_dll_,
            "WebAuthNGetApiVersionNumber");
    api_version_ = get_api_version_number_ ? get_api_version_number_() : 0;

    FIDO_LOG(DEBUG) << "webauthn.dll version " << api_version_;
  }

  ~WinWebAuthnApiImpl() override = default;

  // WinWebAuthnApi:
  bool IsAvailable() const override {
    return base::FeatureList::IsEnabled(device::kWebAuthUseNativeWinApi) &&
           is_bound_ && (api_version_ >= WEBAUTHN_API_VERSION_1);
  }

  bool SupportsSilentDiscovery() const override {
    return get_platform_credential_list_;
  }

  HRESULT IsUserVerifyingPlatformAuthenticatorAvailable(
      BOOL* available) override {
    DCHECK(is_bound_);
    // Mitigate the issues caused by loading DLLs on a background thread
    // (http://crbug/973868).
    SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    return is_user_verifying_platform_authenticator_available_(available);
  }

  HRESULT AuthenticatorMakeCredential(
      HWND h_wnd,
      PCWEBAUTHN_RP_ENTITY_INFORMATION rp,
      PCWEBAUTHN_USER_ENTITY_INFORMATION user,
      PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose_credential_parameters,
      PCWEBAUTHN_CLIENT_DATA client_data,
      PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options,
      PWEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation_ptr) override {
    DCHECK(is_bound_);
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    return authenticator_make_credential_(
        h_wnd, rp, user, cose_credential_parameters, client_data, options,
        credential_attestation_ptr);
  }

  HRESULT AuthenticatorGetAssertion(
      HWND h_wnd,
      LPCWSTR rp_id,
      PCWEBAUTHN_CLIENT_DATA client_data,
      PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options,
      PWEBAUTHN_ASSERTION* assertion_ptr) override {
    DCHECK(is_bound_);
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    return authenticator_get_assertion_(h_wnd, rp_id, client_data, options,
                                        assertion_ptr);
  }

  HRESULT CancelCurrentOperation(GUID* cancellation_id) override {
    DCHECK(is_bound_);
    return cancel_current_operation_(cancellation_id);
  }

  HRESULT GetPlatformCredentialList(
      PCWEBAUTHN_GET_CREDENTIALS_OPTIONS options,
      PWEBAUTHN_CREDENTIAL_DETAILS_LIST* credentials) override {
    DCHECK(is_bound_ && get_platform_credential_list_);
    return get_platform_credential_list_(options, credentials);
  }

  HRESULT DeletePlatformCredential(
      base::span<const uint8_t> credential_id) override {
    return delete_platform_credential_(credential_id.size(),
                                       credential_id.data());
  }

  PCWSTR GetErrorName(HRESULT hr) override {
    DCHECK(is_bound_);
    return get_error_name_(hr);
  }

  void FreeCredentialAttestation(
      PWEBAUTHN_CREDENTIAL_ATTESTATION attestation_ptr) override {
    DCHECK(is_bound_);
    return free_credential_attestation_(attestation_ptr);
  }

  void FreeAssertion(PWEBAUTHN_ASSERTION assertion_ptr) override {
    DCHECK(is_bound_);
    return free_assertion_(assertion_ptr);
  }

  void FreePlatformCredentialList(
      PWEBAUTHN_CREDENTIAL_DETAILS_LIST credentials) override {
    DCHECK(is_bound_ && free_platform_credential_list_);
    free_platform_credential_list_(credentials);
  }

  int Version() override { return api_version_; }

 private:
  bool is_bound_ = false;
  uint32_t api_version_ = 0;
  HMODULE webauthn_dll_;

  decltype(&WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable)
      is_user_verifying_platform_authenticator_available_ = nullptr;
  decltype(&WebAuthNAuthenticatorMakeCredential)
      authenticator_make_credential_ = nullptr;
  decltype(&WebAuthNAuthenticatorGetAssertion) authenticator_get_assertion_ =
      nullptr;
  decltype(&WebAuthNCancelCurrentOperation) cancel_current_operation_ = nullptr;
  decltype(&WebAuthNGetPlatformCredentialList) get_platform_credential_list_ =
      nullptr;
  decltype(&WebAuthNDeletePlatformCredential) delete_platform_credential_ =
      nullptr;
  decltype(&WebAuthNGetErrorName) get_error_name_ = nullptr;
  decltype(&WebAuthNFreeCredentialAttestation) free_credential_attestation_ =
      nullptr;
  decltype(&WebAuthNFreeAssertion) free_assertion_ = nullptr;
  decltype(&WebAuthNFreePlatformCredentialList) free_platform_credential_list_ =
      nullptr;

  // This method is not available in all versions of webauthn.dll.
  decltype(&WebAuthNGetApiVersionNumber) get_api_version_number_ = nullptr;
};

WinWebAuthnApi::ScopedOverride::ScopedOverride(WinWebAuthnApi* api) {
  CHECK(api);
  CHECK(!g_api_override);
  g_api_override = api;
}

WinWebAuthnApi::ScopedOverride::~ScopedOverride() {
  CHECK(g_api_override);
  g_api_override = nullptr;
}

// static
WinWebAuthnApi* WinWebAuthnApi::GetDefault() {
  if (g_api_override) {
    return g_api_override;
  }
  static base::NoDestructor<WinWebAuthnApiImpl> api;
  return api.get();
}

WinWebAuthnApi::WinWebAuthnApi() = default;

WinWebAuthnApi::~WinWebAuthnApi() = default;

bool WinWebAuthnApi::SupportsHybrid() {
  return IsAvailable() && Version() >= WEBAUTHN_API_VERSION_6;
}

std::pair<MakeCredentialStatus,
          std::optional<AuthenticatorMakeCredentialResponse>>
AuthenticatorMakeCredentialBlocking(WinWebAuthnApi* webauthn_api,
                                    HWND h_wnd,
                                    GUID cancellation_id,
                                    CtapMakeCredentialRequest request,
                                    MakeCredentialOptions request_options) {
  DCHECK(webauthn_api->IsAvailable());
  const int api_version = webauthn_api->Version();
  DCHECK(
      request_options.large_blob_support != LargeBlobSupport::kRequired ||
      (api_version >= WEBAUTHN_API_VERSION_3 &&
       request.authenticator_attachment != AuthenticatorAttachment::kPlatform));

  std::u16string rp_id = base::UTF8ToUTF16(request.rp.id);
  std::u16string rp_name = base::UTF8ToUTF16(request.rp.name.value_or(""));
  WEBAUTHN_RP_ENTITY_INFORMATION rp_info{
      WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION, base::as_wcstr(rp_id),
      base::as_wcstr(rp_name),
      /*pwszIcon=*/nullptr};

  std::u16string user_name = base::UTF8ToUTF16(request.user.name.value_or(""));
  std::u16string user_display_name =
      base::UTF8ToUTF16(request.user.display_name.value_or(""));
  std::vector<uint8_t> user_id = request.user.id;
  WEBAUTHN_USER_ENTITY_INFORMATION user_info{
      WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION,
      base::checked_cast<DWORD>(user_id.size()),
      const_cast<unsigned char*>(user_id.data()),
      base::as_wcstr(user_name),
      /*pwszIcon=*/nullptr,
      base::as_wcstr(user_display_name),
  };

  std::vector<WEBAUTHN_COSE_CREDENTIAL_PARAMETER>
      cose_credential_parameter_values;
  for (const PublicKeyCredentialParams::CredentialInfo& credential_info :
       request.public_key_credential_params.public_key_credential_params()) {
    if (credential_info.type != CredentialType::kPublicKey) {
      continue;
    }
    cose_credential_parameter_values.push_back(
        {WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION,
         WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, credential_info.algorithm});
  }
  WEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose_credential_parameters{
      base::checked_cast<DWORD>(cose_credential_parameter_values.size()),
      cose_credential_parameter_values.data()};

  std::string client_data_json = request.client_data_json;
  WEBAUTHN_CLIENT_DATA client_data{
      WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
      base::checked_cast<DWORD>(client_data_json.size()),
      const_cast<unsigned char*>(
          reinterpret_cast<const unsigned char*>(client_data_json.data())),
      WEBAUTHN_HASH_ALGORITHM_SHA_256};

  std::vector<WEBAUTHN_EXTENSION> extensions;
  if (request.hmac_secret) {
    // In version six of webauthn.dll, there's an explicit boolean for this. But
    // older versions of the library require that the extension be listed.
    static BOOL kHMACSecretTrue = TRUE;
    extensions.emplace_back(
        WEBAUTHN_EXTENSION{WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET,
                           sizeof(BOOL), static_cast<void*>(&kHMACSecretTrue)});
  }

  WEBAUTHN_CRED_PROTECT_EXTENSION_IN maybe_cred_protect_extension;
  if (request.cred_protect) {
    // MakeCredentialRequestHandler rejects a request with credProtect
    // enforced=true if webauthn.dll does not support credProtect.
    if (request.cred_protect_enforce && api_version < WEBAUTHN_API_VERSION_2) {
      NOTREACHED_IN_MIGRATION();
      return {MakeCredentialStatus::kWinNotAllowedError, std::nullopt};
    }
    // Windows doesn't support the concept of
    // CredProtectRequest::kUVOrCredIDRequiredOrBetter. So an authenticators
    // that defaults to credProtect level three will only use level two when
    // Chrome is setting the credProtect level for discoverable credentials.
    maybe_cred_protect_extension = WEBAUTHN_CRED_PROTECT_EXTENSION_IN{
        /*dwCredProtect=*/static_cast<uint8_t>(*request.cred_protect),
        /*bRequireCredProtect=*/request.cred_protect_enforce,
    };
    extensions.emplace_back(WEBAUTHN_EXTENSION{
        /*pwszExtensionIdentifier=*/WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT,
        /*cbExtension=*/sizeof(WEBAUTHN_CRED_PROTECT_EXTENSION_IN),
        /*pvExtension=*/&maybe_cred_protect_extension,
    });
  }

  uint32_t authenticator_attachment;
  if (request_options.is_off_the_record_context &&
      api_version < WEBAUTHN_API_VERSION_4) {
    // API versions before `WEBAUTHN_API_VERSION_4` don't have support for
    // showing a warning message that platform credentials will out last the
    // Incognito session. Thus, in this case, only external authenticators are
    // enabled.
    authenticator_attachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
  } else if (request_options.large_blob_support ==
             LargeBlobSupport::kRequired) {
    // The Windows platform authenticator does not have support for large blob,
    // and will ignore the requirement if the user selects it. Force the request
    // to be only external authenticators.
    authenticator_attachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
  } else {
    authenticator_attachment =
        ToWinAuthenticatorAttachment(request.authenticator_attachment);
  }

  WEBAUTHN_CRED_BLOB_EXTENSION cred_blob_ext;
  if (request.cred_blob && api_version >= WEBAUTHN_API_VERSION_3 &&
      request.cred_blob->size() <=
          std::numeric_limits<decltype(cred_blob_ext.cbCredBlob)>::max()) {
    cred_blob_ext = {
        /*cbCredBlob=*/base::checked_cast<decltype(cred_blob_ext.cbCredBlob)>(
            request.cred_blob->size()),
        /*pbCredBlob=*/request.cred_blob->data(),
    };
    extensions.emplace_back(WEBAUTHN_EXTENSION{
        /*pwszExtensionIdentifier=*/WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB,
        /*cbExtension=*/sizeof(cred_blob_ext),
        /*pvExtension=*/&cred_blob_ext,
    });
  }

  if (request.min_pin_length_requested &&
      api_version >= WEBAUTHN_API_VERSION_3) {
    static const BOOL kRequestMinPINLength = TRUE;
    extensions.emplace_back(WEBAUTHN_EXTENSION{
        /*pwszExtensionIdentifier=*/
        WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH,
        /*cbExtension=*/sizeof(kRequestMinPINLength),
        /*pvExtension=*/const_cast<BOOL*>(&kRequestMinPINLength),
    });
  }

  DWORD enterprise_attestation = WEBAUTHN_ENTERPRISE_ATTESTATION_NONE;
  switch (request.attestation_preference) {
    case AttestationConveyancePreference::kEnterpriseIfRPListedOnAuthenticator:
      enterprise_attestation =
          WEBAUTHN_ENTERPRISE_ATTESTATION_VENDOR_FACILITATED;
      break;
    case AttestationConveyancePreference::kEnterpriseApprovedByBrowser:
      enterprise_attestation = WEBAUTHN_ENTERPRISE_ATTESTATION_PLATFORM_MANAGED;
      break;
    default:
      break;
  }

  // Note that entries in |exclude_list_credentials| hold pointers
  // into request.exclude_list.
  std::vector<WEBAUTHN_CREDENTIAL_EX> exclude_list_credentials =
      ToWinCredentialExVector(&request.exclude_list);
  std::vector<WEBAUTHN_CREDENTIAL_EX*> exclude_list_ptrs;
  base::ranges::transform(exclude_list_credentials,
                          std::back_inserter(exclude_list_ptrs),
                          [](auto& cred) { return &cred; });
  WEBAUTHN_CREDENTIAL_LIST exclude_credential_list{
      base::checked_cast<DWORD>(exclude_list_ptrs.size()),
      exclude_list_ptrs.data()};

  WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options{
      WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7,
      kWinWebAuthnTimeoutMilliseconds,
      WEBAUTHN_CREDENTIALS{
          0, nullptr},  // Ignored because pExcludeCredentialList is set.
      WEBAUTHN_EXTENSIONS{base::checked_cast<DWORD>(extensions.size()),
                          extensions.data()},
      authenticator_attachment,
      request.resident_key_required,
      ToWinUserVerificationRequirement(request.user_verification),
      ToWinAttestationConveyancePreference(request.attestation_preference,
                                           api_version),
      /*dwFlags=*/0,
      &cancellation_id,
      &exclude_credential_list,
      enterprise_attestation,
      ToWinLargeBlobSupport(request_options.large_blob_support),
      /*bPreferResidentKey=*/request_options.resident_key ==
          ResidentKeyRequirement::kPreferred,
      request_options.is_off_the_record_context,
      request.hmac_secret,
      /*pLinkedDevice=*/nullptr,
      /*cbJsonExt=*/0,
      /*pbJsonExt=*/nullptr,
  };

  FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential("
                  << "rp=" << rp_info << ", user=" << user_info
                  << ", cose_credential_parameters="
                  << cose_credential_parameters
                  << ", client_data=" << client_data << ", options=" << options
                  << ")";

  WEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation = nullptr;
  HRESULT hresult = webauthn_api->AuthenticatorMakeCredential(
      h_wnd, &rp_info, &user_info, &cose_credential_parameters, &client_data,
      &options, &credential_attestation);
  std::unique_ptr<WEBAUTHN_CREDENTIAL_ATTESTATION,
                  std::function<void(PWEBAUTHN_CREDENTIAL_ATTESTATION)>>
      credential_attestation_deleter(
          credential_attestation,
          [webauthn_api](PWEBAUTHN_CREDENTIAL_ATTESTATION ptr) {
            webauthn_api->FreeCredentialAttestation(ptr);
          });
  if (hresult != S_OK) {
    FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential()="
                    << HresultToHex(hresult) << " ("
                    << webauthn_api->GetErrorName(hresult) << ")";
    return {WinErrorNameToMakeCredentialStatus(
                base::as_u16cstr(webauthn_api->GetErrorName(hresult))),
            std::nullopt};
  }
  FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential()="
                  << *credential_attestation;
  return {MakeCredentialStatus::kSuccess,
          ToAuthenticatorMakeCredentialResponse(*credential_attestation)};
}

std::pair<GetAssertionStatus, std::optional<AuthenticatorGetAssertionResponse>>
AuthenticatorGetAssertionBlocking(WinWebAuthnApi* webauthn_api,
                                  HWND h_wnd,
                                  GUID cancellation_id,
                                  CtapGetAssertionRequest request,
                                  CtapGetAssertionOptions request_options) {
  DCHECK(webauthn_api->IsAvailable());
  const int api_version = webauthn_api->Version();

  std::u16string rp_id16 = base::UTF8ToUTF16(request.rp_id);
  std::string client_data_json = request.client_data_json;
  WEBAUTHN_CLIENT_DATA client_data{
      WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
      base::checked_cast<DWORD>(client_data_json.size()),
      const_cast<unsigned char*>(
          reinterpret_cast<const unsigned char*>(client_data_json.data())),
      WEBAUTHN_HASH_ALGORITHM_SHA_256};

  std::optional<std::u16string> opt_app_id16 = std::nullopt;
  if (request.app_id) {
    opt_app_id16 = base::UTF8ToUTF16(
        std::string_view(reinterpret_cast<const char*>(request.app_id->data()),
                         request.app_id->size()));
  }

  // Note that entries in |allow_list_credentials| hold pointers into
  // request.allow_list.
  std::vector<WEBAUTHN_CREDENTIAL_EX> allow_list_credentials =
      ToWinCredentialExVector(&request.allow_list);
  std::vector<WEBAUTHN_CREDENTIAL_EX*> allow_list_ptrs;
  base::ranges::transform(allow_list_credentials,
                          std::back_inserter(allow_list_ptrs),
                          [](auto& cred) { return &cred; });
  WEBAUTHN_CREDENTIAL_LIST allow_credential_list{
      base::checked_cast<DWORD>(allow_list_ptrs.size()),
      allow_list_ptrs.data()};

  // Note that entries in |legacy_credentials| hold pointers into
  // request.allow_list.
  auto legacy_credentials = ToWinCredentialVector(&request.allow_list);

  std::vector<WEBAUTHN_EXTENSION> extensions;
  if (api_version >= WEBAUTHN_API_VERSION_3 && request.get_cred_blob) {
    static const BOOL kCredBlobTrue = TRUE;
    extensions.emplace_back(WEBAUTHN_EXTENSION{
        /*pwszExtensionIdentifier=*/WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB,
        /*cbExtension=*/sizeof(kCredBlobTrue),
        /*pvExtension=*/const_cast<BOOL*>(&kCredBlobTrue),
    });
  }

  WEBAUTHN_HMAC_SECRET_SALT_VALUES hmac_salt_values_storage;
  std::vector<WEBAUTHN_HMAC_SECRET_SALT> salts_storage;
  std::vector<WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT> cred_salts_storage;
  WEBAUTHN_HMAC_SECRET_SALT_VALUES* const hmac_salt_values =
      FillHMACSaltValues(&hmac_salt_values_storage, &salts_storage,
                         &cred_salts_storage, request_options.prf_inputs);

  DWORD flags = 0;
  if (hmac_salt_values) {
    // The HMAC salts are hashed in the renderer. This flag indicates that they
    // should not be hashed again.
    flags |= WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG;
  }

  DWORD large_blob_operation = WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE;
  base::span<uint8_t> large_blob;
  if (api_version >= WEBAUTHN_API_VERSION_3) {
    if (request_options.large_blob_read) {
      large_blob_operation = WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET;
    } else if (request_options.large_blob_write) {
      large_blob_operation = WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET;
      large_blob = *request_options.large_blob_write;
    }
  }

  static BOOL kUseAppIdTrue = TRUE;    // const
  static BOOL kUseAppIdFalse = FALSE;  // const
  WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options{
      WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7,
      kWinWebAuthnTimeoutMilliseconds,
      // As of Nov 2018, the WebAuthNAuthenticatorGetAssertion method will
      // fail to challenge credentials via CTAP1 if the allowList is passed
      // in the extended form in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS
      // (i.e.  pAllowCredentialList instead of CredentialList). The legacy
      // CredentialList field works fine, but does not support setting
      // transport restrictions on the credential descriptor.
      //
      // As a workaround, MS tells us to also set the CredentialList
      // parameter with an accurate cCredentials count and some arbitrary
      // pCredentials data.
      WEBAUTHN_CREDENTIALS{base::checked_cast<DWORD>(legacy_credentials.size()),
                           legacy_credentials.data()},
      WEBAUTHN_EXTENSIONS{base::checked_cast<DWORD>(extensions.size()),
                          extensions.data()},
      WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY,
      ToWinUserVerificationRequirement(request.user_verification),
      flags,
      opt_app_id16 ? base::as_wcstr(*opt_app_id16) : nullptr,
      opt_app_id16 ? &kUseAppIdTrue : &kUseAppIdFalse,
      &cancellation_id,
      &allow_credential_list,
      large_blob_operation,
      base::checked_cast<DWORD>(large_blob.size()),
      large_blob.data(),
      hmac_salt_values,
      request_options.is_off_the_record_context,
      /*pLinkedDevice=*/nullptr,
      /*bAutoFill=*/FALSE,
      /*cbJsonExt=*/0,
      /*pbJsonExt=*/nullptr,
  };

  FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion("
                  << "rp_id=\"" << rp_id16 << "\", client_data=" << client_data
                  << ", options=" << options << ")";

  WEBAUTHN_ASSERTION* assertion = nullptr;
  HRESULT hresult = webauthn_api->AuthenticatorGetAssertion(
      h_wnd, base::as_wcstr(rp_id16), &client_data, &options, &assertion);
  std::unique_ptr<WEBAUTHN_ASSERTION, std::function<void(PWEBAUTHN_ASSERTION)>>
      assertion_deleter(assertion, [webauthn_api](PWEBAUTHN_ASSERTION ptr) {
        webauthn_api->FreeAssertion(ptr);
      });
  if (hresult != S_OK) {
    FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion()="
                    << HresultToHex(hresult) << " ("
                    << webauthn_api->GetErrorName(hresult) << ")";
    return {WinErrorNameToGetAssertionStatus(
                base::as_u16cstr(webauthn_api->GetErrorName(hresult))),
            std::nullopt};
  }

  FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion()=" << *assertion;
  std::optional<AuthenticatorGetAssertionResponse> response =
      ToAuthenticatorGetAssertionResponse(*assertion, request_options);
  if (response && !request_options.prf_inputs.empty() &&
      webauthn_api->Version() < WEBAUTHN_API_VERSION_4) {
    // This version of Windows does not yet support passing in inputs for
    // hmac_secret.
    response->hmac_secret_not_evaluated = true;
  }
  return {response ? GetAssertionStatus::kSuccess
                   : GetAssertionStatus::kAuthenticatorResponseInvalid,
          std::move(response)};
}

std::pair<bool, std::vector<DiscoverableCredentialMetadata>>
AuthenticatorEnumerateCredentialsBlocking(WinWebAuthnApi* webauthn_api,
                                          std::u16string_view rp_id,
                                          bool is_incognito) {
  if (!webauthn_api || !webauthn_api->IsAvailable() ||
      !webauthn_api->SupportsSilentDiscovery()) {
    FIDO_LOG(DEBUG) << "Silent discovery unavailable";
    return {false, {}};
  }

  WEBAUTHN_GET_CREDENTIALS_OPTIONS options{
      .dwVersion = WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1,
      // For a default-initialized string_view `pwszRpId` will be nullptr,
      // which makes the API not filter on RP ID.
      .pwszRpId = base::as_wcstr(rp_id),
      .bBrowserInPrivateMode = is_incognito};

  FIDO_LOG(DEBUG) << "WebAuthNGetCredentialList("
                  << ", options=" << options << ")";

  PWEBAUTHN_CREDENTIAL_DETAILS_LIST credentials = nullptr;
  HRESULT hresult =
      webauthn_api->GetPlatformCredentialList(&options, &credentials);
  std::unique_ptr<WEBAUTHN_CREDENTIAL_DETAILS_LIST,
                  std::function<void(PWEBAUTHN_CREDENTIAL_DETAILS_LIST)>>
      credentials_deleter(
          credentials, [webauthn_api](PWEBAUTHN_CREDENTIAL_DETAILS_LIST ptr) {
            webauthn_api->FreePlatformCredentialList(ptr);
          });
  if (hresult != S_OK) {
    FIDO_LOG(DEBUG) << "WebAuthNGetPlatformCredentialList()="
                    << HresultToHex(hresult) << " ("
                    << webauthn_api->GetErrorName(hresult) << ")";
    // Indicate failure only if the hresult is unexpected.
    if (hresult != NTE_NOT_FOUND) {
      FIDO_LOG(ERROR) << "Windows API returned unknown result: " << hresult;
      return {false, {}};
    }
    return {true, {}};
  }
  FIDO_LOG(DEBUG) << "WebAuthNGetCredentialList returned "
                  << credentials->cCredentialDetails << " credential(s)";
  return {true, WinCredentialDetailsListToCredentialMetadata(*credentials)};
}

}  // namespace device