summaryrefslogtreecommitdiff
path: root/services/fxaccounts/Credentials.jsm
blob: 56e8b3db78cd95b4f81fb062cdce9969f45b5623 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* 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;
}

var 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 = {};

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