Bug 1383799 - Cancel WebAuthn operations on tab-switch r?ttaubert
WebAuthn operations that are in-flight with authenticators must be cancelled
when switching tabs.
There's an Issue [1] opened with the WebAuthn spec for this already, but the
language is _not_ in spec. Still, it's necessary for security, spec or not.
This also matches how Chromium handles U2F operations during a tab switch.
[1] https://github.com/w3c/webauthn/issues/316
MozReview-Commit-ID: 6Qh9oC4pqys
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -37,17 +37,20 @@ const uint8_t FLAG_AT = 0x40; // Authent
* Statics
**********************************************************************/
namespace {
StaticRefPtr<WebAuthnManager> gWebAuthnManager;
static mozilla::LazyLogModule gWebAuthnManagerLog("webauthnmanager");
}
-NS_IMPL_ISUPPORTS(WebAuthnManager, nsIIPCBackgroundChildCreateCallback);
+NS_NAMED_LITERAL_STRING(kVisibilityChange, "visibilitychange");
+
+NS_IMPL_ISUPPORTS(WebAuthnManager, nsIIPCBackgroundChildCreateCallback,
+ nsIDOMEventListener);
/***********************************************************************
* Utility Functions
**********************************************************************/
template<class OOS>
static nsresult
GetAlgorithmName(const OOS& aAlgorithm,
@@ -126,16 +129,17 @@ AssembleClientData(const nsAString& aOri
aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
return NS_OK;
}
nsresult
GetOrigin(nsPIDOMWindowInner* aParent,
/*out*/ nsAString& aOrigin, /*out*/ nsACString& aHost)
{
+ MOZ_ASSERT(aParent);
nsCOMPtr<nsIDocument> doc = aParent->GetDoc();
MOZ_ASSERT(doc);
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
nsresult rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
if (NS_WARN_IF(NS_FAILED(rv)) ||
NS_WARN_IF(aOrigin.IsEmpty())) {
return NS_ERROR_FAILURE;
@@ -163,16 +167,17 @@ GetOrigin(nsPIDOMWindowInner* aParent,
nsresult
RelaxSameOrigin(nsPIDOMWindowInner* aParent,
const nsAString& aInputRpId,
/* out */ nsACString& aRelaxedRpId)
{
MOZ_ASSERT(aParent);
nsCOMPtr<nsIDocument> doc = aParent->GetDoc();
MOZ_ASSERT(doc);
+
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
nsCOMPtr<nsIURI> uri;
if (NS_FAILED(principal->GetURI(getter_AddRefs(uri)))) {
return NS_ERROR_FAILURE;
}
nsAutoCString originHost;
if (NS_FAILED(uri->GetAsciiHost(originHost))) {
return NS_ERROR_FAILURE;
@@ -207,31 +212,71 @@ RelaxSameOrigin(nsPIDOMWindowInner* aPar
if (!html->IsRegistrableDomainSuffixOfOrEqualTo(inputRpId, originHost)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
aRelaxedRpId.Assign(NS_ConvertUTF16toUTF8(aInputRpId));
return NS_OK;
}
+static void
+ListenForVisibilityEvents(nsPIDOMWindowInner* aParent,
+ WebAuthnManager* aListener)
+{
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(aListener);
+
+ nsCOMPtr<nsIDocument> doc = aParent->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return;
+ }
+
+ nsresult rv = doc->AddSystemEventListener(kVisibilityChange, aListener,
+ /* use capture */ true,
+ /* wants untrusted */ false);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+static void
+StopListeningForVisibilityEvents(nsPIDOMWindowInner* aParent,
+ WebAuthnManager* aListener)
+{
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(aListener);
+
+ nsCOMPtr<nsIDocument> doc = aParent->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return;
+ }
+
+ nsresult rv = doc->RemoveSystemEventListener(kVisibilityChange, aListener,
+ /* use capture */ true);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
/***********************************************************************
* WebAuthnManager Implementation
**********************************************************************/
WebAuthnManager::WebAuthnManager()
{
MOZ_ASSERT(NS_IsMainThread());
}
void
WebAuthnManager::MaybeClearTransaction()
{
mClientData.reset();
mInfo.reset();
mTransactionPromise = nullptr;
+ if (mCurrentParent) {
+ StopListeningForVisibilityEvents(mCurrentParent, this);
+ mCurrentParent = nullptr;
+ }
+
if (mChild) {
RefPtr<WebAuthnTransactionChild> c;
mChild.swap(c);
c->Send__delete__(c);
}
}
WebAuthnManager::~WebAuthnManager()
@@ -498,16 +543,18 @@ WebAuthnManager::MakeCredential(nsPIDOMW
[]() {
// This case can't actually happen, we'll have crashed if the child
// failed to create.
});
mTransactionPromise = promise;
mClientData = Some(clientDataJSON);
mCurrentParent = aParent;
mInfo = Some(info);
+ ListenForVisibilityEvents(aParent, this);
+
return promise.forget();
}
void
WebAuthnManager::StartRegister() {
if (mChild) {
mChild->SendRequestRegister(mInfo.ref());
}
@@ -515,16 +562,23 @@ WebAuthnManager::StartRegister() {
void
WebAuthnManager::StartSign() {
if (mChild) {
mChild->SendRequestSign(mInfo.ref());
}
}
+void
+WebAuthnManager::StartCancel() {
+ if (mChild) {
+ mChild->SendRequestCancel();
+ }
+}
+
already_AddRefed<Promise>
WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
const PublicKeyCredentialRequestOptions& aOptions)
{
MOZ_ASSERT(aParent);
MaybeClearTransaction();
@@ -664,16 +718,18 @@ WebAuthnManager::GetAssertion(nsPIDOMWin
// failed to create.
});
// Only store off the promise if we've succeeded in sending the IPC event.
mTransactionPromise = promise;
mClientData = Some(clientDataJSON);
mCurrentParent = aParent;
mInfo = Some(info);
+ ListenForVisibilityEvents(aParent, this);
+
return promise.forget();
}
void
WebAuthnManager::FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer)
{
MOZ_ASSERT(mTransactionPromise);
MOZ_ASSERT(mInfo.isSome());
@@ -872,22 +928,51 @@ WebAuthnManager::FinishGetAssertion(nsTA
mTransactionPromise->MaybeResolve(credential);
MaybeClearTransaction();
}
void
WebAuthnManager::Cancel(const nsresult& aError)
{
+ MOZ_ASSERT(NS_IsMainThread());
+
if (mTransactionPromise) {
mTransactionPromise->MaybeReject(aError);
}
+
MaybeClearTransaction();
}
+NS_IMETHODIMP
+WebAuthnManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+ MOZ_ASSERT(aEvent);
+
+ nsAutoString type;
+ aEvent->GetType(type);
+ if (!type.Equals(kVisibilityChange)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc =
+ do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
+ MOZ_ASSERT(doc);
+
+ if (doc && doc->Hidden()) {
+ MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug,
+ ("Visibility change: WebAuthn window is hidden, cancelling job."));
+
+ StartCancel();
+ Cancel(NS_ERROR_ABORT);
+ }
+
+ return NS_OK;
+}
+
void
WebAuthnManager::ActorCreated(PBackgroundChild* aActor)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
if (mChild) {
return;
--- a/dom/webauthn/WebAuthnManager.h
+++ b/dom/webauthn/WebAuthnManager.h
@@ -3,17 +3,19 @@
/* 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/. */
#ifndef mozilla_dom_WebAuthnManager_h
#define mozilla_dom_WebAuthnManager_h
#include "mozilla/MozPromise.h"
+#include "mozilla/dom/Event.h"
#include "mozilla/dom/PWebAuthnTransaction.h"
+#include "nsIDOMEventListener.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
/*
* Content process manager for the WebAuthn protocol. Created on calls to the
* WebAuthentication DOM object, this manager handles establishing IPC channels
* for WebAuthn transactions, as well as keeping track of JS Promise objects
* representing transactions in flight.
*
@@ -56,20 +58,22 @@ class ArrayBufferViewOrArrayBuffer;
struct AssertionOptions;
class OwningArrayBufferViewOrArrayBuffer;
struct ScopedCredentialOptions;
struct ScopedCredentialParameters;
class Promise;
class WebAuthnTransactionChild;
class WebAuthnTransactionInfo;
-class WebAuthnManager final : public nsIIPCBackgroundChildCreateCallback
+class WebAuthnManager final : public nsIIPCBackgroundChildCreateCallback,
+ public nsIDOMEventListener
{
public:
NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
static WebAuthnManager* GetOrCreate();
static WebAuthnManager* Get();
void
FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer);
void
FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
@@ -83,16 +87,17 @@ public:
const MakeCredentialOptions& aOptions);
already_AddRefed<Promise>
GetAssertion(nsPIDOMWindowInner* aParent,
const PublicKeyCredentialRequestOptions& aOptions);
void StartRegister();
void StartSign();
+ void StartCancel();
// nsIIPCbackgroundChildCreateCallback methods
void ActorCreated(PBackgroundChild* aActor) override;
void ActorFailed() override;
void ActorDestroyed();
private:
WebAuthnManager();
virtual ~WebAuthnManager();