Skip to content

Commit f92cffb

Browse files
authored
Fix: Validate EC public key is on configured curve (#88)
1 parent 8a83b10 commit f92cffb

File tree

3 files changed

+120
-67
lines changed

3 files changed

+120
-67
lines changed

lib/algorithms/ecdh.js

+60-36
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,47 @@ function ecdhDeriveFn() {
4040
name: "ECDH"
4141
};
4242

43+
var validatePublic = function(pk, form) {
44+
var pubKey = pk && ecUtil.convertToForge(pk, true);
45+
if (!pubKey || !pubKey.isValid()) {
46+
return Promise.reject(new Error("invalid EC public key"));
47+
}
48+
49+
switch (form) {
50+
case "jwk":
51+
pubKey = ecUtil.convertToJWK(pk, true);
52+
break;
53+
case "buffer":
54+
pubKey = ecUtil.convertToBuffer(pk, true);
55+
break;
56+
}
57+
return Promise.resolve(pubKey);
58+
}
59+
4360
// ### fallback implementation -- uses ecc + forge
4461
var fallback = function(key, props) {
4562
props = props || {};
4663
var keyLen = props.length || 0;
4764
// assume {key} is privateKey
48-
var privKey = ecUtil.convertToForge(key, false);
4965
// assume {props.public} is publicKey
50-
if (!props.public) {
51-
return Promise.reject(new Error("invalid EC public key"));
52-
}
53-
var pubKey = ecUtil.convertToForge(props.public, true);
54-
var secret = privKey.computeSecret(pubKey);
55-
if (keyLen) {
56-
// truncate to requested key length
57-
if (secret.length < keyLen) {
58-
return Promise.reject(new Error("key length too large: " + keyLen));
66+
var privKey = ecUtil.convertToForge(key, false);
67+
68+
var p = validatePublic(props.public, "forge");
69+
p = p.then(function(pubKey) {
70+
// {pubKey} is "forge"
71+
72+
var secret = privKey.computeSecret(pubKey);
73+
if (keyLen) {
74+
// truncate to requested key length
75+
if (secret.length < keyLen) {
76+
return Promise.reject(new Error("key length too large: " + keyLen));
77+
}
78+
secret = secret.slice(0, keyLen);
5979
}
60-
secret = secret.slice(0, keyLen);
61-
}
62-
return Promise.resolve(secret);
80+
81+
return secret;
82+
});
83+
return p;
6384
};
6485

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

88109
// assume {props.public} is publicKey
89-
if (!props.public) {
90-
return Promise.reject(new Error("invalid EC public key"));
91-
}
92-
var pubKey = ecUtil.convertToJWK(props.public, true);
93-
pubKey = helpers.subtleCrypto.importKey("jwk",
110+
var pubKey = validatePublic(props.public, "jwk");
111+
pubKey = pubKey.then(function(pubKey) {
112+
// {pubKey} is "jwk"
113+
return helpers.subtleCrypto.importKey("jwk",
94114
pubKey,
95115
algParams,
96116
false,
97117
[]);
118+
});
98119

99-
var promise = Promise.all([privKey, pubKey]);
100-
promise = promise.then(function(keypair) {
120+
var p = Promise.all([privKey, pubKey]);
121+
p = p.then(function(keypair) {
101122
var privKey = keypair[0],
102123
pubKey = keypair[1];
103124

@@ -106,11 +127,11 @@ function ecdhDeriveFn() {
106127
});
107128
return helpers.subtleCrypto.deriveBits(algParams, privKey, keyLen * 8);
108129
});
109-
promise = promise.then(function(result) {
130+
p = p.then(function(result) {
110131
result = new Buffer(result);
111132
return result;
112133
});
113-
return promise;
134+
return p;
114135
};
115136

116137
var nodejs = function(key, props) {
@@ -136,23 +157,26 @@ function ecdhDeriveFn() {
136157
}
137158

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

141-
// assume {props.public} is publicKey
142-
var pubKey = ecUtil.convertToBuffer(props.public, true);
143-
144-
var ecdh = helpers.nodeCrypto.createECDH(curve);
145-
// dummy call so computeSecret doesn't fail
146-
ecdh.generateKeys();
147-
ecdh.setPrivateKey(privKey);
148-
var secret = ecdh.computeSecret(pubKey);
149-
if (keyLen) {
150-
if (secret.length < keyLen) {
151-
return Promise.reject(new Error("key length too large: " + keyLen));
163+
var p = validatePublic(props.public, "buffer");
164+
p = p.then(function(pubKey) {
165+
// {pubKey} is "buffer"
166+
var ecdh = helpers.nodeCrypto.createECDH(curve);
167+
// dummy call so computeSecret doesn't fail
168+
// ecdh.generateKeys();
169+
ecdh.setPrivateKey(privKey);
170+
var secret = ecdh.computeSecret(pubKey);
171+
if (keyLen) {
172+
if (secret.length < keyLen) {
173+
return Promise.reject(new Error("key length too large: " + keyLen));
174+
}
175+
secret = secret.slice(0, keyLen);
152176
}
153-
secret = secret.slice(0, keyLen);
154-
}
155-
return Promise.resolve(secret);
177+
return secret;
178+
});
179+
return p;
156180
};
157181

158182
return helpers.setupFallback(nodejs, webcrypto, fallback);

lib/deps/ecc/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ function ECPublicKey(curve, x, y) {
8787
this.y = bn2bin(y, size);
8888
}
8989

90+
// basics
91+
ECPublicKey.prototype.isValid = function() {
92+
return this.params.curve.contains(this.point);
93+
}
94+
9095
// ECDSA
9196
ECPublicKey.prototype.verify = function(md, sig) {
9297
var N = this.params.getN(),
@@ -176,6 +181,15 @@ ECPrivateKey.prototype.sign = function(md) {
176181
};
177182
};
178183

184+
// basics
185+
ECPrivateKey.prototype.isValid = function() {
186+
var d = bin2bn(this.d),
187+
n1 = params.getN().subtract(BigIneger.ONE);
188+
189+
return (d.compareTo(BigInteger.ONE) >= 0) &&
190+
(d.compareTo(n1) < 0);
191+
}
192+
179193
// ECDH
180194
ECPrivateKey.prototype.computeSecret = function(pubkey) {
181195
var d = bin2bn(this.d);

lib/deps/ecc/math.js

+46-31
Original file line numberDiff line numberDiff line change
@@ -27,42 +27,42 @@ var BigInteger = jsbn.BigInteger,
2727
function ECFieldElementFp(q, x) {
2828
this.x = x;
2929
// TODO if(x.compareTo(q) >= 0) error
30-
this.q = q;
30+
this.p = q;
3131
}
3232

3333
function feFpEquals(other) {
3434
if (other === this) {
3535
return true;
3636
}
37-
return (this.q.equals(other.q) && this.x.equals(other.x));
37+
return (this.p.equals(other.p) && this.x.equals(other.x));
3838
}
3939

4040
function feFpToBigInteger() {
4141
return this.x;
4242
}
4343

4444
function feFpNegate() {
45-
return new ECFieldElementFp(this.q, this.x.negate().mod(this.q));
45+
return new ECFieldElementFp(this.p, this.x.negate().mod(this.p));
4646
}
4747

4848
function feFpAdd(b) {
49-
return new ECFieldElementFp(this.q, this.x.add(b.toBigInteger()).mod(this.q));
49+
return new ECFieldElementFp(this.p, this.x.add(b.toBigInteger()).mod(this.p));
5050
}
5151

5252
function feFpSubtract(b) {
53-
return new ECFieldElementFp(this.q, this.x.subtract(b.toBigInteger()).mod(this.q));
53+
return new ECFieldElementFp(this.p, this.x.subtract(b.toBigInteger()).mod(this.p));
5454
}
5555

5656
function feFpMultiply(b) {
57-
return new ECFieldElementFp(this.q, this.x.multiply(b.toBigInteger()).mod(this.q));
57+
return new ECFieldElementFp(this.p, this.x.multiply(b.toBigInteger()).mod(this.p));
5858
}
5959

6060
function feFpSquare() {
61-
return new ECFieldElementFp(this.q, this.x.square().mod(this.q));
61+
return new ECFieldElementFp(this.p, this.x.square().mod(this.p));
6262
}
6363

6464
function feFpDivide(b) {
65-
return new ECFieldElementFp(this.q, this.x.multiply(b.toBigInteger().modInverse(this.q)).mod(this.q));
65+
return new ECFieldElementFp(this.p, this.x.multiply(b.toBigInteger().modInverse(this.p)).mod(this.p));
6666
}
6767

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

9696
function pointFpGetX() {
9797
if(!this.zinv) {
98-
this.zinv = this.z.modInverse(this.curve.q);
98+
this.zinv = this.z.modInverse(this.curve.p);
9999
}
100100
var r = this.x.toBigInteger().multiply(this.zinv);
101101
this.curve.reduce(r);
@@ -104,7 +104,7 @@ function pointFpGetX() {
104104

105105
function pointFpGetY() {
106106
if(!this.zinv) {
107-
this.zinv = this.z.modInverse(this.curve.q);
107+
this.zinv = this.z.modInverse(this.curve.p);
108108
}
109109
var r = this.y.toBigInteger().multiply(this.zinv);
110110
this.curve.reduce(r);
@@ -123,12 +123,12 @@ function pointFpEquals(other) {
123123
}
124124
var u, v;
125125
// u = Y2 * Z1 - Y1 * Z2
126-
u = other.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(other.z)).mod(this.curve.q);
126+
u = other.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(other.z)).mod(this.curve.p);
127127
if (!u.equals(BigInteger.ZERO)) {
128128
return false;
129129
}
130130
// v = X2 * Z1 - X1 * Z2
131-
v = other.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(other.z)).mod(this.curve.q);
131+
v = other.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(other.z)).mod(this.curve.p);
132132
return v.equals(BigInteger.ZERO);
133133
}
134134

@@ -152,9 +152,9 @@ function pointFpAdd(b) {
152152
}
153153

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

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

175175
// x3 = v * (z2 * (z1 * u^2 - 2 * x1 * v^2) - v^3)
176-
var x3 = zu2.subtract(x1v2.shiftLeft(1)).multiply(b.z).subtract(v3).multiply(v).mod(this.curve.q);
176+
var x3 = zu2.subtract(x1v2.shiftLeft(1)).multiply(b.z).subtract(v3).multiply(v).mod(this.curve.p);
177177
// y3 = z2 * (3 * x1 * u * v^2 - y1 * v^3 - z1 * u^3) + u * v^3
178-
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);
178+
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);
179179
// z3 = v^3 * z1 * z2
180-
var z3 = v3.multiply(this.z).multiply(b.z).mod(this.curve.q);
180+
var z3 = v3.multiply(this.z).multiply(b.z).mod(this.curve.p);
181181

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

198198
var y1z1 = y1.multiply(this.z);
199-
var y1sqz1 = y1z1.multiply(y1).mod(this.curve.q);
199+
var y1sqz1 = y1z1.multiply(y1).mod(this.curve.p);
200200
var a = this.curve.a.toBigInteger();
201201

202202
// w = 3 * x1^2 + a * z1^2
203203
var w = x1.square().multiply(THREE);
204204
if (!BigInteger.ZERO.equals(a)) {
205205
w = w.add(this.z.square().multiply(a));
206206
}
207-
w = w.mod(this.curve.q);
207+
w = w.mod(this.curve.p);
208208
//this.curve.reduce(w);
209209
// x3 = 2 * y1 * z1 * (w^2 - 8 * x1 * y1^2 * z1)
210-
var x3 = w.square().subtract(x1.shiftLeft(3).multiply(y1sqz1)).shiftLeft(1).multiply(y1z1).mod(this.curve.q);
210+
var x3 = w.square().subtract(x1.shiftLeft(3).multiply(y1sqz1)).shiftLeft(1).multiply(y1z1).mod(this.curve.p);
211211
// y3 = 4 * y1^2 * z1 * (3 * w * x1 - 2 * y1^2 * z1) - w^3
212-
var y3 = w.multiply(THREE).multiply(x1).subtract(y1sqz1.shiftLeft(1)).shiftLeft(2).multiply(y1sqz1).subtract(w.square().multiply(w)).mod(this.curve.q);
212+
var y3 = w.multiply(THREE).multiply(x1).subtract(y1sqz1.shiftLeft(1)).shiftLeft(2).multiply(y1sqz1).subtract(w.square().multiply(w)).mod(this.curve.p);
213213
// z3 = 8 * (y1 * z1)^3
214-
var z3 = y1z1.square().multiply(y1z1).shiftLeft(3).mod(this.curve.q);
214+
var z3 = y1z1.square().multiply(y1z1).shiftLeft(3).mod(this.curve.p);
215215

216216
return new ECPointFp(this.curve, this.curve.fromBigInteger(x3), this.curve.fromBigInteger(y3), z3);
217217
}
@@ -293,16 +293,16 @@ ECPointFp.prototype.multiplyTwo = pointFpMultiplyTwo;
293293
// ECCurveFp
294294

295295
// constructor
296-
function ECCurveFp(q, a, b) {
297-
this.q = q;
296+
function ECCurveFp(p, a, b) {
297+
this.p = p;
298298
this.a = this.fromBigInteger(a);
299299
this.b = this.fromBigInteger(b);
300300
this.infinity = new ECPointFp(this, null, null);
301-
this.reducer = new Barrett(this.q);
301+
this.reducer = new Barrett(this.p);
302302
}
303303

304-
function curveFpGetQ() {
305-
return this.q;
304+
function curveFpgetP() {
305+
return this.p;
306306
}
307307

308308
function curveFpGetA() {
@@ -317,15 +317,29 @@ function curveFpEquals(other) {
317317
if (other === this) {
318318
return true;
319319
}
320-
return (this.q.equals(other.q) && this.a.equals(other.a) && this.b.equals(other.b));
320+
return (this.p.equals(other.p) && this.a.equals(other.a) && this.b.equals(other.b));
321+
}
322+
323+
function curveFpContains(pt) {
324+
// y^2 = x^3 + a*x + b mod p
325+
var x = pt.getX().toBigInteger(),
326+
y = pt.getY().toBigInteger(),
327+
a = this.a.toBigInteger(),
328+
b = this.b.toBigInteger(),
329+
p = this.p;
330+
331+
var left = y.pow(2).mod(p),
332+
right = x.pow(3).add(a.multiply(x)).add(b).mod(p)
333+
334+
return left.equals(right);
321335
}
322336

323337
function curveFpGetInfinity() {
324338
return this.infinity;
325339
}
326340

327341
function curveFpFromBigInteger(x) {
328-
return new ECFieldElementFp(this.q, x);
342+
return new ECFieldElementFp(this.p, x);
329343
}
330344

331345
function curveReduce(x) {
@@ -364,7 +378,7 @@ function curveFpEncodePointHex(p) {
364378
}
365379
var xHex = p.getX().toBigInteger().toString(16);
366380
var yHex = p.getY().toBigInteger().toString(16);
367-
var oLen = this.getQ().toString(16).length;
381+
var oLen = this.getP().toString(16).length;
368382
if ((oLen % 2) !== 0) {
369383
oLen++;
370384
}
@@ -377,10 +391,11 @@ function curveFpEncodePointHex(p) {
377391
return "04" + xHex + yHex;
378392
}
379393

380-
ECCurveFp.prototype.getQ = curveFpGetQ;
394+
ECCurveFp.prototype.getP = curveFpgetP;
381395
ECCurveFp.prototype.getA = curveFpGetA;
382396
ECCurveFp.prototype.getB = curveFpGetB;
383397
ECCurveFp.prototype.equals = curveFpEquals;
398+
ECCurveFp.prototype.contains = curveFpContains;
384399
ECCurveFp.prototype.getInfinity = curveFpGetInfinity;
385400
ECCurveFp.prototype.fromBigInteger = curveFpFromBigInteger;
386401
ECCurveFp.prototype.reduce = curveReduce;

0 commit comments

Comments
 (0)