import { CryptoHasher, MD4, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256, gc } from "bun"; import { describe, expect, it } from "bun:test"; import crypto from "crypto"; import { bunEnv, bunExe, tmpdirSync } from "harness"; import path from "path"; import { hashesFixture } from "./fixtures/sign.fixture.ts"; const HashClasses = [MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256]; describe("CryptoHasher", () => { it("CryptoHasher.algorithms", () => { expect(CryptoHasher.algorithms).toEqual([ "blake2b256", "blake2b512", "blake2s256", "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", blake2s256: "9aec6806794561107e594b1f6a8a6b0c92a0cba9acf5e5e93cca06f781813b0b", 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, blake2s256: 256, 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 stderr.text(); expect(err).toBeEmpty(); const out = await 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", "readable" ]`, `[ "cipher", "prefinish" ]`, `[ "cipher", "data" ]`, `[ 1, "dfb6b7e029be3ad6b090349ed75931f28f991b52ca9a89f5bf6f82fa1c87aa2d624bd77701dcddfcceaf3add7d66ce06ced17aebca4cb35feffc4b8b9008b3c4" ]`, `[ "decipher", "readable" ]`, `[ "decipher", "prefinish" ]`, `[ "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" ]`, ``, ]); });