summaryrefslogtreecommitdiffstats
path: root/services/crypto/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'services/crypto/tests/unit')
-rw-r--r--services/crypto/tests/unit/head_helpers.js55
-rw-r--r--services/crypto/tests/unit/test_crypto_crypt.js213
-rw-r--r--services/crypto/tests/unit/test_crypto_deriveKey.js28
-rw-r--r--services/crypto/tests/unit/test_crypto_random.js58
-rw-r--r--services/crypto/tests/unit/test_load_modules.js16
-rw-r--r--services/crypto/tests/unit/test_utils_hawk.js301
-rw-r--r--services/crypto/tests/unit/test_utils_hkdfExpand.js120
-rw-r--r--services/crypto/tests/unit/test_utils_httpmac.js69
-rw-r--r--services/crypto/tests/unit/test_utils_pbkdf2.js162
-rw-r--r--services/crypto/tests/unit/test_utils_sha1.js37
-rw-r--r--services/crypto/tests/unit/xpcshell.ini20
11 files changed, 1079 insertions, 0 deletions
diff --git a/services/crypto/tests/unit/head_helpers.js b/services/crypto/tests/unit/head_helpers.js
new file mode 100644
index 000000000..70522fc38
--- /dev/null
+++ b/services/crypto/tests/unit/head_helpers.js
@@ -0,0 +1,55 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+try {
+ // In the context of xpcshell tests, there won't be a default AppInfo
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
+}
+catch(ex) {
+
+// Make sure to provide the right OS so crypto loads the right binaries
+var OS = "XPCShell";
+if (mozinfo.os == "win")
+ OS = "WINNT";
+else if (mozinfo.os == "mac")
+ OS = "Darwin";
+else
+ OS = "Linux";
+
+Cu.import("resource://testing-common/AppInfo.jsm", this);
+updateAppInfo({
+ name: "XPCShell",
+ ID: "{3e3ba16c-1675-4e88-b9c8-afef81b3d2ef}",
+ version: "1",
+ platformVersion: "",
+ OS: OS,
+});
+}
+
+// Register resource alias. Normally done in SyncComponents.manifest.
+function addResourceAlias() {
+ Cu.import("resource://gre/modules/Services.jsm");
+ const resProt = Services.io.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+ let uri = Services.io.newURI("resource://gre/modules/services-crypto/",
+ null, null);
+ resProt.setSubstitution("services-crypto", uri);
+}
+addResourceAlias();
+
+/**
+ * Print some debug message to the console. All arguments will be printed,
+ * separated by spaces.
+ *
+ * @param [arg0, arg1, arg2, ...]
+ * Any number of arguments to print out
+ * @usage _("Hello World") -> prints "Hello World"
+ * @usage _(1, 2, 3) -> prints "1 2 3"
+ */
+var _ = function(some, debug, text, to) {
+ print(Array.slice(arguments).join(" "));
+};
diff --git a/services/crypto/tests/unit/test_crypto_crypt.js b/services/crypto/tests/unit/test_crypto_crypt.js
new file mode 100644
index 000000000..fcea21d00
--- /dev/null
+++ b/services/crypto/tests/unit/test_crypto_crypt.js
@@ -0,0 +1,213 @@
+Cu.import("resource://services-crypto/WeaveCrypto.js");
+Cu.importGlobalProperties(['crypto']);
+
+var cryptoSvc = new WeaveCrypto();
+
+add_task(function* test_key_memoization() {
+ let cryptoGlobal = cryptoSvc._getCrypto();
+ let oldImport = cryptoGlobal.subtle.importKey;
+ if (!oldImport) {
+ _("Couldn't swizzle crypto.subtle.importKey; returning.");
+ return;
+ }
+
+ let iv = cryptoSvc.generateRandomIV();
+ let key = cryptoSvc.generateRandomKey();
+ let c = 0;
+ cryptoGlobal.subtle.importKey = function(format, keyData, algo, extractable, usages) {
+ c++;
+ return oldImport.call(cryptoGlobal.subtle, format, keyData, algo, extractable, usages);
+ }
+
+ // Encryption should cause a single counter increment.
+ do_check_eq(c, 0);
+ let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
+ do_check_eq(c, 1);
+ cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
+ do_check_eq(c, 1);
+
+ // ... as should decryption.
+ cryptoSvc.decrypt(cipherText, key, iv);
+ cryptoSvc.decrypt(cipherText, key, iv);
+ cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(c, 2);
+
+ // Un-swizzle.
+ cryptoGlobal.subtle.importKey = oldImport;
+});
+
+// Just verify that it gets populated with the correct bytes.
+add_task(function* test_makeUint8Array() {
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+ let item1 = cryptoSvc.makeUint8Array("abcdefghi", false);
+ do_check_true(item1);
+ for (let i = 0; i < 8; ++i)
+ do_check_eq(item1[i], "abcdefghi".charCodeAt(i));
+});
+
+add_task(function* test_encrypt_decrypt() {
+ // First, do a normal run with expected usage... Generate a random key and
+ // iv, encrypt and decrypt a string.
+ var iv = cryptoSvc.generateRandomIV();
+ do_check_eq(iv.length, 24);
+
+ var key = cryptoSvc.generateRandomKey();
+ do_check_eq(key.length, 44);
+
+ var mySecret = "bacon is a vegetable";
+ var cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ do_check_eq(cipherText.length, 44);
+
+ var clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(clearText.length, 20);
+
+ // Did the text survive the encryption round-trip?
+ do_check_eq(clearText, mySecret);
+ do_check_neq(cipherText, mySecret); // just to be explicit
+
+
+ // Do some more tests with a fixed key/iv, to check for reproducable results.
+ key = "St1tFCor7vQEJNug/465dQ==";
+ iv = "oLjkfrLIOnK2bDRvW4kXYA==";
+
+ _("Testing small IV.");
+ mySecret = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=";
+ let shortiv = "YWJj";
+ let err;
+ try {
+ cryptoSvc.encrypt(mySecret, key, shortiv);
+ } catch (ex) {
+ err = ex;
+ }
+ do_check_true(!!err);
+
+ _("Testing long IV.");
+ let longiv = "gsgLRDaxWvIfKt75RjuvFWERt83FFsY2A0TW+0b2iVk=";
+ try {
+ cryptoSvc.encrypt(mySecret, key, longiv);
+ } catch (ex) {
+ err = ex;
+ }
+ do_check_true(!!err);
+
+ // Test small input sizes
+ mySecret = "";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "OGQjp6mK1a3fs9k9Ml4L3w==");
+ do_check_eq(clearText, mySecret);
+
+ mySecret = "x";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "96iMl4vhOxFUW/lVHHzVqg==");
+ do_check_eq(clearText, mySecret);
+
+ mySecret = "xx";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "olpPbETRYROCSqFWcH2SWg==");
+ do_check_eq(clearText, mySecret);
+
+ mySecret = "xxx";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "rRbpHGyVSZizLX/x43Wm+Q==");
+ do_check_eq(clearText, mySecret);
+
+ mySecret = "xxxx";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "HeC7miVGDcpxae9RmiIKAw==");
+ do_check_eq(clearText, mySecret);
+
+ // Test non-ascii input
+ // ("testuser1" using similar-looking glyphs)
+ mySecret = String.fromCharCode(355, 277, 349, 357, 533, 537, 101, 345, 185);
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "Pj4ixByXoH3SU3JkOXaEKPgwRAWplAWFLQZkpJd5Kr4=");
+ do_check_eq(clearText, mySecret);
+
+ // Tests input spanning a block boundary (AES block size is 16 bytes)
+ mySecret = "123456789012345";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "e6c5hwphe45/3VN/M0bMUA==");
+ do_check_eq(clearText, mySecret);
+
+ mySecret = "1234567890123456";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "V6aaOZw8pWlYkoIHNkhsP1JOIQF87E2vTUvBUQnyV04=");
+ do_check_eq(clearText, mySecret);
+
+ mySecret = "12345678901234567";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "V6aaOZw8pWlYkoIHNkhsP5GvxWJ9+GIAS6lXw+5fHTI=");
+ do_check_eq(clearText, mySecret);
+
+
+ key = "iz35tuIMq4/H+IYw2KTgow==";
+ iv = "TJYrvva2KxvkM8hvOIvWp3==";
+ mySecret = "i like pie";
+
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "DLGx8BWqSCLGG7i/xwvvxg==");
+ do_check_eq(clearText, mySecret);
+
+ key = "c5hG3YG+NC61FFy8NOHQak1ZhMEWO79bwiAfar2euzI=";
+ iv = "gsgLRDaxWvIfKt75RjuvFW==";
+ mySecret = "i like pie";
+
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ clearText = cryptoSvc.decrypt(cipherText, key, iv);
+ do_check_eq(cipherText, "o+ADtdMd8ubzNWurS6jt0Q==");
+ do_check_eq(clearText, mySecret);
+
+ key = "St1tFCor7vQEJNug/465dQ==";
+ iv = "oLjkfrLIOnK2bDRvW4kXYA==";
+ mySecret = "does thunder read testcases?";
+ cipherText = cryptoSvc.encrypt(mySecret, key, iv);
+ do_check_eq(cipherText, "T6fik9Ros+DB2ablH9zZ8FWZ0xm/szSwJjIHZu7sjPs=");
+
+ var badkey = "badkeybadkeybadkeybadk==";
+ var badiv = "badivbadivbadivbadivbad=";
+ var badcipher = "crapinputcrapinputcrapinputcrapinputcrapinp=";
+ var failure;
+
+ try {
+ failure = false;
+ clearText = cryptoSvc.decrypt(cipherText, badkey, iv);
+ } catch (e) {
+ failure = true;
+ }
+ do_check_true(failure);
+
+ try {
+ failure = false;
+ clearText = cryptoSvc.decrypt(cipherText, key, badiv);
+ } catch (e) {
+ failure = true;
+ }
+ do_check_true(failure);
+
+ try {
+ failure = false;
+ clearText = cryptoSvc.decrypt(cipherText, badkey, badiv);
+ } catch (e) {
+ failure = true;
+ }
+ do_check_true(failure);
+
+ try {
+ failure = false;
+ clearText = cryptoSvc.decrypt(badcipher, key, iv);
+ } catch (e) {
+ failure = true;
+ }
+ do_check_true(failure);
+});
diff --git a/services/crypto/tests/unit/test_crypto_deriveKey.js b/services/crypto/tests/unit/test_crypto_deriveKey.js
new file mode 100644
index 000000000..00af474cb
--- /dev/null
+++ b/services/crypto/tests/unit/test_crypto_deriveKey.js
@@ -0,0 +1,28 @@
+Components.utils.import("resource://services-crypto/WeaveCrypto.js");
+
+function run_test() {
+ let cryptoSvc = new WeaveCrypto();
+ // Extracted from test_utils_deriveKey.
+ let pp = "secret phrase";
+ let salt = "RE5YUHpQcGl3bg=="; // btoa("DNXPzPpiwn")
+
+ // 16-byte, extract key data.
+ let k = cryptoSvc.deriveKeyFromPassphrase(pp, salt, 16);
+ do_check_eq(16, k.length);
+ do_check_eq(btoa(k), "d2zG0d2cBfXnRwMUGyMwyg==");
+
+ // Test different key lengths.
+ k = cryptoSvc.deriveKeyFromPassphrase(pp, salt, 32);
+ do_check_eq(32, k.length);
+ do_check_eq(btoa(k), "d2zG0d2cBfXnRwMUGyMwyroRXtnrSIeLwSDvReSfcyA=");
+ let encKey = btoa(k);
+
+ // Test via encryption.
+ let iv = cryptoSvc.generateRandomIV();
+ do_check_eq(cryptoSvc.decrypt(cryptoSvc.encrypt("bacon", encKey, iv), encKey, iv), "bacon");
+
+ // Test default length (32).
+ k = cryptoSvc.deriveKeyFromPassphrase(pp, salt);
+ do_check_eq(32, k.length);
+ do_check_eq(encKey, btoa(k));
+}
diff --git a/services/crypto/tests/unit/test_crypto_random.js b/services/crypto/tests/unit/test_crypto_random.js
new file mode 100644
index 000000000..46b4c7f82
--- /dev/null
+++ b/services/crypto/tests/unit/test_crypto_random.js
@@ -0,0 +1,58 @@
+var WeaveCryptoModule = Cu.import("resource://services-crypto/WeaveCrypto.js");
+
+var cryptoSvc = new WeaveCrypto();
+
+function run_test() {
+ if (this.gczeal) {
+ _("Running crypto random tests with gczeal(2).");
+ gczeal(2);
+ }
+
+ // Test salt generation.
+ var salt;
+
+ salt = cryptoSvc.generateRandomBytes(0);
+ do_check_eq(salt.length, 0);
+ salt = cryptoSvc.generateRandomBytes(1);
+ do_check_eq(salt.length, 4);
+ salt = cryptoSvc.generateRandomBytes(2);
+ do_check_eq(salt.length, 4);
+ salt = cryptoSvc.generateRandomBytes(3);
+ do_check_eq(salt.length, 4);
+ salt = cryptoSvc.generateRandomBytes(4);
+ do_check_eq(salt.length, 8);
+ salt = cryptoSvc.generateRandomBytes(8);
+ do_check_eq(salt.length, 12);
+
+ // sanity check to make sure salts seem random
+ var salt2 = cryptoSvc.generateRandomBytes(8);
+ do_check_eq(salt2.length, 12);
+ do_check_neq(salt, salt2);
+
+ salt = cryptoSvc.generateRandomBytes(1024);
+ do_check_eq(salt.length, 1368);
+ salt = cryptoSvc.generateRandomBytes(16);
+ do_check_eq(salt.length, 24);
+
+
+ // Test random key generation
+ var keydata, keydata2, iv;
+
+ keydata = cryptoSvc.generateRandomKey();
+ do_check_eq(keydata.length, 44);
+ keydata2 = cryptoSvc.generateRandomKey();
+ do_check_neq(keydata, keydata2); // sanity check for randomness
+ iv = cryptoSvc.generateRandomIV();
+ do_check_eq(iv.length, 24);
+
+ cryptoSvc.algorithm = WeaveCryptoModule.AES_256_CBC;
+ keydata = cryptoSvc.generateRandomKey();
+ do_check_eq(keydata.length, 44);
+ keydata2 = cryptoSvc.generateRandomKey();
+ do_check_neq(keydata, keydata2); // sanity check for randomness
+ iv = cryptoSvc.generateRandomIV();
+ do_check_eq(iv.length, 24);
+
+ if (this.gczeal)
+ gczeal(0);
+}
diff --git a/services/crypto/tests/unit/test_load_modules.js b/services/crypto/tests/unit/test_load_modules.js
new file mode 100644
index 000000000..50f5d709c
--- /dev/null
+++ b/services/crypto/tests/unit/test_load_modules.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const modules = [
+ "utils.js",
+ "WeaveCrypto.js",
+];
+
+function run_test() {
+ for (let m of modules) {
+ let resource = "resource://services-crypto/" + m;
+ _("Attempting to import: " + resource);
+ Components.utils.import(resource, {});
+ }
+}
+
diff --git a/services/crypto/tests/unit/test_utils_hawk.js b/services/crypto/tests/unit/test_utils_hawk.js
new file mode 100644
index 000000000..0a2cf6c31
--- /dev/null
+++ b/services/crypto/tests/unit/test_utils_hawk.js
@@ -0,0 +1,301 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-crypto/utils.js");
+
+function run_test() {
+ initTestLogging();
+
+ run_next_test();
+}
+
+add_test(function test_hawk() {
+ let compute = CryptoUtils.computeHAWK;
+
+ // vectors copied from the HAWK (node.js) tests
+ let credentials_sha1 = {
+ id: "123456",
+ key: "2983d45yun89q",
+ algorithm: "sha1",
+ };
+
+ let method = "POST";
+ let ts = 1353809207;
+ let nonce = "Ygvqdz";
+ let result;
+
+ let uri_http = CommonUtils.makeURI("http://example.net/somewhere/over/the/rainbow");
+ let sha1_opts = { credentials: credentials_sha1,
+ ext: "Bazinga!",
+ ts: ts,
+ nonce: nonce,
+ payload: "something to write about",
+ };
+ result = compute(uri_http, method, sha1_opts);
+
+ // The HAWK spec uses non-urlsafe base64 (+/) for its output MAC string.
+ do_check_eq(result.field,
+ 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
+ 'hash="bsvY3IfUllw6V5rvk4tStEvpBhE=", ext="Bazinga!", ' +
+ 'mac="qbf1ZPG/r/e06F4ht+T77LXi5vw="'
+ );
+ do_check_eq(result.artifacts.ts, ts);
+ do_check_eq(result.artifacts.nonce, nonce);
+ do_check_eq(result.artifacts.method, method);
+ do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
+ do_check_eq(result.artifacts.host, "example.net");
+ do_check_eq(result.artifacts.port, 80);
+ // artifacts.hash is the *payload* hash, not the overall request MAC.
+ do_check_eq(result.artifacts.hash, "bsvY3IfUllw6V5rvk4tStEvpBhE=");
+ do_check_eq(result.artifacts.ext, "Bazinga!");
+
+ let credentials_sha256 = {
+ id: "123456",
+ key: "2983d45yun89q",
+ algorithm: "sha256",
+ };
+
+ let uri_https = CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow");
+ let sha256_opts = { credentials: credentials_sha256,
+ ext: "Bazinga!",
+ ts: ts,
+ nonce: nonce,
+ payload: "something to write about",
+ contentType: "text/plain",
+ };
+
+ result = compute(uri_https, method, sha256_opts);
+ do_check_eq(result.field,
+ 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
+ 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
+ 'ext="Bazinga!", ' +
+ 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
+ );
+ do_check_eq(result.artifacts.ts, ts);
+ do_check_eq(result.artifacts.nonce, nonce);
+ do_check_eq(result.artifacts.method, method);
+ do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
+ do_check_eq(result.artifacts.host, "example.net");
+ do_check_eq(result.artifacts.port, 443);
+ do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
+ do_check_eq(result.artifacts.ext, "Bazinga!");
+
+ let sha256_opts_noext = { credentials: credentials_sha256,
+ ts: ts,
+ nonce: nonce,
+ payload: "something to write about",
+ contentType: "text/plain",
+ };
+ result = compute(uri_https, method, sha256_opts_noext);
+ do_check_eq(result.field,
+ 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
+ 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
+ 'mac="HTgtd0jPI6E4izx8e4OHdO36q00xFCU0FolNq3RiCYs="'
+ );
+ do_check_eq(result.artifacts.ts, ts);
+ do_check_eq(result.artifacts.nonce, nonce);
+ do_check_eq(result.artifacts.method, method);
+ do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
+ do_check_eq(result.artifacts.host, "example.net");
+ do_check_eq(result.artifacts.port, 443);
+ do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
+
+ /* Leaving optional fields out should work, although of course then we can't
+ * assert much about the resulting hashes. The resulting header should look
+ * roughly like:
+ * Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
+ */
+
+ result = compute(uri_https, method, { credentials: credentials_sha256 });
+ let fields = result.field.split(" ");
+ do_check_eq(fields[0], "Hawk");
+ do_check_eq(fields[1], 'id="123456",'); // from creds.id
+ do_check_true(fields[2].startsWith('ts="'));
+ /* The HAWK spec calls for seconds-since-epoch, not ms-since-epoch.
+ * Warning: this test will fail in the year 33658, and for time travellers
+ * who journey earlier than 2001. Please plan accordingly. */
+ do_check_true(result.artifacts.ts > 1000*1000*1000);
+ do_check_true(result.artifacts.ts < 1000*1000*1000*1000);
+ do_check_true(fields[3].startsWith('nonce="'));
+ do_check_eq(fields[3].length, ('nonce="12345678901=",').length);
+ do_check_eq(result.artifacts.nonce.length, ("12345678901=").length);
+
+ let result2 = compute(uri_https, method, { credentials: credentials_sha256 });
+ do_check_neq(result.artifacts.nonce, result2.artifacts.nonce);
+
+ /* Using an upper-case URI hostname shouldn't affect the hash. */
+
+ let uri_https_upper = CommonUtils.makeURI("https://EXAMPLE.NET/somewhere/over/the/rainbow");
+ result = compute(uri_https_upper, method, sha256_opts);
+ do_check_eq(result.field,
+ 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
+ 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
+ 'ext="Bazinga!", ' +
+ 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
+ );
+
+ /* Using a lower-case method name shouldn't affect the hash. */
+ result = compute(uri_https_upper, method.toLowerCase(), sha256_opts);
+ do_check_eq(result.field,
+ 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
+ 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
+ 'ext="Bazinga!", ' +
+ 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
+ );
+
+ /* The localtimeOffsetMsec field should be honored. HAWK uses this to
+ * compensate for clock skew between client and server: if the request is
+ * rejected with a timestamp out-of-range error, the error includes the
+ * server's time, and the client computes its clock offset and tries again.
+ * Clients can remember this offset for a while.
+ */
+
+ result = compute(uri_https, method, { credentials: credentials_sha256,
+ now: 1378848968650,
+ });
+ do_check_eq(result.artifacts.ts, 1378848968);
+
+ result = compute(uri_https, method, { credentials: credentials_sha256,
+ now: 1378848968650,
+ localtimeOffsetMsec: 1000*1000,
+ });
+ do_check_eq(result.artifacts.ts, 1378848968 + 1000);
+
+ /* Search/query-args in URIs should be included in the hash. */
+ let makeURI = CommonUtils.makeURI;
+ result = compute(makeURI("http://example.net/path"), method, sha256_opts);
+ do_check_eq(result.artifacts.resource, "/path");
+ do_check_eq(result.artifacts.mac, "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=");
+
+ result = compute(makeURI("http://example.net/path/"), method, sha256_opts);
+ do_check_eq(result.artifacts.resource, "/path/");
+ do_check_eq(result.artifacts.mac, "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=");
+
+ result = compute(makeURI("http://example.net/path?query=search"), method, sha256_opts);
+ do_check_eq(result.artifacts.resource, "/path?query=search");
+ do_check_eq(result.artifacts.mac, "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=");
+
+ /* Test handling of the payload, which is supposed to be a bytestring
+ (String with codepoints from U+0000 to U+00FF, pre-encoded). */
+
+ result = compute(makeURI("http://example.net/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ });
+ do_check_eq(result.artifacts.hash, undefined);
+ do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
+
+ // Empty payload changes nothing.
+ result = compute(makeURI("http://example.net/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ payload: null,
+ });
+ do_check_eq(result.artifacts.hash, undefined);
+ do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
+
+ result = compute(makeURI("http://example.net/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ payload: "hello",
+ });
+ do_check_eq(result.artifacts.hash, "uZJnFj0XVBA6Rs1hEvdIDf8NraM0qRNXdFbR3NEQbVA=");
+ do_check_eq(result.artifacts.mac, "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=");
+
+ // update, utf-8 payload
+ result = compute(makeURI("http://example.net/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ payload: "andré@example.org", // non-ASCII
+ });
+ do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
+ do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
+
+ /* If "hash" is provided, "payload" is ignored. */
+ result = compute(makeURI("http://example.net/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
+ payload: "something else",
+ });
+ do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
+ do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
+
+ // the payload "hash" is also non-urlsafe base64 (+/)
+ result = compute(makeURI("http://example.net/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ payload: "something else",
+ });
+ do_check_eq(result.artifacts.hash, "lERFXr/IKOaAoYw+eBseDUSwmqZTX0uKZpcWLxsdzt8=");
+ do_check_eq(result.artifacts.mac, "jiZuhsac35oD7IdcblhFncBr8tJFHcwWLr8NIYWr9PQ=");
+
+ /* Test non-ascii hostname. HAWK (via the node.js "url" module) punycodes
+ * "ëxample.net" into "xn--xample-ova.net" before hashing. I still think
+ * punycode was a bad joke that got out of the lab and into a spec.
+ */
+
+ result = compute(makeURI("http://ëxample.net/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ });
+ do_check_eq(result.artifacts.mac, "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=");
+ do_check_eq(result.artifacts.host, "xn--xample-ova.net");
+
+ result = compute(makeURI("http://example.net/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ ext: "backslash=\\ quote=\" EOF",
+ });
+ do_check_eq(result.artifacts.mac, "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=");
+ do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="');
+
+ result = compute(makeURI("http://example.net:1234/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ });
+ do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
+ do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
+
+ /* HAWK (the node.js library) uses a URL parser which stores the "port"
+ * field as a string, but makeURI() gives us an integer. So we'll diverge
+ * on ports with a leading zero. This test vector would fail on the node.js
+ * library (HAWK-1.1.1), where they get a MAC of
+ * "T+GcAsDO8GRHIvZLeepSvXLwDlFJugcZroAy9+uAtcw=". I think HAWK should be
+ * updated to do what we do here, so port="01234" should get the same hash
+ * as port="1234".
+ */
+ result = compute(makeURI("http://example.net:01234/path"), method,
+ { credentials: credentials_sha256,
+ ts: 1353809207,
+ nonce: "Ygvqdz",
+ });
+ do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
+ do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
+
+ run_next_test();
+});
+
+
+add_test(function test_strip_header_attributes() {
+ let strip = CryptoUtils.stripHeaderAttributes;
+
+ do_check_eq(strip(undefined), "");
+ do_check_eq(strip("text/plain"), "text/plain");
+ do_check_eq(strip("TEXT/PLAIN"), "text/plain");
+ do_check_eq(strip(" text/plain "), "text/plain");
+ do_check_eq(strip("text/plain ; charset=utf-8 "), "text/plain");
+
+ run_next_test();
+});
diff --git a/services/crypto/tests/unit/test_utils_hkdfExpand.js b/services/crypto/tests/unit/test_utils_hkdfExpand.js
new file mode 100644
index 000000000..4b4b21900
--- /dev/null
+++ b/services/crypto/tests/unit/test_utils_hkdfExpand.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-crypto/utils.js");
+
+// Test vectors from RFC 5869
+
+// Test case 1
+
+var tc1 = {
+ IKM: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+ salt: "000102030405060708090a0b0c",
+ info: "f0f1f2f3f4f5f6f7f8f9",
+ L: 42,
+ PRK: "077709362c2e32df0ddc3f0dc47bba63" +
+ "90b6c73bb50f9c3122ec844ad7c2b3e5",
+ OKM: "3cb25f25faacd57a90434f64d0362f2a" +
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
+ "34007208d5b887185865"
+};
+
+// Test case 2
+
+var tc2 = {
+ IKM: "000102030405060708090a0b0c0d0e0f" +
+ "101112131415161718191a1b1c1d1e1f" +
+ "202122232425262728292a2b2c2d2e2f" +
+ "303132333435363738393a3b3c3d3e3f" +
+ "404142434445464748494a4b4c4d4e4f",
+ salt: "606162636465666768696a6b6c6d6e6f" +
+ "707172737475767778797a7b7c7d7e7f" +
+ "808182838485868788898a8b8c8d8e8f" +
+ "909192939495969798999a9b9c9d9e9f" +
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
+ info: "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" +
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" +
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+ L: 82,
+ PRK: "06a6b88c5853361a06104c9ceb35b45c" +
+ "ef760014904671014a193f40c15fc244",
+ OKM: "b11e398dc80327a1c8e7f78c596a4934" +
+ "4f012eda2d4efad8a050cc4c19afa97c" +
+ "59045a99cac7827271cb41c65e590e09" +
+ "da3275600c2f09b8367793a9aca3db71" +
+ "cc30c58179ec3e87c14c01d5c1f3434f" +
+ "1d87"
+};
+
+// Test case 3
+
+var tc3 = {
+ IKM: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+ salt: "",
+ info: "",
+ L: 42,
+ PRK: "19ef24a32c717b167f33a91d6f648bdf" +
+ "96596776afdb6377ac434c1c293ccb04",
+ OKM: "8da4e775a563c18f715f802a063c5a31" +
+ "b8a11f5c5ee1879ec3454e5f3c738d2d" +
+ "9d201395faa4b61a96c8"
+};
+
+function sha256HMAC(message, key) {
+ let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
+ return CryptoUtils.digestBytes(message, h);
+}
+
+function _hexToString(hex) {
+ let ret = "";
+ if (hex.length % 2 != 0) {
+ return false;
+ }
+
+ for (let i = 0; i < hex.length; i += 2) {
+ let cur = hex[i] + hex[i + 1];
+ ret += String.fromCharCode(parseInt(cur, 16));
+ }
+ return ret;
+}
+
+function extract_hex(salt, ikm) {
+ salt = _hexToString(salt);
+ ikm = _hexToString(ikm);
+ return CommonUtils.bytesAsHex(sha256HMAC(ikm, CryptoUtils.makeHMACKey(salt)));
+}
+
+function expand_hex(prk, info, len) {
+ prk = _hexToString(prk);
+ info = _hexToString(info);
+ return CommonUtils.bytesAsHex(CryptoUtils.hkdfExpand(prk, info, len));
+}
+
+function hkdf_hex(ikm, salt, info, len) {
+ ikm = _hexToString(ikm);
+ if (salt)
+ salt = _hexToString(salt);
+ info = _hexToString(info);
+ return CommonUtils.bytesAsHex(CryptoUtils.hkdf(ikm, salt, info, len));
+}
+
+function run_test() {
+ _("Verifying Test Case 1");
+ do_check_eq(extract_hex(tc1.salt, tc1.IKM), tc1.PRK);
+ do_check_eq(expand_hex(tc1.PRK, tc1.info, tc1.L), tc1.OKM);
+ do_check_eq(hkdf_hex(tc1.IKM, tc1.salt, tc1.info, tc1.L), tc1.OKM);
+
+ _("Verifying Test Case 2");
+ do_check_eq(extract_hex(tc2.salt, tc2.IKM), tc2.PRK);
+ do_check_eq(expand_hex(tc2.PRK, tc2.info, tc2.L), tc2.OKM);
+ do_check_eq(hkdf_hex(tc2.IKM, tc2.salt, tc2.info, tc2.L), tc2.OKM);
+
+ _("Verifying Test Case 3");
+ do_check_eq(extract_hex(tc3.salt, tc3.IKM), tc3.PRK);
+ do_check_eq(expand_hex(tc3.PRK, tc3.info, tc3.L), tc3.OKM);
+ do_check_eq(hkdf_hex(tc3.IKM, tc3.salt, tc3.info, tc3.L), tc3.OKM);
+ do_check_eq(hkdf_hex(tc3.IKM, undefined, tc3.info, tc3.L), tc3.OKM);
+}
diff --git a/services/crypto/tests/unit/test_utils_httpmac.js b/services/crypto/tests/unit/test_utils_httpmac.js
new file mode 100644
index 000000000..67b337373
--- /dev/null
+++ b/services/crypto/tests/unit/test_utils_httpmac.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-crypto/utils.js");
+
+function run_test() {
+ initTestLogging();
+
+ run_next_test();
+}
+
+add_test(function test_sha1() {
+ _("Ensure HTTP MAC SHA1 generation works as expected.");
+
+ let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7";
+ let key = "b8u1cc5iiio5o319og7hh8faf2gi5ym4aq0zwf112cv1287an65fudu5zj7zo7dz";
+ let ts = 1329181221;
+ let method = "GET";
+ let nonce = "wGX71";
+ let uri = CommonUtils.makeURI("http://10.250.2.176/alias/");
+
+ let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
+ {ts: ts, nonce: nonce});
+
+ do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
+
+ do_check_eq(result.getHeader(),
+ 'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
+ 'ts="1329181221", nonce="wGX71", mac="jzh5chjQc2zFEvLbyHnPdX11Yck="');
+
+ let ext = "EXTRA DATA; foo,bar=1";
+
+ result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
+ {ts: ts, nonce: nonce, ext: ext});
+ do_check_eq(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
+ do_check_eq(result.getHeader(),
+ 'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
+ 'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' +
+ 'ext="EXTRA DATA; foo,bar=1"');
+
+ run_next_test();
+});
+
+add_test(function test_nonce_length() {
+ _("Ensure custom nonce lengths are honoured.");
+
+ function get_mac(length) {
+ let uri = CommonUtils.makeURI("http://example.com/");
+ return CryptoUtils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
+ nonce_bytes: length
+ });
+ }
+
+ let result = get_mac(12);
+ do_check_eq(12, atob(result.nonce).length);
+
+ result = get_mac(2);
+ do_check_eq(2, atob(result.nonce).length);
+
+ result = get_mac(0);
+ do_check_eq(8, atob(result.nonce).length);
+
+ result = get_mac(-1);
+ do_check_eq(8, atob(result.nonce).length);
+
+ run_next_test();
+});
diff --git a/services/crypto/tests/unit/test_utils_pbkdf2.js b/services/crypto/tests/unit/test_utils_pbkdf2.js
new file mode 100644
index 000000000..7313819ec
--- /dev/null
+++ b/services/crypto/tests/unit/test_utils_pbkdf2.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// XXX until bug 937114 is fixed
+Cu.importGlobalProperties(['btoa']);
+Cu.import("resource://services-crypto/utils.js");
+Cu.import("resource://services-common/utils.js");
+
+var {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)));
+ }
+});
+
+// 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)));
+ }
+});
+
+// turn formatted test vectors into normal hex strings
+function h(hexStr) {
+ return hexStr.replace(/\s+/g, "");
+}
diff --git a/services/crypto/tests/unit/test_utils_sha1.js b/services/crypto/tests/unit/test_utils_sha1.js
new file mode 100644
index 000000000..f99350754
--- /dev/null
+++ b/services/crypto/tests/unit/test_utils_sha1.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+_("Make sure sha1 digests works with various messages");
+
+Cu.import("resource://services-crypto/utils.js");
+
+function run_test() {
+ let mes1 = "hello";
+ let mes2 = "world";
+
+ let dig0 = CryptoUtils.UTF8AndSHA1(mes1);
+ do_check_eq(dig0,
+ "\xaa\xf4\xc6\x1d\xdc\xc5\xe8\xa2\xda\xbe\xde\x0f\x3b\x48\x2c\xd9\xae\xa9\x43\x4d");
+
+ _("Make sure right sha1 digests are generated");
+ let dig1 = CryptoUtils.sha1(mes1);
+ do_check_eq(dig1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
+ let dig2 = CryptoUtils.sha1(mes2);
+ do_check_eq(dig2, "7c211433f02071597741e6ff5a8ea34789abbf43");
+ let dig12 = CryptoUtils.sha1(mes1 + mes2);
+ do_check_eq(dig12, "6adfb183a4a2c94a2f92dab5ade762a47889a5a1");
+ let dig21 = CryptoUtils.sha1(mes2 + mes1);
+ do_check_eq(dig21, "5715790a892990382d98858c4aa38d0617151575");
+
+ _("Repeated sha1s shouldn't change the digest");
+ do_check_eq(CryptoUtils.sha1(mes1), dig1);
+ do_check_eq(CryptoUtils.sha1(mes2), dig2);
+ do_check_eq(CryptoUtils.sha1(mes1 + mes2), dig12);
+ do_check_eq(CryptoUtils.sha1(mes2 + mes1), dig21);
+
+ _("Nested sha1 should work just fine");
+ let nest1 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes1)))));
+ do_check_eq(nest1, "23f340d0cff31e299158b3181b6bcc7e8c7f985a");
+ let nest2 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes2)))));
+ do_check_eq(nest2, "1f6453867e3fb9876ae429918a64cdb8dc5ff2d0");
+}
diff --git a/services/crypto/tests/unit/xpcshell.ini b/services/crypto/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..0b3a9324c
--- /dev/null
+++ b/services/crypto/tests/unit/xpcshell.ini
@@ -0,0 +1,20 @@
+[DEFAULT]
+head = head_helpers.js ../../../common/tests/unit/head_helpers.js
+tail =
+firefox-appdir = browser
+support-files =
+ !/services/common/tests/unit/head_helpers.js
+
+[test_load_modules.js]
+
+[test_crypto_crypt.js]
+[test_crypto_deriveKey.js]
+[test_crypto_random.js]
+# Bug 676977: test hangs consistently on Android
+skip-if = os == "android"
+
+[test_utils_hawk.js]
+[test_utils_hkdfExpand.js]
+[test_utils_httpmac.js]
+[test_utils_pbkdf2.js]
+[test_utils_sha1.js]