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
--- 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,