diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/plainXHR.js | 48 | ||||
| -rw-r--r-- | lib/srp.js | 284 | ||||
| -rw-r--r-- | lib/srp_session.js | 138 | 
3 files changed, 170 insertions, 300 deletions
| 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);    }  } @@ -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 <script> tags and finds the last instance. -  // The path to this script is the "src" attribute of that tag. -  SRP.prototype.srpPath = document.getElementsByTagName('script')[document.getElementsByTagName('script').length-1].getAttribute("src"); diff --git a/lib/srp_session.js b/lib/srp_session.js new file mode 100644 index 0000000..93bfbe5 --- /dev/null +++ b/lib/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; +  }; +} | 
