/* -*- 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 "json/json.h" #include "mozilla/dom/WebAuthnController.h" #include "mozilla/dom/PWebAuthnTransactionParent.h" #include "mozilla/dom/WebAuthnUtil.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_security.h" #include "mozilla/Unused.h" #include "nsEscape.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsIThread.h" #include "nsTextFormatter.h" #include "mozilla/Telemetry.h" #include "AuthrsTransport.h" #include "CtapArgs.h" #include "CtapResults.h" #include "U2FSoftTokenTransport.h" #include "WebAuthnEnumStrings.h" #ifdef MOZ_WIDGET_ANDROID # include "mozilla/dom/AndroidWebAuthnTokenManager.h" #endif namespace mozilla::dom { /*********************************************************************** * Statics **********************************************************************/ namespace { static mozilla::LazyLogModule gWebAuthnControllerLog("webauthncontroller"); StaticRefPtr gWebAuthnController; static nsIThread* gWebAuthnBackgroundThread; } // namespace // Data for WebAuthn UI prompt notifications. static const char16_t kPresencePromptNotification[] = u"{\"is_ctap2\":true,\"action\":\"presence\",\"tid\":%llu," u"\"origin\":\"%s\",\"browsingContextId\":%llu}"; static const char16_t kRegisterDirectPromptNotification[] = u"{\"is_ctap2\":true,\"action\":\"register-direct\",\"tid\":%llu," u"\"origin\":\"%s\",\"browsingContextId\":%llu}"; static const char16_t kCancelPromptNotification[] = u"{\"is_ctap2\":true,\"action\":\"cancel\",\"tid\":%llu}"; static const char16_t kSelectSignResultNotification[] = u"{\"is_ctap2\":true,\"action\":\"select-sign-result\",\"tid\":%llu," u"\"origin\":\"%s\",\"browsingContextId\":%llu,\"usernames\":[%s]}"; /*********************************************************************** * U2FManager Implementation **********************************************************************/ NS_IMPL_ISUPPORTS(WebAuthnController, nsIWebAuthnController, nsIU2FTokenManager); WebAuthnController::WebAuthnController() : mTransactionParent(nullptr) { MOZ_ASSERT(XRE_IsParentProcess()); // Create on the main thread to make sure ClearOnShutdown() works. MOZ_ASSERT(NS_IsMainThread()); } // static void WebAuthnController::Initialize() { if (!XRE_IsParentProcess()) { return; } MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!gWebAuthnController); gWebAuthnController = new WebAuthnController(); ClearOnShutdown(&gWebAuthnController); } // static WebAuthnController* WebAuthnController::Get() { MOZ_ASSERT(XRE_IsParentProcess()); mozilla::ipc::AssertIsOnBackgroundThread(); return gWebAuthnController; } void WebAuthnController::AbortTransaction(const uint64_t& aTransactionId, const nsresult& aError, bool shouldCancelActiveDialog) { if (mTransactionParent && mTransaction.isSome() && aTransactionId > 0 && aTransactionId == mTransaction.ref().mTransactionId) { Unused << mTransactionParent->SendAbort(aTransactionId, aError); ClearTransaction(shouldCancelActiveDialog); } } void WebAuthnController::AbortOngoingTransaction() { if (mTransaction.isSome()) { AbortTransaction(mTransaction.ref().mTransactionId, NS_ERROR_DOM_ABORT_ERR, true); } } void WebAuthnController::MaybeClearTransaction( PWebAuthnTransactionParent* aParent) { // Only clear if we've been requested to do so by our current transaction // parent. if (mTransactionParent == aParent) { ClearTransaction(true); } } void WebAuthnController::ClearTransaction(bool cancel_prompt) { if (cancel_prompt && mTransaction.isSome() && mTransaction.ref().mTransactionId > 0) { // Remove any prompts we might be showing for the current transaction. SendPromptNotification(kCancelPromptNotification, mTransaction.ref().mTransactionId); } mTransactionParent = nullptr; mTransportImpl = nullptr; // Forget any pending registration. mPendingRegisterInfo.reset(); mPendingSignInfo.reset(); mPendingSignResults.Clear(); mTransaction.reset(); } template void WebAuthnController::SendPromptNotification(const char16_t* aFormat, T... aArgs) { MOZ_ASSERT(!NS_IsMainThread()); nsAutoString json; nsTextFormatter::ssprintf(json, aFormat, aArgs...); nsCOMPtr r(NewRunnableMethod( "WebAuthnController::RunSendPromptNotification", this, &WebAuthnController::RunSendPromptNotification, json)); MOZ_ALWAYS_SUCCEEDS(GetMainThreadSerialEventTarget()->Dispatch( r.forget(), NS_DISPATCH_NORMAL)); } NS_IMETHODIMP WebAuthnController::SendPromptNotificationPreformatted( uint64_t aTransactionId, const nsACString& aJson) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); nsCOMPtr r(NewRunnableMethod( "WebAuthnController::RunSendPromptNotification", this, &WebAuthnController::RunSendPromptNotification, NS_ConvertUTF8toUTF16(aJson))); MOZ_ALWAYS_SUCCEEDS(GetMainThreadSerialEventTarget()->Dispatch( r.forget(), NS_DISPATCH_NORMAL)); return NS_OK; } void WebAuthnController::RunSendPromptNotification(const nsString& aJSON) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr os = services::GetObserverService(); if (NS_WARN_IF(!os)) { return; } nsCOMPtr self = this; MOZ_ALWAYS_SUCCEEDS( os->NotifyObservers(self, "webauthn-prompt", aJSON.get())); } nsCOMPtr WebAuthnController::GetTransportImpl() { mozilla::ipc::AssertIsOnBackgroundThread(); if (mTransportImpl) { return mTransportImpl; } /* Enable in Bug 1819414 */ #if 0 # ifdef MOZ_WIDGET_ANDROID // On Android, prefer the platform support if enabled. if (StaticPrefs::security_webauth_webauthn_enable_android_fido2()) { nsCOMPtr transport = AndroidWebAuthnTokenManager::GetInstance(); transport->SetController(this); return transport; } # endif #endif // Prefer the HW token, even if the softtoken is enabled too. // We currently don't support soft and USB tokens enabled at the // same time as the softtoken would always win the race to register. // We could support it for signing though... if (StaticPrefs::security_webauth_webauthn_enable_usbtoken()) { nsCOMPtr transport = NewAuthrsTransport(); transport->SetController(this); return transport; } if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) { nsCOMPtr transport = new U2FSoftTokenTransport( StaticPrefs::security_webauth_softtoken_counter()); transport->SetController(this); return transport; } // TODO Use WebAuthnRequest to aggregate results from all transports, // once we have multiple HW transport types. return nullptr; } void WebAuthnController::Cancel(PWebAuthnTransactionParent* aTransactionParent, const Tainted& aTransactionId) { // The last transaction ID also suffers from the issue described in Bug // 1696159. A content process could cancel another content processes // transaction by guessing the last transaction ID. if (mTransactionParent != aTransactionParent || mTransaction.isNothing() || !MOZ_IS_VALID(aTransactionId, mTransaction.ref().mTransactionId == aTransactionId)) { return; } if (mTransportImpl) { mTransportImpl->Cancel(); } ClearTransaction(true); } void WebAuthnController::Register( PWebAuthnTransactionParent* aTransactionParent, const uint64_t& aTransactionId, const WebAuthnMakeCredentialInfo& aInfo) { mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_LOG(gWebAuthnControllerLog, LogLevel::Debug, ("WebAuthnController::Register")); MOZ_ASSERT(aTransactionId > 0); if (!gWebAuthnBackgroundThread) { gWebAuthnBackgroundThread = NS_GetCurrentThread(); MOZ_ASSERT(gWebAuthnBackgroundThread, "This should never be null!"); } AbortOngoingTransaction(); mTransactionParent = aTransactionParent; /* We could refactor to defer the hashing here */ CryptoBuffer rpIdHash, clientDataHash; NS_ConvertUTF16toUTF8 rpId(aInfo.RpId()); nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash, clientDataHash); if (NS_WARN_IF(NS_FAILED(rv))) { // We haven't set mTransaction yet, so we can't use AbortTransaction Unused << mTransactionParent->SendAbort(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR); return; } // Hold on to any state that we need to finish the transaction. mTransaction = Some( Transaction(aTransactionId, rpIdHash, Nothing(), aInfo.ClientDataJSON())); MOZ_ASSERT(mPendingRegisterInfo.isNothing()); mPendingRegisterInfo = Some(aInfo); // Determine whether direct attestation was requested. bool noneAttestationRequested = true; // On Android, let's always reject direct attestations until we have a // mechanism to solicit user consent, from Bug 1550164 #ifndef MOZ_WIDGET_ANDROID // The default attestation type is "none", so set // noneAttestationRequested=false only if the RP's preference matches one of // the other known types. This needs to be reviewed if values are added to // the AttestationConveyancePreference enum. const nsString& attestation = aInfo.attestationConveyancePreference(); static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 2); if (attestation.EqualsLiteral( MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT) || attestation.EqualsLiteral( MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT) || attestation.EqualsLiteral( MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE)) { noneAttestationRequested = false; } #endif // not MOZ_WIDGET_ANDROID // Start a register request immediately if direct attestation // wasn't requested or the test pref is set. if (noneAttestationRequested || StaticPrefs:: security_webauth_webauthn_testing_allow_direct_attestation()) { DoRegister(aInfo, noneAttestationRequested); return; } // If the RP request direct attestation, ask the user for permission and // store the transaction info until the user proceeds or cancels. NS_ConvertUTF16toUTF8 origin(aInfo.Origin()); SendPromptNotification(kRegisterDirectPromptNotification, aTransactionId, origin.get(), aInfo.BrowsingContextId()); } void WebAuthnController::DoRegister(const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) { mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_ASSERT(mTransaction.isSome()); if (NS_WARN_IF(mTransaction.isNothing())) { // Clear prompt? return; } // Show a prompt that lets the user cancel the ongoing transaction. NS_ConvertUTF16toUTF8 origin(aInfo.Origin()); SendPromptNotification(kPresencePromptNotification, mTransaction.ref().mTransactionId, origin.get(), aInfo.BrowsingContextId(), "false"); RefPtr args( new CtapRegisterArgs(aInfo, aForceNoneAttestation)); mTransportImpl = GetTransportImpl(); if (!mTransportImpl) { AbortTransaction(mTransaction.ref().mTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR, true); return; } nsresult rv = mTransportImpl->MakeCredential( mTransaction.ref().mTransactionId, aInfo.BrowsingContextId(), args); if (NS_FAILED(rv)) { AbortTransaction(mTransaction.ref().mTransactionId, rv, true); return; } } NS_IMETHODIMP WebAuthnController::ResumeRegister(uint64_t aTransactionId, bool aForceNoneAttestation) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); if (!gWebAuthnBackgroundThread) { return NS_ERROR_FAILURE; } nsCOMPtr r(NewRunnableMethod( "WebAuthnController::RunResumeRegister", this, &WebAuthnController::RunResumeRegister, aTransactionId, aForceNoneAttestation)); return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void WebAuthnController::RunResumeRegister(uint64_t aTransactionId, bool aForceNoneAttestation) { mozilla::ipc::AssertIsOnBackgroundThread(); if (NS_WARN_IF(mPendingRegisterInfo.isNothing())) { return; } if (mTransaction.isNothing() || mTransaction.ref().mTransactionId != aTransactionId) { return; } // Resume registration and cleanup. DoRegister(mPendingRegisterInfo.ref(), aForceNoneAttestation); } NS_IMETHODIMP WebAuthnController::FinishRegister(uint64_t aTransactionId, nsICtapRegisterResult* aResult) { MOZ_ASSERT(XRE_IsParentProcess()); nsCOMPtr r( NewRunnableMethod>( "WebAuthnController::RunFinishRegister", this, &WebAuthnController::RunFinishRegister, aTransactionId, aResult)); if (!gWebAuthnBackgroundThread) { return NS_ERROR_FAILURE; } return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void WebAuthnController::RunFinishRegister( uint64_t aTransactionId, const RefPtr& aResult) { mozilla::ipc::AssertIsOnBackgroundThread(); if (mTransaction.isNothing() || aTransactionId != mTransaction.ref().mTransactionId) { // The previous transaction was likely cancelled from the prompt. return; } nsresult status; nsresult rv = aResult->GetStatus(&status); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } if (NS_FAILED(status)) { bool shouldCancelActiveDialog = true; if (status == NS_ERROR_DOM_OPERATION_ERR) { // PIN-related errors. Let the dialog show to inform the user shouldCancelActiveDialog = false; } Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED, u"CTAPRegisterAbort"_ns, 1); AbortTransaction(aTransactionId, status, shouldCancelActiveDialog); return; } nsCString clientDataJson = mPendingRegisterInfo.ref().ClientDataJSON(); nsTArray attObj; rv = aResult->GetAttestationObject(attObj); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } nsTArray credentialId; rv = aResult->GetCredentialId(credentialId); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } nsTArray extensions; WebAuthnMakeCredentialResult result(clientDataJson, attObj, credentialId, extensions); Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED, u"CTAPRegisterFinish"_ns, 1); Unused << mTransactionParent->SendConfirmRegister(aTransactionId, result); ClearTransaction(true); } void WebAuthnController::Sign(PWebAuthnTransactionParent* aTransactionParent, const uint64_t& aTransactionId, const WebAuthnGetAssertionInfo& aInfo) { mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_LOG(gWebAuthnControllerLog, LogLevel::Debug, ("WebAuthnSign")); MOZ_ASSERT(aTransactionId > 0); if (!gWebAuthnBackgroundThread) { gWebAuthnBackgroundThread = NS_GetCurrentThread(); MOZ_ASSERT(gWebAuthnBackgroundThread, "This should never be null!"); } AbortOngoingTransaction(); mTransactionParent = aTransactionParent; /* We could refactor to defer the hashing here */ CryptoBuffer rpIdHash, clientDataHash; NS_ConvertUTF16toUTF8 rpId(aInfo.RpId()); nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash, clientDataHash); if (NS_WARN_IF(NS_FAILED(rv))) { // We haven't set mTransaction yet, so we can't use AbortTransaction Unused << mTransactionParent->SendAbort(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR); return; } Maybe> appIdHash = Nothing(); for (const WebAuthnExtension& ext : aInfo.Extensions()) { if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) { appIdHash = Some(ext.get_WebAuthnExtensionAppId().AppId().Clone()); } } // Hold on to any state that we need to finish the transaction. mTransaction = Some( Transaction(aTransactionId, rpIdHash, appIdHash, aInfo.ClientDataJSON())); mPendingSignInfo = Some(aInfo); // Show a prompt that lets the user cancel the ongoing transaction. NS_ConvertUTF16toUTF8 origin(aInfo.Origin()); SendPromptNotification(kPresencePromptNotification, mTransaction.ref().mTransactionId, origin.get(), aInfo.BrowsingContextId(), "false"); RefPtr args(new CtapSignArgs(aInfo)); mTransportImpl = GetTransportImpl(); if (!mTransportImpl) { AbortTransaction(mTransaction.ref().mTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR, true); return; } rv = mTransportImpl->GetAssertion(mTransaction.ref().mTransactionId, aInfo.BrowsingContextId(), args.get()); if (NS_FAILED(rv)) { AbortTransaction(mTransaction.ref().mTransactionId, rv, true); return; } } NS_IMETHODIMP WebAuthnController::FinishSign( uint64_t aTransactionId, const nsTArray>& aResult) { MOZ_ASSERT(XRE_IsParentProcess()); nsTArray> ownedResult = aResult.Clone(); nsCOMPtr r( NewRunnableMethod>>( "WebAuthnController::RunFinishSign", this, &WebAuthnController::RunFinishSign, aTransactionId, std::move(ownedResult))); if (!gWebAuthnBackgroundThread) { return NS_ERROR_FAILURE; } return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void WebAuthnController::RunFinishSign( uint64_t aTransactionId, const nsTArray>& aResult) { mozilla::ipc::AssertIsOnBackgroundThread(); if (mTransaction.isNothing() || aTransactionId != mTransaction.ref().mTransactionId) { return; } if (aResult.Length() == 0) { Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED, u"CTAPSignAbort"_ns, 1); AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR, true); return; } if (aResult.Length() == 1) { nsresult status; nsresult rv = aResult[0]->GetStatus(&status); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } if (NS_FAILED(status)) { bool shouldCancelActiveDialog = true; if (status == NS_ERROR_DOM_OPERATION_ERR) { // PIN-related errors, e.g. blocked token. Let the dialog show to inform // the user shouldCancelActiveDialog = false; } Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED, u"CTAPSignAbort"_ns, 1); AbortTransaction(aTransactionId, status, shouldCancelActiveDialog); return; } mPendingSignResults = aResult.Clone(); RunResumeWithSelectedSignResult(aTransactionId, 0); return; } // If we more than one assertion, all of them should have OK status. for (const auto& assertion : aResult) { nsresult status; nsresult rv = assertion->GetStatus(&status); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } if (NS_WARN_IF(NS_FAILED(status))) { Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED, u"CTAPSignAbort"_ns, 1); AbortTransaction(aTransactionId, status, true); return; } } nsCString usernames; StringJoinAppend( usernames, ","_ns, aResult, [](nsACString& dst, const RefPtr& assertion) { nsCString username; nsresult rv = assertion->GetUserName(username); if (NS_FAILED(rv)) { username.Assign(""); } nsCString escaped_username; NS_Escape(username, escaped_username, url_XAlphas); dst.Append("\""_ns + escaped_username + "\""_ns); }); mPendingSignResults = aResult.Clone(); NS_ConvertUTF16toUTF8 origin(mPendingSignInfo.ref().Origin()); SendPromptNotification(kSelectSignResultNotification, mTransaction.ref().mTransactionId, origin.get(), mPendingSignInfo.ref().BrowsingContextId(), usernames.get()); } NS_IMETHODIMP WebAuthnController::SignatureSelectionCallback(uint64_t aTransactionId, uint64_t idx) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr r(NewRunnableMethod( "WebAuthnController::RunResumeWithSelectedSignResult", this, &WebAuthnController::RunResumeWithSelectedSignResult, aTransactionId, idx)); if (!gWebAuthnBackgroundThread) { return NS_ERROR_FAILURE; } return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void WebAuthnController::RunResumeWithSelectedSignResult( uint64_t aTransactionId, uint64_t idx) { mozilla::ipc::AssertIsOnBackgroundThread(); if (mTransaction.isNothing() || mTransaction.ref().mTransactionId != aTransactionId) { return; } if (NS_WARN_IF(mPendingSignResults.Length() <= idx)) { return; } RefPtr& selected = mPendingSignResults[idx]; nsTArray credentialId; nsresult rv = selected->GetCredentialId(credentialId); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } nsTArray signature; rv = selected->GetSignature(signature); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } nsTArray authenticatorData; rv = selected->GetAuthenticatorData(authenticatorData); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } nsTArray rpIdHash; rv = selected->GetRpIdHash(rpIdHash); if (NS_WARN_IF(NS_FAILED(rv))) { AbortTransaction(aTransactionId, NS_ERROR_FAILURE, true); return; } nsTArray userHandle; Unused << selected->GetUserHandle(userHandle); // optional nsTArray extensions; if (mTransaction.ref().mAppIdHash.isSome()) { bool usedAppId = (rpIdHash == mTransaction.ref().mAppIdHash.ref()); extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId)); } WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON, credentialId, signature, authenticatorData, extensions, userHandle); Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED, u"CTAPSignFinish"_ns, 1); Unused << mTransactionParent->SendConfirmSign(aTransactionId, result); ClearTransaction(true); } NS_IMETHODIMP WebAuthnController::PinCallback(uint64_t aTransactionId, const nsACString& aPin) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr r(NewRunnableMethod( "WebAuthnController::RunPinCallback", this, &WebAuthnController::RunPinCallback, aTransactionId, aPin)); if (!gWebAuthnBackgroundThread) { return NS_ERROR_FAILURE; } return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void WebAuthnController::RunPinCallback(uint64_t aTransactionId, const nsCString& aPin) { mozilla::ipc::AssertIsOnBackgroundThread(); if (mTransportImpl) { mTransportImpl->PinCallback(aTransactionId, aPin); } } NS_IMETHODIMP WebAuthnController::Cancel(uint64_t aTransactionId) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr r(NewRunnableMethod( "WebAuthnController::RunCancel", this, &WebAuthnController::RunCancel, aTransactionId)); if (!gWebAuthnBackgroundThread) { return NS_ERROR_FAILURE; } return gWebAuthnBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void WebAuthnController::RunCancel(uint64_t aTransactionId) { mozilla::ipc::AssertIsOnBackgroundThread(); if (mTransaction.isNothing() || mTransaction.ref().mTransactionId != aTransactionId) { return; } // Cancel the request. if (mTransportImpl) { mTransportImpl->Cancel(); } // Reject the promise. AbortTransaction(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR, true); } } // namespace mozilla::dom