/**
* @module hasher
* @overview The main algorithm responsible for the generation of
* the proxy password using the PBKDF2 algorithm.
* Most crypto magic is done here.
*
* @author Manjul Apratim (manjul.apratim@gmail.com)
* @date Sep 07, 2014
*
* @license GNU General Public License v3 or Later
* @copyright Manjul Apratim, 2014, 2015
*/
// ========================================================================
// GLOBAL CONSTANTS
/**
* @typedef bitArray
* @summary An SJCL bitArray representing a hash.
* @see {@link https://github.com/bitwiseshiftleft/sjcl}
*/
/**
* @namespace
* @summary A global namespace for miscellaneous "environment variables".
*/
var HASHER_ENV = {
/**
* @summary The actionable events for interacting with the calling code.
* @enum {string}
*/
events : {
DONE : "done"
},
/**
* @summary Types referenced for the "typeof" command.
* @enum {string}
*/
types : {
FUNCTION : "function"
}
};
// ========================================================================
// IMPORT SCRIPTS
// Avoid "ReferenceError: importScripts is not defined"
// when hasher.js is loaded by the main addon code as well.
if (HASHER_ENV.types.FUNCTION === typeof(importScripts)) {
importScripts("./sjcl/sjcl.js");
}
// ========================================================================
// CLASS DEFINITIONS
/**
* @class
* @brief A class for algorithms provided by the "Hasher".
*/
var Hasher = {
/**
* @brief Function to generate the salt from the given domain name
* and the "saltKey". The domain is treated as a "password"
* which is strengthened using the "saltKey" as the salt.
* The strengthened "password" is the salt used subsequently
* to strengthen the password from the user.
* @prop {string} domain - The website domain.
* @prop {string} saltKey - The "key" used to strengthen the salt.
* @prop {int} iterations - The number of PBKDF2 iterations.
* @return {string} The generated "strengthened" salt, encoded
* in base64. A base64 encoding is chosen since it has
* characters from the set [A-Z][a-z][0-9](+,/),
* which looks like "mangled" text.
*/
generateSalt : function(domain, saltKey, iterations) {
var saltObj = sjcl.misc.pbkdf2(sjcl.hash.sha256.hash(domain),
sjcl.hash.sha256.hash(saltKey),
iterations,
256);
return sjcl.codec.base64.fromBits(saltObj);
},
/**
* @summary Function to obtain a url-safe base64-encoded
* key-stretched hash of the seed and the salt
* using the PBKDF2-HMAC-SHA256 algorithm.
* Both the "seed" and the "salt" are encoded to SHA256 before
* feeding into the algorithm.
* @param {string} seedSHA - The SHA256-encoded seed password.
* @param {string} salt - The input salt.
* @param {int} iterations - The number of PBKDF2 iterations.
* @return {string} The proxy password hash object
* generated by PBKDF2 encoded in base64.
*/
generateHash : function(seedSHA, salt, iterations) {
// Hash the salt to SHA256, and pass both salt and seed
// (both hashed to SHA256) to PBKDF2 as bitArrays.
var hashObj = sjcl.misc.pbkdf2(sjcl.codec.hex.toBits(seedSHA),
sjcl.codec.base64.toBits(salt),
iterations,
256);
return sjcl.codec.base64.fromBits(hashObj);
},
/**
* @summary Function to return the final password string from
* the base64-encoded proxy password,
* by replacing trailing "="s,
* and converting the encoding to "urlsafe" as per RFC 4648.
* @param {string} b64Hash - The PBKDF2 hash encoded in base64.
* @param {int} truncation - The number of characters to truncate to.
* If this is negative, no truncation will be performed.
* @return {string} The final password string.
*/
getPasswdStr : function(b64Hash, truncation) {
// Make the base64 hash "URL and filename safe",
// i.e., strip off trailing "="
// and replace the special characters (+, /)
// with the characters (-, _) respectively
// (RFC 4648).
var passwdStr = b64Hash.replace(
/=/g, '').replace(
/\+/g, '-').replace(
/\//g, '_');
// Truncate, if the parameter is supplied.
// Truncation is done after to account for removal of trailing "="s,
// since that may already have brought the length
// to within the desired range.
if (truncation >= 0) {
passwdStr = passwdStr.substring(0, truncation + 1);
}
return passwdStr;
}
};
// ========================================================================
// EVENT HANDLERS
/**
* @summary The main event handler for activating the hasher.
* @param {object} oEvent The incoming event.
* The "data" attribute of this object is expected to have
* the following attributes:
* @prop {string} saltKey - The key used to generate the salt
* from the domain name.
* @prop {string} seedSHA - The SHA256-encoded input password,
* @prop {Attributes} - The attributes
* (salt, iterations, truncation) to apply to the
* PBKDF2 algorithm
* @fires self#postMessage
*/
self.onmessage = function(oEvent) {
var eventData = oEvent.data;
// Check if this is a message to close self.
if (eventData.hasOwnProperty(HASHER_ENV.events.DONE)) {
self.postMessage({msg : "Terminating self..."})
self.close();
return;
}
// Obtain the salt
var salt = Hasher.generateSalt(eventData.attributes.domain,
eventData.saltKey,
eventData.attributes.iterations);
self.postMessage({salt : salt});
// Obtain the PBKDF2 hash.
var hash = Hasher.generateHash(eventData.seedSHA,
salt,
eventData.attributes.iterations);
self.postMessage({hash : hash});
// Obtain the final password string
var passwdStr = Hasher.getPasswdStr(hash,
eventData.attributes.truncation);
self.postMessage({
password : passwdStr,
done : HASHER_ENV.events.DONE
});
};