/* 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/WebAuthnTransactionParent.h"
#include "WebAuthnArgs.h"
#include "WebAuthnUtil.h"
#include "mozilla/Base64.h"
#include "mozilla/JSONStringWriteFuncs.h"
#include "mozilla/JSONWriter.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/dom/PWindowGlobalParent.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsComponentManagerUtils.h"
#include "nsIWebAuthnRelatedOriginFetcher.h"
#include "nsThreadUtils.h"
#ifdef MOZ_WIDGET_ANDROID
# include
"mozilla/java/WebAuthnTokenManagerWrappers.h"
#endif
namespace mozilla::dom {
nsresult AssembleClientData(WindowGlobalParent* aManager,
const nsACString& aType,
const nsTArray<uint8_t>& aChallenge,
/* out */ nsACString& aJsonOut) {
nsAutoCString challengeBase64;
nsresult rv =
Base64URLEncode(aChallenge.Length(), aChallenge.Elements(),
Base64URLEncodePaddingPolicy::Omit, challengeBase64);
if (NS_FAILED(rv)) {
return rv;
}
nsIPrincipal* principal = aManager->DocumentPrincipal();
nsIPrincipal* topPrincipal =
aManager->TopWindowContext()->DocumentPrincipal();
bool crossOrigin = !principal->Equals(topPrincipal);
nsAutoCString origin;
rv = GetWebAuthnClientDataOrigin(principal, origin);
if (NS_FAILED(rv)) {
return rv;
}
// Serialize the collected client data using the algorithm from
// https://www.w3.org/TR/webauthn-3/#clientdatajson-serialization.
// Please update the definition of CollectedClientData in
// dom/webidl/WebAuthentication.webidl when changes are made here.
JSONStringRefWriteFunc f(aJsonOut);
JSONWriter w(f, JSONWriter::CollectionStyle::SingleLineStyle);
w.Start();
// Steps 2 and 3
w.StringProperty(
"type", aType);
// Steps 4 and 5
w.StringProperty(
"challenge", challengeBase64);
// Steps 6 and 7
w.StringProperty(
"origin", origin);
// Steps 8 - 10
w.BoolProperty(
"crossOrigin", crossOrigin);
// Step 11. The description of the algorithm says "If topOrigin is present",
// but the definition of topOrigin says that topOrigin "is set only if [...]
// crossOrigin is true." so we use the latter condition instead.
if (crossOrigin) {
nsCString topOrigin;
rv = topPrincipal->GetWebExposedOriginSerialization(topOrigin);
if (NS_FAILED(rv)) {
return NS_ERROR_FAILURE;
}
w.StringProperty(
"topOrigin", topOrigin);
}
w.End();
return NS_OK;
}
bool GetAssertionRequestIncludesLargeBlobRead(
const WebAuthnGetAssertionInfo& aInfo) {
for (
const WebAuthnExtension& ext : aInfo.Extensions()) {
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionLargeBlob) {
if (ext.get_WebAuthnExtensionLargeBlob().flag().isSome()) {
return ext.get_WebAuthnExtensionLargeBlob().flag().ref();
}
}
}
return false;
}
bool MakeCredentialRequestIncludesPrfExtension(
const WebAuthnMakeCredentialInfo& aInfo) {
for (
const WebAuthnExtension& ext : aInfo.Extensions()) {
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionPrf) {
return true;
}
}
return false;
}
class RelatedOriginCheckHandler final
: public nsIWebAuthnRelatedOriginCheckCallback {
public:
NS_DECL_ISUPPORTS
explicit RelatedOriginCheckHandler(WebAuthnTransactionParent* aParent)
: mParent(aParent) {}
nsresult StartCheck(
const nsACString& aRpId, WebAuthnOp aOp,
bool aShowPrompt) {
mFetcher =
do_CreateInstance(
"@mozilla.org/webauthn/related-origin-fetcher;1");
if (!mFetcher) {
return NS_ERROR_DOM_SECURITY_ERR;
}
auto* manager = static_cast<WindowGlobalParent*>(mParent->Manager());
return mFetcher->CheckRelatedOriginRequest(
manager, aRpId, aOp == WebAuthnOp::Create, aShowPrompt, this);
}
void Disconnect() {
mParent = nullptr;
mFetcher->Cancel();
mFetcher = nullptr;
}
NS_IMETHOD Resolved() override {
mFetcher = nullptr;
if (mParent) {
RefPtr<WebAuthnTransactionParent> parent = std::move(mParent);
parent->RelatedOriginApproved();
}
return NS_OK;
}
NS_IMETHOD Rejected() override {
mFetcher = nullptr;
if (mParent) {
RefPtr<WebAuthnTransactionParent> parent = std::move(mParent);
parent->AbortPendingRelatedOriginCheck(NS_ERROR_DOM_SECURITY_ERR);
}
return NS_OK;
}
NS_IMETHOD UserCancel() override {
mFetcher = nullptr;
if (mParent) {
RefPtr<WebAuthnTransactionParent> parent = std::move(mParent);
parent->AbortPendingRelatedOriginCheck(NS_ERROR_DOM_NOT_ALLOWED_ERR);
}
return NS_OK;
}
private:
~RelatedOriginCheckHandler() =
default;
RefPtr<WebAuthnTransactionParent> mParent;
nsCOMPtr<nsIWebAuthnRelatedOriginFetcher> mFetcher;
};
NS_IMPL_ISUPPORTS(RelatedOriginCheckHandler,
nsIWebAuthnRelatedOriginCheckCallback)
WebAuthnTransactionParent::WebAuthnTransactionParent() =
default;
WebAuthnTransactionParent::~WebAuthnTransactionParent() =
default;
void WebAuthnTransactionParent::CompleteTransaction() {
if (mTransactionId.isSome()) {
MOZ_ASSERT(!mRelatedOriginCheckHandler);
if (mRegisterPromiseRequest.Exists()) {
mRegisterPromiseRequest.Complete();
}
if (mSignPromiseRequest.Exists()) {
mSignPromiseRequest.Complete();
}
if (mRelatedOriginCheckHandler) {
mRelatedOriginCheckHandler->Disconnect();
mRelatedOriginCheckHandler = nullptr;
}
mPendingRegisterInfo.reset();
mPendingRegisterResolver.reset();
mPendingSignInfo.reset();
mPendingSignResolver.reset();
if (mWebAuthnService) {
// We have to do this to work around Bug 1864526.
mWebAuthnService->Cancel(mTransactionId.ref());
}
mTransactionId.reset();
}
}
void WebAuthnTransactionParent::DisconnectTransaction() {
Maybe<uint64_t> transactionId = std::move(mTransactionId);
mRegisterPromiseRequest.DisconnectIfExists();
mSignPromiseRequest.DisconnectIfExists();
if (mRelatedOriginCheckHandler) {
mRelatedOriginCheckHandler->Disconnect();
mRelatedOriginCheckHandler = nullptr;
}
if (mPendingRegisterResolver.isSome()) {
RequestRegisterResolver resolver = mPendingRegisterResolver.extract();
mPendingRegisterInfo.reset();
resolver(NS_ERROR_DOM_ABORT_ERR);
}
if (mPendingSignResolver.isSome()) {
RequestSignResolver resolver = mPendingSignResolver.extract();
mPendingSignInfo.reset();
resolver(NS_ERROR_DOM_ABORT_ERR);
}
if (mWebAuthnService && transactionId.isSome()) {
mWebAuthnService->Cancel(transactionId.ref());
}
}
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister(
const WebAuthnMakeCredentialInfo& aTransactionInfo,
RequestRegisterResolver&& aResolver) {
MOZ_ASSERT(NS_IsMainThread());
if (!mWebAuthnService) {
mWebAuthnService = do_GetService(
"@mozilla.org/webauthn/service;1");
if (!mWebAuthnService) {
aResolver(NS_ERROR_NOT_AVAILABLE);
return IPC_OK();
}
}
// If there's an ongoing transaction, abort it.
if (mTransactionId.isSome()) {
DisconnectTransaction();
}
uint64_t aTransactionId = NextId();
mTransactionId = Some(aTransactionId);
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
if (!IsWebAuthnAllowedInContext(manager)) {
aResolver(NS_ERROR_DOM_SECURITY_ERR);
return IPC_OK();
}
nsIPrincipal* principal = manager->DocumentPrincipal();
nsAutoCString origin;
nsresult rv = GetWebAuthnClientDataOrigin(principal, origin);
if (NS_FAILED(rv)) {
aResolver(NS_ERROR_DOM_SECURITY_ERR);
return IPC_OK();
}
if (IsValidRpId(principal, aTransactionInfo.RpId())) {
ContinueWithRegister(origin, aTransactionInfo, std::move(aResolver));
return IPC_OK();
}
rv = BeginRelatedOriginCheck(aTransactionInfo.RpId(), WebAuthnOp::Create);
if (NS_FAILED(rv)) {
aResolver(NS_ERROR_DOM_SECURITY_ERR);
return IPC_OK();
}
mPendingRegisterInfo = Some(aTransactionInfo);
mPendingRegisterResolver = Some(std::move(aResolver));
return IPC_OK();
}
void WebAuthnTransactionParent::RelatedOriginApproved() {
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
nsAutoCString origin;
nsresult rv =
GetWebAuthnClientDataOrigin(manager->DocumentPrincipal(), origin);
if (NS_FAILED(rv)) {
AbortPendingRelatedOriginCheck(NS_ERROR_DOM_SECURITY_ERR);
return;
}
MOZ_ASSERT(mTransactionId.isSome());
mRelatedOriginCheckHandler = nullptr;
if (mPendingRegisterResolver.isSome()) {
MOZ_ASSERT(mPendingRegisterInfo.isSome());
WebAuthnMakeCredentialInfo info = mPendingRegisterInfo.extract();
RequestRegisterResolver resolver = mPendingRegisterResolver.extract();
ContinueWithRegister(origin, info, std::move(resolver));
}
else {
MOZ_ASSERT(mPendingSignInfo.isSome());
MOZ_ASSERT(mPendingSignResolver.isSome());
WebAuthnGetAssertionInfo info = mPendingSignInfo.extract();
RequestSignResolver resolver = mPendingSignResolver.extract();
ContinueWithSign(origin, info, std::move(resolver));
}
}
void WebAuthnTransactionParent::AbortPendingRelatedOriginCheck(
nsresult aError) {
mRelatedOriginCheckHandler = nullptr;
if (mPendingRegisterResolver.isSome()) {
RequestRegisterResolver resolver = mPendingRegisterResolver.extract();
mPendingRegisterInfo.reset();
resolver(aError);
}
else if (mPendingSignResolver.isSome()) {
RequestSignResolver resolver = mPendingSignResolver.extract();
mPendingSignInfo.reset();
resolver(aError);
}
mTransactionId.reset();
}
void WebAuthnTransactionParent::ContinueWithRegister(
const nsCString& aOrigin,
const WebAuthnMakeCredentialInfo& aInfo,
RequestRegisterResolver&& aResolver) {
MOZ_ASSERT(mTransactionId.isSome());
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
nsIPrincipal* principal = manager->DocumentPrincipal();
uint64_t aTransactionId = mTransactionId.ref();
nsCString clientDataJSON;
nsresult rv = AssembleClientData(manager,
"webauthn.create"_ns,
aInfo.Challenge(), clientDataJSON);
if (NS_FAILED(rv)) {
aResolver(NS_ERROR_FAILURE);
mTransactionId.reset();
return;
}
bool requestIncludesPrfExtension =
MakeCredentialRequestIncludesPrfExtension(aInfo);
RefPtr<WebAuthnRegisterPromiseHolder> promiseHolder =
new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
RefPtr<WebAuthnRegisterPromise> promise = promiseHolder->Ensure();
promise
->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this}, inputClientData = clientDataJSON,
requestIncludesPrfExtension, resolver = std::move(aResolver)](
const WebAuthnRegisterPromise::ResolveOrRejectValue& aValue) {
self->CompleteTransaction();
if (aValue.IsReject()) {
resolver(aValue.RejectValue());
return;
}
auto rejectWithNotAllowed = MakeScopeExit(
[&]() { resolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); });
RefPtr<nsIWebAuthnRegisterResult> registerResult =
aValue.ResolveValue();
nsCString clientData;
nsresult rv = registerResult->GetClientDataJSON(clientData);
if (rv == NS_ERROR_NOT_AVAILABLE) {
clientData = inputClientData;
}
else if (NS_FAILED(rv)) {
return;
}
nsTArray<uint8_t> attObj;
rv = registerResult->GetAttestationObject(attObj);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsTArray<uint8_t> credentialId;
rv = registerResult->GetCredentialId(credentialId);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsTArray<nsString> transports;
rv = registerResult->GetTransports(transports);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
Maybe<nsString> authenticatorAttachment;
nsString maybeAuthenticatorAttachment;
rv = registerResult->GetAuthenticatorAttachment(
maybeAuthenticatorAttachment);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
authenticatorAttachment = Some(maybeAuthenticatorAttachment);
}
nsTArray<WebAuthnExtensionResult> extensions;
bool credPropsRk;
rv = registerResult->GetCredPropsRk(&credPropsRk);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
extensions.AppendElement(
WebAuthnExtensionResultCredProps(credPropsRk));
}
bool hmacCreateSecret;
rv = registerResult->GetHmacCreateSecret(&hmacCreateSecret);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
extensions.AppendElement(
WebAuthnExtensionResultHmacSecret(hmacCreateSecret));
}
bool largeBlobSupported;
rv = registerResult->GetLargeBlobSupported(&largeBlobSupported);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
return;
}
nsTArray<uint8_t> blob;
// unused
extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
largeBlobSupported, blob,
false));
}
if (requestIncludesPrfExtension) {
Maybe<WebAuthnExtensionPrfValues> prfResults = Nothing();
bool prfEnabled =
false;
rv = registerResult->GetPrfEnabled(&prfEnabled);
if (rv != NS_ERROR_NOT_AVAILABLE && NS_FAILED(rv)) {
return;
}
nsTArray<uint8_t> prfResultsFirst;
rv = registerResult->GetPrfResultsFirst(prfResultsFirst);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
bool prfResultsSecondMaybe =
false;
nsTArray<uint8_t> prfResultsSecond;
rv = registerResult->GetPrfResultsSecond(prfResultsSecond);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
prfResultsSecondMaybe = true;
}
prfResults = Some(WebAuthnExtensionPrfValues(
prfResultsFirst, prfResultsSecondMaybe, prfResultsSecond));
}
extensions.AppendElement(
WebAuthnExtensionResultPrf(Some(prfEnabled), prfResults));
}
WebAuthnMakeCredentialResult result(
clientData, attObj, credentialId, transports, extensions,
authenticatorAttachment);
rejectWithNotAllowed.release();
resolver(result);
})
->Track(mRegisterPromiseRequest);
uint64_t browsingContextId = manager->GetBrowsingContext()->Top()->Id();
bool privateBrowsing = principal->GetIsInPrivateBrowsing();
auto args = MakeRefPtr<WebAuthnRegisterArgs>(aOrigin, clientDataJSON,
privateBrowsing, aInfo);
rv = mWebAuthnService->MakeCredential(aTransactionId, browsingContextId, args,
promiseHolder);
if (NS_FAILED(rv)) {
promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
}
}
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
const WebAuthnGetAssertionInfo& aTransactionInfo,
RequestSignResolver&& aResolver) {
MOZ_ASSERT(NS_IsMainThread());
if (!mWebAuthnService) {
mWebAuthnService = do_GetService(
"@mozilla.org/webauthn/service;1");
if (!mWebAuthnService) {
aResolver(NS_ERROR_NOT_AVAILABLE);
return IPC_OK();
}
}
if (mTransactionId.isSome()) {
DisconnectTransaction();
}
uint64_t transactionId = NextId();
mTransactionId = Some(transactionId);
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
if (!IsWebAuthnAllowedInContext(manager)) {
aResolver(NS_ERROR_DOM_SECURITY_ERR);
return IPC_OK();
}
nsIPrincipal* principal = manager->DocumentPrincipal();
nsAutoCString origin;
nsresult rv = GetWebAuthnClientDataOrigin(principal, origin);
if (NS_FAILED(rv)) {
aResolver(NS_ERROR_DOM_SECURITY_ERR);
return IPC_OK();
}
if (IsValidRpId(principal, aTransactionInfo.RpId())) {
ContinueWithSign(origin, aTransactionInfo, std::move(aResolver));
return IPC_OK();
}
// Bug 2043449: A conditionally mediated request must not trigger a related
// origin request, which may prompt the user. Fail with a security error
// instead.
if (aTransactionInfo.ConditionallyMediated()) {
mTransactionId.reset();
aResolver(NS_ERROR_DOM_SECURITY_ERR);
return IPC_OK();
}
rv = BeginRelatedOriginCheck(aTransactionInfo.RpId(), WebAuthnOp::Assert);
if (NS_FAILED(rv)) {
aResolver(NS_ERROR_DOM_SECURITY_ERR);
return IPC_OK();
}
mPendingSignInfo = Some(aTransactionInfo);
mPendingSignResolver = Some(std::move(aResolver));
return IPC_OK();
}
nsresult WebAuthnTransactionParent::BeginRelatedOriginCheck(
const nsACString& aRpId, WebAuthnOp aOp) {
const uint32_t mode =
StaticPrefs::security_webauthn_related_origin_requests_mode();
if (mode == nsIWebAuthnRelatedOriginFetcher::MODE_DISABLED) {
mTransactionId.reset();
return NS_ERROR_DOM_SECURITY_ERR;
}
mRelatedOriginCheckHandler = MakeRefPtr<RelatedOriginCheckHandler>(this);
nsresult rv = mRelatedOriginCheckHandler->StartCheck(
aRpId, aOp, mode == nsIWebAuthnRelatedOriginFetcher::MODE_PROMPT);
if (NS_FAILED(rv)) {
mRelatedOriginCheckHandler = nullptr;
mTransactionId.reset();
return NS_ERROR_DOM_SECURITY_ERR;
}
return NS_OK;
}
void WebAuthnTransactionParent::ContinueWithSign(
const nsCString& aOrigin,
const WebAuthnGetAssertionInfo& aInfo,
RequestSignResolver&& aResolver) {
MOZ_ASSERT(mTransactionId.isSome());
WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
nsIPrincipal* principal = manager->DocumentPrincipal();
uint64_t transactionId = mTransactionId.ref();
if (aInfo.AppId().isSome() && !IsValidAppId(principal, aInfo.AppId().ref())) {
aResolver(NS_ERROR_DOM_SECURITY_ERR);
mTransactionId.reset();
return;
}
for (
const WebAuthnExtension& ext : aInfo.Extensions()) {
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionPrf) {
if (ext.get_WebAuthnExtensionPrf().evalByCredential().Length() >
kWebAuthnMaxAllowedCredentials) {
aResolver(NS_ERROR_DOM_NOT_ALLOWED_ERR);
mTransactionId.reset();
return;
}
}
}
nsCString clientDataJSON;
nsresult rv = AssembleClientData(manager,
"webauthn.get"_ns,
aInfo.Challenge(), clientDataJSON);
if (NS_FAILED(rv)) {
aResolver(NS_ERROR_FAILURE);
mTransactionId.reset();
return;
}
bool requestIncludesAppId = aInfo.AppId().isSome();
bool requestIncludesLargeBlobRead =
GetAssertionRequestIncludesLargeBlobRead(aInfo);
RefPtr<WebAuthnSignPromiseHolder> promiseHolder =
new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget());
RefPtr<WebAuthnSignPromise> promise = promiseHolder->Ensure();
promise
->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this}, inputClientData = clientDataJSON,
requestIncludesAppId, requestIncludesLargeBlobRead,
resolver = std::move(aResolver)](
const WebAuthnSignPromise::ResolveOrRejectValue& aValue) {
self->CompleteTransaction();
if (aValue.IsReject()) {
resolver(aValue.RejectValue());
return;
}
auto rejectWithNotAllowed = MakeScopeExit(
[&]() { resolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); });
RefPtr<nsIWebAuthnSignResult> signResult = aValue.ResolveValue();
nsCString clientData;
nsresult rv = signResult->GetClientDataJSON(clientData);
if (rv == NS_ERROR_NOT_AVAILABLE) {
clientData = inputClientData;
}
else if (NS_FAILED(rv)) {
return;
}
nsTArray<uint8_t> credentialId;
rv = signResult->GetCredentialId(credentialId);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsTArray<uint8_t> signature;
rv = signResult->GetSignature(signature);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsTArray<uint8_t> authenticatorData;
rv = signResult->GetAuthenticatorData(authenticatorData);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsTArray<uint8_t> userHandle;
(
void)signResult->GetUserHandle(userHandle);
// optional
Maybe<nsString> authenticatorAttachment;
nsString maybeAuthenticatorAttachment;
rv = signResult->GetAuthenticatorAttachment(
maybeAuthenticatorAttachment);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
authenticatorAttachment = Some(maybeAuthenticatorAttachment);
}
nsTArray<WebAuthnExtensionResult> extensions;
if (requestIncludesAppId) {
bool usedAppId =
false;
rv = signResult->GetUsedAppId(&usedAppId);
if (rv != NS_ERROR_NOT_AVAILABLE && NS_FAILED(rv)) {
return;
}
extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
}
nsTArray<uint8_t> largeBlobValue;
rv = signResult->GetLargeBlobValue(largeBlobValue);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
return;
}
extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
true, largeBlobValue,
false));
}
else if (requestIncludesLargeBlobRead) {
// Signal a read error by setting both flags.
extensions.AppendElement(
WebAuthnExtensionResultLargeBlob(true, largeBlobValue, true));
}
else {
// Read and write operations are mutually exclusive, so we only
// check for a write result if the read result is not available.
bool largeBlobWritten;
rv = signResult->GetLargeBlobWritten(&largeBlobWritten);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
return;
}
extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
false, largeBlobValue, largeBlobWritten));
}
}
{
Maybe<WebAuthnExtensionPrfValues> prfResults;
bool prfMaybe =
false;
rv = signResult->GetPrfMaybe(&prfMaybe);
if (rv == NS_OK && prfMaybe) {
nsTArray<uint8_t> prfResultsFirst;
rv = signResult->GetPrfResultsFirst(prfResultsFirst);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
bool prfResultsSecondMaybe =
false;
nsTArray<uint8_t> prfResultsSecond;
rv = signResult->GetPrfResultsSecond(prfResultsSecond);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
prfResultsSecondMaybe = true;
}
prfResults = Some(WebAuthnExtensionPrfValues(
prfResultsFirst, prfResultsSecondMaybe,
prfResultsSecond));
}
else {
prfResults = Nothing();
}
extensions.AppendElement(
WebAuthnExtensionResultPrf(Nothing(), prfResults));
}
}
WebAuthnGetAssertionResult result(
clientData, credentialId, signature, authenticatorData,
extensions, userHandle, authenticatorAttachment);
rejectWithNotAllowed.release();
resolver(result);
})
->Track(mSignPromiseRequest);
uint64_t browsingContextId = manager->GetBrowsingContext()->Top()->Id();
bool privateBrowsing = principal->GetIsInPrivateBrowsing();
auto args = MakeRefPtr<WebAuthnSignArgs>(aOrigin, clientDataJSON,
privateBrowsing, aInfo);
rv = mWebAuthnService->GetAssertion(transactionId, browsingContextId, args,
promiseHolder);
if (NS_FAILED(rv)) {
promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
}
}
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestCancel() {
MOZ_ASSERT(NS_IsMainThread());
if (mTransactionId.isNothing()) {
return IPC_OK();
}
DisconnectTransaction();
return IPC_OK();
}
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestIsUVPAA(
RequestIsUVPAAResolver&& aResolver) {
MOZ_ASSERT(NS_IsMainThread());
#ifdef MOZ_WIDGET_ANDROID
// Try the nsIWebAuthnService. If we're configured for tests we
// will get a result. Otherwise we expect NS_ERROR_NOT_IMPLEMENTED.
nsCOMPtr<nsIWebAuthnService> service(
do_GetService(
"@mozilla.org/webauthn/service;1"));
bool available;
nsresult rv = service->GetIsUVPAA(&available);
if (NS_SUCCEEDED(rv)) {
aResolver(available);
return IPC_OK();
}
// Don't consult the platform API if resident key support is disabled.
if (!StaticPrefs::
security_webauthn_webauthn_enable_android_fido2_residentkey()) {
aResolver(
false);
return IPC_OK();
}
// The GeckoView implementation of
// isUserVerifiyingPlatformAuthenticatorAvailable dispatches the work to a
// background thread and returns a MozPromise which we can ->Then to call
// aResolver on the current thread.
nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
auto result = java::WebAuthnTokenManager::
WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable();
auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
MozPromise<
bool,
bool,
false>::FromGeckoResult(geckoResult)
->Then(target, __func__,
[resolver = std::move(aResolver)](
const MozPromise<
bool,
bool,
false>::ResolveOrRejectValue&
aValue) {
if (aValue.IsResolve()) {
resolver(aValue.ResolveValue());
}
else {
resolver(
false);
}
});
return IPC_OK();
#else
nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
__func__, [target, resolver = std::move(aResolver)]() {
bool available;
nsCOMPtr<nsIWebAuthnService> service(
do_GetService(
"@mozilla.org/webauthn/service;1"));
nsresult rv = service->GetIsUVPAA(&available);
if (NS_FAILED(rv)) {
available =
false;
}
BoolPromise::CreateAndResolve(available, __func__)
->Then(target, __func__,
[resolver](
const BoolPromise::ResolveOrRejectValue& value) {
if (value.IsResolve()) {
resolver(value.ResolveValue());
}
else {
resolver(
false);
}
});
}));
NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
return IPC_OK();
#endif
}
void WebAuthnTransactionParent::ActorDestroy(ActorDestroyReason aWhy) {
// Called either by Send__delete__() in RecvDestroyMe() above, or when
// the channel disconnects. Ensure the token manager forgets about us.
MOZ_ASSERT(NS_IsMainThread());
if (mTransactionId.isSome()) {
DisconnectTransaction();
}
}
}
// namespace mozilla::dom