Source: data/hasher.js

/**
 * @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
    });
};