diff options
author | 2022-04-13 04:20:05 -0700 | |
---|---|---|
committer | 2022-04-13 04:20:05 -0700 | |
commit | 3db3057d421a145743d63ebb5a487fb73e0e9f0b (patch) | |
tree | ece61bf4b75fb7f108c038082a6b273a234f27bf | |
parent | f6d73cb06ec5fe217a4d2d03808f68ecbc2d3461 (diff) | |
download | bun-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.js | 98 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/tsconfig.json | 3 | ||||
-rw-r--r-- | packages/bun-types/types.d.ts | 167 | ||||
-rw-r--r-- | src/deps/boringssl.translated.zig | 20 | ||||
-rw-r--r-- | src/javascript/jsc/api/bun.zig | 160 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 22 | ||||
-rw-r--r-- | src/javascript/jsc/node/types.zig | 33 | ||||
-rw-r--r-- | src/javascript/jsc/rare_data.zig | 9 | ||||
-rw-r--r-- | src/sha.zig | 231 | ||||
-rw-r--r-- | types/bun/bun.d.ts | 159 |
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 = |