diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 4143ee71a3..f5ef1f73b5 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -1649,6 +1649,8 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_CLOSED, "Socket is closed"_s)); case ErrorCode::ERR_SOCKET_CLOSED_BEFORE_CONNECTION: return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_SOCKET_CLOSED_BEFORE_CONNECTION, "Socket closed before the connection was established"_s)); + case ErrorCode::ERR_TLS_RENEGOTIATION_DISABLED: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_TLS_RENEGOTIATION_DISABLED, "TLS session renegotiation disabled for this socket"_s)); default: { break; diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index a98eb7c811..eae4e66034 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -115,6 +115,7 @@ const errors: ErrorCodeMapping = [ ["ERR_TLS_CERT_ALTNAME_FORMAT", SyntaxError], ["ERR_TLS_CERT_ALTNAME_INVALID", Error], ["ERR_TLS_SNI_FROM_SERVER", Error], + ["ERR_TLS_RENEGOTIATION_DISABLED", Error], // NET ["ERR_SOCKET_CLOSED_BEFORE_CONNECTION", Error], diff --git a/src/js/builtins.d.ts b/src/js/builtins.d.ts index 365ca07a67..af61e3fa8b 100644 --- a/src/js/builtins.d.ts +++ b/src/js/builtins.d.ts @@ -638,6 +638,7 @@ declare function $ERR_ILLEGAL_CONSTRUCTOR(): TypeError; declare function $ERR_SERVER_ALREADY_LISTEN(): Error; declare function $ERR_SOCKET_CLOSED(): Error; declare function $ERR_SOCKET_CLOSED_BEFORE_CONNECTION(): Error; +declare function $ERR_TLS_RENEGOTIATION_DISABLED(): Error; /** * Convert a function to a class-like object. diff --git a/src/js/node/fs.promises.ts b/src/js/node/fs.promises.ts index e3b4ab770a..88730b4e64 100644 --- a/src/js/node/fs.promises.ts +++ b/src/js/node/fs.promises.ts @@ -106,7 +106,7 @@ function cp(src, dest, options) { throw new TypeError("options must be an object"); } if (options.dereference || options.filter || options.preserveTimestamps || options.verbatimSymlinks) { - return require("../internal/fs/cp")(src, dest, options); + return require("internal/fs/cp")(src, dest, options); } return fs.cp(src, dest, options.recursive, options.errorOnExist, options.force ?? true, options.mode); } diff --git a/src/js/node/fs.ts b/src/js/node/fs.ts index f480b390a2..a7fa2f0905 100644 --- a/src/js/node/fs.ts +++ b/src/js/node/fs.ts @@ -983,7 +983,7 @@ function cpSync(src, dest, options) { throw new TypeError("options must be an object"); } if (options.dereference || options.filter || options.preserveTimestamps || options.verbatimSymlinks) { - return require("../internal/fs/cp-sync")(src, dest, options); + return require("internal/fs/cp-sync")(src, dest, options); } return fs.cpSync(src, dest, options.recursive, options.errorOnExist, options.force ?? true, options.mode); } diff --git a/src/js/node/tls.ts b/src/js/node/tls.ts index 06b66b8243..cd426ff0a0 100644 --- a/src/js/node/tls.ts +++ b/src/js/node/tls.ts @@ -9,16 +9,9 @@ const { Server: NetServer, Socket: NetSocket } = net; const { rootCertificates, canonicalizeIP } = $cpp("NodeTLS.cpp", "createNodeTLSBinding"); -const { - ERR_TLS_CERT_ALTNAME_INVALID, - ERR_TLS_CERT_ALTNAME_FORMAT, - ERR_TLS_SNI_FROM_SERVER, -} = require("internal/errors"); - const SymbolReplace = Symbol.replace; const RegExpPrototypeSymbolReplace = RegExp.prototype[SymbolReplace]; const RegExpPrototypeExec = RegExp.prototype.exec; -const JSONParse = JSON.parse; const ObjectAssign = Object.assign; const StringPrototypeStartsWith = String.prototype.startsWith; @@ -287,216 +280,207 @@ function translatePeerCertificate(c) { return c; } +const ksecureContext = Symbol("ksecureContext"); +const kcheckServerIdentity = Symbol("kcheckServerIdentity"); +const ksession = Symbol("ksession"); +const krenegotiationDisabled = Symbol("renegotiationDisabled"); + const buntls = Symbol.for("::buntls::"); -var SocketClass; -const TLSSocket = (function (InternalTLSSocket) { - SocketClass = InternalTLSSocket; - Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, { - value: "TLSSocket", - enumerable: false, - }); - function Socket(options) { - return new InternalTLSSocket(options); +function TLSSocket(socket?, options?) { + this[ksecureContext] = undefined; + this.ALPNProtocols = undefined; + this[kcheckServerIdentity] = undefined; + this[ksession] = undefined; + this.alpnProtocol = null; + this._secureEstablished = false; + this._rejectUnauthorized = rejectUnauthorizedDefault; + this._securePending = true; + this._newSessionPending = undefined; + this._controlReleased = undefined; + this.secureConnecting = false; + this._SNICallback = undefined; + this.servername = undefined; + this.authorized = false; + this.authorizationError; + this[krenegotiationDisabled] = undefined; + this.encrypted = true; + + NetSocket.$call(this, socket instanceof NetSocket || socket instanceof Duplex ? options : options || socket); + options = options || socket || {}; + if (typeof options === "object") { + const { ALPNProtocols } = options; + if (ALPNProtocols) { + convertALPNProtocols(ALPNProtocols, this); + } + if (socket instanceof NetSocket || socket instanceof Duplex) { + this._handle = socket; + // keep compatibility with http2-wrapper or other places that try to grab JSStreamSocket in node.js, with here is just the TLSSocket + this._handle._parentWrap = this; + } } - Socket.prototype = InternalTLSSocket.prototype; - return Object.defineProperty(Socket, Symbol.hasInstance, { - value(instance) { - return instance instanceof InternalTLSSocket; - }, - }); -})( - class TLSSocket extends NetSocket { - #secureContext; - ALPNProtocols; - #checkServerIdentity; - #session; - alpnProtocol = null; + this[ksecureContext] = options.secureContext || createSecureContext(options); + this.authorized = false; + this.secureConnecting = true; + this._secureEstablished = false; + this._securePending = true; + this[kcheckServerIdentity] = options.checkServerIdentity || checkServerIdentity; + this[ksession] = options.session || null; +} +$toClass(TLSSocket, "TLSSocket", NetSocket); - constructor(socket, options) { - super(socket instanceof NetSocket || socket instanceof Duplex ? options : options || socket); - options = options || socket || {}; - if (typeof options === "object") { - const { ALPNProtocols } = options; - if (ALPNProtocols) { - convertALPNProtocols(ALPNProtocols, this); - } - if (socket instanceof NetSocket || socket instanceof Duplex) { - this._handle = socket; - // keep compatibility with http2-wrapper or other places that try to grab JSStreamSocket in node.js, with here is just the TLSSocket - this._handle._parentWrap = this; - } - } +TLSSocket.prototype._start = function _start() { + // some frameworks uses this _start internal implementation is suposed to start TLS handshake/connect + this.connect(); +}; - this.#secureContext = options.secureContext || createSecureContext(options); - this.authorized = false; - this.secureConnecting = true; - this._secureEstablished = false; - this._securePending = true; - this.#checkServerIdentity = options.checkServerIdentity || checkServerIdentity; - this.#session = options.session || null; +TLSSocket.prototype.getSession = function getSession() { + return this._handle?.getSession?.(); +}; + +TLSSocket.prototype.getEphemeralKeyInfo = function getEphemeralKeyInfo() { + return this._handle?.getEphemeralKeyInfo?.(); +}; + +TLSSocket.prototype.getCipher = function getCipher() { + return this._handle?.getCipher?.(); +}; + +TLSSocket.prototype.getSharedSigalgs = function getSharedSigalgs() { + return this._handle?.getSharedSigalgs?.(); +}; + +TLSSocket.prototype.getProtocol = function getProtocol() { + return this._handle?.getTLSVersion?.(); +}; + +TLSSocket.prototype.getFinished = function getFinished() { + return this._handle?.getTLSFinishedMessage?.() || undefined; +}; + +TLSSocket.prototype.getPeerFinished = function getPeerFinished() { + return this._handle?.getTLSPeerFinishedMessage?.() || undefined; +}; + +TLSSocket.prototype.isSessionReused = function isSessionReused() { + return !!this[ksession]; +}; + +TLSSocket.prototype.renegotiate = function renegotiate(options, callback) { + if (this[krenegotiationDisabled]) { + // if renegotiation is disabled should emit error event in nextTick for nodejs compatibility + const error = $ERR_TLS_RENEGOTIATION_DISABLED(); + typeof callback === "function" && process.nextTick(callback, error); + return false; + } + + const socket = this._handle; + // if the socket is detached we can't renegotiate, nodejs do a noop too (we should not return false or true here) + if (!socket) return; + + if (options) { + let requestCert = !!this._requestCert; + let rejectUnauthorized = !!this._rejectUnauthorized; + + if (options.requestCert !== undefined) requestCert = !!options.requestCert; + if (options.rejectUnauthorized !== undefined) rejectUnauthorized = !!options.rejectUnauthorized; + + if (requestCert !== this._requestCert || rejectUnauthorized !== this._rejectUnauthorized) { + socket.setVerifyMode?.(requestCert, rejectUnauthorized); + this._requestCert = requestCert; + this._rejectUnauthorized = rejectUnauthorized; } + } + try { + socket.renegotiate?.(); + // if renegotiate is successful should emit secure event when done + typeof callback === "function" && this.once("secure", () => callback(null)); + return true; + } catch (err) { + // if renegotiate fails should emit error event in nextTick for nodejs compatibility + typeof callback === "function" && process.nextTick(callback, err); + return false; + } +}; - _secureEstablished = false; - _rejectUnauthorized = rejectUnauthorizedDefault; - _securePending = true; - _newSessionPending; - _controlReleased; - secureConnecting = false; - _SNICallback; - servername; - authorized = false; - authorizationError; - #renegotiationDisabled = false; +TLSSocket.prototype.disableRenegotiation = function disableRenegotiation() { + this[krenegotiationDisabled] = true; + // disable renegotiation on the socket + return this._handle?.disableRenegotiation?.(); +}; - encrypted = true; +TLSSocket.prototype.getTLSTicket = function getTLSTicket() { + return this._handle?.getTLSTicket?.(); +}; - _start() { - // some frameworks uses this _start internal implementation is suposed to start TLS handshake/connect - this.connect(); - } +TLSSocket.prototype.exportKeyingMaterial = function exportKeyingMaterial(length, label, context) { + if (context) { + return this._handle?.exportKeyingMaterial?.(length, label, context); + } + return this._handle?.exportKeyingMaterial?.(length, label); +}; - getSession() { - return this._handle?.getSession?.(); - } +TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(size) { + return this._handle?.setMaxSendFragment?.(size) || false; +}; - getEphemeralKeyInfo() { - return this._handle?.getEphemeralKeyInfo?.(); - } +TLSSocket.prototype.enableTrace = function enableTrace() { + // only for debug purposes so we just mock for now +}; - getCipher() { - return this._handle?.getCipher?.(); - } +TLSSocket.prototype.setServername = function setServername(name) { + if (this.isServer) { + throw $ERR_TLS_SNI_FROM_SERVER("Cannot issue SNI from a TLS server-side socket"); + } + // if the socket is detached we can't set the servername but we set this property so when open will auto set to it + this.servername = name; + this._handle?.setServername?.(name); +}; - getSharedSigalgs() { - return this._handle?.getSharedSigalgs?.(); - } +TLSSocket.prototype.setSession = function setSession(session) { + this[ksession] = session; + if (typeof session === "string") session = Buffer.from(session, "latin1"); + return this._handle?.setSession?.(session); +}; - getProtocol() { - return this._handle?.getTLSVersion?.(); - } +TLSSocket.prototype.getPeerCertificate = function getPeerCertificate(abbreviated) { + const cert = + arguments.length < 1 ? this._handle?.getPeerCertificate?.() : this._handle?.getPeerCertificate?.(abbreviated); + if (cert) { + return translatePeerCertificate(cert); + } +}; - getFinished() { - return this._handle?.getTLSFinishedMessage?.() || undefined; - } +TLSSocket.prototype.getCertificate = function getCertificate() { + // need to implement certificate on socket.zig + const cert = this._handle?.getCertificate?.(); + if (cert) { + // It's not a peer cert, but the formatting is identical. + return translatePeerCertificate(cert); + } +}; - getPeerFinished() { - return this._handle?.getTLSPeerFinishedMessage?.() || undefined; - } - isSessionReused() { - return !!this.#session; - } +TLSSocket.prototype.getPeerX509Certificate = function getPeerX509Certificate() { + return this._handle?.getPeerX509Certificate?.(); +}; - renegotiate(options, callback) { - if (this.#renegotiationDisabled) { - // if renegotiation is disabled should emit error event in nextTick for nodejs compatibility - const error = new Error("ERR_TLS_RENEGOTIATION_DISABLED: TLS session renegotiation disabled for this socket"); - error.name = "ERR_TLS_RENEGOTIATION_DISABLED"; - typeof callback === "function" && process.nextTick(callback, error); - return false; - } +TLSSocket.prototype.getX509Certificate = function getX509Certificate() { + return this._handle?.getX509Certificate?.(); +}; - const socket = this._handle; - // if the socket is detached we can't renegotiate, nodejs do a noop too (we should not return false or true here) - if (!socket) return; +TLSSocket.prototype[buntls] = function (port, host) { + return { + socket: this._handle, + ALPNProtocols: this.ALPNProtocols, + serverName: this.servername || host || "localhost", + checkServerIdentity: this[kcheckServerIdentity], + session: this[ksession], + rejectUnauthorized: this._rejectUnauthorized, + requestCert: this._requestCert, + ...this[ksecureContext], + }; +}; - if (options) { - let requestCert = !!this._requestCert; - let rejectUnauthorized = !!this._rejectUnauthorized; - - if (options.requestCert !== undefined) requestCert = !!options.requestCert; - if (options.rejectUnauthorized !== undefined) rejectUnauthorized = !!options.rejectUnauthorized; - - if (requestCert !== this._requestCert || rejectUnauthorized !== this._rejectUnauthorized) { - socket.setVerifyMode?.(requestCert, rejectUnauthorized); - this._requestCert = requestCert; - this._rejectUnauthorized = rejectUnauthorized; - } - } - try { - socket.renegotiate?.(); - // if renegotiate is successful should emit secure event when done - typeof callback === "function" && this.once("secure", () => callback(null)); - return true; - } catch (err) { - // if renegotiate fails should emit error event in nextTick for nodejs compatibility - typeof callback === "function" && process.nextTick(callback, err); - return false; - } - } - - disableRenegotiation() { - this.#renegotiationDisabled = true; - // disable renegotiation on the socket - return this._handle?.disableRenegotiation?.(); - } - - getTLSTicket() { - return this._handle?.getTLSTicket?.(); - } - exportKeyingMaterial(length, label, context) { - if (context) { - return this._handle?.exportKeyingMaterial?.(length, label, context); - } - return this._handle?.exportKeyingMaterial?.(length, label); - } - - setMaxSendFragment(size) { - return this._handle?.setMaxSendFragment?.(size) || false; - } - - // only for debug purposes so we just mock for now - enableTrace() {} - - setServername(name) { - if (this.isServer) { - throw $ERR_TLS_SNI_FROM_SERVER("Cannot issue SNI from a TLS server-side socket"); - } - // if the socket is detached we can't set the servername but we set this property so when open will auto set to it - this.servername = name; - this._handle?.setServername?.(name); - } - setSession(session) { - this.#session = session; - if (typeof session === "string") session = Buffer.from(session, "latin1"); - return this._handle?.setSession?.(session); - } - getPeerCertificate(abbreviated) { - const cert = - arguments.length < 1 ? this._handle?.getPeerCertificate?.() : this._handle?.getPeerCertificate?.(abbreviated); - if (cert) { - return translatePeerCertificate(cert); - } - } - getCertificate() { - // need to implement certificate on socket.zig - const cert = this._handle?.getCertificate?.(); - if (cert) { - // It's not a peer cert, but the formatting is identical. - return translatePeerCertificate(cert); - } - } - getPeerX509Certificate() { - return this._handle?.getPeerX509Certificate?.(); - } - getX509Certificate() { - return this._handle?.getX509Certificate?.(); - } - - [buntls](port, host) { - return { - socket: this._handle, - ALPNProtocols: this.ALPNProtocols, - serverName: this.servername || host || "localhost", - checkServerIdentity: this.#checkServerIdentity, - session: this.#session, - rejectUnauthorized: this._rejectUnauthorized, - requestCert: this._requestCert, - ...this.#secureContext, - }; - } - }, -); let CLIENT_RENEG_LIMIT = 3, CLIENT_RENEG_WINDOW = 600; @@ -629,7 +613,7 @@ function Server(options, secureConnectionListener): void { clientRenegotiationWindow: CLIENT_RENEG_WINDOW, contexts: contexts, }, - SocketClass, + TLSSocket, ]; }; diff --git a/src/js/node/worker_threads.ts b/src/js/node/worker_threads.ts index 8d5eabb642..b7287c585b 100644 --- a/src/js/node/worker_threads.ts +++ b/src/js/node/worker_threads.ts @@ -4,7 +4,7 @@ declare const self: typeof globalThis; type WebWorker = InstanceType; const EventEmitter = require("node:events"); -const { throwNotImplemented, warnNotImplementedOnce } = require("../internal/shared"); +const { throwNotImplemented, warnNotImplementedOnce } = require("internal/shared"); const { MessageChannel, BroadcastChannel, Worker: WebWorker } = globalThis; const SHARE_ENV = Symbol("nodejs.worker_threads.SHARE_ENV");