1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/fxaccounts/Credentials.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,139 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/** 1.9 + * This module implements client-side key stretching for use in Firefox 1.10 + * Accounts account creation and login. 1.11 + * 1.12 + * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol 1.13 + */ 1.14 + 1.15 +"use strict"; 1.16 + 1.17 +this.EXPORTED_SYMBOLS = ["Credentials"]; 1.18 + 1.19 +const {utils: Cu, interfaces: Ci} = Components; 1.20 + 1.21 +Cu.import("resource://gre/modules/Log.jsm"); 1.22 +Cu.import("resource://gre/modules/Services.jsm"); 1.23 +Cu.import("resource://gre/modules/Promise.jsm"); 1.24 +Cu.import("resource://services-crypto/utils.js"); 1.25 +Cu.import("resource://services-common/utils.js"); 1.26 + 1.27 +const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/"; 1.28 +const PBKDF2_ROUNDS = 1000; 1.29 +const STRETCHED_PW_LENGTH_BYTES = 32; 1.30 +const HKDF_SALT = CommonUtils.hexToBytes("00"); 1.31 +const HKDF_LENGTH = 32; 1.32 +const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256; 1.33 +const HMAC_LENGTH = 32; 1.34 + 1.35 +// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO", 1.36 +// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by 1.37 +// default. 1.38 +const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel"; 1.39 +try { 1.40 + this.LOG_LEVEL = 1.41 + Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING 1.42 + && Services.prefs.getCharPref(PREF_LOG_LEVEL); 1.43 +} catch (e) { 1.44 + this.LOG_LEVEL = Log.Level.Error; 1.45 +} 1.46 + 1.47 +let log = Log.repository.getLogger("Identity.FxAccounts"); 1.48 +log.level = LOG_LEVEL; 1.49 +log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); 1.50 + 1.51 +this.Credentials = Object.freeze({ 1.52 + /** 1.53 + * Make constants accessible to tests 1.54 + */ 1.55 + constants: { 1.56 + PROTOCOL_VERSION: PROTOCOL_VERSION, 1.57 + PBKDF2_ROUNDS: PBKDF2_ROUNDS, 1.58 + STRETCHED_PW_LENGTH_BYTES: STRETCHED_PW_LENGTH_BYTES, 1.59 + HKDF_SALT: HKDF_SALT, 1.60 + HKDF_LENGTH: HKDF_LENGTH, 1.61 + HMAC_ALGORITHM: HMAC_ALGORITHM, 1.62 + HMAC_LENGTH: HMAC_LENGTH, 1.63 + }, 1.64 + 1.65 + /** 1.66 + * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol 1.67 + * 1.68 + * keyWord derivation for use as a salt. 1.69 + * 1.70 + * 1.71 + * @param {String} context String for use in generating salt 1.72 + * 1.73 + * @return {bitArray} the salt 1.74 + * 1.75 + * Note that PROTOCOL_VERSION does not refer in any way to the version of the 1.76 + * Firefox Accounts API. 1.77 + */ 1.78 + keyWord: function(context) { 1.79 + return CommonUtils.stringToBytes(PROTOCOL_VERSION + context); 1.80 + }, 1.81 + 1.82 + /** 1.83 + * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol 1.84 + * 1.85 + * keyWord extended with a name and an email. 1.86 + * 1.87 + * @param {String} name The name of the salt 1.88 + * @param {String} email The email of the user. 1.89 + * 1.90 + * @return {bitArray} the salt combination with the namespace 1.91 + * 1.92 + * Note that PROTOCOL_VERSION does not refer in any way to the version of the 1.93 + * Firefox Accounts API. 1.94 + */ 1.95 + keyWordExtended: function(name, email) { 1.96 + return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ':' + email); 1.97 + }, 1.98 + 1.99 + setup: function(emailInput, passwordInput, options={}) { 1.100 + let deferred = Promise.defer(); 1.101 + log.debug("setup credentials for " + emailInput); 1.102 + 1.103 + let hkdfSalt = options.hkdfSalt || HKDF_SALT; 1.104 + let hkdfLength = options.hkdfLength || HKDF_LENGTH; 1.105 + let hmacLength = options.hmacLength || HMAC_LENGTH; 1.106 + let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM; 1.107 + let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES; 1.108 + let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS; 1.109 + 1.110 + let result = { 1.111 + emailUTF8: emailInput, 1.112 + passwordUTF8: passwordInput, 1.113 + }; 1.114 + 1.115 + let password = CommonUtils.encodeUTF8(passwordInput); 1.116 + let salt = this.keyWordExtended("quickStretch", emailInput); 1.117 + 1.118 + let runnable = () => { 1.119 + let start = Date.now(); 1.120 + let quickStretchedPW = CryptoUtils.pbkdf2Generate( 1.121 + password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength); 1.122 + 1.123 + result.quickStretchedPW = quickStretchedPW; 1.124 + 1.125 + result.authPW = 1.126 + CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength); 1.127 + 1.128 + result.unwrapBKey = 1.129 + CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength); 1.130 + 1.131 + log.debug("Credentials set up after " + (Date.now() - start) + " ms"); 1.132 + deferred.resolve(result); 1.133 + } 1.134 + 1.135 + Services.tm.currentThread.dispatch(runnable, 1.136 + Ci.nsIThread.DISPATCH_NORMAL); 1.137 + log.debug("Dispatched thread for credentials setup crypto work"); 1.138 + 1.139 + return deferred.promise; 1.140 + } 1.141 +}); 1.142 +