diff --git a/services/common/tests/unit/test_utils_convert_string.js b/services/common/tests/unit/test_utils_convert_string.js index e12520e2c36..bbe94e4027d 100644 --- a/services/common/tests/unit/test_utils_convert_string.js +++ b/services/common/tests/unit/test_utils_convert_string.js @@ -5,24 +5,6 @@ Cu.import("resource://services-common/utils.js"); -// A wise line of Greek verse, and the utf-8 byte encoding. -// N.b., Greek begins at utf-8 ce 91 -const TEST_STR = "πόλλ' οἶδ' ἀλώπηξ, ἀλλ' ἐχῖνος ἓν μέγα"; -const TEST_HEX = h("cf 80 cf 8c ce bb ce bb 27 20 ce bf e1 bc b6 ce"+ - "b4 27 20 e1 bc 80 ce bb cf 8e cf 80 ce b7 ce be"+ - "2c 20 e1 bc 80 ce bb ce bb 27 20 e1 bc 90 cf 87"+ - "e1 bf 96 ce bd ce bf cf 82 20 e1 bc 93 ce bd 20"+ - "ce bc ce ad ce b3 ce b1"); -// Integer byte values for the above -const TEST_BYTES = [207,128,207,140,206,187,206,187, - 39, 32,206,191,225,188,182,206, - 180, 39, 32,225,188,128,206,187, - 207,142,207,128,206,183,206,190, - 44, 32,225,188,128,206,187,206, - 187, 39, 32,225,188,144,207,135, - 225,191,150,206,189,206,191,207, - 130, 32,225,188,147,206,189, 32, - 206,188,206,173,206,179,206,177]; function run_test() { run_next_test(); @@ -71,70 +53,3 @@ add_test(function test_bad_argument() { run_next_test(); }); - -add_task(function test_stringAsHex() { - do_check_eq(TEST_HEX, CommonUtils.stringAsHex(TEST_STR)); - run_next_test(); -}); - -add_task(function test_hexAsString() { - do_check_eq(TEST_STR, CommonUtils.hexAsString(TEST_HEX)); - run_next_test(); -}); - -add_task(function test_hexToBytes() { - let bytes = CommonUtils.hexToBytes(TEST_HEX); - do_check_eq(TEST_BYTES.length, bytes.length); - // Ensure that the decimal values of each byte are correct - do_check_true(arraysEqual(TEST_BYTES, - CommonUtils.stringToByteArray(bytes))); - run_next_test(); -}); - -add_task(function test_bytesToHex() { - // Create a list of our character bytes from the reference int values - let bytes = CommonUtils.byteArrayToString(TEST_BYTES); - do_check_eq(TEST_HEX, CommonUtils.bytesAsHex(bytes)); - run_next_test(); -}); - -add_task(function test_stringToBytes() { - do_check_true(arraysEqual(TEST_BYTES, - CommonUtils.stringToByteArray(CommonUtils.stringToBytes(TEST_STR)))); - run_next_test(); -}); - -add_task(function test_stringRoundTrip() { - do_check_eq(TEST_STR, - CommonUtils.hexAsString(CommonUtils.stringAsHex(TEST_STR))); - run_next_test(); -}); - -add_task(function test_hexRoundTrip() { - do_check_eq(TEST_HEX, - CommonUtils.stringAsHex(CommonUtils.hexAsString(TEST_HEX))); - run_next_test(); -}); - -add_task(function test_byteArrayRoundTrip() { - do_check_true(arraysEqual(TEST_BYTES, - CommonUtils.stringToByteArray(CommonUtils.byteArrayToString(TEST_BYTES)))); - run_next_test(); -}); - -// turn formatted test vectors into normal hex strings -function h(hexStr) { - return hexStr.replace(/\s+/g, ""); -} - -function arraysEqual(a1, a2) { - if (a1.length !== a2.length) { - return false; - } - for (let i = 0; i < a1.length; i++) { - if (a1[i] !== a2[i]) { - return false; - } - } - return true; -} diff --git a/services/common/utils.js b/services/common/utils.js index cb778596b12..b7af8f44058 100644 --- a/services/common/utils.js +++ b/services/common/utils.js @@ -196,21 +196,12 @@ this.CommonUtils = { return [String.fromCharCode(byte) for each (byte in bytes)].join(""); }, - stringToByteArray: function stringToByteArray(bytesString) { - return [String.charCodeAt(byte) for each (byte in bytesString)]; - }, - bytesAsHex: function bytesAsHex(bytes) { - return [("0" + bytes.charCodeAt(byte).toString(16)).slice(-2) - for (byte in bytes)].join(""); - }, - - stringAsHex: function stringAsHex(str) { - return CommonUtils.bytesAsHex(CommonUtils.encodeUTF8(str)); - }, - - stringToBytes: function stringToBytes(str) { - return CommonUtils.hexToBytes(CommonUtils.stringAsHex(str)); + let hex = ""; + for (let i = 0; i < bytes.length; i++) { + hex += ("0" + bytes[i].charCodeAt().toString(16)).slice(-2); + } + return hex; }, hexToBytes: function hexToBytes(str) { @@ -221,10 +212,6 @@ this.CommonUtils = { return String.fromCharCode.apply(String, bytes); }, - hexAsString: function hexAsString(hex) { - return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex)); - }, - /** * Base32 encode (RFC 4648) a string */ diff --git a/services/crypto/modules/utils.js b/services/crypto/modules/utils.js index 751f9d0a347..605b8d3a428 100644 --- a/services/crypto/modules/utils.js +++ b/services/crypto/modules/utils.js @@ -167,24 +167,20 @@ this.CryptoUtils = { * c: the number of iterations, a positive integer: e.g., 4096 * dkLen: the length in octets of the destination * key, a positive integer: e.g., 16 - * hmacAlg: The algorithm to use for hmac - * hmacLen: The hmac length - * - * The default value of 20 for hmacLen is appropriate for SHA1. For SHA256, - * hmacLen should be 32. * * The output is an octet string of length dkLen, which you * can encode as you wish. */ - pbkdf2Generate : function pbkdf2Generate(P, S, c, dkLen, - hmacAlg=Ci.nsICryptoHMAC.SHA1, hmacLen=20) { - + pbkdf2Generate : function pbkdf2Generate(P, S, c, dkLen) { // We don't have a default in the algo itself, as NSS does. // Use the constant. if (!dkLen) { dkLen = SYNC_KEY_DECODED_LENGTH; } + /* For HMAC-SHA-1 */ + const HLEN = 20; + function F(S, c, i, h) { function XOR(a, b, isA) { @@ -220,27 +216,27 @@ this.CryptoUtils = { } ret = U[0]; - for (let j = 1; j < c; j++) { + for (j = 1; j < c; j++) { ret = CommonUtils.byteArrayToString(XOR(ret, U[j])); } return ret; } - let l = Math.ceil(dkLen / hmacLen); - let r = dkLen - ((l - 1) * hmacLen); + let l = Math.ceil(dkLen / HLEN); + let r = dkLen - ((l - 1) * HLEN); // Reuse the key and the hasher. Remaking them 4096 times is 'spensive. - let h = CryptoUtils.makeHMACHasher(hmacAlg, + let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1, CryptoUtils.makeHMACKey(P)); - let T = []; + T = []; for (let i = 0; i < l;) { T[i] = F(S, c, ++i, h); } let ret = ""; - for (let i = 0; i < l-1;) { + for (i = 0; i < l-1;) { ret += T[i++]; } ret += T[l - 1].substr(0, r); diff --git a/services/crypto/tests/unit/test_utils_pbkdf2.js b/services/crypto/tests/unit/test_utils_pbkdf2.js index 9926dc9fec6..192d8edcacb 100644 --- a/services/crypto/tests/unit/test_utils_pbkdf2.js +++ b/services/crypto/tests/unit/test_utils_pbkdf2.js @@ -1,166 +1,15 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -// XXX until bug 937114 is fixed -Cu.importGlobalProperties(['btoa']); +// Evil. +let btoa = Cu.import("resource://services-common/utils.js").btoa; Cu.import("resource://services-crypto/utils.js"); -Cu.import("resource://services-common/utils.js"); - -let {bytesAsHex: b2h} = CommonUtils; function run_test() { - run_next_test(); -} - -add_task(function test_pbkdf2() { let symmKey16 = CryptoUtils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16); do_check_eq(symmKey16.length, 16); do_check_eq(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg=="); do_check_eq(CommonUtils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======"); let symmKey32 = CryptoUtils.pbkdf2Generate("passphrase", "salt", 4096, 32); do_check_eq(symmKey32.length, 32); -}); - -// http://tools.ietf.org/html/rfc6070 -// PBKDF2 HMAC-SHA1 Test Vectors -add_task(function test_pbkdf2_hmac_sha1() { - let pbkdf2 = CryptoUtils.pbkdf2Generate; - let vectors = [ - {P: "password", // (8 octets) - S: "salt", // (4 octets) - c: 1, - dkLen: 20, - DK: h("0c 60 c8 0f 96 1f 0e 71"+ - "f3 a9 b5 24 af 60 12 06"+ - "2f e0 37 a6"), // (20 octets) - }, - - {P: "password", // (8 octets) - S: "salt", // (4 octets) - c: 2, - dkLen: 20, - DK: h("ea 6c 01 4d c7 2d 6f 8c"+ - "cd 1e d9 2a ce 1d 41 f0"+ - "d8 de 89 57"), // (20 octets) - }, - - {P: "password", // (8 octets) - S: "salt", // (4 octets) - c: 4096, - dkLen: 20, - DK: h("4b 00 79 01 b7 65 48 9a"+ - "be ad 49 d9 26 f7 21 d0"+ - "65 a4 29 c1"), // (20 octets) - }, - - // XXX Uncomment the following test after Bug 968567 lands - // - // XXX As it stands, I estimate that the CryptoUtils implementation will - // take approximately 16 hours in my 2.3GHz MacBook to perform this many - // rounds. - // - // {P: "password", // (8 octets) - // S: "salt" // (4 octets) - // c: 16777216, - // dkLen = 20, - // DK: h("ee fe 3d 61 cd 4d a4 e4"+ - // "e9 94 5b 3d 6b a2 15 8c"+ - // "26 34 e9 84"), // (20 octets) - // }, - - {P: "passwordPASSWORDpassword", // (24 octets) - S: "saltSALTsaltSALTsaltSALTsaltSALTsalt", // (36 octets) - c: 4096, - dkLen: 25, - DK: h("3d 2e ec 4f e4 1c 84 9b"+ - "80 c8 d8 36 62 c0 e4 4a"+ - "8b 29 1a 96 4c f2 f0 70"+ - "38"), // (25 octets) - - }, - - {P: "pass\0word", // (9 octets) - S: "sa\0lt", // (5 octets) - c: 4096, - dkLen: 16, - DK: h("56 fa 6a a7 55 48 09 9d"+ - "cc 37 d7 f0 34 25 e0 c3"), // (16 octets) - }, - ]; - - for (let v of vectors) { - do_check_eq(v.DK, b2h(pbkdf2(v.P, v.S, v.c, v.dkLen))); - } - - run_next_test(); -}); - -// I can't find any normative ietf test vectors for pbkdf2 hmac-sha256. -// The following vectors are derived with the same inputs as above (the sha1 -// test). Results verified by users here: -// https://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors -add_task(function test_pbkdf2_hmac_sha256() { - let pbkdf2 = CryptoUtils.pbkdf2Generate; - let vectors = [ - {P: "password", // (8 octets) - S: "salt", // (4 octets) - c: 1, - dkLen: 32, - DK: h("12 0f b6 cf fc f8 b3 2c"+ - "43 e7 22 52 56 c4 f8 37"+ - "a8 65 48 c9 2c cc 35 48"+ - "08 05 98 7c b7 0b e1 7b"), // (32 octets) - }, - - {P: "password", // (8 octets) - S: "salt", // (4 octets) - c: 2, - dkLen: 32, - DK: h("ae 4d 0c 95 af 6b 46 d3"+ - "2d 0a df f9 28 f0 6d d0"+ - "2a 30 3f 8e f3 c2 51 df"+ - "d6 e2 d8 5a 95 47 4c 43"), // (32 octets) - }, - - {P: "password", // (8 octets) - S: "salt", // (4 octets) - c: 4096, - dkLen: 32, - DK: h("c5 e4 78 d5 92 88 c8 41"+ - "aa 53 0d b6 84 5c 4c 8d"+ - "96 28 93 a0 01 ce 4e 11"+ - "a4 96 38 73 aa 98 13 4a"), // (32 octets) - }, - - {P: "passwordPASSWORDpassword", // (24 octets) - S: "saltSALTsaltSALTsaltSALTsaltSALTsalt", // (36 octets) - c: 4096, - dkLen: 40, - DK: h("34 8c 89 db cb d3 2b 2f"+ - "32 d8 14 b8 11 6e 84 cf"+ - "2b 17 34 7e bc 18 00 18"+ - "1c 4e 2a 1f b8 dd 53 e1"+ - "c6 35 51 8c 7d ac 47 e9"), // (40 octets) - }, - - {P: "pass\0word", // (9 octets) - S: "sa\0lt", // (5 octets) - c: 4096, - dkLen: 16, - DK: h("89 b6 9d 05 16 f8 29 89"+ - "3c 69 62 26 65 0a 86 87"), // (16 octets) - }, - ]; - - for (let v of vectors) { - do_check_eq(v.DK, - b2h(pbkdf2(v.P, v.S, v.c, v.dkLen, Ci.nsICryptoHMAC.SHA256, 32))); - } - - run_next_test(); -}); - -// turn formatted test vectors into normal hex strings -function h(hexStr) { - return hexStr.replace(/\s+/g, ""); } diff --git a/services/fxaccounts/Credentials.jsm b/services/fxaccounts/Credentials.jsm deleted file mode 100644 index 4feff93186f..00000000000 --- a/services/fxaccounts/Credentials.jsm +++ /dev/null @@ -1,139 +0,0 @@ -/* 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; -} - -let 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 = { - emailUTF8: emailInput, - passwordUTF8: passwordInput, - }; - - 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; - } -}); - diff --git a/services/fxaccounts/FxAccountsClient.jsm b/services/fxaccounts/FxAccountsClient.jsm index 2c3dad2baae..9dc6a2827f9 100644 --- a/services/fxaccounts/FxAccountsClient.jsm +++ b/services/fxaccounts/FxAccountsClient.jsm @@ -13,7 +13,6 @@ Cu.import("resource://services-common/utils.js"); Cu.import("resource://services-common/hawk.js"); Cu.import("resource://services-crypto/utils.js"); Cu.import("resource://gre/modules/FxAccountsCommon.js"); -Cu.import("resource://gre/modules/Credentials.jsm"); // Default can be changed by the preference 'identity.fxaccounts.auth.uri' let _host = "https://api-accounts.dev.lcip.org/v1"; @@ -22,6 +21,34 @@ try { } catch(keepDefault) {} const HOST = _host; +const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/"; + +function KW(context) { + // This is used as a salt. It's specified by the protocol. Note that the + // value of PROTOCOL_VERSION does not refer in any wy to the version of the + // Firefox Accounts API. For this reason, it is not exposed as a pref. + // + // See: + // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#creating-the-account + return PROTOCOL_VERSION + context; +} + +function stringToHex(str) { + let encoder = new TextEncoder("utf-8"); + let bytes = encoder.encode(str); + return bytesToHex(bytes); +} + +// XXX Sadly, CommonUtils.bytesAsHex doesn't handle typed arrays. +function bytesToHex(bytes) { + let hex = []; + for (let i = 0; i < bytes.length; i++) { + hex.push((bytes[i] >>> 4).toString(16)); + hex.push((bytes[i] & 0xF).toString(16)); + } + return hex.join(""); +} + this.FxAccountsClient = function(host = HOST) { this.host = host; @@ -65,18 +92,23 @@ this.FxAccountsClient.prototype = { * @return Promise * Returns a promise that resolves to an object: * { - * uid: the user's unique ID (hex) - * sessionToken: a session token (hex) - * keyFetchToken: a key fetch token (hex) + * uid: the user's unique ID + * sessionToken: a session token * } */ - signUp: function(email, password) { - return Credentials.setup(email, password).then((creds) => { - let data = { - email: creds.emailUTF8, - authPW: CommonUtils.bytesAsHex(creds.authPW), - }; - return this._request("/account/create", "POST", null, data); + signUp: function (email, password) { + let uid; + let hexEmail = stringToHex(email); + let uidPromise = this._request("/raw_password/account/create", "POST", null, + {email: hexEmail, password: password}); + + return uidPromise.then((result) => { + uid = result.uid; + return this.signIn(email, password) + .then(function(result) { + result.uid = uid; + return result; + }); }); }, @@ -90,20 +122,15 @@ this.FxAccountsClient.prototype = { * @return Promise * Returns a promise that resolves to an object: * { - * uid: the user's unique ID (hex) - * sessionToken: a session token (hex) - * keyFetchToken: a key fetch token (hex) + * uid: the user's unique ID + * sessionToken: a session token * verified: flag indicating verification status of the email * } */ signIn: function signIn(email, password) { - return Credentials.setup(email, password).then((creds) => { - let data = { - email: creds.emailUTF8, - authPW: CommonUtils.bytesAsHex(creds.authPW), - }; - return this._request("/account/login", "POST", null, data); - }); + let hexEmail = stringToHex(email); + return this._request("/raw_password/session/create", "POST", null, + {email: hexEmail, password: password}); }, /** @@ -150,16 +177,15 @@ this.FxAccountsClient.prototype = { * @return Promise * Returns a promise that resolves to an object: * { - * kA: an encryption key for recevorable data (bytes) - * wrapKB: an encryption key that requires knowledge of the - * user's password (bytes) + * kA: an encryption key for recevorable data + * wrapKB: an encryption key that requires knowledge of the user's password * } */ accountKeys: function (keyFetchTokenHex) { let creds = this._deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken"); let keyRequestKey = creds.extra.slice(0, 32); let morecreds = CryptoUtils.hkdf(keyRequestKey, undefined, - Credentials.keyWord("account/keys"), 3 * 32); + KW("account/keys"), 3 * 32); let respHMACKey = morecreds.slice(0, 32); let respXORKey = morecreds.slice(32, 96); @@ -225,25 +251,22 @@ this.FxAccountsClient.prototype = { * if it doesn't. The promise is rejected on other errors. */ accountExists: function (email) { - return this.signIn(email, "").then( - (cantHappen) => { - throw new Error("How did I sign in with an empty password?"); - }, - (expectedError) => { - switch (expectedError.errno) { - case ERRNO_ACCOUNT_DOES_NOT_EXIST: + let hexEmail = stringToHex(email); + return this._request("/auth/start", "POST", null, { email: hexEmail }) + .then( + // the account exists + (result) => true, + (err) => { + log.error("accountExists: error: " + JSON.stringify(err)); + // the account doesn't exist + if (err.errno === 102) { + log.debug("returning false for errno 102"); return false; - break; - case ERRNO_INCORRECT_PASSWORD: - return true; - break; - default: - // not so expected, any more ... - throw expectedError; - break; + } + // propogate other request errors + throw err; } - } - ); + ); }, /** @@ -268,7 +291,7 @@ this.FxAccountsClient.prototype = { */ _deriveHawkCredentials: function (tokenHex, context, size) { let token = CommonUtils.hexToBytes(tokenHex); - let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size || 3 * 32); + let out = CryptoUtils.hkdf(token, undefined, KW(context), size || 3 * 32); return { algorithm: "sha256", @@ -310,13 +333,11 @@ this.FxAccountsClient.prototype = { let response = JSON.parse(responseText); deferred.resolve(response); } catch (err) { - log.error("json parse error on response: " + responseText); deferred.reject({error: err}); } }, (error) => { - log.error("request error: " + JSON.stringify(error)); deferred.reject(error); } ); diff --git a/services/fxaccounts/FxAccountsCommon.js b/services/fxaccounts/FxAccountsCommon.js index 141b1c767d8..649cb945148 100644 --- a/services/fxaccounts/FxAccountsCommon.js +++ b/services/fxaccounts/FxAccountsCommon.js @@ -49,7 +49,7 @@ this.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout"; // Server errno. // From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format this.ERRNO_ACCOUNT_ALREADY_EXISTS = 101; -this.ERRNO_ACCOUNT_DOES_NOT_EXIST = 102; +this.ERRNO_ACCOUNT_DOES_NOT_EXISTS = 102; this.ERRNO_INCORRECT_PASSWORD = 103; this.ERRNO_UNVERIFIED_ACCOUNT = 104; this.ERRNO_INVALID_VERIFICATION_CODE = 105; @@ -68,7 +68,7 @@ this.ERRNO_UNKNOWN_ERROR = 999; // Errors. this.ERROR_ACCOUNT_ALREADY_EXISTS = "ACCOUNT_ALREADY_EXISTS"; -this.ERROR_ACCOUNT_DOES_NOT_EXIST = "ACCOUNT_DOES_NOT_EXIST"; +this.ERROR_ACCOUNT_DOES_NOT_EXISTS = "ACCOUNT_DOES_NOT_EXISTS"; this.ERROR_ALREADY_SIGNED_IN_USER = "ALREADY_SIGNED_IN_USER"; this.ERROR_INVALID_ACCOUNTID = "INVALID_ACCOUNTID"; this.ERROR_INVALID_AUDIENCE = "INVALID_AUDIENCE"; @@ -96,7 +96,7 @@ this.ERROR_UNVERIFIED_ACCOUNT = "UNVERIFIED_ACCOUNT"; // Error matching. this.SERVER_ERRNO_TO_ERROR = {}; SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_ALREADY_EXISTS] = ERROR_ACCOUNT_ALREADY_EXISTS; -SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_DOES_NOT_EXIST] = ERROR_ACCOUNT_DOES_NOT_EXIST; +SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_DOES_NOT_EXISTS] = ERROR_ACCOUNT_DOES_NOT_EXISTS; SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_PASSWORD] = ERROR_INVALID_PASSWORD; SERVER_ERRNO_TO_ERROR[ERRNO_UNVERIFIED_ACCOUNT] = ERROR_UNVERIFIED_ACCOUNT; SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_VERIFICATION_CODE] = ERROR_INVALID_VERIFICATION_CODE; diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build index 2d87998efbe..3b77600007d 100644 --- a/services/fxaccounts/moz.build +++ b/services/fxaccounts/moz.build @@ -9,7 +9,6 @@ PARALLEL_DIRS += ['interfaces'] TEST_DIRS += ['tests'] EXTRA_JS_MODULES += [ - 'Credentials.jsm', 'FxAccounts.jsm', 'FxAccountsClient.jsm', 'FxAccountsCommon.js', diff --git a/services/fxaccounts/tests/xpcshell/test_client.js b/services/fxaccounts/tests/xpcshell/test_client.js index ca61445e0fb..751ee761922 100644 --- a/services/fxaccounts/tests/xpcshell/test_client.js +++ b/services/fxaccounts/tests/xpcshell/test_client.js @@ -4,7 +4,6 @@ Cu.import("resource://gre/modules/FxAccountsClient.jsm"); Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://services-common/utils.js"); -Cu.import("resource://services-crypto/utils.js"); const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"; @@ -102,396 +101,131 @@ add_task(function test_500_error() { yield deferredStop(server); }); -add_task(function test_signUp() { - let creationMessage = JSON.stringify({ - uid: "uid", - sessionToken: "sessionToken", - keyFetchToken: "keyFetchToken" - }); - let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"}); - let created = false; - - let server = httpd_setup({ - "/account/create": function(request, response) { - let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); - let jsonBody = JSON.parse(body); - - // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors - do_check_eq(jsonBody.email, "andré@example.org"); - - if (!created) { - do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375"); - created = true; - - response.setStatusLine(request.httpVersion, 200, "OK"); - return response.bodyOutputStream.write(creationMessage, creationMessage.length); - } - - // Error trying to create same account a second time - response.setStatusLine(request.httpVersion, 400, "Bad request"); - return response.bodyOutputStream.write(errorMessage, errorMessage.length); - }, - }); - - let client = new FxAccountsClient(server.baseURI); - let result = yield client.signUp('andré@example.org', 'pässwörd'); - do_check_eq("uid", result.uid); - do_check_eq("sessionToken", result.sessionToken); - do_check_eq("keyFetchToken", result.keyFetchToken); - - // Try to create account again. Triggers error path. - try { - result = yield client.signUp('andré@example.org', 'pässwörd'); - } catch(expectedError) { - do_check_eq(101, expectedError.errno); - } - - yield deferredStop(server); - run_next_test(); -}); - -add_task(function test_signIn() { - let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN}); - let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); - let server = httpd_setup({ - "/account/login": function(request, response) { - let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); - let jsonBody = JSON.parse(body); - - if (jsonBody.email == "mé@example.com") { - do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6"); - response.setStatusLine(request.httpVersion, 200, "OK"); - return response.bodyOutputStream.write(sessionMessage, sessionMessage.length); - } - - // Error trying to sign in to nonexistent account - response.setStatusLine(request.httpVersion, 400, "Bad request"); - return response.bodyOutputStream.write(errorMessage, errorMessage.length); - }, - }); - - let client = new FxAccountsClient(server.baseURI); - let result = yield client.signIn('mé@example.com', 'bigsecret'); - do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); - - // Trigger error path - try { - result = yield client.signIn("yøü@bad.example.org", "nofear"); - } catch(expectedError) { - do_check_eq(102, expectedError.errno); - } - - yield deferredStop(server); - run_next_test(); -}); - -add_task(function test_signOut() { +add_task(function test_api_endpoints() { + let sessionMessage = JSON.stringify({sessionToken: "NotARealToken"}); + let creationMessage = JSON.stringify({uid: "NotARealUid"}); let signoutMessage = JSON.stringify({}); - let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); - let signedOut = false; + let certSignMessage = JSON.stringify({cert: {bar: "baz"}}); + let emailStatus = JSON.stringify({verified: true}); - let server = httpd_setup({ - "/session/destroy": function(request, response) { - if (!signedOut) { - signedOut = true; + let authStarts = 0; + + function writeResp(response, msg) { + response.bodyOutputStream.write(msg, msg.length); + } + + let server = httpd_setup( + { + "/raw_password/account/create": function(request, response) { + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); + let jsonBody = JSON.parse(body); + do_check_eq(jsonBody.email, "796f75406578616d706c652e636f6d"); + do_check_eq(jsonBody.password, "biggersecret"); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(creationMessage, creationMessage.length); + }, + "/raw_password/session/create": function(request, response) { + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); + let jsonBody = JSON.parse(body); + if (jsonBody.password === "bigsecret") { + do_check_eq(jsonBody.email, "6dc3a9406578616d706c652e636f6d"); + } else if (jsonBody.password === "biggersecret") { + do_check_eq(jsonBody.email, "796f75406578616d706c652e636f6d"); + } + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(sessionMessage, sessionMessage.length); + }, + "/recovery_email/status": function(request, response) { do_check_true(request.hasHeader("Authorization")); response.setStatusLine(request.httpVersion, 200, "OK"); - return response.bodyOutputStream.write(signoutMessage, signoutMessage.length); - } - - // Error trying to sign out of nonexistent account - response.setStatusLine(request.httpVersion, 400, "Bad request"); - return response.bodyOutputStream.write(errorMessage, errorMessage.length); - }, - }); - - let client = new FxAccountsClient(server.baseURI); - let result = yield client.signOut("FakeSession"); - do_check_eq(typeof result, "object"); - - // Trigger error path - try { - result = yield client.signOut("FakeSession"); - } catch(expectedError) { - do_check_eq(102, expectedError.errno); - } - - yield deferredStop(server); - run_next_test(); -}); - -add_task(function test_recoveryEmailStatus() { - let emailStatus = JSON.stringify({verified: true}); - let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); - let tries = 0; - - let server = httpd_setup({ - "/recovery_email/status": function(request, response) { - do_check_true(request.hasHeader("Authorization")); - - if (tries === 0) { + response.bodyOutputStream.write(emailStatus, emailStatus.length); + }, + "/session/destroy": function(request, response) { + do_check_true(request.hasHeader("Authorization")); response.setStatusLine(request.httpVersion, 200, "OK"); - return response.bodyOutputStream.write(emailStatus, emailStatus.length); - } - - // Second call gets an error trying to query a nonexistent account - response.setStatusLine(request.httpVersion, 400, "Bad request"); - return response.bodyOutputStream.write(errorMessage, errorMessage.length); - }, - }); - - let client = new FxAccountsClient(server.baseURI); - let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN); - do_check_eq(result.verified, true); - - // Trigger error path - try { - result = yield client.recoveryEmailStatus("some bogus session"); - } catch(expectedError) { - do_check_eq(102, expectedError.errno); - } - - yield deferredStop(server); - run_next_test(); -}); - -add_task(function test_resendVerificationEmail() { - let emptyMessage = "{}"; - let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); - let tries = 0; - - let server = httpd_setup({ - "/recovery_email/resend_code": function(request, response) { - do_check_true(request.hasHeader("Authorization")); - if (tries === 0) { - response.setStatusLine(request.httpVersion, 200, "OK"); - return response.bodyOutputStream.write(emptyMessage, emptyMessage.length); - } - - // Second call gets an error trying to query a nonexistent account - response.setStatusLine(request.httpVersion, 400, "Bad request"); - return response.bodyOutputStream.write(errorMessage, errorMessage.length); - }, - }); - - let client = new FxAccountsClient(server.baseURI); - let result = yield client.resendVerificationEmail(FAKE_SESSION_TOKEN); - do_check_eq(JSON.stringify(result), emptyMessage); - - // Trigger error path - try { - result = yield client.resendVerificationEmail("some bogus session"); - } catch(expectedError) { - do_check_eq(102, expectedError.errno); - } - - yield deferredStop(server); - run_next_test(); -}); - -add_task(function test_accountKeys() { - // Vectors: https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys - - let keyFetch = h("8081828384858687 88898a8b8c8d8e8f"+ - "9091929394959697 98999a9b9c9d9e9f"); - - let response = h("ee5c58845c7c9412 b11bbd20920c2fdd"+ - "d83c33c9cd2c2de2 d66b222613364636"+ - "c2c0f8cfbb7c6304 72c0bd88451342c6"+ - "c05b14ce342c5ad4 6ad89e84464c993c"+ - "3927d30230157d08 17a077eef4b20d97"+ - "6f7a97363faf3f06 4c003ada7d01aa70"); - - let kA = h("2021222324252627 28292a2b2c2d2e2f"+ - "3031323334353637 38393a3b3c3d3e3f"); - - let wrapKB = h("4041424344454647 48494a4b4c4d4e4f"+ - "5051525354555657 58595a5b5c5d5e5f"); - - let responseMessage = JSON.stringify({bundle: response}); - let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); - let emptyMessage = "{}"; - let attempt = 0; - - let server = httpd_setup({ - "/account/keys": function(request, response) { - do_check_true(request.hasHeader("Authorization")); - attempt += 1; - - switch(attempt) { - case 1: - // First time succeeds - response.setStatusLine(request.httpVersion, 200, "OK"); - response.bodyOutputStream.write(responseMessage, responseMessage.length); - break; - - case 2: - // Second time, return no bundle to trigger client error - response.setStatusLine(request.httpVersion, 200, "OK"); - response.bodyOutputStream.write(emptyMessage, emptyMessage.length); - break; - - case 3: - // Return gibberish to trigger client MAC error - let garbage = response; - garbage[0] = 0; // tweak a byte - response.setStatusLine(request.httpVersion, 200, "OK"); - response.bodyOutputStream.write(responseMessage, responseMessage.length); - break; - - case 4: - // Trigger error for nonexistent account - response.setStatusLine(request.httpVersion, 400, "Bad request"); - response.bodyOutputStream.write(errorMessage, errorMessage.length); - break; - } - }, - }); - - let client = new FxAccountsClient(server.baseURI); - - // First try, all should be good - let result = yield client.accountKeys(keyFetch); - do_check_eq(CommonUtils.hexToBytes(kA), result.kA); - do_check_eq(CommonUtils.hexToBytes(wrapKB), result.wrapKB); - - // Second try, empty bundle should trigger error - try { - result = yield client.accountKeys(keyFetch); - } catch(expectedError) { - do_check_eq(expectedError.message, "failed to retrieve keys"); - } - - // Third try, bad bundle results in MAC error - try { - result = yield client.accountKeys(keyFetch); - } catch(expectedError) { - do_check_eq(expectedError.message, "error unbundling encryption keys"); - } - - // Fourth try, pretend account doesn't exist - try { - result = yield client.accountKeys(keyFetch); - } catch(expectedError) { - do_check_eq(102, expectedError.errno); - } - - yield deferredStop(server); - run_next_test(); -}); - -add_task(function test_signCertificate() { - let certSignMessage = JSON.stringify({cert: {bar: "baz"}}); - let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); - let tries = 0; - - let server = httpd_setup({ - "/certificate/sign": function(request, response) { - do_check_true(request.hasHeader("Authorization")); - - if (tries === 0) { - tries += 1; + response.bodyOutputStream.write(signoutMessage, signoutMessage.length); + }, + "/certificate/sign": function(request, response) { + do_check_true(request.hasHeader("Authorization")); let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); let jsonBody = JSON.parse(body); do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar"); do_check_eq(jsonBody.duration, 600); response.setStatusLine(request.httpVersion, 200, "OK"); - return response.bodyOutputStream.write(certSignMessage, certSignMessage.length); - } - - // Second attempt, trigger error - response.setStatusLine(request.httpVersion, 400, "Bad request"); - response.bodyOutputStream.write(errorMessage, errorMessage.length); - }, - }); + response.bodyOutputStream.write(certSignMessage, certSignMessage.length); + }, + "/auth/start": function(request, response) { + if (authStarts === 0) { + response.setStatusLine(request.httpVersion, 200, "OK"); + writeResp(response, JSON.stringify({})); + } else if (authStarts === 1) { + response.setStatusLine(request.httpVersion, 400, "NOT OK"); + writeResp(response, JSON.stringify({errno: 102, error: "no such account"})); + } else if (authStarts === 2) { + response.setStatusLine(request.httpVersion, 400, "NOT OK"); + writeResp(response, JSON.stringify({errno: 107, error: "boom"})); + } + authStarts++; + }, + } + ); let client = new FxAccountsClient(server.baseURI); - let result = yield client.signCertificate(FAKE_SESSION_TOKEN, JSON.stringify({foo: "bar"}), 600); + let result = undefined; + + result = yield client.signUp('you@example.com', 'biggersecret'); + do_check_eq("NotARealUid", result.uid); + + result = yield client.signIn('mé@example.com', 'bigsecret'); + do_check_eq("NotARealToken", result.sessionToken); + + result = yield client.signOut(FAKE_SESSION_TOKEN); + do_check_eq(typeof result, "object"); + + result = yield client.recoveryEmailStatus('NotARealToken'); + do_check_eq(result.verified, true); + + result = yield client.signCertificate('NotARealToken', JSON.stringify({foo: "bar"}), 600); do_check_eq("baz", result.bar); - // Account doesn't exist + result = yield client.accountExists('hey@example.com'); + do_check_eq(result, true); + result = yield client.accountExists('hey2@example.com'); + do_check_eq(result, false); try { - result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600); - } catch(expectedError) { - do_check_eq(102, expectedError.errno); + result = yield client.accountExists('hey3@example.com'); + } catch(e) { + do_check_eq(e.errno, 107); } yield deferredStop(server); - run_next_test(); }); -add_task(function test_accountExists() { - let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN}); - let existsMessage = JSON.stringify({error: "wrong password", code: 400, errno: 103}); - let doesntExistMessage = JSON.stringify({error: "no such account", code: 400, errno: 102}); - let emptyMessage = "{}"; +add_task(function test_error_response() { + let errorMessage = JSON.stringify({error: "Oops", code: 400, errno: 99}); - let server = httpd_setup({ - "/account/login": function(request, response) { - let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); - let jsonBody = JSON.parse(body); + let server = httpd_setup( + { + "/raw_password/session/create": function(request, response) { + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); - switch (jsonBody.email) { - // We'll test that these users' accounts exist - case "i.exist@example.com": - case "i.also.exist@example.com": - response.setStatusLine(request.httpVersion, 400, "Bad request"); - response.bodyOutputStream.write(existsMessage, existsMessage.length); - break; - - // This user's account doesn't exist - case "i.dont.exist@example.com": - response.setStatusLine(request.httpVersion, 400, "Bad request"); - response.bodyOutputStream.write(doesntExistMessage, doesntExistMessage.length); - break; - - // This user throws an unexpected response - // This will reject the client signIn promise - case "i.break.things@example.com": - response.setStatusLine(request.httpVersion, 500, "Alas"); - response.bodyOutputStream.write(emptyMessage, emptyMessage.length); - break; - - default: - throw new Error("Unexpected login from " + jsonBody.email); - break; - } - }, - }); + response.setStatusLine(request.httpVersion, 400, "NOT OK"); + response.bodyOutputStream.write(errorMessage, errorMessage.length); + }, + } + ); let client = new FxAccountsClient(server.baseURI); - let result; try { - result = yield client.accountExists("i.exist@example.com"); - } catch(expectedError) { - do_check_eq(expectedError.code, 400); - do_check_eq(expectedError.errno, 103); - } - - try { - result = yield client.accountExists("i.also.exist@example.com"); - } catch(expectedError) { - do_check_eq(expectedError.errno, 103); - } - - try { - result = yield client.accountExists("i.dont.exist@example.com"); - } catch(expectedError) { - do_check_eq(expectedError.errno, 102); - } - - try { - result = yield client.accountExists("i.break.things@example.com"); - } catch(unexpectedError) { - do_check_eq(unexpectedError.code, 500); + let result = yield client.signIn('mé@example.com', 'bigsecret'); + } catch(result) { + do_check_eq("Oops", result.error); + do_check_eq(400, result.code); + do_check_eq(99, result.errno); } yield deferredStop(server); - run_next_test(); }); - -// turn formatted test vectors into normal hex strings -function h(hexStr) { - return hexStr.replace(/\s+/g, ""); -} diff --git a/services/fxaccounts/tests/xpcshell/test_credentials.js b/services/fxaccounts/tests/xpcshell/test_credentials.js deleted file mode 100644 index 8c2f9030ff9..00000000000 --- a/services/fxaccounts/tests/xpcshell/test_credentials.js +++ /dev/null @@ -1,120 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://gre/modules/Credentials.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://services-common/utils.js"); -Cu.import("resource://services-crypto/utils.js"); - -let {hexToBytes: h2b, - hexAsString: h2s, - stringAsHex: s2h, - bytesAsHex: b2h} = CommonUtils; - -// Test vectors for the "onepw" protocol: -// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors -let vectors = { - "client stretch-KDF": { - email: - h("616e6472c3a94065 78616d706c652e6f 7267"), - password: - h("70c3a4737377c3b6 7264"), - quickStretchedPW: - h("e4e8889bd8bd61ad 6de6b95c059d56e7 b50dacdaf62bd846 44af7e2add84345d"), - authPW: - h("247b675ffb4c4631 0bc87e26d712153a be5e1c90ef00a478 4594f97ef54f2375"), - authSalt: - h("00f0000000000000 0000000000000000 0000000000000000 0000000000000000"), - }, -}; - -// A simple test suite with no utf8 encoding madness. -add_task(function test_onepw_setup_credentials() { - let email = "francine@example.org"; - let password = CommonUtils.encodeUTF8("i like pie"); - - let pbkdf2 = CryptoUtils.pbkdf2Generate; - let hkdf = CryptoUtils.hkdf; - - // quickStretch the email - let saltyEmail = Credentials.keyWordExtended("quickStretch", email); - - do_check_eq(b2h(saltyEmail), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a6672616e63696e65406578616d706c652e6f7267"); - - let pbkdf2Rounds = 1000; - let pbkdf2Len = 32; - - let quickStretchedPW = pbkdf2(password, saltyEmail, pbkdf2Rounds, pbkdf2Len, Ci.nsICryptoHMAC.SHA256, 32); - let quickStretchedActual = "6b88094c1c73bbf133223f300d101ed70837af48d9d2c1b6e7d38804b20cdde4"; - do_check_eq(b2h(quickStretchedPW), quickStretchedActual); - - // obtain hkdf info - let authKeyInfo = Credentials.keyWord('authPW'); - do_check_eq(b2h(authKeyInfo), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f617574685057"); - - // derive auth password - let hkdfSalt = h2b("00"); - let hkdfLen = 32; - let authPW = hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen); - - do_check_eq(b2h(authPW), "4b8dec7f48e7852658163601ff766124c312f9392af6c3d4e1a247eb439be342"); - - // derive unwrap key - let unwrapKeyInfo = Credentials.keyWord('unwrapBkey'); - let unwrapKey = hkdf(quickStretchedPW, hkdfSalt, unwrapKeyInfo, hkdfLen); - - do_check_eq(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9"); - - run_next_test(); -}); - -add_task(function test_client_stretch_kdf() { - let pbkdf2 = CryptoUtils.pbkdf2Generate; - let hkdf = CryptoUtils.hkdf; - let expected = vectors["client stretch-KDF"]; - - let emailUTF8 = h2s(expected.email); - let passwordUTF8 = h2s(expected.password); - - // Intermediate value from sjcl implementation in fxa-js-client - // The key thing is the c3a9 sequence in "andré" - let salt = Credentials.keyWordExtended("quickStretch", emailUTF8); - do_check_eq(b2h(salt), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a616e6472c3a9406578616d706c652e6f7267"); - - let options = { - stretchedPassLength: 32, - pbkdf2Rounds: 1000, - hmacAlgorithm: Ci.nsICryptoHMAC.SHA256, - hmacLength: 32, - hkdfSalt: h2b("00"), - hkdfLength: 32, - }; - - let results = yield Credentials.setup(emailUTF8, passwordUTF8, options); - - do_check_eq(emailUTF8, results.emailUTF8, - "emailUTF8 is wrong"); - - do_check_eq(passwordUTF8, results.passwordUTF8, - "passwordUTF8 is wrong"); - - do_check_eq(expected.quickStretchedPW, b2h(results.quickStretchedPW), - "quickStretchedPW is wrong"); - - do_check_eq(expected.authPW, b2h(results.authPW), - "authPW is wrong"); - - run_next_test(); -}); - -// End of tests -// Utility functions follow - -function run_test() { - run_next_test(); -} - -// turn formatted test vectors into normal hex strings -function h(hexStr) { - return hexStr.replace(/\s+/g, ""); -} diff --git a/services/fxaccounts/tests/xpcshell/xpcshell.ini b/services/fxaccounts/tests/xpcshell/xpcshell.ini index 9d9bffe1254..8b09ed856fe 100644 --- a/services/fxaccounts/tests/xpcshell/xpcshell.ini +++ b/services/fxaccounts/tests/xpcshell/xpcshell.ini @@ -4,7 +4,6 @@ tail = [test_accounts.js] [test_client.js] -[test_credentials.js] [test_manager.js] run-if = appname == 'b2g' reason = FxAccountsManager is only available for B2G for now