services/fxaccounts/Credentials.jsm
author Ryan Hunt <rhunt@eqrion.net>
Mon, 25 Feb 2019 16:11:11 -0600
changeset 522780 76dd23f024d5cac080443bf7872ef7103c429542
parent 516675 6b56696d713a7f7858f16235e37baa8307e73b49
child 544264 854373e1ab9e7a438d9a666d76d4cfd227a7e0ae
permissions -rw-r--r--
Bug 1523969 part 19 - Move method definition inline comments to new line in 'netwerk/'. r=dragana Differential Revision: https://phabricator.services.mozilla.com/D21120

/* 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";

var EXPORTED_SYMBOLS = ["Credentials"];

const {Log} = ChromeUtils.import("resource://gre/modules/Log.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {CryptoUtils} = ChromeUtils.import("resource://services-crypto/utils.js");
const {CommonUtils} = ChromeUtils.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;

// 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;
}

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

var Credentials = Object.freeze({
  /**
   * Make constants accessible to tests
   */
  constants: {
    PROTOCOL_VERSION,
    PBKDF2_ROUNDS,
    STRETCHED_PW_LENGTH_BYTES,
    HKDF_SALT,
    HKDF_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(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(name, email) {
    return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ":" + email);
  },

  setup(emailInput, passwordInput, options = {}) {
    return new Promise(resolve => {
      log.debug("setup credentials for " + emailInput);

      let hkdfSalt = options.hkdfSalt || HKDF_SALT;
      let hkdfLength = options.hkdfLength || HKDF_LENGTH;
      let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
      let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;

      let result = {};

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

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

        result.quickStretchedPW = quickStretchedPW;

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

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

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

      Services.tm.dispatchToMainThread(runnable);
      log.debug("Dispatched thread for credentials setup crypto work");
    });
  },
});