Skip to content

Commit

Permalink
fix(tls) exposes native canonicalizeIP and fix rootCertificates (#3866)
Browse files Browse the repository at this point in the history
* exposes native canonicalizeIP

* remove unintended duplicate

* add tests

* add tests for debug builds

* add rootCertificates test and fix len

* just randomize test ids on prisma

* remove work around and bump usockets with the actual fix

* fix case

* bump uws
  • Loading branch information
cirospaciari authored Jul 28, 2023
1 parent e110ccf commit e7c80b9
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 30 deletions.
16 changes: 14 additions & 2 deletions src/bun.js/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
using namespace Bun;

extern "C" JSC::EncodedJSValue Bun__fetch(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
extern "C" JSC::EncodedJSValue Bun__canonicalizeIP(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);

using JSGlobalObject
= JSC::JSGlobalObject;
Expand Down Expand Up @@ -1634,6 +1635,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionLazyLoad,
return JSValue::encode(
JSFunction::create(vm, globalObject, 1, pathToFileURLString, functionPathToFileURL, ImplementationVisibility::Public, NoIntrinsic));
}

if (string == "fileURLToPath"_s) {
return JSValue::encode(
JSFunction::create(vm, globalObject, 1, fileURLToPathString, functionFileURLToPath, ImplementationVisibility::Public, NoIntrinsic));
Expand Down Expand Up @@ -1663,12 +1665,17 @@ static JSC_DEFINE_HOST_FUNCTION(functionLazyLoad,
return JSValue::encode(obj);
}

if (string == "rootCertificates"_s) {
if (string == "internal/tls"_s) {
auto* obj = constructEmptyObject(globalObject);

auto sourceOrigin = callFrame->callerSourceOrigin(vm).url();
// expose for tests in debug mode only
#ifndef BUN_DEBUG
bool isBuiltin = sourceOrigin.protocolIs("builtin"_s);
if (!isBuiltin) {
return JSC::JSValue::encode(JSC::jsUndefined());
}
#endif
struct us_cert_string_t* out;
auto size = us_raw_root_certs(&out);
if (size < 0) {
Expand All @@ -1680,7 +1687,12 @@ static JSC_DEFINE_HOST_FUNCTION(functionLazyLoad,
auto str = WTF::String::fromUTF8(raw.str, raw.len);
rootCertificates->putDirectIndex(globalObject, i, JSC::jsString(vm, str));
}
return JSValue::encode(rootCertificates);
obj->putDirect(
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "rootCertificates"_s)), rootCertificates, 0);

obj->putDirect(
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "canonicalizeIP"_s)), JSC::JSFunction::create(vm, globalObject, 1, "canonicalizeIP"_s, Bun__canonicalizeIP, ImplementationVisibility::Public, NoIntrinsic), 0);
return JSValue::encode(obj);
}

if (string == "masqueradesAsUndefined"_s) {
Expand Down
77 changes: 62 additions & 15 deletions src/deps/c_ares.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const c = @import("std").c;
const std = @import("std");
const bun = @import("root").bun;
const JSC = bun.JSC;
const strings = bun.strings;
const iovec = @import("std").os.iovec;
const struct_in_addr = std.os.sockaddr.in;
Expand Down Expand Up @@ -180,8 +181,6 @@ pub const struct_hostent = extern struct {
h_length: c_int,
h_addr_list: [*c][*c]u8,

const JSC = bun.JSC;

pub fn toJSReponse(this: *struct_hostent, _: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, comptime lookup_name: []const u8) JSC.JSValue {

// A cname lookup always returns a single record but we follow the common API here.
Expand Down Expand Up @@ -296,13 +295,12 @@ pub const AddrInfo_node = extern struct {
return len;
}
};

pub const AddrInfo = extern struct {
cnames_: [*c]AddrInfo_cname = null,
node: ?*AddrInfo_node = null,
name_: ?[*:0]u8 = null,

const JSC = bun.JSC;

pub fn toJSArray(
addr_info: *AddrInfo,
parent_allocator: std.mem.Allocator,
Expand Down Expand Up @@ -624,8 +622,6 @@ pub const struct_ares_caa_reply = extern struct {
value: [*c]u8,
length: usize,

const JSC = bun.JSC;

pub fn toJSReponse(this: *struct_ares_caa_reply, parent_allocator: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, comptime _: []const u8) JSC.JSValue {
var stack = std.heap.stackFallback(2048, parent_allocator);
var arena = @import("root").bun.ArenaAllocator.init(stack.get());
Expand Down Expand Up @@ -703,7 +699,6 @@ pub const struct_ares_srv_reply = extern struct {
priority: c_ushort,
weight: c_ushort,
port: c_ushort,
const JSC = bun.JSC;

pub fn toJSReponse(this: *struct_ares_srv_reply, parent_allocator: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, comptime _: []const u8) JSC.JSValue {
var stack = std.heap.stackFallback(2048, parent_allocator);
Expand Down Expand Up @@ -788,8 +783,6 @@ pub const struct_ares_mx_reply = extern struct {
host: [*c]u8,
priority: c_ushort,

const JSC = bun.JSC;

pub fn toJSReponse(this: *struct_ares_mx_reply, parent_allocator: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, comptime _: []const u8) JSC.JSValue {
var stack = std.heap.stackFallback(2048, parent_allocator);
var arena = @import("root").bun.ArenaAllocator.init(stack.get());
Expand Down Expand Up @@ -864,8 +857,6 @@ pub const struct_ares_txt_reply = extern struct {
txt: [*c]u8,
length: usize,

const JSC = bun.JSC;

pub fn toJSReponse(this: *struct_ares_txt_reply, parent_allocator: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, comptime _: []const u8) JSC.JSValue {
var stack = std.heap.stackFallback(2048, parent_allocator);
var arena = @import("root").bun.ArenaAllocator.init(stack.get());
Expand Down Expand Up @@ -946,8 +937,6 @@ pub const struct_ares_naptr_reply = extern struct {
order: c_ushort,
preference: c_ushort,

const JSC = bun.JSC;

pub fn toJSReponse(this: *struct_ares_naptr_reply, parent_allocator: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, comptime _: []const u8) JSC.JSValue {
var stack = std.heap.stackFallback(2048, parent_allocator);
var arena = @import("root").bun.ArenaAllocator.init(stack.get());
Expand Down Expand Up @@ -1040,8 +1029,6 @@ pub const struct_ares_soa_reply = extern struct {
expire: c_uint,
minttl: c_uint,

const JSC = bun.JSC;

pub fn toJSReponse(this: *struct_ares_soa_reply, parent_allocator: std.mem.Allocator, globalThis: *JSC.JSGlobalObject, comptime _: []const u8) JSC.JSValue {
var stack = std.heap.stackFallback(2048, parent_allocator);
var arena = @import("root").bun.ArenaAllocator.init(stack.get());
Expand Down Expand Up @@ -1377,3 +1364,63 @@ pub const ares_soa_reply = struct_ares_soa_reply;
pub const ares_uri_reply = struct_ares_uri_reply;
pub const ares_addr_node = struct_ares_addr_node;
pub const ares_addr_port_node = struct_ares_addr_port_node;

pub export fn Bun__canonicalizeIP(
ctx: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());

const globalThis = ctx.ptr();
const arguments = callframe.arguments(1);

if (arguments.len == 0) {
globalThis.throwInvalidArguments("canonicalizeIP() expects a string but received no arguments.", .{});
return .zero;
}
// windows uses 65 bytes for ipv6 addresses and linux/macos uses 46
const INET6_ADDRSTRLEN = if (comptime bun.Environment.isWindows) 65 else 46;

var script_ctx = globalThis.bunVM();
var args = JSC.Node.ArgumentsSlice.init(script_ctx, arguments.ptr[0..arguments.len]);
var addr_arg = args.nextEat().?;

if (bun.String.tryFromJS(addr_arg, globalThis)) |addr| {
const addr_slice = addr.toSlice(bun.default_allocator);
defer addr_slice.deinit();
const addr_str = addr_slice.slice();
if (addr_str.len >= INET6_ADDRSTRLEN) {
return JSC.JSValue.jsUndefined();
}

var ip_std_text: [INET6_ADDRSTRLEN + 1]u8 = undefined;
// we need a null terminated string as input
var ip_addr: [INET6_ADDRSTRLEN + 1]u8 = undefined;
bun.copy(u8, &ip_addr, addr_str);
ip_addr[addr_str.len] = 0;

var af: c_int = std.os.AF.INET;
// get the standard text representation of the IP
if (ares_inet_pton(af, &ip_addr, &ip_std_text) != 1) {
af = std.os.AF.INET6;
if (ares_inet_pton(af, &ip_addr, &ip_std_text) != 1) {
return JSC.JSValue.jsUndefined();
}
}
// ip_addr will contain the null-terminated string of the cannonicalized IP
if (ares_inet_ntop(af, &ip_std_text, &ip_addr, @sizeOf(@TypeOf(ip_addr))) == null) {
return JSC.JSValue.jsUndefined();
}
// 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]).toValueGC(globalThis);
} else {
globalThis.throwInvalidArguments("address must be a string", .{});
return .zero;
}
}
comptime {
if (!JSC.is_bindgen) {
_ = Bun__canonicalizeIP;
}
}
2 changes: 1 addition & 1 deletion src/deps/uws
Submodule uws updated 1 files
+1 −1 uSockets
10 changes: 4 additions & 6 deletions src/js/node/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const InternalTCPSocket = net[Symbol.for("::bunternal::")];
const bunSocketInternal = Symbol.for("::bunnetsocketinternal::");

const { RegExp, Array, String } = $lazy("primordials");

const { rootCertificates, canonicalizeIP } = $lazy("internal/tls");

const SymbolReplace = Symbol.replace;
const RegExpPrototypeSymbolReplace = RegExp.prototype[SymbolReplace];
const RegExpPrototypeExec = RegExp.prototype.exec;
Expand Down Expand Up @@ -146,11 +149,6 @@ function splitEscapedAltNames(altNames) {
return result;
}

// TODO:
function canonicalizeIP(ip) {
return ip;
}

function checkServerIdentity(hostname, cert) {
const subject = cert.subject;
const altNames = cert.subjectaltname;
Expand Down Expand Up @@ -683,7 +681,7 @@ function convertALPNProtocols(protocols, out) {
out.ALPNProtocols = protocols;
}
}
var rootCertificates = $lazy("rootCertificates");

var exports = {
[Symbol.for("CommonJS")]: 0,
CLIENT_RENEG_LIMIT,
Expand Down
6 changes: 2 additions & 4 deletions src/js/out/modules/node/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ var parseCertString = function() {
currentToken += StringPrototypeSubstring.call(altNames, offset), offset = altNames.length;
}
return ArrayPrototypePush.call(result, currentToken), result;
}, canonicalizeIP = function(ip) {
return ip;
}, checkServerIdentity = function(hostname, cert) {
const { subject, subjectaltname: altNames } = cert, dnsNames = [], ips = [];
if (hostname = "" + hostname, altNames) {
Expand Down Expand Up @@ -151,7 +149,7 @@ var parseCertString = function() {
out.ALPNProtocols = Buffer.from(protocols.buffer.slice(protocols.byteOffset, protocols.byteOffset + protocols.byteLength));
else if (Buffer.isBuffer(protocols))
out.ALPNProtocols = protocols;
}, InternalTCPSocket = net[Symbol.for("::bunternal::")], bunSocketInternal = Symbol.for("::bunnetsocketinternal::"), { RegExp, Array, String } = globalThis[Symbol.for("Bun.lazy")]("primordials"), SymbolReplace = Symbol.replace, RegExpPrototypeSymbolReplace = RegExp.prototype[SymbolReplace], RegExpPrototypeExec = RegExp.prototype.exec, StringPrototypeStartsWith = String.prototype.startsWith, StringPrototypeSlice = String.prototype.slice, StringPrototypeIncludes = String.prototype.includes, StringPrototypeSplit = String.prototype.split, StringPrototypeIndexOf = String.prototype.indexOf, StringPrototypeSubstring = String.prototype.substring, StringPrototypeEndsWith = String.prototype.endsWith, StringFromCharCode = String.fromCharCode, StringPrototypeCharCodeAt = String.prototype.charCodeAt, ArrayPrototypeIncludes = Array.prototype.includes, ArrayPrototypeJoin = Array.prototype.join, ArrayPrototypeForEach = Array.prototype.forEach, ArrayPrototypePush = Array.prototype.push, ArrayPrototypeSome = Array.prototype.some, ArrayPrototypeReduce = Array.prototype.reduce, jsonStringPattern = /^"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"/, InternalSecureContext = class SecureContext2 {
}, InternalTCPSocket = net[Symbol.for("::bunternal::")], bunSocketInternal = Symbol.for("::bunnetsocketinternal::"), { RegExp, Array, String } = globalThis[Symbol.for("Bun.lazy")]("primordials"), { rootCertificates, canonicalizeIP } = globalThis[Symbol.for("Bun.lazy")]("internal/tls"), SymbolReplace = Symbol.replace, RegExpPrototypeSymbolReplace = RegExp.prototype[SymbolReplace], RegExpPrototypeExec = RegExp.prototype.exec, StringPrototypeStartsWith = String.prototype.startsWith, StringPrototypeSlice = String.prototype.slice, StringPrototypeIncludes = String.prototype.includes, StringPrototypeSplit = String.prototype.split, StringPrototypeIndexOf = String.prototype.indexOf, StringPrototypeSubstring = String.prototype.substring, StringPrototypeEndsWith = String.prototype.endsWith, StringFromCharCode = String.fromCharCode, StringPrototypeCharCodeAt = String.prototype.charCodeAt, ArrayPrototypeIncludes = Array.prototype.includes, ArrayPrototypeJoin = Array.prototype.join, ArrayPrototypeForEach = Array.prototype.forEach, ArrayPrototypePush = Array.prototype.push, ArrayPrototypeSome = Array.prototype.some, ArrayPrototypeReduce = Array.prototype.reduce, jsonStringPattern = /^"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"/, InternalSecureContext = class SecureContext2 {
context;
constructor(options) {
const context = {};
Expand Down Expand Up @@ -416,7 +414,7 @@ var CLIENT_RENEG_LIMIT = 3, CLIENT_RENEG_WINDOW = 600, DEFAULT_ECDH_CURVE = "aut
return new TLSSocket(port).connect(port, host2, connectListener);
}
return new TLSSocket().connect(port, host2, connectListener);
}, connect = createConnection, rootCertificates = globalThis[Symbol.for("Bun.lazy")]("rootCertificates"), exports = {
}, connect = createConnection, exports = {
[Symbol.for("CommonJS")]: 0,
CLIENT_RENEG_LIMIT,
CLIENT_RENEG_WINDOW,
Expand Down
42 changes: 42 additions & 0 deletions test/js/node/tls/node-tls-internals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createTest } from "node-harness";
const { describe, expect, it } = createTest(import.meta.path);
//@ts-ignore
const $lazy = globalThis[Symbol.for("Bun.lazy")];
const tlsInternals = $lazy("internal/tls");

describe("node/tls", () => {
// this is only exposed on debug builds so we skip on release builds
const test = tlsInternals ? it : it.skip;

test("canonicalizeIP", () => {
const { canonicalizeIP } = tlsInternals;
expect(canonicalizeIP("127.0.0.1")).toBe("127.0.0.1");
expect(canonicalizeIP("10.1.0.1")).toBe("10.1.0.1");
expect(canonicalizeIP("::1")).toBe("::1");
expect(canonicalizeIP("fe80:0:0:0:0:0:0:1")).toBe("fe80::1");
expect(canonicalizeIP("fe80:0:0:0:0:0:0:0")).toBe("fe80::");
expect(canonicalizeIP("fe80::0000:0010:0001")).toBe("fe80::10:1");
expect(canonicalizeIP("0001:2222:3333:4444:5555:6666:7777:0088")).toBe("1:2222:3333:4444:5555:6666:7777:88");

expect(canonicalizeIP("0001:2222:3333:4444:5555:6666::")).toBe("1:2222:3333:4444:5555:6666::");

expect(canonicalizeIP("a002:B12:00Ba:4444:5555:6666:0:0")).toBe("a002:b12:ba:4444:5555:6666::");

// IPv4 address represented in IPv6
expect(canonicalizeIP("0:0:0:0:0:ffff:c0a8:101")).toBe("::ffff:192.168.1.1");

expect(canonicalizeIP("::ffff:192.168.1.1")).toBe("::ffff:192.168.1.1");
});

test("rootCertificates", () => {
const { rootCertificates } = tlsInternals;
expect(rootCertificates).toBeInstanceOf(Array);
expect(rootCertificates.length).toBeGreaterThan(0);
expect(typeof rootCertificates[0]).toBe("string");

for (const cert of rootCertificates) {
expect(cert).toStartWith("-----BEGIN CERTIFICATE-----");
expect(cert).toEndWith("-----END CERTIFICATE-----");
}
});
});
3 changes: 1 addition & 2 deletions test/js/third_party/prisma/prisma.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { generateClient } from "./helper.ts";
import type { PrismaClient } from "./prisma/types.d.ts";

function* TestIDGenerator(): Generator<number> {
let i = Math.floor(Math.random() * 10000);
while (true) {
yield i++;
yield Math.floor(1 + Math.random() * 2147483648);
}
}
const test_id = TestIDGenerator();
Expand Down

0 comments on commit e7c80b9

Please sign in to comment.