diff --git a/Makefile b/Makefile index 59adaee220..99040c82e9 100644 --- a/Makefile +++ b/Makefile @@ -255,7 +255,7 @@ DEFAULT_LINKER_FLAGS= -pthread -ldl endif ifeq ($(OS_NAME),darwin) _MIMALLOC_OBJECT_FILE = 0 - JSC_BUILD_STEPS += jsc-build-mac jsc-copy-headers + JSC_BUILD_STEPS += jsc-build-mac JSC_BUILD_STEPS_DEBUG += jsc-build-mac-debug _MIMALLOC_FILE = libmimalloc.a _MIMALLOC_INPUT_PATH = libmimalloc.a @@ -924,7 +924,7 @@ bun-codesign-release-local-debug: .PHONY: jsc -jsc: jsc-build jsc-copy-headers jsc-bindings +jsc: jsc-build .PHONY: jsc-debug jsc-debug: jsc-build-debug .PHONY: jsc-build diff --git a/bench/crypto/hkdf.mjs b/bench/crypto/hkdf.mjs new file mode 100644 index 0000000000..b71775fa95 --- /dev/null +++ b/bench/crypto/hkdf.mjs @@ -0,0 +1,50 @@ +import crypto from "node:crypto"; +import { bench, run } from "../runner.mjs"; + +// Sample keys with different lengths +const keys = { + short: "secret", + long: "this-is-a-much-longer-secret-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", +}; + +// Test parameters +const salts = ["", "salt"]; +const infos = ["", "info"]; +const hashes = ["sha256", "sha512"]; +const sizes = [10, 1024]; + +// Benchmark sync HKDF +for (const hash of hashes) { + for (const keyName of Object.keys(keys)) { + const key = keys[keyName]; + for (const size of sizes) { + bench(`hkdfSync ${hash} ${keyName}-key ${size} bytes`, () => { + return crypto.hkdfSync(hash, key, "salt", "info", size); + }); + } + } +} + +// Benchmark different combinations of salt and info +for (const salt of salts) { + for (const info of infos) { + bench(`hkdfSync sha256 with ${salt ? "salt" : "no-salt"} and ${info ? "info" : "no-info"}`, () => { + return crypto.hkdfSync("sha256", "secret", salt, info, 64); + }); + } +} + +// Benchmark async HKDF (using promises for cleaner benchmark) +// Note: async benchmarks in Mitata require returning a Promise +for (const hash of hashes) { + bench(`hkdf ${hash} async`, async () => { + return new Promise((resolve, reject) => { + crypto.hkdf(hash, "secret", "salt", "info", 64, (err, derivedKey) => { + if (err) reject(err); + else resolve(derivedKey); + }); + }); + }); +} + +await run(); diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index cafa239b12..e8b448522e 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -2100,7 +2100,7 @@ pub const Formatter = struct { }, .String => { // This is called from the '%s' formatter, so it can actually be any value - const str: bun.String = try bun.String.fromJS2(value, this.globalThis); + const str: bun.String = try bun.String.fromJS(value, this.globalThis); defer str.deref(); this.addForNewLine(str.length()); @@ -2897,7 +2897,7 @@ pub const Formatter = struct { break :brk JSValue.undefined; }; - const event_type = switch (EventType.map.fromJS(this.globalThis, event_type_value) orelse .unknown) { + const event_type = switch (try EventType.map.fromJS(this.globalThis, event_type_value) orelse .unknown) { .MessageEvent, .ErrorEvent => |evt| evt, else => { return try this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors); diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 205fb6c3e8..d6c8f28b42 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -1371,16 +1371,14 @@ pub const Crypto = struct { this.salt.deinit(); } - pub fn fromJS(globalThis: *JSC.JSGlobalObject, arguments: []const JSC.JSValue, is_async: bool) bun.JSError!PBKDF2 { - if (arguments.len < 5) { - return globalThis.throwNotEnoughArguments("pbkdf2", 5, arguments.len); + pub fn fromJS(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame, is_async: bool) bun.JSError!PBKDF2 { + const arg0, const arg1, const arg2, const arg3, const arg4, const arg5 = callFrame.argumentsAsArray(6); + + if (!arg3.isNumber()) { + return globalThis.throwInvalidArgumentTypeValue("keylen", "number", arg3); } - if (!arguments[3].isNumber()) { - return globalThis.throwInvalidArgumentTypeValue("keylen", "number", arguments[3]); - } - - const keylen_num = arguments[3].asNumber(); + const keylen_num = arg3.asNumber(); if (std.math.isInf(keylen_num) or std.math.isNan(keylen_num)) { return globalThis.throwRangeError(keylen_num, .{ @@ -1399,11 +1397,11 @@ pub const Crypto = struct { return error.JSError; } - if (!arguments[2].isAnyInt()) { - return globalThis.throwInvalidArgumentTypeValue("iterations", "number", arguments[2]); + if (!arg2.isAnyInt()) { + return globalThis.throwInvalidArgumentTypeValue("iterations", "number", arg2); } - const iteration_count = arguments[2].coerce(i64, globalThis); + const iteration_count = arg2.coerce(i64, globalThis); if (!globalThis.hasException() and (iteration_count < 1 or iteration_count > std.math.maxInt(i32))) { return globalThis.throwRangeError(iteration_count, .{ .field_name = "iterations", .min = 1, .max = std.math.maxInt(i32) + 1 }); @@ -1414,19 +1412,19 @@ pub const Crypto = struct { } const algorithm = brk: { - if (!arguments[4].isString()) { - return globalThis.throwInvalidArgumentTypeValue("digest", "string", arguments[4]); + if (!arg4.isString()) { + return globalThis.throwInvalidArgumentTypeValue("digest", "string", arg4); } invalid: { - switch (EVP.Algorithm.map.fromJSCaseInsensitive(globalThis, arguments[4]) orelse break :invalid) { + switch (try EVP.Algorithm.map.fromJSCaseInsensitive(globalThis, arg4) orelse break :invalid) { .shake128, .shake256, .@"sha3-224", .@"sha3-256", .@"sha3-384", .@"sha3-512" => break :invalid, else => |alg| break :brk alg, } } if (!globalThis.hasException()) { - const slice = try arguments[4].toSlice(globalThis, bun.default_allocator); + const slice = try arg4.toSlice(globalThis, bun.default_allocator); defer slice.deinit(); const name = slice.slice(); return globalThis.ERR_CRYPTO_INVALID_DIGEST("Invalid digest: {s}", .{name}).throw(); @@ -1449,19 +1447,16 @@ pub const Crypto = struct { } const allow_string_object = true; - out.salt = JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, arguments[1], is_async, allow_string_object) orelse { - return globalThis.throwInvalidArgumentTypeValue("salt", "string or buffer", arguments[1]); + out.salt = try JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, arg1, is_async, allow_string_object) orelse { + return globalThis.throwInvalidArgumentTypeValue("salt", "string or buffer", arg1); }; if (out.salt.slice().len > std.math.maxInt(i32)) { return globalThis.throwInvalidArguments("salt is too long", .{}); } - out.password = JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, arguments[0], is_async, allow_string_object) orelse { - if (!globalThis.hasException()) { - return globalThis.throwInvalidArgumentTypeValue("password", "string or buffer", arguments[0]); - } - return error.JSError; + out.password = try JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, arg0, is_async, allow_string_object) orelse { + return globalThis.throwInvalidArgumentTypeValue("password", "string or buffer", arg0); }; if (out.password.slice().len > std.math.maxInt(i32)) { @@ -1469,8 +1464,8 @@ pub const Crypto = struct { } if (is_async) { - if (!arguments[5].isFunction()) { - return globalThis.throwInvalidArgumentTypeValue("callback", "function", arguments[5]); + if (!arg5.isFunction()) { + return globalThis.throwInvalidArgumentTypeValue("callback", "function", arg5); } } @@ -2089,12 +2084,12 @@ pub const Crypto = struct { algorithm = try PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]); } - const password_to_hash = JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[0], bun.default_allocator) catch { - if (!globalObject.hasException()) { - return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray"); - } - return error.JSError; - }; + // TODO: this most likely should error like `hashSync` instead of stringifying. + // + // fromJS(...) orelse { + // return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray"); + // } + const password_to_hash = try JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[0], bun.default_allocator); errdefer bun.default_allocator.free(password_to_hash); if (password_to_hash.len == 0) { @@ -2119,11 +2114,8 @@ pub const Crypto = struct { algorithm = try PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]); } - var string_or_buffer = JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse { - if (!globalObject.hasException()) { - return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray"); - } - return error.JSError; + var string_or_buffer = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse { + return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray"); }; defer string_or_buffer.deinit(); @@ -2250,15 +2242,21 @@ pub const Crypto = struct { }; } - const owned_password = JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[0], bun.default_allocator) catch { - if (!globalObject.hasException()) return globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray"); - return error.JSError; - }; + // TODO: this most likely should error like `verifySync` instead of stringifying. + // + // fromJS(...) orelse { + // return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray"); + // } + const owned_password = try JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[0], bun.default_allocator); - const owned_hash = JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[1], bun.default_allocator) catch { + // TODO: this most likely should error like `verifySync` instead of stringifying. + // + // fromJS(...) orelse { + // return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray"); + // } + const owned_hash = JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[1], bun.default_allocator) catch |err| { bun.default_allocator.free(owned_password); - if (!globalObject.hasException()) return globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray"); - return error.JSError; + return err; }; if (owned_hash.len == 0) { @@ -2300,19 +2298,13 @@ pub const Crypto = struct { }; } - var password = JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse { - if (!globalObject.hasException()) { - return globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray"); - } - return .zero; + var password = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse { + return globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray"); }; - var hash_ = JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse { + var hash_ = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse { password.deinit(); - if (!globalObject.hasException()) { - return globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray"); - } - return .zero; + return globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray"); }; defer password.deinit(); @@ -2588,7 +2580,7 @@ pub const Crypto = struct { } if (!hmac_value.isEmptyOrUndefinedOrNull()) { - hmac_key = JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, hmac_value) orelse { + hmac_key = try JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, hmac_value) orelse { return globalThis.throwInvalidArguments("key must be a string or buffer", .{}); }; } @@ -3098,7 +3090,7 @@ pub const Crypto = struct { } const thisValue = callframe.this(); const input = callframe.argument(0); - const buffer = JSC.Node.BlobOrStringOrBuffer.fromJS(globalThis, globalThis.bunVM().allocator, input) orelse { + const buffer = try JSC.Node.BlobOrStringOrBuffer.fromJS(globalThis, globalThis.bunVM().allocator, input) orelse { return globalThis.throwInvalidArguments("expected blob or string or buffer", .{}); }; defer buffer.deinit(); @@ -4538,7 +4530,7 @@ pub const JSZlib = struct { return globalThis.throwInvalidArguments("Expected options to be an object", .{}); } else null; - if (JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, buffer_value)) |buffer| { + if (try JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, buffer_value)) |buffer| { return .{ buffer, options_val }; } @@ -4601,7 +4593,7 @@ pub const JSZlib = struct { return globalThis.throwInvalidArguments("Expected library to be a string", .{}); } - library = Library.map.fromJS(globalThis, library_value) orelse { + library = try Library.map.fromJS(globalThis, library_value) orelse { return globalThis.throwInvalidArguments("Expected library to be one of 'zlib' or 'libdeflate'", .{}); }; } @@ -4713,7 +4705,7 @@ pub const JSZlib = struct { return globalThis.throwInvalidArguments("Expected library to be a string", .{}); } - library = Library.map.fromJS(globalThis, library_value) orelse { + library = try Library.map.fromJS(globalThis, library_value) orelse { return globalThis.throwInvalidArguments("Expected library to be one of 'zlib' or 'libdeflate'", .{}); }; } diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index 0294c47339..a076f6cda8 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -856,9 +856,17 @@ pub const JSBundler = struct { } } else { const loader: Api.Loader = @enumFromInt(loader_as_int.to(u8)); - const source_code = JSC.Node.StringOrBuffer.fromJSToOwnedSlice(this.bv2.plugins.?.globalObject(), source_code_value, bun.default_allocator) catch - // TODO: + const global = this.bv2.plugins.?.globalObject(); + const source_code = JSC.Node.StringOrBuffer.fromJSToOwnedSlice(global, source_code_value, bun.default_allocator) catch |err| { + switch (err) { + error.OutOfMemory => { + bun.outOfMemory(); + }, + error.JSError => {}, + } + @panic("Unexpected: source_code is not a string"); + }; this.value = .{ .success = .{ .loader = options.Loader.fromAPI(loader), diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index e792d33944..62fcdaaf4d 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -545,7 +545,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std transpiler.transform.source_map = .none; } } else { - if (options.SourceMapOption.Map.fromJS(globalObject, flag)) |source| { + if (try options.SourceMapOption.Map.fromJS(globalObject, flag)) |source| { transpiler.transform.source_map = source.toAPI(); } else { return globalObject.throwInvalidArguments("sourcemap must be one of \"inline\", \"linked\", \"external\", or \"none\"", .{}); @@ -688,7 +688,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std } if (try object.getTruthy(globalThis, "logLevel")) |logLevel| { - if (logger.Log.Level.Map.fromJS(globalObject, logLevel)) |level| { + if (try logger.Log.Level.Map.fromJS(globalObject, logLevel)) |level| { transpiler.log.level = level; } else { return globalObject.throwInvalidArguments("logLevel must be one of \"verbose\", \"debug\", \"info\", \"warn\", or \"error\"", .{}); @@ -834,7 +834,7 @@ pub fn scan(this: *JSTranspiler, globalThis: *JSC.JSGlobalObject, callframe: *JS return globalThis.throwInvalidArgumentType("scan", "code", "string or Uint8Array"); }; - const code_holder = JSC.Node.StringOrBuffer.fromJS(globalThis, args.arena.allocator(), code_arg) orelse { + const code_holder = try JSC.Node.StringOrBuffer.fromJS(globalThis, args.arena.allocator(), code_arg) orelse { return globalThis.throwInvalidArgumentType("scan", "code", "string or Uint8Array"); }; defer code_holder.deinit(); @@ -957,7 +957,7 @@ pub fn transformSync( var arena = Mimalloc.Arena.init() catch unreachable; defer arena.deinit(); - const code_holder = JSC.Node.StringOrBuffer.fromJS(globalThis, arena.allocator(), code_arg) orelse { + const code_holder = try JSC.Node.StringOrBuffer.fromJS(globalThis, arena.allocator(), code_arg) orelse { return globalThis.throwInvalidArgumentType("transformSync", "code", "string or Uint8Array"); }; defer code_holder.deinit(); @@ -1119,7 +1119,7 @@ pub fn scanImports(this: *JSTranspiler, globalThis: *JSC.JSGlobalObject, callfra return globalThis.throwInvalidArgumentType("scanImports", "code", "string or Uint8Array"); }; - const code_holder = JSC.Node.StringOrBuffer.fromJS(globalThis, args.arena.allocator(), code_arg) orelse { + const code_holder = try JSC.Node.StringOrBuffer.fromJS(globalThis, args.arena.allocator(), code_arg) orelse { if (!globalThis.hasException()) { return globalThis.throwInvalidArgumentType("scanImports", "code", "string or Uint8Array"); } diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig index 88f03afbd4..f184e8851c 100644 --- a/src/bun.js/api/bun/dns_resolver.zig +++ b/src/bun.js/api/bun/dns_resolver.zig @@ -2673,7 +2673,16 @@ pub const DNSResolver = struct { options = GetAddrInfo.Options.fromJS(optionsObject, globalThis) catch |err| { return switch (err) { error.InvalidFlags => globalThis.throwInvalidArgumentValue("flags", try optionsObject.getTruthy(globalThis, "flags") orelse .undefined), - else => globalThis.throw("Invalid options passed to lookup(): {s}", .{@errorName(err)}), + error.JSError => |exception| exception, + error.OutOfMemory => |oom| oom, + + // more information with these errors + error.InvalidOptions, + error.InvalidFamily, + error.InvalidSocketType, + error.InvalidProtocol, + error.InvalidBackend, + => globalThis.throw("Invalid options passed to lookup(): {s}", .{@errorName(err)}), }; }; } diff --git a/src/bun.js/api/bun/h2_frame_parser.zig b/src/bun.js/api/bun/h2_frame_parser.zig index 8dcbcfd764..1ef983c7d7 100644 --- a/src/bun.js/api/bun/h2_frame_parser.zig +++ b/src/bun.js/api/bun/h2_frame_parser.zig @@ -3420,9 +3420,8 @@ pub const H2FrameParser = struct { return globalObject.throwInvalidArgumentTypeValue("write", "encoding", encoding_arg); } - break :brk JSC.Node.Encoding.fromJS(encoding_arg, globalObject) orelse { - if (!globalObject.hasException()) return globalObject.throwInvalidArgumentTypeValue("write", "encoding", encoding_arg); - return error.JSError; + break :brk try JSC.Node.Encoding.fromJS(encoding_arg, globalObject) orelse { + return globalObject.throwInvalidArgumentTypeValue("write", "encoding", encoding_arg); }; }; @@ -3432,8 +3431,7 @@ pub const H2FrameParser = struct { data_arg, encoding, ) orelse { - if (!globalObject.hasException()) return globalObject.throwInvalidArgumentTypeValue("write", "Buffer or String", data_arg); - return error.JSError; + return globalObject.throwInvalidArgumentTypeValue("write", "Buffer or String", data_arg); }; defer buffer.deinit(); diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 83f66bdead..8d71cb5b12 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -2743,7 +2743,7 @@ fn NewSocket(comptime ssl: bool) type { var arena: bun.ArenaAllocator = bun.ArenaAllocator.init(bun.default_allocator); defer arena.deinit(); - if (JSC.Node.StringOrBuffer.fromJS(globalObject, arena.allocator(), session_arg)) |sb| { + if (try JSC.Node.StringOrBuffer.fromJS(globalObject, arena.allocator(), session_arg)) |sb| { defer sb.deinit(); const session_slice = sb.slice(); const ssl_ptr = this.socket.ssl(); @@ -2860,7 +2860,7 @@ fn NewSocket(comptime ssl: bool) type { var arena: bun.ArenaAllocator = bun.ArenaAllocator.init(bun.default_allocator); defer arena.deinit(); - if (JSC.Node.StringOrBuffer.fromJS(globalObject, arena.allocator(), context_arg)) |sb| { + if (try JSC.Node.StringOrBuffer.fromJS(globalObject, arena.allocator(), context_arg)) |sb| { defer sb.deinit(); const context_slice = sb.slice(); diff --git a/src/bun.js/api/bun/socket/SocketAddress.zig b/src/bun.js/api/bun/socket/SocketAddress.zig index 7854ec63b7..5de8d612f0 100644 --- a/src/bun.js/api/bun/socket/SocketAddress.zig +++ b/src/bun.js/api/bun/socket/SocketAddress.zig @@ -36,13 +36,13 @@ pub const Options = struct { const address_str: ?bun.String = if (try obj.get(global, "address")) |a| addr: { if (!a.isString()) return global.throwInvalidArgumentTypeValue("options.address", "string", a); - break :addr try bun.String.fromJS2(a, global); + break :addr try bun.String.fromJS(a, global); } else null; const _family: AF = if (try obj.get(global, "family")) |fam| blk: { // "ipv4" or "ipv6", ignoring case if (fam.isString()) { - const fam_str = try bun.String.fromJS2(fam, global); + const fam_str = try bun.String.fromJS(fam, global); defer fam_str.deref(); if (fam_str.length() != 4) return throwBadFamilyIP(global, fam); @@ -129,7 +129,7 @@ pub fn parse(global: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError const input = blk: { const input_arg = callframe.argument(0); if (!input_arg.isString()) return global.throwInvalidArgumentTypeValue("input", "string", input_arg); - break :blk try bun.String.fromJS2(input_arg, global); + break :blk try bun.String.fromJS(input_arg, global); }; defer input.deref(); @@ -197,7 +197,7 @@ pub fn constructor(global: *JSC.JSGlobalObject, frame: *JSC.CallFrame) bun.JSErr ._addr = sockaddr.@"127.0.0.1", ._presentation = .empty, // ._presentation = WellKnownAddress.@"127.0.0.1"(), - // ._presentation = bun.String.fromJS2(global.commonStrings().@"127.0.0.1"()) catch unreachable, + // ._presentation = bun.String.fromJS(global.commonStrings().@"127.0.0.1"()) catch unreachable, }); options_obj.ensureStillAlive(); diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index a95c12bf74..02ad860037 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -1976,11 +1976,8 @@ pub fn spawnMaybeSync( maybe_ipc_mode = ipc_mode: { if (try args.getTruthy(globalThis, "serialization")) |mode_val| { if (mode_val.isString()) { - break :ipc_mode IPC.Mode.fromJS(globalThis, mode_val) orelse { - if (!globalThis.hasException()) { - return globalThis.throwInvalidArguments("serialization must be \"json\" or \"advanced\"", .{}); - } - return error.JSError; + break :ipc_mode try IPC.Mode.fromJS(globalThis, mode_val) orelse { + return globalThis.throwInvalidArguments("serialization must be \"json\" or \"advanced\"", .{}); }; } else { if (!globalThis.hasException()) { diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index c7d09c586f..86222d1a56 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -834,7 +834,7 @@ pub const ServerConfig = struct { var sliced = try key_file_name.toSlice(global, bun.default_allocator); defer sliced.deinit(); if (sliced.len > 0) { - result.key_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable; + result.key_file_name = try bun.default_allocator.dupeZ(u8, sliced.slice()); if (std.posix.system.access(result.key_file_name, std.posix.F_OK) != 0) { return global.throwInvalidArguments("Unable to access keyFile path", .{}); } @@ -847,16 +847,16 @@ pub const ServerConfig = struct { if (js_obj.jsType().isArray()) { const count = js_obj.getLength(global); if (count > 0) { - const native_array = bun.default_allocator.alloc([*c]const u8, count) catch unreachable; + const native_array = try bun.default_allocator.alloc([*c]const u8, count); var valid_count: u32 = 0; for (0..count) |i| { const item = js_obj.getIndex(global, @intCast(i)); - if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| { + if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| { defer sb.deinit(); const sliced = sb.slice(); if (sliced.len > 0) { - native_array[valid_count] = bun.default_allocator.dupeZ(u8, sliced) catch unreachable; + native_array[valid_count] = try bun.default_allocator.dupeZ(u8, sliced); valid_count += 1; any = true; result.requires_custom_request_ctx = true; @@ -890,7 +890,7 @@ pub const ServerConfig = struct { } } else if (try BlobFileContentResult.init("key", js_obj, global)) |content| { if (content.data.len > 0) { - const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; + const native_array = try bun.default_allocator.alloc([*c]const u8, 1); native_array[0] = content.data.ptr; result.key = native_array; result.key_count = 1; @@ -901,12 +901,12 @@ pub const ServerConfig = struct { return null; } } else { - const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; - if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| { + const native_array = try bun.default_allocator.alloc([*c]const u8, 1); + if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| { defer sb.deinit(); const sliced = sb.slice(); if (sliced.len > 0) { - native_array[0] = bun.default_allocator.dupeZ(u8, sliced) catch unreachable; + native_array[0] = try bun.default_allocator.dupeZ(u8, sliced); any = true; result.requires_custom_request_ctx = true; result.key = native_array; @@ -926,7 +926,7 @@ pub const ServerConfig = struct { var sliced = try cert_file_name.toSlice(global, bun.default_allocator); defer sliced.deinit(); if (sliced.len > 0) { - result.cert_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable; + result.cert_file_name = try bun.default_allocator.dupeZ(u8, sliced.slice()); if (std.posix.system.access(result.cert_file_name, std.posix.F_OK) != 0) { return global.throwInvalidArguments("Unable to access certFile path", .{}); } @@ -936,11 +936,11 @@ pub const ServerConfig = struct { } if (try obj.getTruthy(global, "ALPNProtocols")) |protocols| { - if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), protocols)) |sb| { + if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), protocols)) |sb| { defer sb.deinit(); const sliced = sb.slice(); if (sliced.len > 0) { - result.protos = bun.default_allocator.dupeZ(u8, sliced) catch unreachable; + result.protos = try bun.default_allocator.dupeZ(u8, sliced); result.protos_len = sliced.len; } @@ -955,16 +955,16 @@ pub const ServerConfig = struct { if (js_obj.jsType().isArray()) { const count = js_obj.getLength(global); if (count > 0) { - const native_array = bun.default_allocator.alloc([*c]const u8, count) catch unreachable; + const native_array = try bun.default_allocator.alloc([*c]const u8, count); var valid_count: u32 = 0; for (0..count) |i| { const item = js_obj.getIndex(global, @intCast(i)); - if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| { + if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| { defer sb.deinit(); const sliced = sb.slice(); if (sliced.len > 0) { - native_array[valid_count] = bun.default_allocator.dupeZ(u8, sliced) catch unreachable; + native_array[valid_count] = try bun.default_allocator.dupeZ(u8, sliced); valid_count += 1; any = true; result.requires_custom_request_ctx = true; @@ -998,7 +998,7 @@ pub const ServerConfig = struct { } } else if (try BlobFileContentResult.init("cert", js_obj, global)) |content| { if (content.data.len > 0) { - const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; + const native_array = try bun.default_allocator.alloc([*c]const u8, 1); native_array[0] = content.data.ptr; result.cert = native_array; result.cert_count = 1; @@ -1009,12 +1009,12 @@ pub const ServerConfig = struct { return null; } } else { - const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; - if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| { + const native_array = try bun.default_allocator.alloc([*c]const u8, 1); + if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| { defer sb.deinit(); const sliced = sb.slice(); if (sliced.len > 0) { - native_array[0] = bun.default_allocator.dupeZ(u8, sliced) catch unreachable; + native_array[0] = try bun.default_allocator.dupeZ(u8, sliced); any = true; result.requires_custom_request_ctx = true; result.cert = native_array; @@ -1052,7 +1052,7 @@ pub const ServerConfig = struct { var sliced = try ssl_ciphers.toSlice(global, bun.default_allocator); defer sliced.deinit(); if (sliced.len > 0) { - result.ssl_ciphers = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable; + result.ssl_ciphers = try bun.default_allocator.dupeZ(u8, sliced.slice()); any = true; result.requires_custom_request_ctx = true; } @@ -1062,7 +1062,7 @@ pub const ServerConfig = struct { var sliced = try server_name.toSlice(global, bun.default_allocator); defer sliced.deinit(); if (sliced.len > 0) { - result.server_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable; + result.server_name = try bun.default_allocator.dupeZ(u8, sliced.slice()); any = true; result.requires_custom_request_ctx = true; } @@ -1072,12 +1072,12 @@ pub const ServerConfig = struct { if (js_obj.jsType().isArray()) { const count = js_obj.getLength(global); if (count > 0) { - const native_array = bun.default_allocator.alloc([*c]const u8, count) catch unreachable; + const native_array = try bun.default_allocator.alloc([*c]const u8, count); var valid_count: u32 = 0; for (0..count) |i| { const item = js_obj.getIndex(global, @intCast(i)); - if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| { + if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), item)) |sb| { defer sb.deinit(); const sliced = sb.slice(); if (sliced.len > 0) { @@ -1115,7 +1115,7 @@ pub const ServerConfig = struct { } } else if (try BlobFileContentResult.init("ca", js_obj, global)) |content| { if (content.data.len > 0) { - const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; + const native_array = try bun.default_allocator.alloc([*c]const u8, 1); native_array[0] = content.data.ptr; result.ca = native_array; result.ca_count = 1; @@ -1126,12 +1126,12 @@ pub const ServerConfig = struct { return null; } } else { - const native_array = bun.default_allocator.alloc([*c]const u8, 1) catch unreachable; - if (JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| { + const native_array = try bun.default_allocator.alloc([*c]const u8, 1); + if (try JSC.Node.StringOrBuffer.fromJS(global, arena.allocator(), js_obj)) |sb| { defer sb.deinit(); const sliced = sb.slice(); if (sliced.len > 0) { - native_array[0] = bun.default_allocator.dupeZ(u8, sliced) catch unreachable; + native_array[0] = try bun.default_allocator.dupeZ(u8, sliced); any = true; result.requires_custom_request_ctx = true; result.ca = native_array; @@ -1151,7 +1151,7 @@ pub const ServerConfig = struct { var sliced = try ca_file_name.toSlice(global, bun.default_allocator); defer sliced.deinit(); if (sliced.len > 0) { - result.ca_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable; + result.ca_file_name = try bun.default_allocator.dupeZ(u8, sliced.slice()); if (std.posix.system.access(result.ca_file_name, std.posix.F_OK) != 0) { return global.throwInvalidArguments("Invalid caFile path", .{}); } @@ -1181,7 +1181,7 @@ pub const ServerConfig = struct { var sliced = try dh_params_file_name.toSlice(global, bun.default_allocator); defer sliced.deinit(); if (sliced.len > 0) { - result.dh_params_file_name = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable; + result.dh_params_file_name = try bun.default_allocator.dupeZ(u8, sliced.slice()); if (std.posix.system.access(result.dh_params_file_name, std.posix.F_OK) != 0) { return global.throwInvalidArguments("Invalid dhParamsFile path", .{}); } @@ -1192,7 +1192,7 @@ pub const ServerConfig = struct { var sliced = try passphrase.toSlice(global, bun.default_allocator); defer sliced.deinit(); if (sliced.len > 0) { - result.passphrase = bun.default_allocator.dupeZ(u8, sliced.slice()) catch unreachable; + result.passphrase = try bun.default_allocator.dupeZ(u8, sliced.slice()); } } @@ -6941,12 +6941,12 @@ pub const NodeHTTPResponse = struct { return globalObject.throwInvalidArgumentTypeValue("encoding", "string", encoding_value); } - encoding = JSC.Node.Encoding.fromJS(encoding_value, globalObject) orelse { + encoding = try JSC.Node.Encoding.fromJS(encoding_value, globalObject) orelse { return globalObject.throwInvalidArguments("Invalid encoding", .{}); }; } - const result = JSC.Node.StringOrBuffer.fromJSWithEncoding(globalObject, bun.default_allocator, input_value, encoding) catch |err| return err; + const result = try JSC.Node.StringOrBuffer.fromJSWithEncoding(globalObject, bun.default_allocator, input_value, encoding); break :brk result orelse { return globalObject.throwInvalidArgumentTypeValue("input", "string or buffer", input_value); }; diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index 66b2889d3e..e69d9ae292 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -1062,7 +1062,7 @@ pub fn wrapInstanceMethod( iter.deinit(); return globalThis.throwInvalidArguments("expected string or buffer", .{}); }; - args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse { + args[i] = try JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse { iter.deinit(); return globalThis.throwInvalidArguments("expected string or buffer", .{}); }; @@ -1070,7 +1070,7 @@ pub fn wrapInstanceMethod( ?JSC.Node.StringOrBuffer => { if (iter.nextEat()) |arg| { if (!arg.isEmptyOrUndefinedOrNull()) { - args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse { + args[i] = try JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse { iter.deinit(); return globalThis.throwInvalidArguments("expected string or buffer", .{}); }; @@ -1204,14 +1204,14 @@ pub fn wrapStaticMethod( iter.deinit(); return globalThis.throwInvalidArguments("expected string or buffer", .{}); }; - args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse { + args[i] = try JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse { iter.deinit(); return globalThis.throwInvalidArguments("expected string or buffer", .{}); }; }, ?JSC.Node.StringOrBuffer => { if (iter.nextEat()) |arg| { - args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse brk: { + args[i] = try JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse brk: { if (arg == .undefined) { break :brk null; } @@ -1225,7 +1225,7 @@ pub fn wrapStaticMethod( }, JSC.Node.BlobOrStringOrBuffer => { if (iter.nextEat()) |arg| { - args[i] = JSC.Node.BlobOrStringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse { + args[i] = try JSC.Node.BlobOrStringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse { iter.deinit(); return globalThis.throwInvalidArguments("expected blob, string or buffer", .{}); }; diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 84169453f0..03d91d6387 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -1046,6 +1046,13 @@ JSC::EncodedJSValue CRYPTO_INVALID_KEYTYPE(JSC::ThrowScope& throwScope, JSC::JSG return {}; } +JSC::EncodedJSValue CRYPTO_INVALID_KEYLEN(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + auto message = "Invalid key length"_s; + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_KEYLEN, message)); + return {}; +} + JSC::EncodedJSValue CRYPTO_OPERATION_FAILED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral message) { throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, message)); diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index 44e097d955..ce090e748d 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -107,6 +107,7 @@ JSC::EncodedJSValue CRYPTO_TIMING_SAFE_EQUAL_LENGTH(JSC::ThrowScope&, JSC::JSGlo JSC::EncodedJSValue CRYPTO_UNKNOWN_DH_GROUP(JSC::ThrowScope&, JSC::JSGlobalObject*); JSC::EncodedJSValue CRYPTO_INVALID_KEYTYPE(JSC::ThrowScope&, JSC::JSGlobalObject*, WTF::ASCIILiteral message); JSC::EncodedJSValue CRYPTO_INVALID_KEYTYPE(JSC::ThrowScope&, JSC::JSGlobalObject*); +JSC::EncodedJSValue CRYPTO_INVALID_KEYLEN(JSC::ThrowScope&, JSC::JSGlobalObject*); // URL diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index c08bfb492a..b381618fbb 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -399,11 +399,13 @@ pub const JSValue = enum(i64) { return @as(JSValue, @enumFromInt(@as(i64, @bitCast(@intFromPtr(ptr))))); } + // TODO: use JSError! `toInt32` can throw extern fn JSC__JSValue__coerceToInt32(this: JSValue, globalThis: *JSC.JSGlobalObject) i32; pub fn coerceToInt32(this: JSValue, globalThis: *JSC.JSGlobalObject) i32 { return JSC__JSValue__coerceToInt32(this, globalThis); } + // TODO: use JSError! `toInt32` can throw extern fn JSC__JSValue__coerceToInt64(this: JSValue, globalThis: *JSC.JSGlobalObject) i64; pub fn coerceToInt64(this: JSValue, globalThis: *JSC.JSGlobalObject) i64 { return JSC__JSValue__coerceToInt64(this, globalThis); @@ -1473,7 +1475,7 @@ pub const JSValue = enum(i64) { /// Increments the reference count, you must call `.deref()` or it will leak memory. pub fn toBunString(this: JSValue, globalObject: *JSC.JSGlobalObject) JSError!bun.String { - return bun.String.fromJS2(this, globalObject); + return bun.String.fromJS(this, globalObject); } extern fn JSC__JSValue__toMatch(this: JSValue, global: *JSGlobalObject, other: JSValue) bool; @@ -1541,7 +1543,7 @@ pub const JSValue = enum(i64) { /// Convert a JSValue to a string, potentially calling `toString` on the /// JSValue in JavaScript. Can throw an error. pub fn toSlice(this: JSValue, global: *JSGlobalObject, allocator: std.mem.Allocator) JSError!ZigString.Slice { - const str = try bun.String.fromJS2(this, global); + const str = try bun.String.fromJS(this, global); defer str.deref(); // This keeps the WTF::StringImpl alive if it was originally a latin1 @@ -1573,14 +1575,14 @@ pub const JSValue = enum(i64) { /// Call `toString()` on the JSValue and clone the result. pub fn toSliceOrNull(this: JSValue, globalThis: *JSGlobalObject) bun.JSError!ZigString.Slice { - const str = try bun.String.fromJS2(this, globalThis); + const str = try bun.String.fromJS(this, globalThis); defer str.deref(); return str.toUTF8(bun.default_allocator); } /// Call `toString()` on the JSValue and clone the result. pub fn toSliceOrNullWithAllocator(this: JSValue, globalThis: *JSGlobalObject, allocator: std.mem.Allocator) bun.JSError!ZigString.Slice { - const str = try bun.String.fromJS2(this, globalThis); + const str = try bun.String.fromJS(this, globalThis); defer str.deref(); return str.toUTF8(allocator); } @@ -1597,9 +1599,9 @@ pub const JSValue = enum(i64) { /// On exception or out of memory, this returns null. /// /// Remember that `Symbol` throws an exception when you call `toString()`. - pub fn toSliceCloneZ(this: JSValue, globalThis: *JSGlobalObject) ?[:0]u8 { - var str = bun.String.tryFromJS(this, globalThis) orelse return null; - return str.toOwnedSliceZ(bun.default_allocator) catch return null; + pub fn toSliceCloneZ(this: JSValue, globalThis: *JSGlobalObject) JSError!?[:0]u8 { + var str = try bun.String.fromJS(this, globalThis); + return try str.toOwnedSliceZ(bun.default_allocator); } /// On exception or out of memory, this returns null, to make exception checks clearer. @@ -1952,7 +1954,7 @@ pub const JSValue = enum(i64) { return globalThis.throwInvalidArguments(property_name ++ " must be a string", .{}); } - return StringMap.fromJS(globalThis, this) orelse { + return try StringMap.fromJS(globalThis, this) orelse { const one_of = struct { pub const list = brk: { var str: []const u8 = "'"; @@ -1970,9 +1972,8 @@ pub const JSValue = enum(i64) { pub const label = property_name ++ " must be one of " ++ list; }.label; - if (!globalThis.hasException()) - return globalThis.throwInvalidArguments(one_of, .{}); - return error.JSError; + + return globalThis.throwInvalidArguments(one_of, .{}); }; } diff --git a/src/bun.js/bindings/NodeModuleModule.zig b/src/bun.js/bindings/NodeModuleModule.zig index 3d80dcef9e..b5f66759b1 100644 --- a/src/bun.js/bindings/NodeModuleModule.zig +++ b/src/bun.js/bindings/NodeModuleModule.zig @@ -27,9 +27,9 @@ pub export fn NodeModuleModule__findPath( const found = if (paths_maybe) |paths| found: { var iter = paths.iterator(global); while (iter.next()) |path| { - const cur_path = bun.String.tryFromJS(path, global) orelse { - if (global.hasException()) return .zero; - continue; + const cur_path = bun.String.fromJS(path, global) catch |err| switch (err) { + error.JSError => return .zero, + error.OutOfMemory => return global.throwOutOfMemoryValue(), }; defer cur_path.deref(); diff --git a/src/bun.js/bindings/ZigString.zig b/src/bun.js/bindings/ZigString.zig index 344545e88c..9b171b6b70 100644 --- a/src/bun.js/bindings/ZigString.zig +++ b/src/bun.js/bindings/ZigString.zig @@ -12,6 +12,7 @@ const JSGlobalObject = JSC.JSGlobalObject; const JSValue = JSC.JSValue; const JSString = @import("JSString.zig").JSString; const C_API = bun.JSC.C; +const JSError = bun.JSError; const Environment = bun.Environment; const Mimalloc = bun.Mimalloc; @@ -259,7 +260,7 @@ pub const ZigString = extern struct { return JSC.WebCore.Encoder.byteLengthU8(this.slice().ptr, this.slice().len, .utf8); } - pub fn toOwnedSlice(this: ZigString, allocator: std.mem.Allocator) ![]u8 { + pub fn toOwnedSlice(this: ZigString, allocator: std.mem.Allocator) OOM![]u8 { if (this.isUTF8()) return try allocator.dupeZ(u8, this.slice()); @@ -276,7 +277,7 @@ pub const ZigString = extern struct { return list.items; } - pub fn toOwnedSliceZ(this: ZigString, allocator: std.mem.Allocator) ![:0]u8 { + pub fn toOwnedSliceZ(this: ZigString, allocator: std.mem.Allocator) OOM![:0]u8 { if (this.isUTF8()) return allocator.dupeZ(u8, this.slice()); diff --git a/src/bun.js/bindings/ncrypto.cpp b/src/bun.js/bindings/ncrypto.cpp index a07591fe53..6ad25c8f1e 100644 --- a/src/bun.js/bindings/ncrypto.cpp +++ b/src/bun.js/bindings/ncrypto.cpp @@ -127,29 +127,79 @@ DataPointer DataPointer::Alloc(size_t len) #endif } +DataPointer DataPointer::SecureAlloc(size_t len) +{ +#ifndef OPENSSL_IS_BORINGSSL + auto ptr = OPENSSL_secure_zalloc(len); + if (ptr == nullptr) return {}; + return DataPointer(ptr, len, true); +#else + // BoringSSL does not implement the OPENSSL_secure_zalloc API. + auto ptr = OPENSSL_malloc(len); + if (ptr == nullptr) return {}; + memset(ptr, 0, len); + return DataPointer(ptr, len); +#endif +} + +size_t DataPointer::GetSecureHeapUsed() +{ +#ifndef OPENSSL_IS_BORINGSSL + return CRYPTO_secure_malloc_initialized() ? CRYPTO_secure_used() : 0; +#else + // BoringSSL does not have the secure heap and therefore + // will always return 0. + return 0; +#endif +} + +DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount, + size_t min) +{ +#ifndef OPENSSL_IS_BORINGSSL + switch (CRYPTO_secure_malloc_init(amount, min)) { + case 0: + return InitSecureHeapResult::FAILED; + case 2: + return InitSecureHeapResult::UNABLE_TO_MEMORY_MAP; + case 1: + return InitSecureHeapResult::OK; + default: + return InitSecureHeapResult::FAILED; + } +#else + // BoringSSL does not actually support the secure heap + return InitSecureHeapResult::FAILED; +#endif +} + DataPointer DataPointer::Copy(const Buffer& buffer) { return DataPointer(OPENSSL_memdup(buffer.data, buffer.len), buffer.len); } -DataPointer::DataPointer(void* data, size_t length) +DataPointer::DataPointer(void* data, size_t length, bool secure) : data_(data) , len_(length) + , secure_(secure) { } -DataPointer::DataPointer(const Buffer& buffer) +DataPointer::DataPointer(const Buffer& buffer, bool secure) : data_(buffer.data) , len_(buffer.len) + , secure_(secure) { } DataPointer::DataPointer(DataPointer&& other) noexcept : data_(other.data_) , len_(other.len_) + , secure_(other.secure_) { other.data_ = nullptr; other.len_ = 0; + other.secure_ = false; } DataPointer& DataPointer::operator=(DataPointer&& other) noexcept @@ -173,7 +223,11 @@ void DataPointer::zero() void DataPointer::reset(void* data, size_t length) { if (data_ != nullptr) { - OPENSSL_clear_free(data_, len_); + if (secure_) { + OPENSSL_secure_clear_free(data_, len_); + } else { + OPENSSL_clear_free(data_, len_); + } } data_ = data; len_ = length; diff --git a/src/bun.js/bindings/ncrypto.h b/src/bun.js/bindings/ncrypto.h index c5d9d3f16b..0251a40a24 100644 --- a/src/bun.js/bindings/ncrypto.h +++ b/src/bun.js/bindings/ncrypto.h @@ -243,6 +243,11 @@ template struct Buffer { T* data = nullptr; size_t len = 0; + + static Buffer from(std::span input) + { + return { input.data(), input.size() }; + } }; class Digest final { @@ -487,9 +492,31 @@ public: static DataPointer Alloc(size_t len); static DataPointer Copy(const Buffer& buffer); + // Attempts to allocate the buffer space using the secure heap, if + // supported/enabled. If the secure heap is disabled, then this + // ends up being equivalent to Alloc(len). Note that allocation + // will fail if there is not enough free space remaining in the + // secure heap space. + static DataPointer SecureAlloc(size_t len); + + // If the secure heap is enabled, returns the amount of data that + // has been allocated from the heap. + static size_t GetSecureHeapUsed(); + + enum class InitSecureHeapResult { + FAILED, + UNABLE_TO_MEMORY_MAP, + OK, + }; + + // Attempt to initialize the secure heap. The secure heap is not + // supported on all operating systems and whenever boringssl is + // used. + static InitSecureHeapResult TryInitSecureHeap(size_t amount, size_t min); + DataPointer() = default; - explicit DataPointer(void* data, size_t len); - explicit DataPointer(const Buffer& buffer); + explicit DataPointer(void* data, size_t len, bool secure = false); + explicit DataPointer(const Buffer& buffer, bool secure = false); DataPointer(DataPointer&& other) noexcept; DataPointer& operator=(DataPointer&& other) noexcept; NCRYPTO_DISALLOW_COPY(DataPointer) @@ -528,9 +555,12 @@ public: }; } + bool isSecure() const { return secure_; } + private: void* data_ = nullptr; size_t len_ = 0; + bool secure_ = false; }; class BIOPointer final { diff --git a/src/bun.js/bindings/node/crypto/CryptoHkdf.cpp b/src/bun.js/bindings/node/crypto/CryptoHkdf.cpp new file mode 100644 index 0000000000..f4613bd5b4 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoHkdf.cpp @@ -0,0 +1,318 @@ +#include "CryptoHkdf.h" +#include "NodeValidator.h" +#include "CryptoUtil.h" +#include "KeyObject.h" +#include "JSCryptoKey.h" +#include "CryptoKey.h" +#include "AsymmetricKeyValue.h" +#include "JSBuffer.h" +#include "ErrorCode.h" +#include "BunString.h" + +using namespace JSC; +using namespace Bun; +using namespace WebCore; +using namespace ncrypto; + +HkdfJobCtx::HkdfJobCtx(Digest digest, size_t length, WTF::Vector&& key, WTF::Vector&& info, WTF::Vector&& salt) + : m_digest(digest) + , m_length(length) + , m_key(WTFMove(key)) + , m_info(WTFMove(info)) + , m_salt(WTFMove(salt)) +{ +} + +HkdfJobCtx::HkdfJobCtx(HkdfJobCtx&& other) + : m_digest(other.m_digest) + , m_length(other.m_length) + , m_key(WTFMove(other.m_key)) + , m_info(WTFMove(other.m_info)) + , m_salt(WTFMove(other.m_salt)) + , m_result(WTFMove(other.m_result)) +{ +} + +HkdfJobCtx::~HkdfJobCtx() +{ +} + +extern "C" void Bun__HkdfJobCtx__runTask(HkdfJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) +{ + ctx->runTask(lexicalGlobalObject); +} +void HkdfJobCtx::runTask(JSGlobalObject* lexicalGlobalObject) +{ + auto keyBuf = ncrypto::Buffer { + .data = m_key.data(), + .len = m_key.size(), + }; + auto infoBuf = ncrypto::Buffer { + .data = m_info.data(), + .len = m_info.size(), + }; + auto saltBuf = ncrypto::Buffer { + .data = m_salt.data(), + .len = m_salt.size(), + }; + auto dp = ncrypto::hkdf(m_digest, keyBuf, infoBuf, saltBuf, m_length); + + if (!dp) { + // indicate an error with m_result == std::nullopt + return; + } + + m_result = ByteSource::allocated(dp.release()); +} + +extern "C" void Bun__HkdfJobCtx__runFromJS(HkdfJobCtx* ctx, JSGlobalObject* lexicalGlobalObject, EncodedJSValue callback) +{ + ctx->runFromJS(lexicalGlobalObject, JSValue::decode(callback)); +} +void HkdfJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!m_result) { + JSObject* err = createError(lexicalGlobalObject, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "hkdf operation failed"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + auto& result = m_result.value(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + RefPtr buf = ArrayBuffer::tryCreateUninitialized(result.size(), 1); + if (!buf) { + JSObject* err = createOutOfMemoryError(lexicalGlobalObject); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + memcpy(buf->data(), result.data(), result.size()); + + Bun__EventLoop__runCallback2(lexicalGlobalObject, + JSValue::encode(callback), + JSValue::encode(jsUndefined()), + JSValue::encode(jsUndefined()), + JSValue::encode(JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(), buf.releaseNonNull()))); +} + +extern "C" void Bun__HkdfJobCtx__deinit(HkdfJobCtx* ctx) +{ + ctx->deinit(); +} +void HkdfJobCtx::deinit() +{ + delete this; +} + +extern "C" HkdfJob* Bun__HkdfJob__create(JSGlobalObject* globalObject, HkdfJobCtx* ctx, EncodedJSValue callback); +HkdfJob* HkdfJob::create(JSGlobalObject* globalObject, HkdfJobCtx&& ctx, JSValue callback) +{ + HkdfJobCtx* ctxCopy = new HkdfJobCtx(WTFMove(ctx)); + return Bun__HkdfJob__create(globalObject, ctxCopy, JSValue::encode(callback)); +} + +extern "C" void Bun__HkdfJob__schedule(HkdfJob* job); +void HkdfJob::schedule() +{ + Bun__HkdfJob__schedule(this); +} + +extern "C" void Bun__HkdfJob__createAndSchedule(JSGlobalObject* globalObject, HkdfJobCtx* ctx, EncodedJSValue callback); +void HkdfJob::createAndSchedule(JSGlobalObject* globalObject, HkdfJobCtx&& ctx, JSValue callback) +{ + HkdfJobCtx* ctxCopy = new HkdfJobCtx(WTFMove(ctx)); + return Bun__HkdfJob__createAndSchedule(globalObject, ctxCopy, JSValue::encode(callback)); +} + +// similar to prepareSecretKey +void prepareKey(JSGlobalObject* globalObject, ThrowScope& scope, Vector& out, JSValue key) +{ + VM& vm = globalObject->vm(); + + // Handle KeyObject (if not bufferOnly) + if (key.isObject()) { + JSObject* obj = key.getObject(); + auto& names = WebCore::builtinNames(vm); + + // Check for BunNativePtr on the object + if (auto val = obj->getIfPropertyExists(globalObject, names.bunNativePtrPrivateName())) { + if (auto* cryptoKey = jsDynamicCast(val.asCell())) { + + JSValue typeValue = obj->get(globalObject, vm.propertyNames->type); + RETURN_IF_EXCEPTION(scope, ); + + auto wrappedKey = cryptoKey->protectedWrapped(); + + if (!typeValue.isString()) { + Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); + return; + } + + WTF::String typeString = typeValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, ); + + if (wrappedKey->type() != CryptoKeyType::Secret || typeString != "secret"_s) { + Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); + return; + } + + auto keyData = getSymmetricKey(wrappedKey); + + if (UNLIKELY(!keyData)) { + Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); + return; + } + + out.append(keyData.value()); + return; + } + } + } + + // Handle string or buffer + if (key.isString()) { + JSString* keyString = key.toString(globalObject); + RETURN_IF_EXCEPTION(scope, ); + + auto keyView = keyString->view(globalObject); + RETURN_IF_EXCEPTION(scope, ); + + JSValue buffer = JSValue::decode(WebCore::constructFromEncoding(globalObject, keyView, WebCore::BufferEncodingType::utf8)); + auto* view = jsDynamicCast(buffer); + out.append(view->span()); + return; + } + + // Handle ArrayBuffer types + if (auto* view = jsDynamicCast(key)) { + out.append(view->span()); + return; + } + + if (auto* buf = jsDynamicCast(key)) { + out.append(buf->impl()->span()); + return; + } + + // If we got here, the key is not a valid type + WTF::String expectedTypes + = "string, SecretKeyObject, ArrayBuffer, TypedArray, DataView, or Buffer"_s; + Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "ikm"_s, expectedTypes, key); +} + +void copyBufferOrString(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, JSValue value, const WTF::ASCIILiteral& name, WTF::Vector& buffer) +{ + if (value.isString()) { + JSString* str = value.toString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, ); + GCOwnedDataScope view = str->view(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, ); + UTF8View utf8(view); + buffer.append(utf8.span()); + } else if (auto* view = jsDynamicCast(value)) { + buffer.append(view->span()); + } else if (auto* buf = jsDynamicCast(value)) { + buffer.append(buf->impl()->span()); + } else { + ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, name, "string, ArrayBuffer, TypedArray, Buffer"_s, value); + } +} + +std::optional HkdfJobCtx::fromJS(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame, ThrowScope& scope, Mode mode) +{ + JSValue hashValue = callFrame->argument(0); + JSValue keyValue = callFrame->argument(1); + JSValue saltValue = callFrame->argument(2); + JSValue infoValue = callFrame->argument(3); + JSValue lengthValue = callFrame->argument(4); + + V::validateString(scope, lexicalGlobalObject, hashValue, "digest"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + // TODO(dylan-conway): All of these don't need to copy for sync mode + + WTF::Vector keyData; + prepareKey(lexicalGlobalObject, scope, keyData, keyValue); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + WTF::Vector salt; + copyBufferOrString(lexicalGlobalObject, scope, saltValue, "salt"_s, salt); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + WTF::Vector info; + copyBufferOrString(lexicalGlobalObject, scope, infoValue, "info"_s, info); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + int32_t length = 0; + V::validateInteger(scope, lexicalGlobalObject, lengthValue, "length"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength), &length); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + if (info.size() > 1024) { + ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, "info"_s, "must not contain more than 1024 bytes"_s, jsNumber(info.size())); + return std::nullopt; + } + + WTF::String hashString = hashValue.toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + Digest hash = Digest::FromName(hashString); + if (!hash) { + ERR::CRYPTO_INVALID_DIGEST(scope, lexicalGlobalObject, hashString); + return std::nullopt; + } + + if (!ncrypto::checkHkdfLength(hash, length)) { + ERR::CRYPTO_INVALID_KEYLEN(scope, lexicalGlobalObject); + return std::nullopt; + } + + return HkdfJobCtx(hash, length, WTFMove(keyData), WTFMove(info), WTFMove(salt)); +} + +JSC_DEFINE_HOST_FUNCTION(jsHkdf, (JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + std::optional ctx = HkdfJobCtx::fromJS(lexicalGlobalObject, callFrame, scope, HkdfJobCtx::Mode::Async); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + JSValue callback = callFrame->argument(5); + V::validateFunction(scope, lexicalGlobalObject, callback, "callback"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + HkdfJob::createAndSchedule(lexicalGlobalObject, WTFMove(ctx.value()), callback); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsHkdfSync, (JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + std::optional ctx = HkdfJobCtx::fromJS(lexicalGlobalObject, callFrame, scope, HkdfJobCtx::Mode::Sync); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + ctx->runTask(lexicalGlobalObject); + + if (!ctx->m_result.has_value()) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "hkdf operation failed"_s); + } + + auto& result = ctx->m_result.value(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + RefPtr buf = JSC::ArrayBuffer::tryCreateUninitialized(result.size(), 1); + if (!buf) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return JSValue::encode({}); + } + + memcpy(buf->data(), result.data(), result.size()); + + return JSValue::encode(JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(), buf.releaseNonNull())); +} diff --git a/src/bun.js/bindings/node/crypto/CryptoHkdf.h b/src/bun.js/bindings/node/crypto/CryptoHkdf.h new file mode 100644 index 0000000000..6c40d6c959 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoHkdf.h @@ -0,0 +1,47 @@ +#pragma once + +#include "root.h" +#include "helpers.h" +#include "ncrypto.h" +#include "CryptoUtil.h" + +using namespace JSC; +using namespace Bun; +using namespace ncrypto; + +JSC_DECLARE_HOST_FUNCTION(jsHkdf); +JSC_DECLARE_HOST_FUNCTION(jsHkdfSync); + +struct HkdfJobCtx { + + enum class Mode { + Sync, + Async, + }; + + HkdfJobCtx(Digest digest, size_t length, WTF::Vector&& key, WTF::Vector&& info, WTF::Vector&& salt); + HkdfJobCtx(HkdfJobCtx&&); + ~HkdfJobCtx(); + + static std::optional fromJS(JSGlobalObject*, CallFrame*, ThrowScope&, Mode); + + void runTask(JSGlobalObject*); + void runFromJS(JSGlobalObject*, JSValue callback); + void deinit(); + + ncrypto::Digest m_digest; + size_t m_length; + WTF::Vector m_key; + WTF::Vector m_info; + WTF::Vector m_salt; + + std::optional m_result; + + WTF_MAKE_TZONE_ALLOCATED(HkdfJobCtx); +}; + +struct HkdfJob { + static HkdfJob* create(JSGlobalObject*, HkdfJobCtx&&, JSValue callback); + static void createAndSchedule(JSGlobalObject*, HkdfJobCtx&&, JSValue callback); + void schedule(); +}; diff --git a/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp b/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp index 72fadb6cae..32d3fee060 100644 --- a/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp +++ b/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp @@ -5,17 +5,14 @@ #include "CryptoUtil.h" #include "NodeValidator.h" -CheckPrimeJobCtx::CheckPrimeJobCtx(ncrypto::BignumPointer candidate, int32_t checks, JSValue callback) +CheckPrimeJobCtx::CheckPrimeJobCtx(ncrypto::BignumPointer candidate, int32_t checks) : m_candidate(WTFMove(candidate)) , m_checks(checks) - , m_callback(callback) { - gcProtect(m_callback); } CheckPrimeJobCtx::~CheckPrimeJobCtx() { - gcUnprotect(m_callback); } extern "C" void Bun__CheckPrimeJobCtx__runTask(CheckPrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) @@ -34,13 +31,13 @@ void CheckPrimeJobCtx::runTask(JSGlobalObject* lexicalGlobalObject) m_result = res != 0; } -extern "C" void Bun__CheckPrimeJobCtx__runFromJS(CheckPrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) +extern "C" void Bun__CheckPrimeJobCtx__runFromJS(CheckPrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject, EncodedJSValue callback) { - ctx->runFromJS(lexicalGlobalObject); + ctx->runFromJS(lexicalGlobalObject, JSValue::decode(callback)); } -void CheckPrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject) +void CheckPrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback) { - Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(jsBoolean(m_result))); + Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(jsBoolean(m_result))); } extern "C" void Bun__CheckPrimeJobCtx__deinit(CheckPrimeJobCtx* ctx) @@ -52,11 +49,11 @@ void CheckPrimeJobCtx::deinit() delete this; } -extern "C" CheckPrimeJob* Bun__CheckPrimeJob__create(JSGlobalObject*, CheckPrimeJobCtx*); +extern "C" CheckPrimeJob* Bun__CheckPrimeJob__create(JSGlobalObject*, CheckPrimeJobCtx*, EncodedJSValue callback); CheckPrimeJob* CheckPrimeJob::create(JSGlobalObject* globalObject, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback) { - CheckPrimeJobCtx* ctx = new CheckPrimeJobCtx(WTFMove(candidate), checks, callback); - return Bun__CheckPrimeJob__create(globalObject, ctx); + CheckPrimeJobCtx* ctx = new CheckPrimeJobCtx(WTFMove(candidate), checks); + return Bun__CheckPrimeJob__create(globalObject, ctx, JSValue::encode(callback)); } extern "C" void Bun__CheckPrimeJob__schedule(CheckPrimeJob*); @@ -65,11 +62,11 @@ void CheckPrimeJob::schedule() Bun__CheckPrimeJob__schedule(this); } -extern "C" void Bun__CheckPrimeJob__createAndSchedule(JSGlobalObject*, CheckPrimeJobCtx*); +extern "C" void Bun__CheckPrimeJob__createAndSchedule(JSGlobalObject*, CheckPrimeJobCtx*, EncodedJSValue callback); void CheckPrimeJob::createAndSchedule(JSGlobalObject* globalObject, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback) { - CheckPrimeJobCtx* ctx = new CheckPrimeJobCtx(WTFMove(candidate), checks, callback); - return Bun__CheckPrimeJob__createAndSchedule(globalObject, ctx); + CheckPrimeJobCtx* ctx = new CheckPrimeJobCtx(WTFMove(candidate), checks); + return Bun__CheckPrimeJob__createAndSchedule(globalObject, ctx, JSValue::encode(callback)); } JSC_DEFINE_HOST_FUNCTION(jsCheckPrimeSync, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) @@ -94,9 +91,9 @@ JSC_DEFINE_HOST_FUNCTION(jsCheckPrimeSync, (JSC::JSGlobalObject * lexicalGlobalO } int32_t checks = 0; - if (optionsValue.isObject()) { - JSObject* options = optionsValue.getObject(); - JSValue checksValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "checks"_s)); + if (auto* optionsObj = optionsValue.getObject()) { + auto clientData = WebCore::clientData(vm); + JSValue checksValue = optionsObj->get(lexicalGlobalObject, clientData->builtinNames().checksPublicName()); RETURN_IF_EXCEPTION(scope, {}); if (!checksValue.isUndefined()) { @@ -174,21 +171,18 @@ JSC_DEFINE_HOST_FUNCTION(jsCheckPrime, (JSC::JSGlobalObject * lexicalGlobalObjec return JSValue::encode(jsUndefined()); } -GeneratePrimeJobCtx::GeneratePrimeJobCtx(int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback) +GeneratePrimeJobCtx::GeneratePrimeJobCtx(int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint) : m_size(size) , m_safe(safe) , m_bigint(bigint) , m_add(WTFMove(add)) , m_rem(WTFMove(rem)) , m_prime(WTFMove(prime)) - , m_callback(callback) { - gcProtect(m_callback); } GeneratePrimeJobCtx::~GeneratePrimeJobCtx() { - gcUnprotect(m_callback); } extern "C" void Bun__GeneratePrimeJobCtx__runTask(GeneratePrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) @@ -205,11 +199,11 @@ void GeneratePrimeJobCtx::runTask(JSGlobalObject* lexicalGlobalObject) }); } -extern "C" void Bun__GeneratePrimeJobCtx__runFromJS(GeneratePrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) +extern "C" void Bun__GeneratePrimeJobCtx__runFromJS(GeneratePrimeJobCtx* ctx, JSGlobalObject* lexicalGlobalObject, EncodedJSValue callback) { - ctx->runFromJS(lexicalGlobalObject); + ctx->runFromJS(lexicalGlobalObject, JSValue::decode(callback)); } -void GeneratePrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject) +void GeneratePrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback) { auto& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -217,19 +211,19 @@ void GeneratePrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject) if (m_bigint) { ncrypto::DataPointer primeHex = m_prime.toHex(); if (!primeHex) { - JSObject* err = createOutOfMemoryError(lexicalGlobalObject, "could not generate prime"_s); - Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + JSObject* err = createOutOfMemoryError(lexicalGlobalObject); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); return; } JSValue result = JSBigInt::parseInt(lexicalGlobalObject, vm, primeHex.span(), 16, JSBigInt::ErrorParseMode::IgnoreExceptions, JSBigInt::ParseIntSign::Unsigned); if (result.isEmpty()) { JSObject* err = createError(lexicalGlobalObject, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "could not generate prime"_s); - Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); return; } - Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(result)); + Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(result)); return; } @@ -237,14 +231,14 @@ void GeneratePrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject) JSC::JSUint8Array* result = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), m_prime.byteLength()); if (!result) { - JSObject* err = createOutOfMemoryError(lexicalGlobalObject, "could not generate prime"_s); - Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + JSObject* err = createOutOfMemoryError(lexicalGlobalObject); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); return; } ncrypto::BignumPointer::EncodePaddedInto(m_prime.get(), reinterpret_cast(result->vector()), result->byteLength()); - Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(m_callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(result)); + Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(result)); } extern "C" void Bun__GeneratePrimeJobCtx__deinit(GeneratePrimeJobCtx* ctx) @@ -256,11 +250,11 @@ void GeneratePrimeJobCtx::deinit() delete this; } -extern "C" GeneratePrimeJob* Bun__GeneratePrimeJob__create(JSGlobalObject*, GeneratePrimeJobCtx*); +extern "C" GeneratePrimeJob* Bun__GeneratePrimeJob__create(JSGlobalObject*, GeneratePrimeJobCtx*, EncodedJSValue callback); GeneratePrimeJob* GeneratePrimeJob::create(JSGlobalObject* globalObject, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback) { - GeneratePrimeJobCtx* ctx = new GeneratePrimeJobCtx(size, safe, WTFMove(prime), WTFMove(add), WTFMove(rem), bigint, callback); - return Bun__GeneratePrimeJob__create(globalObject, ctx); + GeneratePrimeJobCtx* ctx = new GeneratePrimeJobCtx(size, safe, WTFMove(prime), WTFMove(add), WTFMove(rem), bigint); + return Bun__GeneratePrimeJob__create(globalObject, ctx, JSValue::encode(callback)); } extern "C" void Bun__GeneratePrimeJob__schedule(GeneratePrimeJob*); @@ -269,11 +263,11 @@ void GeneratePrimeJob::schedule() Bun__GeneratePrimeJob__schedule(this); } -extern "C" void Bun__GeneratePrimeJob__createAndSchedule(JSGlobalObject*, GeneratePrimeJobCtx*); +extern "C" void Bun__GeneratePrimeJob__createAndSchedule(JSGlobalObject*, GeneratePrimeJobCtx*, EncodedJSValue callback); void GeneratePrimeJob::createAndSchedule(JSGlobalObject* globalObject, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback) { - GeneratePrimeJobCtx* ctx = new GeneratePrimeJobCtx(size, safe, WTFMove(prime), WTFMove(add), WTFMove(rem), bigint, callback); - Bun__GeneratePrimeJob__createAndSchedule(globalObject, ctx); + GeneratePrimeJobCtx* ctx = new GeneratePrimeJobCtx(size, safe, WTFMove(prime), WTFMove(add), WTFMove(rem), bigint); + Bun__GeneratePrimeJob__createAndSchedule(globalObject, ctx, JSValue::encode(callback)); } JSC_DEFINE_HOST_FUNCTION(jsGeneratePrime, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) diff --git a/src/bun.js/bindings/node/crypto/CryptoPrimes.h b/src/bun.js/bindings/node/crypto/CryptoPrimes.h index 8f910cfc00..4a82d28008 100644 --- a/src/bun.js/bindings/node/crypto/CryptoPrimes.h +++ b/src/bun.js/bindings/node/crypto/CryptoPrimes.h @@ -8,15 +8,14 @@ using namespace JSC; using namespace Bun; struct CheckPrimeJobCtx { - CheckPrimeJobCtx(ncrypto::BignumPointer candidate, int32_t checks, JSValue callback); + CheckPrimeJobCtx(ncrypto::BignumPointer candidate, int32_t checks); ~CheckPrimeJobCtx(); void runTask(JSGlobalObject* lexicalGlobalObject); - void runFromJS(JSGlobalObject* lexicalGlobalObject); + void runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback); void deinit(); int32_t m_checks; - JSValue m_callback; ncrypto::BignumPointer m_candidate; bool m_result { false }; @@ -24,10 +23,6 @@ struct CheckPrimeJobCtx { WTF_MAKE_TZONE_ALLOCATED(CheckPrimeJobCtx); }; -extern "C" void Bun__CheckPrimeJobCtx__runTask(CheckPrimeJobCtx*, JSGlobalObject*); -extern "C" void Bun__CheckPrimeJobCtx__runFromJS(CheckPrimeJobCtx*, JSGlobalObject*); -extern "C" void Bun__CheckPrimeJobCtx__deinit(CheckPrimeJobCtx*); - // Opaque struct created zig land struct CheckPrimeJob { static CheckPrimeJob* create(JSGlobalObject*, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback); @@ -37,17 +32,16 @@ struct CheckPrimeJob { }; struct GeneratePrimeJobCtx { - GeneratePrimeJobCtx(int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback); + GeneratePrimeJobCtx(int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint); ~GeneratePrimeJobCtx(); void runTask(JSGlobalObject* lexicalGlobalObject); - void runFromJS(JSGlobalObject* lexicalGlobalObject); + void runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback); void deinit(); int32_t m_size; bool m_safe; bool m_bigint; - JSValue m_callback; ncrypto::BignumPointer m_add; ncrypto::BignumPointer m_rem; ncrypto::BignumPointer m_prime; @@ -55,10 +49,6 @@ struct GeneratePrimeJobCtx { WTF_MAKE_TZONE_ALLOCATED(GeneratePrimeJobCtx); }; -extern "C" void Bun__GeneratePrimeJobCtx__runTask(GeneratePrimeJobCtx*, JSGlobalObject*); -extern "C" void Bun__GeneratePrimeJobCtx__runFromJS(GeneratePrimeJobCtx*, JSGlobalObject*); -extern "C" void Bun__GeneratePrimeJobCtx__deinit(GeneratePrimeJobCtx*); - // Opaque struct created zig land struct GeneratePrimeJob { static GeneratePrimeJob* create(JSGlobalObject*, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback); diff --git a/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp b/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp index f97deccfe0..d762a34a48 100644 --- a/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp +++ b/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp @@ -45,6 +45,7 @@ #include "JSHmac.h" #include "JSHash.h" #include "CryptoPrimes.h" +#include "CryptoHkdf.h" using namespace JSC; using namespace Bun; @@ -424,6 +425,11 @@ JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject) obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "checkPrimeSync"_s)), JSFunction::create(vm, globalObject, 2, "checkPrimeSync"_s, jsCheckPrimeSync, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "hkdf"_s)), + JSFunction::create(vm, globalObject, 6, "hkdf"_s, jsHkdf, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "hkdfSync"_s)), + JSFunction::create(vm, globalObject, 5, "hkdfSync"_s, jsHkdfSync, ImplementationVisibility::Public, NoIntrinsic), 0); + return obj; } diff --git a/src/bun.js/node/node_crypto_binding.zig b/src/bun.js/node/node_crypto_binding.zig index 296edf8d3f..38378aaf49 100644 --- a/src/bun.js/node/node_crypto_binding.zig +++ b/src/bun.js/node/node_crypto_binding.zig @@ -16,21 +16,26 @@ const JSGlobalObject = JSC.JSGlobalObject; const JSError = bun.JSError; const String = bun.String; const UUID = bun.UUID; +const Async = bun.Async; -fn ExternCryptoJob( - comptime name: []const u8, - comptime externRunTask: fn (*anyopaque, *JSGlobalObject) callconv(.c) void, - comptime externRunFromJS: fn (*anyopaque, *JSGlobalObject) callconv(.c) void, - comptime externDeinit: fn (*anyopaque) callconv(.c) void, -) type { +fn ExternCryptoJob(comptime name: []const u8) type { return struct { vm: *JSC.VirtualMachine, task: JSC.WorkPoolTask, any_task: JSC.AnyTask, + poll: Async.KeepAlive = .{}, - ctx: *anyopaque, + ctx: *Ctx, + callback: JSValue, - pub fn create(global: *JSGlobalObject, ctx: *anyopaque) callconv(.c) *@This() { + const Ctx = opaque { + const ctx_name = name ++ "Ctx"; + pub const runTask = @extern(*const fn (*Ctx, *JSGlobalObject) callconv(.c) void, .{ .name = "Bun__" ++ ctx_name ++ "__runTask" }).*; + pub const runFromJS = @extern(*const fn (*Ctx, *JSGlobalObject, JSValue) callconv(.c) void, .{ .name = "Bun__" ++ ctx_name ++ "__runFromJS" }).*; + pub const deinit = @extern(*const fn (*Ctx) callconv(.c) void, .{ .name = "Bun__" ++ ctx_name ++ "__deinit" }).*; + }; + + pub fn create(global: *JSGlobalObject, ctx: *Ctx, callback: JSValue) callconv(.c) *@This() { const vm = global.bunVM(); const job = bun.new(@This(), .{ .vm = vm, @@ -39,13 +44,15 @@ fn ExternCryptoJob( }, .any_task = undefined, .ctx = ctx, + .callback = callback, }); job.any_task = JSC.AnyTask.New(@This(), &runFromJS).init(job); + job.callback.protect(); return job; } - pub fn createAndSchedule(global: *JSGlobalObject, ctx: *anyopaque) callconv(.c) void { - var job = create(global, ctx); + pub fn createAndSchedule(global: *JSGlobalObject, ctx: *Ctx, callback: JSValue) callconv(.c) void { + var job = create(global, ctx, callback); job.schedule(); } @@ -54,7 +61,7 @@ fn ExternCryptoJob( var vm = job.vm; defer vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(job.any_task.task())); - externRunTask(job.ctx, vm.global); + job.ctx.runTask(vm.global); } pub fn runFromJS(this: *@This()) void { @@ -65,15 +72,18 @@ fn ExternCryptoJob( return; } - externRunFromJS(this.ctx, vm.global); + this.ctx.runFromJS(vm.global, this.callback); } fn deinit(this: *@This()) void { - externDeinit(this.ctx); + this.ctx.deinit(); + this.poll.unref(this.vm); + this.callback.unprotect(); bun.destroy(this); } pub fn schedule(this: *@This()) callconv(.c) void { + this.poll.ref(this.vm); JSC.WorkPool.schedule(&this.task); } @@ -85,31 +95,15 @@ fn ExternCryptoJob( }; } -extern fn Bun__CheckPrimeJobCtx__runTask(ctx: *anyopaque, global: *JSGlobalObject) void; -extern fn Bun__CheckPrimeJobCtx__runFromJS(ctx: *anyopaque, global: *JSGlobalObject) void; -extern fn Bun__CheckPrimeJobCtx__deinit(ctx: *anyopaque) void; - -const CheckPrimeJob = ExternCryptoJob( - "CheckPrimeJob", - Bun__CheckPrimeJobCtx__runTask, - Bun__CheckPrimeJobCtx__runFromJS, - Bun__CheckPrimeJobCtx__deinit, -); - -extern fn Bun__GeneratePrimeJobCtx__runTask(ctx: *anyopaque, global: *JSGlobalObject) void; -extern fn Bun__GeneratePrimeJobCtx__runFromJS(ctx: *anyopaque, global: *JSGlobalObject) void; -extern fn Bun__GeneratePrimeJobCtx__deinit(ctx: *anyopaque) void; - -const GeneratePrimeJob = ExternCryptoJob( - "GeneratePrimeJob", - Bun__GeneratePrimeJobCtx__runTask, - Bun__GeneratePrimeJobCtx__runFromJS, - Bun__GeneratePrimeJobCtx__deinit, -); +// Definitions for job structs created from c++ +pub const CheckPrimeJob = ExternCryptoJob("CheckPrimeJob"); +pub const GeneratePrimeJob = ExternCryptoJob("GeneratePrimeJob"); +pub const HkdfJob = ExternCryptoJob("HkdfJob"); comptime { _ = CheckPrimeJob; _ = GeneratePrimeJob; + _ = HkdfJob; } const random = struct { @@ -371,19 +365,15 @@ const random = struct { } }; -fn pbkdf2(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { - const arguments = callframe.arguments_old(6); - - const data = try PBKDF2.fromJS(globalThis, arguments.slice(), true); +fn pbkdf2(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue { + const data = try PBKDF2.fromJS(globalThis, callFrame, true); const job = PBKDF2.Job.create(JSC.VirtualMachine.get(), globalThis, &data); return job.promise.value(); } -fn pbkdf2Sync(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { - const arguments = callframe.arguments_old(5); - - var data = try PBKDF2.fromJS(globalThis, arguments.slice(), false); +fn pbkdf2Sync(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue { + var data = try PBKDF2.fromJS(globalThis, callFrame, false); defer data.deinit(); var out_arraybuffer = JSC.JSValue.createBufferFromLength(globalThis, @intCast(data.length)); if (out_arraybuffer == .zero or globalThis.hasException()) { @@ -425,6 +415,45 @@ pub fn timingSafeEqual(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSErr return JSC.jsBoolean(BoringSSL.CRYPTO_memcmp(l.ptr, r.ptr, l.len) == 0); } +pub fn secureHeapUsed(_: *JSGlobalObject, _: *JSC.CallFrame) JSError!JSValue { + return .undefined; +} + +pub fn getFips(_: *JSGlobalObject, _: *JSC.CallFrame) JSError!JSValue { + return JSValue.jsNumber(0); +} + +pub fn setFips(_: *JSGlobalObject, _: *JSC.CallFrame) JSError!JSValue { + return .undefined; +} + +pub fn setEngine(global: *JSGlobalObject, _: *JSC.CallFrame) JSError!JSValue { + return global.ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED("Custom engines not supported by BoringSSL", .{}).throw(); +} + +fn forEachHash(_: *const BoringSSL.EVP_MD, maybe_from: ?[*:0]const u8, _: ?[*:0]const u8, ctx: *anyopaque) callconv(.c) void { + const from = maybe_from orelse return; + const hashes: *bun.CaseInsensitiveASCIIStringArrayHashMap(void) = @alignCast(@ptrCast(ctx)); + hashes.put(bun.span(from), {}) catch bun.outOfMemory(); +} + +fn getHashes(global: *JSGlobalObject, _: *JSC.CallFrame) JSError!JSValue { + var hashes: bun.CaseInsensitiveASCIIStringArrayHashMap(void) = .init(bun.default_allocator); + defer hashes.deinit(); + + // TODO(dylan-conway): cache the names + BoringSSL.EVP_MD_do_all_sorted(&forEachHash, @alignCast(@ptrCast(&hashes))); + + const array = JSValue.createEmptyArray(global, hashes.count()); + + for (hashes.keys(), 0..) |hash, i| { + const str = String.createUTF8ForJS(global, hash); + array.putIndex(global, @intCast(i), str); + } + + return array; +} + pub fn createNodeCryptoBindingZig(global: *JSC.JSGlobalObject) JSC.JSValue { const crypto = JSC.JSValue.createEmptyObject(global, 8); @@ -437,5 +466,12 @@ pub fn createNodeCryptoBindingZig(global: *JSC.JSGlobalObject) JSC.JSValue { crypto.put(global, String.init("randomBytes"), JSC.JSFunction.create(global, "randomBytes", random.randomBytes, 2, .{})); crypto.put(global, String.init("timingSafeEqual"), JSC.JSFunction.create(global, "timingSafeEqual", timingSafeEqual, 2, .{})); + crypto.put(global, String.init("secureHeapUsed"), JSC.JSFunction.create(global, "secureHeapUsed", secureHeapUsed, 0, .{})); + crypto.put(global, String.init("getFips"), JSC.JSFunction.create(global, "getFips", getFips, 0, .{})); + crypto.put(global, String.init("setFips"), JSC.JSFunction.create(global, "setFips", setFips, 1, .{})); + crypto.put(global, String.init("setEngine"), JSC.JSFunction.create(global, "setEngine", setEngine, 2, .{})); + + crypto.put(global, String.init("getHashes"), JSC.JSFunction.create(global, "getHashes", getHashes, 0, .{})); + return crypto; } diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index a895f931bd..06bdb667f0 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -2485,7 +2485,7 @@ pub const Arguments = struct { }; const buffer_value = arguments.next(); - const buffer = StringOrBuffer.fromJS(ctx, bun.default_allocator, buffer_value orelse { + const buffer = try StringOrBuffer.fromJS(ctx, bun.default_allocator, buffer_value orelse { return ctx.throwInvalidArguments("data is required", .{}); }) orelse { return ctx.throwInvalidArgumentTypeValue("buffer", "string or TypedArray", buffer_value.?); diff --git a/src/bun.js/node/node_util_binding.zig b/src/bun.js/node/node_util_binding.zig index 1cd5e9464a..821eb28ed7 100644 --- a/src/bun.js/node/node_util_binding.zig +++ b/src/bun.js/node/node_util_binding.zig @@ -203,7 +203,7 @@ pub fn SplitNewlineIterator(comptime T: type) type { pub fn normalizeEncoding(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { const input = callframe.argument(0); - const str = bun.String.fromJS(input, globalThis); + const str = try bun.String.fromJS(input, globalThis); bun.assert(str.tag != .Dead); defer str.deref(); if (str.length() == 0) return JSC.Node.Encoding.utf8.toJS(globalThis); diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 6f75988b59..2a98022a65 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -22,7 +22,7 @@ const Syscall = bun.sys; const URL = @import("../../url.zig").URL; const Value = std.json.Value; pub const validators = @import("./util/validators.zig"); - +const JSError = bun.JSError; pub const Path = @import("./path.zig"); fn typeBaseNameT(comptime T: type) []const u8 { @@ -382,14 +382,14 @@ pub const BlobOrStringOrBuffer = union(enum) { } } - pub fn fromJS(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue) ?BlobOrStringOrBuffer { + pub fn fromJS(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue) JSError!?BlobOrStringOrBuffer { if (value.as(JSC.WebCore.Blob)) |blob| { if (blob.store) |store| { store.ref(); } return .{ .blob = blob.* }; } - return .{ .string_or_buffer = StringOrBuffer.fromJS(global, allocator, value) orelse return null }; + return .{ .string_or_buffer = try StringOrBuffer.fromJS(global, allocator, value) orelse return null }; } pub fn fromJSWithEncodingValue(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, encoding_value: JSC.JSValue) bun.JSError!?BlobOrStringOrBuffer { @@ -481,7 +481,7 @@ pub const StringOrBuffer = union(enum) { return try allocator.dupe(u8, array_buffer.byteSlice()); } - const str = try bun.String.fromJS2(value, globalObject); + const str = try bun.String.fromJS(value, globalObject); defer str.deref(); const result = try str.toOwnedSlice(allocator); @@ -544,7 +544,7 @@ pub const StringOrBuffer = union(enum) { } } - pub fn fromJSMaybeAsync(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, is_async: bool, allow_string_object: bool) ?StringOrBuffer { + pub fn fromJSMaybeAsync(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, is_async: bool, allow_string_object: bool) JSError!?StringOrBuffer { return switch (value.jsType()) { .String, .StringObject, @@ -553,7 +553,7 @@ pub const StringOrBuffer = union(enum) { if (!allow_string_object and str_type != .String) { return null; } - const str = bun.String.fromJS(value, global); + const str = try bun.String.fromJS(value, global); if (is_async) { defer str.deref(); @@ -590,7 +590,7 @@ pub const StringOrBuffer = union(enum) { }; } - pub fn fromJS(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue) ?StringOrBuffer { + pub fn fromJS(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue) JSError!?StringOrBuffer { return fromJSMaybeAsync(global, allocator, value, false, true); } @@ -608,7 +608,7 @@ pub const StringOrBuffer = union(enum) { } if (value.isString()) { - var str = try bun.String.fromJS2(value, global); + var str = try bun.String.fromJS(value, global); defer str.deref(); if (str.isEmpty()) { return fromJSMaybeAsync(global, allocator, value, is_async, allow_string_object); @@ -627,7 +627,7 @@ pub const StringOrBuffer = union(enum) { const encoding: Encoding = brk: { if (!encoding_value.isCell()) break :brk .utf8; - break :brk Encoding.fromJS(encoding_value, global) orelse .utf8; + break :brk try Encoding.fromJS(encoding_value, global) orelse .utf8; }; return fromJSWithEncoding(global, allocator, value, encoding); @@ -637,7 +637,7 @@ pub const StringOrBuffer = union(enum) { const encoding: Encoding = brk: { if (!encoding_value.isCell()) break :brk .utf8; - break :brk Encoding.fromJS(encoding_value, global) orelse .utf8; + break :brk try Encoding.fromJS(encoding_value, global) orelse .utf8; }; return fromJSWithEncodingMaybeAsync(global, allocator, value, encoding, maybe_async, allow_string_object); } @@ -687,7 +687,7 @@ pub const Encoding = enum(u8) { }; } - pub fn fromJS(value: JSC.JSValue, global: *JSC.JSGlobalObject) ?Encoding { + pub fn fromJS(value: JSC.JSValue, global: *JSC.JSGlobalObject) JSError!?Encoding { return map.fromJSCaseInsensitive(global, value); } @@ -709,7 +709,7 @@ pub const Encoding = enum(u8) { } pub fn fromJSWithDefaultOnEmpty(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject, default: Encoding) bun.JSError!?Encoding { - const str = try bun.String.fromJS2(value, globalObject); + const str = try bun.String.fromJS(value, globalObject); defer str.deref(); if (str.isEmpty()) { return default; diff --git a/src/bun.js/node/util/parse_args.zig b/src/bun.js/node/util/parse_args.zig index e78817f261..b7e103b7f6 100644 --- a/src/bun.js/node/util/parse_args.zig +++ b/src/bun.js/node/util/parse_args.zig @@ -9,13 +9,6 @@ const JSGlobalObject = JSC.JSGlobalObject; const ZigString = JSC.ZigString; const validators = @import("./validators.zig"); -const validateArray = validators.validateArray; -const validateBoolean = validators.validateBoolean; -const validateBooleanArray = validators.validateBooleanArray; -const validateObject = validators.validateObject; -const validateString = validators.validateString; -const validateStringArray = validators.validateStringArray; -const validateStringEnum = validators.validateStringEnum; const utils = @import("./parse_args_utils.zig"); const OptionValueType = utils.OptionValueType; @@ -298,7 +291,7 @@ fn storeOption(globalThis: *JSGlobalObject, option_name: ValueRef, option_value: } fn parseOptionDefinitions(globalThis: *JSGlobalObject, options_obj: JSValue, option_definitions: *std.ArrayList(OptionDefinition)) bun.JSError!void { - try validateObject(globalThis, options_obj, "options", .{}, .{}); + try validators.validateObject(globalThis, options_obj, "options", .{}, .{}); var iter = try JSC.JSPropertyIterator(.{ .skip_empty_name = false, @@ -312,14 +305,14 @@ fn parseOptionDefinitions(globalThis: *JSGlobalObject, options_obj: JSValue, opt }; const obj: JSValue = iter.value; - try validateObject(globalThis, obj, "options.{s}", .{option.long_name}, .{}); + try validators.validateObject(globalThis, obj, "options.{s}", .{option.long_name}, .{}); // type field is required const option_type = obj.getOwn(globalThis, "type") orelse JSValue.undefined; - option.type = try validateStringEnum(OptionValueType, globalThis, option_type, "options.{s}.type", .{option.long_name}); + option.type = try validators.validateStringEnum(OptionValueType, globalThis, option_type, "options.{s}.type", .{option.long_name}); if (obj.getOwn(globalThis, "short")) |short_option| { - try validateString(globalThis, short_option, "options.{s}.short", .{option.long_name}); + try validators.validateString(globalThis, short_option, "options.{s}.short", .{option.long_name}); var short_option_str = try short_option.toBunString(globalThis); if (short_option_str.length() != 1) { const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, "options.{s}.short must be a single character", .{option.long_name}, globalThis); @@ -330,7 +323,7 @@ fn parseOptionDefinitions(globalThis: *JSGlobalObject, options_obj: JSValue, opt if (obj.getOwn(globalThis, "multiple")) |multiple_value| { if (!multiple_value.isUndefined()) { - option.multiple = try validateBoolean(globalThis, multiple_value, "options.{s}.multiple", .{option.long_name}); + option.multiple = try validators.validateBoolean(globalThis, multiple_value, "options.{s}.multiple", .{option.long_name}); } } @@ -339,16 +332,16 @@ fn parseOptionDefinitions(globalThis: *JSGlobalObject, options_obj: JSValue, opt switch (option.type) { .string => { if (option.multiple) { - _ = try validateStringArray(globalThis, default_value, "options.{s}.default", .{option.long_name}); + _ = try validators.validateStringArray(globalThis, default_value, "options.{s}.default", .{option.long_name}); } else { - try validateString(globalThis, default_value, "options.{s}.default", .{option.long_name}); + try validators.validateString(globalThis, default_value, "options.{s}.default", .{option.long_name}); } }, .boolean => { if (option.multiple) { - _ = try validateBooleanArray(globalThis, default_value, "options.{s}.default", .{option.long_name}); + _ = try validators.validateBooleanArray(globalThis, default_value, "options.{s}.default", .{option.long_name}); } else { - _ = try validateBoolean(globalThis, default_value, "options.{s}.default", .{option.long_name}); + _ = try validators.validateBoolean(globalThis, default_value, "options.{s}.default", .{option.long_name}); } }, } @@ -662,14 +655,14 @@ pub fn parseArgsImpl(globalThis: *JSGlobalObject, config_obj: JSValue) bun.JSErr const config = if (config_obj.isUndefinedOrNull()) null else config_obj; if (config) |c| { - try validateObject(globalThis, c, "config", .{}, .{}); + try validators.validateObject(globalThis, c, "config", .{}, .{}); } // Phase 0.A: Get and validate type of input args var args: ArgsSlice = undefined; const config_args_or_null: ?JSValue = if (config) |c| c.getOwn(globalThis, "args") else null; if (config_args_or_null) |config_args| { - try validateArray(globalThis, config_args, "args", .{}, null); + try validators.validateArray(globalThis, config_args, "args", .{}, null); args = .{ .array = config_args, .start = 0, @@ -686,14 +679,14 @@ pub fn parseArgsImpl(globalThis: *JSGlobalObject, config_obj: JSValue) bun.JSErr const config_return_tokens: JSValue = (if (config) |c| c.getOwn(globalThis, "tokens") else null) orelse JSValue.jsBoolean(false); const config_options_obj: ?JSValue = if (config) |c| c.getOwn(globalThis, "options") else null; - const strict = try validateBoolean(globalThis, config_strict, "strict", .{}); + const strict = try validators.validateBoolean(globalThis, config_strict, "strict", .{}); var allow_positionals = !strict; if (config_allow_positionals) |config_allow_positionals_value| { - allow_positionals = try validateBoolean(globalThis, config_allow_positionals_value, "allowPositionals", .{}); + allow_positionals = try validators.validateBoolean(globalThis, config_allow_positionals_value, "allowPositionals", .{}); } - const return_tokens = try validateBoolean(globalThis, config_return_tokens, "tokens", .{}); + const return_tokens = try validators.validateBoolean(globalThis, config_return_tokens, "tokens", .{}); // Phase 0.C: Parse the options definitions diff --git a/src/bun.js/node/util/validators.zig b/src/bun.js/node/util/validators.zig index 4e717ee9ed..4c4027786b 100644 --- a/src/bun.js/node/util/validators.zig +++ b/src/bun.js/node/util/validators.zig @@ -5,6 +5,7 @@ const JSC = bun.JSC; const JSValue = JSC.JSValue; const JSGlobalObject = JSC.JSGlobalObject; const ZigString = JSC.ZigString; +const JSError = bun.JSError; pub fn getTypeName(globalObject: *JSGlobalObject, value: JSValue) ZigString { var js_type = value.jsType(); diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index ed88720abf..f4ac1ff1cc 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -4583,10 +4583,8 @@ pub const Expect = struct { if (total_count >= return_count and times_value.isCell()) { if (try times_value.get(globalThis, "type")) |type_string| { if (type_string.isString()) { - break :brk ReturnStatus.Map.fromJS(globalThis, type_string) orelse { - if (!globalThis.hasException()) - return globalThis.throw("Expected value must be a mock function with returns: {}", .{value}); - return .zero; + break :brk try ReturnStatus.Map.fromJS(globalThis, type_string) orelse { + return globalThis.throw("Expected value must be a mock function with returns: {}", .{value}); }; } } @@ -4857,7 +4855,7 @@ pub const Expect = struct { assert(message.isCallable()); // checked above const message_result = try message.callWithGlobalThis(globalThis, &.{}); - message_text = try bun.String.fromJS2(message_result, globalThis); + message_text = try bun.String.fromJS(message_result, globalThis); } const matcher_params = CustomMatcherParamsFormatter{ diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig index a7975549dd..9810e5f88b 100644 --- a/src/bun.js/test/pretty_format.zig +++ b/src/bun.js/test/pretty_format.zig @@ -1431,7 +1431,7 @@ pub const JestPrettyFormat = struct { break :brk JSValue.undefined; }; - const event_type = switch (EventType.map.fromJS(this.globalThis, event_type_value) orelse .unknown) { + const event_type = switch (try EventType.map.fromJS(this.globalThis, event_type_value) orelse .unknown) { .MessageEvent, .ErrorEvent => |evt| evt, else => { return try this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors); diff --git a/src/bun.js/webcore.zig b/src/bun.js/webcore.zig index e8ac98ef6a..7259af59cb 100644 --- a/src/bun.js/webcore.zig +++ b/src/bun.js/webcore.zig @@ -636,7 +636,7 @@ pub const Crypto = struct { if (arguments[0] != .undefined) { if (arguments[0].isString()) { encoding_value = arguments[0]; - break :brk JSC.Node.Encoding.fromJS(encoding_value, globalThis) orelse { + break :brk try JSC.Node.Encoding.fromJS(encoding_value, globalThis) orelse { return globalThis.ERR_UNKNOWN_ENCODING("Encoding must be one of base64, base64url, hex, or buffer", .{}).throw(); }; } diff --git a/src/bun.js/webcore/S3File.zig b/src/bun.js/webcore/S3File.zig index a132f61b3a..4cb3e266d6 100644 --- a/src/bun.js/webcore/S3File.zig +++ b/src/bun.js/webcore/S3File.zig @@ -492,7 +492,7 @@ pub fn getPresignUrlFrom(this: *Blob, globalThis: *JSC.JSGlobalObject, extra_opt if (extra_options) |options| { if (options.isObject()) { if (try options.getTruthyComptime(globalThis, "method")) |method_| { - method = Method.fromJS(globalThis, method_) orelse { + method = try Method.fromJS(globalThis, method_) orelse { return globalThis.throwInvalidArguments("method must be GET, PUT, DELETE or HEAD when using s3 protocol", .{}); }; } diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 05122f1a28..c199000e32 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -20,6 +20,7 @@ const FeatureFlags = bun.FeatureFlags; const ArrayBuffer = @import("../base.zig").ArrayBuffer; const Properties = @import("../base.zig").Properties; const getAllocator = @import("../base.zig").getAllocator; +const JSError = bun.JSError; const Environment = @import("../../env.zig"); const ZigString = JSC.ZigString; @@ -1663,12 +1664,7 @@ pub const Blob = struct { return globalThis.throwInvalidArguments("new File(bits, name) expects at least 2 arguments", .{}); } { - const name_value_str = bun.String.tryFromJS(args[1], globalThis) orelse { - if (!globalThis.hasException()) { - return globalThis.throwInvalidArguments("new File(bits, name) expects string as the second argument", .{}); - } - return error.JSError; - }; + const name_value_str = try bun.String.fromJS(args[1], globalThis); defer name_value_str.deref(); blob = get(globalThis, args[0], false, true) catch |err| switch (err) { @@ -4704,6 +4700,8 @@ pub const Blob = struct { jsThis: JSC.JSValue, globalThis: *JSC.JSGlobalObject, value: JSValue, + + // TODO: support JSError for getters/setters ) bool { // by default we don't have a name so lets allow it to be set undefined if (value.isEmptyOrUndefinedOrNull()) { @@ -4715,8 +4713,13 @@ pub const Blob = struct { if (value.isString()) { const old_name = this.name; - this.name = bun.String.tryFromJS(value, globalThis) orelse { - // Handle allocation failure. + this.name = bun.String.fromJS(value, globalThis) catch |err| { + switch (err) { + error.JSError => {}, + error.OutOfMemory => { + globalThis.throwOutOfMemory() catch {}; + }, + } this.name = bun.String.empty; return false; }; diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig index 2a6d4b3077..0119bc5d1f 100644 --- a/src/bun.js/webcore/request.zig +++ b/src/bun.js/webcore/request.zig @@ -581,7 +581,7 @@ pub const Request = struct { url_or_object.as(JSC.DOMURL) != null; if (is_first_argument_a_url) { - const str = try bun.String.fromJS2(arguments[0], globalThis); + const str = try bun.String.fromJS(arguments[0], globalThis); req.url = str; if (!req.url.isEmpty()) @@ -683,7 +683,7 @@ pub const Request = struct { if (!fields.contains(.url)) { if (value.fastGet(globalThis, .url)) |url| { - req.url = bun.String.fromJS(url, globalThis); + req.url = try bun.String.fromJS(url, globalThis); if (!req.url.isEmpty()) fields.insert(.url); @@ -691,7 +691,7 @@ pub const Request = struct { } else if (@intFromEnum(value) == @intFromEnum(values_to_try[values_to_try.len - 1]) and !is_first_argument_a_url and value.implementsToString(globalThis)) { - const str = bun.String.tryFromJS(value, globalThis) orelse return error.JSError; + const str = try bun.String.fromJS(value, globalThis); req.url = str; if (!req.url.isEmpty()) fields.insert(.url); diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 49c44c04f7..aa2aba418c 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -730,23 +730,15 @@ pub const Response = struct { } if (response_init.fastGet(globalThis, .statusText)) |status_text| { - result.status_text = bun.String.fromJS(status_text, globalThis); - } - - if (globalThis.hasException()) { - return error.JSError; + result.status_text = try bun.String.fromJS(status_text, globalThis); } if (response_init.fastGet(globalThis, .method)) |method_value| { - if (Method.fromJS(globalThis, method_value)) |method| { + if (try Method.fromJS(globalThis, method_value)) |method| { result.method = method; } } - if (globalThis.hasException()) { - return error.JSError; - } - return result; } @@ -2299,7 +2291,7 @@ pub const Fetch = struct { const StringOrURL = struct { pub fn fromJS(value: JSC.JSValue, globalThis: *JSC.JSGlobalObject) bun.JSError!?bun.String { if (value.isString()) { - return try bun.String.fromJS2(value, globalThis); + return try bun.String.fromJS(value, globalThis); } const out = try JSC.URL.hrefFromJS(value, globalThis); @@ -2464,7 +2456,7 @@ pub const Fetch = struct { if (request_init_object) |request_init| { if (request_init.fastGet(globalThis, .url)) |url_| { if (!url_.isUndefined()) { - break :extract_url try bun.String.fromJS2(url_, globalThis); + break :extract_url try bun.String.fromJS(url_, globalThis); } } } @@ -2532,12 +2524,7 @@ pub const Fetch = struct { method = extract_method: { if (options_object) |options| { if (try options.getTruthyComptime(globalThis, "method")) |method_| { - break :extract_method Method.fromJS(globalThis, method_); - } - - if (globalThis.hasException()) { - is_error = true; - return .zero; + break :extract_method try Method.fromJS(globalThis, method_); } } @@ -2547,23 +2534,13 @@ pub const Fetch = struct { if (request_init_object) |req| { if (try req.getTruthyComptime(globalThis, "method")) |method_| { - break :extract_method Method.fromJS(globalThis, method_); - } - - if (globalThis.hasException()) { - is_error = true; - return .zero; + break :extract_method try Method.fromJS(globalThis, method_); } } break :extract_method null; } orelse .GET; - if (globalThis.hasException()) { - is_error = true; - return .zero; - } - // "decompress: boolean" disable_decompression = extract_disable_decompression: { const objects_to_try = [_]JSValue{ diff --git a/src/comptime_string_map.zig b/src/comptime_string_map.zig index f4998a3285..0093549ed8 100644 --- a/src/comptime_string_map.zig +++ b/src/comptime_string_map.zig @@ -197,28 +197,28 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co } /// Caller must ensure that the input is a string. - pub fn fromJS(globalThis: *JSC.JSGlobalObject, input: JSC.JSValue) ?V { + pub fn fromJS(globalThis: *JSC.JSGlobalObject, input: JSC.JSValue) bun.JSError!?V { if (comptime bun.Environment.allow_assert) { if (!input.isString()) { @panic("ComptimeStringMap.fromJS: input is not a string"); } } - const str = bun.String.fromJS(input, globalThis); + const str = try bun.String.fromJS(input, globalThis); bun.assert(str.tag != .Dead); defer str.deref(); return getWithEql(str, bun.String.eqlComptime); } /// Caller must ensure that the input is a string. - pub fn fromJSCaseInsensitive(globalThis: *JSC.JSGlobalObject, input: JSC.JSValue) ?V { + pub fn fromJSCaseInsensitive(globalThis: *JSC.JSGlobalObject, input: JSC.JSValue) bun.JSError!?V { if (comptime bun.Environment.allow_assert) { if (!input.isString()) { @panic("ComptimeStringMap.fromJS: input is not a string"); } } - const str = bun.String.fromJS(input, globalThis); + const str = try bun.String.fromJS(input, globalThis); bun.assert(str.tag != .Dead); defer str.deref(); return str.inMapCaseInsensitive(@This()); diff --git a/src/csrf.zig b/src/csrf.zig index 7c0046a0c9..e4e256648a 100644 --- a/src/csrf.zig +++ b/src/csrf.zig @@ -258,7 +258,7 @@ pub fn csrf__generate_impl(globalObject: *JSC.JSGlobalObject, callframe: *JSC.Ca if (!algorithm_js.isString()) { return globalObject.throwInvalidArgumentTypeValue("algorithm", "string", algorithm_js); } - algorithm = JSC.API.Bun.Crypto.EVP.Algorithm.map.fromJSCaseInsensitive(globalObject, algorithm_js) orelse { + algorithm = try JSC.API.Bun.Crypto.EVP.Algorithm.map.fromJSCaseInsensitive(globalObject, algorithm_js) orelse { return globalObject.throwInvalidArguments("Algorithm not supported", .{}); }; switch (algorithm) { @@ -353,7 +353,7 @@ pub fn csrf__verify_impl(globalObject: *JSC.JSGlobalObject, call_frame: *JSC.Cal if (!algorithm_js.isString()) { return globalObject.throwInvalidArgumentTypeValue("algorithm", "string", algorithm_js); } - algorithm = JSC.API.Bun.Crypto.EVP.Algorithm.map.fromJSCaseInsensitive(globalObject, algorithm_js) orelse { + algorithm = try JSC.API.Bun.Crypto.EVP.Algorithm.map.fromJSCaseInsensitive(globalObject, algorithm_js) orelse { return globalObject.throwInvalidArguments("Algorithm not supported", .{}); }; switch (algorithm) { diff --git a/src/deps/boringssl.translated.zig b/src/deps/boringssl.translated.zig index dd88fbfd98..d22bb26aba 100644 --- a/src/deps/boringssl.translated.zig +++ b/src/deps/boringssl.translated.zig @@ -1471,7 +1471,7 @@ pub extern fn OpenSSL_add_all_ciphers() void; pub extern fn OpenSSL_add_all_digests() void; pub extern fn EVP_cleanup() void; pub extern fn EVP_CIPHER_do_all_sorted(callback: ?*const fn (?*const EVP_CIPHER, [*c]const u8, [*c]const u8, ?*anyopaque) callconv(.C) void, arg: ?*anyopaque) void; -pub extern fn EVP_MD_do_all_sorted(callback: ?*const fn (?*const EVP_MD, [*c]const u8, [*c]const u8, ?*anyopaque) callconv(.C) void, arg: ?*anyopaque) void; +pub extern fn EVP_MD_do_all_sorted(callback: *const fn (*const EVP_MD, ?[*:0]const u8, ?[*:0]const u8, *anyopaque) callconv(.C) void, arg: *anyopaque) void; pub extern fn EVP_MD_do_all(callback: ?*const fn (?*const EVP_MD, [*c]const u8, [*c]const u8, ?*anyopaque) callconv(.C) void, arg: ?*anyopaque) void; pub extern fn i2d_PrivateKey(key: [*c]const EVP_PKEY, outp: [*c][*c]u8) c_int; pub extern fn i2d_PublicKey(key: [*c]const EVP_PKEY, outp: [*c][*c]u8) c_int; diff --git a/src/deps/c_ares.zig b/src/deps/c_ares.zig index 4c77b428bb..110f3b90e3 100644 --- a/src/deps/c_ares.zig +++ b/src/deps/c_ares.zig @@ -2017,7 +2017,8 @@ pub fn Bun__canonicalizeIP_(globalThis: *JSC.JSGlobalObject, callframe: *JSC.Cal var args = JSC.Node.ArgumentsSlice.init(script_ctx, arguments.slice()); const addr_arg = args.nextEat().?; - if (bun.String.tryFromJS(addr_arg, globalThis)) |addr| { + const addr = try bun.String.fromJS(addr_arg, globalThis); + { defer addr.deref(); const addr_slice = addr.toSlice(bun.default_allocator); const addr_str = addr_slice.slice(); @@ -2046,10 +2047,6 @@ pub fn Bun__canonicalizeIP_(globalThis: *JSC.JSGlobalObject, callframe: *JSC.Cal // use the null-terminated size to return the string const size = bun.len(bun.cast([*:0]u8, &ip_addr)); return JSC.ZigString.init(ip_addr[0..size]).toJS(globalThis); - } else { - if (!globalThis.hasException()) - return globalThis.throwInvalidArguments("address must be a string", .{}); - return error.JSError; } } diff --git a/src/dns.zig b/src/dns.zig index 398be2d0ad..45d7638ee0 100644 --- a/src/dns.zig +++ b/src/dns.zig @@ -2,6 +2,7 @@ const bun = @import("root").bun; const std = @import("std"); const JSC = bun.JSC; const JSValue = JSC.JSValue; +const JSError = bun.JSError; pub const AI_V4MAPPED: c_int = if (bun.Environment.isWindows) 2048 else bun.c.AI_V4MAPPED; pub const AI_ADDRCONFIG: c_int = if (bun.Environment.isWindows) 1024 else bun.c.AI_ADDRCONFIG; @@ -71,7 +72,15 @@ pub const GetAddrInfo = struct { return hints; } - pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Options { + const FromJSError = Family.FromJSError || + SocketType.FromJSError || + Protocol.FromJSError || + Backend.FromJSError || error{ + InvalidFlags, + InvalidOptions, + }; + + pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) FromJSError!Options { if (value.isEmptyOrUndefinedOrNull()) return Options{}; @@ -125,7 +134,11 @@ pub const GetAddrInfo = struct { .{ "any", Family.unspecified }, }); - pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Family { + const FromJSError = JSError || error{ + InvalidFamily, + }; + + pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) FromJSError!Family { if (value.isEmptyOrUndefinedOrNull()) return .unspecified; @@ -139,7 +152,7 @@ pub const GetAddrInfo = struct { } if (value.isString()) { - return map.fromJS(globalObject, value) orelse { + return try map.fromJS(globalObject, value) orelse { if (value.toString(globalObject).length() == 0) { return .unspecified; } @@ -181,7 +194,11 @@ pub const GetAddrInfo = struct { } } - pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !SocketType { + const FromJSError = JSError || error{ + InvalidSocketType, + }; + + pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) FromJSError!SocketType { if (value.isEmptyOrUndefinedOrNull()) // Default to .stream return .stream; @@ -196,7 +213,7 @@ pub const GetAddrInfo = struct { } if (value.isString()) { - return map.fromJS(globalObject, value) orelse { + return try map.fromJS(globalObject, value) orelse { if (value.toString(globalObject).length() == 0) return .unspecified; @@ -218,7 +235,11 @@ pub const GetAddrInfo = struct { .{ "udp", Protocol.udp }, }); - pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Protocol { + const FromJSError = JSError || error{ + InvalidProtocol, + }; + + pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) FromJSError!Protocol { if (value.isEmptyOrUndefinedOrNull()) return .unspecified; @@ -232,7 +253,7 @@ pub const GetAddrInfo = struct { } if (value.isString()) { - return map.fromJS(globalObject, value) orelse { + return try map.fromJS(globalObject, value) orelse { const str = value.toString(globalObject); if (str.length() == 0) return .unspecified; @@ -273,12 +294,16 @@ pub const GetAddrInfo = struct { else => .c_ares, }; - pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) !Backend { + pub const FromJSError = JSError || error{ + InvalidBackend, + }; + + pub fn fromJS(value: JSC.JSValue, globalObject: *JSC.JSGlobalObject) FromJSError!Backend { if (value.isEmptyOrUndefinedOrNull()) return default; if (value.isString()) { - return label.fromJS(globalObject, value) orelse { + return try label.fromJS(globalObject, value) orelse { if (value.toString(globalObject).length() == 0) { return default; } diff --git a/src/install/dependency.zig b/src/install/dependency.zig index 1f606b39ce..29b017b94a 100644 --- a/src/install/dependency.zig +++ b/src/install/dependency.zig @@ -772,7 +772,7 @@ pub const Version = struct { return .undefined; } - const tag = Tag.fromJS(globalObject, arguments[0]) orelse return .undefined; + const tag = try Tag.fromJS(globalObject, arguments[0]) orelse return .undefined; var str = bun.String.init(@tagName(tag)); return str.transferToJS(globalObject); } diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h index eeacfbbaed..a39525d95c 100644 --- a/src/js/builtins/BunBuiltinNames.h +++ b/src/js/builtins/BunBuiltinNames.h @@ -61,6 +61,7 @@ using namespace JSC; macro(cancel) \ macro(cancelAlgorithm) \ macro(chdir) \ + macro(checks) \ macro(checkBufferRead) \ macro(cloneArrayBuffer) \ macro(close) \ diff --git a/src/js/node/crypto.ts b/src/js/node/crypto.ts index d3ddb1ca56..b2d5e2c8de 100644 --- a/src/js/node/crypto.ts +++ b/src/js/node/crypto.ts @@ -46,17 +46,24 @@ const { checkPrimeSync, generatePrime, generatePrimeSync, + hkdf, + hkdfSync, } = $cpp("node_crypto_binding.cpp", "createNodeCryptoBinding"); const { pbkdf2: _pbkdf2, - pbkdf2Sync: _pbkdf2Sync, - timingSafeEqual: _timingSafeEqual, + pbkdf2Sync, + timingSafeEqual, randomInt, randomUUID, randomBytes, randomFillSync, randomFill, + secureHeapUsed, + getFips, + setFips, + setEngine, + getHashes, } = $zig("node_crypto_binding.zig", "createNodeCryptoBindingZig"); const { validateObject, validateString } = require("internal/validators"); @@ -422,27 +429,6 @@ var require_algos = __commonJS({ module.exports = require_algorithms(); }, }); -function pbkdf2(password, salt, iterations, keylen, digest, callback) { - if (typeof digest === "function") { - callback = digest; - digest = undefined; - } - - const promise = _pbkdf2(password, salt, iterations, keylen, digest, callback); - if (callback) { - promise.then( - result => callback(null, result), - err => callback(err), - ); - return; - } - - promise.then(() => {}); -} - -function pbkdf2Sync(password, salt, iterations, keylen, digest) { - return _pbkdf2Sync(password, salt, iterations, keylen, digest); -} // node_modules/des.js/lib/des/utils.js var require_utils = __commonJS({ @@ -1975,14 +1961,6 @@ var require_browser6 = __commonJS({ var require_crypto_browserify2 = __commonJS({ "node_modules/crypto-browserify/index.js"(exports) { "use strict"; - var algos = require_algos(), - algoKeys = Object.keys(algos), - hashes = ["sha1", "sha224", "sha256", "sha384", "sha512", "md5", "rmd160"].concat(algoKeys); - exports.getHashes = function () { - return hashes; - }; - exports.pbkdf2Sync = pbkdf2Sync; - exports.pbkdf2 = pbkdf2; var aes = require_browser6(); exports.Cipher = aes.Cipher; exports.createCipher = aes.createCipher; @@ -2361,15 +2339,36 @@ crypto_exports.hash = function hash(algorithm, input, outputEncoding = "hex") { return CryptoHasher.hash(algorithm, input, outputEncoding); }; -crypto_exports.getFips = function getFips() { - return 0; -}; +// TODO: move this to zig +function pbkdf2(password, salt, iterations, keylen, digest, callback) { + if (typeof digest === "function") { + callback = digest; + digest = undefined; + } + + const promise = _pbkdf2(password, salt, iterations, keylen, digest, callback); + if (callback) { + promise.then( + result => callback(null, result), + err => callback(err), + ); + return; + } + + promise.then(() => {}); +} + +crypto_exports.pbkdf2 = pbkdf2; +crypto_exports.pbkdf2Sync = pbkdf2Sync; + +crypto_exports.hkdf = hkdf; +crypto_exports.hkdfSync = hkdfSync; crypto_exports.getCurves = getCurves; crypto_exports.getCipherInfo = getCipherInfo; crypto_exports.scrypt = scrypt; crypto_exports.scryptSync = scryptSync; -crypto_exports.timingSafeEqual = _timingSafeEqual; +crypto_exports.timingSafeEqual = timingSafeEqual; crypto_exports.webcrypto = webcrypto; crypto_exports.subtle = _subtle; crypto_exports.X509Certificate = X509Certificate; @@ -2516,6 +2515,8 @@ crypto_exports.createVerify = createVerify; }; } +crypto_exports.getHashes = getHashes; + crypto_exports.randomInt = randomInt; crypto_exports.randomFill = randomFill; crypto_exports.randomFillSync = randomFillSync; @@ -2527,6 +2528,16 @@ crypto_exports.checkPrimeSync = checkPrimeSync; crypto_exports.generatePrime = generatePrime; crypto_exports.generatePrimeSync = generatePrimeSync; +crypto_exports.secureHeapUsed = secureHeapUsed; +crypto_exports.setEngine = setEngine; +crypto_exports.getFips = getFips; +crypto_exports.setFips = setFips; +Object.defineProperty(crypto_exports, "fips", { + __proto__: null, + get: getFips, + set: setFips, +}); + for (const rng of ["pseudoRandomBytes", "prng", "rng"]) { Object.defineProperty(crypto_exports, rng, { value: randomBytes, diff --git a/src/options.zig b/src/options.zig index 510bf6a4a1..5a479a05d1 100644 --- a/src/options.zig +++ b/src/options.zig @@ -618,7 +618,7 @@ pub const Format = enum { return global.throwInvalidArguments("format must be a string", .{}); } - return Map.fromJS(global, format) orelse { + return try Map.fromJS(global, format) orelse { return global.throwInvalidArguments("Invalid format - must be esm, cjs, or iife", .{}); }; } diff --git a/src/s3/credentials.zig b/src/s3/credentials.zig index fec4c242d6..0e5d763824 100644 --- a/src/s3/credentials.zig +++ b/src/s3/credentials.zig @@ -63,7 +63,7 @@ pub const S3Credentials = struct { if (try opts.getTruthyComptime(globalObject, "accessKeyId")) |js_value| { if (!js_value.isEmptyOrUndefinedOrNull()) { if (js_value.isString()) { - const str = bun.String.fromJS(js_value, globalObject); + const str = try bun.String.fromJS(js_value, globalObject); defer str.deref(); if (str.tag != .Empty and str.tag != .Dead) { new_credentials._accessKeyIdSlice = str.toUTF8(bun.default_allocator); @@ -78,7 +78,7 @@ pub const S3Credentials = struct { if (try opts.getTruthyComptime(globalObject, "secretAccessKey")) |js_value| { if (!js_value.isEmptyOrUndefinedOrNull()) { if (js_value.isString()) { - const str = bun.String.fromJS(js_value, globalObject); + const str = try bun.String.fromJS(js_value, globalObject); defer str.deref(); if (str.tag != .Empty and str.tag != .Dead) { new_credentials._secretAccessKeySlice = str.toUTF8(bun.default_allocator); @@ -93,7 +93,7 @@ pub const S3Credentials = struct { if (try opts.getTruthyComptime(globalObject, "region")) |js_value| { if (!js_value.isEmptyOrUndefinedOrNull()) { if (js_value.isString()) { - const str = bun.String.fromJS(js_value, globalObject); + const str = try bun.String.fromJS(js_value, globalObject); defer str.deref(); if (str.tag != .Empty and str.tag != .Dead) { new_credentials._regionSlice = str.toUTF8(bun.default_allocator); @@ -108,7 +108,7 @@ pub const S3Credentials = struct { if (try opts.getTruthyComptime(globalObject, "endpoint")) |js_value| { if (!js_value.isEmptyOrUndefinedOrNull()) { if (js_value.isString()) { - const str = bun.String.fromJS(js_value, globalObject); + const str = try bun.String.fromJS(js_value, globalObject); defer str.deref(); if (str.tag != .Empty and str.tag != .Dead) { new_credentials._endpointSlice = str.toUTF8(bun.default_allocator); @@ -136,7 +136,7 @@ pub const S3Credentials = struct { if (try opts.getTruthyComptime(globalObject, "bucket")) |js_value| { if (!js_value.isEmptyOrUndefinedOrNull()) { if (js_value.isString()) { - const str = bun.String.fromJS(js_value, globalObject); + const str = try bun.String.fromJS(js_value, globalObject); defer str.deref(); if (str.tag != .Empty and str.tag != .Dead) { new_credentials._bucketSlice = str.toUTF8(bun.default_allocator); @@ -157,7 +157,7 @@ pub const S3Credentials = struct { if (try opts.getTruthyComptime(globalObject, "sessionToken")) |js_value| { if (!js_value.isEmptyOrUndefinedOrNull()) { if (js_value.isString()) { - const str = bun.String.fromJS(js_value, globalObject); + const str = try bun.String.fromJS(js_value, globalObject); defer str.deref(); if (str.tag != .Empty and str.tag != .Dead) { new_credentials._sessionTokenSlice = str.toUTF8(bun.default_allocator); diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index fc3db716b6..c9b920472d 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -654,7 +654,7 @@ pub const ParsedShellScript = struct { const str_js = arguments.nextEat() orelse { return globalThis.throw("$`...`.cwd(): expected a string argument", .{}); }; - const str = bun.String.fromJS(str_js, globalThis); + const str = try bun.String.fromJS(str_js, globalThis); this.cwd = str; return .undefined; } @@ -1699,7 +1699,7 @@ pub const Interpreter = struct { pub fn setCwd(this: *ThisInterpreter, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { const value = callframe.argument(0); - const str = bun.String.fromJS(value, globalThis); + const str = try bun.String.fromJS(value, globalThis); const slice = str.toUTF8(bun.default_allocator); defer slice.deinit(); diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index 422cb32473..2bf49f85bf 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -1038,7 +1038,7 @@ pub const PostgresRequest = struct { }, else => { - const str = try String.fromJS2(value, globalObject); + const str = try String.fromJS(value, globalObject); if (str.tag == .Dead) return error.OutOfMemory; defer str.deref(); const slice = str.toUTF8WithoutRef(bun.default_allocator); diff --git a/src/string.zig b/src/string.zig index 5452b00463..36d14796c5 100644 --- a/src/string.zig +++ b/src/string.zig @@ -3,6 +3,7 @@ const bun = @import("root").bun; const JSC = bun.JSC; const JSValue = bun.JSC.JSValue; const OOM = bun.OOM; +const JSError = bun.JSError; pub const HashedString = @import("string/HashedString.zig"); pub const MutableString = @import("string/MutableString.zig"); @@ -314,7 +315,7 @@ pub const String = extern struct { return String.init(this.toZigString().trunc(len)); } - pub fn toOwnedSliceZ(this: String, allocator: std.mem.Allocator) ![:0]u8 { + pub fn toOwnedSliceZ(this: String, allocator: std.mem.Allocator) OOM![:0]u8 { return this.toZigString().toOwnedSliceZ(allocator); } @@ -457,42 +458,19 @@ pub const String = extern struct { try self.toZigString().format(fmt, opts, writer); } - /// Deprecated: use `fromJS2` to handle errors explicitly - pub fn fromJS(value: bun.JSC.JSValue, globalObject: *JSC.JSGlobalObject) String { - JSC.markBinding(@src()); - - var out: String = String.dead; - if (BunString__fromJS(globalObject, value, &out)) { - return out; - } else { - return String.dead; - } - } - - pub fn fromJS2(value: bun.JSC.JSValue, globalObject: *JSC.JSGlobalObject) bun.JSError!String { + pub fn fromJS(value: bun.JSC.JSValue, globalObject: *JSC.JSGlobalObject) bun.JSError!String { var out: String = String.dead; if (BunString__fromJS(globalObject, value, &out)) { if (comptime bun.Environment.isDebug) { bun.assert(out.tag != .Dead); } return out; - } else { - if (comptime bun.Environment.isDebug) { - bun.assert(globalObject.hasException()); - } - return error.JSError; } - } - pub fn tryFromJS(value: bun.JSC.JSValue, globalObject: *JSC.JSGlobalObject) ?String { - JSC.markBinding(@src()); - - var out: String = String.dead; - if (BunString__fromJS(globalObject, value, &out)) { - return out; - } else { - return null; //TODO: return error.JSError + if (comptime bun.Environment.isDebug) { + bun.assert(globalObject.hasException()); } + return error.JSError; } pub fn toJS(this: *const String, globalObject: *bun.JSC.JSGlobalObject) JSC.JSValue { diff --git a/src/string_immutable.zig b/src/string_immutable.zig index cfd65526ae..2d45fe2728 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -2215,7 +2215,7 @@ pub fn toPathMaybeDir(buf: []u8, utf8: []const u8, comptime add_trailing_lash: b return buf[0..len :0]; } -pub fn convertUTF16ToUTF8(list_: std.ArrayList(u8), comptime Type: type, utf16: Type) !std.ArrayList(u8) { +pub fn convertUTF16ToUTF8(list_: std.ArrayList(u8), comptime Type: type, utf16: Type) OOM!std.ArrayList(u8) { var list = list_; const result = bun.simdutf.convert.utf16.to.utf8.with_errors.le( utf16, @@ -2287,7 +2287,7 @@ pub fn toUTF8AllocWithType(allocator: std.mem.Allocator, comptime Type: type, ut return list.items; } -pub fn toUTF8ListWithType(list_: std.ArrayList(u8), comptime Type: type, utf16: Type) !std.ArrayList(u8) { +pub fn toUTF8ListWithType(list_: std.ArrayList(u8), comptime Type: type, utf16: Type) OOM!std.ArrayList(u8) { if (bun.FeatureFlags.use_simdutf and comptime Type == []const u16) { var list = list_; const length = bun.simdutf.length.utf8.from.utf16.le(utf16); @@ -2334,7 +2334,7 @@ pub fn toUTF8FromLatin1Z(allocator: std.mem.Allocator, latin1: []const u8) !?std return list1; } -pub fn toUTF8ListWithTypeBun(list: *std.ArrayList(u8), comptime Type: type, utf16: Type, comptime skip_trailing_replacement: bool) !(if (skip_trailing_replacement) ?u16 else std.ArrayList(u8)) { +pub fn toUTF8ListWithTypeBun(list: *std.ArrayList(u8), comptime Type: type, utf16: Type, comptime skip_trailing_replacement: bool) OOM!(if (skip_trailing_replacement) ?u16 else std.ArrayList(u8)) { var utf16_remaining = utf16; while (firstNonASCII16(Type, utf16_remaining)) |i| { @@ -4345,15 +4345,15 @@ pub fn indexOfNeedsEscape(slice: []const u8, comptime quote_char: u8) ?u32 { const vec: AsciiVector = remaining[0..ascii_vector_size].*; const cmp: AsciiVectorU1 = if (comptime quote_char == '`') ( // @as(AsciiVectorU1, @bitCast((vec > max_16_ascii))) | - @as(AsciiVectorU1, @bitCast((vec < min_16_ascii))) | - @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, '\\'))))) | - @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, quote_char))))) | - @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, '$'))))) // + @as(AsciiVectorU1, @bitCast((vec < min_16_ascii))) | + @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, '\\'))))) | + @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, quote_char))))) | + @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, '$'))))) // ) else ( // @as(AsciiVectorU1, @bitCast((vec > max_16_ascii))) | - @as(AsciiVectorU1, @bitCast((vec < min_16_ascii))) | - @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, '\\'))))) | - @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, quote_char))))) // + @as(AsciiVectorU1, @bitCast((vec < min_16_ascii))) | + @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, '\\'))))) | + @as(AsciiVectorU1, @bitCast(vec == @as(AsciiVector, @splat(@as(u8, quote_char))))) // ); if (@reduce(.Max, cmp) > 0) { diff --git a/test/js/node/test/parallel/test-crypto-fips.js b/test/js/node/test/parallel/test-crypto-fips.js index c862b617b2..6e4f005c56 100644 --- a/test/js/node/test/parallel/test-crypto-fips.js +++ b/test/js/node/test/parallel/test-crypto-fips.js @@ -14,6 +14,7 @@ const path = require('path'); const fixtures = require('../common/fixtures'); const { internalBinding } = require('internal/test/binding'); const { testFipsCrypto } = internalBinding('crypto'); +const { hasOpenSSL3 } = require('../common/crypto'); const FIPS_ENABLED = 1; const FIPS_DISABLED = 0; @@ -118,7 +119,7 @@ assert.ok(test_result === 1 || test_result === 0); // ("Error: Cannot set FIPS mode in a non-FIPS build."). // Due to this uncertainty the following tests are skipped when configured // with --shared-openssl. -if (!sharedOpenSSL() && !common.hasOpenSSL3) { +if (!sharedOpenSSL() && !hasOpenSSL3) { // OpenSSL config file should be able to turn on FIPS mode testHelper( 'stdout', @@ -148,7 +149,7 @@ if (!sharedOpenSSL() && !common.hasOpenSSL3) { // will not work as expected with that version. // TODO(danbev) Revisit these test once FIPS support is available in // OpenSSL 3.x. -if (!common.hasOpenSSL3) { +if (!hasOpenSSL3) { testHelper( 'stdout', [`--openssl-config=${CNF_FIPS_OFF}`], diff --git a/test/js/node/test/parallel/test-crypto-hkdf.js b/test/js/node/test/parallel/test-crypto-hkdf.js new file mode 100644 index 0000000000..36bd78105d --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-hkdf.js @@ -0,0 +1,226 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { kMaxLength } = require('buffer'); +const assert = require('assert'); +const { + createSecretKey, + hkdf, + hkdfSync, + getHashes +} = require('crypto'); +const { hasOpenSSL3 } = require('../common/crypto'); + +{ + assert.throws(() => hkdf(), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "digest" argument must be of type string/ + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf(i, 'a'), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "digest" argument must be of type string/ + }); + assert.throws(() => hkdfSync(i, 'a'), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "digest" argument must be of type string/ + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf('sha256', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "ikm" argument must be / + }); + assert.throws(() => hkdfSync('sha256', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "ikm" argument must be / + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf('sha256', 'secret', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "salt" argument must be / + }); + assert.throws(() => hkdfSync('sha256', 'secret', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "salt" argument must be / + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf('sha256', 'secret', 'salt', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "info" argument must be / + }); + assert.throws(() => hkdfSync('sha256', 'secret', 'salt', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "info" argument must be / + }); + }); + + ['test', {}, [], false].forEach((i) => { + assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "length" argument must be of type number/ + }); + assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "length" argument must be of type number/ + }); + }); + + assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', -1), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', -1), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', + kMaxLength + 1), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', + kMaxLength + 1), { + code: 'ERR_OUT_OF_RANGE' + }); + + assert.throws(() => hkdfSync('unknown', 'a', '', '', 10), { + code: 'ERR_CRYPTO_INVALID_DIGEST' + }); + + assert.throws(() => hkdf('unknown', 'a', '', '', 10, common.mustNotCall()), { + code: 'ERR_CRYPTO_INVALID_DIGEST' + }); + + assert.throws(() => hkdf('unknown', 'a', '', Buffer.alloc(1025), 10, + common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' + }); + + assert.throws(() => hkdfSync('unknown', 'a', '', Buffer.alloc(1025), 10), { + code: 'ERR_OUT_OF_RANGE' + }); + + assert.throws( + () => hkdf('sha512', 'a', '', '', 64 * 255 + 1, common.mustNotCall()), { + code: 'ERR_CRYPTO_INVALID_KEYLEN' + }); + + assert.throws( + () => hkdfSync('sha512', 'a', '', '', 64 * 255 + 1), { + code: 'ERR_CRYPTO_INVALID_KEYLEN' + }); +} + +const algorithms = [ + ['sha256', 'secret', 'salt', 'info', 10], + ['sha256', '', '', '', 10], + ['sha256', '', 'salt', '', 10], + ['sha512', 'secret', 'salt', '', 15], +]; +if (!hasOpenSSL3 && !common.openSSLIsBoringSSL) + algorithms.push(['whirlpool', 'secret', '', 'info', 20]); + +algorithms.forEach(([ hash, secret, salt, info, length ]) => { + { + const syncResult = hkdfSync(hash, secret, salt, info, length); + assert(syncResult instanceof ArrayBuffer); + let is_async = false; + hkdf(hash, secret, salt, info, length, + common.mustSucceed((asyncResult) => { + assert(is_async); + assert(asyncResult instanceof ArrayBuffer); + assert.deepStrictEqual(syncResult, asyncResult); + })); + // Keep this after the hkdf call above. This verifies + // that the callback is invoked asynchronously. + is_async = true; + } + + { + const buf_secret = Buffer.from(secret); + const buf_salt = Buffer.from(salt); + const buf_info = Buffer.from(info); + + const syncResult = hkdfSync(hash, buf_secret, buf_salt, buf_info, length); + hkdf(hash, buf_secret, buf_salt, buf_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } + + { + const key_secret = createSecretKey(Buffer.from(secret)); + const buf_salt = Buffer.from(salt); + const buf_info = Buffer.from(info); + + const syncResult = hkdfSync(hash, key_secret, buf_salt, buf_info, length); + hkdf(hash, key_secret, buf_salt, buf_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } + + { + const ta_secret = new Uint8Array(Buffer.from(secret)); + const ta_salt = new Uint16Array(Buffer.from(salt)); + const ta_info = new Uint32Array(Buffer.from(info)); + + const syncResult = hkdfSync(hash, ta_secret, ta_salt, ta_info, length); + hkdf(hash, ta_secret, ta_salt, ta_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } + + { + const ta_secret = new Uint8Array(Buffer.from(secret)); + const ta_salt = new Uint16Array(Buffer.from(salt)); + const ta_info = new Uint32Array(Buffer.from(info)); + + const syncResult = hkdfSync( + hash, + ta_secret.buffer, + ta_salt.buffer, + ta_info.buffer, + length); + hkdf(hash, ta_secret, ta_salt, ta_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } + + { + const ta_secret = new Uint8Array(Buffer.from(secret)); + const sa_salt = new SharedArrayBuffer(0); + const sa_info = new SharedArrayBuffer(1); + + const syncResult = hkdfSync( + hash, + ta_secret.buffer, + sa_salt, + sa_info, + length); + hkdf(hash, ta_secret, sa_salt, sa_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } +}); + + +if (!hasOpenSSL3) { + const kKnownUnsupported = ['shake128', 'shake256']; + getHashes() + .filter((hash) => !kKnownUnsupported.includes(hash)) + .forEach((hash) => { + assert(hkdfSync(hash, 'key', 'salt', 'info', 5)); + }); +} diff --git a/test/js/node/test/parallel/test-crypto-pbkdf2.js b/test/js/node/test/parallel/test-crypto-pbkdf2.js index dac8aed95d..e293fed04b 100644 --- a/test/js/node/test/parallel/test-crypto-pbkdf2.js +++ b/test/js/node/test/parallel/test-crypto-pbkdf2.js @@ -5,6 +5,7 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { hasOpenSSL3 } = require('../common/crypto'); function runPBKDF2(password, salt, iterations, keylen, hash) { const syncResult = diff --git a/test/js/node/test/parallel/test-crypto-secure-heap.js b/test/js/node/test/parallel/test-crypto-secure-heap.js index 9e19181e40..fd3355c541 100644 --- a/test/js/node/test/parallel/test-crypto-secure-heap.js +++ b/test/js/node/test/parallel/test-crypto-secure-heap.js @@ -5,21 +5,26 @@ https://github.com/electron/electron/blob/5680c628b6718385bbd975b51ec2640aa7df22 'use strict'; const common = require('../common'); -if (!common.hasCrypto) +if (!common.hasCrypto) { common.skip('missing crypto'); +} -if (common.isWindows) +if (common.isWindows) { common.skip('Not supported on Windows'); +} -if (common.isASan) +if (common.isASan) { common.skip('ASan does not play well with secure heap allocations'); +} const assert = require('assert'); const { fork } = require('child_process'); const fixtures = require('../common/fixtures'); +const { hasOpenSSL3 } = require('../common/crypto'); const { secureHeapUsed, createDiffieHellman, + getFips, } = require('crypto'); if (process.argv[2] === 'child') { @@ -33,7 +38,7 @@ if (process.argv[2] === 'child') { assert.strictEqual(a.used, 0); { - const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256; + const size = getFips() || hasOpenSSL3 ? 1024 : 256; const dh1 = createDiffieHellman(size); const p1 = dh1.getPrime('buffer'); const dh2 = createDiffieHellman(p1, 'buffer');