From 0461d76899379cb1e2ecd15456d2e6eb4fb8fa60 Mon Sep 17 00:00:00 2001
From: Azul <azul@riseup.net>
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