// most of these tests are taken from Node.js // thank you Node.js team for the tests import { expect, it } from "bun:test"; const crypto = require("crypto"); const assert = { strictEqual: (a, b) => { expect(a).toEqual(b); }, deepStrictEqual: (a, b) => { expect(a).toEqual(b); }, throws: (fn, err) => { try { fn(); throw "Fail"; } catch (e) { if (err.name) { expect(e?.name).toEqual(err.name); } // if (err.message) { // expect(err.message.test(e?.message)).toBeTruthy(); // } if (err.code) { expect(e?.code).toEqual(err.code); } expect(e).not.toEqual("Fail"); return; } }, }; const good = [ // Zero-length key is legal, functions as a parameter validation check. { pass: "", salt: "", keylen: 0, N: 16, p: 1, r: 1, expected: "", }, // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that // should pass. Note that the test vector with N=1048576 is omitted // because it takes too long to complete and uses over 1 GB of memory. { pass: "", salt: "", keylen: 64, N: 16, p: 1, r: 1, expected: "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" + "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", }, { pass: "password", salt: "NaCl", keylen: 64, N: 1024, p: 16, r: 8, expected: "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" + "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", }, { pass: "pleaseletmein", salt: "SodiumChloride", keylen: 64, N: 16384, p: 1, r: 8, expected: "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" + "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", }, { pass: "", salt: "", keylen: 64, cost: 16, parallelization: 1, blockSize: 1, expected: "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" + "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", }, { pass: "password", salt: "NaCl", keylen: 64, cost: 1024, parallelization: 16, blockSize: 8, expected: "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" + "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", }, { pass: "pleaseletmein", salt: "SodiumChloride", keylen: 64, cost: 16384, parallelization: 1, blockSize: 8, expected: "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" + "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", }, ]; // Test vectors that should fail. const bad = [ { N: 1, p: 1, r: 1 }, // N < 2 { N: 3, p: 1, r: 1 }, // Not power of 2. { N: 1, cost: 1 }, // Both N and cost // TODO: these should error, but I don't quite understand why. // { p: 1, parallelization: 1 }, // Both p and parallelization // { r: 1, blockSize: 1 }, // Both r and blocksize ]; // Test vectors where 128*N*r exceeds maxmem. const toobig = [ { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16) { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r { N: 2 ** 20, p: 1, r: 8 }, { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 }, ]; const badargs = [ { args: [], expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"password"/ */ }, }, { args: [null], expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"password"/ */ }, }, { args: [""], expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"salt"/ */ }, }, { args: ["", null], expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"salt"/ */ }, }, { args: ["", ""], expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ }, }, { args: ["", "", null], expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ }, }, // TODO: throw on these // { // args: ["", "", 0.42], // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, // }, // { // args: ["", "", -42], // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, // }, // { // args: ["", "", 2147485780], // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, // }, ]; it("scrypt good", () => { for (const options of good) { const { pass, salt, keylen, expected } = options; const actual = crypto.scryptSync(pass, salt, keylen, options); assert.strictEqual(actual.toString("hex"), expected); } }); it("scrypt bad", () => { for (const options of bad) { const expected = { message: /Invalid scrypt param/, }; assert.throws(() => crypto.scryptSync("pass", "salt", 1, options), expected); } }); it("scrypt toobig", () => { for (const options of toobig) { const expected = { message: /Invalid scrypt param/, }; assert.throws(() => crypto.scryptSync("pass", "salt", 1, options), expected); } }); it("scrypt defaults eql", () => { { const defaults = { N: 16384, p: 1, r: 8 }; const expected = crypto.scryptSync("pass", "salt", 1, defaults); const actual = crypto.scryptSync("pass", "salt", 1); assert.deepStrictEqual(actual.toString("hex"), expected.toString("hex")); } }); // TODO: DEFAULT_ENCODING is read-only // it("scrypt defaults encoding", () => { // { // const defaultEncoding = crypto.DEFAULT_ENCODING; // const defaults = { N: 16384, p: 1, r: 8 }; // const expected = crypto.scryptSync("pass", "salt", 1, defaults); // const testEncoding = "latin1"; // crypto.DEFAULT_ENCODING = testEncoding; // const actual = crypto.scryptSync("pass", "salt", 1); // assert.deepStrictEqual(actual, expected.toString(testEncoding)); // crypto.DEFAULT_ENCODING = defaultEncoding; // } // }); it("scrypt badargs", () => { { for (const { args, expected } of badargs) { assert.throws(() => crypto.scryptSync(...args), expected); } } { const expected = { code: "ERR_INVALID_ARG_TYPE" }; assert.throws(() => crypto.scryptSync("", "", 42, null), expected); // assert.throws(() => crypto.scryptSync("", "", 42, {}, null), expected); // assert.throws(() => crypto.scryptSync("", "", 42, {}), expected); // assert.throws(() => crypto.scryptSync("", "", 42, {}, {}), expected); } // { // // Values for maxmem that do not fit in 32 bits but that are still safe // // integers should be allowed. // crypto.scrypt( // "", // "", // 4, // { maxmem: 2 ** 52 }, // common.mustSucceed((actual) => { // assert.strictEqual(actual.toString("hex"), "d72c87d0"); // }), // ); // // Values that exceed Number.isSafeInteger should not be allowed. // assert.throws(() => crypto.scryptSync("", "", 0, { maxmem: 2 ** 53 }), { // code: "ERR_OUT_OF_RANGE", // }); // } });