summaryrefslogtreecommitdiffstats
path: root/dom/webauthn/WebAuthnTransactionParent.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/webauthn/WebAuthnTransactionParent.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/webauthn/WebAuthnTransactionParent.cpp')
-rw-r--r--dom/webauthn/WebAuthnTransactionParent.cpp409
1 files changed, 409 insertions, 0 deletions
diff --git a/dom/webauthn/WebAuthnTransactionParent.cpp b/dom/webauthn/WebAuthnTransactionParent.cpp
new file mode 100644
index 0000000000..697e8dc970
--- /dev/null
+++ b/dom/webauthn/WebAuthnTransactionParent.cpp
@@ -0,0 +1,409 @@
+/* -*- 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