tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WebAuthnTransactionParent.cpp (21874B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/WebAuthnTransactionParent.h"
      8 
      9 #include "WebAuthnArgs.h"
     10 #include "WebAuthnUtil.h"
     11 #include "mozilla/Base64.h"
     12 #include "mozilla/JSONStringWriteFuncs.h"
     13 #include "mozilla/JSONWriter.h"
     14 #include "mozilla/StaticPrefs_security.h"
     15 #include "mozilla/dom/PWindowGlobalParent.h"
     16 #include "mozilla/dom/WindowGlobalParent.h"
     17 #include "nsThreadUtils.h"
     18 
     19 #ifdef MOZ_WIDGET_ANDROID
     20 #  include "mozilla/java/WebAuthnTokenManagerWrappers.h"
     21 #endif
     22 
     23 namespace mozilla::dom {
     24 
     25 nsresult AssembleClientData(WindowGlobalParent* aManager,
     26                            const nsACString& aType,
     27                            const nsTArray<uint8_t>& aChallenge,
     28                            /* out */ nsACString& aJsonOut) {
     29  nsAutoCString challengeBase64;
     30  nsresult rv =
     31      Base64URLEncode(aChallenge.Length(), aChallenge.Elements(),
     32                      Base64URLEncodePaddingPolicy::Omit, challengeBase64);
     33  if (NS_FAILED(rv)) {
     34    return NS_ERROR_FAILURE;
     35  }
     36 
     37  nsIPrincipal* principal = aManager->DocumentPrincipal();
     38  nsIPrincipal* topPrincipal =
     39      aManager->TopWindowContext()->DocumentPrincipal();
     40 
     41  nsCString origin;
     42  rv = principal->GetWebExposedOriginSerialization(origin);
     43  if (NS_FAILED(rv)) {
     44    return NS_ERROR_FAILURE;
     45  }
     46 
     47  bool crossOrigin = !principal->Equals(topPrincipal);
     48 
     49  // Serialize the collected client data using the algorithm from
     50  // https://www.w3.org/TR/webauthn-3/#clientdatajson-serialization.
     51  // Please update the definition of CollectedClientData in
     52  // dom/webidl/WebAuthentication.webidl when changes are made here.
     53  JSONStringRefWriteFunc f(aJsonOut);
     54  JSONWriter w(f, JSONWriter::CollectionStyle::SingleLineStyle);
     55  w.Start();
     56  // Steps 2 and 3
     57  w.StringProperty("type", aType);
     58  // Steps 4 and 5
     59  w.StringProperty("challenge", challengeBase64);
     60  // Steps 6 and 7
     61  w.StringProperty("origin", origin);
     62  // Steps 8 - 10
     63  w.BoolProperty("crossOrigin", crossOrigin);
     64  // Step 11. The description of the algorithm says "If topOrigin is present",
     65  // but the definition of topOrigin says that topOrigin "is set only if [...]
     66  // crossOrigin is true." so we use the latter condition instead.
     67  if (crossOrigin) {
     68    nsCString topOrigin;
     69    rv = topPrincipal->GetWebExposedOriginSerialization(topOrigin);
     70    if (NS_FAILED(rv)) {
     71      return NS_ERROR_FAILURE;
     72    }
     73    w.StringProperty("topOrigin", topOrigin);
     74  }
     75  w.End();
     76 
     77  return NS_OK;
     78 }
     79 
     80 bool GetAssertionRequestIncludesLargeBlobRead(
     81    const WebAuthnGetAssertionInfo& aInfo) {
     82  for (const WebAuthnExtension& ext : aInfo.Extensions()) {
     83    if (ext.type() == WebAuthnExtension::TWebAuthnExtensionLargeBlob) {
     84      if (ext.get_WebAuthnExtensionLargeBlob().flag().isSome()) {
     85        return ext.get_WebAuthnExtensionLargeBlob().flag().ref();
     86      }
     87    }
     88  }
     89  return false;
     90 }
     91 
     92 bool MakeCredentialRequestIncludesPrfExtension(
     93    const WebAuthnMakeCredentialInfo& aInfo) {
     94  for (const WebAuthnExtension& ext : aInfo.Extensions()) {
     95    if (ext.type() == WebAuthnExtension::TWebAuthnExtensionPrf) {
     96      return true;
     97    }
     98  }
     99  return false;
    100 }
    101 
    102 void WebAuthnTransactionParent::CompleteTransaction() {
    103  if (mTransactionId.isSome()) {
    104    if (mRegisterPromiseRequest.Exists()) {
    105      mRegisterPromiseRequest.Complete();
    106    }
    107    if (mSignPromiseRequest.Exists()) {
    108      mSignPromiseRequest.Complete();
    109    }
    110    if (mWebAuthnService) {
    111      // We have to do this to work around Bug 1864526.
    112      mWebAuthnService->Cancel(mTransactionId.ref());
    113    }
    114    mTransactionId.reset();
    115  }
    116 }
    117 
    118 void WebAuthnTransactionParent::DisconnectTransaction() {
    119  mTransactionId.reset();
    120  mRegisterPromiseRequest.DisconnectIfExists();
    121  mSignPromiseRequest.DisconnectIfExists();
    122  if (mWebAuthnService) {
    123    mWebAuthnService->Reset();
    124  }
    125 }
    126 
    127 mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister(
    128    const WebAuthnMakeCredentialInfo& aTransactionInfo,
    129    RequestRegisterResolver&& aResolver) {
    130  MOZ_ASSERT(NS_IsMainThread());
    131 
    132  if (!mWebAuthnService) {
    133    mWebAuthnService = do_GetService("@mozilla.org/webauthn/service;1");
    134    if (!mWebAuthnService) {
    135      aResolver(NS_ERROR_NOT_AVAILABLE);
    136      return IPC_OK();
    137    }
    138  }
    139 
    140  // If there's an ongoing transaction, abort it.
    141  if (mTransactionId.isSome()) {
    142    DisconnectTransaction();
    143  }
    144  uint64_t aTransactionId = NextId();
    145  mTransactionId = Some(aTransactionId);
    146 
    147  WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
    148 
    149  if (!IsWebAuthnAllowedInContext(manager)) {
    150    aResolver(NS_ERROR_DOM_SECURITY_ERR);
    151    return IPC_OK();
    152  }
    153 
    154  nsIPrincipal* principal = manager->DocumentPrincipal();
    155  if (!IsValidRpId(principal, aTransactionInfo.RpId())) {
    156    aResolver(NS_ERROR_DOM_SECURITY_ERR);
    157    return IPC_OK();
    158  }
    159 
    160  nsCString origin;
    161  nsresult rv = principal->GetWebExposedOriginSerialization(origin);
    162  if (NS_FAILED(rv)) {
    163    aResolver(NS_ERROR_FAILURE);
    164    return IPC_OK();
    165  }
    166 
    167  nsCString clientDataJSON;
    168  rv = AssembleClientData(manager, "webauthn.create"_ns,
    169                          aTransactionInfo.Challenge(), clientDataJSON);
    170  if (NS_FAILED(rv)) {
    171    aResolver(NS_ERROR_FAILURE);
    172    return IPC_OK();
    173  }
    174 
    175  bool requestIncludesPrfExtension =
    176      MakeCredentialRequestIncludesPrfExtension(aTransactionInfo);
    177 
    178  RefPtr<WebAuthnRegisterPromiseHolder> promiseHolder =
    179      new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
    180 
    181  RefPtr<WebAuthnRegisterPromise> promise = promiseHolder->Ensure();
    182  promise
    183      ->Then(
    184          GetCurrentSerialEventTarget(), __func__,
    185          [self = RefPtr{this}, inputClientData = clientDataJSON,
    186           requestIncludesPrfExtension, resolver = std::move(aResolver)](
    187              const WebAuthnRegisterPromise::ResolveOrRejectValue& aValue) {
    188            self->CompleteTransaction();
    189 
    190            if (aValue.IsReject()) {
    191              resolver(aValue.RejectValue());
    192              return;
    193            }
    194 
    195            auto rejectWithNotAllowed = MakeScopeExit(
    196                [&]() { resolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); });
    197 
    198            RefPtr<nsIWebAuthnRegisterResult> registerResult =
    199                aValue.ResolveValue();
    200 
    201            nsCString clientData;
    202            nsresult rv = registerResult->GetClientDataJSON(clientData);
    203            if (rv == NS_ERROR_NOT_AVAILABLE) {
    204              clientData = inputClientData;
    205            } else if (NS_FAILED(rv)) {
    206              return;
    207            }
    208 
    209            nsTArray<uint8_t> attObj;
    210            rv = registerResult->GetAttestationObject(attObj);
    211            if (NS_WARN_IF(NS_FAILED(rv))) {
    212              return;
    213            }
    214 
    215            nsTArray<uint8_t> credentialId;
    216            rv = registerResult->GetCredentialId(credentialId);
    217            if (NS_WARN_IF(NS_FAILED(rv))) {
    218              return;
    219            }
    220 
    221            nsTArray<nsString> transports;
    222            rv = registerResult->GetTransports(transports);
    223            if (NS_WARN_IF(NS_FAILED(rv))) {
    224              return;
    225            }
    226 
    227            Maybe<nsString> authenticatorAttachment;
    228            nsString maybeAuthenticatorAttachment;
    229            rv = registerResult->GetAuthenticatorAttachment(
    230                maybeAuthenticatorAttachment);
    231            if (rv != NS_ERROR_NOT_AVAILABLE) {
    232              if (NS_WARN_IF(NS_FAILED(rv))) {
    233                return;
    234              }
    235              authenticatorAttachment = Some(maybeAuthenticatorAttachment);
    236            }
    237 
    238            nsTArray<WebAuthnExtensionResult> extensions;
    239            bool credPropsRk;
    240            rv = registerResult->GetCredPropsRk(&credPropsRk);
    241            if (rv != NS_ERROR_NOT_AVAILABLE) {
    242              if (NS_WARN_IF(NS_FAILED(rv))) {
    243                return;
    244              }
    245              extensions.AppendElement(
    246                  WebAuthnExtensionResultCredProps(credPropsRk));
    247            }
    248 
    249            bool hmacCreateSecret;
    250            rv = registerResult->GetHmacCreateSecret(&hmacCreateSecret);
    251            if (rv != NS_ERROR_NOT_AVAILABLE) {
    252              if (NS_WARN_IF(NS_FAILED(rv))) {
    253                return;
    254              }
    255              extensions.AppendElement(
    256                  WebAuthnExtensionResultHmacSecret(hmacCreateSecret));
    257            }
    258 
    259            bool largeBlobSupported;
    260            rv = registerResult->GetLargeBlobSupported(&largeBlobSupported);
    261            if (rv != NS_ERROR_NOT_AVAILABLE) {
    262              if (NS_FAILED(rv)) {
    263                return;
    264              }
    265              nsTArray<uint8_t> blob;  // unused
    266              extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
    267                  largeBlobSupported, blob, false));
    268            }
    269 
    270            if (requestIncludesPrfExtension) {
    271              Maybe<WebAuthnExtensionPrfValues> prfResults = Nothing();
    272 
    273              bool prfEnabled = false;
    274              rv = registerResult->GetPrfEnabled(&prfEnabled);
    275              if (rv != NS_ERROR_NOT_AVAILABLE && NS_FAILED(rv)) {
    276                return;
    277              }
    278 
    279              nsTArray<uint8_t> prfResultsFirst;
    280              rv = registerResult->GetPrfResultsFirst(prfResultsFirst);
    281              if (rv != NS_ERROR_NOT_AVAILABLE) {
    282                if (NS_WARN_IF(NS_FAILED(rv))) {
    283                  return;
    284                }
    285 
    286                bool prfResultsSecondMaybe = false;
    287                nsTArray<uint8_t> prfResultsSecond;
    288                rv = registerResult->GetPrfResultsSecond(prfResultsSecond);
    289                if (rv != NS_ERROR_NOT_AVAILABLE) {
    290                  if (NS_WARN_IF(NS_FAILED(rv))) {
    291                    return;
    292                  }
    293                  prfResultsSecondMaybe = true;
    294                }
    295 
    296                prfResults = Some(WebAuthnExtensionPrfValues(
    297                    prfResultsFirst, prfResultsSecondMaybe, prfResultsSecond));
    298              }
    299 
    300              extensions.AppendElement(
    301                  WebAuthnExtensionResultPrf(Some(prfEnabled), prfResults));
    302            }
    303 
    304            WebAuthnMakeCredentialResult result(
    305                clientData, attObj, credentialId, transports, extensions,
    306                authenticatorAttachment);
    307 
    308            rejectWithNotAllowed.release();
    309            resolver(result);
    310          })
    311      ->Track(mRegisterPromiseRequest);
    312 
    313  uint64_t browsingContextId = manager->GetBrowsingContext()->Top()->Id();
    314  bool privateBrowsing = principal->GetIsInPrivateBrowsing();
    315  auto args = MakeRefPtr<WebAuthnRegisterArgs>(
    316      origin, clientDataJSON, privateBrowsing, aTransactionInfo);
    317  rv = mWebAuthnService->MakeCredential(aTransactionId, browsingContextId, args,
    318                                        promiseHolder);
    319  if (NS_FAILED(rv)) {
    320    promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
    321  }
    322 
    323  return IPC_OK();
    324 }
    325 
    326 mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
    327    const WebAuthnGetAssertionInfo& aTransactionInfo,
    328    RequestSignResolver&& aResolver) {
    329  MOZ_ASSERT(NS_IsMainThread());
    330 
    331  if (!mWebAuthnService) {
    332    mWebAuthnService = do_GetService("@mozilla.org/webauthn/service;1");
    333    if (!mWebAuthnService) {
    334      aResolver(NS_ERROR_NOT_AVAILABLE);
    335      return IPC_OK();
    336    }
    337  }
    338 
    339  if (mTransactionId.isSome()) {
    340    DisconnectTransaction();
    341  }
    342  uint64_t transactionId = NextId();
    343  mTransactionId = Some(transactionId);
    344 
    345  WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager());
    346 
    347  if (!IsWebAuthnAllowedInContext(manager)) {
    348    aResolver(NS_ERROR_DOM_SECURITY_ERR);
    349    return IPC_OK();
    350  }
    351 
    352  nsIPrincipal* principal = manager->DocumentPrincipal();
    353  if (!IsValidRpId(principal, aTransactionInfo.RpId())) {
    354    aResolver(NS_ERROR_DOM_SECURITY_ERR);
    355    return IPC_OK();
    356  }
    357 
    358  if (aTransactionInfo.AppId().isSome() &&
    359      !IsValidAppId(principal, aTransactionInfo.AppId().ref())) {
    360    aResolver(NS_ERROR_DOM_SECURITY_ERR);
    361    return IPC_OK();
    362  }
    363 
    364  nsCString origin;
    365  nsresult rv = principal->GetWebExposedOriginSerialization(origin);
    366  if (NS_FAILED(rv)) {
    367    aResolver(NS_ERROR_FAILURE);
    368    return IPC_OK();
    369  }
    370 
    371  nsCString clientDataJSON;
    372  rv = AssembleClientData(manager, "webauthn.get"_ns,
    373                          aTransactionInfo.Challenge(), clientDataJSON);
    374  if (NS_FAILED(rv)) {
    375    aResolver(NS_ERROR_FAILURE);
    376    return IPC_OK();
    377  }
    378 
    379  bool requestIncludesAppId = aTransactionInfo.AppId().isSome();
    380 
    381  bool requestIncludesLargeBlobRead =
    382      GetAssertionRequestIncludesLargeBlobRead(aTransactionInfo);
    383 
    384  RefPtr<WebAuthnSignPromiseHolder> promiseHolder =
    385      new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget());
    386 
    387  RefPtr<WebAuthnSignPromise> promise = promiseHolder->Ensure();
    388  promise
    389      ->Then(
    390          GetCurrentSerialEventTarget(), __func__,
    391          [self = RefPtr{this}, inputClientData = clientDataJSON,
    392           requestIncludesAppId, requestIncludesLargeBlobRead,
    393           resolver = std::move(aResolver)](
    394              const WebAuthnSignPromise::ResolveOrRejectValue& aValue) {
    395            self->CompleteTransaction();
    396 
    397            if (aValue.IsReject()) {
    398              resolver(aValue.RejectValue());
    399              return;
    400            }
    401 
    402            auto rejectWithNotAllowed = MakeScopeExit(
    403                [&]() { resolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); });
    404 
    405            RefPtr<nsIWebAuthnSignResult> signResult = aValue.ResolveValue();
    406 
    407            nsCString clientData;
    408            nsresult rv = signResult->GetClientDataJSON(clientData);
    409            if (rv == NS_ERROR_NOT_AVAILABLE) {
    410              clientData = inputClientData;
    411            } else if (NS_FAILED(rv)) {
    412              return;
    413            }
    414 
    415            nsTArray<uint8_t> credentialId;
    416            rv = signResult->GetCredentialId(credentialId);
    417            if (NS_WARN_IF(NS_FAILED(rv))) {
    418              return;
    419            }
    420 
    421            nsTArray<uint8_t> signature;
    422            rv = signResult->GetSignature(signature);
    423            if (NS_WARN_IF(NS_FAILED(rv))) {
    424              return;
    425            }
    426 
    427            nsTArray<uint8_t> authenticatorData;
    428            rv = signResult->GetAuthenticatorData(authenticatorData);
    429            if (NS_WARN_IF(NS_FAILED(rv))) {
    430              return;
    431            }
    432 
    433            nsTArray<uint8_t> userHandle;
    434            (void)signResult->GetUserHandle(userHandle);  // optional
    435 
    436            Maybe<nsString> authenticatorAttachment;
    437            nsString maybeAuthenticatorAttachment;
    438            rv = signResult->GetAuthenticatorAttachment(
    439                maybeAuthenticatorAttachment);
    440            if (rv != NS_ERROR_NOT_AVAILABLE) {
    441              if (NS_WARN_IF(NS_FAILED(rv))) {
    442                return;
    443              }
    444              authenticatorAttachment = Some(maybeAuthenticatorAttachment);
    445            }
    446 
    447            nsTArray<WebAuthnExtensionResult> extensions;
    448            if (requestIncludesAppId) {
    449              bool usedAppId = false;
    450              rv = signResult->GetUsedAppId(&usedAppId);
    451              if (rv != NS_ERROR_NOT_AVAILABLE && NS_FAILED(rv)) {
    452                return;
    453              }
    454              extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
    455            }
    456 
    457            nsTArray<uint8_t> largeBlobValue;
    458            rv = signResult->GetLargeBlobValue(largeBlobValue);
    459            if (rv != NS_ERROR_NOT_AVAILABLE) {
    460              if (NS_FAILED(rv)) {
    461                return;
    462              }
    463              extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
    464                  true, largeBlobValue, false));
    465            } else if (requestIncludesLargeBlobRead) {
    466              // Signal a read error by setting both flags.
    467              extensions.AppendElement(
    468                  WebAuthnExtensionResultLargeBlob(true, largeBlobValue, true));
    469            } else {
    470              // Read and write operations are mutually exclusive, so we only
    471              // check for a write result if the read result is not available.
    472              bool largeBlobWritten;
    473              rv = signResult->GetLargeBlobWritten(&largeBlobWritten);
    474              if (rv != NS_ERROR_NOT_AVAILABLE) {
    475                if (NS_FAILED(rv)) {
    476                  return;
    477                }
    478                extensions.AppendElement(WebAuthnExtensionResultLargeBlob(
    479                    false, largeBlobValue, largeBlobWritten));
    480              }
    481            }
    482 
    483            {
    484              Maybe<WebAuthnExtensionPrfValues> prfResults;
    485              bool prfMaybe = false;
    486              rv = signResult->GetPrfMaybe(&prfMaybe);
    487              if (rv == NS_OK && prfMaybe) {
    488                nsTArray<uint8_t> prfResultsFirst;
    489                rv = signResult->GetPrfResultsFirst(prfResultsFirst);
    490                if (rv != NS_ERROR_NOT_AVAILABLE) {
    491                  if (NS_WARN_IF(NS_FAILED(rv))) {
    492                    return;
    493                  }
    494 
    495                  bool prfResultsSecondMaybe = false;
    496                  nsTArray<uint8_t> prfResultsSecond;
    497                  rv = signResult->GetPrfResultsSecond(prfResultsSecond);
    498                  if (rv != NS_ERROR_NOT_AVAILABLE) {
    499                    if (NS_WARN_IF(NS_FAILED(rv))) {
    500                      return;
    501                    }
    502                    prfResultsSecondMaybe = true;
    503                  }
    504 
    505                  prfResults = Some(WebAuthnExtensionPrfValues(
    506                      prfResultsFirst, prfResultsSecondMaybe,
    507                      prfResultsSecond));
    508                } else {
    509                  prfResults = Nothing();
    510                }
    511 
    512                extensions.AppendElement(
    513                    WebAuthnExtensionResultPrf(Nothing(), prfResults));
    514              }
    515            }
    516 
    517            WebAuthnGetAssertionResult result(
    518                clientData, credentialId, signature, authenticatorData,
    519                extensions, userHandle, authenticatorAttachment);
    520 
    521            rejectWithNotAllowed.release();
    522            resolver(result);
    523          })
    524      ->Track(mSignPromiseRequest);
    525 
    526  uint64_t browsingContextId = manager->GetBrowsingContext()->Top()->Id();
    527  bool privateBrowsing = principal->GetIsInPrivateBrowsing();
    528  auto args = MakeRefPtr<WebAuthnSignArgs>(origin, clientDataJSON,
    529                                           privateBrowsing, aTransactionInfo);
    530  rv = mWebAuthnService->GetAssertion(transactionId, browsingContextId, args,
    531                                      promiseHolder);
    532  if (NS_FAILED(rv)) {
    533    promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
    534  }
    535 
    536  return IPC_OK();
    537 }
    538 
    539 mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestCancel() {
    540  MOZ_ASSERT(NS_IsMainThread());
    541 
    542  if (mTransactionId.isNothing()) {
    543    return IPC_OK();
    544  }
    545 
    546  DisconnectTransaction();
    547  return IPC_OK();
    548 }
    549 
    550 mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestIsUVPAA(
    551    RequestIsUVPAAResolver&& aResolver) {
    552  MOZ_ASSERT(NS_IsMainThread());
    553 
    554 #ifdef MOZ_WIDGET_ANDROID
    555  // Try the nsIWebAuthnService. If we're configured for tests we
    556  // will get a result. Otherwise we expect NS_ERROR_NOT_IMPLEMENTED.
    557  nsCOMPtr<nsIWebAuthnService> service(
    558      do_GetService("@mozilla.org/webauthn/service;1"));
    559  bool available;
    560  nsresult rv = service->GetIsUVPAA(&available);
    561  if (NS_SUCCEEDED(rv)) {
    562    aResolver(available);
    563    return IPC_OK();
    564  }
    565 
    566  // Don't consult the platform API if resident key support is disabled.
    567  if (!StaticPrefs::
    568          security_webauthn_webauthn_enable_android_fido2_residentkey()) {
    569    aResolver(false);
    570    return IPC_OK();
    571  }
    572 
    573  // The GeckoView implementation of
    574  // isUserVerifiyingPlatformAuthenticatorAvailable dispatches the work to a
    575  // background thread and returns a MozPromise which we can ->Then to call
    576  // aResolver on the current thread.
    577  nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
    578  auto result = java::WebAuthnTokenManager::
    579      WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable();
    580  auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
    581  MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
    582      ->Then(target, __func__,
    583             [resolver = std::move(aResolver)](
    584                 const MozPromise<bool, bool, false>::ResolveOrRejectValue&
    585                     aValue) {
    586               if (aValue.IsResolve()) {
    587                 resolver(aValue.ResolveValue());
    588               } else {
    589                 resolver(false);
    590               }
    591             });
    592  return IPC_OK();
    593 
    594 #else
    595 
    596  nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
    597  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
    598      __func__, [target, resolver = std::move(aResolver)]() {
    599        bool available;
    600        nsCOMPtr<nsIWebAuthnService> service(
    601            do_GetService("@mozilla.org/webauthn/service;1"));
    602        nsresult rv = service->GetIsUVPAA(&available);
    603        if (NS_FAILED(rv)) {
    604          available = false;
    605        }
    606        BoolPromise::CreateAndResolve(available, __func__)
    607            ->Then(target, __func__,
    608                   [resolver](const BoolPromise::ResolveOrRejectValue& value) {
    609                     if (value.IsResolve()) {
    610                       resolver(value.ResolveValue());
    611                     } else {
    612                       resolver(false);
    613                     }
    614                   });
    615      }));
    616  NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
    617  return IPC_OK();
    618 #endif
    619 }
    620 
    621 void WebAuthnTransactionParent::ActorDestroy(ActorDestroyReason aWhy) {
    622  // Called either by Send__delete__() in RecvDestroyMe() above, or when
    623  // the channel disconnects. Ensure the token manager forgets about us.
    624  MOZ_ASSERT(NS_IsMainThread());
    625 
    626  if (mTransactionId.isSome()) {
    627    DisconnectTransaction();
    628  }
    629 }
    630 
    631 }  // namespace mozilla::dom