From 94d1938e2e5d0ee5e8e7b9a8ed44a067677e0133 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 3 Aug 2012 20:37:11 +0200 Subject: moved all xhr related stuff to a seperate class We can replace this if we want to use jquery ajax or similar. Also this has all the urls so it's super easy to overwrite --- lib/plainXHR.js | 124 ++++++++++++++++++++++++++++++++ lib/srp.js | 173 +++++++++++---------------------------------- lib/srp_register.js | 22 ++---- spec/DjangoSpecRunner.html | 1 + spec/signup.js | 4 +- 5 files changed, 173 insertions(+), 151 deletions(-) create mode 100644 lib/plainXHR.js diff --git a/lib/plainXHR.js b/lib/plainXHR.js new file mode 100644 index 0000000..44ee5df --- /dev/null +++ b/lib/plainXHR.js @@ -0,0 +1,124 @@ +plainXHR = function() { + + function getUrl() + { + return ""; + } + + function paths(path) + { + return path + } + + // Perform ajax requests at the specified path, with the specified parameters + // Calling back the specified function. + function ajaxRequest(relative_path, params, callback) + { + var full_url = this.geturl() + this.paths(relative_path); + if( window.XMLHttpRequest) + xhr = new XMLHttpRequest(); + else if (window.ActiveXObject){ + try{ + xhr = new ActiveXObject("Microsoft.XMLHTTP"); + }catch (e){} + } + else + { + session.error_message("Ajax not supported."); + return; + } + if(xhr){ + xhr.onreadystatechange = function() { + if(xhr.readyState == 4 && xhr.status == 200) { + callback(parseResponse()); + } + }; + xhr.open("POST", full_url, true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("Content-length", params.length); + xhr.send(params); + } + else + { + session.error_message("Ajax failed."); + } + }; + + function parseResponse() { + if (responseIsXML()) { + return parseXML(xhr.responseXML); + } else if (responseIsJSON()) { + return JSON.parse(xhr.responseText); + } + }; + + function responseIsXML() { + return (xhr.responseType == 'document') || + (xhr.getResponseHeader("Content-Type").indexOf('application/xml') >= 0) + } + + function responseIsJSON() { + return (xhr.responseType == 'json') || + (xhr.getResponseHeader("Content-Type").indexOf('application/json') >= 0) + } + + function parseXML(xml) { + if (xml.getElementsByTagName("r").length > 0) { + return parseAttributesOfElement(xml.getElementsByTagName("r")[0]); + } else { + return parseNodes(xml.childNodes); + } + }; + + function parseAttributesOfElement(elem) { + var response = {}; + for (var i = 0; i < elem.attributes.length; i++) { + var attrib = elem.attributes[i]; + if (attrib.specified) { + response[attrib.name] = attrib.value; + } + } + return response; + }; + + function parseNodes(nodes) { + var response = {}; + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + response[node.tagName] = node.textContent || true; + } + return response; + }; + + function register(I, callback) + { + this.ajaxRequest("register/salt/", "I="+I, callback); + } + + function sendVerifier(v, callback) { + this.ajaxRequest("register/user/", "v="+v, callback); + } + + function handshake(I, Astr, callback) { + this.ajaxRequest("handshake/", "I="+I+"&A="+Astr, callback); + } + + function authenticate(M, callback) { + this.ajaxRequest("authenticate/", "M="+M, callback); + } + + function upgrade(M, callback) { + this.ajaxRequest("upgrade/authenticate/", "M="+M, callback); + } + + return { + geturl: getUrl, + paths: paths, + ajaxRequest: ajaxRequest, + register: register, + register_send_verifier: sendVerifier, + handshake: handshake, + authenticate: authenticate, + upgrade: upgrade + } +} diff --git a/lib/srp.js b/lib/srp.js index 0f6889b..9ef75f5 100644 --- a/lib/srp.js +++ b/lib/srp.js @@ -1,6 +1,6 @@ function SRP() { - // Variables that will be used in the SRP protocol + // Variables session will be used in the SRP protocol var Nstr = "115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3"; var N = new BigInteger(Nstr, 16); var g = new BigInteger("2"); @@ -18,11 +18,11 @@ function SRP() var K = null; var M = null; var M2 = null; - var that = this; + var session = this; var authenticated = false; var I = document.getElementById("srp_username").value; var p = document.getElementById("srp_password").value; - var xhr = null; + var remote = plainXHR(); // *** Accessor methods *** @@ -47,18 +47,6 @@ function SRP() return new BigInteger(64, rng).toString(16); } - // Returns the XMLHttpRequest object - this.getxhr = function() - { - return xhr; - }; - - // Returns the base URL - overwrite to use a different one - this.geturl = function() - { - return ""; - }; - // Returns the BigInteger, g this.getg = function() { @@ -82,129 +70,43 @@ function SRP() return this.getg().modPow(this.calcX(salt), this.getN()); } - // Overwrite this if you want to change the paths - this.paths = function(str) - { - return str; - }; - // Check whether or not a variable is defined function isdefined ( variable) { return (typeof(window[variable]) != "undefined"); }; - + // *** Actions *** - // Perform ajax requests at the specified url, with the specified parameters - // Calling back the specified function. - this.ajaxRequest = function(full_url, params, callback) - { - if( window.XMLHttpRequest) - xhr = new XMLHttpRequest(); - else if (window.ActiveXObject){ - try{ - xhr = new ActiveXObject("Microsoft.XMLHTTP"); - }catch (e){} - } - else - { - that.error_message("Ajax not supported."); - return; - } - if(xhr){ - xhr.onreadystatechange = function() { - if(xhr.readyState == 4 && xhr.status == 200) { - callback(parseResponse()); - } - }; - xhr.open("POST", full_url, true); - xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.setRequestHeader("Content-length", params.length); - xhr.send(params); - } - else - { - that.error_message("Ajax failed."); - } - }; - - function parseResponse() { - if (responseIsXML()) { - return parseXML(xhr.responseXML); - } else if (responseIsJSON()) { - return JSON.parse(xhr.responseText); - } - }; - - function responseIsXML() { - return (xhr.responseType == 'document') || - (xhr.getResponseHeader("Content-Type").indexOf('application/xml') >= 0) - } - - function responseIsJSON() { - return (xhr.responseType == 'json') || - (xhr.getResponseHeader("Content-Type").indexOf('application/json') >= 0) - } - - function parseXML(xml) { - if (xml.getElementsByTagName("r").length > 0) { - return parseAttributesOfElement(xml.getElementsByTagName("r")[0]); - } else { - return parseNodes(xml.childNodes); - } - }; - - function parseAttributesOfElement(elem) { - var response = {}; - for (var i = 0; i < elem.attributes.length; i++) { - var attrib = elem.attributes[i]; - if (attrib.specified) { - response[attrib.name] = attrib.value; - } - } - return response; - }; - - function parseNodes(nodes) { - var response = {}; - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i]; - response[node.tagName] = node.textContent || true; - } - return response; - }; - // Start the login process by identifying the user this.identify = function() { - var handshake_url = that.geturl() + that.paths("handshake/"); - var params = "I="+I+"&A="+Astr; - that.ajaxRequest(handshake_url, params, receive_salts); + this.remote.handshake(I, Astr, receive_salts); }; // Receive login salts from the server, start calculations function receive_salts(response) { if(response.error) { - that.error_message(response.error); - } - // B = 0 will make the algorithm always succeed - refuse such a server - // answer + session.error_message(response.error); + } + // B = 0 will make the algorithm always succeed - refuse such a server + // answer else if(response.B == 0) { - that.error_message("Server send random number 0 - this is not allowed"); + 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); - that.ajaxRequest(that.geturl()+that.paths("authenticate/"), "M="+M, confirm_authentication); + remote.authenticate(M, 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) @@ -235,13 +137,13 @@ function SRP() if(response.M == M2) { authenticated = true; - that.success(); + session.success(); } else - that.error_message("Server key does not match"); + session.error_message("Server key does not match"); } else if (response.error) - that.error_message(response.error); + session.error_message(response.error); }; // *** Upgrades *** @@ -267,7 +169,7 @@ function SRP() hashfun = MD5; //alert(hashfun(dsalt+p)); calculations(s, ephemeral, hashfun(dsalt+p)); - that.ajaxRequest(that.geturl()+that.paths("upgrade/authenticate/"), "M="+M, confirm_upgrade); + remote.upgrade(M, session.confirm_upgrade) }; window.setTimeout(do_upgrade,10); }; @@ -275,7 +177,7 @@ function SRP() // Encrypt plaintext using slowAES function encrypt(plaintext) { - var key = cryptoHelpers.toNumbers(that.key()); + var key = cryptoHelpers.toNumbers(session.key()); var byteMessage = cryptoHelpers.convertStringToByteArray(plaintext); var iv = new Array(16); rng.nextBytes(iv); @@ -287,7 +189,7 @@ function SRP() return retstring; }; - // Receive the server's M, confirming that the server has HASH(p) + // 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) { @@ -296,26 +198,26 @@ function SRP() if(response.M == M2) { K = SHA256(S.toString(16)); - var auth_url = that.geturl() + that.paths("upgrade/verifier/"); - that.ajaxRequest(auth_url, "p="+encrypt(p)+"&l="+p.length, confirm_verifier); + var auth_url = session.geturl() + session.paths("upgrade/verifier/"); + session.ajaxRequest(auth_url, "p="+encrypt(p)+"&l="+p.length, confirm_verifier); } else - that.error_message("Server key does not match"); + session.error_message("Server key does not match"); } else if (response.error) { - that.error_message(response.error); + session.error_message(response.error); } }; - // After sending the password, check that the response is OK, then reidentify + // After sending the password, check session the response is OK, then reidentify function confirm_verifier(response) { K = null; if(response.ok) - that.identify(); + session.identify(); else - that.error_message("Verifier could not be confirmed"); + session.error_message("Verifier could not be confirmed"); }; // This loads javascript libraries. Fname is the path to the library to be imported @@ -329,10 +231,10 @@ function SRP() // If we need SHA1 or MD5, we need to load the javascript files function import_hashes() { - // First check that the functions aren't already loaded + // First check session the functions aren't already loaded if(isdefined("SHA1") && isdefined("MD5")) return; - // Get the directory that this javascript file was loaded from - var arr=that.srpPath.split("/"); + // 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") @@ -372,18 +274,23 @@ function SRP() return K; } else - that.error_message("User has not been authenticated."); + 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); - }; + // 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/signup.js b/spec/signup.js index b5099b8..b38778a 100644 --- a/spec/signup.js +++ b/spec/signup.js @@ -24,11 +24,11 @@ describe("Signup", function() { it("receives the salt from /register/salt", function(){ var callback = sinon.spy(); - this.srp.register_send_verifier = callback; + this.srp.remote.register_send_verifier = callback; this.srp.register(); this.expectRequest('register/salt/', "I=user") this.respondXML("5d3055e0acd3ddcfc15"); - expect(callback).toHaveBeenCalledWith("adcd57b4a4a05c2e205b0b7b30014d9ff635d8d8db2f502f08e9b9c132800c44"); + expect(callback).toHaveBeenCalledWith("adcd57b4a4a05c2e205b0b7b30014d9ff635d8d8db2f502f08e9b9c132800c44", this.srp.registered_user); }); it("identifies after successful registration (INTEGRATION)", function(){ -- cgit v1.2.3