Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add brainpoolP256r1 curve support #421

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/algorithms/ec-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var clone = require("lodash/clone"),

var EC_KEYSIZES = {
"P-256": 256,
"BP-256": 256,
"P-384": 384,
"P-521": 521
};
Expand Down Expand Up @@ -86,6 +87,8 @@ function curveNameToOid(crv) {
return "1.3.132.0.34";
case "P-521":
return "1.3.132.0.35";
case "BP-256":
return "1.3.36.3.3.2.8.1.1.7";
default:
return null;
}
Expand Down
4 changes: 4 additions & 0 deletions lib/algorithms/ecdh.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var pick = require("lodash/pick");
function idealHash(curve) {
switch (curve) {
case "P-256":
case "BP-256":
return "SHA-256";
case "P-384":
return "SHA-384";
Expand Down Expand Up @@ -153,6 +154,9 @@ function ecdhDeriveFn() {
case "P-521":
curve = "secp521r1";
break;
case "BP-256":
curve = "brainpoolP256r1";
break;
default:
return Promise.reject(new Error("invalid curve: " + curve));
}
Expand Down
29 changes: 16 additions & 13 deletions lib/algorithms/ecdsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,26 @@ var ecUtil = require("./ec-util.js"),
helpers = require("./helpers.js"),
sha = require("./sha.js");

// TODO: Handle gracefully
function idealCurve(hash) {
switch (hash) {
case "SHA-256":
return "P-256";
return ["P-256", "BP-256"];
case "SHA-384":
return "P-384";
return ["P-384"];
case "SHA-512":
return "P-521";
return ["P-521"];
default:
throw new Error("unsupported hash: " + hash);
}
}

function ecdsaSignFN(hash) {
var curve = idealCurve(hash);
var curves = idealCurve(hash);

// ### Fallback implementation -- uses forge
var fallback = function(key, pdata /*, props */) {
if (curve !== key.crv) {
if (!curves.includes(key.crv)) {
return Promise.reject(new Error("invalid curve"));
}
var pk = ecUtil.convertToForge(key, false);
Expand All @@ -49,7 +50,7 @@ function ecdsaSignFN(hash) {

// ### WebCrypto API implementation
var webcrypto = function(key, pdata /*, props */) {
if (curve !== key.crv) {
if (!curves.includes(key.crv)) {
return Promise.reject(new Error("invalid curve"));
}
var pk = ecUtil.convertToJWK(key, false);
Expand Down Expand Up @@ -84,7 +85,7 @@ function ecdsaSignFN(hash) {
var nodeHash = hash.toLowerCase().replace("-", "");
if (helpers.nodeCrypto && helpers.nodeCrypto.getHashes().indexOf(nodeHash) > -1) {
nodejs = function(key, pdata) {
if (curve !== key.crv) {
if (!curves.includes(key.crv)) {
return Promise.reject(new Error("invalid curve"));
}

Expand Down Expand Up @@ -127,11 +128,11 @@ function ecdsaSignFN(hash) {
}

function ecdsaVerifyFN(hash) {
var curve = idealCurve(hash);
var curves = idealCurve(hash);

// ### Fallback implementation -- uses forge
var fallback = function(key, pdata, mac /*, props */) {
if (curve !== key.crv) {
if (!curves.includes(key.crv)) {
return Promise.reject(new Error("invalid curve"));
}
var pk = ecUtil.convertToForge(key, true);
Expand Down Expand Up @@ -160,7 +161,7 @@ function ecdsaVerifyFN(hash) {

// ### WebCrypto API implementation
var webcrypto = function(key, pdata, mac /* , props */) {
if (curve !== key.crv) {
if (!curves.includes(key.crv)) {
return Promise.reject(new Error("invalid curve"));
}
var pk = ecUtil.convertToJWK(key, true);
Expand Down Expand Up @@ -198,7 +199,7 @@ function ecdsaVerifyFN(hash) {
var nodeHash = hash.toLowerCase().replace("-", "");
if (helpers.nodeCrypto && helpers.nodeCrypto.getHashes().indexOf(nodeHash) > -1) {
nodejs = function(key, pdata, mac /* , props */) {
if (curve !== key.crv) {
if (!curves.includes(key.crv)) {
return Promise.reject(new Error("invalid curve"));
}

Expand Down Expand Up @@ -247,11 +248,13 @@ var ecdsa = {};
[
"ES256",
"ES384",
"ES512"
"ES512",
"BP256R1"
].forEach(function(name) {
var hash = name.replace(/ES(\d+)/g, function(m, size) {
var hash = name.replace(/(?:ES|BP)(\d+)\S*/g, function(m, size) {
return "SHA-" + size;
});

ecdsa[name] = {
sign: ecdsaSignFN(hash),
verify: ecdsaVerifyFN(hash)
Expand Down
11 changes: 4 additions & 7 deletions lib/algorithms/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,14 @@ exports.setupFallback = function(nodejs, webcrypto, fallback) {
var args = arguments,
promise;

function check(err) {
if (0 === err.message.indexOf("unsupported algorithm:")) {
impl = fallback;
return impl.apply(null, args);
}

return Promise.reject(err);
function check(_err) {
impl = fallback;
return impl.apply(null, args);
}

try {
promise = Promise.resolve(nodejs.apply(null, args));
promise = promise.catch(check);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nodejs.apply(null, args) is a synchronous operation so i think this statement is unreachable.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaccenture good point! nodejs.apply is a synchronous function that can throw. However, it also returns a promise that can reject. Hence we need to both wrap the call in a try/catch, and call promise.catch here. I'll add a comment to clarify.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the link, I saw return Promise.reject() so this catch here save us from unhandled promise rejection. Legitimate.

} catch(err) {
promise = check(err);
}
Expand Down
17 changes: 16 additions & 1 deletion lib/deps/ecc/curves.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,31 @@ function secp521r1() {
return new X9ECParameters(curve, G, n, h);
}

function bp256() {
var p = new BigInteger("76884956397045344220809746629001649093037950200943055203735601445031516197751");
var a = new BigInteger("56698187605326110043627228396178346077120614539475214109386828188763884139993");
var b = new BigInteger("17577232497321838841075697789794520262950426058923084567046852300633325438902");
var curve = new ec.ECCurveFp(p, a, b);
var G = curve.decodePointHex("04"
+ "8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262"
+ "547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997");
var n = new BigInteger("76884956397045344220809746629001649092737531784414529538755519063063536359079");
var h = BigInteger.ONE;
return new X9ECParameters(curve, G, n, h);
}

// ----------------
// Public API

var CURVES = module.exports = {
"secp256r1": secp256r1(),
"secp384r1": secp384r1(),
"secp521r1": secp521r1()
"secp521r1": secp521r1(),
"brainpoolP256r1": bp256()
};

// also export NIST names
CURVES["P-256"] = CURVES.secp256r1;
CURVES["P-384"] = CURVES.secp384r1;
CURVES["P-521"] = CURVES.secp521r1;
CURVES["BP-256"] = CURVES.brainpoolP256r1;
17 changes: 11 additions & 6 deletions lib/jwk/eckey.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ var JWK = {
var SIG_ALGS = [
"ES256",
"ES384",
"ES512"
"ES512",
"BP256R1"
];
var WRAP_ALGS = [
"ECDH-ES",
Expand All @@ -35,6 +36,8 @@ function oidToCurveName(oid) {
return "P-384";
case "1.3.132.0.35":
return "P-521";
case "1.3.36.3.3.2.8.1.1.7":
return "BP-256";
default:
return null;
}
Expand Down Expand Up @@ -107,16 +110,18 @@ var JWKEcCfg = {
if (!keys.private) {
return [];
}
return SIG_ALGS.filter(function(a) {
return (a === ("ES" + len));
var sigAlgs = SIG_ALGS.filter(function(a) {
return (a === ("ES" + len) || a === ("BP" + len + "R1"));
});
return sigAlgs;
case "verify":
if (!keys.public) {
return [];
}
return SIG_ALGS.filter(function(a) {
return (a === ("ES" + len));
var sigAlgs = SIG_ALGS.filter(function(a) {
return (a === ("ES" + len) || a === ("BP" + len + "R1"));
});
return sigAlgs
}
},

Expand Down Expand Up @@ -285,7 +290,7 @@ var JWKEcFactory = {
crv = oidToCurveName(crv);
}
if (!crv) {
return null;
throw new Error('Unsupported curve specified')
}

if (!input.parsed) {
Expand Down
2 changes: 2 additions & 0 deletions lib/jws/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ var JWSVerifier = function(ks, globalOpts) {
var processSig = function() {
var sig = sigList.shift();
if (!sig) {
// TODO: Here is a bug - the promise resolves twice with sigList = []
// in case of Error is thrown on line 227
reject(new Error("no key found"));
return;
}
Expand Down
4 changes: 2 additions & 2 deletions test/jwk/eckey-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ describe("jwk/EC", function() {
assert.deepEqual(algs, []);

algs = JWK.EC.config.algorithms(keys, "verify");
assert.deepEqual(algs, ["ES256"]);
assert.deepEqual(algs, ["ES256", "BP256R1"]);
});
it("returns suite for private key", function() {
var keys = pick(keyPair, "private");
Expand All @@ -248,7 +248,7 @@ describe("jwk/EC", function() {
assert.deepEqual(algs, ["ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW"]);

algs = JWK.EC.config.algorithms(keys, "sign");
assert.deepEqual(algs, ["ES256"]);
assert.deepEqual(algs, ["ES256", "BP256R1"]);

algs = JWK.EC.config.algorithms(keys, "verify");
assert.deepEqual(algs, []);
Expand Down