/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/Assertions.h"
#include "mozilla/dom/PWebAuthnTransactionParent.h"
#include "mozilla/dom/WebAuthnCBORUtil.h"
#include "mozilla/MozPromise.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/CryptoBuffer.h"
#include "mozilla/Unused.h"
#include "nsTextFormatter.h"
#include "nsWindowsHelpers.h"
#include "WebAuthnEnumStrings.h"
#include "WebAuthnTransportIdentifiers.h"
#include "winwebauthn/webauthn.h"
#include "WinWebAuthnManager.h"

namespace mozilla::dom {

namespace {
static mozilla::LazyLogModule gWinWebAuthnManagerLog("winwebauthnkeymanager");
StaticAutoPtr<WinWebAuthnManager> gWinWebAuthnManager;
static HMODULE gWinWebAuthnModule = 0;

static decltype(WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable)*
    gWinWebauthnIsUVPAA = nullptr;
static decltype(WebAuthNAuthenticatorMakeCredential)*
    gWinWebauthnMakeCredential = nullptr;
static decltype(WebAuthNFreeCredentialAttestation)*
    gWinWebauthnFreeCredentialAttestation = nullptr;
static decltype(WebAuthNAuthenticatorGetAssertion)* gWinWebauthnGetAssertion =
    nullptr;
static decltype(WebAuthNFreeAssertion)* gWinWebauthnFreeAssertion = nullptr;
static decltype(WebAuthNGetCancellationId)* gWinWebauthnGetCancellationId =
    nullptr;
static decltype(WebAuthNCancelCurrentOperation)*
    gWinWebauthnCancelCurrentOperation = nullptr;
static decltype(WebAuthNGetErrorName)* gWinWebauthnGetErrorName = nullptr;
static decltype(WebAuthNGetApiVersionNumber)* gWinWebauthnGetApiVersionNumber =
    nullptr;

}  // namespace

/***********************************************************************
 * WinWebAuthnManager Implementation
 **********************************************************************/

constexpr uint32_t kMinWinWebAuthNApiVersion = WEBAUTHN_API_VERSION_1;

WinWebAuthnManager::WinWebAuthnManager() {
  // Create on the main thread to make sure ClearOnShutdown() works.
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!gWinWebAuthnModule);

  gWinWebAuthnModule = LoadLibrarySystem32(L"webauthn.dll");

  if (gWinWebAuthnModule) {
    gWinWebauthnIsUVPAA = reinterpret_cast<
        decltype(WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable)*>(
        GetProcAddress(
            gWinWebAuthnModule,
            "WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable"));
    gWinWebauthnMakeCredential =
        reinterpret_cast<decltype(WebAuthNAuthenticatorMakeCredential)*>(
            GetProcAddress(gWinWebAuthnModule,
                           "WebAuthNAuthenticatorMakeCredential"));
    gWinWebauthnFreeCredentialAttestation =
        reinterpret_cast<decltype(WebAuthNFreeCredentialAttestation)*>(
            GetProcAddress(gWinWebAuthnModule,
                           "WebAuthNFreeCredentialAttestation"));
    gWinWebauthnGetAssertion =
        reinterpret_cast<decltype(WebAuthNAuthenticatorGetAssertion)*>(
            GetProcAddress(gWinWebAuthnModule,
                           "WebAuthNAuthenticatorGetAssertion"));
    gWinWebauthnFreeAssertion =
        reinterpret_cast<decltype(WebAuthNFreeAssertion)*>(
            GetProcAddress(gWinWebAuthnModule, "WebAuthNFreeAssertion"));
    gWinWebauthnGetCancellationId =
        reinterpret_cast<decltype(WebAuthNGetCancellationId)*>(
            GetProcAddress(gWinWebAuthnModule, "WebAuthNGetCancellationId"));
    gWinWebauthnCancelCurrentOperation =
        reinterpret_cast<decltype(WebAuthNCancelCurrentOperation)*>(
            GetProcAddress(gWinWebAuthnModule,
                           "WebAuthNCancelCurrentOperation"));
    gWinWebauthnGetErrorName =
        reinterpret_cast<decltype(WebAuthNGetErrorName)*>(
            GetProcAddress(gWinWebAuthnModule, "WebAuthNGetErrorName"));
    gWinWebauthnGetApiVersionNumber =
        reinterpret_cast<decltype(WebAuthNGetApiVersionNumber)*>(
            GetProcAddress(gWinWebAuthnModule, "WebAuthNGetApiVersionNumber"));

    if (gWinWebauthnIsUVPAA && gWinWebauthnMakeCredential &&
        gWinWebauthnFreeCredentialAttestation && gWinWebauthnGetAssertion &&
        gWinWebauthnFreeAssertion && gWinWebauthnGetCancellationId &&
        gWinWebauthnCancelCurrentOperation && gWinWebauthnGetErrorName &&
        gWinWebauthnGetApiVersionNumber) {
      mWinWebAuthNApiVersion = gWinWebauthnGetApiVersionNumber();
    }
  }
}

WinWebAuthnManager::~WinWebAuthnManager() {
  if (gWinWebAuthnModule) {
    FreeLibrary(gWinWebAuthnModule);
  }
  gWinWebAuthnModule = 0;
}

// static
void WinWebAuthnManager::Initialize() {
  if (!gWinWebAuthnManager) {
    gWinWebAuthnManager = new WinWebAuthnManager();
    ClearOnShutdown(&gWinWebAuthnManager);
  }
}

// static
WinWebAuthnManager* WinWebAuthnManager::Get() {
  MOZ_ASSERT(gWinWebAuthnManager);
  return gWinWebAuthnManager;
}

uint32_t WinWebAuthnManager::GetWebAuthNApiVersion() {
  return mWinWebAuthNApiVersion;
}

// static
bool WinWebAuthnManager::AreWebAuthNApisAvailable() {
  WinWebAuthnManager* mgr = WinWebAuthnManager::Get();
  return mgr->GetWebAuthNApiVersion() >= kMinWinWebAuthNApiVersion;
}

bool WinWebAuthnManager::
    IsUserVerifyingPlatformAuthenticatorAvailableInternal() {
  BOOL isUVPAA = FALSE;
  return (gWinWebauthnIsUVPAA(&isUVPAA) == S_OK && isUVPAA == TRUE);
}

// static
bool WinWebAuthnManager::IsUserVerifyingPlatformAuthenticatorAvailable() {
  if (WinWebAuthnManager::AreWebAuthNApisAvailable()) {
    return WinWebAuthnManager::Get()
        ->IsUserVerifyingPlatformAuthenticatorAvailableInternal();
  }
  return false;
}

void WinWebAuthnManager::AbortTransaction(const uint64_t& aTransactionId,
                                          const nsresult& aError) {
  Unused << mTransactionParent->SendAbort(aTransactionId, aError);
  ClearTransaction();
}

void WinWebAuthnManager::MaybeClearTransaction(
    PWebAuthnTransactionParent* aParent) {
  // Only clear if we've been requested to do so by our current transaction
  // parent.
  if (mTransactionParent == aParent) {
    ClearTransaction();
  }
}

void WinWebAuthnManager::ClearTransaction() { mTransactionParent = nullptr; }

void WinWebAuthnManager::Register(
    PWebAuthnTransactionParent* aTransactionParent,
    const uint64_t& aTransactionId, const WebAuthnMakeCredentialInfo& aInfo) {
  MOZ_LOG(gWinWebAuthnManagerLog, LogLevel::Debug, ("WinWebAuthNRegister"));

  ClearTransaction();
  mTransactionParent = aTransactionParent;

  WEBAUTHN_EXTENSION rgExtension[1] = {};
  DWORD cExtensions = 0;
  BOOL HmacCreateSecret = FALSE;

  // RP Information
  WEBAUTHN_RP_ENTITY_INFORMATION rpInfo = {
      WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION, aInfo.RpId().get(),
      nullptr, nullptr};

  // User Information
  WEBAUTHN_USER_ENTITY_INFORMATION userInfo = {
      WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION,
      0,
      nullptr,
      nullptr,
      nullptr,
      nullptr};

  // Client Data
  WEBAUTHN_CLIENT_DATA WebAuthNClientData = {
      WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
      (DWORD)aInfo.ClientDataJSON().Length(),
      (BYTE*)(aInfo.ClientDataJSON().get()), WEBAUTHN_HASH_ALGORITHM_SHA_256};

  // Algorithms
  nsTArray<WEBAUTHN_COSE_CREDENTIAL_PARAMETER> coseParams;

  // User Verification Requirement
  DWORD winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;

  // Attachment
  DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;

  // Resident Key
  BOOL winRequireResidentKey = FALSE;
  BOOL winPreferResidentKey = FALSE;

  // AttestationConveyance
  DWORD winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY;

  rpInfo.pwszName = aInfo.Rp().Name().get();
  rpInfo.pwszIcon = aInfo.Rp().Icon().get();

  userInfo.cbId = static_cast<DWORD>(aInfo.User().Id().Length());
  userInfo.pbId = const_cast<unsigned char*>(aInfo.User().Id().Elements());
  userInfo.pwszName = aInfo.User().Name().get();
  userInfo.pwszIcon = aInfo.User().Icon().get();
  userInfo.pwszDisplayName = aInfo.User().DisplayName().get();

  for (const auto& coseAlg : aInfo.coseAlgs()) {
    WEBAUTHN_COSE_CREDENTIAL_PARAMETER coseAlgorithm = {
        WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION,
        WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, coseAlg.alg()};
    coseParams.AppendElement(coseAlgorithm);
  }

  const auto& sel = aInfo.AuthenticatorSelection();

  const nsString& userVerificationRequirement =
      sel.userVerificationRequirement();
  // This mapping needs to be reviewed if values are added to the
  // UserVerificationRequirement enum.
  static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 2);
  if (userVerificationRequirement.EqualsLiteral(
          MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
    winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
  } else if (userVerificationRequirement.EqualsLiteral(
                 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
    winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
  } else if (userVerificationRequirement.EqualsLiteral(
                 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED)) {
    winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
  } else {
    winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;
  }

  if (sel.authenticatorAttachment().isSome()) {
    const nsString& authenticatorAttachment =
        sel.authenticatorAttachment().value();
    // This mapping needs to be reviewed if values are added to the
    // AuthenticatorAttachement enum.
    static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 2);
    if (authenticatorAttachment.EqualsLiteral(
            MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) {
      winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM;
    } else if (authenticatorAttachment.EqualsLiteral(
                   MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
      winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
    } else {
      winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
    }
  }

  const nsString& residentKey = sel.residentKey();
  // This mapping needs to be reviewed if values are added to the
  // ResidentKeyRequirement enum.
  static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 2);
  if (residentKey.EqualsLiteral(
          MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED)) {
    winRequireResidentKey = TRUE;
    winPreferResidentKey = TRUE;
  } else if (residentKey.EqualsLiteral(
                 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED)) {
    winRequireResidentKey = FALSE;
    winPreferResidentKey = TRUE;
  } else if (residentKey.EqualsLiteral(
                 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED)) {
    winRequireResidentKey = FALSE;
    winPreferResidentKey = FALSE;
  } else {
    // WebAuthnManager::MakeCredential is supposed to assign one of the above
    // values, so this shouldn't happen.
    MOZ_ASSERT_UNREACHABLE();
    MaybeAbortRegister(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR);
    return;
  }

  // AttestationConveyance
  const nsString& attestation = aInfo.attestationConveyancePreference();
  // This mapping needs to be reviewed if values are added to the
  // AttestationConveyancePreference enum.
  static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 2);
  if (attestation.EqualsLiteral(
          MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE)) {
    winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
  } else if (attestation.EqualsLiteral(
                 MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT)) {
    winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT;
  } else if (attestation.EqualsLiteral(
                 MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT)) {
    winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT;
  } else {
    winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY;
  }

  if (aInfo.Extensions().Length() >
      (int)(sizeof(rgExtension) / sizeof(rgExtension[0]))) {
    nsresult aError = NS_ERROR_DOM_INVALID_STATE_ERR;
    MaybeAbortRegister(aTransactionId, aError);
    return;
  }
  for (const WebAuthnExtension& ext : aInfo.Extensions()) {
    if (ext.type() == WebAuthnExtension::TWebAuthnExtensionHmacSecret) {
      HmacCreateSecret =
          ext.get_WebAuthnExtensionHmacSecret().hmacCreateSecret() == true;
      if (HmacCreateSecret) {
        rgExtension[cExtensions].pwszExtensionIdentifier =
            WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET;
        rgExtension[cExtensions].cbExtension = sizeof(BOOL);
        rgExtension[cExtensions].pvExtension = &HmacCreateSecret;
        cExtensions++;
      }
    }
  }

  WEBAUTHN_COSE_CREDENTIAL_PARAMETERS WebAuthNCredentialParameters = {
      static_cast<DWORD>(coseParams.Length()), coseParams.Elements()};

  // Exclude Credentials
  nsTArray<WEBAUTHN_CREDENTIAL_EX> excludeCredentials;
  WEBAUTHN_CREDENTIAL_EX* pExcludeCredentials = nullptr;
  nsTArray<WEBAUTHN_CREDENTIAL_EX*> excludeCredentialsPtrs;
  WEBAUTHN_CREDENTIAL_LIST excludeCredentialList = {0};
  WEBAUTHN_CREDENTIAL_LIST* pExcludeCredentialList = nullptr;

  for (auto& cred : aInfo.ExcludeList()) {
    uint8_t transports = cred.transports();
    DWORD winTransports = 0;
    if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
      winTransports |= WEBAUTHN_CTAP_TRANSPORT_USB;
    }
    if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
      winTransports |= WEBAUTHN_CTAP_TRANSPORT_NFC;
    }
    if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
      winTransports |= WEBAUTHN_CTAP_TRANSPORT_BLE;
    }
    if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL) {
      winTransports |= WEBAUTHN_CTAP_TRANSPORT_INTERNAL;
    }

    WEBAUTHN_CREDENTIAL_EX credential = {
        WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION,
        static_cast<DWORD>(cred.id().Length()), (PBYTE)(cred.id().Elements()),
        WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, winTransports};
    excludeCredentials.AppendElement(credential);
  }

  if (!excludeCredentials.IsEmpty()) {
    pExcludeCredentials = excludeCredentials.Elements();
    for (DWORD i = 0; i < excludeCredentials.Length(); i++) {
      excludeCredentialsPtrs.AppendElement(&pExcludeCredentials[i]);
    }
    excludeCredentialList.cCredentials = excludeCredentials.Length();
    excludeCredentialList.ppCredentials = excludeCredentialsPtrs.Elements();
    pExcludeCredentialList = &excludeCredentialList;
  }

  // MakeCredentialOptions
  WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS WebAuthNCredentialOptions = {
      WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4,
      aInfo.TimeoutMS(),
      {0, NULL},
      {0, NULL},
      winAttachment,
      winRequireResidentKey,
      winUserVerificationReq,
      winAttestation,
      0,     // Flags
      NULL,  // CancellationId
      pExcludeCredentialList,
      WEBAUTHN_ENTERPRISE_ATTESTATION_NONE,
      WEBAUTHN_LARGE_BLOB_SUPPORT_NONE,
      winPreferResidentKey,  // PreferResidentKey
  };

  GUID cancellationId = {0};
  if (gWinWebauthnGetCancellationId(&cancellationId) == S_OK) {
    WebAuthNCredentialOptions.pCancellationId = &cancellationId;
    mCancellationIds.emplace(aTransactionId, &cancellationId);
  }

  if (cExtensions != 0) {
    WebAuthNCredentialOptions.Extensions.cExtensions = cExtensions;
    WebAuthNCredentialOptions.Extensions.pExtensions = rgExtension;
  }

  WEBAUTHN_CREDENTIAL_ATTESTATION* pWebAuthNCredentialAttestation = nullptr;

  // Bug 1518876: Get Window Handle from Content process for Windows WebAuthN
  // APIs
  HWND hWnd = GetForegroundWindow();

  HRESULT hr = gWinWebauthnMakeCredential(
      hWnd, &rpInfo, &userInfo, &WebAuthNCredentialParameters,
      &WebAuthNClientData, &WebAuthNCredentialOptions,
      &pWebAuthNCredentialAttestation);

  mCancellationIds.erase(aTransactionId);

  if (hr == S_OK) {
    nsTArray<uint8_t> credentialId;
    credentialId.AppendElements(pWebAuthNCredentialAttestation->pbCredentialId,
                                pWebAuthNCredentialAttestation->cbCredentialId);

    nsTArray<uint8_t> authenticatorData;

    authenticatorData.AppendElements(
        pWebAuthNCredentialAttestation->pbAuthenticatorData,
        pWebAuthNCredentialAttestation->cbAuthenticatorData);

    nsTArray<uint8_t> attObject;
    if (winAttestation == WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE) {
      // Zero AAGuid
      const uint8_t zeroGuid[16] = {0};
      authenticatorData.ReplaceElementsAt(32 + 1 + 4 /*AAGuid offset*/, 16,
                                          zeroGuid, 16);

      CryptoBuffer authData;
      authData.Assign(authenticatorData);
      CryptoBuffer noneAttObj;
      CBOREncodeNoneAttestationObj(authData, noneAttObj);
      attObject.AppendElements(noneAttObj);
    } else {
      attObject.AppendElements(
          pWebAuthNCredentialAttestation->pbAttestationObject,
          pWebAuthNCredentialAttestation->cbAttestationObject);
    }

    nsTArray<WebAuthnExtensionResult> extensions;

    if (pWebAuthNCredentialAttestation->dwVersion >=
        WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2) {
      PCWEBAUTHN_EXTENSIONS pExtensionList =
          &pWebAuthNCredentialAttestation->Extensions;
      if (pExtensionList->cExtensions != 0 &&
          pExtensionList->pExtensions != NULL) {
        for (DWORD dwIndex = 0; dwIndex < pExtensionList->cExtensions;
             dwIndex++) {
          PWEBAUTHN_EXTENSION pExtension =
              &pExtensionList->pExtensions[dwIndex];
          if (pExtension->pwszExtensionIdentifier &&
              (0 == _wcsicmp(pExtension->pwszExtensionIdentifier,
                             WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET)) &&
              pExtension->cbExtension == sizeof(BOOL)) {
            BOOL* pCredentialCreatedWithHmacSecret =
                (BOOL*)pExtension->pvExtension;
            if (*pCredentialCreatedWithHmacSecret) {
              extensions.AppendElement(WebAuthnExtensionResultHmacSecret(true));
            }
          }
        }
      }
    }

    WebAuthnMakeCredentialResult result(aInfo.ClientDataJSON(), attObject,
                                        credentialId, extensions);

    Unused << mTransactionParent->SendConfirmRegister(aTransactionId, result);
    ClearTransaction();
    gWinWebauthnFreeCredentialAttestation(pWebAuthNCredentialAttestation);

  } else {
    PCWSTR errorName = gWinWebauthnGetErrorName(hr);
    nsresult aError = NS_ERROR_DOM_ABORT_ERR;

    if (_wcsicmp(errorName, L"InvalidStateError") == 0) {
      aError = NS_ERROR_DOM_INVALID_STATE_ERR;
    } else if (_wcsicmp(errorName, L"ConstraintError") == 0 ||
               _wcsicmp(errorName, L"UnknownError") == 0) {
      aError = NS_ERROR_DOM_UNKNOWN_ERR;
    } else if (_wcsicmp(errorName, L"NotSupportedError") == 0) {
      aError = NS_ERROR_DOM_INVALID_STATE_ERR;
    } else if (_wcsicmp(errorName, L"NotAllowedError") == 0) {
      aError = NS_ERROR_DOM_NOT_ALLOWED_ERR;
    }

    MaybeAbortRegister(aTransactionId, aError);
  }
}

void WinWebAuthnManager::MaybeAbortRegister(const uint64_t& aTransactionId,
                                            const nsresult& aError) {
  AbortTransaction(aTransactionId, aError);
}

void WinWebAuthnManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
                              const uint64_t& aTransactionId,
                              const WebAuthnGetAssertionInfo& aInfo) {
  MOZ_LOG(gWinWebAuthnManagerLog, LogLevel::Debug, ("WinWebAuthNSign"));

  ClearTransaction();
  mTransactionParent = aTransactionParent;

  // User Verification Requirement
  DWORD winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;

  // RPID
  PCWSTR rpID = nullptr;

  // Attachment
  DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;

  // AppId
  BOOL bU2fAppIdUsed = FALSE;
  BOOL* pbU2fAppIdUsed = nullptr;
  PCWSTR winAppIdentifier = nullptr;

  // Client Data
  WEBAUTHN_CLIENT_DATA WebAuthNClientData = {
      WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
      (DWORD)aInfo.ClientDataJSON().Length(),
      (BYTE*)(aInfo.ClientDataJSON().get()), WEBAUTHN_HASH_ALGORITHM_SHA_256};

  for (const WebAuthnExtension& ext : aInfo.Extensions()) {
    if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
      winAppIdentifier = ext.get_WebAuthnExtensionAppId().appIdentifier().get();
      pbU2fAppIdUsed = &bU2fAppIdUsed;
      break;
    }
  }

  // RPID
  rpID = aInfo.RpId().get();

  // User Verification Requirement
  const nsString& userVerificationReq = aInfo.userVerificationRequirement();
  // This mapping needs to be reviewed if values are added to the
  // UserVerificationRequirement enum.
  static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 2);
  if (userVerificationReq.EqualsLiteral(
          MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
    winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
  } else if (userVerificationReq.EqualsLiteral(
                 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
    winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
  } else if (userVerificationReq.EqualsLiteral(
                 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED)) {
    winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
  } else {
    winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;
  }

  // allow Credentials
  nsTArray<WEBAUTHN_CREDENTIAL_EX> allowCredentials;
  WEBAUTHN_CREDENTIAL_EX* pAllowCredentials = nullptr;
  nsTArray<WEBAUTHN_CREDENTIAL_EX*> allowCredentialsPtrs;
  WEBAUTHN_CREDENTIAL_LIST allowCredentialList = {0};
  WEBAUTHN_CREDENTIAL_LIST* pAllowCredentialList = nullptr;

  for (auto& cred : aInfo.AllowList()) {
    uint8_t transports = cred.transports();
    DWORD winTransports = 0;
    if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
      winTransports |= WEBAUTHN_CTAP_TRANSPORT_USB;
    }
    if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
      winTransports |= WEBAUTHN_CTAP_TRANSPORT_NFC;
    }
    if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
      winTransports |= WEBAUTHN_CTAP_TRANSPORT_BLE;
    }
    if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL) {
      winTransports |= WEBAUTHN_CTAP_TRANSPORT_INTERNAL;
    }

    WEBAUTHN_CREDENTIAL_EX credential = {
        WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION,
        static_cast<DWORD>(cred.id().Length()), (PBYTE)(cred.id().Elements()),
        WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, winTransports};
    allowCredentials.AppendElement(credential);
  }

  if (allowCredentials.Length()) {
    pAllowCredentials = allowCredentials.Elements();
    for (DWORD i = 0; i < allowCredentials.Length(); i++) {
      allowCredentialsPtrs.AppendElement(&pAllowCredentials[i]);
    }
    allowCredentialList.cCredentials = allowCredentials.Length();
    allowCredentialList.ppCredentials = allowCredentialsPtrs.Elements();
    pAllowCredentialList = &allowCredentialList;
  }

  WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS WebAuthNAssertionOptions = {
      WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION,
      aInfo.TimeoutMS(),
      {0, NULL},
      {0, NULL},
      winAttachment,
      winUserVerificationReq,
      0,  // dwFlags
      winAppIdentifier,
      pbU2fAppIdUsed,
      nullptr,  // pCancellationId
      pAllowCredentialList,
  };

  GUID cancellationId = {0};
  if (gWinWebauthnGetCancellationId(&cancellationId) == S_OK) {
    WebAuthNAssertionOptions.pCancellationId = &cancellationId;
    mCancellationIds.emplace(aTransactionId, &cancellationId);
  }

  PWEBAUTHN_ASSERTION pWebAuthNAssertion = nullptr;

  // Bug 1518876: Get Window Handle from Content process for Windows WebAuthN
  // APIs
  HWND hWnd = GetForegroundWindow();

  HRESULT hr =
      gWinWebauthnGetAssertion(hWnd, rpID, &WebAuthNClientData,
                               &WebAuthNAssertionOptions, &pWebAuthNAssertion);

  mCancellationIds.erase(aTransactionId);

  if (hr == S_OK) {
    nsTArray<uint8_t> signature;
    signature.AppendElements(pWebAuthNAssertion->pbSignature,
                             pWebAuthNAssertion->cbSignature);

    nsTArray<uint8_t> keyHandle;
    keyHandle.AppendElements(pWebAuthNAssertion->Credential.pbId,
                             pWebAuthNAssertion->Credential.cbId);

    nsTArray<uint8_t> userHandle;
    userHandle.AppendElements(pWebAuthNAssertion->pbUserId,
                              pWebAuthNAssertion->cbUserId);

    nsTArray<uint8_t> authenticatorData;
    authenticatorData.AppendElements(pWebAuthNAssertion->pbAuthenticatorData,
                                     pWebAuthNAssertion->cbAuthenticatorData);

    nsTArray<WebAuthnExtensionResult> extensions;

    if (pbU2fAppIdUsed && *pbU2fAppIdUsed) {
      extensions.AppendElement(WebAuthnExtensionResultAppId(true));
    }

    WebAuthnGetAssertionResult result(aInfo.ClientDataJSON(), keyHandle,
                                      signature, authenticatorData, extensions,
                                      userHandle);

    Unused << mTransactionParent->SendConfirmSign(aTransactionId, result);
    ClearTransaction();

    gWinWebauthnFreeAssertion(pWebAuthNAssertion);

  } else {
    PCWSTR errorName = gWinWebauthnGetErrorName(hr);
    nsresult aError = NS_ERROR_DOM_ABORT_ERR;

    if (_wcsicmp(errorName, L"InvalidStateError") == 0) {
      aError = NS_ERROR_DOM_INVALID_STATE_ERR;
    } else if (_wcsicmp(errorName, L"ConstraintError") == 0 ||
               _wcsicmp(errorName, L"UnknownError") == 0) {
      aError = NS_ERROR_DOM_UNKNOWN_ERR;
    } else if (_wcsicmp(errorName, L"NotSupportedError") == 0) {
      aError = NS_ERROR_DOM_INVALID_STATE_ERR;
    } else if (_wcsicmp(errorName, L"NotAllowedError") == 0) {
      aError = NS_ERROR_DOM_NOT_ALLOWED_ERR;
    }

    MaybeAbortSign(aTransactionId, aError);
  }
}

void WinWebAuthnManager::MaybeAbortSign(const uint64_t& aTransactionId,
                                        const nsresult& aError) {
  AbortTransaction(aTransactionId, aError);
}

void WinWebAuthnManager::Cancel(PWebAuthnTransactionParent* aParent,
                                const Tainted<uint64_t>& aTransactionId) {
  if (mTransactionParent != aParent) {
    return;
  }

  ClearTransaction();

  auto iter = mCancellationIds.find(
      MOZ_NO_VALIDATE(aTransactionId,
                      "Transaction ID is checked against a global container, "
                      "so an invalid entry can affect another origin's "
                      "request. This issue is filed as Bug 1696159."));
  if (iter != mCancellationIds.end()) {
    gWinWebauthnCancelCurrentOperation(iter->second);
  }
}

}  // namespace mozilla::dom
