/* -*- 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 promiseHolder = new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget()); PWebAuthnTransactionParent* parent = this; RefPtr 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 attObj; rv = aValue->GetAttestationObject(attObj); if (NS_WARN_IF(NS_FAILED(rv))) { Unused << parent->SendAbort(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR); return; } nsTArray credentialId; rv = aValue->GetCredentialId(credentialId); if (NS_WARN_IF(NS_FAILED(rv))) { Unused << parent->SendAbort(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR); return; } nsTArray transports; rv = aValue->GetTransports(transports); if (NS_WARN_IF(NS_FAILED(rv))) { Unused << parent->SendAbort(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR); return; } Maybe 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 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 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 promiseHolder = new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget()); PWebAuthnTransactionParent* parent = this; RefPtr 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 credentialId; rv = aValue->GetCredentialId(credentialId); if (NS_WARN_IF(NS_FAILED(rv))) { Unused << parent->SendAbort(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR); return; } nsTArray signature; rv = aValue->GetSignature(signature); if (NS_WARN_IF(NS_FAILED(rv))) { Unused << parent->SendAbort(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR); return; } nsTArray authenticatorData; rv = aValue->GetAuthenticatorData(authenticatorData); if (NS_WARN_IF(NS_FAILED(rv))) { Unused << parent->SendAbort(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR); return; } nsTArray userHandle; Unused << aValue->GetUserHandle(userHandle); // optional Maybe 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 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 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& 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 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 target = GetCurrentSerialEventTarget(); nsCOMPtr runnable(NS_NewRunnableFunction( __func__, [target, resolver = std::move(aResolver)]() { auto result = java::WebAuthnTokenManager:: WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable(); auto geckoResult = java::GeckoResult::LocalRef(std::move(result)); MozPromise::FromGeckoResult(geckoResult) ->Then( target, __func__, [resolver]( const MozPromise::ResolveOrRejectValue& aValue) { if (aValue.IsResolve()) { resolver(aValue.ResolveValue()); } else { resolver(false); } }); })); NS_DispatchToMainThread(runnable.forget()); return IPC_OK(); #else nsCOMPtr target = GetCurrentSerialEventTarget(); nsCOMPtr runnable(NS_NewRunnableFunction( __func__, [target, resolver = std::move(aResolver)]() { bool available; nsCOMPtr 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