Bug 1406456 - WebAuthn WebIDL Updates for WD-07 (part 1) r=keeler,qdot
authorJ.C. Jones <jjones@mozilla.com>
Fri, 06 Oct 2017 16:10:57 -0700
changeset 385709 ca60b3cab4ed50a6f4c06ac0bb9498aaedd9551b
parent 385708 462c85ecfe5b684a34b1b133995dc5d2e97ae10e
child 385710 dd5ff0119c3f20f9b887c23774890e64d15a7f28
push id32664
push userarchaeopteryx@coole-files.de
push dateThu, 12 Oct 2017 09:34:55 +0000
treeherdermozilla-central@a32c32d9631c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, qdot
bugs1406456, 1406468
milestone58.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 1406456 - WebAuthn WebIDL Updates for WD-07 (part 1) r=keeler,qdot This covers these renames: * In PublicKeyCredentialParameters, algorithm => alg * MakeCredentialOptions => MakePublicKeyCredentialOptions * PublicKeyCredentialEntity => PublicKeyCredentialRpEntity * Attachment => AuthenticatorAttachment It sets a default excludeList and allowList for the make / get options. It adds the method isPlatformAuthenticatorAvailable which is incomplete and not callable, to be completed in Bug 1406468. Adds type PublicKeyCredentialRpEntity. Adds "userId" to AuthenticatorAssertionResponse. Adds "id" as a buffer source to PublicKeyCredentialUserEntity and as a DOMString to PublicKeyCredentialRpEntity, refactoring out the "id" field from the parent PublicKeyCredentialEntity. It also adds a simple enforcement per spec 4.4.3 "User Account Parameters for Credential Generation" that the new user ID buffer, if set, be no more than 64 bytes long. I mostly added it here so I could adjust the tests all at once in this commit. MozReview-Commit-ID: IHUdGVoWocq
dom/webauthn/AuthenticatorAssertionResponse.cpp
dom/webauthn/AuthenticatorAssertionResponse.h
dom/webauthn/PublicKeyCredential.cpp
dom/webauthn/PublicKeyCredential.h
dom/webauthn/WebAuthnManager.cpp
dom/webauthn/WebAuthnManager.h
dom/webauthn/tests/browser/tab_webauthn_success.html
dom/webauthn/tests/mochitest.ini
dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html
dom/webauthn/tests/test_webauthn_loopback.html
dom/webauthn/tests/test_webauthn_make_credential.html
dom/webauthn/tests/test_webauthn_no_token.html
dom/webauthn/tests/test_webauthn_sameorigin.html
dom/webidl/CredentialManagement.webidl
dom/webidl/WebAuthentication.webidl
--- a/dom/webauthn/AuthenticatorAssertionResponse.cpp
+++ b/dom/webauthn/AuthenticatorAssertionResponse.cpp
@@ -87,10 +87,25 @@ nsresult
 AuthenticatorAssertionResponse::SetSignature(CryptoBuffer& aBuffer)
 {
   if (NS_WARN_IF(!mSignature.Assign(aBuffer))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
 
+void
+AuthenticatorAssertionResponse::GetUserId(DOMString& aRetVal)
+{
+  // This requires mUserId to not be re-set for the life of the caller's in-var.
+  aRetVal.SetOwnedString(mUserId);
+}
+
+nsresult
+AuthenticatorAssertionResponse::SetUserId(const nsAString& aUserId)
+{
+  MOZ_ASSERT(mUserId.IsEmpty(), "We already have a UserID?");
+  mUserId.Assign(aUserId);
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/webauthn/AuthenticatorAssertionResponse.h
+++ b/dom/webauthn/AuthenticatorAssertionResponse.h
@@ -42,19 +42,26 @@ public:
   SetAuthenticatorData(CryptoBuffer& aBuffer);
 
   void
   GetSignature(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal);
 
   nsresult
   SetSignature(CryptoBuffer& aBuffer);
 
+  void
+  GetUserId(DOMString& aRetVal);
+
+  nsresult
+  SetUserId(const nsAString& aUserId);
+
 private:
   CryptoBuffer mAuthenticatorData;
   JS::Heap<JSObject*> mAuthenticatorDataCachedObj;
   CryptoBuffer mSignature;
   JS::Heap<JSObject*> mSignatureCachedObj;
+  nsString mUserId;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_AuthenticatorAssertionResponse_h
--- a/dom/webauthn/PublicKeyCredential.cpp
+++ b/dom/webauthn/PublicKeyCredential.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/Promise.h"
 #include "mozilla/dom/PublicKeyCredential.h"
 #include "mozilla/dom/WebAuthenticationBinding.h"
 #include "nsCycleCollectionParticipant.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(PublicKeyCredential)
@@ -77,10 +78,34 @@ PublicKeyCredential::SetRawId(CryptoBuff
 }
 
 void
 PublicKeyCredential::SetResponse(RefPtr<AuthenticatorResponse> aResponse)
 {
   mResponse = aResponse;
 }
 
+/* static */ already_AddRefed<Promise>
+PublicKeyCredential::IsPlatformAuthenticatorAvailable(GlobalObject& aGlobal)
+{
+  nsIGlobalObject* globalObject =
+    xpc::NativeGlobal(JS::CurrentGlobalOrNull(aGlobal.Context()));
+  if (NS_WARN_IF(!globalObject)) {
+    return nullptr;
+  }
+
+  ErrorResult rv;
+  RefPtr<Promise> promise = Promise::Create(globalObject, rv);
+  if(rv.Failed()) {
+    return nullptr;
+  }
+
+  // Complete in Bug 1406468. This shouldn't just always return true, it should
+  // follow the guidelines in
+  // https://w3c.github.io/webauthn/#isPlatformAuthenticatorAvailable
+  // such as ensuring that U2FTokenManager isn't in some way disabled.
+  promise->MaybeResolve(true);
+  return promise.forget();
+}
+
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/webauthn/PublicKeyCredential.h
+++ b/dom/webauthn/PublicKeyCredential.h
@@ -41,16 +41,19 @@ public:
   Response() const;
 
   nsresult
   SetRawId(CryptoBuffer& aBuffer);
 
   void
   SetResponse(RefPtr<AuthenticatorResponse>);
 
+  static already_AddRefed<Promise>
+  IsPlatformAuthenticatorAvailable(GlobalObject& aGlobal);
+
 private:
   CryptoBuffer mRawId;
   JS::Heap<JSObject*> mRawIdCachedObj;
   RefPtr<AuthenticatorResponse> mResponse;
   // Extensions are not supported yet.
   // <some type> mClientExtensionResults;
 };
 
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -277,17 +277,17 @@ WebAuthnManager*
 WebAuthnManager::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return gWebAuthnManager;
 }
 
 already_AddRefed<Promise>
 WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
-                                const MakeCredentialOptions& aOptions)
+                                const MakePublicKeyCredentialOptions& aOptions)
 {
   MOZ_ASSERT(aParent);
 
   MaybeClearTransaction();
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
 
   ErrorResult rv;
@@ -299,16 +299,28 @@ WebAuthnManager::MakeCredential(nsPIDOMW
   nsString origin;
   nsCString rpId;
   rv = GetOrigin(aParent, origin, rpId);
   if (NS_WARN_IF(rv.Failed())) {
     promise->MaybeReject(rv);
     return promise.forget();
   }
 
+  // Enforce 4.4.3 User Account Parameters for Credential Generation
+  if (aOptions.mUser.mId.WasPassed()) {
+    // When we add UX, we'll want to do more with this value, but for now
+    // we just have to verify its correctness.
+    CryptoBuffer userId;
+    userId.Assign(aOptions.mUser.mId.Value());
+    if (userId.Length() > 64) {
+      promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
+      return promise.forget();
+    }
+  }
+
   // If timeoutSeconds was specified, check if its value lies within a
   // reasonable range as defined by the platform and if not, correct it to the
   // closest value lying within that range.
 
   uint32_t adjustedTimeout = 30000;
   if (aOptions.mTimeout.WasPassed()) {
     adjustedTimeout = aOptions.mTimeout.Value();
     adjustedTimeout = std::max(15000u, adjustedTimeout);
@@ -366,27 +378,27 @@ WebAuthnManager::MakeCredential(nsPIDOMW
 
     // Let normalizedAlgorithm be the result of normalizing an algorithm using
     // the procedure defined in [WebCryptoAPI], with alg set to
     // current.algorithm and op set to 'generateKey'. If an error occurs during
     // this procedure, then stop processing current and move on to the next
     // element in cryptoParameters.
 
     nsString algName;
-    if (NS_FAILED(GetAlgorithmName(aOptions.mParameters[a].mAlgorithm,
+    if (NS_FAILED(GetAlgorithmName(aOptions.mParameters[a].mAlg,
                                    algName))) {
       continue;
     }
 
     // Add a new object of type PublicKeyCredentialParameters to
     // normalizedParameters, with type set to current.type and algorithm set to
     // normalizedAlgorithm.
     PublicKeyCredentialParameters normalizedObj;
     normalizedObj.mType = aOptions.mParameters[a].mType;
-    normalizedObj.mAlgorithm.SetAsString().Assign(algName);
+    normalizedObj.mAlg.SetAsString().Assign(algName);
 
     if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){
       promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
       return promise.forget();
     }
   }
 
   // If normalizedAlgorithm is empty and cryptoParameters was not empty, cancel
@@ -406,18 +418,18 @@ WebAuthnManager::MakeCredential(nsPIDOMW
   // PublicKeyCredentialParameters and cryptographic parameters is supported. If
   // not, return an error code equivalent to NotSupportedError and terminate the
   // operation.
 
   bool isValidCombination = false;
 
   for (size_t a = 0; a < normalizedParams.Length(); ++a) {
     if (normalizedParams[a].mType == PublicKeyCredentialType::Public_key &&
-        normalizedParams[a].mAlgorithm.IsString() &&
-        normalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
+        normalizedParams[a].mAlg.IsString() &&
+        normalizedParams[a].mAlg.GetAsString().EqualsLiteral(
           JWK_ALG_ECDSA_P_256)) {
       isValidCombination = true;
       break;
     }
   }
   if (!isValidCombination) {
     promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return promise.forget();
@@ -459,24 +471,22 @@ WebAuthnManager::MakeCredential(nsPIDOMW
 
   srv = HashCString(hashService, clientDataJSON, clientDataHash);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
-  if (aOptions.mExcludeList.WasPassed()) {
-    for (const auto& s: aOptions.mExcludeList.Value()) {
-      WebAuthnScopedCredentialDescriptor c;
-      CryptoBuffer cb;
-      cb.Assign(s.mId);
-      c.id() = cb;
-      excludeList.AppendElement(c);
-    }
+  for (const auto& s: aOptions.mExcludeList) {
+    WebAuthnScopedCredentialDescriptor c;
+    CryptoBuffer cb;
+    cb.Assign(s.mId);
+    c.id() = cb;
+    excludeList.AppendElement(c);
   }
 
   // TODO: Add extension list building
   nsTArray<WebAuthnExtension> extensions;
 
   WebAuthnTransactionInfo info(rpIdHash,
                                clientDataHash,
                                adjustedTimeout,
--- a/dom/webauthn/WebAuthnManager.h
+++ b/dom/webauthn/WebAuthnManager.h
@@ -52,18 +52,17 @@ public:
 
 namespace mozilla {
 namespace dom {
 
 struct Account;
 class ArrayBufferViewOrArrayBuffer;
 struct AssertionOptions;
 class OwningArrayBufferViewOrArrayBuffer;
-struct ScopedCredentialOptions;
-struct ScopedCredentialParameters;
+struct MakePublicKeyCredentialOptions;
 class Promise;
 class WebAuthnTransactionChild;
 class WebAuthnTransactionInfo;
 
 class WebAuthnManager final : public nsIIPCBackgroundChildCreateCallback,
                               public nsIDOMEventListener
 {
 public:
@@ -79,17 +78,17 @@ public:
   FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
                      nsTArray<uint8_t>& aSigBuffer);
 
   void
   Cancel(const nsresult& aError);
 
   already_AddRefed<Promise>
   MakeCredential(nsPIDOMWindowInner* aParent,
-                 const MakeCredentialOptions& aOptions);
+                 const MakePublicKeyCredentialOptions& aOptions);
 
   already_AddRefed<Promise>
   GetAssertion(nsPIDOMWindowInner* aParent,
                const PublicKeyCredentialRequestOptions& aOptions);
 
   void StartRegister();
   void StartSign();
   void StartCancel();
--- a/dom/webauthn/tests/browser/tab_webauthn_success.html
+++ b/dom/webauthn/tests/browser/tab_webauthn_success.html
@@ -30,84 +30,81 @@ function signalCompletion(aText) {
   result.id = "result";
   result.textContent = aText;
   document.body.append(result);
 }
 
 let gState = {};
 let makeCredentialOptions = {
   rp: {id: document.domain, name: "none", icon: "none"},
-  user: {id: "none", name: "none", icon: "none", displayName: "none"},
+  user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
   challenge: gCredentialChallenge,
   timeout: 5000, // the minimum timeout is actually 15 seconds
-  parameters: [{type: "public-key", algorithm: "ES256"}],
+  parameters: [{type: "public-key", alg: "ES256"}],
 };
 
 navigator.credentials.create({publicKey: makeCredentialOptions})
 .then(function (aNewCredentialInfo) {
   gState.credential = aNewCredentialInfo;
 
   return webAuthnDecodeCBORAttestation(aNewCredentialInfo.response.attestationObject);
 })
 .then(function testAssertion(aCredInfo) {
+  gState.authDataObj = aCredInfo.authDataObj;
   gState.publicKeyHandle = aCredInfo.authDataObj.publicKeyHandle;
 
   let newCredential = {
     type: "public-key",
     id: new Uint8Array(gState.credential.rawId),
     transports: ["usb"],
   }
 
   let publicKeyCredentialRequestOptions = {
     challenge: gAssertionChallenge,
     timeout: 5000, // the minimum timeout is actually 15 seconds
     rpId: document.domain,
     allowList: [newCredential]
   };
 
-  return navigator.credentials.get({publicKey: publicKeyCredentialRequestOptions});
+  // Make sure the RP ID hash matches what we calculate.
+  return crypto.subtle.digest("SHA-256", string2buffer(document.domain))
+  .then(function(calculatedRpIdHash) {
+    let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
+    let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(gState.authDataObj.rpIdHash));
+
+    if (calcHashStr != providedHashStr) {
+      return Promise.reject("Calculated RP ID hash must match what the browser derived.");
+    }
+
+    return navigator.credentials.get({publicKey: publicKeyCredentialRequestOptions});
+  });
 })
 .then(function(aAssertion) {
   let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
 
   gState.assertion = aAssertion;
 
   return webAuthnDecodeAuthDataArray(new Uint8Array(aAssertion.response.authenticatorData));
 })
-.then(function(aAttestationObj) {
-  gState.attestation = aAttestationObj;
-
-  // Make sure the RP ID hash matches what we calculate.
-  return crypto.subtle.digest("SHA-256", string2buffer(document.domain))
-  .then(function(calculatedRpIdHash) {
-    let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
-    let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(aAttestationObj.authDataObj.rpIdHash));
-
-    if (calcHashStr != providedHashStr) {
-      return Promise.reject("Calculated RP ID hash must match what the browser derived.");
-    }
-    return Promise.resolve(aAttestationObj);
-  });
-})
 .then(function(aAttestation) {
   if (new Uint8Array(aAttestation.flags) != flag_TUP) {
     return Promise.reject("Assertion's user presence byte not set correctly.");
   }
 
   let clientDataJSON = gState.assertion.response.clientDataJSON;
   return deriveAppAndChallengeParam(document.domain, clientDataJSON, aAttestation);
 })
 .then(function(aParams) {
   return assembleSignedData(aParams.appParam, aParams.attestation.flags,
                             aParams.attestation.counter, aParams.challengeParam);
 })
 .then(function(aSignedData) {
   let signature = gState.assertion.response.signature;
   console.log(gState.publicKeyHandle, aSignedData, signature);
-  return verifySignature(gState.publicKeyHandle, aSignedData, new Uint8Array(signature));
+  return verifySignature(gState.publicKeyHandle, aSignedData, signature);
 })
 .then(function(aSigVerifyResult) {
   signalCompletion("Signing signature verified: " + aSigVerifyResult);
   gState = {};
 })
 .catch(function(aReason) {
   signalCompletion("Failure: " + aReason);
   gState = {};
--- a/dom/webauthn/tests/mochitest.ini
+++ b/dom/webauthn/tests/mochitest.ini
@@ -1,21 +1,14 @@
 [DEFAULT]
 support-files =
   cbor/*
   pkijs/*
   u2futil.js
-
-[test_webauthn_loopback.html]
-skip-if = !e10s
-scheme = https
-[test_webauthn_no_token.html]
 skip-if = !e10s
 scheme = https
+
+[test_webauthn_loopback.html]
+[test_webauthn_no_token.html]
 [test_webauthn_make_credential.html]
-skip-if = !e10s
-scheme = https
 [test_webauthn_get_assertion.html]
-skip-if = !e10s
-scheme = https
 [test_webauthn_sameorigin.html]
-skip-if = !e10s
-scheme = https
+[test_webauthn_isplatformauthenticatoravailable.html]
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>Test for W3C Web Authentication isPlatformAuthenticatorAvailable</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <script type="text/javascript" src="pkijs/common.js"></script>
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
+  <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+  <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<h1>Test for W3C Web Authentication isPlatformAuthenticatorAvailable</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";
+
+// Execute the full-scope test
+SimpleTest.waitForExplicitFinish();
+
+// Turn off all tokens. This should result in "not allowed" failures
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
+                                   ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+function() {
+  PublicKeyCredential.isPlatformAuthenticatorAvailable()
+  .then(function(aResult) {
+    // The specification requires this method, if will return false, to wait 10
+    // minutes for anti-fingerprinting reasons. So we really can't test that
+    // in an automated way.
+    ok(aResult, "Should be available!");
+  })
+  .catch(function(aProblem) {
+    is(false, "Problem encountered: " + aProblem);
+  })
+  .then(function() {
+    SimpleTest.finish();
+  })
+});
+
+</script>
+
+</body>
+</html>
--- a/dom/webauthn/tests/test_webauthn_loopback.html
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -103,16 +103,17 @@ function() {
 
     is(aAssertion.type, "public-key", "Credential type must be public-key")
 
     ok(aAssertion.rawId.byteLength > 0, "Key ID exists");
     is(aAssertion.id, bytesToBase64UrlSafe(new Uint8Array(aAssertion.rawId)), "Encoded Key ID and Raw Key ID match");
 
     ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject");
     ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject");
+    isnot(aAssertion.response.userId, undefined, "AuthenticatorAssertionResponse.UserId is defined")
 
     ok(aAssertion.response.authenticatorData.byteLength > 0, "Authenticator data exists");
     let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
     is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
     is(clientData.origin, window.location.origin, "Origin is correct");
     is(clientData.hashAlg, "SHA-256", "Hash algorithm is correct");
 
     return webAuthnDecodeAuthDataArray(aAssertion.response.authenticatorData)
@@ -131,18 +132,18 @@ function() {
     .then(function(aSignedData) {
       console.log(aPublicKey, aSignedData, aAssertion.response.signature);
       return verifySignature(aPublicKey, aSignedData, aAssertion.response.signature);
     })
   }
 
   function testMakeCredential() {
     let rp = {id: document.domain, name: "none", icon: "none"};
-    let user = {id: "none", name: "none", icon: "none", displayName: "none"};
-    let param = {type: "public-key", algorithm: "ES256"};
+    let user = {name: "none", icon: "none", displayName: "none"};
+    let param = {type: "public-key", alg: "ES256"};
     let makeCredentialOptions = {
       rp: rp,
       user: user,
       challenge: gCredentialChallenge,
       parameters: [param]
     };
     credm.create({publicKey: makeCredentialOptions})
     .then(decodeCreatedCredential)
@@ -150,18 +151,18 @@ function() {
     .catch(function(aReason) {
       ok(false, aReason);
       SimpleTest.finish();
     });
   }
 
   function testMakeDuplicate(aCredInfo) {
     let rp = {id: document.domain, name: "none", icon: "none"};
-    let user = {id: "none", name: "none", icon: "none", displayName: "none"};
-    let param = {type: "public-key", algorithm: "ES256"};
+    let user = {name: "none", icon: "none", displayName: "none"};
+    let param = {type: "public-key", alg: "ES256"};
     let makeCredentialOptions = {
       rp: rp,
       user: user,
       challenge: gCredentialChallenge,
       parameters: [param],
       excludeList: [{type: "public-key", id: new Uint8Array(aCredInfo.rawId),
                      transports: ["usb"]}]
     };
--- a/dom/webauthn/tests/test_webauthn_make_credential.html
+++ b/dom/webauthn/tests/test_webauthn_make_credential.html
@@ -56,20 +56,20 @@
       isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
       let credm = navigator.credentials;
 
       let gCredentialChallenge = new Uint8Array(16);
       window.crypto.getRandomValues(gCredentialChallenge);
 
       let rp = {id: document.domain, name: "none", icon: "none"};
-      let user = {id: "none", name: "none", icon: "none", displayName: "none"};
-      let param = {type: "public-key", algorithm: "es256"};
-      let unsupportedParam = {type: "public-key", algorithm: "3DES"};
-      let badParam = {type: "SimplePassword", algorithm: "MaxLength=2"};
+      let user = {id: new Uint8Array(64), name: "none", icon: "none", displayName: "none"};
+      let param = {type: "public-key", alg: "es256"};
+      let unsupportedParam = {type: "public-key", alg: "3DES"};
+      let badParam = {type: "SimplePassword", alg: "MaxLength=2"};
 
       var testFuncs = [
         // Test basic good call
         function() {
           let makeCredentialOptions = {
             rp: rp, user: user, challenge: gCredentialChallenge, parameters: [param]
           };
           return credm.create({publicKey: makeCredentialOptions})
@@ -198,41 +198,68 @@
           let makeCredentialOptions = {
             user: user, challenge: gCredentialChallenge, parameters: [param]
           };
           return credm.create({publicKey: makeCredentialOptions})
                       .then(arrivingHereIsBad)
                       .catch(expectTypeError);
         },
 
+        // Test with incorrect user ID type
+        function() {
+          let invalidType = user;
+          invalidType.id = "a string, which is not a buffer";
+          let makeCredentialOptions = {
+            user: invalidType, challenge: gCredentialChallenge, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
+        },
+
         // Test with missing user
         function() {
           let makeCredentialOptions = {
             rp: rp, challenge: gCredentialChallenge, parameters: [param]
           };
           return credm.create({publicKey: makeCredentialOptions})
                       .then(arrivingHereIsBad)
                       .catch(expectTypeError);
         },
 
         // Test a complete account
         function() {
           let completeRP = {id: document.domain, name: "Foxxy Name",
                             icon: "https://example.com/fox.svg"};
-          let completeUser = {id: "foxes_are_the_best@example.com",
+          let completeUser = {id: string2buffer("foxes_are_the_best@example.com"),
                               name: "Fox F. Foxington",
                               icon: "https://example.com/fox.svg",
                               displayName: "Foxxy V"};
           let makeCredentialOptions = {
             rp: completeRP, user: completeUser, challenge: gCredentialChallenge,
             parameters: [param]
           };
           return credm.create({publicKey: makeCredentialOptions})
                       .then(arrivingHereIsGood)
                       .catch(arrivingHereIsBad);
+        },
+
+        // Test with too-large user ID buffer
+        function() {
+          let hugeUser = {id: new Uint8Array(65),
+                              name: "Fox F. Foxington",
+                              icon: "https://example.com/fox.svg",
+                              displayName: "Foxxy V"};
+          let makeCredentialOptions = {
+            rp: rp, user: hugeUser, challenge: gCredentialChallenge,
+            parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
         }];
 
       var i = 0;
       var runNextTest = () => {
         if (i == testFuncs.length) {
           SimpleTest.finish();
           return;
         }
--- a/dom/webauthn/tests/test_webauthn_no_token.html
+++ b/dom/webauthn/tests/test_webauthn_no_token.html
@@ -39,18 +39,18 @@ function() {
   window.crypto.getRandomValues(assertionChallenge);
   let credentialId = new Uint8Array(128);
   window.crypto.getRandomValues(credentialId);
 
   testMakeCredential();
 
   function testMakeCredential() {
     let rp = {id: document.domain, name: "none", icon: "none"};
-    let user = {id: "none", name: "none", icon: "none", displayName: "none"};
-    let param = {type: "public-key", algorithm: "es256"};
+    let user = {name: "none", icon: "none", displayName: "none"};
+    let param = {type: "public-key", alg: "es256"};
     let makeCredentialOptions = {
       rp: rp, user: user, challenge: credentialChallenge, parameters: [param]
     };
     credm.create({publicKey: makeCredentialOptions})
     .then(function(aResult) {
       ok(false, "Should have failed.");
       testAssertion();
     })
--- a/dom/webauthn/tests/test_webauthn_sameorigin.html
+++ b/dom/webauthn/tests/test_webauthn_sameorigin.html
@@ -55,18 +55,18 @@
       isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
       isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
       let credm = navigator.credentials;
 
       let chall = new Uint8Array(16);
       window.crypto.getRandomValues(chall);
 
-      let user = {id: "none", name: "none", icon: "none", displayName: "none"};
-      let param = {type: "public-key", algorithm: "Es256"};
+      let user = {id: new Uint8Array(16), name: "none", icon: "none", displayName: "none"};
+      let param = {type: "public-key", alg: "Es256"};
 
       var testFuncs = [
         function() {
           // Test basic good call
           let rp = {id: document.domain};
           let makeCredentialOptions = {
             rp: rp, user: user, challenge: chall, parameters: [param]
           };
--- a/dom/webidl/CredentialManagement.webidl
+++ b/dom/webidl/CredentialManagement.webidl
@@ -19,10 +19,10 @@ interface CredentialsContainer {
   Promise<Credential?> create(optional CredentialCreationOptions options);
 };
 
 dictionary CredentialRequestOptions {
   PublicKeyCredentialRequestOptions publicKey;
 };
 
 dictionary CredentialCreationOptions {
-  MakeCredentialOptions publicKey;
+  MakePublicKeyCredentialOptions publicKey;
 };
--- a/dom/webidl/WebAuthentication.webidl
+++ b/dom/webidl/WebAuthentication.webidl
@@ -6,106 +6,125 @@
  * The origin of this IDL file is
  * https://www.w3.org/TR/webauthn/
  */
 
 /***** Interfaces to Data *****/
 
 [SecureContext, Pref="security.webauth.webauthn"]
 interface PublicKeyCredential : Credential {
-    [SameObject] readonly attribute ArrayBuffer           rawId;
-    [SameObject] readonly attribute AuthenticatorResponse response;
+    [SameObject] readonly attribute ArrayBuffer              rawId;
+    [SameObject] readonly attribute AuthenticatorResponse    response;
     // Extensions are not supported yet.
-    // [SameObject] readonly attribute AuthenticationExtensions clientExtensionResults;
+    // [SameObject] readonly attribute AuthenticationExtensions clientExtensionResults; // Add in Bug 1406458
+};
+
+[SecureContext]
+partial interface PublicKeyCredential {
+    static Promise<boolean> isPlatformAuthenticatorAvailable();
 };
 
 [SecureContext, Pref="security.webauth.webauthn"]
 interface AuthenticatorResponse {
     [SameObject] readonly attribute ArrayBuffer clientDataJSON;
 };
 
 [SecureContext, Pref="security.webauth.webauthn"]
 interface AuthenticatorAttestationResponse : AuthenticatorResponse {
     [SameObject] readonly attribute ArrayBuffer attestationObject;
 };
 
 dictionary PublicKeyCredentialParameters {
     required PublicKeyCredentialType  type;
-    required WebAuthnAlgorithmID algorithm; // NOTE: changed from AllgorithmIdentifier because typedef (object or DOMString) not serializable
+    required WebAuthnAlgorithmID      alg; // Switch to COSE in Bug 1381190
 };
 
 dictionary PublicKeyCredentialUserEntity : PublicKeyCredentialEntity {
-    DOMString displayName;
+    BufferSource   id;
+    DOMString      displayName;
 };
 
-dictionary MakeCredentialOptions {
-    required PublicKeyCredentialEntity rp;
+dictionary MakePublicKeyCredentialOptions {
+    required PublicKeyCredentialRpEntity   rp;
     required PublicKeyCredentialUserEntity user;
 
-    required BufferSource                         challenge;
+    required BufferSource                            challenge;
     required sequence<PublicKeyCredentialParameters> parameters;
 
-    unsigned long                        timeout;
-    sequence<PublicKeyCredentialDescriptor> excludeList;
-    AuthenticatorSelectionCriteria       authenticatorSelection;
+    unsigned long                                timeout;
+    sequence<PublicKeyCredentialDescriptor>      excludeList = [];
+    AuthenticatorSelectionCriteria               authenticatorSelection;
     // Extensions are not supported yet.
-    // AuthenticationExtensions             extensions;
+    // AuthenticationExtensions                  extensions; // Add in Bug 1406458
 };
 
 dictionary PublicKeyCredentialEntity {
-    DOMString id;
-    DOMString name;
-    USVString icon;
+    DOMString      name;
+    USVString      icon;
+};
+
+dictionary PublicKeyCredentialRpEntity : PublicKeyCredentialEntity {
+    DOMString      id;
 };
 
 dictionary AuthenticatorSelectionCriteria {
-    Attachment    attachment;
-    boolean       requireResidentKey = false;
+    AuthenticatorAttachment      authenticatorAttachment;
+    boolean                      requireResidentKey = false;
+    boolean                      requireUserVerification = false;
 };
 
-enum Attachment {
-    "platform",
-    "cross-platform"
+enum AuthenticatorAttachment {
+    "platform",       // Platform attachment
+    "cross-platform"  // Cross-platform attachment
 };
 
 dictionary PublicKeyCredentialRequestOptions {
     required BufferSource                challenge;
     unsigned long                        timeout;
     USVString                            rpId;
     sequence<PublicKeyCredentialDescriptor> allowList = [];
     // Extensions are not supported yet.
-    // AuthenticationExtensions             extensions;
+    // AuthenticationExtensions             extensions; // Add in Bug 1406458
 };
 
+typedef record<DOMString, any>       AuthenticationExtensions;
+
 dictionary CollectedClientData {
     required DOMString           challenge;
     required DOMString           origin;
     required DOMString           hashAlg;
     DOMString                    tokenBinding;
     // Extensions are not supported yet.
-    // AuthenticationExtensions     clientExtensions;
-    // AuthenticationExtensions     authenticatorExtensions;
+    // AuthenticationExtensions     clientExtensions; // Add in Bug 1406458
+    // AuthenticationExtensions     authenticatorExtensions; // Add in Bug 1406458
 };
 
 enum PublicKeyCredentialType {
     "public-key"
 };
 
 dictionary PublicKeyCredentialDescriptor {
     required PublicKeyCredentialType type;
     required BufferSource id;
     sequence<WebAuthnTransport>   transports;
 };
 
-typedef (boolean or DOMString) WebAuthnAlgorithmID; // Fix when upstream there's a definition of how to serialize AlgorithmIdentifier
+typedef (boolean or DOMString) WebAuthnAlgorithmID; // Switch to COSE in Bug 1381190
 
 [SecureContext, Pref="security.webauth.webauthn"]
 interface AuthenticatorAssertionResponse : AuthenticatorResponse {
     [SameObject] readonly attribute ArrayBuffer      authenticatorData;
     [SameObject] readonly attribute ArrayBuffer      signature;
+    readonly attribute DOMString                     userId;
 };
 
 // Renamed from "Transport" to avoid a collision with U2F
 enum WebAuthnTransport {
     "usb",
     "nfc",
     "ble"
 };
+
+typedef long COSEAlgorithmIdentifier;
+
+typedef sequence<AAGUID>      AuthenticatorSelectionList;
+
+typedef BufferSource      AAGUID;