michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * This module implements client-side key stretching for use in Firefox michael@0: * Accounts account creation and login. michael@0: * michael@0: * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["Credentials"]; michael@0: michael@0: const {utils: Cu, interfaces: Ci} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: Cu.import("resource://services-crypto/utils.js"); michael@0: Cu.import("resource://services-common/utils.js"); michael@0: michael@0: const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/"; michael@0: const PBKDF2_ROUNDS = 1000; michael@0: const STRETCHED_PW_LENGTH_BYTES = 32; michael@0: const HKDF_SALT = CommonUtils.hexToBytes("00"); michael@0: const HKDF_LENGTH = 32; michael@0: const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256; michael@0: const HMAC_LENGTH = 32; michael@0: michael@0: // loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO", michael@0: // "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by michael@0: // default. michael@0: const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel"; michael@0: try { michael@0: this.LOG_LEVEL = michael@0: Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING michael@0: && Services.prefs.getCharPref(PREF_LOG_LEVEL); michael@0: } catch (e) { michael@0: this.LOG_LEVEL = Log.Level.Error; michael@0: } michael@0: michael@0: let log = Log.repository.getLogger("Identity.FxAccounts"); michael@0: log.level = LOG_LEVEL; michael@0: log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); michael@0: michael@0: this.Credentials = Object.freeze({ michael@0: /** michael@0: * Make constants accessible to tests michael@0: */ michael@0: constants: { michael@0: PROTOCOL_VERSION: PROTOCOL_VERSION, michael@0: PBKDF2_ROUNDS: PBKDF2_ROUNDS, michael@0: STRETCHED_PW_LENGTH_BYTES: STRETCHED_PW_LENGTH_BYTES, michael@0: HKDF_SALT: HKDF_SALT, michael@0: HKDF_LENGTH: HKDF_LENGTH, michael@0: HMAC_ALGORITHM: HMAC_ALGORITHM, michael@0: HMAC_LENGTH: HMAC_LENGTH, michael@0: }, michael@0: michael@0: /** michael@0: * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol michael@0: * michael@0: * keyWord derivation for use as a salt. michael@0: * michael@0: * michael@0: * @param {String} context String for use in generating salt michael@0: * michael@0: * @return {bitArray} the salt michael@0: * michael@0: * Note that PROTOCOL_VERSION does not refer in any way to the version of the michael@0: * Firefox Accounts API. michael@0: */ michael@0: keyWord: function(context) { michael@0: return CommonUtils.stringToBytes(PROTOCOL_VERSION + context); michael@0: }, michael@0: michael@0: /** michael@0: * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol michael@0: * michael@0: * keyWord extended with a name and an email. michael@0: * michael@0: * @param {String} name The name of the salt michael@0: * @param {String} email The email of the user. michael@0: * michael@0: * @return {bitArray} the salt combination with the namespace michael@0: * michael@0: * Note that PROTOCOL_VERSION does not refer in any way to the version of the michael@0: * Firefox Accounts API. michael@0: */ michael@0: keyWordExtended: function(name, email) { michael@0: return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ':' + email); michael@0: }, michael@0: michael@0: setup: function(emailInput, passwordInput, options={}) { michael@0: let deferred = Promise.defer(); michael@0: log.debug("setup credentials for " + emailInput); michael@0: michael@0: let hkdfSalt = options.hkdfSalt || HKDF_SALT; michael@0: let hkdfLength = options.hkdfLength || HKDF_LENGTH; michael@0: let hmacLength = options.hmacLength || HMAC_LENGTH; michael@0: let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM; michael@0: let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES; michael@0: let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS; michael@0: michael@0: let result = { michael@0: emailUTF8: emailInput, michael@0: passwordUTF8: passwordInput, michael@0: }; michael@0: michael@0: let password = CommonUtils.encodeUTF8(passwordInput); michael@0: let salt = this.keyWordExtended("quickStretch", emailInput); michael@0: michael@0: let runnable = () => { michael@0: let start = Date.now(); michael@0: let quickStretchedPW = CryptoUtils.pbkdf2Generate( michael@0: password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength); michael@0: michael@0: result.quickStretchedPW = quickStretchedPW; michael@0: michael@0: result.authPW = michael@0: CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength); michael@0: michael@0: result.unwrapBKey = michael@0: CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength); michael@0: michael@0: log.debug("Credentials set up after " + (Date.now() - start) + " ms"); michael@0: deferred.resolve(result); michael@0: } michael@0: michael@0: Services.tm.currentThread.dispatch(runnable, michael@0: Ci.nsIThread.DISPATCH_NORMAL); michael@0: log.debug("Dispatched thread for credentials setup crypto work"); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: }); michael@0: