Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /**
6 * This module implements client-side key stretching for use in Firefox
7 * Accounts account creation and login.
8 *
9 * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
10 */
12 "use strict";
14 this.EXPORTED_SYMBOLS = ["Credentials"];
16 const {utils: Cu, interfaces: Ci} = Components;
18 Cu.import("resource://gre/modules/Log.jsm");
19 Cu.import("resource://gre/modules/Services.jsm");
20 Cu.import("resource://gre/modules/Promise.jsm");
21 Cu.import("resource://services-crypto/utils.js");
22 Cu.import("resource://services-common/utils.js");
24 const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/";
25 const PBKDF2_ROUNDS = 1000;
26 const STRETCHED_PW_LENGTH_BYTES = 32;
27 const HKDF_SALT = CommonUtils.hexToBytes("00");
28 const HKDF_LENGTH = 32;
29 const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256;
30 const HMAC_LENGTH = 32;
32 // loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
33 // "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
34 // default.
35 const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
36 try {
37 this.LOG_LEVEL =
38 Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
39 && Services.prefs.getCharPref(PREF_LOG_LEVEL);
40 } catch (e) {
41 this.LOG_LEVEL = Log.Level.Error;
42 }
44 let log = Log.repository.getLogger("Identity.FxAccounts");
45 log.level = LOG_LEVEL;
46 log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
48 this.Credentials = Object.freeze({
49 /**
50 * Make constants accessible to tests
51 */
52 constants: {
53 PROTOCOL_VERSION: PROTOCOL_VERSION,
54 PBKDF2_ROUNDS: PBKDF2_ROUNDS,
55 STRETCHED_PW_LENGTH_BYTES: STRETCHED_PW_LENGTH_BYTES,
56 HKDF_SALT: HKDF_SALT,
57 HKDF_LENGTH: HKDF_LENGTH,
58 HMAC_ALGORITHM: HMAC_ALGORITHM,
59 HMAC_LENGTH: HMAC_LENGTH,
60 },
62 /**
63 * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
64 *
65 * keyWord derivation for use as a salt.
66 *
67 *
68 * @param {String} context String for use in generating salt
69 *
70 * @return {bitArray} the salt
71 *
72 * Note that PROTOCOL_VERSION does not refer in any way to the version of the
73 * Firefox Accounts API.
74 */
75 keyWord: function(context) {
76 return CommonUtils.stringToBytes(PROTOCOL_VERSION + context);
77 },
79 /**
80 * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
81 *
82 * keyWord extended with a name and an email.
83 *
84 * @param {String} name The name of the salt
85 * @param {String} email The email of the user.
86 *
87 * @return {bitArray} the salt combination with the namespace
88 *
89 * Note that PROTOCOL_VERSION does not refer in any way to the version of the
90 * Firefox Accounts API.
91 */
92 keyWordExtended: function(name, email) {
93 return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ':' + email);
94 },
96 setup: function(emailInput, passwordInput, options={}) {
97 let deferred = Promise.defer();
98 log.debug("setup credentials for " + emailInput);
100 let hkdfSalt = options.hkdfSalt || HKDF_SALT;
101 let hkdfLength = options.hkdfLength || HKDF_LENGTH;
102 let hmacLength = options.hmacLength || HMAC_LENGTH;
103 let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
104 let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
105 let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
107 let result = {
108 emailUTF8: emailInput,
109 passwordUTF8: passwordInput,
110 };
112 let password = CommonUtils.encodeUTF8(passwordInput);
113 let salt = this.keyWordExtended("quickStretch", emailInput);
115 let runnable = () => {
116 let start = Date.now();
117 let quickStretchedPW = CryptoUtils.pbkdf2Generate(
118 password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);
120 result.quickStretchedPW = quickStretchedPW;
122 result.authPW =
123 CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
125 result.unwrapBKey =
126 CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
128 log.debug("Credentials set up after " + (Date.now() - start) + " ms");
129 deferred.resolve(result);
130 }
132 Services.tm.currentThread.dispatch(runnable,
133 Ci.nsIThread.DISPATCH_NORMAL);
134 log.debug("Dispatched thread for credentials setup crypto work");
136 return deferred.promise;
137 }
138 });