mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Initial commit of SRP package.
This commit is contained in:
1279
packages/srp/biginteger.js
Normal file
1279
packages/srp/biginteger.js
Normal file
File diff suppressed because it is too large
Load Diff
15
packages/srp/package.js
Normal file
15
packages/srp/package.js
Normal file
@@ -0,0 +1,15 @@
|
||||
Package.describe({
|
||||
summary: "Library for Secure Remote Password (SRP) exchanges",
|
||||
internal: true
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('uuid', ['client', 'server']);
|
||||
api.add_files(['biginteger.js', 'sha256.js', 'srp.js'],
|
||||
['client', 'server']);
|
||||
});
|
||||
|
||||
Package.on_test(function (api) {
|
||||
api.use('srp', ['client', 'server']);
|
||||
api.add_files(['srp_tests.js'], ['client', 'server']);
|
||||
});
|
||||
140
packages/srp/sha256.js
Normal file
140
packages/srp/sha256.js
Normal file
@@ -0,0 +1,140 @@
|
||||
/// METEOR WRAPPER
|
||||
//
|
||||
// XXX this should get packaged and moved into the Meteor.crypto
|
||||
// namespace, along with other hash functions.
|
||||
if (typeof Meteor._srp === "undefined")
|
||||
Meteor._srp = {};
|
||||
Meteor._srp.SHA256 = (function () {
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Secure Hash Algorithm (SHA256)
|
||||
* http://www.webtoolkit.info/
|
||||
*
|
||||
* Original code by Angel Marin, Paul Johnston.
|
||||
*
|
||||
**/
|
||||
|
||||
function SHA256(s){
|
||||
|
||||
var chrsz = 8;
|
||||
var hexcase = 0;
|
||||
|
||||
function safe_add (x, y) {
|
||||
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
|
||||
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xFFFF);
|
||||
}
|
||||
|
||||
function S (X, n) { return ( X >>> n ) | (X << (32 - n)); }
|
||||
function R (X, n) { return ( X >>> n ); }
|
||||
function Ch(x, y, z) { return ((x & y) ^ ((~x) & z)); }
|
||||
function Maj(x, y, z) { return ((x & y) ^ (x & z) ^ (y & z)); }
|
||||
function Sigma0256(x) { return (S(x, 2) ^ S(x, 13) ^ S(x, 22)); }
|
||||
function Sigma1256(x) { return (S(x, 6) ^ S(x, 11) ^ S(x, 25)); }
|
||||
function Gamma0256(x) { return (S(x, 7) ^ S(x, 18) ^ R(x, 3)); }
|
||||
function Gamma1256(x) { return (S(x, 17) ^ S(x, 19) ^ R(x, 10)); }
|
||||
|
||||
function core_sha256 (m, l) {
|
||||
var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2);
|
||||
var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
|
||||
var W = new Array(64);
|
||||
var a, b, c, d, e, f, g, h, i, j;
|
||||
var T1, T2;
|
||||
|
||||
m[l >> 5] |= 0x80 << (24 - l % 32);
|
||||
m[((l + 64 >> 9) << 4) + 15] = l;
|
||||
|
||||
for ( var i = 0; i<m.length; i+=16 ) {
|
||||
a = HASH[0];
|
||||
b = HASH[1];
|
||||
c = HASH[2];
|
||||
d = HASH[3];
|
||||
e = HASH[4];
|
||||
f = HASH[5];
|
||||
g = HASH[6];
|
||||
h = HASH[7];
|
||||
|
||||
for ( var j = 0; j<64; j++) {
|
||||
if (j < 16) W[j] = m[j + i];
|
||||
else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
|
||||
|
||||
T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
|
||||
T2 = safe_add(Sigma0256(a), Maj(a, b, c));
|
||||
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = safe_add(d, T1);
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = safe_add(T1, T2);
|
||||
}
|
||||
|
||||
HASH[0] = safe_add(a, HASH[0]);
|
||||
HASH[1] = safe_add(b, HASH[1]);
|
||||
HASH[2] = safe_add(c, HASH[2]);
|
||||
HASH[3] = safe_add(d, HASH[3]);
|
||||
HASH[4] = safe_add(e, HASH[4]);
|
||||
HASH[5] = safe_add(f, HASH[5]);
|
||||
HASH[6] = safe_add(g, HASH[6]);
|
||||
HASH[7] = safe_add(h, HASH[7]);
|
||||
}
|
||||
return HASH;
|
||||
}
|
||||
|
||||
function str2binb (str) {
|
||||
var bin = Array();
|
||||
var mask = (1 << chrsz) - 1;
|
||||
for(var i = 0; i < str.length * chrsz; i += chrsz) {
|
||||
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
|
||||
}
|
||||
return bin;
|
||||
}
|
||||
|
||||
function Utf8Encode(string) {
|
||||
string = string.replace(/\r\n/g,"\n");
|
||||
var utftext = "";
|
||||
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
|
||||
var c = string.charCodeAt(n);
|
||||
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
}
|
||||
else if((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return utftext;
|
||||
}
|
||||
|
||||
function binb2hex (binarray) {
|
||||
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
|
||||
var str = "";
|
||||
for(var i = 0; i < binarray.length * 4; i++) {
|
||||
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
|
||||
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
s = Utf8Encode(s);
|
||||
return binb2hex(core_sha256(str2binb(s), s.length * chrsz));
|
||||
|
||||
}
|
||||
|
||||
/// METEOR WRAPPER
|
||||
return SHA256;
|
||||
})();
|
||||
341
packages/srp/srp.js
Normal file
341
packages/srp/srp.js
Normal file
@@ -0,0 +1,341 @@
|
||||
(function () {
|
||||
|
||||
if (typeof Meteor._srp === "undefined")
|
||||
Meteor._srp = {};
|
||||
|
||||
|
||||
/////// PUBLIC CLIENT
|
||||
|
||||
/**
|
||||
* Generate a new SRP verifier. Password is the plaintext password.
|
||||
*
|
||||
* options is optional and can include:
|
||||
* - identity: String. The SRP username to user. Mostly this is passed
|
||||
* in for testing. Random UUID if not provided.
|
||||
* - salt: String. A salt to use. Mostly this is passed in for
|
||||
* testing. Random UUID if not provided.
|
||||
* - SRP parameters (see _defaults and paramsFromOptions below)
|
||||
*/
|
||||
Meteor._srp.generateVerifier = function (password, options) {
|
||||
var params = paramsFromOptions(options);
|
||||
|
||||
var identity = (options && options.identity) || Meteor.uuid();
|
||||
var salt = (options && options.salt) || Meteor.uuid();
|
||||
|
||||
var x = params.hash(salt + params.hash(identity + ":" + password));
|
||||
var xi = new Meteor._srp.BigInteger(x, 16);
|
||||
var v = params.g.modPow(xi, params.N);
|
||||
|
||||
|
||||
return {
|
||||
identity: identity,
|
||||
salt: salt,
|
||||
verifier: v.toString(16)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generate a new SRP client object. Password is the plaintext password.
|
||||
*
|
||||
* options is optional and can include:
|
||||
* - a: client's private ephemeral value. String or
|
||||
* BigInteger. Normally, this is picked randomly, but it can be
|
||||
* passed in for testing.
|
||||
* - SRP parameters (see _defaults and paramsFromOptions below)
|
||||
*/
|
||||
Meteor._srp.Client = function (password, options) {
|
||||
var self = this;
|
||||
self.params = paramsFromOptions(options);
|
||||
self.password = password;
|
||||
|
||||
// shorthand
|
||||
var N = self.params.N;
|
||||
var g = self.params.g;
|
||||
|
||||
// construct public and private keys.
|
||||
var a, A;
|
||||
if (options && options.a) {
|
||||
if (typeof options.a === "string")
|
||||
a = new Meteor._srp.BigInteger(options.a, 16);
|
||||
else if (options.a instanceof Meteor._srp.BigInteger)
|
||||
a = options.a;
|
||||
else
|
||||
throw new Error("Invalid parameter: a");
|
||||
|
||||
A = g.modPow(a, N);
|
||||
|
||||
if (A.mod(N) === 0)
|
||||
throw new Error("Invalid parameter: a: A mod N == 0.");
|
||||
|
||||
} else {
|
||||
while (!A || A.mod(N) === 0) {
|
||||
a = randInt();
|
||||
A = g.modPow(a, N);
|
||||
}
|
||||
}
|
||||
|
||||
self.a = a;
|
||||
self.A = A;
|
||||
self.Astr = A.toString(16);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initiate an SRP exchange.
|
||||
*
|
||||
* returns { A: 'client public ephemeral key. hex encoded integer.' }
|
||||
*/
|
||||
Meteor._srp.Client.prototype.startExchange = function () {
|
||||
var self = this;
|
||||
|
||||
return {
|
||||
A: self.Astr
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to the server's challenge with a proof of password.
|
||||
*
|
||||
* challenge is an object with
|
||||
* - B: server public ephemeral key. hex encoded integer.
|
||||
* - identity: user's identity (SRP username).
|
||||
* - salt: user's salt.
|
||||
*
|
||||
* returns { M: 'client proof of password. hex encoded integer.' }
|
||||
* throws an error if it got an invalid challenge.
|
||||
*/
|
||||
Meteor._srp.Client.prototype.respondToChallenge = function (challenge) {
|
||||
var self = this;
|
||||
|
||||
// shorthand
|
||||
var N = self.params.N;
|
||||
var g = self.params.g;
|
||||
var k = self.params.k;
|
||||
var H = self.params.hash;
|
||||
|
||||
// XXX check for missing / bad parameters.
|
||||
self.identity = challenge.identity;
|
||||
self.salt = challenge.salt;
|
||||
self.Bstr = challenge.B;
|
||||
self.B = new Meteor._srp.BigInteger(self.Bstr, 16);
|
||||
|
||||
if (self.B.mod(N) === 0)
|
||||
throw new Error("Server sent invalid key: B mod N == 0.");
|
||||
|
||||
var u = new Meteor._srp.BigInteger(H(self.Astr + self.Bstr), 16);
|
||||
var x = new Meteor._srp.BigInteger(
|
||||
H(self.salt + H(self.identity + ":" + self.password)), 16);
|
||||
|
||||
var kgx = k.multiply(g.modPow(x, N));
|
||||
var aux = self.a.add(u.multiply(x));
|
||||
var S = self.B.subtract(kgx).modPow(aux, N);
|
||||
var M = H(self.Astr + self.Bstr + S.toString(16));
|
||||
var HAMK = H(self.Astr + M + S.toString(16));
|
||||
|
||||
self.S = S;
|
||||
self.HAMK = HAMK;
|
||||
|
||||
return {
|
||||
M: M
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify server's confirmation message.
|
||||
*
|
||||
* confirmation is an object with
|
||||
* - HAMK: server's proof of password.
|
||||
*
|
||||
* returns true or false.
|
||||
*/
|
||||
Meteor._srp.Client.prototype.verifyConfirmation = function (confirmation) {
|
||||
var self = this;
|
||||
|
||||
return (self.HAMK && (confirmation.HAMK === self.HAMK));
|
||||
};
|
||||
|
||||
|
||||
|
||||
/////// PUBLIC SERVER
|
||||
|
||||
|
||||
/**
|
||||
* Generate a new SRP server object. Password is the plaintext password.
|
||||
*
|
||||
* options is optional and can include:
|
||||
* - b: server's private ephemeral value. String or
|
||||
* BigInteger. Normally, this is picked randomly, but it can be
|
||||
* passed in for testing.
|
||||
* - SRP parameters (see _defaults and paramsFromOptions below)
|
||||
*/
|
||||
Meteor._srp.Server = function (verifier, options) {
|
||||
var self = this;
|
||||
self.params = paramsFromOptions(options);
|
||||
self.verifier = verifier;
|
||||
|
||||
// shorthand
|
||||
var N = self.params.N;
|
||||
var g = self.params.g;
|
||||
var k = self.params.k;
|
||||
var v = new Meteor._srp.BigInteger(self.verifier.verifier, 16);
|
||||
|
||||
// construct public and private keys.
|
||||
var b, B;
|
||||
if (options && options.b) {
|
||||
if (typeof options.b === "string")
|
||||
b = new Meteor._srp.BigInteger(options.b, 16);
|
||||
else if (options.b instanceof Meteor._srp.BigInteger)
|
||||
b = options.b;
|
||||
else
|
||||
throw new Error("Invalid parameter: b");
|
||||
|
||||
B = k.multiply(v).add(g.modPow(b, N)).mod(N);
|
||||
|
||||
if (B.mod(N) === 0)
|
||||
throw new Error("Invalid parameter: b: B mod N == 0.");
|
||||
|
||||
} else {
|
||||
while (!B || B.mod(N) === 0) {
|
||||
b = randInt();
|
||||
B = k.multiply(v).add(g.modPow(b, N)).mod(N);
|
||||
}
|
||||
}
|
||||
|
||||
self.b = b;
|
||||
self.B = B;
|
||||
self.Bstr = B.toString(16);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Issue a challenge to the client.
|
||||
*
|
||||
* Takes a request from the client containing:
|
||||
* - A: hex encoded int.
|
||||
*
|
||||
* Returns a challenge with:
|
||||
* - B: server public ephemeral key. hex encoded integer.
|
||||
* - identity: user's identity (SRP username).
|
||||
* - salt: user's salt.
|
||||
*
|
||||
* Throws an error if issued a bad request.
|
||||
*/
|
||||
Meteor._srp.Server.prototype.issueChallenge = function (request) {
|
||||
var self = this;
|
||||
|
||||
// XXX check for missing / bad parameters.
|
||||
self.Astr = request.A;
|
||||
self.A = new Meteor._srp.BigInteger(self.Astr, 16);
|
||||
|
||||
if (self.A.mod(self.params.N) === 0)
|
||||
throw new Error("Client sent invalid key: A mod N == 0.");
|
||||
|
||||
// shorthand
|
||||
var N = self.params.N;
|
||||
var H = self.params.hash;
|
||||
|
||||
// Compute M and HAMK in advance. Don't send to client yet.
|
||||
var u = new Meteor._srp.BigInteger(H(self.Astr + self.Bstr), 16);
|
||||
var v = new Meteor._srp.BigInteger(self.verifier.verifier, 16);
|
||||
var avu = self.A.multiply(v.modPow(u, N));
|
||||
self.S = avu.modPow(self.b, N);
|
||||
self.M = H(self.Astr + self.Bstr + self.S.toString(16));
|
||||
self.HAMK = H(self.Astr + self.M + self.S.toString(16));
|
||||
|
||||
return {
|
||||
identity: self.verifier.identity,
|
||||
salt: self.verifier.salt,
|
||||
B: self.Bstr
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify a response from the client and return confirmation.
|
||||
*
|
||||
* Takes a challenge response from the client containing:
|
||||
* - M: client proof of password. hex encoded int.
|
||||
*
|
||||
* Returns a confirmation if the client's proof is good:
|
||||
* - HAMK: server proof of password. hex encoded integer.
|
||||
* OR null if the client's proof doesn't match.
|
||||
*/
|
||||
Meteor._srp.Server.prototype.verifyResponse = function (response) {
|
||||
var self = this;
|
||||
|
||||
if (response.M !== self.M)
|
||||
return null;
|
||||
|
||||
return {
|
||||
HAMK: self.HAMK
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
/////// INTERNAL
|
||||
|
||||
/**
|
||||
* Default parameter values for SRP.
|
||||
*
|
||||
*/
|
||||
Meteor._srp._defaults = {
|
||||
hash: function (x) { return Meteor._srp.SHA256(x).toLowerCase(); },
|
||||
N: new Meteor._srp.BigInteger("EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3", 16),
|
||||
g: new Meteor._srp.BigInteger("2")
|
||||
};
|
||||
Meteor._srp._defaults.k = new Meteor._srp.BigInteger(
|
||||
Meteor._srp._defaults.hash(
|
||||
Meteor._srp._defaults.N.toString(16) +
|
||||
Meteor._srp._defaults.g.toString(16)),
|
||||
16);
|
||||
|
||||
/**
|
||||
* Process an options hash to create SRP parameters.
|
||||
*
|
||||
* Options can include:
|
||||
* - hash: Function. Defaults to SHA256.
|
||||
* - N: String or BigInteger. Defaults to 1024 bit value from RFC 5054
|
||||
* - g: String or BigInteger. Defaults to 2.
|
||||
* - k: String or BigInteger. Defaults to hash(N, g)
|
||||
*/
|
||||
var paramsFromOptions = function (options) {
|
||||
if (!options) // fast path
|
||||
return Meteor._srp._defaults;
|
||||
|
||||
var ret = _.extend({}, Meteor._srp._defaults);
|
||||
|
||||
_.each(['N', 'g', 'k'], function (p) {
|
||||
if (options[p]) {
|
||||
if (typeof options[p] === "string")
|
||||
ret[p] = new Meteor._srp.BigInteger(options[p], 16);
|
||||
else if (options[p] instanceof Meteor._srp.BigInteger)
|
||||
ret[p] = options[p];
|
||||
else
|
||||
throw new Error("Invalid parameter: " + p);
|
||||
}
|
||||
});
|
||||
|
||||
if (options.hash)
|
||||
ret.hash = function (x) { return options.hash(x).toLowerCase(); };
|
||||
|
||||
if (!options.k && (options.N || options.g || options.hash)) {
|
||||
ret.k = ret.hash(ret.N.toString(16) + ret.g.toString(16));
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
var randInt = function () {
|
||||
// XXX XXX need a better implementation!
|
||||
return new Meteor._srp.BigInteger(Meteor.uuid().replace(/-/g, ''), 16);
|
||||
};
|
||||
|
||||
|
||||
|
||||
})();
|
||||
119
packages/srp/srp_tests.js
Normal file
119
packages/srp/srp_tests.js
Normal file
@@ -0,0 +1,119 @@
|
||||
(function() {
|
||||
|
||||
Tinytest.add("srp - good exchange", function(test) {
|
||||
var password = 'hi there!';
|
||||
var verifier = Meteor._srp.generateVerifier(password);
|
||||
|
||||
var C = new Meteor._srp.Client(password);
|
||||
var S = new Meteor._srp.Server(verifier);
|
||||
|
||||
var request = C.startExchange();
|
||||
var challenge = S.issueChallenge(request);
|
||||
var response = C.respondToChallenge(challenge);
|
||||
var confirmation = S.verifyResponse(response);
|
||||
|
||||
test.isTrue(confirmation);
|
||||
test.isTrue(C.verifyConfirmation(confirmation));
|
||||
|
||||
});
|
||||
|
||||
Tinytest.add("srp - bad exchange", function(test) {
|
||||
var verifier = Meteor._srp.generateVerifier('one password');
|
||||
|
||||
var C = new Meteor._srp.Client('another password');
|
||||
var S = new Meteor._srp.Server(verifier);
|
||||
|
||||
var request = C.startExchange();
|
||||
var challenge = S.issueChallenge(request);
|
||||
var response = C.respondToChallenge(challenge);
|
||||
var confirmation = S.verifyResponse(response);
|
||||
|
||||
test.isFalse(confirmation);
|
||||
});
|
||||
|
||||
|
||||
Tinytest.add("srp - fixed values", function(test) {
|
||||
// Test exact values during the exchange. We have to be very careful
|
||||
// about changing the SRP code, because changes could render
|
||||
// people's existing user database unusable. This test is
|
||||
// intentionally brittle to catch change that could affect the
|
||||
// validity of user passwords.
|
||||
|
||||
var identity = "b73d9af9-4e74-4ce0-879c-484828b08436";
|
||||
var salt = "85f8b9d3-744a-487d-8982-a50e4c9f552a";
|
||||
var password = "95109251-3d8a-4777-bdec-44ffe8d86dfb";
|
||||
var a = "dc99c646fa4cb7c24314bb6f4ca2d391297acd0dacb0430a13bbf1e37dcf8071";
|
||||
var b = "cf878e00c9f2b6aa48a10f66df9706e64fef2ca399f396d65f5b0a27cb8ae237";
|
||||
|
||||
var verifier = Meteor._srp.generateVerifier(
|
||||
password, {identity: identity, salt: salt});
|
||||
|
||||
var C = new Meteor._srp.Client(password, {a: a});
|
||||
var S = new Meteor._srp.Server(verifier, {b: b});
|
||||
|
||||
var request = C.startExchange();
|
||||
test.equal(request.A, "8a75aa61471a92d4c3b5d53698c910af5ef013c42799876c40612d1d5e0dc41d01f669bc022fadcd8a704030483401a1b86b8670191bd9dfb1fb506dd11c688b2f08e9946756263954db2040c1df1894af7af5f839c9215bb445268439157e65e8f100469d575d5d0458e19e8bd4dd4ea2c0b30b1b3f4f39264de4ec596e0bb7");
|
||||
|
||||
var challenge = S.issueChallenge(request);
|
||||
test.equal(challenge.B, "77ab0a40ef428aa2fa2bc257c905f352c7f75fbcfdb8761393c9dc0f730bbb0270ba9f837545b410c955c3f761494b329ad23c6efdec7e63509e538c2f68a3526e072550a11dac46017718362205e0c698b5bed67d6ff475aa92c191ca169f865c81a1a577373c449b98df720c7b7ff50536f9919d781e698025fd7164932ba7");
|
||||
|
||||
var response = C.respondToChallenge(challenge);
|
||||
test.equal(response.M, "8705d31bb61497279adf44eef6c167dcb7e03aa7a42102c1ea7e73025fbd4cd9");
|
||||
|
||||
var confirmation = S.verifyResponse(response);
|
||||
test.equal(confirmation.HAMK, "07a0f200392fa9a084db7acc2021fbc174bfb36956b46835cc12506b68b27bba");
|
||||
|
||||
test.isTrue(C.verifyConfirmation(confirmation));
|
||||
});
|
||||
|
||||
|
||||
Tinytest.add("srp - options", function(test) {
|
||||
// test that all options are respected.
|
||||
//
|
||||
// Note, all test strings here should be hex, because the 'hash'
|
||||
// function needs to output numbers.
|
||||
|
||||
var baseOptions = {
|
||||
hash: function (x) { return x; },
|
||||
N: 'b',
|
||||
g: '2',
|
||||
k: '1'
|
||||
};
|
||||
var verifierOptions = _.extend({
|
||||
identity: 'a',
|
||||
salt: 'b'
|
||||
}, baseOptions);
|
||||
var clientOptions = _.extend({
|
||||
a: "2"
|
||||
}, baseOptions);
|
||||
var serverOptions = _.extend({
|
||||
b: "2"
|
||||
}, baseOptions);
|
||||
|
||||
var verifier = Meteor._srp.generateVerifier('c', verifierOptions);;
|
||||
|
||||
test.equal(verifier.identity, 'a');
|
||||
test.equal(verifier.salt, 'b');
|
||||
test.equal(verifier.verifier, '3');
|
||||
|
||||
var C = new Meteor._srp.Client('c', clientOptions);
|
||||
var S = new Meteor._srp.Server(verifier, serverOptions);
|
||||
|
||||
var request = C.startExchange();
|
||||
test.equal(request.A, '4');
|
||||
|
||||
var challenge = S.issueChallenge(request);
|
||||
test.equal(challenge.identity, 'a');
|
||||
test.equal(challenge.salt, 'b');
|
||||
test.equal(challenge.B, '7');
|
||||
|
||||
var response = C.respondToChallenge(challenge);
|
||||
test.equal(response.M, '471');
|
||||
|
||||
var confirmation = S.verifyResponse(response);
|
||||
test.isTrue(confirmation);
|
||||
test.equal(confirmation.HAMK, '44711');
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user