diff --git a/LICENSE b/LICENSE index f4d749f..f1928f0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -The MIT License (MIT) Copyright (c) 2015 Zarmack Tanen +Copyright (c) 2012-2013 Jed Parsons Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/index.js b/index.js new file mode 100644 index 0000000..0282279 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +module.exports = require('./lib/srp'); + +module.exports.params = require('./lib/params'); diff --git a/lib/jsbn.js b/lib/jsbn.js new file mode 100644 index 0000000..7f2e624 --- /dev/null +++ b/lib/jsbn.js @@ -0,0 +1,1542 @@ +/* + * Basic JavaScript BN library - subset useful for RSA encryption. + * + * Copyright (c) 2003-2005 Tom Wu + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * In addition, the following condition applies: + * + * All redistributions must retain an intact copy of this copyright notice + * and disclaimer. + */ + +/* + * Added Node.js Buffers support + * 2014 rzcoder + */ + +var crypt = require('crypto'); +var _ = require('lodash'); + +// Bits per digit +var dbits; + +// JavaScript engine analysis +var canary = 0xdeadbeefcafe; +var j_lm = ((canary & 0xffffff) == 0xefcafe); + +// (public) Constructor +function BigInteger(a, b) { + if (a != null) { + if ("number" == typeof a) { + this.fromNumber(a, b); + } else if (Buffer.isBuffer(a)) { + this.fromBuffer(a); + } else if (b == null && "string" != typeof a) { + this.fromByteArray(a); + } else { + this.fromString(a, b); + } + } +} + +// return new, unset BigInteger +function nbi() { + return new BigInteger(null); +} + +// am: Compute w_j += (x*this_i), propagate carries, +// c is initial carry, returns final carry. +// c < 3*dvalue, x < 2*dvalue, this_i < dvalue +// We need to select the fastest one that works in this environment. + +// am1: use a single mult and divide to get the high bits, +// max digit bits should be 26 because +// max internal value = 2*dvalue^2-2*dvalue (< 2^53) +function am1(i, x, w, j, c, n) { + while (--n >= 0) { + var v = x * this[i++] + w[j] + c; + c = Math.floor(v / 0x4000000); + w[j++] = v & 0x3ffffff; + } + return c; +} +// am2 avoids a big mult-and-extract completely. +// Max digit bits should be <= 30 because we do bitwise ops +// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) +function am2(i, x, w, j, c, n) { + var xl = x & 0x7fff, xh = x >> 15; + while (--n >= 0) { + var l = this[i] & 0x7fff; + var h = this[i++] >> 15; + var m = xh * l + h * xl; + l = xl * l + ((m & 0x7fff) << 15) + w[j] + (c & 0x3fffffff); + c = (l >>> 30) + (m >>> 15) + xh * h + (c >>> 30); + w[j++] = l & 0x3fffffff; + } + return c; +} +// Alternately, set max digit bits to 28 since some +// browsers slow down when dealing with 32-bit numbers. +function am3(i, x, w, j, c, n) { + var xl = x & 0x3fff, xh = x >> 14; + while (--n >= 0) { + var l = this[i] & 0x3fff; + var h = this[i++] >> 14; + var m = xh * l + h * xl; + l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; + c = (l >> 28) + (m >> 14) + xh * h; + w[j++] = l & 0xfffffff; + } + return c; +} + +// We need to select the fastest one that works in this environment. +//if (j_lm && (navigator.appName == "Microsoft Internet Explorer")) { +// BigInteger.prototype.am = am2; +// dbits = 30; +//} else if (j_lm && (navigator.appName != "Netscape")) { +// BigInteger.prototype.am = am1; +// dbits = 26; +//} else { // Mozilla/Netscape seems to prefer am3 +// BigInteger.prototype.am = am3; +// dbits = 28; +//} + +// For node.js, we pick am3 with max dbits to 28. +BigInteger.prototype.am = am3; +dbits = 28; + +BigInteger.prototype.DB = dbits; +BigInteger.prototype.DM = ((1 << dbits) - 1); +BigInteger.prototype.DV = (1 << dbits); + +var BI_FP = 52; +BigInteger.prototype.FV = Math.pow(2, BI_FP); +BigInteger.prototype.F1 = BI_FP - dbits; +BigInteger.prototype.F2 = 2 * dbits - BI_FP; + +// Digit conversions +var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; +var BI_RC = new Array(); +var rr, vv; +rr = "0".charCodeAt(0); +for (vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; +rr = "a".charCodeAt(0); +for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; +rr = "A".charCodeAt(0); +for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; + +function int2char(n) { + return BI_RM.charAt(n); +} +function intAt(s, i) { + var c = BI_RC[s.charCodeAt(i)]; + return (c == null) ? -1 : c; +} + +// (protected) copy this to r +function bnpCopyTo(r) { + for (var i = this.t - 1; i >= 0; --i) r[i] = this[i]; + r.t = this.t; + r.s = this.s; +} + +// (protected) set from integer value x, -DV <= x < DV +function bnpFromInt(x) { + this.t = 1; + this.s = (x < 0) ? -1 : 0; + if (x > 0) this[0] = x; + else if (x < -1) this[0] = x + DV; + else this.t = 0; +} + +// return bigint initialized to value +function nbv(i) { + var r = nbi(); + r.fromInt(i); + return r; +} + +// (protected) set from string and radix +function bnpFromString(data, radix, unsigned) { + var k; + switch (radix) { + case 2: + k = 1; + break; + case 4: + k = 2; + break; + case 8: + k = 3; + break; + case 16: + k = 4; + break; + case 32: + k = 5; + break; + case 256: + k = 8; + break; + default: + this.fromRadix(data, radix); + return; + } + + this.t = 0; + this.s = 0; + + var i = data.length; + var mi = false; + var sh = 0; + + while (--i >= 0) { + var x = (k == 8) ? data[i] & 0xff : intAt(data, i); + if (x < 0) { + if (data.charAt(i) == "-") mi = true; + continue; + } + mi = false; + if (sh === 0) + this[this.t++] = x; + else if (sh + k > this.DB) { + this[this.t - 1] |= (x & ((1 << (this.DB - sh)) - 1)) << sh; + this[this.t++] = (x >> (this.DB - sh)); + } + else + this[this.t - 1] |= x << sh; + sh += k; + if (sh >= this.DB) sh -= this.DB; + } + if ((!unsigned) && k == 8 && (data[0] & 0x80) != 0) { + this.s = -1; + if (sh > 0) this[this.t - 1] |= ((1 << (this.DB - sh)) - 1) << sh; + } + this.clamp(); + if (mi) BigInteger.ZERO.subTo(this, this); +} + +function bnpFromByteArray(a, unsigned) { + this.fromString(a, 256, unsigned) +} + +function bnpFromBuffer(a) { + this.fromString(a, 256, true) +} + +// (protected) clamp off excess high words +function bnpClamp() { + var c = this.s & this.DM; + while (this.t > 0 && this[this.t - 1] == c) --this.t; +} + +// (public) return string representation in given radix +function bnToString(b) { + if (this.s < 0) return "-" + this.negate().toString(b); + var k; + if (b == 16) k = 4; + else if (b == 8) k = 3; + else if (b == 2) k = 1; + else if (b == 32) k = 5; + else if (b == 4) k = 2; + else return this.toRadix(b); + var km = (1 << k) - 1, d, m = false, r = "", i = this.t; + var p = this.DB - (i * this.DB) % k; + if (i-- > 0) { + if (p < this.DB && (d = this[i] >> p) > 0) { + m = true; + r = int2char(d); + } + while (i >= 0) { + if (p < k) { + d = (this[i] & ((1 << p) - 1)) << (k - p); + d |= this[--i] >> (p += this.DB - k); + } + else { + d = (this[i] >> (p -= k)) & km; + if (p <= 0) { + p += this.DB; + --i; + } + } + if (d > 0) m = true; + if (m) r += int2char(d); + } + } + if(r.length == 1) + r = '0' + r; + return m ? r : "0"; +} + +// (public) -this +function bnNegate() { + var r = nbi(); + BigInteger.ZERO.subTo(this, r); + return r; +} + +// (public) |this| +function bnAbs() { + return (this.s < 0) ? this.negate() : this; +} + +// (public) return + if this > a, - if this < a, 0 if equal +function bnCompareTo(a) { + var r = this.s - a.s; + if (r != 0) return r; + var i = this.t; + r = i - a.t; + if (r != 0) return (this.s < 0) ? -r : r; + while (--i >= 0) if ((r = this[i] - a[i]) != 0) return r; + return 0; +} + +// returns bit length of the integer x +function nbits(x) { + var r = 1, t; + if ((t = x >>> 16) != 0) { + x = t; + r += 16; + } + if ((t = x >> 8) != 0) { + x = t; + r += 8; + } + if ((t = x >> 4) != 0) { + x = t; + r += 4; + } + if ((t = x >> 2) != 0) { + x = t; + r += 2; + } + if ((t = x >> 1) != 0) { + x = t; + r += 1; + } + return r; +} + +// (public) return the number of bits in "this" +function bnBitLength() { + if (this.t <= 0) return 0; + return this.DB * (this.t - 1) + nbits(this[this.t - 1] ^ (this.s & this.DM)); +} + +// (protected) r = this << n*DB +function bnpDLShiftTo(n, r) { + var i; + for (i = this.t - 1; i >= 0; --i) r[i + n] = this[i]; + for (i = n - 1; i >= 0; --i) r[i] = 0; + r.t = this.t + n; + r.s = this.s; +} + +// (protected) r = this >> n*DB +function bnpDRShiftTo(n, r) { + for (var i = n; i < this.t; ++i) r[i - n] = this[i]; + r.t = Math.max(this.t - n, 0); + r.s = this.s; +} + +// (protected) r = this << n +function bnpLShiftTo(n, r) { + var bs = n % this.DB; + var cbs = this.DB - bs; + var bm = (1 << cbs) - 1; + var ds = Math.floor(n / this.DB), c = (this.s << bs) & this.DM, i; + for (i = this.t - 1; i >= 0; --i) { + r[i + ds + 1] = (this[i] >> cbs) | c; + c = (this[i] & bm) << bs; + } + for (i = ds - 1; i >= 0; --i) r[i] = 0; + r[ds] = c; + r.t = this.t + ds + 1; + r.s = this.s; + r.clamp(); +} + +// (protected) r = this >> n +function bnpRShiftTo(n, r) { + r.s = this.s; + var ds = Math.floor(n / this.DB); + if (ds >= this.t) { + r.t = 0; + return; + } + var bs = n % this.DB; + var cbs = this.DB - bs; + var bm = (1 << bs) - 1; + r[0] = this[ds] >> bs; + for (var i = ds + 1; i < this.t; ++i) { + r[i - ds - 1] |= (this[i] & bm) << cbs; + r[i - ds] = this[i] >> bs; + } + if (bs > 0) r[this.t - ds - 1] |= (this.s & bm) << cbs; + r.t = this.t - ds; + r.clamp(); +} + +// (protected) r = this - a +function bnpSubTo(a, r) { + var i = 0, c = 0, m = Math.min(a.t, this.t); + while (i < m) { + c += this[i] - a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + if (a.t < this.t) { + c -= a.s; + while (i < this.t) { + c += this[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while (i < a.t) { + c -= a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c -= a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c < -1) r[i++] = this.DV + c; + else if (c > 0) r[i++] = c; + r.t = i; + r.clamp(); +} + +// (protected) r = this * a, r != this,a (HAC 14.12) +// "this" should be the larger one if appropriate. +function bnpMultiplyTo(a, r) { + var x = this.abs(), y = a.abs(); + var i = x.t; + r.t = i + y.t; + while (--i >= 0) r[i] = 0; + for (i = 0; i < y.t; ++i) r[i + x.t] = x.am(0, y[i], r, i, 0, x.t); + r.s = 0; + r.clamp(); + if (this.s != a.s) BigInteger.ZERO.subTo(r, r); +} + +// (protected) r = this^2, r != this (HAC 14.16) +function bnpSquareTo(r) { + var x = this.abs(); + var i = r.t = 2 * x.t; + while (--i >= 0) r[i] = 0; + for (i = 0; i < x.t - 1; ++i) { + var c = x.am(i, x[i], r, 2 * i, 0, 1); + if ((r[i + x.t] += x.am(i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) { + r[i + x.t] -= x.DV; + r[i + x.t + 1] = 1; + } + } + if (r.t > 0) r[r.t - 1] += x.am(i, x[i], r, 2 * i, 0, 1); + r.s = 0; + r.clamp(); +} + +// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) +// r != q, this != m. q or r may be null. +function bnpDivRemTo(m, q, r) { + var pm = m.abs(); + if (pm.t <= 0) return; + var pt = this.abs(); + if (pt.t < pm.t) { + if (q != null) q.fromInt(0); + if (r != null) this.copyTo(r); + return; + } + if (r == null) r = nbi(); + var y = nbi(), ts = this.s, ms = m.s; + var nsh = this.DB - nbits(pm[pm.t - 1]); // normalize modulus + if (nsh > 0) { + pm.lShiftTo(nsh, y); + pt.lShiftTo(nsh, r); + } + else { + pm.copyTo(y); + pt.copyTo(r); + } + var ys = y.t; + var y0 = y[ys - 1]; + if (y0 === 0) return; + var yt = y0 * (1 << this.F1) + ((ys > 1) ? y[ys - 2] >> this.F2 : 0); + var d1 = this.FV / yt, d2 = (1 << this.F1) / yt, e = 1 << this.F2; + var i = r.t, j = i - ys, t = (q == null) ? nbi() : q; + y.dlShiftTo(j, t); + if (r.compareTo(t) >= 0) { + r[r.t++] = 1; + r.subTo(t, r); + } + BigInteger.ONE.dlShiftTo(ys, t); + t.subTo(y, y); // "negative" y so we can replace sub with am later + while (y.t < ys) y[y.t++] = 0; + while (--j >= 0) { + // Estimate quotient digit + var qd = (r[--i] == y0) ? this.DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); + if ((r[i] += y.am(0, qd, r, j, 0, ys)) < qd) { // Try it out + y.dlShiftTo(j, t); + r.subTo(t, r); + while (r[i] < --qd) r.subTo(t, r); + } + } + if (q != null) { + r.drShiftTo(ys, q); + if (ts != ms) BigInteger.ZERO.subTo(q, q); + } + r.t = ys; + r.clamp(); + if (nsh > 0) r.rShiftTo(nsh, r); // Denormalize remainder + if (ts < 0) BigInteger.ZERO.subTo(r, r); +} + +// (public) this mod a +function bnMod(a) { + var r = nbi(); + this.abs().divRemTo(a, null, r); + if (this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r, r); + return r; +} + +// Modular reduction using "classic" algorithm +function Classic(m) { + this.m = m; +} +function cConvert(x) { + if (x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); + else return x; +} +function cRevert(x) { + return x; +} +function cReduce(x) { + x.divRemTo(this.m, null, x); +} +function cMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} +function cSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +Classic.prototype.convert = cConvert; +Classic.prototype.revert = cRevert; +Classic.prototype.reduce = cReduce; +Classic.prototype.mulTo = cMulTo; +Classic.prototype.sqrTo = cSqrTo; + +// (protected) return "-1/this % 2^DB"; useful for Mont. reduction +// justification: +// xy == 1 (mod m) +// xy = 1+km +// xy(2-xy) = (1+km)(1-km) +// x[y(2-xy)] = 1-k^2m^2 +// x[y(2-xy)] == 1 (mod m^2) +// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 +// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. +// JS multiply "overflows" differently from C/C++, so care is needed here. +function bnpInvDigit() { + if (this.t < 1) return 0; + var x = this[0]; + if ((x & 1) === 0) return 0; + var y = x & 3; // y == 1/x mod 2^2 + y = (y * (2 - (x & 0xf) * y)) & 0xf; // y == 1/x mod 2^4 + y = (y * (2 - (x & 0xff) * y)) & 0xff; // y == 1/x mod 2^8 + y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; // y == 1/x mod 2^16 + // last step - calculate inverse mod DV directly; + // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints + y = (y * (2 - x * y % this.DV)) % this.DV; // y == 1/x mod 2^dbits + // we really want the negative inverse, and -DV < y < DV + return (y > 0) ? this.DV - y : -y; +} + +// Montgomery reduction +function Montgomery(m) { + this.m = m; + this.mp = m.invDigit(); + this.mpl = this.mp & 0x7fff; + this.mph = this.mp >> 15; + this.um = (1 << (m.DB - 15)) - 1; + this.mt2 = 2 * m.t; +} + +// xR mod m +function montConvert(x) { + var r = nbi(); + x.abs().dlShiftTo(this.m.t, r); + r.divRemTo(this.m, null, r); + if (x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r, r); + return r; +} + +// x/R mod m +function montRevert(x) { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; +} + +// x = x/R mod m (HAC 14.32) +function montReduce(x) { + while (x.t <= this.mt2) // pad x so am has enough room later + x[x.t++] = 0; + for (var i = 0; i < this.m.t; ++i) { + // faster way of calculating u0 = x[i]*mp mod DV + var j = x[i] & 0x7fff; + var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & x.DM; + // use am to combine the multiply-shift-add into one call + j = i + this.m.t; + x[j] += this.m.am(0, u0, x, i, 0, this.m.t); + // propagate carry + while (x[j] >= x.DV) { + x[j] -= x.DV; + x[++j]++; + } + } + x.clamp(); + x.drShiftTo(this.m.t, x); + if (x.compareTo(this.m) >= 0) x.subTo(this.m, x); +} + +// r = "x^2/R mod m"; x != r +function montSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +// r = "xy/R mod m"; x,y != r +function montMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} + +Montgomery.prototype.convert = montConvert; +Montgomery.prototype.revert = montRevert; +Montgomery.prototype.reduce = montReduce; +Montgomery.prototype.mulTo = montMulTo; +Montgomery.prototype.sqrTo = montSqrTo; + +// (protected) true iff this is even +function bnpIsEven() { + return ((this.t > 0) ? (this[0] & 1) : this.s) === 0; +} + +// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) +function bnpExp(e, z) { + if (e > 0xffffffff || e < 1) return BigInteger.ONE; + var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e) - 1; + g.copyTo(r); + while (--i >= 0) { + z.sqrTo(r, r2); + if ((e & (1 << i)) > 0) z.mulTo(r2, g, r); + else { + var t = r; + r = r2; + r2 = t; + } + } + return z.revert(r); +} + +// (public) this^e % m, 0 <= e < 2^32 +function bnModPowInt(e, m) { + var z; + if (e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); + return this.exp(e, z); +} + +// Copyright (c) 2005-2009 Tom Wu +// All Rights Reserved. +// See "LICENSE" for details. + +// Extended JavaScript BN functions, required for RSA private ops. + +// Version 1.1: new BigInteger("0", 10) returns "proper" zero +// Version 1.2: square() API, isProbablePrime fix + +//(public) +function bnClone() { + var r = nbi(); + this.copyTo(r); + return r; +} + +//(public) return value as integer +function bnIntValue() { + if (this.s < 0) { + if (this.t == 1) return this[0] - this.DV; + else if (this.t === 0) return -1; + } + else if (this.t == 1) return this[0]; + else if (this.t === 0) return 0; +// assumes 16 < DB < 32 + return ((this[1] & ((1 << (32 - this.DB)) - 1)) << this.DB) | this[0]; +} + +//(public) return value as byte +function bnByteValue() { + return (this.t == 0) ? this.s : (this[0] << 24) >> 24; +} + +//(public) return value as short (assumes DB>=16) +function bnShortValue() { + return (this.t == 0) ? this.s : (this[0] << 16) >> 16; +} + +//(protected) return x s.t. r^x < DV +function bnpChunkSize(r) { + return Math.floor(Math.LN2 * this.DB / Math.log(r)); +} + +//(public) 0 if this === 0, 1 if this > 0 +function bnSigNum() { + if (this.s < 0) return -1; + else if (this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; + else return 1; +} + +//(protected) convert to radix string +function bnpToRadix(b) { + if (b == null) b = 10; + if (this.signum() === 0 || b < 2 || b > 36) return "0"; + var cs = this.chunkSize(b); + var a = Math.pow(b, cs); + var d = nbv(a), y = nbi(), z = nbi(), r = ""; + this.divRemTo(d, y, z); + while (y.signum() > 0) { + r = (a + z.intValue()).toString(b).substr(1) + r; + y.divRemTo(d, y, z); + } + return z.intValue().toString(b) + r; +} + +//(protected) convert from radix string +function bnpFromRadix(s, b) { + this.fromInt(0); + if (b == null) b = 10; + var cs = this.chunkSize(b); + var d = Math.pow(b, cs), mi = false, j = 0, w = 0; + for (var i = 0; i < s.length; ++i) { + var x = intAt(s, i); + if (x < 0) { + if (s.charAt(i) == "-" && this.signum() === 0) mi = true; + continue; + } + w = b * w + x; + if (++j >= cs) { + this.dMultiply(d); + this.dAddOffset(w, 0); + j = 0; + w = 0; + } + } + if (j > 0) { + this.dMultiply(Math.pow(b, j)); + this.dAddOffset(w, 0); + } + if (mi) BigInteger.ZERO.subTo(this, this); +} + +//(protected) alternate constructor +function bnpFromNumber(a, b) { + if ("number" == typeof b) { + // new BigInteger(int,int,RNG) + if (a < 2) this.fromInt(1); + else { + this.fromNumber(a); + if (!this.testBit(a - 1)) // force MSB set + this.bitwiseTo(BigInteger.ONE.shiftLeft(a - 1), op_or, this); + if (this.isEven()) this.dAddOffset(1, 0); // force odd + while (!this.isProbablePrime(b)) { + this.dAddOffset(2, 0); + if (this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a - 1), this); + } + } + } else { + // new BigInteger(int,RNG) + var x = crypt.randomBytes((a >> 3) + 1) + var t = a & 7; + + if (t > 0) + x[0] &= ((1 << t) - 1); + else + x[0] = 0; + + this.fromByteArray(x); + } +} + +//(public) convert to bigendian byte array +function bnToByteArray() { + var i = this.t, r = new Array(); + r[0] = this.s; + var p = this.DB - (i * this.DB) % 8, d, k = 0; + if (i-- > 0) { + if (p < this.DB && (d = this[i] >> p) != (this.s & this.DM) >> p) + r[k++] = d | (this.s << (this.DB - p)); + while (i >= 0) { + if (p < 8) { + d = (this[i] & ((1 << p) - 1)) << (8 - p); + d |= this[--i] >> (p += this.DB - 8); + } + else { + d = (this[i] >> (p -= 8)) & 0xff; + if (p <= 0) { + p += this.DB; + --i; + } + } + if ((d & 0x80) != 0) d |= -256; + if (k === 0 && (this.s & 0x80) != (d & 0x80)) ++k; + if (k > 0 || d != this.s) r[k++] = d; + } + } + return r; +} + +/** + * return Buffer object + * @param trim {boolean} slice buffer if first element == 0 + * @returns {Buffer} + */ +function bnToBuffer(trimOrSize) { + var res = new Buffer(this.toByteArray()); + if (trimOrSize === true && res[0] === 0) { + res = res.slice(1); + } else if (_.isNumber(trimOrSize)) { + if (res.length > trimOrSize) { + for (var i = 0; i < res.length - trimOrSize; i++) { + if (res[i] !== 0) { + return null; + } + } + return res.slice(res.length - trimOrSize); + } else if (res.length < trimOrSize) { + var padded = new Buffer(trimOrSize); + padded.fill(0, 0, trimOrSize - res.length); + res.copy(padded, trimOrSize - res.length); + return padded; + } + } + return res; +} + +function bnEquals(a) { + return (this.compareTo(a) == 0); +} +function bnMin(a) { + return (this.compareTo(a) < 0) ? this : a; +} +function bnMax(a) { + return (this.compareTo(a) > 0) ? this : a; +} + +//(protected) r = this op a (bitwise) +function bnpBitwiseTo(a, op, r) { + var i, f, m = Math.min(a.t, this.t); + for (i = 0; i < m; ++i) r[i] = op(this[i], a[i]); + if (a.t < this.t) { + f = a.s & this.DM; + for (i = m; i < this.t; ++i) r[i] = op(this[i], f); + r.t = this.t; + } + else { + f = this.s & this.DM; + for (i = m; i < a.t; ++i) r[i] = op(f, a[i]); + r.t = a.t; + } + r.s = op(this.s, a.s); + r.clamp(); +} + +//(public) this & a +function op_and(x, y) { + return x & y; +} +function bnAnd(a) { + var r = nbi(); + this.bitwiseTo(a, op_and, r); + return r; +} + +//(public) this | a +function op_or(x, y) { + return x | y; +} +function bnOr(a) { + var r = nbi(); + this.bitwiseTo(a, op_or, r); + return r; +} + +//(public) this ^ a +function op_xor(x, y) { + return x ^ y; +} +function bnXor(a) { + var r = nbi(); + this.bitwiseTo(a, op_xor, r); + return r; +} + +//(public) this & ~a +function op_andnot(x, y) { + return x & ~y; +} +function bnAndNot(a) { + var r = nbi(); + this.bitwiseTo(a, op_andnot, r); + return r; +} + +//(public) ~this +function bnNot() { + var r = nbi(); + for (var i = 0; i < this.t; ++i) r[i] = this.DM & ~this[i]; + r.t = this.t; + r.s = ~this.s; + return r; +} + +//(public) this << n +function bnShiftLeft(n) { + var r = nbi(); + if (n < 0) this.rShiftTo(-n, r); else this.lShiftTo(n, r); + return r; +} + +//(public) this >> n +function bnShiftRight(n) { + var r = nbi(); + if (n < 0) this.lShiftTo(-n, r); else this.rShiftTo(n, r); + return r; +} + +//return index of lowest 1-bit in x, x < 2^31 +function lbit(x) { + if (x === 0) return -1; + var r = 0; + if ((x & 0xffff) === 0) { + x >>= 16; + r += 16; + } + if ((x & 0xff) === 0) { + x >>= 8; + r += 8; + } + if ((x & 0xf) === 0) { + x >>= 4; + r += 4; + } + if ((x & 3) === 0) { + x >>= 2; + r += 2; + } + if ((x & 1) === 0) ++r; + return r; +} + +//(public) returns index of lowest 1-bit (or -1 if none) +function bnGetLowestSetBit() { + for (var i = 0; i < this.t; ++i) + if (this[i] != 0) return i * this.DB + lbit(this[i]); + if (this.s < 0) return this.t * this.DB; + return -1; +} + +//return number of 1 bits in x +function cbit(x) { + var r = 0; + while (x != 0) { + x &= x - 1; + ++r; + } + return r; +} + +//(public) return number of set bits +function bnBitCount() { + var r = 0, x = this.s & this.DM; + for (var i = 0; i < this.t; ++i) r += cbit(this[i] ^ x); + return r; +} + +//(public) true iff nth bit is set +function bnTestBit(n) { + var j = Math.floor(n / this.DB); + if (j >= this.t) return (this.s != 0); + return ((this[j] & (1 << (n % this.DB))) != 0); +} + +//(protected) this op (1<>= this.DB; + } + if (a.t < this.t) { + c += a.s; + while (i < this.t) { + c += this[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while (i < a.t) { + c += a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c > 0) r[i++] = c; + else if (c < -1) r[i++] = this.DV + c; + r.t = i; + r.clamp(); +} + +//(public) this + a +function bnAdd(a) { + var r = nbi(); + this.addTo(a, r); + return r; +} + +//(public) this - a +function bnSubtract(a) { + var r = nbi(); + this.subTo(a, r); + return r; +} + +//(public) this * a +function bnMultiply(a) { + var r = nbi(); + this.multiplyTo(a, r); + return r; +} + +// (public) this^2 +function bnSquare() { + var r = nbi(); + this.squareTo(r); + return r; +} + +//(public) this / a +function bnDivide(a) { + var r = nbi(); + this.divRemTo(a, r, null); + return r; +} + +//(public) this % a +function bnRemainder(a) { + var r = nbi(); + this.divRemTo(a, null, r); + return r; +} + +//(public) [this/a,this%a] +function bnDivideAndRemainder(a) { + var q = nbi(), r = nbi(); + this.divRemTo(a, q, r); + return new Array(q, r); +} + +//(protected) this *= n, this >= 0, 1 < n < DV +function bnpDMultiply(n) { + this[this.t] = this.am(0, n - 1, this, 0, 0, this.t); + ++this.t; + this.clamp(); +} + +//(protected) this += n << w words, this >= 0 +function bnpDAddOffset(n, w) { + if (n === 0) return; + while (this.t <= w) this[this.t++] = 0; + this[w] += n; + while (this[w] >= this.DV) { + this[w] -= this.DV; + if (++w >= this.t) this[this.t++] = 0; + ++this[w]; + } +} + +//A "null" reducer +function NullExp() { +} +function nNop(x) { + return x; +} +function nMulTo(x, y, r) { + x.multiplyTo(y, r); +} +function nSqrTo(x, r) { + x.squareTo(r); +} + +NullExp.prototype.convert = nNop; +NullExp.prototype.revert = nNop; +NullExp.prototype.mulTo = nMulTo; +NullExp.prototype.sqrTo = nSqrTo; + +//(public) this^e +function bnPow(e) { + return this.exp(e, new NullExp()); +} + +//(protected) r = lower n words of "this * a", a.t <= n +//"this" should be the larger one if appropriate. +function bnpMultiplyLowerTo(a, n, r) { + var i = Math.min(this.t + a.t, n); + r.s = 0; // assumes a,this >= 0 + r.t = i; + while (i > 0) r[--i] = 0; + var j; + for (j = r.t - this.t; i < j; ++i) r[i + this.t] = this.am(0, a[i], r, i, 0, this.t); + for (j = Math.min(a.t, n); i < j; ++i) this.am(0, a[i], r, i, 0, n - i); + r.clamp(); +} + +//(protected) r = "this * a" without lower n words, n > 0 +//"this" should be the larger one if appropriate. +function bnpMultiplyUpperTo(a, n, r) { + --n; + var i = r.t = this.t + a.t - n; + r.s = 0; // assumes a,this >= 0 + while (--i >= 0) r[i] = 0; + for (i = Math.max(n - this.t, 0); i < a.t; ++i) + r[this.t + i - n] = this.am(n - i, a[i], r, 0, 0, this.t + i - n); + r.clamp(); + r.drShiftTo(1, r); +} + +//Barrett modular reduction +function Barrett(m) { +// setup Barrett + this.r2 = nbi(); + this.q3 = nbi(); + BigInteger.ONE.dlShiftTo(2 * m.t, this.r2); + this.mu = this.r2.divide(m); + this.m = m; +} + +function barrettConvert(x) { + if (x.s < 0 || x.t > 2 * this.m.t) return x.mod(this.m); + else if (x.compareTo(this.m) < 0) return x; + else { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; + } +} + +function barrettRevert(x) { + return x; +} + +//x = x mod m (HAC 14.42) +function barrettReduce(x) { + x.drShiftTo(this.m.t - 1, this.r2); + if (x.t > this.m.t + 1) { + x.t = this.m.t + 1; + x.clamp(); + } + this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3); + this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); + while (x.compareTo(this.r2) < 0) x.dAddOffset(1, this.m.t + 1); + x.subTo(this.r2, x); + while (x.compareTo(this.m) >= 0) x.subTo(this.m, x); +} + +//r = x^2 mod m; x != r +function barrettSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +//r = x*y mod m; x,y != r +function barrettMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} + +Barrett.prototype.convert = barrettConvert; +Barrett.prototype.revert = barrettRevert; +Barrett.prototype.reduce = barrettReduce; +Barrett.prototype.mulTo = barrettMulTo; +Barrett.prototype.sqrTo = barrettSqrTo; + +//(public) this^e % m (HAC 14.85) +function bnModPow(e, m) { + var i = e.bitLength(), k, r = nbv(1), z; + if (i <= 0) return r; + else if (i < 18) k = 1; + else if (i < 48) k = 3; + else if (i < 144) k = 4; + else if (i < 768) k = 5; + else k = 6; + if (i < 8) + z = new Classic(m); + else if (m.isEven()) + z = new Barrett(m); + else + z = new Montgomery(m); + +// precomputation + var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; + g[1] = z.convert(this); + if (k > 1) { + var g2 = nbi(); + z.sqrTo(g[1], g2); + while (n <= km) { + g[n] = nbi(); + z.mulTo(g2, g[n - 2], g[n]); + n += 2; + } + } + + var j = e.t - 1, w, is1 = true, r2 = nbi(), t; + i = nbits(e[j]) - 1; + while (j >= 0) { + if (i >= k1) w = (e[j] >> (i - k1)) & km; + else { + w = (e[j] & ((1 << (i + 1)) - 1)) << (k1 - i); + if (j > 0) w |= e[j - 1] >> (this.DB + i - k1); + } + + n = k; + while ((w & 1) === 0) { + w >>= 1; + --n; + } + if ((i -= n) < 0) { + i += this.DB; + --j; + } + if (is1) { // ret == 1, don't bother squaring or multiplying it + g[w].copyTo(r); + is1 = false; + } + else { + while (n > 1) { + z.sqrTo(r, r2); + z.sqrTo(r2, r); + n -= 2; + } + if (n > 0) z.sqrTo(r, r2); else { + t = r; + r = r2; + r2 = t; + } + z.mulTo(r2, g[w], r); + } + + while (j >= 0 && (e[j] & (1 << i)) === 0) { + z.sqrTo(r, r2); + t = r; + r = r2; + r2 = t; + if (--i < 0) { + i = this.DB - 1; + --j; + } + } + } + return z.revert(r); +} + +//(public) gcd(this,a) (HAC 14.54) +function bnGCD(a) { + var x = (this.s < 0) ? this.negate() : this.clone(); + var y = (a.s < 0) ? a.negate() : a.clone(); + if (x.compareTo(y) < 0) { + var t = x; + x = y; + y = t; + } + var i = x.getLowestSetBit(), g = y.getLowestSetBit(); + if (g < 0) return x; + if (i < g) g = i; + if (g > 0) { + x.rShiftTo(g, x); + y.rShiftTo(g, y); + } + while (x.signum() > 0) { + if ((i = x.getLowestSetBit()) > 0) x.rShiftTo(i, x); + if ((i = y.getLowestSetBit()) > 0) y.rShiftTo(i, y); + if (x.compareTo(y) >= 0) { + x.subTo(y, x); + x.rShiftTo(1, x); + } + else { + y.subTo(x, y); + y.rShiftTo(1, y); + } + } + if (g > 0) y.lShiftTo(g, y); + return y; +} + +//(protected) this % n, n < 2^26 +function bnpModInt(n) { + if (n <= 0) return 0; + var d = this.DV % n, r = (this.s < 0) ? n - 1 : 0; + if (this.t > 0) + if (d === 0) r = this[0] % n; + else for (var i = this.t - 1; i >= 0; --i) r = (d * r + this[i]) % n; + return r; +} + +//(public) 1/this % m (HAC 14.61) +function bnModInverse(m) { + var ac = m.isEven(); + if ((this.isEven() && ac) || m.signum() === 0) return BigInteger.ZERO; + var u = m.clone(), v = this.clone(); + var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); + while (u.signum() != 0) { + while (u.isEven()) { + u.rShiftTo(1, u); + if (ac) { + if (!a.isEven() || !b.isEven()) { + a.addTo(this, a); + b.subTo(m, b); + } + a.rShiftTo(1, a); + } + else if (!b.isEven()) b.subTo(m, b); + b.rShiftTo(1, b); + } + while (v.isEven()) { + v.rShiftTo(1, v); + if (ac) { + if (!c.isEven() || !d.isEven()) { + c.addTo(this, c); + d.subTo(m, d); + } + c.rShiftTo(1, c); + } + else if (!d.isEven()) d.subTo(m, d); + d.rShiftTo(1, d); + } + if (u.compareTo(v) >= 0) { + u.subTo(v, u); + if (ac) a.subTo(c, a); + b.subTo(d, b); + } + else { + v.subTo(u, v); + if (ac) c.subTo(a, c); + d.subTo(b, d); + } + } + if (v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; + if (d.compareTo(m) >= 0) return d.subtract(m); + if (d.signum() < 0) d.addTo(m, d); else return d; + if (d.signum() < 0) return d.add(m); else return d; +} + +var lowprimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]; +var lplim = (1 << 26) / lowprimes[lowprimes.length - 1]; + +//(public) test primality with certainty >= 1-.5^t +function bnIsProbablePrime(t) { + var i, x = this.abs(); + if (x.t == 1 && x[0] <= lowprimes[lowprimes.length - 1]) { + for (i = 0; i < lowprimes.length; ++i) + if (x[0] == lowprimes[i]) return true; + return false; + } + if (x.isEven()) return false; + i = 1; + while (i < lowprimes.length) { + var m = lowprimes[i], j = i + 1; + while (j < lowprimes.length && m < lplim) m *= lowprimes[j++]; + m = x.modInt(m); + while (i < j) if (m % lowprimes[i++] === 0) return false; + } + return x.millerRabin(t); +} + +//(protected) true if probably prime (HAC 4.24, Miller-Rabin) +function bnpMillerRabin(t) { + var n1 = this.subtract(BigInteger.ONE); + var k = n1.getLowestSetBit(); + if (k <= 0) return false; + var r = n1.shiftRight(k); + t = (t + 1) >> 1; + if (t > lowprimes.length) t = lowprimes.length; + var a = nbi(); + for (var i = 0; i < t; ++i) { + //Pick bases at random, instead of starting at 2 + a.fromInt(lowprimes[Math.floor(Math.random() * lowprimes.length)]); + var y = a.modPow(r, this); + if (y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { + var j = 1; + while (j++ < k && y.compareTo(n1) != 0) { + y = y.modPowInt(2, this); + if (y.compareTo(BigInteger.ONE) === 0) return false; + } + if (y.compareTo(n1) != 0) return false; + } + } + return true; +} + +// protected +BigInteger.prototype.copyTo = bnpCopyTo; +BigInteger.prototype.fromInt = bnpFromInt; +BigInteger.prototype.fromString = bnpFromString; +BigInteger.prototype.fromByteArray = bnpFromByteArray; +BigInteger.prototype.fromBuffer = bnpFromBuffer; +BigInteger.prototype.clamp = bnpClamp; +BigInteger.prototype.dlShiftTo = bnpDLShiftTo; +BigInteger.prototype.drShiftTo = bnpDRShiftTo; +BigInteger.prototype.lShiftTo = bnpLShiftTo; +BigInteger.prototype.rShiftTo = bnpRShiftTo; +BigInteger.prototype.subTo = bnpSubTo; +BigInteger.prototype.multiplyTo = bnpMultiplyTo; +BigInteger.prototype.squareTo = bnpSquareTo; +BigInteger.prototype.divRemTo = bnpDivRemTo; +BigInteger.prototype.invDigit = bnpInvDigit; +BigInteger.prototype.isEven = bnpIsEven; +BigInteger.prototype.exp = bnpExp; + +BigInteger.prototype.chunkSize = bnpChunkSize; +BigInteger.prototype.toRadix = bnpToRadix; +BigInteger.prototype.fromRadix = bnpFromRadix; +BigInteger.prototype.fromNumber = bnpFromNumber; +BigInteger.prototype.bitwiseTo = bnpBitwiseTo; +BigInteger.prototype.changeBit = bnpChangeBit; +BigInteger.prototype.addTo = bnpAddTo; +BigInteger.prototype.dMultiply = bnpDMultiply; +BigInteger.prototype.dAddOffset = bnpDAddOffset; +BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; +BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; +BigInteger.prototype.modInt = bnpModInt; +BigInteger.prototype.millerRabin = bnpMillerRabin; + + +// public +BigInteger.prototype.toString = bnToString; +BigInteger.prototype.negate = bnNegate; +BigInteger.prototype.abs = bnAbs; +BigInteger.prototype.compareTo = bnCompareTo; +BigInteger.prototype.bitLength = bnBitLength; +BigInteger.prototype.mod = bnMod; +BigInteger.prototype.modPowInt = bnModPowInt; + +BigInteger.prototype.clone = bnClone; +BigInteger.prototype.intValue = bnIntValue; +BigInteger.prototype.byteValue = bnByteValue; +BigInteger.prototype.shortValue = bnShortValue; +BigInteger.prototype.signum = bnSigNum; +BigInteger.prototype.toByteArray = bnToByteArray; +BigInteger.prototype.toBuffer = bnToBuffer; +BigInteger.prototype.equals = bnEquals; +BigInteger.prototype.min = bnMin; +BigInteger.prototype.max = bnMax; +BigInteger.prototype.and = bnAnd; +BigInteger.prototype.or = bnOr; +BigInteger.prototype.xor = bnXor; +BigInteger.prototype.andNot = bnAndNot; +BigInteger.prototype.not = bnNot; +BigInteger.prototype.shiftLeft = bnShiftLeft; +BigInteger.prototype.shiftRight = bnShiftRight; +BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; +BigInteger.prototype.bitCount = bnBitCount; +BigInteger.prototype.testBit = bnTestBit; +BigInteger.prototype.setBit = bnSetBit; +BigInteger.prototype.clearBit = bnClearBit; +BigInteger.prototype.flipBit = bnFlipBit; +BigInteger.prototype.add = bnAdd; +BigInteger.prototype.subtract = bnSubtract; +BigInteger.prototype.multiply = bnMultiply; +BigInteger.prototype.divide = bnDivide; +BigInteger.prototype.remainder = bnRemainder; +BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; +BigInteger.prototype.modPow = bnModPow; +BigInteger.prototype.modInverse = bnModInverse; +BigInteger.prototype.pow = bnPow; +BigInteger.prototype.gcd = bnGCD; +BigInteger.prototype.isProbablePrime = bnIsProbablePrime; +BigInteger.int2char = int2char; + +// "constants" +BigInteger.ZERO = nbv(0); +BigInteger.ONE = nbv(1); + +// JSBN-specific extension +BigInteger.prototype.square = bnSquare; + +//BigInteger interfaces not implemented in jsbn: + +//BigInteger(int signum, byte[] magnitude) +//double doubleValue() +//float floatValue() +//int hashCode() +//long longValue() +//static BigInteger valueOf(long val) + +module.exports = BigInteger; diff --git a/lib/params.js b/lib/params.js new file mode 100644 index 0000000..7111a4a --- /dev/null +++ b/lib/params.js @@ -0,0 +1,182 @@ +/* + * SRP Group Parameters + * http://tools.ietf.org/html/rfc5054#appendix-A + * + * The 1024-, 1536-, and 2048-bit groups are taken from software + * developed by Tom Wu and Eugene Jhong for the Stanford SRP + * distribution, and subsequently proven to be prime. The larger primes + * are taken from [MODP], but generators have been calculated that are + * primitive roots of N, unlike the generators in [MODP]. + * + * The 1024-bit and 1536-bit groups MUST be supported. + */ + +// since these are meant to be used internally, all values are numbers. If +// you want to add parameter sets, you'll need to convert them to bignums. + +"use strict"; + +var BigInteger = require('./jsbn'); + + +function hex(s) { + return new BigInteger(s.split(/\s/).join(''), 16); +} + +module.exports = { + + 1024: { + N_length_bits: 1024, + N: hex(' EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B 9EA2314C' + +'9C256576 D674DF74 96EA81D3 383B4813 D692C6E0 E0D5D8E2 50B98BE4' + +'8E495C1D 6089DAD1 5DC7D7B4 6154D6B6 CE8EF4AD 69B15D49 82559B29' + +'7BCF1885 C529F566 660E57EC 68EDBC3C 05726CC0 2FD4CBF4 976EAA9A' + +'FD5138FE 8376435B 9FC61D2F C0EB06E3'), + g: hex('02'), + hash: 'sha1'}, + + 1536: { + N_length_bits: 1536, + N: hex(' 9DEF3CAF B939277A B1F12A86 17A47BBB DBA51DF4 99AC4C80 BEEEA961' + +'4B19CC4D 5F4F5F55 6E27CBDE 51C6A94B E4607A29 1558903B A0D0F843' + +'80B655BB 9A22E8DC DF028A7C EC67F0D0 8134B1C8 B9798914 9B609E0B' + +'E3BAB63D 47548381 DBC5B1FC 764E3F4B 53DD9DA1 158BFD3E 2B9C8CF5' + +'6EDF0195 39349627 DB2FD53D 24B7C486 65772E43 7D6C7F8C E442734A' + +'F7CCB7AE 837C264A E3A9BEB8 7F8A2FE9 B8B5292E 5A021FFF 5E91479E' + +'8CE7A28C 2442C6F3 15180F93 499A234D CF76E3FE D135F9BB'), + g: hex('02'), + hash: 'sha1'}, + + 2048: { + N_length_bits: 2048, + N: hex(' AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294' + +'3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D' + +'CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB' + +'D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74' + +'7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A' + +'436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D' + +'5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73' + +'03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6' + +'94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F' + +'9E4AFF73'), + g: hex('02'), + hash: 'sha256'}, + + 3072: { + N_length_bits: 3072, + N: hex(' FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08' + +'8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B' + +'302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9' + +'A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6' + +'49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8' + +'FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D' + +'670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C' + +'180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718' + +'3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D' + +'04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D' + +'B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226' + +'1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C' + +'BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC' + +'E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF'), + g: hex('05'), + hash: 'sha256'}, + + 4096: { + N_length_bits: 4096, + N: hex(' FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08' + +'8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B' + +'302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9' + +'A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6' + +'49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8' + +'FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D' + +'670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C' + +'180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718' + +'3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D' + +'04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D' + +'B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226' + +'1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C' + +'BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC' + +'E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26' + +'99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB' + +'04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2' + +'233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127' + +'D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199' + +'FFFFFFFF FFFFFFFF'), + g: hex('05'), + hash: 'sha256'}, + + 6244: { + N_length_bits: 6244, + N: hex(' FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08' + +'8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B' + +'302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9' + +'A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6' + +'49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8' + +'FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D' + +'670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C' + +'180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718' + +'3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D' + +'04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D' + +'B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226' + +'1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C' + +'BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC' + +'E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26' + +'99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB' + +'04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2' + +'233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127' + +'D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492' + +'36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406' + +'AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918' + +'DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151' + +'2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03' + +'F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F' + +'BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA' + +'CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B' + +'B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632' + +'387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E' + +'6DCC4024 FFFFFFFF FFFFFFFF'), + g: hex('05'), + hash: 'sha256'}, + + 8192: { + N_length_bits: 8192, + N: hex(' FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08' + +'8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B' + +'302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9' + +'A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6' + +'49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8' + +'FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D' + +'670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C' + +'180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718' + +'3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D' + +'04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D' + +'B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226' + +'1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C' + +'BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC' + +'E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26' + +'99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB' + +'04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2' + +'233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127' + +'D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492' + +'36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406' + +'AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918' + +'DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151' + +'2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03' + +'F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F' + +'BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA' + +'CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B' + +'B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632' + +'387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E' + +'6DBE1159 74A3926F 12FEE5E4 38777CB6 A932DF8C D8BEC4D0 73B931BA' + +'3BC832B6 8D9DD300 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C' + +'5AE4F568 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9' + +'22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B 4BCBC886' + +'2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A 062B3CF5 B3A278A6' + +'6D2A13F8 3F44F82D DF310EE0 74AB6A36 4597E899 A0255DC1 64F31CC5' + +'0846851D F9AB4819 5DED7EA1 B1D510BD 7EE74D73 FAF36BC3 1ECFA268' + +'359046F4 EB879F92 4009438B 481C6CD7 889A002E D5EE382B C9190DA6' + +'FC026E47 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71' + +'60C980DD 98EDD3DF FFFFFFFF FFFFFFFF'), + g: hex('13'), + hash: 'sha256'} +}; diff --git a/lib/srp.js b/lib/srp.js new file mode 100644 index 0000000..616ca0b --- /dev/null +++ b/lib/srp.js @@ -0,0 +1,456 @@ +"use strict"; + +var crypto = require('crypto'); +var assert = require('assert'); +var util = require('util'); +var BigInteger = require('./jsbn'); + +var zero = new BigInteger(0); + +function assert_(val, msg) { + if (!val) + throw new Error(msg||"assertion"); +} + +/* + * If a conversion is explicitly specified with the operator PAD(), + * the integer will first be implicitly converted, then the resultant + * byte-string will be left-padded with zeros (if necessary) until its + * length equals the implicitly-converted length of N. + * + * params: + * n (buffer) Number to pad + * len (int) length of the resulting Buffer + * + * returns: buffer + */ +function padTo(n, len) { + assertIsBuffer(n, "n"); + var padding = len - n.length; +/* + console.log("n = " + n.toString('hex')); + console.log("n.length = " + n.length); + console.log("len = " + len); +*/ + assert_(padding > -1, "Negative padding. Very uncomfortable."); + var result = new Buffer(len); + result.fill(0, 0, padding); + n.copy(result, padding); + assert.equal(result.length, len); + return result; +}; + +function padToN(number, params) { + assertIsBigInteger(number); +//! return padTo(number.toBuffer(), params.N_length_bits/8); + var n = number.toString(16).length % 2 != 0 ? "0" + number.toString(16) : number.toString(16); + return padTo(new Buffer(n, 'hex'), params.N_length_bits / 8); +} + +function padToH(number, params) { + assertIsBigInteger(number); + var hashlen_bits; + if (params.hash === "sha1") + hashlen_bits = 160; + else if (params.hash === "sha256") + hashlen_bits = 256; + else if (params.hash === "sha512") + hashlen_bits = 512; + else + throw Error("cannot determine length of hash '"+params.hash+"'"); + +//! return padTo(number.toBuffer(), hashlen_bits / 8); + return padTo(new Buffer(number.toString(16), 'hex'), hashlen_bits / 8); +} + +function assertIsBuffer(arg, argname) { + argname = argname || "arg"; + assert_(Buffer.isBuffer(arg), "Type error: "+argname+" must be a buffer"); +} + +function assertIsNBuffer(arg, params, argname) { + argname = argname || "arg"; + assert_(Buffer.isBuffer(arg), "Type error: "+argname+" must be a buffer"); + if (arg.length != params.N_length_bits/8) + assert_(false, argname+" was "+arg.length+", expected "+(params.N_length_bits/8)); +} + +function assertIsBigInteger(arg) { + assert_(arg.constructor.name === 'BigInteger', "Type error: " + arg.argname + " must be a BigInteger"); +} + +/* + * compute the intermediate value x as a hash of three buffers: + * salt, identity, and password. And a colon. FOUR buffers. + * + * x = H(s | H(I | ":" | P)) + * + * params: + * salt (buffer) salt + * I (buffer) user identity + * P (buffer) user password + * + * returns: x (bignum) user secret + */ +function getx(params, salt, I, P) { + assertIsBuffer(salt, "salt (salt)"); + assertIsBuffer(I, "identity (I)"); + assertIsBuffer(P, "password (P)"); + var hashIP = crypto.createHash(params.hash) + .update(Buffer.concat([I, new Buffer(':'), P])) + .digest(); + var hashX = crypto.createHash(params.hash) + .update(salt) + .update(hashIP) + .digest(); +//! return bignum.fromBuffer(hashX); + return(new BigInteger(hashX)); +}; + +/* + * The verifier is calculated as described in Section 3 of [SRP-RFC]. + * We give the algorithm here for convenience. + * + * The verifier (v) is computed based on the salt (s), user name (I), + * password (P), and group parameters (N, g). + * + * x = H(s | H(I | ":" | P)) + * v = g^x % N + * + * params: + * params (obj) group parameters, with .N, .g, .hash + * salt (buffer) salt + * I (buffer) user identity + * P (buffer) user password + * + * returns: buffer + */ +function computeVerifier(params, salt, I, P) { + assertIsBuffer(salt, "salt (salt)"); + assertIsBuffer(I, "identity (I)"); + assertIsBuffer(P, "password (P)"); +//* var v_num = params.g.powm(getx(params, salt, I, P), params.N); + var v_num = params.g.modPow(getx(params, salt, I, P), params.N); + return padToN(v_num, params); +}; + +/* + * calculate the SRP-6 multiplier + * + * params: + * params (obj) group parameters, with .N, .g, .hash + * + * returns: bignum + */ +function getk(params) { + var k_buf = crypto + .createHash(params.hash) + .update(padToN(params.N, params)) + .update(padToN(params.g, params)) + .digest(); + return(new BigInteger(k_buf.toString('hex'), 16)); +}; + +/* + * Generate a random key + * + * params: + * bytes (int) length of key (default=32) + * callback (func) function to call with err,key + * + * returns: nothing, but runs callback with a Buffer + */ +function genKey(bytes, callback) { + // bytes is optional + if (arguments.length < 2) { + callback = bytes; + bytes = 32; + } + if (typeof callback !== 'function') { + throw("Callback required"); + } + crypto.randomBytes(bytes, function(err, buf) { + if (err) return callback (err); + return callback(null, buf); + }); +}; + +/* + * The server key exchange message also contains the server's public + * value (B). The server calculates this value as B = k*v + g^b % N, + * where b is a random number that SHOULD be at least 256 bits in length + * and k = H(N | PAD(g)). + * + * Note: as the tests imply, the entire expression is mod N. + * + * params: + * params (obj) group parameters, with .N, .g, .hash + * v (bignum) verifier (stored) + * b (bignum) server secret exponent + * + * returns: B (buffer) the server public message + */ +function getB(params, k, v, b) { + assertIsBigInteger(v); + assertIsBigInteger(k); + assertIsBigInteger(b); + var N = params.N; +//* var r = k.mul(v).add(params.g.powm(b, N)).mod(N); + var r = k.multiply(v).add(params.g.modPow(b, N)).mod(N); + return padToN(r, params); +}; + +/* + * The client key exchange message carries the client's public value + * (A). The client calculates this value as A = g^a % N, where a is a + * random number that SHOULD be at least 256 bits in length. + * + * Note: for this implementation, we take that to mean 256/8 bytes. + * + * params: + * params (obj) group parameters, with .N, .g, .hash + * a (bignum) client secret exponent + * + * returns A (bignum) the client public message + */ +function getA(params, a_num) { + assertIsBigInteger(a_num); +//! if (Math.ceil(a_num.bitLength() / 8) < 256/8) { + if (Math.ceil(a_num.toString(16).length / 2) < 32) { + console.warn("getA: client key length", a_num.bitLength(), "is less than the recommended 256 bits"); + } +//* return padToN(params.g.powm(a_num, params.N), params); + return padToN(params.g.modPow(a_num, params.N), params); +}; + +/* + * getu() hashes the two public messages together, to obtain a scrambling + * parameter "u" which cannot be predicted by either party ahead of time. + * This makes it safe to use the message ordering defined in the SRP-6a + * paper, in which the server reveals their "B" value before the client + * commits to their "A" value. + * + * params: + * params (obj) group parameters, with .N, .g, .hash + * A (Buffer) client ephemeral public key + * B (Buffer) server ephemeral public key + * + * returns: u (bignum) shared scrambling parameter + */ +function getu(params, A, B) { + assertIsNBuffer(A, params, "A"); + assertIsNBuffer(B, params, "B"); + var u_buf = crypto.createHash(params.hash) + .update(A).update(B) + .digest(); +//! return bignum.fromBuffer(u_buf); + return(new BigInteger(u_buf.toString('hex'), 16)); +}; + +/* + * The TLS premaster secret as calculated by the client + * + * params: + * params (obj) group parameters, with .N, .g, .hash + * salt (buffer) salt (read from server) + * I (buffer) user identity (read from user) + * P (buffer) user password (read from user) + * a (bignum) ephemeral private key (generated for session) + * B (bignum) server ephemeral public key (read from server) + * + * returns: buffer + */ + +function client_getS(params, k_num, x_num, a_num, B_num, u_num) { + assertIsBigInteger(k_num); + assertIsBigInteger(x_num); + assertIsBigInteger(a_num); + assertIsBigInteger(B_num); + assertIsBigInteger(u_num); + var g = params.g; + var N = params.N; +// if (zero.greater(B_num) || N.lesser(B_num)) + if((zero.compareTo(B_num) > 0) && (N.compareTo(B_num) < 0)) + throw new Error("invalid server-supplied 'B', must be 1..N-1"); +//* var S_num = B_num.sub(k_num.mul(g.powm(x_num, N))).powm(a_num.add(u_num.mul(x_num)), N).mod(N); + var S_num = B_num.subtract(k_num.multiply(g.modPow(x_num, N))).modPow(a_num.add(u_num.multiply(x_num)), N).mod(N); + + return padToN(S_num, params); +}; + +/* + * The TLS premastersecret as calculated by the server + * + * params: + * params (obj) group parameters, with .N, .g, .hash + * v (bignum) verifier (stored on server) + * A (bignum) ephemeral client public key (read from client) + * b (bignum) server ephemeral private key (generated for session) + * + * returns: bignum + */ + +function server_getS(params, v_num, A_num, b_num, u_num) { + assertIsBigInteger(v_num); + assertIsBigInteger(A_num); + assertIsBigInteger(b_num); + assertIsBigInteger(u_num); + var N = params.N; +//! if (zero.greater(A_num) || N.lesser(A_num)) + if((zero.compareTo(A_num) > 0) && (N.compareTo(A_num) < 0)) + throw new Error("invalid client-supplied 'A', must be 1..N-1"); +//* var S_num = A_num.mul(v_num.powm(u_num, N)).powm(b_num, N).mod(N); + var S_num = A_num.multiply(v_num.modPow(u_num, N)).modPow(b_num, N).mod(N); + return padToN(S_num, params); +}; + +/* + * Compute the shared session key K from S + * + * params: + * params (obj) group parameters, with .N, .g, .hash + * S (buffer) Session key + * + * returns: buffer + */ +function getK(params, S_buf) { + assertIsNBuffer(S_buf, params, "S"); + return crypto.createHash(params.hash) + .update(S_buf) + .digest(); +}; + +function getM1(params, A_buf, B_buf, S_buf) { + assertIsNBuffer(A_buf, params, "A"); + assertIsNBuffer(B_buf, params, "B"); + assertIsNBuffer(S_buf, params, "S"); + return crypto.createHash(params.hash) + .update(A_buf).update(B_buf).update(S_buf) + .digest(); +} + +function getM2(params, A_buf, M_buf, K_buf) { + assertIsNBuffer(A_buf, params, "A"); + assertIsBuffer(M_buf, "M"); + assertIsBuffer(K_buf, "K"); + return crypto.createHash(params.hash) + .update(A_buf).update(M_buf).update(K_buf) + .digest(); +} + +function equal(buf1, buf2) { + // constant-time comparison. A drop in the ocean compared to our + // non-constant-time modexp operations, but still good practice. + var mismatch = buf1.length - buf2.length; + if (mismatch) { + return false; + } + for (var i = 0; i < buf1.length; i++) { + mismatch |= buf1[i] ^ buf2[i]; + } + return mismatch === 0; +} + +function Client(params, salt_buf, identity_buf, password_buf, secret1_buf) { + if (!(this instanceof Client)) { + return new Client(params, salt_buf, identity_buf, password_buf, secret1_buf); + } + assertIsBuffer(salt_buf, "salt (salt)"); + assertIsBuffer(identity_buf, "identity (I)"); + assertIsBuffer(password_buf, "password (P)"); + assertIsBuffer(secret1_buf, "secret1"); + this._private = { params: params, + k_num: getk(params), + x_num: getx(params, salt_buf, identity_buf, password_buf), +//! a_num: bignum.fromBuffer(secret1_buf) }; + a_num: new BigInteger(secret1_buf.toString('hex'), 16) }; + this._private.A_buf = getA(params, this._private.a_num); +} + +Client.prototype = { + computeA: function computeA() { + return this._private.A_buf; + }, + setB: function setB(B_buf) { + var p = this._private; +//! var B_num = bignum.fromBuffer(B_buf); + var B_num = new BigInteger(B_buf.toString('hex'), 16); + var u_num = getu(p.params, p.A_buf, B_buf); +// console.log(util.inspect(p)); + var S_buf_x = client_getS(p.params, p.k_num, p.x_num, p.a_num, B_num, u_num); + p.K_buf = getK(p.params, S_buf_x); + p.M1_buf = getM1(p.params, p.A_buf, B_buf, S_buf_x); + p.M2_buf = getM2(p.params, p.A_buf, p.M1_buf, p.K_buf); + p.u_num = u_num; // only for tests + p.S_buf = S_buf_x; // only for tests + }, + computeM1: function computeM1() { + if (this._private.M1_buf === undefined) + throw new Error("incomplete protocol"); + return this._private.M1_buf; + }, + checkM2: function checkM2(serverM2_buf) { + if (!equal(this._private.M2_buf, serverM2_buf)) + throw new Error("server is not authentic"); + }, + computeK: function computeK() { + if (this._private.K_buf === undefined) + throw new Error("incomplete protocol"); + return this._private.K_buf; + } +}; + +function Server(params, verifier_buf, secret2_buf) { + if (!(this instanceof Server)) { + return new Server(params, verifier_buf, secret2_buf); + } + assertIsBuffer(verifier_buf, "verifier"); + assertIsBuffer(secret2_buf, "secret2"); + this._private = { params: params, + k_num: getk(params), +//! b_num: bignum.fromBuffer(secret2_buf), +//! v_num: bignum.fromBuffer(verifier_buf) }; + b_num: new BigInteger(secret2_buf.toString('hex'), 16), + v_num: new BigInteger(verifier_buf.toString('hex'), 16) }; + + this._private.B_buf = getB(params, this._private.k_num, + this._private.v_num, this._private.b_num); +} + +Server.prototype = { + computeB: function computeB() { + return this._private.B_buf; + }, + setA: function setA(A_buf) { + var p = this._private; +//! var A_num = bignum.fromBuffer(A_buf); + var A_num = new BigInteger(A_buf.toString('hex'), 16); + var u_num = getu(p.params, A_buf, p.B_buf); + var S_buf = server_getS(p.params, p.v_num, A_num, p.b_num, u_num); + p.K_buf = getK(p.params, S_buf); + p.M1_buf = getM1(p.params, A_buf, p.B_buf, S_buf); + p.M2_buf = getM2(p.params, A_buf, p.M1_buf, p.K_buf); + p.u_num = u_num; // only for tests + p.S_buf = S_buf; // only for tests + }, + checkM1: function checkM1(clientM1_buf) { + if (this._private.M1_buf === undefined) + throw new Error("incomplete protocol"); + if (!equal(this._private.M1_buf, clientM1_buf)) + throw new Error("client did not use the same password"); + return this._private.M2_buf; + }, + computeK: function computeK() { + if (this._private.K_buf === undefined) + throw new Error("incomplete protocol"); + return this._private.K_buf; + } +}; + +module.exports = { + params: require('./params'), + genKey: genKey, + computeVerifier: computeVerifier, + Client: Client, + Server: Server +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..13fc2a7 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "fast-srp", + "description": "Secure Remote Password (SRP)", + "version": "0.9.0", + "main": "index.js", + "scripts": { + "test": "vows test/test_*.js --spec" + }, + "repository": { + "type": "git", + "url": "https://github.com/zarmack/fast-srp" + }, + "author": "Zarmack Tanen", + "licence": "MIT", + "readmeFilename": "README.md", + "dependencies": { + "lodash": "^3.10.1" + }, + "devDependencies": { + "vows": "0.7.0" + } +} diff --git a/test/test_picl_vectors.js b/test/test_picl_vectors.js new file mode 100644 index 0000000..829b784 --- /dev/null +++ b/test/test_picl_vectors.js @@ -0,0 +1,313 @@ +"use strict"; + +var vows = require('vows'); +var assert = require('assert'); +var BigInteger = require('../lib/jsbn'); +var srp = require('../lib/srp'); +var util = require('util'); + +/* + * Vectors from https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol + * + * Verify that we are inter-compatible with the SRP implementation used by + * Mozilla's Identity-Attached Services, aka PiCl (Profile in the Cloud). + * + * Note that P is the HKDF-stretched key, computed elsewhere. + */ + +function join(s) { + return s.split(/\s/).join(''); +} + +function h(s) { + return new Buffer(join(s), 'hex'); +} + +var params = srp.params['2048']; + +/* inputs_1/expected_1 are the main PiCl test vectors. They were mechanically + * generated to force certain derived values (stretched-password "P", v, A, + * B, and S) to begin with a 0x00 byte (to exercise padding bugs). + */ + +var inputs_1 = { + I: new Buffer('andré@example.org', 'utf8'), + P: h('00f9b71800ab5337 d51177d8fbc682a3 653fa6dae5b87628 eeec43a18af59a9d'), + salt: h('00f1000000000000000000000000000000000000000000000000000000000179'), + // a and b are usually random. For testing, we force them to specific values. + a: h(' 00f2000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 000000000000d3d7' + ), + b: h(' 00f3000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 000000000000000f' + ) +}; + +var expected_1 = { + // 'k' encodes the group (N and g), used in SRP-6a + k: h('05b9e8ef059c6b32 ea59fc1d322d37f0 4aa30bae5aa9003b 8321e21ddb04e300'), + // 'x' is derived from the salt and password + // 'v' is the SRP verifier + x: h('b5200337cc3f3f92 6cdddae0b2d31029 c069936a844aff58 779a545be89d0abe'), + v: h(' 00173ffa0263e63c cfd6791b8ee2a40f 048ec94cd95aa8a3 125726f9805e0c82' + +'83c658dc0b607fbb 25db68e68e93f265 8483049c68af7e82 14c49fde2712a775' + +'b63e545160d64b00 189a86708c69657d a7a1678eda0cd79f 86b8560ebdb1ffc2' + +'21db360eab901d64 3a75bf1205070a57 91230ae56466b8c3 c1eb656e19b794f1' + +'ea0d2a077b3a7553 50208ea0118fec8c 4b2ec344a05c66ae 1449b32609ca7189' + +'451c259d65bd15b3 4d8729afdb5faff8 af1f3437bbdc0c3d 0b069a8ab2a959c9' + +'0c5a43d42082c774 90f3afcc10ef5648 625c0605cdaace6c 6fdc9e9a7e6635d6' + +'19f50af773452247 0502cab26a52a198 f5b00a2798589165 07b0b4e9ef9524d6'), + // 'B' is the server's public message + B: h(' 0022ce5a7b9d8127 7172caa20b0f1efb 4643b3becc535664 73959b07b790d3c3' + +'f08650d5531c19ad 30ebb67bdb481d1d 9cf61bf272f84398 48fdda58a4e6abc5' + +'abb2ac496da5098d 5cbf90e29b4b110e 4e2c033c70af7392 5fa37457ee13ea3e' + +'8fde4ab516dff1c2 ae8e57a6b264fb9d b637eeeae9b5e43d faba9b329d3b8770' + +'ce89888709e02627 0e474eef822436e6 397562f284778673 a1a7bc12b6883d1c' + +'21fbc27ffb3dbeb8 5efda279a69a1941 4969113f10451603 065f0a0126666456' + +'51dde44a52f4d8de 113e2131321df1bf 4369d2585364f9e5 36c39a4dce33221b' + +'e57d50ddccb4384e 3612bbfd03a268a3 6e4f7e01de651401 e108cc247db50392'), + // 'A' is the client's public message + A: h(' 007da76cb7e77af5 ab61f334dbd5a958 513afcdf0f47ab99 271fc5f7860fe213' + +'2e5802ca79d2e5c0 64bb80a38ee08771 c98a937696698d87 8d78571568c98a1c' + +'40cc6e7cb101988a 2f9ba3d65679027d 4d9068cb8aad6ebf f0101bab6d52b5fd' + +'fa81d2ed48bba119 d4ecdb7f3f478bd2 36d5749f2275e948 4f2d0a9259d05e49' + +'d78a23dd26c60bfb a04fd346e5146469 a8c3f010a627be81 c58ded1caaef2363' + +'635a45f97ca0d895 cc92ace1d09a99d6 beb6b0dc0829535c 857a419e834db128' + +'64cd6ee8a843563b 0240520ff0195735 cd9d316842d5d3f8 ef7209a0bb4b54ad' + +'7374d73e79be2c39 75632de562c59647 0bb27bad79c3e2fc ddf194e1666cb9fc'), + // 'u' combines the two public messages + u: h('b284aa1064e87751 50da6b5e2147b47c a7df505bed94a6f4 bb2ad873332ad732'), + // 'S' is the shared secret + S: h(' 0092aaf0f527906a a5e8601f5d707907 a03137e1b601e04b 5a1deb02a981f4be' + +'037b39829a27dba5 0f1b27545ff2e287 29c2b79dcbdd32c9 d6b20d340affab91' + +'a626a8075806c26f e39df91d0ad979f9 b2ee8aad1bc783e7 097407b63bfe58d9' + +'118b9b0b2a7c5c4c debaf8e9a460f4bf 6247b0da34b760a5 9fac891757ddedca' + +'f08eed823b090586 c63009b2d740cc9f 5397be89a2c32cdc fe6d6251ce11e44e' + +'6ecbdd9b6d93f30e 90896d2527564c7e b9ff70aa91acc0ba c1740a11cd184ffb' + +'989554ab58117c21 96b353d70c356160 100ef5f4c28d19f6 e59ea2508e8e8aac' + +'6001497c27f362ed bafb25e0f045bfdf 9fb02db9c908f103 40a639fe84c31b27'), + // 'K' is the shared derived key + K: h('e68fd0112bfa31dc ffc8e9c96a1cbadb 4c3145978ff35c73 e5bf8d30bbc7499a'), + // 'M1' is the client's proof that it knows the shared key + M1: h('27949ec1e0f16256 33436865edb037e2 3eb6bf5cb91873f2 a2729373c2039008') +}; + +/* inputs_2/expected_2 have leading 0x00 bytes in 'x' and 'u' */ +var inputs_2 = { + I: inputs_1.I, P: inputs_1.P, + salt: h('00f1000000000000000000000000000000000000000000000000000000000021'), + a: h(' 00f2000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 000000000000000d' + ), + b: h(' 00f3000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000001' + ) +}; +var expected_2 = { + k: expected_1.k, + x: h('009b2740fb49284d 69cab7c916d449ee d7dcabf41332b8b8 d6928f529bd1a94e'), + v: h(' 1cd8b856685672ee 7a5895d897121234 6c17c3472f2696e4 8cdeec5533c06693' + +'179bc24802b762bc c1e1f8fc8abe607a f2f44aac9172e7dd 0c0110e45cf3b700' + +'f8db153b67fb0e76 3c6710b8c1c26baf c3b67a50652ee0d7 c6045a5c4b51ff33' + +'d0135065dca5d6bb 7e150e07414bd572 a954471059c1b466 d0530b0a80bd2d0c' + +'f1bedf5abfc05c3c f2736ac40b083dcf 62271e834042ecb0 d4882ddd35403c1e' + +'d24bc4ffe274c5f6 be50ec9b85aa0cfa 26d97e086ec45e06 3c29174d3dbe5490' + +'1d2a557b7eb46b18 9e17cc721fc098a0 baee2f364a2b409d 49d9372a9625db11' + +'acfd74ba7f41285f 9c1916d3caaf5238 852694bbde2a13f7 8fcc92d16658dd04'), + // 'B' is the server's public message + B: h(' 485d56912c60d9c1 7af15494d4d50006 45eefa2d41f6bcb5 785e08efad0833a1' + +'3cb43ee3869e78d4 c2006f42b9741782 a85c90a110cc9a74 4fc2a361d5535966' + +'2dc5fa4a8d0c7c0e 63e0cf32a28af655 863dd5d66f550557 eacd3e3e64d90f9f' + +'0d757403c9bbfb08 fcc9a35e1cb421d7 3bb93fa72d5b54ed bfa219d3867255ba' + +'f96223eef038f085 722b2d14457a5a13 1857a56e66d3011b b5aa7504c4b9a346' + +'8d0ebdd817d20105 be06ba261ea16740 723faa097f27ddc2 efe34cf8fe59451a' + +'5bb3987d7161085f b8fc28d5cc28c466 6a3ca486ad0ca83d 1984248ac838574e' + +'348fb9745ffd1163 f53b5566768a8971 237065d8f6e786be e15107125fb10df1'), + // 'A' is the client's public message + A: h(' a4b17836b1e7d6f1 5b9901f644bcdf5e 119e7a861c6ee88d 006d8420a5066f22' + +'d9bf5ccf3d380437 0d29d778ec40afcf c88de7bf22ec03fc 6ab12e0dd95d15e3' + +'a6249c94393435b0 0d23b1b0439dabed cce1726b2b3cdea2 647c8790d604d87d' + +'2ac890cfceec0dbe 434f09a9bc11d984 a1e1990f69956ae0 db6068992ad1715f' + +'b4381516da83637a 73de4211908c8f2f f8b3a09e8535acf3 c2b8de4e9a632f89' + +'9bfa08cee543b4ea 50d0aca0b3e4fbfa e49ffa2a1ab89a42 8bea928868828501' + +'2e8af13fcdd444ad da9ad1d0ab4c2069 91919e5391bd2b1a ab8c2d006baceaf8' + +'cdcb555a6b16e844 5b03e09776eba841 7576dac458afbbd5 2902dfb0282bed79'), + // 'u' combines the two public messages + u: h('000e4039be3989ad 088dc17d8ade899a 6409e7e57b3e8518 cee1cbc77e1de243'), + // 'S' is the shared secret + S: h(' 5c7f591d134d19f9 fcedc2b4e3eecd3d 5deadfe7dd42bd59 b1c960516c65ab61' + +'d007f8134e0a7ca3 0dd409128ef2c780 6784afd95985c8f3 c2d42cd73d26d315' + +'541645d28aefabc9 980c9a6e5714b178 aa69e5321828ca00 f3d10d742776cfe4' + +'4b7f5f5c0247addc 0ab0640b49b540ff 9bccea8702e1f996 49448680c00fb484' + +'51919224d44236ba 1b1e5cf62a5946bd 637f189ff7b8eba9 7b719f18ad9251f0' + +'a81c157604065388 d7bf4abbf774bfb2 d7b95ed8359b0d70 6ff5df0223992c81' + +'4aac506e1bace002 d134ed5e41d74f93 a8f410dfe7dc5954 f70b6bafcd0ddfde' + +'e75f0058f718ec14 f9bbeb29ff966e00 ddfdd2d38a1c7a68 ac455a57b972d528'), + // 'K' is the shared derived key + K: h('b637ede0b7a31c46 b2567e855eb8a7f7 a994937deee76479 62afbe35d6929709'), + // 'M1' is the client's proof that it knows the shared key + M1: h('67c83797eb1a3987 e2d48d287e3bd772 d25db2b3cd86ea22 c8cf3ae932a1e45b') +}; + +/* inputs_3/expected_3 have leading 0x00 bytes in 'x' and 'K' */ +var inputs_3 = { + I: inputs_2.I, P: inputs_2.P, + salt: h('00f1000000000000000000000000000000000000000000000000000000000021'), + a: h(' 00f2000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 00000000000001a0' + ), + b: inputs_2.b +}; +var expected_3 = { + k: expected_2.k, + x: expected_2.x, + v: expected_2.v, + B: expected_2.B, + A: h(' 87b6da9e4162843b 4d5ee60c403ae3e1 e9fdab64883f13ab 4a44b0718a9ea1b6' + +'1ad17c675e0f0395 b37d58a046a2d5ab 1fb665a9777abe80 8077ccf6fd8ec583' + +'854eab98deb257d9 10e5bf5cafed4955 2a5cd9927c0979f7 5a21654644000173' + +'aef6f2244296439c 10b3c61a03e7146e f6c9c9564b1d2bf5 1ece84d115965f9c' + +'c82006bdb7a124da 3304bcc24c8f3724 522b748fb19a0cb6 b60e355acbf649b5' + +'40b4972e24077c29 32004a3ad9e59464 2e90a3bfc8de7085 f4a4efc195bd06c9' + +'6c7011f3c979eaab 469f06465a5b7239 afaee535aedc5bd2 1a220546e0e6b70b' + +'5b6f54db3fea46d5 7ebc7fe46156d793 c59e6290d3cf9bc2 4316528da34f4640'), + u: h('865d0efca6cf17d6 f489e129231f1a48 b20c83ec6581d11f 3a2fa48ea93cd305'), + S: h(' 0ae26456e1a0dec1 ce162fb2e5bc7300 3c285e17c0b44f03 7ebbc57f8020ceae' + +'5d10a9e6e44eab2a 6915b582ab5f6e7d 16002ce05e524015 e9bc7c56d5131da4' + +'d4c4d7c3debaffcd b60e58468bd2c0da 5de95855480190a3 5258c79032001882' + +'3d836ca91848c5b6 3ca4265c3329eb44 161af9ce64cf4468 ef0eb88a788a0d07' + +'52a69821278c94ae 7193161b5c638b55 bf732e2a5996ccc5 16335f9f3d00dfa9' + +'8ac1b1e4971c5417 d34eba1e2a90ed60 a07d1d8be5b9d773 d8f2cb03bfb75994' + +'249f7734081aa42d 58dd54f8f725b245 175cf7d102e1086c eba4cfe7e49a2d27' + +'ffd6aef7549d402f bfcea78b4f3398ac 9ab1ee199f70acb6 4d2a17e159ff500d'), + K: h('00217598a4008956 4b17196bd43422d6 03a0a88a545b61b3 98c42c9cbcc1d1b3'), + M1: h('96d815ecece1dff4 254cd77517b37b97 65e741c1a57169ab af538e867444ec7f') +}; + +/* inputs_4/expected_4 have leading 0x00 bytes in 'x' and 'M1' */ +var inputs_4 = { + I: inputs_2.I, P: inputs_2.P, + salt: h('00f1000000000000000000000000000000000000000000000000000000000021'), + a: h(' 00f2000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000000' + +'0000000000000000 0000000000000000 0000000000000000 0000000000000190' + ), + b: inputs_2.b +}; +var expected_4 = { + k: expected_2.k, + x: expected_2.x, + v: expected_2.v, + B: expected_2.B, + A: h(' 4aee66beefb92d12 c8e341814809afcd 9ce083c11abcda70 0c03d5379c429cb9' + +'acbde6bb42a628f3 7a2536c864c40f74 f48a9d9356029a8b fe0e10cb9cf5a8a4' + +'2e591841f426d281 edf7c9b04112d8ef bf73f9768a4faace ddd351d3e9380bf1' + +'dcd0590c7ab50a95 bd23e9617e303bea 6f8fbe8a657b6417 4b60cdf5c059ba67' + +'1b6735324ae0c30a e7f3e361de8f273c af7b2513fa048ed1 0106c66ce460c5cc' + +'78544c790f5ffcce 378b79d5f02ec361 3a457b03fa0cc39c 80d6fdd645e24f65' + +'c690f9478d5b331d c00eef68670edbf3 629fd1a6c85267d2 cbb90f1670e7ba09' + +'cf2b5a9b00be8e11 f33e47a1c1f04eca f35bccb61af1116e 4d0f9d475017bad2'), + u: h('d0913eb75b61e15a 87756ffa04d4f967 e492bd0b330a2b11 fe8976aada2bb1ee'), + S: h(' 7ba3ce4a3d236b95 3c2d0fee42195c85 081664a44f55b82d a3abf66ac68bdbd7' + +'ad82d5ad95090782 5241fb706de8fc58 0a29e4579fbbedf3 0bec0138b3f76e06' + +'f9c86b16ad673890 3003ce8c86cb14ea 552db904a20970a9 7d9258a768087d30' + +'47a6e77520d32968 de3f64e94cd8c463 92c13e194194745c 8e53a9bb15a79473' + +'2a645068970fcdd9 a7c98b4aec19773a 5196802c2e932e71 d3a4a340e6f4fe16' + +'9e7ccc687f7246fe 20edeaf88d1125da c812751317f7213c d84f9efe2313d701' + +'d4a9bf0242bfe703 26fc19b68c90e83b 59b5cc21886ab602 f8bfa16fb50c3147' + +'9aad5e31698abf67 863b7ca6b6ac25a7 09a24d8f94c80bbf 691e38c81beb3c72'), + K: h('bd2a167a93b8496e 68c7e24b37956924 672eb8249d25c281 13984912d5cf27a6'), + M1: h('00cef66a047d506c bf941c236218e583 5343534ae08cf0cd 0fb7980bed242e05') +}; + + +function hexequal(a, b, msg) { + assert.equal(a.length, b.length, msg); + assert.equal(a.toString('hex'), b.toString('hex'), msg); +} + +function numequal(a, b, msg) { + assert(a.compareTo(b) == 0, msg); +} + +function checkVectors(params, inputs, expected) { + hexequal(inputs.I, new Buffer('616e6472c3a9406578616d706c652e6f7267', "hex"), "I"); + hexequal(srp.computeVerifier(params, inputs.salt, inputs.I, inputs.P), expected.v, "v"); + + var client = new srp.Client(params, inputs.salt, inputs.I, inputs.P, inputs.a); + var server = new srp.Server(params, expected.v, inputs.b); + +//! numequal(client._private.k_num, bignum.fromBuffer(expected.k), "k"); +//! numequal(client._private.x_num, bignum.fromBuffer(expected.x), "x"); +// console.log("k_num = " + client._private.k_num.toString(16) + ", expected k = " + bigInt(expected.k.toString('hex'), 16).toString(16)); + numequal(client._private.k_num, new BigInteger(expected.k.toString('hex'), 16), "k"); + numequal(client._private.x_num, new BigInteger(expected.x.toString('hex'), 16), "x"); + hexequal(client.computeA(), expected.A); + hexequal(server.computeB(), expected.B); + + assert.throws(function() {client.computeM1();}, /incomplete protocol/); + assert.throws(function() {client.computeK();}, /incomplete protocol/); + assert.throws(function() {server.checkM1(expected.M1);}, /incomplete protocol/); + assert.throws(function() {server.computeK();}, /incomplete protocol/); + + client.setB(expected.B); + + numequal(client._private.u_num, new BigInteger(expected.u.toString('hex'), 16)); + hexequal(client._private.S_buf, expected.S); + hexequal(client.computeM1(), expected.M1); + hexequal(client.computeK(), expected.K); + + server.setA(expected.A); + numequal(server._private.u_num, new BigInteger(expected.u.toString('hex'), 16)); + hexequal(server._private.S_buf, expected.S); + assert.throws(function() {server.checkM1(Buffer("notM1"));}, + /client did not use the same password/); + server.checkM1(expected.M1); // happy, not throwy + hexequal(server.computeK(), expected.K); +} + +vows.describe('picl vectors') + +.addBatch({ + 'vectors 1': function() { checkVectors(params, inputs_1, expected_1); }, + 'vectors 2': function() { checkVectors(params, inputs_2, expected_2); }, + 'vectors 3': function() { checkVectors(params, inputs_3, expected_3); }, + 'vectors 4': function() { checkVectors(params, inputs_4, expected_4); } +}) + +.export(module); diff --git a/test/test_rfc_5054.js b/test/test_rfc_5054.js new file mode 100644 index 0000000..c0542ec --- /dev/null +++ b/test/test_rfc_5054.js @@ -0,0 +1,98 @@ +const vows = require('vows'), + assert = require('assert'), + srp = require('../lib/srp'), + params = srp.params['1024']; + +/* + * http://tools.ietf.org/html/rfc5054#appendix-B + */ + +const I = new Buffer("alice"), + P = new Buffer("password123"), + s = Buffer('beb25379d1a8581eb5a727673a2441ee', 'hex'), + k_expected = '7556aa045aef2cdd07abaf0f665c3e818913186f', + x_expected = '94b7555aabe9127cc58ccf4993db6cf84d16c124', + v_expected = ('7e273de8 696ffc4f 4e337d05 b4b375be b0dde156 9e8fa00a 9886d812' + +'9bada1f1 822223ca 1a605b53 0e379ba4 729fdc59 f105b478 7e5186f5' + +'c671085a 1447b52a 48cf1970 b4fb6f84 00bbf4ce bfbb1681 52e08ab5' + +'ea53d15c 1aff87b2 b9da6e04 e058ad51 cc72bfc9 033b564e 26480d78' + +'e955a5e2 9e7ab245 db2be315 e2099afb').split(/\s/).join(''), + + a = Buffer('60975527035cf2ad1989806f0407210bc81edc04e2762a56afd529ddda2d4393', 'hex'), + b = Buffer('e487cb59d31ac550471e81f00f6928e01dda08e974a004f49e61f5d105284d20', 'hex'), + A_expected = ('61d5e490 f6f1b795 47b0704c 436f523d d0e560f0 c64115bb 72557ec4' + +'4352e890 3211c046 92272d8b 2d1a5358 a2cf1b6e 0bfcf99f 921530ec' + +'8e393561 79eae45e 42ba92ae aced8251 71e1e8b9 af6d9c03 e1327f44' + +'be087ef0 6530e69f 66615261 eef54073 ca11cf58 58f0edfd fe15efea' + +'b349ef5d 76988a36 72fac47b 0769447b').split(/\s/).join(''), + B_expected = ('bd0c6151 2c692c0c b6d041fa 01bb152d 4916a1e7 7af46ae1 05393011' + +'baf38964 dc46a067 0dd125b9 5a981652 236f99d9 b681cbf8 7837ec99' + +'6c6da044 53728610 d0c6ddb5 8b318885 d7d82c7f 8deb75ce 7bd4fbaa' + +'37089e6f 9c6059f3 88838e7a 00030b33 1eb76840 910440b1 b27aaeae' + +'eb4012b7 d7665238 a8e3fb00 4b117b58').split(/\s/).join(''), + + u_expected = 'ce38b9593487da98554ed47d70a7ae5f462ef019', + S_expected = ('b0dc82ba bcf30674 ae450c02 87745e79 90a3381f 63b387aa f271a10d' + +'233861e3 59b48220 f7c4693c 9ae12b0a 6f67809f 0876e2d0 13800d6c' + +'41bb59b6 d5979b5c 00a172b4 a2a5903a 0bdcaf8a 709585eb 2afafa8f' + +'3499b200 210dcc1f 10eb3394 3cd67fc8 8a2f39a4 be5bec4e c0a3212d' + +'c346d7e4 74b29ede 8a469ffe ca686e5a').split(/\s/).join(''); + +function asHex(num) { +//! return num.toBuffer().toString('hex'); + return num.toString(16); +} + +vows.describe('RFC 5054') + +.addBatch({ + "Test vectors": { + topic: function() { + return srp.computeVerifier(params, s, I, P); + }, + + "x": function() { + var client = new srp.Client(params, s, I, P, a); + assert.equal(asHex(client._private.x_num), x_expected); + }, + + "V": function(v) { + assert.equal(v.toString('hex'), v_expected); + }, + + "k": function() { + var client = new srp.Client(params, s, I, P, a); + assert.equal(asHex(client._private.k_num), k_expected); + }, + + "A": function() { + var client = new srp.Client(params, s, I, P, a); + assert.equal(client.computeA().toString('hex'), A_expected); + }, + + "B": function(v) { + var server = new srp.Server(params, v, b); + assert.equal(server.computeB().toString('hex'), B_expected); + }, + + "u": function() { + var client = new srp.Client(params, s, I, P, a); + client.setB(Buffer(B_expected, 'hex')); + assert.equal(asHex(client._private.u_num), u_expected); + }, + + "S client": function() { + var client = new srp.Client(params, s, I, P, a); + client.setB(Buffer(B_expected, 'hex')); + assert.equal(client._private.S_buf.toString('hex'), S_expected); + }, + + "S server": function(v) { + var server = new srp.Server(params, v, b); + server.setA(Buffer(A_expected, 'hex')); + assert.equal(server._private.S_buf.toString('hex'), S_expected); + } + } +}) + +.export(module); diff --git a/test/test_srp.js b/test/test_srp.js new file mode 100644 index 0000000..acd1b4d --- /dev/null +++ b/test/test_srp.js @@ -0,0 +1,194 @@ +const vows = require('vows'), + assert = require('assert'), + srp = require('../lib/srp'), + params = srp.params[4096], + + salt = new Buffer("salty"), + identity = new Buffer("alice"), + password = new Buffer("password123"); + +assert(params, "missing parameters"); + +var client, server; +var a, A; +var b, B; +var verifier; +var S_client, S_server; + +vows.describe("srp.js") + +.addBatch({ + "create Verifier": function() { + verifier = srp.computeVerifier(params, salt, identity, password); + assert.equal(verifier.toString("hex"), "f0e47f50f5dead8db8d93a279e3b62d6ff50854b31fbd3474a886bef916261717e84dd4fb8b4d27feaa5146db7b1cbbc274fdf96a132b5029c2cd72527427a9b9809d5a4d018252928b4fc343bc17ce63c1859d5806f5466014fc361002d8890aeb4d6316ff37331fc2761be0144c91cdd8e00ed0138c0ce51534d1b9a9ba629d7be34d2742dd4097daabc9ecb7aaad89e53c342b038f1d2adae1f2410b7884a3e9a124c357e421bccd4524467e1922660e0a4460c5f7c38c0877b65f6e32f28296282a93fc11bbabb7bb69bf1b3f9391991d8a86dd05e15000b7e38ba38a536bb0bf59c808ec25e791b8944719488b8087df8bfd7ff20822997a53f6c86f3d45d004476d6303301376bb25a9f94b552cce5ed40de5dd7da8027d754fa5f66738c7e3fc4ef3e20d625df62cbe6e7adfc21e47880d8a6ada37e60370fd4d8fc82672a90c29f2e72f35652649d68348de6f36d0e435c8bd42dd00155d35d501becc0661b43e04cdb2da84ce92b8bf49935d73d75efcbd1176d7bbccc3cc4d4b5fefcc02d478614ee1681d2ff3c711a61a7686eb852ae06fb8227be21fb8802719b1271ba1c02b13bbf0a2c2e459d9bedcc8d1269f6a785cb4563aa791b38fb038269f63f58f47e9051499549789269cc7b8ec7026fc34ba73289c4af829d5a532e723967ce9b6c023ef0fd0cfe37f51f10f19463b6534159a09ddd2f51f3b30033"); + }, + + "create a and b": { + topic: function() { + var cb = this.callback; + srp.genKey(64, function(err, key) { + assert(err === null); + a = key; + srp.genKey(32, function(err, key) { + assert(err === null); + b = key; + cb(); + }); + }); + }, + + "use a and b": function() { + client = new srp.Client(params, salt, identity, password, a); + + // client produces A + A = client.computeA(); + + // create server + server = new srp.Server(params, verifier, b); + + // server produces B + B = server.computeB(); + + // server accepts A + server.setA(A); + + // client doesn't produce M1 too early + assert.throws(function(){client.computeM1();}, /incomplete protocol/); + + // client accepts B + client.setB(B); + + // client produces M1 now + client.computeM1(); + + // server likes client's M1 + var serverM2 = server.checkM1(client.computeM1()); + + // client and server agree on K + var client_K = client.computeK(); + var server_K = server.computeK(); + assert.equal(client_K.toString("hex"), server_K.toString("hex")); + + // server is authentic + assert.doesNotThrow(function(){client.checkM2(serverM2);}, "M2 didn't check"); + }, + + "constructor doesn't require 'new'": function() { + client = srp.Client(params, salt, identity, password, a); + + // client produces A + A = client.computeA(); + + // create server + server = srp.Server(params, verifier, b); + + // server produces B + B = server.computeB(); + + // server accepts A + server.setA(A); + + // client doesn't produce M1 too early + assert.throws(function(){client.computeM1();}, /incomplete protocol/); + + // client accepts B + client.setB(B); + + // client produces M1 now + client.computeM1(); + + // server likes client's M1 + serverM2 = server.checkM1(client.computeM1()); + + // client and server agree on K + var client_K = client.computeK(); + var server_K = server.computeK(); + assert.equal(client_K.toString("hex"), server_K.toString("hex")); + + // server is authentic + assert.doesNotThrow(function(){client.checkM2(serverM2);}, "M2 didn't check"); + }, + + "server rejects wrong M1": function() { + var bad_client = new srp.Client(params, salt, identity, Buffer("bad"), a); + var server2 = new srp.Server(params, verifier, b); + bad_client.setB(server2.computeB()); + assert.throws(function(){server.checkM1(bad_client.computeM1());}, + /client did not use the same password/); + }, + + "server rejects bad A": function() { + // client's "A" must be 1..N-1 . Reject 0 and N and N+1. We should + // reject 2*N too, but our Buffer-length checks reject it before the + // number itself is examined. + + var server2 = new srp.Server(params, verifier, b); + var Azero = new Buffer(params.N_length_bits/8); + Azero.fill(0); +//! var AN = params.N.toBuffer(); +//! var AN1 = params.N.add(1).toBuffer(); + var AN = new Buffer(params.N.toString(16), 16); + var AN1 = new Buffer(params.N.add(1).toString(16), 16); + assert.throws(function() {server2.setA(Azero);}, + /invalid client-supplied 'A'/); + assert.throws(function() {server2.setA(AN);}, + /invalid client-supplied 'A'/); + assert.throws(function() {server2.setA(AN1);}, + /invalid client-supplied 'A'/); + }, + + "client rejects bad B": function() { + // server's "B" must be 1..N-1 . Reject 0 and N and N+1 + var client2 = new srp.Client(params, salt, identity, password, a); + var Bzero = new Buffer(params.N_length_bits/8); + Bzero.fill(0, 0, params.N_length_bits/8); +//! var BN = params.N.toBuffer(); +//! var BN1 = params.N.add(1).toBuffer(); + var BN = new Buffer(params.N.toString(16), 16); + var BN1 = new Buffer(params.N.add(1).toString(16), 16); + assert.throws(function() {client2.setB(Bzero);}, + /invalid server-supplied 'B'/); + assert.throws(function() {client2.setB(BN);}, + /invalid server-supplied 'B'/); + assert.throws(function() {client2.setB(BN1);}, + /invalid server-supplied 'B'/); + }, + + "client rejects bad M2": function() { + client = srp.Client(params, salt, identity, password, a); + + // client produces A + A = client.computeA(); + + // create server + server = srp.Server(params, verifier, b); + + // server produces B + B = server.computeB(); + + // server accepts A + server.setA(A); + + // client accepts B + client.setB(B); + + // client produces M1 now + client.computeM1(); + + // server likes client's M1 + var serverM2 = server.checkM1(client.computeM1()); + // we tamper with the server's M2 + serverM2 = serverM2 + "a"; + + // client and server agree on K + var client_K = client.computeK(); + var server_K = server.computeK(); + assert.equal(client_K.toString("hex"), server_K.toString("hex")); + + // server is NOT authentic + assert.throws(function(){client.checkM2(serverM2);}, "M2 didn't check"); + }, + } + +}) +.export(module);