Files
bun.sh/test/js/node/crypto/crypto.test.ts
Jarred Sumner 864cbc1555 Fixes #301 (#11988)
Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
2024-06-20 16:14:26 -07:00

308 lines
10 KiB
TypeScript

import { sha, MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256, gc, CryptoHasher } from "bun";
import { it, expect, describe } from "bun:test";
import crypto from "crypto";
import path from "path";
import { hashesFixture } from "./fixtures/sign.fixture.ts";
import { bunEnv, bunExe, tmpdirSync } from "harness";
const HashClasses = [MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256];
describe("CryptoHasher", () => {
it("CryptoHasher.algorithms", () => {
expect(CryptoHasher.algorithms).toEqual([
"blake2b256",
"blake2b512",
"md4",
"md5",
"ripemd160",
"sha1",
"sha224",
"sha256",
"sha384",
"sha512",
"sha512-224",
"sha512-256",
"sha3-224",
"sha3-256",
"sha3-384",
"sha3-512",
"shake128",
"shake256",
]);
});
// prettier-ignore
const expected = {
blake2b256: "256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610",
blake2b512: "021ced8799296ceca557832ab941a50b4a11f83478cf141f51f933f653ab9fbcc05a037cddbed06e309bf334942c4e58cdf1a46e237911ccd7fcf9787cbc7fd0",
md4: "aa010fbc1d14c795d86ef98c95479d17",
md5: "5eb63bbbe01eeed093cb22bb8f5acdc3",
ripemd160: "98c615784ccb5fe5936fbc0cbe9dfdb408d92f0f",
sha1: "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
sha224: "2f05477fc24bb4faefd86517156dafdecec45b8ad3cf2522a563582b",
sha256: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
sha384: "fdbd8e75a67f29f701a4e040385e2e23986303ea10239211af907fcbb83578b3e417cb71ce646efd0819dd8c088de1bd",
sha512: "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f",
"sha512-224": "22e0d52336f64a998085078b05a6e37b26f8120f43bf4db4c43a64ee",
"sha512-256": "0ac561fac838104e3f2e4ad107b4bee3e938bf15f2b15f009ccccd61a913f017",
"sha3-224": "dfb7f18c77e928bb56faeb2da27291bd790bc1045cde45f3210bb6c5",
"sha3-256": "644bcc7e564373040999aac89e7622f3ca71fba1d972fd94a31c3bfbf24e3938",
"sha3-384": "83bff28dde1b1bf5810071c6643c08e5b05bdb836effd70b403ea8ea0a634dc4997eb1053aa3593f590f9c63630dd90b",
"sha3-512": "840006653e9ac9e95117a15c915caab81662918e925de9e004f774ff82d7079a40d4d27b1b372657c61d46d470304c88c788b3a4527ad074d1dccbee5dbaa99a",
shake128: "3a9159f071e4dd1c8c4f968607c30942",
shake256: "369771bb2cb9d2b04c1d54cca487e372d9f187f73f7ba3f65b95c8ee7798c527",
} as const;
const expectedBitLength = {
blake2b256: 256,
blake2b512: 512,
md4: 128,
md5: 128,
ripemd160: 160,
sha1: 160,
sha224: 224,
sha256: 256,
sha384: 384,
sha512: 512,
"sha512-224": 224,
"sha512-256": 256,
"sha3-224": 224,
"sha3-256": 256,
"sha3-384": 384,
"sha3-512": 512,
shake128: 128,
shake256: 256,
} as const;
for (const algorithm of CryptoHasher.algorithms) {
it(`new CryptoHasher ${algorithm}`, () => {
var hasher = new CryptoHasher(algorithm);
expect(hasher.algorithm).toEqual(algorithm);
expect(hasher.byteLength).toEqual(expectedBitLength[algorithm] / 8);
hasher.update("hello world");
expect(hasher.digest("hex")).toEqual(expected[algorithm]);
});
it(`CryptoHasher.hash ${algorithm}`, () => {
expect(CryptoHasher.hash(algorithm, "hello world").toString("hex")).toEqual(expected[algorithm]);
});
it(`new CryptoHasher ${algorithm} multi-part`, () => {
var hasher = new CryptoHasher(algorithm);
hasher.update("hello ");
hasher.update("world");
expect(hasher.digest("hex")).toBe(expected[algorithm]);
expect(hasher.algorithm).toBe(algorithm);
});
it(`new CryptoHasher ${algorithm} to Buffer`, () => {
var hasher = new CryptoHasher(algorithm);
expect(hasher.algorithm).toEqual(algorithm);
hasher.update("hello world");
expect(hasher.digest()).toEqual(Buffer.from(expected[algorithm], "hex"));
});
}
it("CryptoHasher resets when digest is called", () => {
var hasher = new CryptoHasher("sha256");
hasher.update("hello");
expect(hasher.digest("hex")).toBe("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824");
hasher.update("world");
expect(hasher.digest("hex")).toBe("486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7");
});
for (let alg of CryptoHasher.algorithms) {
it(`CryptoHasher ${alg} copy is the same`, () => {
const orig = new CryptoHasher(alg);
orig.update("hello");
const copy = orig.copy();
expect(copy.digest("hex")).toBe(orig.digest("hex"));
expect(copy.algorithm).toBe(orig.algorithm);
});
it(`CryptoHasher ${alg} copy is not linked`, () => {
const orig = new CryptoHasher(alg);
orig.update("hello");
const copy = orig.copy();
orig.update("world");
expect(copy.digest("hex")).not.toBe(orig.digest("hex"));
});
it(`CryptoHasher ${alg} copy can be used after digest()`, () => {
const orig = new CryptoHasher(alg);
orig.update("hello");
orig.digest("hex");
const copy = orig.copy();
expect(() => copy.digest("hex")).not.toThrow();
});
it(`CryptoHasher ${alg} copy updates the same`, () => {
const orig = new CryptoHasher(alg);
orig.update("hello");
const copy = orig.copy();
orig.update("world");
copy.update("world");
expect(copy.digest("hex")).toBe(orig.digest("hex"));
});
}
});
describe("crypto.getCurves", () => {
it("should return an array of strings", () => {
expect(Array.isArray(crypto.getCurves())).toBe(true);
expect(typeof crypto.getCurves()[0]).toBe("string");
});
});
describe("crypto", () => {
for (let Hash of HashClasses) {
for (let [input, label] of [
["hello world", '"hello world"'],
["hello world".repeat(20).slice(), '"hello world" x 20'],
["", "empty string"],
["a", '"a"'],
]) {
describe(label, () => {
gc(true);
it(`${Hash.name} base64`, () => {
gc(true);
const result = new Hash();
result.update(input);
expect(typeof result.digest("base64")).toBe("string");
gc(true);
});
it(`${Hash.name} hash base64`, () => {
Hash.hash(input, "base64");
gc(true);
});
it(`${Hash.name} hex`, () => {
const result = new Hash();
result.update(input);
expect(typeof result.digest("hex")).toBe("string");
gc(true);
});
it(`${Hash.name} hash hex`, () => {
expect(typeof Hash.hash(input, "hex")).toBe("string");
gc(true);
});
it(`${Hash.name} buffer`, () => {
var buf = new Uint8Array(256);
const result = new Hash();
result.update(input);
expect(result.digest(buf)).toBe(buf);
expect(buf[0] != 0).toBe(true);
gc(true);
});
it(`${Hash.name} buffer`, () => {
var buf = new Uint8Array(256);
expect(Hash.hash(input, buf) instanceof Uint8Array).toBe(true);
gc(true);
});
});
}
}
});
describe("crypto.createSign()/.verifySign()", () => {
it.each(hashesFixture)(
"should create and verify digital signature for %s",
async (alg, privKey, pubKey, expectedSign) => {
const p = await Bun.file(`${__dirname}/${privKey}`).text();
const sign = crypto.createSign(alg).update("text").sign(p, "base64");
expect(sign).toEqual(expectedSign);
const verify = crypto
.createVerify(alg)
.update("text")
.verify(await Bun.file(`${__dirname}/${pubKey}`).text(), sign, "base64");
expect(verify).toBeTrue();
},
);
});
it("should send cipher events in the right order", async () => {
const package_dir = tmpdirSync();
const fixture_path = path.join(package_dir, "fixture.js");
await Bun.write(
fixture_path,
String.raw`
function patchEmitter(emitter, prefix) {
var oldEmit = emitter.emit;
emitter.emit = function () {
console.log([prefix, arguments[0]]);
oldEmit.apply(emitter, arguments);
};
}
const crypto = require("node:crypto");
const plaintext = "Out of the mountain of despair, a stone of hope.";
const key = Buffer.from("3fad401bb178066f201b55368712530229d6329a5e2c05f48ff36ca65792d21d", "hex");
const iv = Buffer.from("22371787d3e04a6589d8a1de50c81208", "hex");
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
patchEmitter(cipher, "cipher");
cipher.end(plaintext);
let ciph = cipher.read();
console.log([1, ciph.toString("hex")]);
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
patchEmitter(decipher, "decipher");
decipher.end(ciph);
let dciph = decipher.read();
console.log([2, dciph.toString("hex")]);
let txt = dciph.toString("utf8");
console.log([3, plaintext]);
console.log([4, txt]);
`,
);
const { stdout, stderr } = Bun.spawn({
cmd: [bunExe(), "run", fixture_path],
stdout: "pipe",
stdin: "ignore",
stderr: "pipe",
env: bunEnv,
});
const err = await new Response(stderr).text();
expect(err).toBeEmpty();
const out = await new Response(stdout).text();
// TODO: prefinish and readable (on both cipher and decipher) should be flipped
// This seems like a bug in our crypto code, which
expect(out.split("\n")).toEqual([
`[ "cipher", "prefinish" ]`,
`[ "cipher", "readable" ]`,
`[ "cipher", "data" ]`,
`[ 1, "dfb6b7e029be3ad6b090349ed75931f28f991b52ca9a89f5bf6f82fa1c87aa2d624bd77701dcddfcceaf3add7d66ce06ced17aebca4cb35feffc4b8b9008b3c4" ]`,
`[ "decipher", "prefinish" ]`,
`[ "decipher", "readable" ]`,
`[ "decipher", "data" ]`,
`[ 2, "4f7574206f6620746865206d6f756e7461696e206f6620646573706169722c20612073746f6e65206f6620686f70652e" ]`,
`[ 3, "Out of the mountain of despair, a stone of hope." ]`,
`[ 4, "Out of the mountain of despair, a stone of hope." ]`,
`[ "cipher", "finish" ]`,
`[ "cipher", "end" ]`,
`[ "decipher", "finish" ]`,
`[ "decipher", "end" ]`,
`[ "cipher", "close" ]`,
`[ "decipher", "close" ]`,
``,
]);
});