Bug 1383799 - Cancel WebAuthn operations on tab-switch r=ttaubert
authorJ.C. Jones <jjones@mozilla.com>
Fri, 04 Aug 2017 12:34:18 -0700
changeset 374022 f7a53ff2f8cb312eb6a65b127207e04d2bd1c79c
parent 374021 3a17b61bc3e83cfb7db6af894fb4e30bf3f11963
child 374023 64d02c02a345d6100ff304b27ab654fe1079e37d
push id32312
push userarchaeopteryx@coole-files.de
push dateFri, 11 Aug 2017 09:55:13 +0000
treeherdermozilla-central@f74094603063 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert
bugs1383799
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
dom/webauthn/WebAuthnManager.cpp
dom/webauthn/WebAuthnManager.h
--- 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();