From 0461d76899379cb1e2ecd15456d2e6eb4fb8fa60 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 21 Aug 2012 17:59:11 +0200 Subject: moved srp-js files from lib to src --- src/jqueryRest.js | 127 ++++++++++++++++++++++++++++++++++++++++++++++++ src/plainXHR.js | 120 ++++++++++++++++++++++++++++++++++++++++++++++ src/srp.js | 76 +++++++++++++++++++++++++++++ src/srp_session.js | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 461 insertions(+) create mode 100644 src/jqueryRest.js create mode 100644 src/plainXHR.js create mode 100644 src/srp.js create mode 100644 src/srp_session.js (limited to 'src') diff --git a/src/jqueryRest.js b/src/jqueryRest.js new file mode 100644 index 0000000..8c8163c --- /dev/null +++ b/src/jqueryRest.js @@ -0,0 +1,127 @@ +jqueryRest = 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; + }; + + // we do not fetch the salt from the server + function register(session, callback) + { + sendVerifier(session, callback); + } + + function sendVerifier(session, callback) { + this.ajaxRequest("users", "user[login]=" + session.getI() + + "&user[password_salt]=" + session.getSalt() + + "&user[password_verifier]=" + session.getV().toString(16), 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/src/plainXHR.js b/src/plainXHR.js new file mode 100644 index 0000000..d07416b --- /dev/null +++ b/src/plainXHR.js @@ -0,0 +1,120 @@ +// +// SRP JS - Plain XHR module +// +// This is deprecated - unless you are using srp-js with the original drupal +// server side I recommend you use a different API such as restful.js +// +// This code has been largely refactored, tests are still passing but I did +// not test it with the server itself. + +SRP.prototype.Remote = function() { + + // Perform ajax requests at the specified path, with the specified parameters + // Calling back the specified function. + function ajaxRequest(url, params, callback) + { + 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", 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; + }; + + // Drupal version fetches the salt from the server. No idea why but this + // should still do it. + this.register = function(session, callback) + { + var that = this; + ajaxRequest("register/salt/", "I="+session.getI(), receive_salt); + + function receive_salt(response) + { + if(response.salt) + { + var s = response.salt; + var v = session.getV(s); + that.sendVerifier(session, callback); + } + }; + } + + this.sendVerifier = function(session, callback) { + ajaxRequest("register/user/", "v="+session.getV().toString(16), callback); + } + + this.handshake = function(session, callback) { + ajaxRequest("handshake/", "I="+session.getI()+"&A="+session.getAstr(), callback); + } + + this.authenticate = function(session, callback) { + ajaxRequest("authenticate/", "M="+session.getM(), callback); + } +} diff --git a/src/srp.js b/src/srp.js new file mode 100644 index 0000000..972b211 --- /dev/null +++ b/src/srp.js @@ -0,0 +1,76 @@ +function SRP(remote, session) +{ + var srp = this; + session = session || new this.Session(); + remote = remote || new this.Remote(); + remote.onError = remote.onError || this.error; + session.onError = session.onError || this.error; + this.remote = remote; + this.session = session; + + // Start the login process by identifying the user + this.identify = function() + { + remote.handshake(session, receive_salts); + + // Receive login salts from the server, start calculations + function receive_salts(response) + { + // 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); + } + } + + // Receive M2 from the server and verify it + // If an error occurs, raise it as an alert. + function confirm_authentication(response) + { + if (session.validate(response.M)) + srp.success(); + else + srp.error("Server key does not match"); + }; + }; + + // Initiate the registration process + this.register = function() + { + remote.register(session, srp.registered_user); + }; + + // The user has been registered successfully, now login + this.registered_user = function(response) + { + if(response.ok) + { + srp.identify(); + } + }; + + // Minimal error handling - set remote.onError to sth better to overwrite. + this.error = function(text) + { + alert(text); + }; + + // This function is called when authentication is successful. + // Developers can set this to other functions in specific implementations + // and change the functionality. + this.success = function() + { + var forward_url = document.getElementById("srp_forward").value; + if(forward_url.charAt(0) != "#") + window.location = forward_url; + else + { + window.location = forward_url; + alert("Login successful."); + } + }; +}; + diff --git a/src/srp_session.js b/src/srp_session.js new file mode 100644 index 0000000..93bfbe5 --- /dev/null +++ b/src/srp_session.js @@ -0,0 +1,138 @@ +SRP.prototype.Session = function() { + + // 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 authenticated = false; + var I = document.getElementById("srp_username").value; + var pass = document.getElementById("srp_password").value; + var V; + var salt; + + // *** 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; + }; + + this.getAstr = function() { + 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 + ":" + pass)), 16); + }; + + this.getV = function(salt) + { + V = V || this.getg().modPow(this.calcX(salt), this.getN()); + return V; + } + + // Calculate S, M, and M2 + // This is the client side of the SRP specification + this.calculations = function(salt, ephemeral) + { + //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(salt + 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) + }; + + this.getM = function() { + return M; + } + + this.validate = function(serverM2) { + authenticated = (serverM2 && serverM2 == M2) + return authenticated; + } + + // If someone wants to use the session key for encrypting traffic, they can + // access the key with this function. + this.key = function() + { + if(K) return K; + if(authenticated) { + K = SHA256(S.toString(16)); + return K; + } + else + this.onError("User has not been authenticated."); + }; + + // Encrypt plaintext using slowAES + this.encrypt = function(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; + }; +} -- cgit v1.2.3