mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	authenticator-rs keeps its device monitor thread alive after a transaction has completed. This is a workaround to ensure that the device monitor thread does not prevent the browser from closing. Differential Revision: https://phabricator.services.mozilla.com/D193481
		
			
				
	
	
		
			409 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
	
		
			15 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/WebAuthnTransactionParent.h"
 | 
						|
#include "mozilla/ipc/PBackgroundParent.h"
 | 
						|
#include "mozilla/ipc/BackgroundParent.h"
 | 
						|
#include "mozilla/StaticPrefs_security.h"
 | 
						|
 | 
						|
#include "nsThreadUtils.h"
 | 
						|
#include "WebAuthnArgs.h"
 | 
						|
 | 
						|
namespace mozilla::dom {
 | 
						|
 | 
						|
void WebAuthnTransactionParent::CompleteTransaction() {
 | 
						|
  if (mTransactionId.isSome()) {
 | 
						|
    if (mRegisterPromiseRequest.Exists()) {
 | 
						|
      mRegisterPromiseRequest.Complete();
 | 
						|
    }
 | 
						|
    if (mSignPromiseRequest.Exists()) {
 | 
						|
      mSignPromiseRequest.Complete();
 | 
						|
    }
 | 
						|
    if (mWebAuthnService) {
 | 
						|
      // We have to do this to work around Bug 1864526.
 | 
						|
      mWebAuthnService->Cancel(mTransactionId.ref());
 | 
						|
    }
 | 
						|
    mTransactionId.reset();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void WebAuthnTransactionParent::DisconnectTransaction() {
 | 
						|
  mTransactionId.reset();
 | 
						|
  mRegisterPromiseRequest.DisconnectIfExists();
 | 
						|
  mSignPromiseRequest.DisconnectIfExists();
 | 
						|
  if (mWebAuthnService) {
 | 
						|
    mWebAuthnService->Reset();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister(
 | 
						|
    const uint64_t& aTransactionId,
 | 
						|
    const WebAuthnMakeCredentialInfo& aTransactionInfo) {
 | 
						|
  ::mozilla::ipc::AssertIsOnBackgroundThread();
 | 
						|
 | 
						|
  if (!mWebAuthnService) {
 | 
						|
    mWebAuthnService = do_GetService("@mozilla.org/webauthn/service;1");
 | 
						|
    if (!mWebAuthnService) {
 | 
						|
      return IPC_FAIL_NO_REASON(this);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If there's an ongoing transaction, abort it.
 | 
						|
  if (mTransactionId.isSome()) {
 | 
						|
    DisconnectTransaction();
 | 
						|
    Unused << SendAbort(mTransactionId.ref(), NS_ERROR_DOM_ABORT_ERR);
 | 
						|
  }
 | 
						|
  mTransactionId = Some(aTransactionId);
 | 
						|
 | 
						|
  RefPtr<WebAuthnRegisterPromiseHolder> promiseHolder =
 | 
						|
      new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
 | 
						|
 | 
						|
  PWebAuthnTransactionParent* parent = this;
 | 
						|
  RefPtr<WebAuthnRegisterPromise> promise = promiseHolder->Ensure();
 | 
						|
  promise
 | 
						|
      ->Then(
 | 
						|
          GetCurrentSerialEventTarget(), __func__,
 | 
						|
          [this, parent, aTransactionId,
 | 
						|
           inputClientData = aTransactionInfo.ClientDataJSON()](
 | 
						|
              const WebAuthnRegisterPromise::ResolveValueType& aValue) {
 | 
						|
            CompleteTransaction();
 | 
						|
 | 
						|
            nsCString clientData;
 | 
						|
            nsresult rv = aValue->GetClientDataJSON(clientData);
 | 
						|
            if (rv == NS_ERROR_NOT_AVAILABLE) {
 | 
						|
              clientData = inputClientData;
 | 
						|
            } else if (NS_FAILED(rv)) {
 | 
						|
              Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                          NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<uint8_t> attObj;
 | 
						|
            rv = aValue->GetAttestationObject(attObj);
 | 
						|
            if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
              Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                          NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<uint8_t> credentialId;
 | 
						|
            rv = aValue->GetCredentialId(credentialId);
 | 
						|
            if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
              Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                          NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<nsString> transports;
 | 
						|
            rv = aValue->GetTransports(transports);
 | 
						|
            if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
              Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                          NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
 | 
						|
            Maybe<nsString> authenticatorAttachment;
 | 
						|
            nsString maybeAuthenticatorAttachment;
 | 
						|
            rv = aValue->GetAuthenticatorAttachment(
 | 
						|
                maybeAuthenticatorAttachment);
 | 
						|
            if (rv != NS_ERROR_NOT_AVAILABLE) {
 | 
						|
              if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
                Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                            NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
                return;
 | 
						|
              }
 | 
						|
              authenticatorAttachment = Some(maybeAuthenticatorAttachment);
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<WebAuthnExtensionResult> extensions;
 | 
						|
            bool credPropsRk;
 | 
						|
            rv = aValue->GetCredPropsRk(&credPropsRk);
 | 
						|
            if (rv != NS_ERROR_NOT_AVAILABLE) {
 | 
						|
              if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
                Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                            NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
                return;
 | 
						|
              }
 | 
						|
              extensions.AppendElement(
 | 
						|
                  WebAuthnExtensionResultCredProps(credPropsRk));
 | 
						|
            }
 | 
						|
 | 
						|
            bool hmacCreateSecret;
 | 
						|
            rv = aValue->GetHmacCreateSecret(&hmacCreateSecret);
 | 
						|
            if (rv != NS_ERROR_NOT_AVAILABLE) {
 | 
						|
              if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
                Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                            NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
                return;
 | 
						|
              }
 | 
						|
              extensions.AppendElement(
 | 
						|
                  WebAuthnExtensionResultHmacSecret(hmacCreateSecret));
 | 
						|
            }
 | 
						|
 | 
						|
            WebAuthnMakeCredentialResult result(
 | 
						|
                clientData, attObj, credentialId, transports, extensions,
 | 
						|
                authenticatorAttachment);
 | 
						|
 | 
						|
            Unused << parent->SendConfirmRegister(aTransactionId, result);
 | 
						|
          },
 | 
						|
          [this, parent, aTransactionId](
 | 
						|
              const WebAuthnRegisterPromise::RejectValueType aValue) {
 | 
						|
            CompleteTransaction();
 | 
						|
            Unused << parent->SendAbort(aTransactionId, aValue);
 | 
						|
          })
 | 
						|
      ->Track(mRegisterPromiseRequest);
 | 
						|
 | 
						|
  uint64_t browsingContextId = aTransactionInfo.BrowsingContextId();
 | 
						|
  RefPtr<WebAuthnRegisterArgs> args(new WebAuthnRegisterArgs(aTransactionInfo));
 | 
						|
 | 
						|
  nsresult rv = mWebAuthnService->MakeCredential(
 | 
						|
      aTransactionId, browsingContextId, args, promiseHolder);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
  }
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
 | 
						|
    const uint64_t& aTransactionId,
 | 
						|
    const WebAuthnGetAssertionInfo& aTransactionInfo) {
 | 
						|
  ::mozilla::ipc::AssertIsOnBackgroundThread();
 | 
						|
 | 
						|
  if (!mWebAuthnService) {
 | 
						|
    mWebAuthnService = do_GetService("@mozilla.org/webauthn/service;1");
 | 
						|
    if (!mWebAuthnService) {
 | 
						|
      return IPC_FAIL_NO_REASON(this);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (mTransactionId.isSome()) {
 | 
						|
    DisconnectTransaction();
 | 
						|
    Unused << SendAbort(mTransactionId.ref(), NS_ERROR_DOM_ABORT_ERR);
 | 
						|
  }
 | 
						|
  mTransactionId = Some(aTransactionId);
 | 
						|
 | 
						|
  RefPtr<WebAuthnSignPromiseHolder> promiseHolder =
 | 
						|
      new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget());
 | 
						|
 | 
						|
  PWebAuthnTransactionParent* parent = this;
 | 
						|
  RefPtr<WebAuthnSignPromise> promise = promiseHolder->Ensure();
 | 
						|
  promise
 | 
						|
      ->Then(
 | 
						|
          GetCurrentSerialEventTarget(), __func__,
 | 
						|
          [this, parent, aTransactionId,
 | 
						|
           inputClientData = aTransactionInfo.ClientDataJSON()](
 | 
						|
              const WebAuthnSignPromise::ResolveValueType& aValue) {
 | 
						|
            CompleteTransaction();
 | 
						|
 | 
						|
            nsCString clientData;
 | 
						|
            nsresult rv = aValue->GetClientDataJSON(clientData);
 | 
						|
            if (rv == NS_ERROR_NOT_AVAILABLE) {
 | 
						|
              clientData = inputClientData;
 | 
						|
            } else if (NS_FAILED(rv)) {
 | 
						|
              Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                          NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<uint8_t> credentialId;
 | 
						|
            rv = aValue->GetCredentialId(credentialId);
 | 
						|
            if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
              Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                          NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<uint8_t> signature;
 | 
						|
            rv = aValue->GetSignature(signature);
 | 
						|
            if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
              Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                          NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<uint8_t> authenticatorData;
 | 
						|
            rv = aValue->GetAuthenticatorData(authenticatorData);
 | 
						|
            if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
              Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                          NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
              return;
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<uint8_t> userHandle;
 | 
						|
            Unused << aValue->GetUserHandle(userHandle);  // optional
 | 
						|
 | 
						|
            Maybe<nsString> authenticatorAttachment;
 | 
						|
            nsString maybeAuthenticatorAttachment;
 | 
						|
            rv = aValue->GetAuthenticatorAttachment(
 | 
						|
                maybeAuthenticatorAttachment);
 | 
						|
            if (rv != NS_ERROR_NOT_AVAILABLE) {
 | 
						|
              if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
                Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                            NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
                return;
 | 
						|
              }
 | 
						|
              authenticatorAttachment = Some(maybeAuthenticatorAttachment);
 | 
						|
            }
 | 
						|
 | 
						|
            nsTArray<WebAuthnExtensionResult> extensions;
 | 
						|
            bool usedAppId;
 | 
						|
            rv = aValue->GetUsedAppId(&usedAppId);
 | 
						|
            if (rv != NS_ERROR_NOT_AVAILABLE) {
 | 
						|
              if (NS_FAILED(rv)) {
 | 
						|
                Unused << parent->SendAbort(aTransactionId,
 | 
						|
                                            NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
                return;
 | 
						|
              }
 | 
						|
              extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
 | 
						|
            }
 | 
						|
 | 
						|
            WebAuthnGetAssertionResult result(
 | 
						|
                clientData, credentialId, signature, authenticatorData,
 | 
						|
                extensions, userHandle, authenticatorAttachment);
 | 
						|
 | 
						|
            Unused << parent->SendConfirmSign(aTransactionId, result);
 | 
						|
          },
 | 
						|
          [this, parent,
 | 
						|
           aTransactionId](const WebAuthnSignPromise::RejectValueType aValue) {
 | 
						|
            CompleteTransaction();
 | 
						|
            Unused << parent->SendAbort(aTransactionId, aValue);
 | 
						|
          })
 | 
						|
      ->Track(mSignPromiseRequest);
 | 
						|
 | 
						|
  RefPtr<WebAuthnSignArgs> args(new WebAuthnSignArgs(aTransactionInfo));
 | 
						|
 | 
						|
  nsresult rv = mWebAuthnService->GetAssertion(
 | 
						|
      aTransactionId, aTransactionInfo.BrowsingContextId(), args,
 | 
						|
      promiseHolder);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
 | 
						|
  }
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestCancel(
 | 
						|
    const Tainted<uint64_t>& aTransactionId) {
 | 
						|
  ::mozilla::ipc::AssertIsOnBackgroundThread();
 | 
						|
 | 
						|
  if (mTransactionId.isNothing() ||
 | 
						|
      !MOZ_IS_VALID(aTransactionId, mTransactionId.ref() == aTransactionId)) {
 | 
						|
    return IPC_OK();
 | 
						|
  }
 | 
						|
 | 
						|
  DisconnectTransaction();
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestIsUVPAA(
 | 
						|
    RequestIsUVPAAResolver&& aResolver) {
 | 
						|
#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 does not block, but we must
 | 
						|
  // call it on the main thread. It returns a MozPromise which we can ->Then to
 | 
						|
  // call aResolver on the IPDL background thread.
 | 
						|
  //
 | 
						|
  // Bug 1550788: there is an unnecessary layer of dispatching here: ipdl ->
 | 
						|
  // main -> a background thread. Other platforms just do ipdl -> a background
 | 
						|
  // thread.
 | 
						|
  nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
 | 
						|
  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
 | 
						|
      __func__, [target, resolver = std::move(aResolver)]() {
 | 
						|
        auto result = java::WebAuthnTokenManager::
 | 
						|
            WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable();
 | 
						|
        auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
 | 
						|
        MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
 | 
						|
            ->Then(
 | 
						|
                target, __func__,
 | 
						|
                [resolver](
 | 
						|
                    const MozPromise<bool, bool, false>::ResolveOrRejectValue&
 | 
						|
                        aValue) {
 | 
						|
                  if (aValue.IsResolve()) {
 | 
						|
                    resolver(aValue.ResolveValue());
 | 
						|
                  } else {
 | 
						|
                    resolver(false);
 | 
						|
                  }
 | 
						|
                });
 | 
						|
      }));
 | 
						|
  NS_DispatchToMainThread(runnable.forget());
 | 
						|
  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
 | 
						|
}
 | 
						|
 | 
						|
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvDestroyMe() {
 | 
						|
  ::mozilla::ipc::AssertIsOnBackgroundThread();
 | 
						|
 | 
						|
  // The child was disconnected from the WebAuthnManager instance and will send
 | 
						|
  // no further messages. It is kept alive until we delete it explicitly.
 | 
						|
 | 
						|
  // The child should have cancelled any active transaction. This means
 | 
						|
  // we expect no more messages to the child. We'll crash otherwise.
 | 
						|
 | 
						|
  // The IPC roundtrip is complete. No more messages, hopefully.
 | 
						|
  IProtocol* mgr = Manager();
 | 
						|
  if (!Send__delete__(this)) {
 | 
						|
    return IPC_FAIL_NO_REASON(mgr);
 | 
						|
  }
 | 
						|
 | 
						|
  return IPC_OK();
 | 
						|
}
 | 
						|
 | 
						|
void WebAuthnTransactionParent::ActorDestroy(ActorDestroyReason aWhy) {
 | 
						|
  ::mozilla::ipc::AssertIsOnBackgroundThread();
 | 
						|
 | 
						|
  // Called either by Send__delete__() in RecvDestroyMe() above, or when
 | 
						|
  // the channel disconnects. Ensure the token manager forgets about us.
 | 
						|
 | 
						|
  if (mTransactionId.isSome()) {
 | 
						|
    DisconnectTransaction();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla::dom
 |