michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: Cu.import("resource://gre/modules/Credentials.jsm"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: Cu.import("resource://services-common/utils.js"); michael@0: Cu.import("resource://services-crypto/utils.js"); michael@0: michael@0: let {hexToBytes: h2b, michael@0: hexAsString: h2s, michael@0: stringAsHex: s2h, michael@0: bytesAsHex: b2h} = CommonUtils; michael@0: michael@0: // Test vectors for the "onepw" protocol: michael@0: // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors michael@0: let vectors = { michael@0: "client stretch-KDF": { michael@0: email: michael@0: h("616e6472c3a94065 78616d706c652e6f 7267"), michael@0: password: michael@0: h("70c3a4737377c3b6 7264"), michael@0: quickStretchedPW: michael@0: h("e4e8889bd8bd61ad 6de6b95c059d56e7 b50dacdaf62bd846 44af7e2add84345d"), michael@0: authPW: michael@0: h("247b675ffb4c4631 0bc87e26d712153a be5e1c90ef00a478 4594f97ef54f2375"), michael@0: authSalt: michael@0: h("00f0000000000000 0000000000000000 0000000000000000 0000000000000000"), michael@0: }, michael@0: }; michael@0: michael@0: // A simple test suite with no utf8 encoding madness. michael@0: add_task(function test_onepw_setup_credentials() { michael@0: let email = "francine@example.org"; michael@0: let password = CommonUtils.encodeUTF8("i like pie"); michael@0: michael@0: let pbkdf2 = CryptoUtils.pbkdf2Generate; michael@0: let hkdf = CryptoUtils.hkdf; michael@0: michael@0: // quickStretch the email michael@0: let saltyEmail = Credentials.keyWordExtended("quickStretch", email); michael@0: michael@0: do_check_eq(b2h(saltyEmail), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a6672616e63696e65406578616d706c652e6f7267"); michael@0: michael@0: let pbkdf2Rounds = 1000; michael@0: let pbkdf2Len = 32; michael@0: michael@0: let quickStretchedPW = pbkdf2(password, saltyEmail, pbkdf2Rounds, pbkdf2Len, Ci.nsICryptoHMAC.SHA256, 32); michael@0: let quickStretchedActual = "6b88094c1c73bbf133223f300d101ed70837af48d9d2c1b6e7d38804b20cdde4"; michael@0: do_check_eq(b2h(quickStretchedPW), quickStretchedActual); michael@0: michael@0: // obtain hkdf info michael@0: let authKeyInfo = Credentials.keyWord('authPW'); michael@0: do_check_eq(b2h(authKeyInfo), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f617574685057"); michael@0: michael@0: // derive auth password michael@0: let hkdfSalt = h2b("00"); michael@0: let hkdfLen = 32; michael@0: let authPW = hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen); michael@0: michael@0: do_check_eq(b2h(authPW), "4b8dec7f48e7852658163601ff766124c312f9392af6c3d4e1a247eb439be342"); michael@0: michael@0: // derive unwrap key michael@0: let unwrapKeyInfo = Credentials.keyWord('unwrapBkey'); michael@0: let unwrapKey = hkdf(quickStretchedPW, hkdfSalt, unwrapKeyInfo, hkdfLen); michael@0: michael@0: do_check_eq(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9"); michael@0: }); michael@0: michael@0: add_task(function test_client_stretch_kdf() { michael@0: let pbkdf2 = CryptoUtils.pbkdf2Generate; michael@0: let hkdf = CryptoUtils.hkdf; michael@0: let expected = vectors["client stretch-KDF"]; michael@0: michael@0: let emailUTF8 = h2s(expected.email); michael@0: let passwordUTF8 = h2s(expected.password); michael@0: michael@0: // Intermediate value from sjcl implementation in fxa-js-client michael@0: // The key thing is the c3a9 sequence in "andré" michael@0: let salt = Credentials.keyWordExtended("quickStretch", emailUTF8); michael@0: do_check_eq(b2h(salt), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a616e6472c3a9406578616d706c652e6f7267"); michael@0: michael@0: let options = { michael@0: stretchedPassLength: 32, michael@0: pbkdf2Rounds: 1000, michael@0: hmacAlgorithm: Ci.nsICryptoHMAC.SHA256, michael@0: hmacLength: 32, michael@0: hkdfSalt: h2b("00"), michael@0: hkdfLength: 32, michael@0: }; michael@0: michael@0: let results = yield Credentials.setup(emailUTF8, passwordUTF8, options); michael@0: michael@0: do_check_eq(emailUTF8, results.emailUTF8, michael@0: "emailUTF8 is wrong"); michael@0: michael@0: do_check_eq(passwordUTF8, results.passwordUTF8, michael@0: "passwordUTF8 is wrong"); michael@0: michael@0: do_check_eq(expected.quickStretchedPW, b2h(results.quickStretchedPW), michael@0: "quickStretchedPW is wrong"); michael@0: michael@0: do_check_eq(expected.authPW, b2h(results.authPW), michael@0: "authPW is wrong"); michael@0: }); michael@0: michael@0: // End of tests michael@0: // Utility functions follow michael@0: michael@0: function run_test() { michael@0: run_next_test(); michael@0: } michael@0: michael@0: // turn formatted test vectors into normal hex strings michael@0: function h(hexStr) { michael@0: return hexStr.replace(/\s+/g, ""); michael@0: }