services/fxaccounts/Credentials.jsm
author Jim Blandy <jimb@mozilla.com>
Tue, 24 Jun 2014 22:12:07 -0700
changeset 199513 69d61e42d5dfbf4588b72449249ff3e7f2125304
parent 173046 6e77dfcf5c6e376e86ea7ddb6d372c8d7fa836f0
child 282579 380817d573cdfbfc4a4b4a4647cf1a53bb52c3b9
permissions -rw-r--r--
Bug 914753: Make Emacs file variable header lines correct, or at least consistent. DONTBUILD r=ehsan The -*- file variable lines -*- establish per-file settings that Emacs will pick up. This patch makes the following changes to those lines (and touches nothing else): - Never set the buffer's mode. Years ago, Emacs did not have a good JavaScript mode, so it made sense to use Java or C++ mode in .js files. However, Emacs has had js-mode for years now; it's perfectly serviceable, and is available and enabled by default in all major Emacs packagings. Selecting a mode in the -*- file variable line -*- is almost always the wrong thing to do anyway. It overrides Emacs's default choice, which is (now) reasonable; and even worse, it overrides settings the user might have made in their '.emacs' file for that file extension. It's only useful when there's something specific about that particular file that makes a particular mode appropriate. - Correctly propagate settings that establish the correct indentation level for this file: c-basic-offset and js2-basic-offset should be js-indent-level. Whatever value they're given should be preserved; different parts of our tree use different indentation styles. - We don't use tabs in Mozilla JS code. Always set indent-tabs-mode: nil. Remove tab-width: settings, at least in files that don't contain tab characters. - Remove js2-mode settings that belong in the user's .emacs file, like js2-skip-preprocessor-directives.

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module implements client-side key stretching for use in Firefox
 * Accounts account creation and login.
 *
 * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
 */

"use strict";

this.EXPORTED_SYMBOLS = ["Credentials"];

const {utils: Cu, interfaces: Ci} = Components;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/utils.js");

const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/";
const PBKDF2_ROUNDS = 1000;
const STRETCHED_PW_LENGTH_BYTES = 32;
const HKDF_SALT = CommonUtils.hexToBytes("00");
const HKDF_LENGTH = 32;
const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256;
const HMAC_LENGTH = 32;

// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
// default.
const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
try {
  this.LOG_LEVEL =
    Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
    && Services.prefs.getCharPref(PREF_LOG_LEVEL);
} catch (e) {
  this.LOG_LEVEL = Log.Level.Error;
}

let log = Log.repository.getLogger("Identity.FxAccounts");
log.level = LOG_LEVEL;
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));

this.Credentials = Object.freeze({
  /**
   * Make constants accessible to tests
   */
  constants: {
    PROTOCOL_VERSION: PROTOCOL_VERSION,
    PBKDF2_ROUNDS: PBKDF2_ROUNDS,
    STRETCHED_PW_LENGTH_BYTES: STRETCHED_PW_LENGTH_BYTES,
    HKDF_SALT: HKDF_SALT,
    HKDF_LENGTH: HKDF_LENGTH,
    HMAC_ALGORITHM: HMAC_ALGORITHM,
    HMAC_LENGTH: HMAC_LENGTH,
  },

  /**
   * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
   *
   * keyWord derivation for use as a salt.
   *
   *
   *   @param {String} context  String for use in generating salt
   *
   *   @return {bitArray} the salt
   *
   * Note that PROTOCOL_VERSION does not refer in any way to the version of the
   * Firefox Accounts API.
   */
  keyWord: function(context) {
    return CommonUtils.stringToBytes(PROTOCOL_VERSION + context);
  },

  /**
   * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
   *
   * keyWord extended with a name and an email.
   *
   *   @param {String} name The name of the salt
   *   @param {String} email The email of the user.
   *
   *   @return {bitArray} the salt combination with the namespace
   *
   * Note that PROTOCOL_VERSION does not refer in any way to the version of the
   * Firefox Accounts API.
   */
  keyWordExtended: function(name, email) {
    return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ':' + email);
  },

  setup: function(emailInput, passwordInput, options={}) {
    let deferred = Promise.defer();
    log.debug("setup credentials for " + emailInput);

    let hkdfSalt = options.hkdfSalt || HKDF_SALT;
    let hkdfLength = options.hkdfLength || HKDF_LENGTH;
    let hmacLength = options.hmacLength || HMAC_LENGTH;
    let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
    let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
    let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;

    let result = {
      emailUTF8: emailInput,
      passwordUTF8: passwordInput,
    };

    let password = CommonUtils.encodeUTF8(passwordInput);
    let salt = this.keyWordExtended("quickStretch", emailInput);

    let runnable = () => {
      let start = Date.now();
      let quickStretchedPW = CryptoUtils.pbkdf2Generate(
          password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);

      result.quickStretchedPW = quickStretchedPW;

      result.authPW =
        CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);

      result.unwrapBKey =
        CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);

      log.debug("Credentials set up after " + (Date.now() - start) + " ms");
      deferred.resolve(result);
    }

    Services.tm.currentThread.dispatch(runnable,
        Ci.nsIThread.DISPATCH_NORMAL);
    log.debug("Dispatched thread for credentials setup crypto work");

    return deferred.promise;
  }
});