aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-04-13 04:20:05 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-04-13 04:20:05 -0700
commit3db3057d421a145743d63ebb5a487fb73e0e9f0b (patch)
treeece61bf4b75fb7f108c038082a6b273a234f27bf
parentf6d73cb06ec5fe217a4d2d03808f68ecbc2d3461 (diff)
downloadbun-3db3057d421a145743d63ebb5a487fb73e0e9f0b.tar.gz
bun-3db3057d421a145743d63ebb5a487fb73e0e9f0b.tar.zst
bun-3db3057d421a145743d63ebb5a487fb73e0e9f0b.zip
Support digest("base64" | "hex") in the hashings
-rw-r--r--integration/bunjs-only-snippets/crypto.test.js98
-rw-r--r--integration/bunjs-only-snippets/tsconfig.json3
-rw-r--r--packages/bun-types/types.d.ts167
-rw-r--r--src/deps/boringssl.translated.zig20
-rw-r--r--src/javascript/jsc/api/bun.zig160
-rw-r--r--src/javascript/jsc/base.zig22
-rw-r--r--src/javascript/jsc/node/types.zig33
-rw-r--r--src/javascript/jsc/rare_data.zig9
-rw-r--r--src/sha.zig231
-rw-r--r--types/bun/bun.d.ts159
10 files changed, 752 insertions, 150 deletions
diff --git a/integration/bunjs-only-snippets/crypto.test.js b/integration/bunjs-only-snippets/crypto.test.js
index 344d935d5..2c784e496 100644
--- a/integration/bunjs-only-snippets/crypto.test.js
+++ b/integration/bunjs-only-snippets/crypto.test.js
@@ -1,28 +1,70 @@
-import { it, expect } from "bun:test";
-
-for (let Hasher of [
- Bun.SHA1,
- Bun.SHA256,
- Bun.SHA384,
- Bun.SHA512,
- Bun.SHA512_256,
-]) {
- it(`${Hasher.name} instance`, () => {
- var buf = new Uint8Array(256);
- const result = new Hasher();
- result.update("hello world");
- result.final(buf);
- });
-}
-
-for (let HashFn of [
- Bun.sha1,
- Bun.sha256,
- Bun.sha384,
- Bun.sha512,
- Bun.sha512_256,
-]) {
- it(`${HashFn.name} instance`, () => {
- HashFn("hello world");
- });
-}
+import {
+ sha,
+ MD5,
+ MD4,
+ SHA1,
+ SHA256,
+ SHA384,
+ SHA512,
+ SHA512_256,
+ gc,
+} from "bun";
+import { it, expect, describe } from "bun:test";
+import { readFileSync } from "fs";
+
+describe("crypto", () => {
+ for (let Hash of [MD5, MD4, SHA1, SHA256, SHA384, SHA512, SHA512_256]) {
+ for (let input of [
+ "hello world",
+ "hello world".repeat(20).slice(),
+ "",
+ "a",
+ ]) {
+ describe(input, () => {
+ 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);
+ });
+ });
+ }
+ }
+});
diff --git a/integration/bunjs-only-snippets/tsconfig.json b/integration/bunjs-only-snippets/tsconfig.json
index 05df31d44..9a6c36e06 100644
--- a/integration/bunjs-only-snippets/tsconfig.json
+++ b/integration/bunjs-only-snippets/tsconfig.json
@@ -3,8 +3,11 @@
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
+ "noEmit": true,
+ "allowJs": true,
"typeRoots": ["../../types"],
"types": ["bun"],
+ "allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
"foo/bar": ["baz.js"]
diff --git a/packages/bun-types/types.d.ts b/packages/bun-types/types.d.ts
index 731454297..528b3c0bf 100644
--- a/packages/bun-types/types.d.ts
+++ b/packages/bun-types/types.d.ts
@@ -577,6 +577,8 @@ declare module "bun" {
* Stop listening to prevent new connections from being accepted.
*
* It does not close existing connections.
+ *
+ * It may take a second or two to actually stop.
*/
stop(): void;
@@ -748,6 +750,8 @@ declare module "bun" {
}
export const unsafe: unsafe;
+ type DigestEncoding = "hex" | "base64";
+
/**
* Are ANSI colors enabled for stdin and stdout?
*
@@ -819,6 +823,163 @@ declare module "bun" {
line?: number;
column?: number;
}
+
+ /**
+ * This class only exists in types
+ */
+ abstract class CryptoHashInterface<T> {
+ /**
+ * Update the hash with data
+ *
+ * @param data
+ */
+ update(data: StringOrBuffer): T;
+
+ /**
+ * Finalize the hash
+ *
+ * @param encoding `DigestEncoding` to return the hash in. If none is provided, it will return a `Uint8Array`.
+ */
+ digest(encoding: DigestEncoding): string;
+
+ /**
+ * Finalize the hash
+ *
+ * @param hashInto `TypedArray` to write the hash into. Faster than creating a new one each time
+ */
+ digest(hashInto?: TypedArray): TypedArray;
+
+ /**
+ * Run the hash over the given data
+ *
+ * @param input `string`, `Uint8Array`, or `ArrayBuffer` to hash. `Uint8Array` or `ArrayBuffer` is faster.
+ *
+ * @param hashInto `TypedArray` to write the hash into. Faster than creating a new one each time
+ */
+ static hash(input: StringOrBuffer, hashInto?: TypedArray): TypedArray;
+
+ /**
+ * Run the hash over the given data
+ *
+ * @param input `string`, `Uint8Array`, or `ArrayBuffer` to hash. `Uint8Array` or `ArrayBuffer` is faster.
+ *
+ * @param encoding `DigestEncoding` to return the hash in
+ */
+ static hash(input: StringOrBuffer, encoding: DigestEncoding): string;
+ }
+
+ /**
+ *
+ * Hash `input` using [SHA-2 512/256](https://en.wikipedia.org/wiki/SHA-2#Comparison_of_SHA_functions)
+ *
+ * @param input `string`, `Uint8Array`, or `ArrayBuffer` to hash. `Uint8Array` or `ArrayBuffer` will be faster
+ * @param hashInto optional `Uint8Array` to write the hash to. 32 bytes minimum.
+ *
+ * This hashing function balances speed with cryptographic strength. This does not encrypt or decrypt data.
+ *
+ * The implementation uses [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
+ *
+ * The equivalent `openssl` command is:
+ *
+ * ```bash
+ * # You will need OpenSSL 3 or later
+ * openssl sha512-256 /path/to/file
+ *```
+ */
+ export function sha(input: StringOrBuffer, hashInto?: Uint8Array): Uint8Array;
+
+ /**
+ *
+ * Hash `input` using [SHA-2 512/256](https://en.wikipedia.org/wiki/SHA-2#Comparison_of_SHA_functions)
+ *
+ * @param input `string`, `Uint8Array`, or `ArrayBuffer` to hash. `Uint8Array` or `ArrayBuffer` will be faster
+ * @param encoding `DigestEncoding` to return the hash in
+ *
+ * This hashing function balances speed with cryptographic strength. This does not encrypt or decrypt data.
+ *
+ * The implementation uses [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
+ *
+ * The equivalent `openssl` command is:
+ *
+ * ```bash
+ * # You will need OpenSSL 3 or later
+ * openssl sha512-256 /path/to/file
+ *```
+ */
+ export function sha(input: StringOrBuffer, encoding: DigestEncoding): string;
+
+ /**
+ * This is not the default because it's not cryptographically secure and it's slower than {@link SHA512}
+ *
+ * Consider using the ugly-named {@link SHA512_256} instead
+ */
+ export class SHA1 extends CryptoHashInterface<SHA1> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 20;
+ }
+ export class MD5 extends CryptoHashInterface<MD5> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 16;
+ }
+ export class MD4 extends CryptoHashInterface<MD4> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 16;
+ }
+ export class SHA224 extends CryptoHashInterface<SHA224> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 28;
+ }
+ export class SHA512 extends CryptoHashInterface<SHA512> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 64;
+ }
+ export class SHA384 extends CryptoHashInterface<SHA384> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 48;
+ }
+ export class SHA256 extends CryptoHashInterface<SHA256> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 32;
+ }
+ /**
+ * See also {@link sha}
+ */
+ export class SHA512_256 extends CryptoHashInterface<SHA512_256> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 32;
+ }
}
type TypedArray =
@@ -853,7 +1014,6 @@ interface BufferEncodingOption {
declare var Bun: typeof import("bun");
-
// ./fs.d.ts
/**
@@ -4455,7 +4615,6 @@ declare module "node:fs" {
export = fs;
}
-
// ./html-rewriter.d.ts
declare namespace HTMLRewriterTypes {
@@ -4572,7 +4731,6 @@ declare class HTMLRewriter {
transform(input: Response): Response;
}
-
// ./globals.d.ts
type Encoding = "utf-8" | "windows-1252" | "utf-16";
@@ -6001,7 +6159,6 @@ declare var Loader: {
resolveSync: (specifier: string, from: string) => string;
};
-
// ./path.d.ts
/**
@@ -6209,7 +6366,6 @@ declare module "node:path/win32" {
export * from "path/win32";
}
-
// ./bun-test.d.ts
/**
@@ -6248,4 +6404,3 @@ declare module "test" {
import BunTestModule = require("bun:test");
export = BunTestModule;
}
-
diff --git a/src/deps/boringssl.translated.zig b/src/deps/boringssl.translated.zig
index 17bb1af9d..4561df8ca 100644
--- a/src/deps/boringssl.translated.zig
+++ b/src/deps/boringssl.translated.zig
@@ -1302,16 +1302,16 @@ pub extern fn EVP_bf_cfb() [*c]const EVP_CIPHER;
pub extern fn EVP_cast5_ecb() [*c]const EVP_CIPHER;
pub extern fn EVP_cast5_cbc() [*c]const EVP_CIPHER;
pub extern fn EVP_CIPHER_CTX_set_flags(ctx: [*c]const EVP_CIPHER_CTX, flags: u32) void;
-pub extern fn EVP_md4() ?*const EVP_MD;
-pub extern fn EVP_md5() ?*const EVP_MD;
-pub extern fn EVP_sha1() ?*const EVP_MD;
-pub extern fn EVP_sha224() ?*const EVP_MD;
-pub extern fn EVP_sha256() ?*const EVP_MD;
-pub extern fn EVP_sha384() ?*const EVP_MD;
-pub extern fn EVP_sha512() ?*const EVP_MD;
-pub extern fn EVP_sha512_256() ?*const EVP_MD;
-pub extern fn EVP_blake2b256() ?*const EVP_MD;
-pub extern fn EVP_md5_sha1() ?*const EVP_MD;
+pub extern fn EVP_md4() *const EVP_MD;
+pub extern fn EVP_md5() *const EVP_MD;
+pub extern fn EVP_sha1() *const EVP_MD;
+pub extern fn EVP_sha224() *const EVP_MD;
+pub extern fn EVP_sha256() *const EVP_MD;
+pub extern fn EVP_sha384() *const EVP_MD;
+pub extern fn EVP_sha512() *const EVP_MD;
+pub extern fn EVP_sha512_256() *const EVP_MD;
+pub extern fn EVP_blake2b256() *const EVP_MD;
+pub extern fn EVP_md5_sha1() *const EVP_MD;
pub extern fn EVP_get_digestbynid(nid: c_int) ?*const EVP_MD;
pub extern fn EVP_get_digestbyobj(obj: ?*const ASN1_OBJECT) ?*const EVP_MD;
pub extern fn EVP_MD_CTX_init(ctx: [*c]EVP_MD_CTX) void;
diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig
index 011314644..660f2aba8 100644
--- a/src/javascript/jsc/api/bun.zig
+++ b/src/javascript/jsc/api/bun.zig
@@ -1118,19 +1118,7 @@ pub const Class = NewClass(
.rfn = JSC.WebCore.Blob.writeFile,
.ts = d.ts{},
},
- .sha1 = .{
- .rfn = JSC.wrapWithHasContainer(Crypto.SHA1, "hash", false, false),
- },
- .sha256 = .{
- .rfn = JSC.wrapWithHasContainer(Crypto.SHA256, "hash", false, false),
- },
- .sha384 = .{
- .rfn = JSC.wrapWithHasContainer(Crypto.SHA384, "hash", false, false),
- },
- .sha512 = .{
- .rfn = JSC.wrapWithHasContainer(Crypto.SHA512, "hash", false, false),
- },
- .sha512_256 = .{
+ .sha = .{
.rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false),
},
},
@@ -1189,18 +1177,28 @@ pub const Class = NewClass(
.unsafe = .{
.get = getUnsafe,
},
+
.SHA1 = .{
.get = Crypto.SHA1.getter,
},
- .SHA256 = .{
- .get = Crypto.SHA256.getter,
+ .MD5 = .{
+ .get = Crypto.MD5.getter,
},
- .SHA384 = .{
- .get = Crypto.SHA384.getter,
+ .MD4 = .{
+ .get = Crypto.MD4.getter,
+ },
+ .SHA224 = .{
+ .get = Crypto.SHA224.getter,
},
.SHA512 = .{
.get = Crypto.SHA512.getter,
},
+ .SHA384 = .{
+ .get = Crypto.SHA384.getter,
+ },
+ .SHA256 = .{
+ .get = Crypto.SHA256.getter,
+ },
.SHA512_256 = .{
.get = Crypto.SHA512_256.getter,
},
@@ -1238,7 +1236,7 @@ pub const Crypto = struct {
@This(),
.{
.hash = .{
- .rfn = JSC.wrapSync(@This(), "hash"),
+ .rfn = JSC.wrapWithHasContainer(@This(), "hash", false, false),
},
.constructor = .{ .rfn = constructor },
},
@@ -1258,8 +1256,8 @@ pub const Crypto = struct {
.update = .{
.rfn = JSC.wrapSync(@This(), "update"),
},
- .final = .{
- .rfn = JSC.wrapSync(@This(), "final"),
+ .digest = .{
+ .rfn = JSC.wrapSync(@This(), "digest"),
},
.finalize = finalize,
},
@@ -1270,7 +1268,20 @@ pub const Crypto = struct {
},
);
- pub fn hash(
+ fn hashToEncoding(
+ globalThis: *JSGlobalObject,
+ input: JSC.Node.StringOrBuffer,
+ encoding: JSC.Node.Encoding,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.JSValue {
+ var output_digest_buf: Hasher.Digest = undefined;
+
+ Hasher.hash(input.slice(), &output_digest_buf, JSC.VirtualMachine.vm.rareData().boringEngine());
+
+ return encoding.encodeWithSize(globalThis, Hasher.digest, &output_digest_buf, exception);
+ }
+
+ fn hashToBytes(
globalThis: *JSGlobalObject,
input: JSC.Node.StringOrBuffer,
output: ?JSC.ArrayBuffer,
@@ -1293,7 +1304,7 @@ pub const Crypto = struct {
output_digest_slice = bytes[0..Hasher.digest];
}
- Hasher.hash(input.slice(), output_digest_slice);
+ Hasher.hash(input.slice(), output_digest_slice, JSC.VirtualMachine.vm.rareData().boringEngine());
if (output) |output_buf| {
return output_buf.value;
@@ -1303,6 +1314,37 @@ pub const Crypto = struct {
}
}
+ pub fn hash(
+ globalThis: *JSGlobalObject,
+ input: JSC.Node.StringOrBuffer,
+ output: ?JSC.Node.StringOrBuffer,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.JSValue {
+ if (output) |string_or_buffer| {
+ switch (string_or_buffer) {
+ .string => |str| {
+ const encoding = JSC.Node.Encoding.from(str) orelse {
+ JSC.JSError(
+ bun.default_allocator,
+ "Unknown encoding",
+ .{},
+ globalThis.ref(),
+ exception,
+ );
+ return JSC.JSValue.zero;
+ };
+
+ return hashToEncoding(globalThis, input, encoding, exception);
+ },
+ .buffer => |buffer| {
+ return hashToBytes(globalThis, input, buffer.buffer, exception);
+ },
+ }
+ } else {
+ return hashToBytes(globalThis, input, null, exception);
+ }
+ }
+
pub fn constructor(
ctx: js.JSContextRef,
_: js.JSObjectRef,
@@ -1336,12 +1378,47 @@ pub const Crypto = struct {
return existing.asObjectRef();
}
- pub fn update(this: *@This(), buffer: JSC.Node.StringOrBuffer) JSC.JSValue {
+ pub fn update(this: *@This(), thisObj: JSC.C.JSObjectRef, buffer: JSC.Node.StringOrBuffer) JSC.JSValue {
this.hashing.update(buffer.slice());
- return JSC.JSValue.jsUndefined();
+ return JSC.JSValue.c(thisObj);
}
- pub fn final(this: *@This(), globalThis: *JSGlobalObject, exception: JSC.C.ExceptionRef, output: ?JSC.ArrayBuffer) JSC.JSValue {
+ pub fn digest(
+ this: *@This(),
+ globalThis: *JSGlobalObject,
+ output: ?JSC.Node.StringOrBuffer,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.JSValue {
+ if (output) |string_or_buffer| {
+ switch (string_or_buffer) {
+ .string => |str| {
+ const encoding = JSC.Node.Encoding.from(str) orelse {
+ JSC.JSError(
+ bun.default_allocator,
+ "Unknown encoding",
+ .{},
+ globalThis.ref(),
+ exception,
+ );
+ return JSC.JSValue.zero;
+ };
+
+ return this.digestToEncoding(globalThis, exception, encoding);
+ },
+ .buffer => |buffer| {
+ return this.digestToBytes(
+ globalThis,
+ exception,
+ buffer.buffer,
+ );
+ },
+ }
+ } else {
+ return this.digestToBytes(globalThis, exception, null);
+ }
+ }
+
+ fn digestToBytes(this: *@This(), globalThis: *JSGlobalObject, exception: JSC.C.ExceptionRef, output: ?JSC.ArrayBuffer) JSC.JSValue {
var output_digest_buf: Hasher.Digest = undefined;
var output_digest_slice: *Hasher.Digest = &output_digest_buf;
if (output) |output_buf| {
@@ -1370,6 +1447,7 @@ pub const Crypto = struct {
}
this.hashing.final(output_digest_slice);
+
if (output) |output_buf| {
return output_buf.value;
} else {
@@ -1378,17 +1456,39 @@ pub const Crypto = struct {
}
}
+ fn digestToEncoding(this: *@This(), globalThis: *JSGlobalObject, exception: JSC.C.ExceptionRef, encoding: JSC.Node.Encoding) JSC.JSValue {
+ var output_digest_buf: Hasher.Digest = comptime brk: {
+ var bytes: Hasher.Digest = undefined;
+ var i: usize = 0;
+ while (i < Hasher.digest) {
+ bytes[i] = 0;
+ i += 1;
+ }
+ break :brk bytes;
+ };
+
+ var output_digest_slice: *Hasher.Digest = &output_digest_buf;
+
+ this.hashing.final(output_digest_slice);
+
+ return encoding.encodeWithSize(globalThis, Hasher.digest, output_digest_slice, exception);
+ }
+
pub fn finalize(this: *@This()) void {
VirtualMachine.vm.allocator.destroy(this);
}
};
}
- pub const SHA1 = CryptoHasher(Hashers.SHA1, "SHA1", "Bun_SHA1");
- pub const SHA256 = CryptoHasher(Hashers.SHA256, "SHA256", "Bun_SHA256");
- pub const SHA384 = CryptoHasher(Hashers.SHA384, "SHA384", "Bun_SHA384");
- pub const SHA512 = CryptoHasher(Hashers.SHA512, "SHA512", "Bun_SHA512");
- pub const SHA512_256 = CryptoHasher(Hashers.SHA512_256, "SHA512_256", "Bun_SHA512_256");
+ pub const SHA1 = CryptoHasher(Hashers.SHA1, "SHA1", "Bun_Crypto_SHA1");
+ pub const MD5 = CryptoHasher(Hashers.MD5, "MD5", "Bun_Crypto_MD5");
+ pub const MD4 = CryptoHasher(Hashers.MD4, "MD4", "Bun_Crypto_MD4");
+ pub const SHA224 = CryptoHasher(Hashers.SHA224, "SHA224", "Bun_Crypto_SHA224");
+ pub const SHA512 = CryptoHasher(Hashers.SHA512, "SHA512", "Bun_Crypto_SHA512");
+ pub const SHA384 = CryptoHasher(Hashers.SHA384, "SHA384", "Bun_Crypto_SHA384");
+ pub const SHA256 = CryptoHasher(Hashers.SHA256, "SHA256", "Bun_Crypto_SHA256");
+ pub const SHA512_256 = CryptoHasher(Hashers.SHA512_256, "SHA512_256", "Bun_Crypto_SHA512_256");
+ pub const MD5_SHA1 = CryptoHasher(Hashers.MD5_SHA1, "MD5_SHA1", "Bun_Crypto_MD5_SHA1");
};
pub fn serve(
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index 9c07e9b37..23224b7ab 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -2557,10 +2557,14 @@ const SSLServer = JSC.API.SSLServer;
const DebugServer = JSC.API.DebugServer;
const DebugSSLServer = JSC.API.DebugSSLServer;
const SHA1 = JSC.API.Bun.Crypto.SHA1;
-const SHA256 = JSC.API.Bun.Crypto.SHA256;
-const SHA384 = JSC.API.Bun.Crypto.SHA384;
+const MD5 = JSC.API.Bun.Crypto.MD5;
+const MD4 = JSC.API.Bun.Crypto.MD4;
+const SHA224 = JSC.API.Bun.Crypto.SHA224;
const SHA512 = JSC.API.Bun.Crypto.SHA512;
+const SHA384 = JSC.API.Bun.Crypto.SHA384;
+const SHA256 = JSC.API.Bun.Crypto.SHA256;
const SHA512_256 = JSC.API.Bun.Crypto.SHA512_256;
+const MD5_SHA1 = JSC.API.Bun.Crypto.MD5_SHA1;
pub const JSPrivateDataPtr = TaggedPointerUnion(.{
AttributeIterator,
@@ -2584,6 +2588,9 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
HTMLRewriter,
JSNode,
LazyPropertiesObject,
+ MD4,
+ MD5_SHA1,
+ MD5,
ModuleNamespace,
NodeFS,
Request,
@@ -2591,6 +2598,12 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
Response,
Router,
Server,
+ SHA1,
+ SHA224,
+ SHA256,
+ SHA384,
+ SHA512_256,
+ SHA512,
SSLServer,
Stats,
TextChunk,
@@ -2598,11 +2611,6 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
TextEncoder,
TimeoutTask,
Transpiler,
- SHA1,
- SHA256,
- SHA384,
- SHA512,
- SHA512_256,
});
pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type {
diff --git a/src/javascript/jsc/node/types.zig b/src/javascript/jsc/node/types.zig
index 63b9b4444..1bf44e211 100644
--- a/src/javascript/jsc/node/types.zig
+++ b/src/javascript/jsc/node/types.zig
@@ -193,6 +193,11 @@ pub const Encoding = enum(u8) {
var str = JSC.ZigString.Empty;
value.toZigString(&str, global);
const slice = str.slice();
+ return from(slice);
+ }
+
+ /// Caller must verify the value is a string
+ pub fn from(slice: []const u8) ?Encoding {
return switch (slice.len) {
0...2 => null,
else => switch (Eight.matchLower(slice)) {
@@ -214,6 +219,34 @@ pub const Encoding = enum(u8) {
},
};
}
+
+ pub fn encodeWithSize(encoding: Encoding, globalThis: *JSC.JSGlobalObject, comptime size: usize, input: *const [size]u8, exception: JSC.C.ExceptionRef) JSC.JSValue {
+ switch (encoding) {
+ .base64 => {
+ var base64: [std.base64.standard.Encoder.calcSize(size)]u8 = undefined;
+ const result = JSC.ZigString.init(std.base64.standard.Encoder.encode(&base64, input)).toValueGC(globalThis);
+ return result;
+ },
+ .base64url => {
+ var buf: [std.base64.url_safe.Encoder.calcSize(size) + "data:;base64,".len]u8 = undefined;
+ var encoded = std.base64.url_safe.Encoder.encode(buf["data:;base64,".len..], input);
+ buf[0.."data:;base64,".len].* = "data:;base64,".*;
+
+ const result = JSC.ZigString.init(buf[0 .. "data:;base64,".len + encoded.len]).toValueGC(globalThis);
+ return result;
+ },
+ .hex => {
+ var buf: [size * 4]u8 = undefined;
+ var out = std.fmt.bufPrint(&buf, "{}", .{std.fmt.fmtSliceHexLower(input)}) catch unreachable;
+ const result = JSC.ZigString.init(out).toValueGC(globalThis);
+ return result;
+ },
+ else => {
+ JSC.throwInvalidArguments("Unexpected encoding", .{}, globalThis.ref(), exception);
+ return JSC.JSValue.zero;
+ },
+ }
+ }
};
const PathOrBuffer = union(Tag) {
diff --git a/src/javascript/jsc/rare_data.zig b/src/javascript/jsc/rare_data.zig
index efd602085..b6559826c 100644
--- a/src/javascript/jsc/rare_data.zig
+++ b/src/javascript/jsc/rare_data.zig
@@ -6,12 +6,21 @@ const RareData = @This();
const Syscall = @import("./node/syscall.zig");
const JSC = @import("javascript_core");
const std = @import("std");
+const BoringSSL = @import("boringssl");
+boring_ssl_engine: ?*BoringSSL.ENGINE = null,
editor_context: EditorContext = EditorContext{},
stderr_store: ?*Blob.Store = null,
stdin_store: ?*Blob.Store = null,
stdout_store: ?*Blob.Store = null,
+pub fn boringEngine(rare: *RareData) *BoringSSL.ENGINE {
+ return rare.boring_ssl_engine orelse brk: {
+ rare.boring_ssl_engine = BoringSSL.ENGINE_new();
+ break :brk rare.boring_ssl_engine.?;
+ };
+}
+
pub fn stderr(rare: *RareData) *Blob.Store {
return rare.stderr_store orelse brk: {
var store = default_allocator.create(Blob.Store) catch unreachable;
diff --git a/src/sha.zig b/src/sha.zig
index 525392a30..415941fb4 100644
--- a/src/sha.zig
+++ b/src/sha.zig
@@ -31,78 +31,150 @@ fn NewHasher(comptime digest_size: comptime_int, comptime ContextType: type, com
};
}
-pub const SHA1 = NewHasher(
- std.crypto.hash.Sha1.digest_length,
- BoringSSL.SHA_CTX,
- BoringSSL.SHA1,
- BoringSSL.SHA1_Init,
- BoringSSL.SHA1_Update,
- BoringSSL.SHA1_Final,
-);
-
-pub const SHA512 = NewHasher(
- std.crypto.hash.sha2.Sha512.digest_length,
- BoringSSL.SHA512_CTX,
- BoringSSL.SHA512,
- BoringSSL.SHA512_Init,
- BoringSSL.SHA512_Update,
- BoringSSL.SHA512_Final,
-);
-
-pub const SHA384 = NewHasher(
- std.crypto.hash.sha2.Sha384.digest_length,
- BoringSSL.SHA512_CTX,
- BoringSSL.SHA384,
- BoringSSL.SHA384_Init,
- BoringSSL.SHA384_Update,
- BoringSSL.SHA384_Final,
-);
-
-pub const SHA256 = NewHasher(
- std.crypto.hash.sha2.Sha256.digest_length,
- BoringSSL.SHA256_CTX,
- BoringSSL.SHA256,
- BoringSSL.SHA256_Init,
- BoringSSL.SHA256_Update,
- BoringSSL.SHA256_Final,
-);
-
-pub const SHA512_256 = NewHasher(
- std.crypto.hash.sha2.Sha512256.digest_length,
- BoringSSL.SHA512_CTX,
- BoringSSL.SHA512_256,
- BoringSSL.SHA512_256_Init,
- BoringSSL.SHA512_256_Update,
- BoringSSL.SHA512_256_Final,
-);
+fn NewEVP(
+ comptime digest_size: comptime_int,
+ comptime MDName: []const u8,
+) type {
+ return struct {
+ ctx: BoringSSL.EVP_MD_CTX,
-pub fn main() anyerror!void {
- var file = try std.fs.cwd().openFileZ(std.os.argv[std.os.argv.len - 1], .{});
- var bytes = try file.readToEndAlloc(std.heap.c_allocator, std.math.maxInt(usize));
+ pub const Digest = [digest_size]u8;
+ pub const digest: comptime_int = digest_size;
- const boring = [_]type{
- SHA1,
- SHA512,
- SHA384,
- SHA256,
- SHA512_256,
- };
+ pub fn init() @This() {
+ const md = @call(.{}, @field(BoringSSL, MDName), .{});
+ var this: @This() = .{
+ .ctx = undefined,
+ };
- const zig = [_]type{
- std.crypto.hash.Sha1,
- std.crypto.hash.sha2.Sha512,
- std.crypto.hash.sha2.Sha384,
- std.crypto.hash.sha2.Sha256,
- std.crypto.hash.sha2.Sha512256,
- };
+ BoringSSL.EVP_MD_CTX_init(&this.ctx);
+
+ std.debug.assert(BoringSSL.EVP_DigestInit(&this.ctx, md) == 1);
+
+ return this;
+ }
+
+ pub fn hash(bytes: []const u8, out: *Digest, engine: *BoringSSL.ENGINE) void {
+ const md = @call(.{}, @field(BoringSSL, MDName), .{});
+
+ std.debug.assert(BoringSSL.EVP_Digest(bytes.ptr, bytes.len, out, null, md, engine) == 1);
+ }
- const labels = [_][]const u8{
- "SHA1",
- "SHA512",
- "SHA384",
- "SHA256",
- "SHA512_256",
+ pub fn update(this: *@This(), data: []const u8) void {
+ std.debug.assert(BoringSSL.EVP_DigestUpdate(&this.ctx, data.ptr, data.len) == 1);
+ }
+
+ pub fn final(this: *@This(), out: *Digest) void {
+ std.debug.assert(BoringSSL.EVP_DigestFinal(&this.ctx, out, null) == 1);
+ }
};
+}
+pub const EVP = struct {
+ pub const SHA1 = NewEVP(std.crypto.hash.Sha1.digest_length, "EVP_sha1");
+ pub const MD5 = NewEVP(32, "EVP_md5");
+ pub const MD4 = NewEVP(32, "EVP_md4");
+ pub const SHA224 = NewEVP(28, "EVP_sha224");
+ pub const SHA512 = NewEVP(std.crypto.hash.sha2.Sha512.digest_length, "EVP_sha512");
+ pub const SHA384 = NewEVP(std.crypto.hash.sha2.Sha384.digest_length, "EVP_sha384");
+ pub const SHA256 = NewEVP(std.crypto.hash.sha2.Sha256.digest_length, "EVP_sha256");
+ pub const SHA512_256 = NewEVP(std.crypto.hash.sha2.Sha512256.digest_length, "EVP_sha512_256");
+ pub const MD5_SHA1 = NewEVP(std.crypto.hash.Sha1.digest_length, "EVP_md5_sha1");
+};
+
+pub const SHA1 = EVP.SHA1;
+pub const MD5 = EVP.MD5;
+pub const MD4 = EVP.MD4;
+pub const SHA224 = EVP.SHA224;
+pub const SHA512 = EVP.SHA512;
+pub const SHA384 = EVP.SHA384;
+pub const SHA256 = EVP.SHA256;
+pub const SHA512_256 = EVP.SHA512_256;
+pub const MD5_SHA1 = EVP.MD5_SHA1;
+
+/// API that OpenSSL 3 deprecated
+pub const Hashers = struct {
+ pub const SHA1 = NewHasher(
+ std.crypto.hash.Sha1.digest_length,
+ BoringSSL.SHA_CTX,
+ BoringSSL.SHA1,
+ BoringSSL.SHA1_Init,
+ BoringSSL.SHA1_Update,
+ BoringSSL.SHA1_Final,
+ );
+
+ pub const SHA512 = NewHasher(
+ std.crypto.hash.sha2.Sha512.digest_length,
+ BoringSSL.SHA512_CTX,
+ BoringSSL.SHA512,
+ BoringSSL.SHA512_Init,
+ BoringSSL.SHA512_Update,
+ BoringSSL.SHA512_Final,
+ );
+
+ pub const SHA384 = NewHasher(
+ std.crypto.hash.sha2.Sha384.digest_length,
+ BoringSSL.SHA512_CTX,
+ BoringSSL.SHA384,
+ BoringSSL.SHA384_Init,
+ BoringSSL.SHA384_Update,
+ BoringSSL.SHA384_Final,
+ );
+
+ pub const SHA256 = NewHasher(
+ std.crypto.hash.sha2.Sha256.digest_length,
+ BoringSSL.SHA256_CTX,
+ BoringSSL.SHA256,
+ BoringSSL.SHA256_Init,
+ BoringSSL.SHA256_Update,
+ BoringSSL.SHA256_Final,
+ );
+
+ pub const SHA512_256 = NewHasher(
+ std.crypto.hash.sha2.Sha512256.digest_length,
+ BoringSSL.SHA512_CTX,
+ BoringSSL.SHA512_256,
+ BoringSSL.SHA512_256_Init,
+ BoringSSL.SHA512_256_Update,
+ BoringSSL.SHA512_256_Final,
+ );
+};
+
+const boring = [_]type{
+ Hashers.SHA1,
+ Hashers.SHA512,
+ Hashers.SHA384,
+ Hashers.SHA256,
+ Hashers.SHA512_256,
+};
+
+const zig = [_]type{
+ std.crypto.hash.Sha1,
+ std.crypto.hash.sha2.Sha512,
+ std.crypto.hash.sha2.Sha384,
+ std.crypto.hash.sha2.Sha256,
+ std.crypto.hash.sha2.Sha512256,
+};
+
+const evp = [_]type{
+ EVP.SHA1,
+ EVP.SHA512,
+ EVP.SHA384,
+ EVP.SHA256,
+ EVP.SHA512_256,
+};
+
+const labels = [_][]const u8{
+ "SHA1",
+ "SHA512",
+ "SHA384",
+ "SHA256",
+ "SHA512_256",
+};
+pub fn main() anyerror!void {
+ var file = try std.fs.cwd().openFileZ(std.os.argv[std.os.argv.len - 1], .{});
+ var bytes = try file.readToEndAlloc(std.heap.c_allocator, std.math.maxInt(usize));
+
+ var engine = BoringSSL.ENGINE_new().?;
inline for (boring) |BoringHasher, i| {
const ZigHasher = zig[i];
@@ -112,6 +184,9 @@ pub fn main() anyerror!void {
);
var digest1: BoringHasher.Digest = undefined;
var digest2: BoringHasher.Digest = undefined;
+ var digest3: BoringHasher.Digest = undefined;
+ var digest4: BoringHasher.Digest = undefined;
+
var clock1 = try std.time.Timer.start();
ZigHasher.hash(bytes, &digest1, .{});
const zig_time = clock1.read();
@@ -120,16 +195,34 @@ pub fn main() anyerror!void {
BoringHasher.hash(bytes, &digest2);
const boring_time = clock2.read();
+ var clock3 = try std.time.Timer.start();
+ evp[i].hash(bytes, &digest3, engine);
+ const evp_time = clock3.read();
+
+ var evp_in = evp[i].init();
+ var clock4 = try std.time.Timer.start();
+ evp_in.update(bytes);
+ evp_in.final(&digest4);
+ const evp_in_time = clock4.read();
+
std.debug.print(
" zig: {}\n",
.{std.fmt.fmtDuration(zig_time)},
);
std.debug.print(
- " boring: {}\n\n",
+ " boring: {}\n",
.{std.fmt.fmtDuration(boring_time)},
);
+ std.debug.print(
+ " evp: {}\n",
+ .{std.fmt.fmtDuration(evp_time)},
+ );
+ std.debug.print(
+ " evp in: {}\n\n",
+ .{std.fmt.fmtDuration(evp_in_time)},
+ );
- if (!std.mem.eql(u8, &digest1, &digest2)) {
+ if (!std.mem.eql(u8, &digest3, &digest2)) {
@panic("\ndigests don't match! for " ++ labels[i]);
}
}
diff --git a/types/bun/bun.d.ts b/types/bun/bun.d.ts
index b20508529..4a1ddc518 100644
--- a/types/bun/bun.d.ts
+++ b/types/bun/bun.d.ts
@@ -740,6 +740,8 @@ declare module "bun" {
}
export const unsafe: unsafe;
+ type DigestEncoding = "hex" | "base64";
+
/**
* Are ANSI colors enabled for stdin and stdout?
*
@@ -811,6 +813,163 @@ declare module "bun" {
line?: number;
column?: number;
}
+
+ /**
+ * This class only exists in types
+ */
+ abstract class CryptoHashInterface<T> {
+ /**
+ * Update the hash with data
+ *
+ * @param data
+ */
+ update(data: StringOrBuffer): T;
+
+ /**
+ * Finalize the hash
+ *
+ * @param encoding `DigestEncoding` to return the hash in. If none is provided, it will return a `Uint8Array`.
+ */
+ digest(encoding: DigestEncoding): string;
+
+ /**
+ * Finalize the hash
+ *
+ * @param hashInto `TypedArray` to write the hash into. Faster than creating a new one each time
+ */
+ digest(hashInto?: TypedArray): TypedArray;
+
+ /**
+ * Run the hash over the given data
+ *
+ * @param input `string`, `Uint8Array`, or `ArrayBuffer` to hash. `Uint8Array` or `ArrayBuffer` is faster.
+ *
+ * @param hashInto `TypedArray` to write the hash into. Faster than creating a new one each time
+ */
+ static hash(input: StringOrBuffer, hashInto?: TypedArray): TypedArray;
+
+ /**
+ * Run the hash over the given data
+ *
+ * @param input `string`, `Uint8Array`, or `ArrayBuffer` to hash. `Uint8Array` or `ArrayBuffer` is faster.
+ *
+ * @param encoding `DigestEncoding` to return the hash in
+ */
+ static hash(input: StringOrBuffer, encoding: DigestEncoding): string;
+ }
+
+ /**
+ *
+ * Hash `input` using [SHA-2 512/256](https://en.wikipedia.org/wiki/SHA-2#Comparison_of_SHA_functions)
+ *
+ * @param input `string`, `Uint8Array`, or `ArrayBuffer` to hash. `Uint8Array` or `ArrayBuffer` will be faster
+ * @param hashInto optional `Uint8Array` to write the hash to. 32 bytes minimum.
+ *
+ * This hashing function balances speed with cryptographic strength. This does not encrypt or decrypt data.
+ *
+ * The implementation uses [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
+ *
+ * The equivalent `openssl` command is:
+ *
+ * ```bash
+ * # You will need OpenSSL 3 or later
+ * openssl sha512-256 /path/to/file
+ *```
+ */
+ export function sha(input: StringOrBuffer, hashInto?: Uint8Array): Uint8Array;
+
+ /**
+ *
+ * Hash `input` using [SHA-2 512/256](https://en.wikipedia.org/wiki/SHA-2#Comparison_of_SHA_functions)
+ *
+ * @param input `string`, `Uint8Array`, or `ArrayBuffer` to hash. `Uint8Array` or `ArrayBuffer` will be faster
+ * @param encoding `DigestEncoding` to return the hash in
+ *
+ * This hashing function balances speed with cryptographic strength. This does not encrypt or decrypt data.
+ *
+ * The implementation uses [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
+ *
+ * The equivalent `openssl` command is:
+ *
+ * ```bash
+ * # You will need OpenSSL 3 or later
+ * openssl sha512-256 /path/to/file
+ *```
+ */
+ export function sha(input: StringOrBuffer, encoding: DigestEncoding): string;
+
+ /**
+ * This is not the default because it's not cryptographically secure and it's slower than {@link SHA512}
+ *
+ * Consider using the ugly-named {@link SHA512_256} instead
+ */
+ export class SHA1 extends CryptoHashInterface<SHA1> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 20;
+ }
+ export class MD5 extends CryptoHashInterface<MD5> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 16;
+ }
+ export class MD4 extends CryptoHashInterface<MD4> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 16;
+ }
+ export class SHA224 extends CryptoHashInterface<SHA224> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 28;
+ }
+ export class SHA512 extends CryptoHashInterface<SHA512> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 64;
+ }
+ export class SHA384 extends CryptoHashInterface<SHA384> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 48;
+ }
+ export class SHA256 extends CryptoHashInterface<SHA256> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 32;
+ }
+ /**
+ * See also {@link sha}
+ */
+ export class SHA512_256 extends CryptoHashInterface<SHA512_256> {
+ constructor();
+
+ /**
+ * The number of bytes the hash will produce
+ */
+ static readonly byteLength: 32;
+ }
}
type TypedArray =