Skip to content

Fix: Validate EC public key is on configured curve #88

New issue

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

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

Already on GitHub? # to your account

Merged
merged 1 commit into from
Feb 15, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
96 changes: 60 additions & 36 deletions lib/algorithms/ecdh.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,47 @@ function ecdhDeriveFn() {
name: "ECDH"
};

var validatePublic = function(pk, form) {
var pubKey = pk && ecUtil.convertToForge(pk, true);
if (!pubKey || !pubKey.isValid()) {
return Promise.reject(new Error("invalid EC public key"));
}

switch (form) {
case "jwk":
pubKey = ecUtil.convertToJWK(pk, true);
break;
case "buffer":
pubKey = ecUtil.convertToBuffer(pk, true);
break;
}
return Promise.resolve(pubKey);
}

// ### fallback implementation -- uses ecc + forge
var fallback = function(key, props) {
props = props || {};
var keyLen = props.length || 0;
// assume {key} is privateKey
var privKey = ecUtil.convertToForge(key, false);
// assume {props.public} is publicKey
if (!props.public) {
return Promise.reject(new Error("invalid EC public key"));
}
var pubKey = ecUtil.convertToForge(props.public, true);
var secret = privKey.computeSecret(pubKey);
if (keyLen) {
// truncate to requested key length
if (secret.length < keyLen) {
return Promise.reject(new Error("key length too large: " + keyLen));
var privKey = ecUtil.convertToForge(key, false);

var p = validatePublic(props.public, "forge");
p = p.then(function(pubKey) {
// {pubKey} is "forge"

var secret = privKey.computeSecret(pubKey);
if (keyLen) {
// truncate to requested key length
if (secret.length < keyLen) {
return Promise.reject(new Error("key length too large: " + keyLen));
}
secret = secret.slice(0, keyLen);
}
secret = secret.slice(0, keyLen);
}
return Promise.resolve(secret);

return secret;
});
return p;
};

// ### WebCryptoAPI implementation
Expand All @@ -86,18 +107,18 @@ function ecdhDeriveFn() {
[ "deriveBits" ]);

// assume {props.public} is publicKey
if (!props.public) {
return Promise.reject(new Error("invalid EC public key"));
}
var pubKey = ecUtil.convertToJWK(props.public, true);
pubKey = helpers.subtleCrypto.importKey("jwk",
var pubKey = validatePublic(props.public, "jwk");
pubKey = pubKey.then(function(pubKey) {
// {pubKey} is "jwk"
return helpers.subtleCrypto.importKey("jwk",
pubKey,
algParams,
false,
[]);
});

var promise = Promise.all([privKey, pubKey]);
promise = promise.then(function(keypair) {
var p = Promise.all([privKey, pubKey]);
p = p.then(function(keypair) {
var privKey = keypair[0],
pubKey = keypair[1];

Expand All @@ -106,11 +127,11 @@ function ecdhDeriveFn() {
});
return helpers.subtleCrypto.deriveBits(algParams, privKey, keyLen * 8);
});
promise = promise.then(function(result) {
p = p.then(function(result) {
result = new Buffer(result);
return result;
});
return promise;
return p;
};

var nodejs = function(key, props) {
Expand All @@ -136,23 +157,26 @@ function ecdhDeriveFn() {
}

// assume {key} is privateKey
// assume {props.public} is publicKey
var privKey = ecUtil.convertToBuffer(key, false);

// assume {props.public} is publicKey
var pubKey = ecUtil.convertToBuffer(props.public, true);

var ecdh = helpers.nodeCrypto.createECDH(curve);
// dummy call so computeSecret doesn't fail
ecdh.generateKeys();
ecdh.setPrivateKey(privKey);
var secret = ecdh.computeSecret(pubKey);
if (keyLen) {
if (secret.length < keyLen) {
return Promise.reject(new Error("key length too large: " + keyLen));
var p = validatePublic(props.public, "buffer");
p = p.then(function(pubKey) {
// {pubKey} is "buffer"
var ecdh = helpers.nodeCrypto.createECDH(curve);
// dummy call so computeSecret doesn't fail
// ecdh.generateKeys();
ecdh.setPrivateKey(privKey);
var secret = ecdh.computeSecret(pubKey);
if (keyLen) {
if (secret.length < keyLen) {
return Promise.reject(new Error("key length too large: " + keyLen));
}
secret = secret.slice(0, keyLen);
}
secret = secret.slice(0, keyLen);
}
return Promise.resolve(secret);
return secret;
});
return p;
};

return helpers.setupFallback(nodejs, webcrypto, fallback);
Expand Down
14 changes: 14 additions & 0 deletions lib/deps/ecc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ function ECPublicKey(curve, x, y) {
this.y = bn2bin(y, size);
}

// basics
ECPublicKey.prototype.isValid = function() {
return this.params.curve.contains(this.point);
}

// ECDSA
ECPublicKey.prototype.verify = function(md, sig) {
var N = this.params.getN(),
Expand Down Expand Up @@ -176,6 +181,15 @@ ECPrivateKey.prototype.sign = function(md) {
};
};

// basics
ECPrivateKey.prototype.isValid = function() {
var d = bin2bn(this.d),
n1 = params.getN().subtract(BigIneger.ONE);

return (d.compareTo(BigInteger.ONE) >= 0) &&
(d.compareTo(n1) < 0);
}

// ECDH
ECPrivateKey.prototype.computeSecret = function(pubkey) {
var d = bin2bn(this.d);
Expand Down
77 changes: 46 additions & 31 deletions lib/deps/ecc/math.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,42 @@ var BigInteger = jsbn.BigInteger,
function ECFieldElementFp(q, x) {
this.x = x;
// TODO if(x.compareTo(q) >= 0) error
this.q = q;
this.p = q;
}

function feFpEquals(other) {
if (other === this) {
return true;
}
return (this.q.equals(other.q) && this.x.equals(other.x));
return (this.p.equals(other.p) && this.x.equals(other.x));
}

function feFpToBigInteger() {
return this.x;
}

function feFpNegate() {
return new ECFieldElementFp(this.q, this.x.negate().mod(this.q));
return new ECFieldElementFp(this.p, this.x.negate().mod(this.p));
}

function feFpAdd(b) {
return new ECFieldElementFp(this.q, this.x.add(b.toBigInteger()).mod(this.q));
return new ECFieldElementFp(this.p, this.x.add(b.toBigInteger()).mod(this.p));
}

function feFpSubtract(b) {
return new ECFieldElementFp(this.q, this.x.subtract(b.toBigInteger()).mod(this.q));
return new ECFieldElementFp(this.p, this.x.subtract(b.toBigInteger()).mod(this.p));
}

function feFpMultiply(b) {
return new ECFieldElementFp(this.q, this.x.multiply(b.toBigInteger()).mod(this.q));
return new ECFieldElementFp(this.p, this.x.multiply(b.toBigInteger()).mod(this.p));
}

function feFpSquare() {
return new ECFieldElementFp(this.q, this.x.square().mod(this.q));
return new ECFieldElementFp(this.p, this.x.square().mod(this.p));
}

function feFpDivide(b) {
return new ECFieldElementFp(this.q, this.x.multiply(b.toBigInteger().modInverse(this.q)).mod(this.q));
return new ECFieldElementFp(this.p, this.x.multiply(b.toBigInteger().modInverse(this.p)).mod(this.p));
}

ECFieldElementFp.prototype.equals = feFpEquals;
Expand Down Expand Up @@ -95,7 +95,7 @@ function ECPointFp(curve, x, y, z) {

function pointFpGetX() {
if(!this.zinv) {
this.zinv = this.z.modInverse(this.curve.q);
this.zinv = this.z.modInverse(this.curve.p);
}
var r = this.x.toBigInteger().multiply(this.zinv);
this.curve.reduce(r);
Expand All @@ -104,7 +104,7 @@ function pointFpGetX() {

function pointFpGetY() {
if(!this.zinv) {
this.zinv = this.z.modInverse(this.curve.q);
this.zinv = this.z.modInverse(this.curve.p);
}
var r = this.y.toBigInteger().multiply(this.zinv);
this.curve.reduce(r);
Expand All @@ -123,12 +123,12 @@ function pointFpEquals(other) {
}
var u, v;
// u = Y2 * Z1 - Y1 * Z2
u = other.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(other.z)).mod(this.curve.q);
u = other.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(other.z)).mod(this.curve.p);
if (!u.equals(BigInteger.ZERO)) {
return false;
}
// v = X2 * Z1 - X1 * Z2
v = other.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(other.z)).mod(this.curve.q);
v = other.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(other.z)).mod(this.curve.p);
return v.equals(BigInteger.ZERO);
}

Expand All @@ -152,9 +152,9 @@ function pointFpAdd(b) {
}

// u = Y2 * Z1 - Y1 * Z2
var u = b.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(b.z)).mod(this.curve.q);
var u = b.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(b.z)).mod(this.curve.p);
// v = X2 * Z1 - X1 * Z2
var v = b.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(b.z)).mod(this.curve.q);
var v = b.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(b.z)).mod(this.curve.p);

if (BigInteger.ZERO.equals(v)) {
if (BigInteger.ZERO.equals(u)) {
Expand All @@ -173,11 +173,11 @@ function pointFpAdd(b) {
var zu2 = u.square().multiply(this.z);

// x3 = v * (z2 * (z1 * u^2 - 2 * x1 * v^2) - v^3)
var x3 = zu2.subtract(x1v2.shiftLeft(1)).multiply(b.z).subtract(v3).multiply(v).mod(this.curve.q);
var x3 = zu2.subtract(x1v2.shiftLeft(1)).multiply(b.z).subtract(v3).multiply(v).mod(this.curve.p);
// y3 = z2 * (3 * x1 * u * v^2 - y1 * v^3 - z1 * u^3) + u * v^3
var y3 = x1v2.multiply(THREE).multiply(u).subtract(y1.multiply(v3)).subtract(zu2.multiply(u)).multiply(b.z).add(u.multiply(v3)).mod(this.curve.q);
var y3 = x1v2.multiply(THREE).multiply(u).subtract(y1.multiply(v3)).subtract(zu2.multiply(u)).multiply(b.z).add(u.multiply(v3)).mod(this.curve.p);
// z3 = v^3 * z1 * z2
var z3 = v3.multiply(this.z).multiply(b.z).mod(this.curve.q);
var z3 = v3.multiply(this.z).multiply(b.z).mod(this.curve.p);

return new ECPointFp(this.curve, this.curve.fromBigInteger(x3), this.curve.fromBigInteger(y3), z3);
}
Expand All @@ -196,22 +196,22 @@ function pointFpTwice() {
var y1 = this.y.toBigInteger();

var y1z1 = y1.multiply(this.z);
var y1sqz1 = y1z1.multiply(y1).mod(this.curve.q);
var y1sqz1 = y1z1.multiply(y1).mod(this.curve.p);
var a = this.curve.a.toBigInteger();

// w = 3 * x1^2 + a * z1^2
var w = x1.square().multiply(THREE);
if (!BigInteger.ZERO.equals(a)) {
w = w.add(this.z.square().multiply(a));
}
w = w.mod(this.curve.q);
w = w.mod(this.curve.p);
//this.curve.reduce(w);
// x3 = 2 * y1 * z1 * (w^2 - 8 * x1 * y1^2 * z1)
var x3 = w.square().subtract(x1.shiftLeft(3).multiply(y1sqz1)).shiftLeft(1).multiply(y1z1).mod(this.curve.q);
var x3 = w.square().subtract(x1.shiftLeft(3).multiply(y1sqz1)).shiftLeft(1).multiply(y1z1).mod(this.curve.p);
// y3 = 4 * y1^2 * z1 * (3 * w * x1 - 2 * y1^2 * z1) - w^3
var y3 = w.multiply(THREE).multiply(x1).subtract(y1sqz1.shiftLeft(1)).shiftLeft(2).multiply(y1sqz1).subtract(w.square().multiply(w)).mod(this.curve.q);
var y3 = w.multiply(THREE).multiply(x1).subtract(y1sqz1.shiftLeft(1)).shiftLeft(2).multiply(y1sqz1).subtract(w.square().multiply(w)).mod(this.curve.p);
// z3 = 8 * (y1 * z1)^3
var z3 = y1z1.square().multiply(y1z1).shiftLeft(3).mod(this.curve.q);
var z3 = y1z1.square().multiply(y1z1).shiftLeft(3).mod(this.curve.p);

return new ECPointFp(this.curve, this.curve.fromBigInteger(x3), this.curve.fromBigInteger(y3), z3);
}
Expand Down Expand Up @@ -293,16 +293,16 @@ ECPointFp.prototype.multiplyTwo = pointFpMultiplyTwo;
// ECCurveFp

// constructor
function ECCurveFp(q, a, b) {
this.q = q;
function ECCurveFp(p, a, b) {
this.p = p;
this.a = this.fromBigInteger(a);
this.b = this.fromBigInteger(b);
this.infinity = new ECPointFp(this, null, null);
this.reducer = new Barrett(this.q);
this.reducer = new Barrett(this.p);
}

function curveFpGetQ() {
return this.q;
function curveFpgetP() {
return this.p;
}

function curveFpGetA() {
Expand All @@ -317,15 +317,29 @@ function curveFpEquals(other) {
if (other === this) {
return true;
}
return (this.q.equals(other.q) && this.a.equals(other.a) && this.b.equals(other.b));
return (this.p.equals(other.p) && this.a.equals(other.a) && this.b.equals(other.b));
}

function curveFpContains(pt) {
// y^2 = x^3 + a*x + b mod p
var x = pt.getX().toBigInteger(),
y = pt.getY().toBigInteger(),
a = this.a.toBigInteger(),
b = this.b.toBigInteger(),
p = this.p;

var left = y.pow(2).mod(p),
right = x.pow(3).add(a.multiply(x)).add(b).mod(p)

return left.equals(right);
}

function curveFpGetInfinity() {
return this.infinity;
}

function curveFpFromBigInteger(x) {
return new ECFieldElementFp(this.q, x);
return new ECFieldElementFp(this.p, x);
}

function curveReduce(x) {
Expand Down Expand Up @@ -364,7 +378,7 @@ function curveFpEncodePointHex(p) {
}
var xHex = p.getX().toBigInteger().toString(16);
var yHex = p.getY().toBigInteger().toString(16);
var oLen = this.getQ().toString(16).length;
var oLen = this.getP().toString(16).length;
if ((oLen % 2) !== 0) {
oLen++;
}
Expand All @@ -377,10 +391,11 @@ function curveFpEncodePointHex(p) {
return "04" + xHex + yHex;
}

ECCurveFp.prototype.getQ = curveFpGetQ;
ECCurveFp.prototype.getP = curveFpgetP;
ECCurveFp.prototype.getA = curveFpGetA;
ECCurveFp.prototype.getB = curveFpGetB;
ECCurveFp.prototype.equals = curveFpEquals;
ECCurveFp.prototype.contains = curveFpContains;
ECCurveFp.prototype.getInfinity = curveFpGetInfinity;
ECCurveFp.prototype.fromBigInteger = curveFpFromBigInteger;
ECCurveFp.prototype.reduce = curveReduce;
Expand Down