Bug 1420763 - encode webauthn keys as a COSE key; r=keeler draft
authorAdam Langley <agl@chromium.org>
Thu, 04 Jan 2018 14:05:14 -0700
changeset 716458 21d84d67f19d1885b73473a4d77d15f6c4cd80c2
parent 715663 f78a83244fbebe8a469ae3512fce7f638cab7e1f
child 745048 e543e87f586bc01946029e6228361a38e0f620c6
push id94450
push userbmo:jjones@mozilla.com
push dateFri, 05 Jan 2018 21:26:01 +0000
reviewerskeeler
bugs1420763
milestone59.0a1
Bug 1420763 - encode webauthn keys as a COSE key; r=keeler webauthn says[1] that public keys are encoded as COSE keys. I find the COSE RFC quite circuitous in many respects and so any reviews should check whether they agree with my understanding of what should be in a COSE key. The webauthn spec says that the key: “MUST contain the "alg" parameter and MUST NOT contain any other optional parameters.” I don't believe that any of the parameters included are optional but, again, I don't think the RFC is completely clear. [1] https://www.w3.org/TR/webauthn/#sec-attested-credential-data MozReview-Commit-ID: 2023mW3yVWU
dom/webauthn/WebAuthnCBORUtil.cpp
dom/webauthn/tests/u2futil.js
--- a/dom/webauthn/WebAuthnCBORUtil.cpp
+++ b/dom/webauthn/WebAuthnCBORUtil.cpp
@@ -16,37 +16,33 @@ CBOREncodePublicKeyObj(const CryptoBuffe
                        /* out */ CryptoBuffer& aPubKeyObj)
 {
   mozilla::dom::CryptoBuffer xBuf, yBuf;
   nsresult rv = U2FDecomposeECKey(aPubKeyBuf, xBuf, yBuf);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  /*
-  Public Key Object, encoded in CBOR (description is CDDL)
-
-  pubKey = $pubKeyFmt
-
-  pubKeyFmt /= eccPubKey
-  eccPubKey = { alg: eccAlgName, x: biguint, y: biguint }
-  eccAlgName = "ES256" / "ES384" / "ES512"
-  */
+  // COSE_Key object. See https://tools.ietf.org/html/rfc8152#section-7
   cbor::output_dynamic cborPubKeyOut;
   cbor::encoder encoder(cborPubKeyOut);
-  encoder.write_map(3);
+  encoder.write_map(5);
   {
-    encoder.write_string("x");
-    encoder.write_bytes(xBuf.Elements(), xBuf.Length());
+    encoder.write_int(1);   // kty
+    encoder.write_int(2);   // EC2
+    encoder.write_int(3);   // alg
+    encoder.write_int(-7);  // ES256
 
-    encoder.write_string("y");
+    // See https://tools.ietf.org/html/rfc8152#section-13.1
+    encoder.write_int(-1);  // crv
+    encoder.write_int(1);   // P-256
+    encoder.write_int(-2);  // x
+    encoder.write_bytes(xBuf.Elements(), xBuf.Length());
+    encoder.write_int(-3);  // y
     encoder.write_bytes(yBuf.Elements(), yBuf.Length());
-
-    encoder.write_string("alg");
-    encoder.write_string(JWK_ALG_ECDSA_P_256); // Always ES256 for U2F
   }
 
   if (!aPubKeyObj.Assign(cborPubKeyOut.data(), cborPubKeyOut.size())) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
 
--- a/dom/webauthn/tests/u2futil.js
+++ b/dom/webauthn/tests/u2futil.js
@@ -1,18 +1,26 @@
 // Used by local_addTest() / local_completeTest()
 var _countCompletions = 0;
 var _expectedCompletions = 0;
 
 const flag_TUP = 0x01;
 const flag_UV = 0x04;
 const flag_AT = 0x40;
 
+
+const cose_kty = 1;
+const cose_kty_ec2 = 2;
+const cose_alg = 3;
 const cose_alg_ECDSA_w_SHA256 = -7;
 const cose_alg_ECDSA_w_SHA512 = -36;
+const cose_crv = -1;
+const cose_crv_P256 = 1;
+const cose_crv_x = -2;
+const cose_crv_y = -3;
 
 function handleEventMessage(event) {
   if ("test" in event.data) {
     let summary = event.data.test + ": " + event.data.msg;
     log(event.data.status + ": " + summary);
     ok(event.data.status, summary);
   } else if ("done" in event.data) {
     SimpleTest.finish();
@@ -182,28 +190,37 @@ function webAuthnDecodeAuthDataArray(aAu
   attData.credIdLen = (aAuthData[53] << 8) + aAuthData[54];
   attData.credId = aAuthData.slice(55, 55 + attData.credIdLen);
 
   console.log(":: Authenticator Data ::");
   console.log("AAGUID: " + hexEncode(attData.aaguid));
 
   cborPubKey = aAuthData.slice(55 + attData.credIdLen);
   var pubkeyObj = CBOR.decode(cborPubKey.buffer);
-  if (!("alg" in pubkeyObj && "x" in pubkeyObj && "y" in pubkeyObj)) {
-    return Promise.reject("Invalid CBOR Public Key Object");
+  if (!(cose_kty in pubkeyObj && cose_alg in pubkeyObj && cose_crv in pubkeyObj
+        && cose_crv_x in pubkeyObj && cose_crv_y in pubkeyObj)) {
+    throw "Invalid CBOR Public Key Object";
   }
-  if (pubkeyObj.alg != "ES256") {
-    return Promise.reject("Unexpected public key algorithm");
+  if (pubkeyObj[cose_kty] != cose_kty_ec2) {
+    throw "Unexpected key type";
+  }
+  if (pubkeyObj[cose_alg] != cose_alg_ECDSA_w_SHA256) {
+    throw "Unexpected public key algorithm";
+  }
+  if (pubkeyObj[cose_crv] != cose_crv_P256) {
+    throw "Unexpected curve";
   }
 
-  let pubKeyBytes = assemblePublicKeyBytesData(pubkeyObj.x, pubkeyObj.y);
+  let pubKeyBytes = assemblePublicKeyBytesData(pubkeyObj[cose_crv_x], pubkeyObj[cose_crv_y]);
   console.log(":: CBOR Public Key Object Data ::");
-  console.log("Algorithm: " + pubkeyObj.alg);
-  console.log("X: " + pubkeyObj.x);
-  console.log("Y: " + pubkeyObj.y);
+  console.log("kty: " + pubkeyObj[cose_kty] + " (EC2)");
+  console.log("alg: " + pubkeyObj[cose_alg] + " (ES256)");
+  console.log("crv: " + pubkeyObj[cose_crv] + " (P256)");
+  console.log("X: " + pubkeyObj[cose_crv_x]);
+  console.log("Y: " + pubkeyObj[cose_crv_y]);
   console.log("Uncompressed (hex): " + hexEncode(pubKeyBytes));
 
   return importPublicKey(pubKeyBytes)
   .then(function(aKeyHandle) {
     return Promise.resolve({
       rpIdHash: rpIdHash,
       flags: flags,
       counter: counter,