gecko-dev/dom/webauthn/WinWebAuthnService.cpp

1039 lines
39 KiB
C++

/* -*- 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/dom/PWebAuthnTransactionParent.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/Assertions.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Preferences.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/Unused.h"
#include "nsTextFormatter.h"
#include "nsWindowsHelpers.h"
#include "WebAuthnAutoFillEntry.h"
#include "WebAuthnEnumStrings.h"
#include "WebAuthnResult.h"
#include "WebAuthnTransportIdentifiers.h"
#include "winwebauthn/webauthn.h"
#include "WinWebAuthnService.h"
namespace mozilla::dom {
namespace {
StaticRWLock gWinWebAuthnModuleLock;
static bool gWinWebAuthnModuleUnusable = false;
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;
static decltype(WebAuthNGetPlatformCredentialList)*
gWinWebauthnGetPlatformCredentialList = nullptr;
static decltype(WebAuthNFreePlatformCredentialList)*
gWinWebauthnFreePlatformCredentialList = nullptr;
} // namespace
/***********************************************************************
* WinWebAuthnService Implementation
**********************************************************************/
constexpr uint32_t kMinWinWebAuthNApiVersion = WEBAUTHN_API_VERSION_1;
NS_IMPL_ISUPPORTS(WinWebAuthnService, nsIWebAuthnService)
/* static */
nsresult WinWebAuthnService::EnsureWinWebAuthnModuleLoaded() {
{
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
if (gWinWebAuthnModule) {
// The module is already loaded.
return NS_OK;
}
if (gWinWebAuthnModuleUnusable) {
// A previous attempt to load the module failed.
return NS_ERROR_NOT_AVAILABLE;
}
}
StaticAutoWriteLock lock(gWinWebAuthnModuleLock);
if (gWinWebAuthnModule) {
// Another thread successfully loaded the module while we were waiting.
return NS_OK;
}
if (gWinWebAuthnModuleUnusable) {
// Another thread failed to load the module while we were waiting.
return NS_ERROR_NOT_AVAILABLE;
}
gWinWebAuthnModule = LoadLibrarySystem32(L"webauthn.dll");
auto markModuleUnusable = MakeScopeExit([]() {
if (gWinWebAuthnModule) {
FreeLibrary(gWinWebAuthnModule);
gWinWebAuthnModule = 0;
}
gWinWebAuthnModuleUnusable = true;
});
if (!gWinWebAuthnModule) {
return NS_ERROR_NOT_AVAILABLE;
}
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)) {
return NS_ERROR_NOT_AVAILABLE;
}
DWORD version = gWinWebauthnGetApiVersionNumber();
if (version >= WEBAUTHN_API_VERSION_4) {
gWinWebauthnGetPlatformCredentialList =
reinterpret_cast<decltype(WebAuthNGetPlatformCredentialList)*>(
GetProcAddress(gWinWebAuthnModule,
"WebAuthNGetPlatformCredentialList"));
gWinWebauthnFreePlatformCredentialList =
reinterpret_cast<decltype(WebAuthNFreePlatformCredentialList)*>(
GetProcAddress(gWinWebAuthnModule,
"WebAuthNFreePlatformCredentialList"));
if (!(gWinWebauthnGetPlatformCredentialList &&
gWinWebauthnFreePlatformCredentialList)) {
return NS_ERROR_NOT_AVAILABLE;
}
}
// Bug 1869584: In some of our tests, a content process can end up here due to
// a call to WinWebAuthnService::AreWebAuthNApisAvailable. This causes us to
// fail an assertion in Preferences::SetBool, which is parent-process only.
if (XRE_IsParentProcess()) {
NS_DispatchToMainThread(NS_NewRunnableFunction(__func__, [version]() {
Preferences::SetBool("security.webauthn.show_ms_settings_link",
version >= WEBAUTHN_API_VERSION_7);
}));
}
markModuleUnusable.release();
return NS_OK;
}
WinWebAuthnService::~WinWebAuthnService() {
StaticAutoWriteLock lock(gWinWebAuthnModuleLock);
if (gWinWebAuthnModule) {
FreeLibrary(gWinWebAuthnModule);
}
gWinWebAuthnModule = 0;
}
// static
bool WinWebAuthnService::AreWebAuthNApisAvailable() {
nsresult rv = EnsureWinWebAuthnModuleLoaded();
NS_ENSURE_SUCCESS(rv, false);
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
return gWinWebAuthnModule &&
gWinWebauthnGetApiVersionNumber() >= kMinWinWebAuthNApiVersion;
}
NS_IMETHODIMP
WinWebAuthnService::GetIsUVPAA(bool* aAvailable) {
nsresult rv = EnsureWinWebAuthnModuleLoaded();
NS_ENSURE_SUCCESS(rv, rv);
if (WinWebAuthnService::AreWebAuthNApisAvailable()) {
BOOL isUVPAA = FALSE;
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
*aAvailable = gWinWebAuthnModule && gWinWebauthnIsUVPAA(&isUVPAA) == S_OK &&
isUVPAA == TRUE;
} else {
*aAvailable = false;
}
return NS_OK;
}
NS_IMETHODIMP
WinWebAuthnService::Cancel(uint64_t aTransactionId) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::Reset() {
// Reset will never be the first function to use gWinWebAuthnModule, so
// we shouldn't try to initialize it here.
auto guard = mTransactionState.Lock();
if (guard->isSome()) {
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
if (gWinWebAuthnModule) {
const GUID cancellationId = guard->ref().cancellationId;
gWinWebauthnCancelCurrentOperation(&cancellationId);
}
if (guard->ref().pendingSignPromise.isSome()) {
// This request was never dispatched to the platform API, so
// we need to reject the promise ourselves.
guard->ref().pendingSignPromise.ref()->Reject(
NS_ERROR_DOM_NOT_ALLOWED_ERR);
}
guard->reset();
}
return NS_OK;
}
NS_IMETHODIMP
WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
uint64_t aBrowsingContextId,
nsIWebAuthnRegisterArgs* aArgs,
nsIWebAuthnRegisterPromise* aPromise) {
nsresult rv = EnsureWinWebAuthnModuleLoaded();
NS_ENSURE_SUCCESS(rv, rv);
Reset();
auto guard = mTransactionState.Lock();
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
GUID cancellationId;
if (gWinWebauthnGetCancellationId(&cancellationId) != S_OK) {
// caller will reject promise
return NS_ERROR_DOM_UNKNOWN_ERR;
}
*guard = Some(TransactionState{
aTransactionId,
aBrowsingContextId,
Nothing(),
Nothing(),
cancellationId,
});
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
"WinWebAuthnService::MakeCredential",
[self = RefPtr{this}, aArgs = RefPtr{aArgs}, aPromise = RefPtr{aPromise},
cancellationId]() mutable {
// Take a read lock on gWinWebAuthnModuleLock to prevent the module from
// being unloaded while the operation is in progress. This does not
// prevent the operation from being cancelled, so it does not block a
// clean shutdown.
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
if (!gWinWebAuthnModule) {
aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
BOOL HmacCreateSecret = FALSE;
BOOL MinPinLength = FALSE;
// RP Information
nsString rpId;
Unused << aArgs->GetRpId(rpId);
WEBAUTHN_RP_ENTITY_INFORMATION rpInfo = {
WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION, rpId.get(), nullptr,
nullptr};
// User Information
WEBAUTHN_USER_ENTITY_INFORMATION userInfo = {
WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION,
0,
nullptr,
nullptr,
nullptr,
nullptr};
// Client Data
nsCString clientDataJSON;
Unused << aArgs->GetClientDataJSON(clientDataJSON);
WEBAUTHN_CLIENT_DATA WebAuthNClientData = {
WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
(DWORD)clientDataJSON.Length(), (BYTE*)(clientDataJSON.get()),
WEBAUTHN_HASH_ALGORITHM_SHA_256};
// User Verification Requirement
DWORD winUserVerificationReq =
WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;
// Resident Key
BOOL winRequireResidentKey = FALSE;
BOOL winPreferResidentKey = FALSE;
// AttestationConveyance
DWORD winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY;
nsString rpName;
Unused << aArgs->GetRpName(rpName);
rpInfo.pwszName = rpName.get();
rpInfo.pwszIcon = nullptr;
nsTArray<uint8_t> userId;
Unused << aArgs->GetUserId(userId);
userInfo.cbId = static_cast<DWORD>(userId.Length());
userInfo.pbId = const_cast<unsigned char*>(userId.Elements());
nsString userName;
Unused << aArgs->GetUserName(userName);
userInfo.pwszName = userName.get();
userInfo.pwszIcon = nullptr;
nsString userDisplayName;
Unused << aArgs->GetUserDisplayName(userDisplayName);
userInfo.pwszDisplayName = userDisplayName.get();
// Algorithms
nsTArray<WEBAUTHN_COSE_CREDENTIAL_PARAMETER> coseParams;
nsTArray<int32_t> coseAlgs;
Unused << aArgs->GetCoseAlgs(coseAlgs);
for (const int32_t& coseAlg : coseAlgs) {
WEBAUTHN_COSE_CREDENTIAL_PARAMETER coseAlgorithm = {
WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION,
WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, coseAlg};
coseParams.AppendElement(coseAlgorithm);
}
nsString userVerificationReq;
Unused << aArgs->GetUserVerification(userVerificationReq);
// This mapping needs to be reviewed if values are added to the
// UserVerificationRequirement enum.
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
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;
}
// Attachment
DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
nsString authenticatorAttachment;
nsresult rv =
aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
aPromise->Reject(rv);
return;
}
// This mapping needs to be reviewed if values are added to the
// AuthenticatorAttachement enum.
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
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;
}
}
nsString residentKey;
Unused << aArgs->GetResidentKey(residentKey);
// This mapping needs to be reviewed if values are added to the
// ResidentKeyRequirement enum.
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
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();
aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
// AttestationConveyance
nsString attestation;
Unused << aArgs->GetAttestationConveyancePreference(attestation);
// This mapping needs to be reviewed if values are added to the
// AttestationConveyancePreference enum.
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
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;
}
bool requestedCredProps;
Unused << aArgs->GetCredProps(&requestedCredProps);
bool requestedMinPinLength;
Unused << aArgs->GetMinPinLength(&requestedMinPinLength);
bool requestedHmacCreateSecret;
Unused << aArgs->GetHmacCreateSecret(&requestedHmacCreateSecret);
// Extensions that might require an entry: hmac-secret, minPinLength.
WEBAUTHN_EXTENSION rgExtension[2] = {};
DWORD cExtensions = 0;
if (requestedHmacCreateSecret) {
HmacCreateSecret = TRUE;
rgExtension[cExtensions].pwszExtensionIdentifier =
WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET;
rgExtension[cExtensions].cbExtension = sizeof(BOOL);
rgExtension[cExtensions].pvExtension = &HmacCreateSecret;
cExtensions++;
}
if (requestedMinPinLength) {
MinPinLength = TRUE;
rgExtension[cExtensions].pwszExtensionIdentifier =
WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH;
rgExtension[cExtensions].cbExtension = sizeof(BOOL);
rgExtension[cExtensions].pvExtension = &MinPinLength;
cExtensions++;
}
WEBAUTHN_COSE_CREDENTIAL_PARAMETERS WebAuthNCredentialParameters = {
static_cast<DWORD>(coseParams.Length()), coseParams.Elements()};
// Exclude Credentials
nsTArray<nsTArray<uint8_t>> excludeList;
Unused << aArgs->GetExcludeList(excludeList);
nsTArray<uint8_t> excludeListTransports;
Unused << aArgs->GetExcludeListTransports(excludeListTransports);
if (excludeList.Length() != excludeListTransports.Length()) {
aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
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 (size_t i = 0; i < excludeList.Length(); i++) {
nsTArray<uint8_t>& cred = excludeList[i];
uint8_t& transports = excludeListTransports[i];
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;
}
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID) {
winTransports |= WEBAUTHN_CTAP_TRANSPORT_HYBRID;
}
WEBAUTHN_CREDENTIAL_EX credential = {
WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION,
static_cast<DWORD>(cred.Length()), (PBYTE)(cred.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;
}
uint32_t timeout_u32;
Unused << aArgs->GetTimeoutMS(&timeout_u32);
DWORD timeout = timeout_u32;
// MakeCredentialOptions
WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS
WebAuthNCredentialOptions = {
WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7,
timeout,
{0, NULL},
{0, NULL},
winAttachment,
winRequireResidentKey,
winUserVerificationReq,
winAttestation,
0, // Flags
&cancellationId, // CancellationId
pExcludeCredentialList,
WEBAUTHN_ENTERPRISE_ATTESTATION_NONE,
WEBAUTHN_LARGE_BLOB_SUPPORT_NONE,
winPreferResidentKey, // PreferResidentKey
FALSE, // BrowserInPrivateMode
FALSE, // EnablePrf
NULL, // LinkedDevice
0, // size of JsonExt
NULL, // JsonExt
};
if (cExtensions != 0) {
WebAuthNCredentialOptions.Extensions.cExtensions = cExtensions;
WebAuthNCredentialOptions.Extensions.pExtensions = rgExtension;
}
PWEBAUTHN_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);
if (hr == S_OK) {
RefPtr<WebAuthnRegisterResult> result = new WebAuthnRegisterResult(
clientDataJSON, pWebAuthNCredentialAttestation);
// WEBAUTHN_CREDENTIAL_ATTESTATION structs of version >= 4 always
// include a flag to indicate whether a resident key was created. We
// copy that flag to the credProps extension output only if the RP
// requested the credProps extension.
if (requestedCredProps &&
pWebAuthNCredentialAttestation->dwVersion >=
WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4) {
BOOL rk = pWebAuthNCredentialAttestation->bResidentKey;
Unused << result->SetCredPropsRk(rk == TRUE);
}
gWinWebauthnFreeCredentialAttestation(pWebAuthNCredentialAttestation);
aPromise->Resolve(result);
} 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;
}
aPromise->Reject(aError);
}
}));
NS_DispatchBackgroundTask(runnable, NS_DISPATCH_EVENT_MAY_BLOCK);
return NS_OK;
}
NS_IMETHODIMP
WinWebAuthnService::GetAssertion(uint64_t aTransactionId,
uint64_t aBrowsingContextId,
nsIWebAuthnSignArgs* aArgs,
nsIWebAuthnSignPromise* aPromise) {
nsresult rv = EnsureWinWebAuthnModuleLoaded();
NS_ENSURE_SUCCESS(rv, rv);
Reset();
auto guard = mTransactionState.Lock();
GUID cancellationId;
{
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
if (gWinWebauthnGetCancellationId(&cancellationId) != S_OK) {
// caller will reject promise
return NS_ERROR_DOM_UNKNOWN_ERR;
}
}
*guard = Some(TransactionState{
aTransactionId,
aBrowsingContextId,
Some(RefPtr{aArgs}),
Some(RefPtr{aPromise}),
cancellationId,
});
bool conditionallyMediated;
Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
if (!conditionallyMediated) {
DoGetAssertion(Nothing(), guard);
}
return NS_OK;
}
void WinWebAuthnService::DoGetAssertion(
Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
const TransactionStateMutex::AutoLock& aGuard) {
if (aGuard->isNothing() || aGuard->ref().pendingSignArgs.isNothing() ||
aGuard->ref().pendingSignPromise.isNothing()) {
return;
}
// Take the pending Args and Promise to prevent repeated calls to
// DoGetAssertion for this transaction.
RefPtr<nsIWebAuthnSignArgs> aArgs = aGuard->ref().pendingSignArgs.extract();
RefPtr<nsIWebAuthnSignPromise> aPromise =
aGuard->ref().pendingSignPromise.extract();
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
"WinWebAuthnService::MakeCredential",
[self = RefPtr{this}, aArgs, aPromise,
aSelectedCredentialId = std::move(aSelectedCredentialId),
aCancellationId = aGuard->ref().cancellationId]() mutable {
// Take a read lock on gWinWebAuthnModuleLock to prevent the module from
// being unloaded while the operation is in progress. This does not
// prevent the operation from being cancelled, so it does not block a
// clean shutdown.
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
if (!gWinWebAuthnModule) {
aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
// Attachment
DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
// AppId
BOOL bAppIdUsed = FALSE;
BOOL* pbAppIdUsed = nullptr;
PCWSTR winAppIdentifier = nullptr;
// Client Data
nsCString clientDataJSON;
Unused << aArgs->GetClientDataJSON(clientDataJSON);
WEBAUTHN_CLIENT_DATA WebAuthNClientData = {
WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
(DWORD)clientDataJSON.Length(), (BYTE*)(clientDataJSON.get()),
WEBAUTHN_HASH_ALGORITHM_SHA_256};
nsString appId;
nsresult rv = aArgs->GetAppId(appId);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
aPromise->Reject(rv);
return;
}
winAppIdentifier = appId.get();
pbAppIdUsed = &bAppIdUsed;
}
// RPID
nsString rpId;
Unused << aArgs->GetRpId(rpId);
// User Verification Requirement
nsString userVerificationReq;
Unused << aArgs->GetUserVerification(userVerificationReq);
DWORD winUserVerificationReq;
// This mapping needs to be reviewed if values are added to the
// UserVerificationRequirement enum.
static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
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<nsTArray<uint8_t>> allowList;
nsTArray<uint8_t> allowListTransports;
if (aSelectedCredentialId.isSome()) {
allowList.AppendElement(aSelectedCredentialId.extract());
allowListTransports.AppendElement(
MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL);
} else {
Unused << aArgs->GetAllowList(allowList);
Unused << aArgs->GetAllowListTransports(allowListTransports);
}
if (allowList.Length() != allowListTransports.Length()) {
aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
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 (size_t i = 0; i < allowList.Length(); i++) {
nsTArray<uint8_t>& cred = allowList[i];
uint8_t& transports = allowListTransports[i];
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;
}
if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID) {
winTransports |= WEBAUTHN_CTAP_TRANSPORT_HYBRID;
}
WEBAUTHN_CREDENTIAL_EX credential = {
WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION,
static_cast<DWORD>(cred.Length()), (PBYTE)(cred.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;
}
uint32_t timeout_u32;
Unused << aArgs->GetTimeoutMS(&timeout_u32);
DWORD timeout = timeout_u32;
WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS WebAuthNAssertionOptions =
{
WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7,
timeout,
{0, NULL},
{0, NULL},
winAttachment,
winUserVerificationReq,
0, // dwFlags
winAppIdentifier,
pbAppIdUsed,
&aCancellationId, // CancellationId
pAllowCredentialList,
WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE,
0, // Size of CredLargeBlob
NULL, // CredLargeBlob
NULL, // HmacSecretSaltValues
FALSE, // BrowserInPrivateMode
NULL, // LinkedDevice
FALSE, // AutoFill
0, // Size of JsonExt
NULL, // JsonExt
};
PWEBAUTHN_ASSERTION pWebAuthNAssertion = nullptr;
// Bug 1518876: Get Window Handle from Content process for Windows
// WebAuthN APIs
HWND hWnd = GetForegroundWindow();
HRESULT hr = gWinWebauthnGetAssertion(
hWnd, rpId.get(), &WebAuthNClientData, &WebAuthNAssertionOptions,
&pWebAuthNAssertion);
if (hr == S_OK) {
RefPtr<WebAuthnSignResult> result =
new WebAuthnSignResult(clientDataJSON, pWebAuthNAssertion);
gWinWebauthnFreeAssertion(pWebAuthNAssertion);
if (winAppIdentifier != nullptr) {
// The gWinWebauthnGetAssertion call modified bAppIdUsed through
// a pointer provided in WebAuthNAssertionOptions.
Unused << result->SetUsedAppId(bAppIdUsed == TRUE);
}
aPromise->Resolve(result);
} 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;
}
aPromise->Reject(aError);
}
}));
NS_DispatchBackgroundTask(runnable, NS_DISPATCH_EVENT_MAY_BLOCK);
}
NS_IMETHODIMP
WinWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
const nsAString& aOrigin,
uint64_t* aRv) {
auto guard = mTransactionState.Lock();
if (guard->isNothing() ||
guard->ref().browsingContextId != aBrowsingContextId ||
guard->ref().pendingSignArgs.isNothing()) {
*aRv = 0;
return NS_OK;
}
nsString origin;
Unused << guard->ref().pendingSignArgs.ref()->GetOrigin(origin);
if (origin != aOrigin) {
*aRv = 0;
return NS_OK;
}
*aRv = guard->ref().transactionId;
return NS_OK;
}
NS_IMETHODIMP
WinWebAuthnService::GetAutoFillEntries(
uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
auto guard = mTransactionState.Lock();
if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
guard->ref().pendingSignArgs.isNothing()) {
return NS_ERROR_NOT_AVAILABLE;
}
StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
if (!gWinWebAuthnModule) {
return NS_ERROR_NOT_AVAILABLE;
}
aRv.Clear();
if (gWinWebauthnGetApiVersionNumber() < WEBAUTHN_API_VERSION_4) {
// GetPlatformCredentialList was added in version 4. Earlier versions
// can still present a generic "Use a Passkey" autofill entry, so
// this isn't an error.
return NS_OK;
}
nsString rpId;
Unused << guard->ref().pendingSignArgs.ref()->GetRpId(rpId);
WEBAUTHN_GET_CREDENTIALS_OPTIONS getCredentialsOptions{
WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1,
rpId.get(), // pwszRpId
FALSE, // bBrowserInPrivateMode
};
PWEBAUTHN_CREDENTIAL_DETAILS_LIST pCredentialList = nullptr;
HRESULT hr = gWinWebauthnGetPlatformCredentialList(&getCredentialsOptions,
&pCredentialList);
// WebAuthNGetPlatformCredentialList has an _Outptr_result_maybenull_
// annotation and a comment "Returns NTE_NOT_FOUND when credentials are
// not found."
if (pCredentialList == nullptr) {
if (hr != NTE_NOT_FOUND) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
MOZ_ASSERT(hr == S_OK);
for (size_t i = 0; i < pCredentialList->cCredentialDetails; i++) {
RefPtr<nsIWebAuthnAutoFillEntry> entry(
new WebAuthnAutoFillEntry(pCredentialList->ppCredentialDetails[i]));
aRv.AppendElement(entry);
}
gWinWebauthnFreePlatformCredentialList(pCredentialList);
return NS_OK;
}
NS_IMETHODIMP
WinWebAuthnService::SelectAutoFillEntry(
uint64_t aTransactionId, const nsTArray<uint8_t>& aCredentialId) {
auto guard = mTransactionState.Lock();
if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
guard->ref().pendingSignArgs.isNothing()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsTArray<nsTArray<uint8_t>> allowList;
Unused << guard->ref().pendingSignArgs.ref()->GetAllowList(allowList);
if (!allowList.IsEmpty() && !allowList.Contains(aCredentialId)) {
return NS_ERROR_FAILURE;
}
Maybe<nsTArray<uint8_t>> id;
id.emplace();
id.ref().Assign(aCredentialId);
DoGetAssertion(std::move(id), guard);
return NS_OK;
}
NS_IMETHODIMP
WinWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
auto guard = mTransactionState.Lock();
if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
guard->ref().pendingSignArgs.isNothing()) {
return NS_ERROR_NOT_AVAILABLE;
}
DoGetAssertion(Nothing(), guard);
return NS_OK;
}
NS_IMETHODIMP
WinWebAuthnService::PinCallback(uint64_t aTransactionId,
const nsACString& aPin) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
bool aHasConsent) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::SelectionCallback(uint64_t aTransactionId,
uint64_t aIndex) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::AddVirtualAuthenticator(
const nsACString& protocol, const nsACString& transport,
bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
bool isUserVerified, uint64_t* _retval) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::AddCredential(uint64_t authenticatorId,
const nsACString& credentialId,
bool isResidentCredential,
const nsACString& rpId,
const nsACString& privateKey,
const nsACString& userHandle,
uint32_t signCount) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::GetCredentials(
uint64_t authenticatorId,
nsTArray<RefPtr<nsICredentialParameters>>& _retval) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::RemoveCredential(uint64_t authenticatorId,
const nsACString& credentialId) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::SetUserVerified(uint64_t authenticatorId,
bool isUserVerified) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WinWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHODIMP
WinWebAuthnService::RunCommand(const nsACString& cmd) {
return NS_ERROR_NOT_IMPLEMENTED;
}
} // namespace mozilla::dom