From 79610eaf3c0628c8b84da3a4bbf8a6598e1a03cb Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 13 Aug 2012 11:45:51 +0200 Subject: seperated session from the srp flow - login tests pass, signup fail --- lib/plainXHR.js | 48 ++------ lib/srp.js | 284 ++++----------------------------------------- lib/srp_session.js | 138 ++++++++++++++++++++++ spec/DjangoSpecRunner.html | 1 + spec/django/login.js | 10 +- 5 files changed, 177 insertions(+), 304 deletions(-) create mode 100644 lib/srp_session.js diff --git a/lib/plainXHR.js b/lib/plainXHR.js index 67d8137..95ceeac 100644 --- a/lib/plainXHR.js +++ b/lib/plainXHR.js @@ -1,20 +1,9 @@ -plainXHR = function() { - - function getUrl() - { - return ""; - } - - function paths(path) - { - return path - } +SRP.prototype.Remote = function() { // Perform ajax requests at the specified path, with the specified parameters // Calling back the specified function. - function ajaxRequest(relative_path, params, callback) + function ajaxRequest(url, params, callback) { - var full_url = this.geturl() + this.paths(relative_path); if( window.XMLHttpRequest) xhr = new XMLHttpRequest(); else if (window.ActiveXObject){ @@ -33,7 +22,7 @@ plainXHR = function() { callback(parseResponse()); } }; - xhr.open("POST", full_url, true); + xhr.open("POST", url, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("Content-length", params.length); xhr.send(params); @@ -90,35 +79,20 @@ plainXHR = function() { return response; }; - function register(session, callback) + this.register = function(session, callback) { - this.ajaxRequest("register/salt/", "I="+session.getI(), callback); - } - - function sendVerifier(session, callback) { - this.ajaxRequest("register/user/", "v="+session.getV().toString(16), callback); - } - - function handshake(I, Astr, callback) { - this.ajaxRequest("handshake/", "I="+I+"&A="+Astr, callback); + ajaxRequest("register/salt/", "I="+session.getI(), callback); } - function authenticate(M, callback) { - this.ajaxRequest("authenticate/", "M="+M, callback); + this.sendVerifier = function(session, callback) { + ajaxRequest("register/user/", "v="+session.getV().toString(16), callback); } - function upgrade(M, callback) { - this.ajaxRequest("upgrade/authenticate/", "M="+M, callback); + this.handshake = function(session, callback) { + ajaxRequest("handshake/", "I="+session.getI()+"&A="+session.getAstr(), callback); } - return { - geturl: getUrl, - paths: paths, - ajaxRequest: ajaxRequest, - register: register, - register_send_verifier: sendVerifier, - handshake: handshake, - authenticate: authenticate, - upgrade: upgrade + this.authenticate = function(session, callback) { + ajaxRequest("authenticate/", "M="+session.getM(), callback); } } diff --git a/lib/srp.js b/lib/srp.js index 8cb0c03..b54d6b7 100644 --- a/lib/srp.js +++ b/lib/srp.js @@ -1,257 +1,45 @@ -function SRP(remote) +function SRP(remote, session) { - // Variables session will be used in the SRP protocol - var Nstr = "115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3"; - var N = new BigInteger(Nstr, 16); - var g = new BigInteger("2"); - var k = new BigInteger("c46d46600d87fef149bd79b81119842f3c20241fda67d06ef412d8f6d9479c58", 16); - var rng = new SecureRandom(); - var a = new BigInteger(32, rng); - var A = g.modPow(a, N); - while(A.mod(N) == 0) - { - a = new BigInteger(32, rng); - A = g.modPow(a, N); - } - var Astr = A.toString(16); - var S = null; - var K = null; - var M = null; - var M2 = null; - var session = this; - var authenticated = false; - var I = document.getElementById("srp_username").value; - var p = document.getElementById("srp_password").value; - var V; - var salt; - remote = remote || plainXHR(); - - // *** Accessor methods *** - - // allows setting the random number A for testing - - this.calculateAndSetA = function(_a) - { - a = new BigInteger(_a, 16); - A = g.modPow(a, N); - Astr = A.toString(16); - return Astr; - }; - - // Returns the user's identity - this.getI = function() - { - return I; - }; - - // some 16 byte random number - this.getSalt = function() { - salt = salt || new BigInteger(64, rng).toString(16); - return salt - } - - // Returns the BigInteger, g - this.getg = function() - { - return g; - }; - - // Returns the BigInteger, N - this.getN = function() - { - return N; - }; - - // Calculates the X value and return it as a BigInteger - this.calcX = function(s) - { - return new BigInteger(SHA256(s + SHA256(I + ":" + p)), 16); - }; - - this.getV = function(salt) - { - V = V || this.getg().modPow(this.calcX(salt), this.getN()); - return V; - } - - // Check whether or not a variable is defined - function isdefined ( variable) - { - return (typeof(window[variable]) != "undefined"); - }; - - // *** Actions *** + var srp = this; + session = session || new this.Session(); + remote = remote || new this.Remote(); // Start the login process by identifying the user this.identify = function() { - this.remote.handshake(I, Astr, receive_salts); + remote.handshake(session, receive_salts); }; // Receive login salts from the server, start calculations function receive_salts(response) { - if(response.error) { - session.error_message(response.error); - } - // B = 0 will make the algorithm always succeed - refuse such a server - // answer - else if(response.B == 0) { - session.error_message("Server send random number 0 - this is not allowed"); - } - // If there is no algorithm specified, calculate M given s, B, and P - else if(!response.a) - { - calculations(response.s, response.B, p); - remote.authenticate(M, confirm_authentication) + // B = 0 will make the algorithm always succeed + // -> refuse such a server answer + if(response.B == 0) { + srp.error("Server send random number 0 - this is not allowed"); + } else { + session.calculations(response.s, response.B); + remote.authenticate(session, confirm_authentication) } - // If there is an algorithm specified, start the login process - else { - upgrade(response.s, response.B, response.a, response.d); - } - }; - - // Calculate S, M, and M2 - // This is the client side of the SRP specification - function calculations(s, ephemeral, pass) - { - //S -> C: s | B - var B = new BigInteger(ephemeral, 16); - var Bstr = ephemeral; - // u = H(A,B) - var u = new BigInteger(SHA256(Astr + Bstr), 16); - // x = H(s, H(I:p)) - var x = new BigInteger(SHA256(s + SHA256(I + ":" + pass)), 16); - //S = (B - kg^x) ^ (a + ux) - var kgx = k.multiply(g.modPow(x, N)); - var aux = a.add(u.multiply(x)); - S = B.subtract(kgx).modPow(aux, N); - // M = H(H(N) xor H(g), H(I), s, A, B, K) - var Mstr = A.toString(16) + B.toString(16) + S.toString(16); - M = SHA256(Mstr); - M2 = SHA256(A.toString(16) + M + S.toString(16)); - //M2 = H(A, M, K) }; // Receive M2 from the server and verify it + // If an error occurs, raise it as an alert. function confirm_authentication(response) { - if(response.M) - { - if(response.M == M2) - { - authenticated = true; - session.success(); - } - else - session.error_message("Server key does not match"); - } - else if (response.error) - session.error_message(response.error); - }; - - // *** Upgrades *** - - // Start the process to upgrade the user's account - function upgrade(s,ephemeral,algo,dsalt) - { - // First we need to import the hash functions - import_hashes(); - - // Once the hash functions are imported, do the calculations using the hashpass as the password - function do_upgrade() - { - // If sha1 and md5 are still undefined, sleep again - if(!isdefined("SHA1") || !isdefined("MD5")) - { - window.setTimeout(do_upgrade, 10); - return; - } - if(algo == "sha1") - hashfun = SHA1; - else if(algo == "md5") - hashfun = MD5; - //alert(hashfun(dsalt+p)); - calculations(s, ephemeral, hashfun(dsalt+p)); - remote.upgrade(M, session.confirm_upgrade) - }; - window.setTimeout(do_upgrade,10); - }; - - // Encrypt plaintext using slowAES - function encrypt(plaintext) - { - var key = cryptoHelpers.toNumbers(session.key()); - var byteMessage = cryptoHelpers.convertStringToByteArray(plaintext); - var iv = new Array(16); - rng.nextBytes(iv); - var paddedByteMessage = slowAES.getPaddedBlock(byteMessage, 0, byteMessage.length, slowAES.modeOfOperation.CFB); - var ciphertext = slowAES.encrypt(paddedByteMessage, slowAES.modeOfOperation.CFB, key, key.length, iv).cipher; - var retstring = cryptoHelpers.base64.encode(iv.concat(ciphertext)); - while(retstring.indexOf("+",0) > -1) - retstring = retstring.replace("+", "_"); - return retstring; - }; - - // Receive the server's M, confirming session the server has HASH(p) - // Next, send P in plaintext (this is the **only** time it should ever be sent plain text) - function confirm_upgrade(response) - { - if(response.M) - { - if(response.M == M2) - { - K = SHA256(S.toString(16)); - var auth_url = session.geturl() + session.paths("upgrade/verifier/"); - session.ajaxRequest(auth_url, "p="+encrypt(p)+"&l="+p.length, confirm_verifier); - } - else - session.error_message("Server key does not match"); - } - else if (response.error) - { - session.error_message(response.error); - } - }; - - // After sending the password, check session the response is OK, then reidentify - function confirm_verifier(response) - { - K = null; - if(response.ok) - session.identify(); + if (session.validate(response.M)) + srp.success(); else - session.error_message("Verifier could not be confirmed"); + alertErrorMessage("Server key does not match"); }; - // This loads javascript libraries. Fname is the path to the library to be imported - function import_file(fname) + // Minimal error handling - set remote.onError to sth better to overwrite. + this.error = function(text) { - var scriptElt = document.createElement('script'); - scriptElt.type = 'text/javascript'; - scriptElt.src = fname; - document.getElementsByTagName('head')[0].appendChild(scriptElt); + alert(text); }; - // If we need SHA1 or MD5, we need to load the javascript files - function import_hashes() - { - // First check session the functions aren't already loaded - if(isdefined("SHA1") && isdefined("MD5")) return; - // Get the directory session this javascript file was loaded from - var arr=session.srpPath.split("/"); - var path = arr.slice(0, arr.length-1).join("/"); - // If this file is called srp.min.js, we will load the packed hash file - if(arr[arr.length-1] == "srp.min.js") - import_file(path+"/crypto.min.js"); - // Otherwise, this file is presumably srp.js, and we will load individual hash files - else - { - import_file(path+"/MD5.js"); - import_file(path+"/SHA1.js"); - import_file(path+"/cryptoHelpers.js"); - import_file(path+"/aes.js"); - } - } + remote.onError = remote.onError || this.error; + session.onError = session.onError || this.error; // This function is called when authentication is successful. // Developers can set this to other functions in specific implementations @@ -267,35 +55,5 @@ function SRP(remote) alert("Login successful."); } }; - // If someone wants to use the session key for encrypting traffic, they can - // access the key with this function. - this.key = function() - { - if(K == null) - if(authenticated) - { - K = SHA256(S.toString(16)); - return K; - } - else - session.error_message("User has not been authenticated."); - else - return K; - }; - - // If an error occurs, raise it as an alert. - // Developers can set this to an alternative function to handle erros differently. - this.error_message = function(t) - { - alert(t); - }; - - - // exposing the remote handler so it can be modified - this.remote = remote; }; - // This line is run while the document is loading - // It gets a list of all + diff --git a/spec/django/login.js b/spec/django/login.js index eea6062..d13f695 100644 --- a/spec/django/login.js +++ b/spec/django/login.js @@ -16,8 +16,10 @@ describe("Login", function() { beforeEach(function() { - this.srp = new SRP(); - A = this.srp.calculateAndSetA(a); + var srp = new SRP(); + var session = new srp.Session(); + this.srp = new SRP(null, session) + A = session.calculateAndSetA(a); specHelper.setupFakeXHR.apply(this); @@ -51,14 +53,14 @@ describe("Login", function() { }); it("rejects B = 0", function(){ - this.srp.error_message = sinon.spy(); + this.srp.error = sinon.spy(); this.srp.identify(); this.expectRequest('handshake/', 'I=user&A='+A); this.respondJSON({s: salt, B: 0}); // aborting if B=0 expect(this.requests).toEqual([]); - expect(this.srp.error_message).toHaveBeenCalled(); + expect(this.srp.error).toHaveBeenCalled(); }); }); -- cgit v1.2.3