mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
308 lines
10 KiB
TypeScript
308 lines
10 KiB
TypeScript
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",
|
|
"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", "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" ]`,
|
|
``,
|
|
]);
|
|
});
|