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 679137 ca60b3cab4ed50a6f4c06ac0bb9498aaedd9551b
parent 679136 462c85ecfe5b684a34b1b133995dc5d2e97ae10e
child 679138 dd5ff0119c3f20f9b887c23774890e64d15a7f28
push id84141
push userbmo:schien@mozilla.com
push dateThu, 12 Oct 2017 11:13:04 +0000
reviewerskeeler, qdot
bugs1406456, 1406468
milestone58.0a1
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;