Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node:crypto: add blake2b512, sha512-224, sha3-* #10383

Merged
merged 16 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/api/hashing.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Bun.hash.murmur64v2("data", 1234);
`Bun.CryptoHasher` is a general-purpose utility class that lets you incrementally compute a hash of string or binary data using a range of cryptographic hash algorithms. The following algorithms are supported:

- `"blake2b256"`
- `"blake2b512"`
- `"md4"`
- `"md5"`
- `"ripemd160"`
Expand All @@ -120,7 +121,12 @@ Bun.hash.murmur64v2("data", 1234);
- `"sha256"`
- `"sha384"`
- `"sha512"`
- `"sha512-224"`
- `"sha512-256"`
- `"sha3-224"`
- `"sha3-256"`
- `"sha3-384"`
- `"sha3-512"`

```ts
const hasher = new Bun.CryptoHasher("sha256");
Expand Down
9 changes: 8 additions & 1 deletion packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3065,6 +3065,7 @@ declare module "bun" {

type SupportedCryptoAlgorithms =
| "blake2b256"
| "blake2b512"
| "md4"
| "md5"
| "ripemd160"
Expand All @@ -3073,7 +3074,13 @@ declare module "bun" {
| "sha256"
| "sha384"
| "sha512"
| "sha512-256";
| "sha512-224"
| "sha512-256"
| "sha3-224"
| "sha3-256"
| "sha3-384"
| "sha3-512";

/**
* Hardware-accelerated cryptographic hash functions
*
Expand Down
2 changes: 1 addition & 1 deletion scripts/build-boringssl.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ try {
Copy-Item ssl/ssl.lib $BUN_DEPS_OUT_DIR
Copy-Item decrepit/decrepit.lib $BUN_DEPS_OUT_DIR
Write-Host "-> crypto.lib, ssl.lib, decrepit.lib"
} finally { Pop-Location }
} finally { Pop-Location }
221 changes: 198 additions & 23 deletions src/bun.js/api/BunObject.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,7 @@ pub const Crypto = struct {
// @"RSA-SHA512",
// @"ecdsa-with-SHA1",
blake2b256,
blake2b512,
md4,
md5,
ripemd160,
Expand All @@ -1346,7 +1347,12 @@ pub const Crypto = struct {
sha256,
sha384,
sha512,
@"sha512-224",
@"sha512-256",
@"sha3-224",
@"sha3-256",
@"sha3-384",
@"sha3-512",

pub const names: std.EnumArray(Algorithm, ZigString) = brk: {
var all = std.EnumArray(Algorithm, ZigString).initUndefined();
Expand All @@ -1359,6 +1365,7 @@ pub const Crypto = struct {

pub const map = bun.ComptimeStringMap(Algorithm, .{
.{ "blake2b256", .blake2b256 },
.{ "blake2b512", .blake2b512 },
.{ "ripemd160", .ripemd160 },
.{ "rmd160", .ripemd160 },
.{ "md4", .md4 },
Expand All @@ -1374,11 +1381,19 @@ pub const Crypto = struct {
.{ "sha-256", .sha256 },
.{ "sha-384", .sha384 },
.{ "sha-512", .sha512 },
.{ "sha-512/224", .@"sha512-224" },
.{ "sha-512_224", .@"sha512-224" },
.{ "sha-512224", .@"sha512-224" },
.{ "sha512-224", .@"sha512-224" },
.{ "sha-512/256", .@"sha512-256" },
.{ "sha-512_256", .@"sha512-256" },
.{ "sha-512256", .@"sha512-256" },
.{ "sha512-256", .@"sha512-256" },
.{ "sha384", .sha384 },
.{ "sha3-224", .@"sha3-224" },
.{ "sha3-256", .@"sha3-256" },
.{ "sha3-384", .@"sha3-384" },
.{ "sha3-512", .@"sha3-512" },
// .{ "md5-sha1", .@"MD5-SHA1" },
// .{ "dsa-sha", .@"DSA-SHA" },
// .{ "dsa-sha1", .@"DSA-SHA1" },
Expand Down Expand Up @@ -1460,18 +1475,17 @@ pub const Crypto = struct {

pub fn byNameAndEngine(engine: *BoringSSL.ENGINE, name: []const u8) ?EVP {
if (Algorithm.map.getWithEql(name, strings.eqlCaseInsensitiveASCIIIgnoreLength)) |algorithm| {
if (algorithm == .blake2b256) {
return EVP.init(algorithm, BoringSSL.EVP_blake2b256(), engine);
}

switch (algorithm) {
.blake2b256 => return EVP.init(algorithm, BoringSSL.EVP_blake2b256(), engine),
.blake2b512 => return EVP.init(algorithm, BoringSSL.EVP_blake2b512(), engine),
.md4 => return EVP.init(algorithm, BoringSSL.EVP_md4(), engine),
.md5 => return EVP.init(algorithm, BoringSSL.EVP_md5(), engine),
.sha1 => return EVP.init(algorithm, BoringSSL.EVP_sha1(), engine),
.sha224 => return EVP.init(algorithm, BoringSSL.EVP_sha224(), engine),
.sha256 => return EVP.init(algorithm, BoringSSL.EVP_sha256(), engine),
.sha384 => return EVP.init(algorithm, BoringSSL.EVP_sha384(), engine),
.sha512 => return EVP.init(algorithm, BoringSSL.EVP_sha512(), engine),
.@"sha512-224" => return EVP.init(algorithm, BoringSSL.EVP_sha512_224(), engine),
.@"sha512-256" => return EVP.init(algorithm, BoringSSL.EVP_sha512_256(), engine),
else => {
if (BoringSSL.EVP_get_digestbyname(@tagName(algorithm))) |md|
Expand Down Expand Up @@ -2304,15 +2318,18 @@ pub const Crypto = struct {
}
};

pub const CryptoHasher = struct {
evp: EVP = undefined,
pub const CryptoHasher = union(enum) {
nektro marked this conversation as resolved.
Show resolved Hide resolved
evp: EVP,
zig: CryptoHasherZig,

const Digest = EVP.Digest;

pub usingnamespace JSC.Codegen.JSCryptoHasher;
usingnamespace bun.New(@This());

pub const digest = JSC.wrapInstanceMethod(CryptoHasher, "digest_", false);
pub const hash = JSC.wrapStaticMethod(CryptoHasher, "hash_", false);

pub fn getByteLength(
this: *CryptoHasher,
_: *JSC.JSGlobalObject,
Expand All @@ -2324,7 +2341,9 @@ pub const Crypto = struct {
this: *CryptoHasher,
globalObject: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
return ZigString.fromUTF8(bun.asByteSlice(@tagName(this.evp.algorithm))).toValueGC(globalObject);
return switch (this.*) {
inline else => |*inner| ZigString.fromUTF8(bun.asByteSlice(@tagName(inner.algorithm))).toValueGC(globalObject),
};
}

pub fn getAlgorithms(
Expand Down Expand Up @@ -2407,7 +2426,7 @@ pub const Crypto = struct {
input: JSC.Node.BlobOrStringOrBuffer,
output: ?JSC.Node.StringOrBuffer,
) JSC.JSValue {
var evp = EVP.byName(algorithm, globalThis) orelse {
var evp = EVP.byName(algorithm, globalThis) orelse return CryptoHasherZig.hashByName(globalThis, algorithm, input, output) orelse {
globalThis.throwInvalidArguments("Unsupported algorithm \"{any}\"", .{algorithm});
return .zero;
};
Expand Down Expand Up @@ -2453,13 +2472,13 @@ pub const Crypto = struct {
return null;
}

const evp = EVP.byName(algorithm, globalThis) orelse {
var this: CryptoHasher = undefined;
const evp = EVP.byName(algorithm, globalThis) orelse return CryptoHasherZig.constructor(algorithm) orelse {
globalThis.throwInvalidArguments("Unsupported algorithm {any}", .{algorithm});
return null;
};
var this = bun.default_allocator.create(CryptoHasher) catch return null;
this.evp = evp;
return this;
this = .{ .evp = evp };
return CryptoHasher.new(this);
}

pub fn getter(
Expand All @@ -2484,6 +2503,14 @@ pub const Crypto = struct {
return .zero;
}

switch (this.*) {
.evp => {},
.zig => |*inner| {
inner.update(buffer.slice());
return thisValue;
},
}

this.evp.update(buffer.slice());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you move this to .evp =>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wanted to keep the diff small for the first iteration to make easier to review and test

const err = BoringSSL.ERR_get_error();
if (err != 0) {
Expand All @@ -2501,13 +2528,20 @@ pub const Crypto = struct {
globalObject: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const new = bun.default_allocator.create(CryptoHasher) catch @panic("Out of memory");
new.evp = this.evp.copy(globalObject.bunVM().rareData().boringEngine()) catch @panic("Out of memory");
return new.toJS(globalObject);
var new: CryptoHasher = undefined;
switch (this.*) {
.evp => {
new = .{ .evp = this.evp.copy(globalObject.bunVM().rareData().boringEngine()) catch @panic("Out of memory") };
},
.zig => |*inner| {
nektro marked this conversation as resolved.
Show resolved Hide resolved
new = .{ .zig = inner.copy() };
},
}
return CryptoHasher.new(new).toJS(globalObject);
}

pub fn digest_(
this: *@This(),
this: *CryptoHasher,
globalThis: *JSGlobalObject,
output: ?JSC.Node.StringOrBuffer,
) JSC.JSValue {
Expand Down Expand Up @@ -2563,16 +2597,157 @@ pub const Crypto = struct {

const output_digest_slice: []u8 = &output_digest_buf;

switch (this.*) {
.evp => {},
.zig => |*inner| {
inner.final(output_digest_slice);
return encoding.encodeWithMaxSize(globalThis, BoringSSL.EVP_MAX_MD_SIZE, output_digest_slice[0..inner.digest_length]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BoringSSL.EVP_MAX_MD_SIZE should be a different value here right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BoringSSL.EVP_MAX_MD_SIZE is the max size of all digests, output_digest_slice is the size of the current digest

},
}

const out = this.evp.final(globalThis.bunVM().rareData().boringEngine(), output_digest_slice);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be moved into the .evp branch?:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wanted to keep the diff small for the first iteration to make easier to review and test


return encoding.encodeWithMaxSize(globalThis, BoringSSL.EVP_MAX_MD_SIZE, out);
}

pub fn finalize(this: *CryptoHasher) callconv(.C) void {
// https://github.com/oven-sh/bun/issues/3250
this.evp.deinit();
switch (this.*) {
.evp => {
// https://github.com/oven-sh/bun/issues/3250
this.evp.deinit();
},
.zig => |*inner| {
inner.deinit();
},
}
this.destroy();
}
};

bun.default_allocator.destroy(this);
const CryptoHasherZig = struct {
algorithm: EVP.Algorithm,
state: *anyopaque,
nektro marked this conversation as resolved.
Show resolved Hide resolved
digest_length: u8,

const algo_map = [_]struct { string, type }{
.{ "sha3-224", std.crypto.hash.sha3.Sha3_224 },
.{ "sha3-256", std.crypto.hash.sha3.Sha3_256 },
.{ "sha3-384", std.crypto.hash.sha3.Sha3_384 },
.{ "sha3-512", std.crypto.hash.sha3.Sha3_512 },
};

pub fn hashByName(
globalThis: *JSGlobalObject,
algorithm: ZigString,
input: JSC.Node.BlobOrStringOrBuffer,
output: ?JSC.Node.StringOrBuffer,
) ?JSC.JSValue {
inline for (algo_map) |item| {
if (bun.strings.eqlComptime(algorithm.slice(), item[0])) {
return hashByNameInner(globalThis, item[1], input, output);
}
}
return null;
}

fn hashByNameInner(globalThis: *JSGlobalObject, comptime Algorithm: type, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.Node.StringOrBuffer) JSC.JSValue {
if (output) |string_or_buffer| {
switch (string_or_buffer) {
inline else => |*str| {
defer str.deinit();
globalThis.throwInvalidArguments("Unknown encoding: {s}", .{str.slice()});
return JSC.JSValue.zero;
},
.buffer => |buffer| {
return hashByNameInnerToBytes(globalThis, Algorithm, input, buffer.buffer);
},
}
}
return hashByNameInnerToBytes(globalThis, Algorithm, input, null);
}

fn hashByNameInnerToBytes(globalThis: *JSGlobalObject, comptime Algorithm: type, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.ArrayBuffer) JSC.JSValue {
defer input.deinit();

if (input == .blob and input.blob.isBunFile()) {
globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
return .zero;
}

var h = Algorithm.init(.{});
const digest_length_comptime = Algorithm.digest_length;

if (output) |output_buf| {
if (output_buf.byteSlice().len < digest_length_comptime) {
globalThis.throwInvalidArguments("TypedArray must be at least {d} bytes", .{digest_length_comptime});
return JSC.JSValue.zero;
}
}

h.update(input.slice());

if (output) |output_buf| {
h.final(output_buf.slice()[0..digest_length_comptime]);
return output_buf.value;
} else {
var out: [Algorithm.digest_length]u8 = undefined;
h.final(&out);
// Clone to GC-managed memory
return JSC.ArrayBuffer.create(globalThis, &out, .Buffer);
}
}

fn constructor(algorithm: ZigString) callconv(.C) ?*CryptoHasher {
inline for (algo_map) |item| {
if (bun.strings.eqlComptime(algorithm.slice(), item[0])) {
return CryptoHasher.new(.{ .zig = .{
.algorithm = @field(EVP.Algorithm, item[0]),
.state = bun.new(item[1], .{}),
.digest_length = item[1].digest_length,
} });
}
}
return null;
}

fn update(self: *CryptoHasherZig, bytes: []const u8) void {
inline for (algo_map) |item| {
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
return item[1].update(@ptrCast(@alignCast(self.state)), bytes);
}
}
@panic("unreachable");
}

fn copy(self: *const CryptoHasherZig) CryptoHasherZig {
inline for (algo_map) |item| {
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
return .{
.algorithm = self.algorithm,
.state = bun.dupe(item[1], @ptrCast(@alignCast(self.state))),
.digest_length = self.digest_length,
};
}
}
@panic("unreachable");
}

fn final(self: *CryptoHasherZig, output_digest_slice: []u8) void {
inline for (algo_map) |item| {
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
return item[1].final(@ptrCast(@alignCast(self.state)), @ptrCast(output_digest_slice));
}
}
@panic("unreachable");
}

fn deinit(self: *CryptoHasherZig) void {
inline for (algo_map) |item| {
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
return bun.destroy(@as(*item[1], @ptrCast(@alignCast(self.state))));
}
}
@panic("unreachable");
}
};

Expand Down Expand Up @@ -2792,13 +2967,13 @@ pub const Crypto = struct {
};
}

pub const SHA1 = StaticCryptoHasher(Hashers.SHA1, "SHA1");
pub const MD5 = StaticCryptoHasher(Hashers.MD5, "MD5");
pub const MD4 = StaticCryptoHasher(Hashers.MD4, "MD4");
pub const MD5 = StaticCryptoHasher(Hashers.MD5, "MD5");
pub const SHA1 = StaticCryptoHasher(Hashers.SHA1, "SHA1");
pub const SHA224 = StaticCryptoHasher(Hashers.SHA224, "SHA224");
pub const SHA512 = StaticCryptoHasher(Hashers.SHA512, "SHA512");
pub const SHA384 = StaticCryptoHasher(Hashers.SHA384, "SHA384");
pub const SHA256 = StaticCryptoHasher(Hashers.SHA256, "SHA256");
pub const SHA384 = StaticCryptoHasher(Hashers.SHA384, "SHA384");
pub const SHA512 = StaticCryptoHasher(Hashers.SHA512, "SHA512");
pub const SHA512_256 = StaticCryptoHasher(Hashers.SHA512_256, "SHA512_256");
};

Expand Down
Loading
Loading