Bug 1407829 - WebAuthn: Implement CredMan's Store method r?qdot r?ttaubert draft
authorJ.C. Jones <jjones@mozilla.com>
Thu, 12 Oct 2017 17:02:22 -0700
changeset 681064 24a7c867f08c52fd5b25b2c28382dc821273466c
parent 679509 98247fbf95c260786361e12ad924c4370885f686
child 736085 8d30eafee811cb52d6b0bd2d2c1111eb78c1eea3
push id84755
push userbmo:jjones@mozilla.com
push dateMon, 16 Oct 2017 23:20:40 +0000
reviewersqdot, ttaubert
bugs1407829
milestone58.0a1
Bug 1407829 - WebAuthn: Implement CredMan's Store method r?qdot r?ttaubert Credential Management defines a Store operation [1], which needs to be implemented for WebAuthn's spec compliance. It only returns a NotSupportedError for WebAuthn [2], so it's pretty simple. [1] https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-store [2] https://w3c.github.io/webauthn/#storeCredential MozReview-Commit-ID: KDEB8r5feQt
dom/credentialmanagement/CredentialsContainer.cpp
dom/credentialmanagement/CredentialsContainer.h
dom/webauthn/WebAuthnManager.cpp
dom/webauthn/WebAuthnManager.h
dom/webauthn/tests/mochitest.ini
dom/webauthn/tests/test_webauthn_store_credential.html
dom/webidl/CredentialManagement.webidl
--- a/dom/credentialmanagement/CredentialsContainer.cpp
+++ b/dom/credentialmanagement/CredentialsContainer.cpp
@@ -43,10 +43,17 @@ CredentialsContainer::Get(const Credenti
 
 already_AddRefed<Promise>
 CredentialsContainer::Create(const CredentialCreationOptions& aOptions)
 {
   RefPtr<WebAuthnManager> mgr = WebAuthnManager::GetOrCreate();
   return mgr->MakeCredential(mParent, aOptions.mPublicKey);
 }
 
+already_AddRefed<Promise>
+CredentialsContainer::Store(const Credential& aCredential)
+{
+  RefPtr<WebAuthnManager> mgr = WebAuthnManager::GetOrCreate();
+  return mgr->Store(mParent, aCredential);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/credentialmanagement/CredentialsContainer.h
+++ b/dom/credentialmanagement/CredentialsContainer.h
@@ -29,16 +29,18 @@ public:
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   already_AddRefed<Promise>
   Get(const CredentialRequestOptions& aOptions);
   already_AddRefed<Promise>
   Create(const CredentialCreationOptions& aOptions);
+  already_AddRefed<Promise>
+  Store(const Credential& aCredential);
 
 private:
   ~CredentialsContainer();
 
   nsCOMPtr<nsPIDOMWindowInner> mParent;
 };
 
 } // namespace dom
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -682,16 +682,37 @@ WebAuthnManager::GetAssertion(nsPIDOMWin
   mClientData = Some(clientDataJSON);
   mCurrentParent = aParent;
   mInfo = Some(info);
   ListenForVisibilityEvents(aParent, this);
 
   return promise.forget();
 }
 
+already_AddRefed<Promise>
+WebAuthnManager::Store(nsPIDOMWindowInner* aParent,
+                       const Credential& aCredential)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aParent);
+
+  MaybeClearTransaction();
+
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
+
+  ErrorResult rv;
+  RefPtr<Promise> promise = Promise::Create(global, rv);
+  if (rv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+  return promise.forget();
+}
+
 void
 WebAuthnManager::FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer)
 {
   MOZ_ASSERT(mTransactionPromise);
   MOZ_ASSERT(mInfo.isSome());
 
   CryptoBuffer regData;
   if (NS_WARN_IF(!regData.Assign(aRegBuffer.Elements(), aRegBuffer.Length()))) {
--- a/dom/webauthn/WebAuthnManager.h
+++ b/dom/webauthn/WebAuthnManager.h
@@ -84,16 +84,19 @@ public:
   already_AddRefed<Promise>
   MakeCredential(nsPIDOMWindowInner* aParent,
                  const MakePublicKeyCredentialOptions& aOptions);
 
   already_AddRefed<Promise>
   GetAssertion(nsPIDOMWindowInner* aParent,
                const PublicKeyCredentialRequestOptions& aOptions);
 
+  already_AddRefed<Promise>
+  Store(nsPIDOMWindowInner* aParent, const Credential& aCredential);
+
   void StartRegister();
   void StartSign();
   void StartCancel();
 
   // nsIIPCbackgroundChildCreateCallback methods
   void ActorCreated(PBackgroundChild* aActor) override;
   void ActorFailed() override;
   void ActorDestroyed();
--- a/dom/webauthn/tests/mochitest.ini
+++ b/dom/webauthn/tests/mochitest.ini
@@ -5,10 +5,11 @@ support-files =
   u2futil.js
 skip-if = !e10s
 scheme = https
 
 [test_webauthn_loopback.html]
 [test_webauthn_no_token.html]
 [test_webauthn_make_credential.html]
 [test_webauthn_get_assertion.html]
+[test_webauthn_store_credential.html]
 [test_webauthn_sameorigin.html]
 [test_webauthn_isplatformauthenticatoravailable.html]
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_store_credential.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>Tests for Store for W3C Web Authentication</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+  <h1>Tests for Store for W3C Web Authentication</h1>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+  <script class="testbody" type="text/javascript">
+    "use strict";
+
+    function arrivingHereIsBad(aResult) {
+      ok(false, "Bad result! Received a: " + aResult);
+      return Promise.resolve();
+    }
+
+    function expectNotSupportedError(aResult) {
+      ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError, received: " + aResult);
+      return Promise.resolve();
+    }
+
+    add_task(async function(){
+      await SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
+                                   ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]});
+
+      isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+      isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+      isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+      isnot(navigator.credentials.store, undefined, "CredentialManagement store API endpoint must exist");
+
+      let credentialChallenge = new Uint8Array(16);
+      window.crypto.getRandomValues(credentialChallenge);
+
+      let rp = {id: document.domain, name: "none", icon: "none"};
+      let user = {id: new Uint8Array(64), name: "none", icon: "none", displayName: "none"};
+      let params = [ {type: "public-key", alg: "es256"}, {type: "public-key", alg: -7} ]
+
+      let makeCredentialOptions = {
+        rp: rp, user: user, challenge: credentialChallenge, pubKeyCredParams: params
+      };
+
+      let credential = await navigator.credentials.create({publicKey: makeCredentialOptions})
+        .catch(arrivingHereIsBad);
+
+      await navigator.credentials.store(credential)
+        .then(arrivingHereIsBad)
+        .catch(expectNotSupportedError);
+    });
+  </script>
+
+</body>
+</html>
--- a/dom/webidl/CredentialManagement.webidl
+++ b/dom/webidl/CredentialManagement.webidl
@@ -12,16 +12,17 @@ interface Credential {
   readonly attribute USVString id;
   readonly attribute DOMString type;
 };
 
 [Exposed=Window, SecureContext, Pref="security.webauth.webauthn"]
 interface CredentialsContainer {
   Promise<Credential?> get(optional CredentialRequestOptions options);
   Promise<Credential?> create(optional CredentialCreationOptions options);
+  Promise<Credential> store(Credential credential);
 };
 
 dictionary CredentialRequestOptions {
   PublicKeyCredentialRequestOptions publicKey;
 };
 
 dictionary CredentialCreationOptions {
   MakePublicKeyCredentialOptions publicKey;