diff options
author | 2023-07-17 23:39:09 -0300 | |
---|---|---|
committer | 2023-07-17 19:39:09 -0700 | |
commit | 13b54fbdb8cc36bbe027238654360f159ecaefbb (patch) | |
tree | 9f905b3520a01ba64cf10df0eefe2fe35c196648 /src/js/node | |
parent | 9273e29f0eba9c9d185a602d30def5a6b981ad55 (diff) | |
download | bun-13b54fbdb8cc36bbe027238654360f159ecaefbb.tar.gz bun-13b54fbdb8cc36bbe027238654360f159ecaefbb.tar.zst bun-13b54fbdb8cc36bbe027238654360f159ecaefbb.zip |
[tls] General compatibility improvements (#3596)
* wip
* subjectaltname
* more progress
* bindings
* fmt
* getCert/getPeerCertificate
* fix checkServerIdentity
* fix checkServerIdentity
* add a lot of TLSSocket functions
* getEphemeralKeyInfo fix and comment
* add alternative for getEphemeralKeyInfo
* add get session and set session
* fix isSessionReused
* get back the raw data for MSSQL
* fixeup
* fixup getSession + tests
* fix doc + fmt
* getFinished/getPeerFinished
* codegen
* fixup
* revert webkit
* more fixes
* ssl helper + revert test oops
* asserts
Diffstat (limited to 'src/js/node')
-rw-r--r-- | src/js/node/net.js | 86 | ||||
-rw-r--r-- | src/js/node/tls.js | 135 |
2 files changed, 142 insertions, 79 deletions
diff --git a/src/js/node/net.js b/src/js/node/net.js index d45bb23a1..5501d327b 100644 --- a/src/js/node/net.js +++ b/src/js/node/net.js @@ -65,6 +65,7 @@ const bunSocketServerHandlers = Symbol.for("::bunsocket_serverhandlers::"); const bunSocketServerConnections = Symbol.for("::bunnetserverconnections::"); const bunSocketServerOptions = Symbol.for("::bunnetserveroptions::"); const bunSocketInternal = Symbol.for("::bunnetsocketinternal::"); +const bunTLSConnectOptions = Symbol.for("::buntlsconnectoptions::"); var SocketClass; const Socket = (function (InternalSocket) { @@ -91,7 +92,6 @@ const Socket = (function (InternalSocket) { close: Socket.#Close, connectError(socket, error) { const self = socket.data; - self.emit("error", error); }, data({ data: self }, buffer) { @@ -120,11 +120,21 @@ const Socket = (function (InternalSocket) { socket.ref(); self[bunSocketInternal] = socket; self.connecting = false; + const options = self[bunTLSConnectOptions]; + + if (options) { + const { session } = options; + if (session) { + self.setSession(session); + } + } + if (!self.#upgraded) { // this is not actually emitted on nodejs when socket used on the connection - // this is already emmited on non-TLS socket and on TLS socket is emmited secureConnect on handshake + // this is already emmited on non-TLS socket and on TLS socket is emmited secureConnect after handshake self.emit("connect", self); } + Socket.#Drain(socket); }, handshake(socket, success, verifyError) { @@ -133,16 +143,13 @@ const Socket = (function (InternalSocket) { self._securePending = false; self.secureConnecting = false; self._secureEstablished = !!success; + self.emit("secure", self); - // Needs getPeerCertificate support (not implemented yet) - // if (!verifyError && !this.isSessionReused()) { - // const hostname = options.servername || - // options.host || - // (options.socket && options.socket._host) || - // 'localhost'; - // const cert = this.getPeerCertificate(true); - // verifyError = options.checkServerIdentity(hostname, cert); - // } + const { checkServerIdentity } = self[bunTLSConnectOptions]; + if (!verifyError && typeof checkServerIdentity === "function" && self.servername) { + const cert = self.getPeerCertificate(true); + verifyError = checkServerIdentity(self.servername, cert); + } if (self._requestCert || self._rejectUnauthorized) { if (verifyError) { @@ -250,19 +257,13 @@ const Socket = (function (InternalSocket) { self.emit("connection", _socket); }, - handshake({ data: self }, success, verifyError) { + handshake(socket, success, verifyError) { + const { data: self } = socket; + self.emit("secure", self); + self._securePending = false; self.secureConnecting = false; self._secureEstablished = !!success; - // Needs getPeerCertificate support (not implemented yet) - // if (!verifyError && !this.isSessionReused()) { - // const hostname = options.servername || - // options.host || - // (options.socket && options.socket._host) || - // 'localhost'; - // const cert = this.getPeerCertificate(true); - // verifyError = options.checkServerIdentity(hostname, cert); - // } if (self._requestCert || self._rejectUnauthorized) { if (verifyError) { @@ -276,7 +277,7 @@ const Socket = (function (InternalSocket) { } else { self.authorized = true; } - self.emit("secureConnect", verifyError); + self.emit("secureConnection", verifyError); }, error(socket, error) { Socket.#Handlers.error(socket, error); @@ -296,6 +297,7 @@ const Socket = (function (InternalSocket) { #readQueue = createFIFO(); remotePort; [bunSocketInternal] = null; + [bunTLSConnectOptions] = null; timeout = 0; #writeCallback; #writeChunk; @@ -347,13 +349,18 @@ const Socket = (function (InternalSocket) { socket.ref(); this[bunSocketInternal] = socket; this.connecting = false; - this.emit("connect", this); + if (!this.#upgraded) { + // this is not actually emitted on nodejs when socket used on the connection + // this is already emmited on non-TLS socket and on TLS socket is emmited secureConnect after handshake + this.emit("connect", this); + } Socket.#Drain(socket); } connect(port, host, connectListener) { var path; var connection = this.#socket; + var _checkServerIdentity = undefined; if (typeof port === "string") { path = port; port = undefined; @@ -390,8 +397,10 @@ const Socket = (function (InternalSocket) { rejectUnauthorized, pauseOnConnect, servername, + checkServerIdentity, + session, } = port; - + _checkServerIdentity = checkServerIdentity; this.servername = servername; if (socket) { connection = socket; @@ -414,18 +423,14 @@ const Socket = (function (InternalSocket) { this._rejectUnauthorized = rejectUnauthorized; if (tls) { - // TLS can true/false or options - if (typeof tls !== "object") { - tls = { - rejectUnauthorized: rejectUnauthorized, - requestCert: true, - }; - } else { - tls.rejectUnauthorized = rejectUnauthorized; - tls.requestCert = true; - if (!connection && tls.socket) { - connection = tls.socket; - } + tls.rejectUnauthorized = rejectUnauthorized; + tls.requestCert = true; + tls.session = session || tls.session; + this.servername = tls.servername; + tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity; + this[bunTLSConnectOptions] = tls; + if (!connection && tls.socket) { + connection = tls.socket; } } if (connection) { @@ -441,6 +446,7 @@ const Socket = (function (InternalSocket) { this.secureConnecting = true; this._secureEstablished = false; this._securePending = true; + if (connectListener) this.on("secureConnect", connectListener); } else if (connectListener) this.on("connect", connectListener); // start using existing connection @@ -809,12 +815,16 @@ class Server extends EventEmitter { var tls = undefined; var TLSSocketClass = undefined; const bunTLS = this[bunTlsSymbol]; + const options = this[bunSocketServerOptions]; + if (typeof bunTLS === "function") { [tls, TLSSocketClass] = bunTLS.call(this, port, hostname, false); + options.servername = tls.serverName; + options.InternalSocketClass = TLSSocketClass; + } else { + options.InternalSocketClass = SocketClass; } - this[bunSocketServerOptions].InternalSocketClass = TLSSocketClass || SocketClass; - this.#server = Bun.listen( path ? { diff --git a/src/js/node/tls.js b/src/js/node/tls.js index 60903239b..259e6e832 100644 --- a/src/js/node/tls.js +++ b/src/js/node/tls.js @@ -16,6 +16,8 @@ const StringPrototypeSplit = String.prototype.split; const StringPrototypeIndexOf = String.prototype.indexOf; const StringPrototypeSubstring = String.prototype.substring; const StringPrototypeEndsWith = String.prototype.endsWith; +const StringFromCharCode = String.fromCharCode; +const StringPrototypeCharCodeAt = String.prototype.charCodeAt; const ArrayPrototypeIncludes = Array.prototype.includes; const ArrayPrototypeJoin = Array.prototype.join; @@ -40,11 +42,16 @@ function isValidTLSArray(obj) { } function unfqdn(host) { - return RegExpPrototypeSymbolReplace(/[.]$/, host, ""); + return RegExpPrototypeSymbolReplace.call(/[.]$/, host, ""); +} +// String#toLowerCase() is locale-sensitive so we use +// a conservative version that only lowercases A-Z. +function toLowerCase(c) { + return StringFromCharCode.call(32 + StringPrototypeCharCodeAt.call(c, 0)); } function splitHost(host) { - return StringPrototypeSplit.call(RegExpPrototypeSymbolReplace(/[A-Z]/g, unfqdn(host), toLowerCase), "."); + return StringPrototypeSplit.call(RegExpPrototypeSymbolReplace.call(/[A-Z]/g, unfqdn(host), toLowerCase), "."); } function check(hostParts, pattern, wildcards) { @@ -163,7 +170,6 @@ function checkServerIdentity(hostname, cert) { let reason = "Unknown reason"; hostname = unfqdn(hostname); // Remove trailing dot for error messages. - if (net.isIP(hostname)) { valid = ArrayPrototypeIncludes.call(ips, canonicalizeIP(hostname)); if (!valid) reason = `IP: ${hostname} is not in the cert's list: ` + ArrayPrototypeJoin.call(ips, ", "); @@ -178,7 +184,7 @@ function checkServerIdentity(hostname, cert) { // Match against Common Name only if no supported identifiers exist. const cn = subject.CN; - if (ArrayIsArray(cn)) valid = ArrayPrototypeSome.call(cn, wildcard); + if (Array.isArray(cn)) valid = ArrayPrototypeSome.call(cn, wildcard); else if (cn) valid = wildcard(cn); if (!valid) reason = `Host: ${hostname}. is not cert's CN: ${cn}`; @@ -186,7 +192,6 @@ function checkServerIdentity(hostname, cert) { } else { reason = "Cert does not contain a DNS name"; } - if (!valid) { let error = new Error(`ERR_TLS_CERT_ALTNAME_INVALID: Hostname/IP does not match certificate's altnames: ${reason}`); error.name = "ERR_TLS_CERT_ALTNAME_INVALID"; @@ -274,9 +279,8 @@ function translatePeerCertificate(c) { if (c.infoAccess != null) { const info = c.infoAccess; c.infoAccess = { __proto__: null }; - // XXX: More key validation? - RegExpPrototypeSymbolReplace(/([^\n:]*):([^\n]*)(?:\n|$)/g, info, (all, key, val) => { + RegExpPrototypeSymbolReplace.call(/([^\n:]*):([^\n]*)(?:\n|$)/g, info, (all, key, val) => { if (val.charCodeAt(0) === 0x22) { // The translatePeerCertificate function is only // used on internally created legacy certificate @@ -318,6 +322,8 @@ const TLSSocket = (function (InternalTLSSocket) { #secureContext; ALPNProtocols; #socket; + #checkServerIdentity; + #session; constructor(socket, options) { super(socket instanceof InternalTCPSocket ? options : options || socket); @@ -337,6 +343,8 @@ const TLSSocket = (function (InternalTLSSocket) { this.secureConnecting = true; this._secureEstablished = false; this._securePending = true; + this.#checkServerIdentity = options.checkServerIdentity || checkServerIdentity; + this.#session = options.session || null; } _secureEstablished = false; @@ -348,22 +356,76 @@ const TLSSocket = (function (InternalTLSSocket) { servername; authorized = false; authorizationError; + #renegotiationDisabled = false; encrypted = true; _start() { - // some frameworks uses this _start internal implementation is suposed to start TLS handshake - // on Bun we auto start this after on_open callback and when wrapping we start it after the socket is attached to the net.Socket/tls.Socket + // some frameworks uses this _start internal implementation is suposed to start TLS handshake/connect + this.connect(); } - exportKeyingMaterial(length, label, context) { - //SSL_export_keying_material + getSession() { + return this[bunSocketInternal]?.getSession(); + } + + getEphemeralKeyInfo() { + return this[bunSocketInternal]?.getEphemeralKeyInfo(); + } + + getCipher() { + return this[bunSocketInternal]?.getCipher(); + } + + getSharedSigalgs() { + return this[bunSocketInternal]?.getSharedSigalgs(); + } + + getProtocol() { + return this[bunSocketInternal]?.getTLSVersion(); + } + + getFinished() { + return this[bunSocketInternal]?.getTLSFinishedMessage() || undefined; + } + + getPeerFinished() { + return this[bunSocketInternal]?.getTLSPeerFinishedMessage() || undefined; + } + + isSessionReused() { + return !!this.#session; + } + + renegotiate() { + if (this.#renegotiationDisabled) { + const error = new Error("ERR_TLS_RENEGOTIATION_DISABLED: TLS session renegotiation disabled for this socket"); + error.name = "ERR_TLS_RENEGOTIATION_DISABLED"; + throw error; + } + throw Error("Not implented in Bun yet"); } + disableRenegotiation() { + this.#renegotiationDisabled = true; + } + getTLSTicket() { + return this[bunSocketInternal]?.getTLSTicket(); + } + exportKeyingMaterial(length, label, context) { + if (context) { + return this[bunSocketInternal]?.exportKeyingMaterial(length, label, context); + } + return this[bunSocketInternal]?.exportKeyingMaterial(length, label); + } + setMaxSendFragment(size) { - // SSL_set_max_send_fragment - throw Error("Not implented in Bun yet"); + return this[bunSocketInternal]?.setMaxSendFragment(size) || false; } + + // only for debug purposes so we just mock for now + enableTrace() {} + setServername(name) { if (this.isServer) { let error = new Error("ERR_TLS_SNI_FROM_SERVER: Cannot issue SNI from a TLS server-side socket"); @@ -374,25 +436,27 @@ const TLSSocket = (function (InternalTLSSocket) { this.servername = name; this[bunSocketInternal]?.setServername(name); } - setSession() { - throw Error("Not implented in Bun yet"); + setSession(session) { + this.#session = session; + if (typeof session === "string") session = Buffer.from(session, "latin1"); + return this[bunSocketInternal]?.setSession(session); } - getPeerCertificate() { - // need to implement peerCertificate on socket.zig - // const cert = this[bunSocketInternal]?.peerCertificate; - // if(cert) { - // return translatePeerCertificate(cert); - // } - throw Error("Not implented in Bun yet"); + getPeerCertificate(abbreviated) { + const cert = + arguments.length < 1 + ? this[bunSocketInternal]?.getPeerCertificate() + : this[bunSocketInternal]?.getPeerCertificate(abbreviated); + if (cert) { + return translatePeerCertificate(cert); + } } getCertificate() { // need to implement certificate on socket.zig - // const cert = this[bunSocketInternal]?.certificate; - // if(cert) { - // It's not a peer cert, but the formatting is identical. - // return translatePeerCertificate(cert); - // } - throw Error("Not implented in Bun yet"); + const cert = this[bunSocketInternal]?.getCertificate(); + if (cert) { + // It's not a peer cert, but the formatting is identical. + return translatePeerCertificate(cert); + } } getPeerX509Certificate() { throw Error("Not implented in Bun yet"); @@ -410,6 +474,8 @@ const TLSSocket = (function (InternalTLSSocket) { socket: this.#socket, ALPNProtocols: this.ALPNProtocols, serverName: this.servername || host || "localhost", + checkServerIdentity: this.#checkServerIdentity, + session: this.#session, ...this.#secureContext, }; } @@ -426,23 +492,11 @@ class Server extends NetServer { _requestCert; servername; ALPNProtocols; - #checkServerIdentity; constructor(options, secureConnectionListener) { super(options, secureConnectionListener); - this.#checkServerIdentity = options?.checkServerIdentity || checkServerIdentity; this.setSecureContext(options); } - emit(event, args) { - super.emit(event, args); - - if (event === "connection") { - // grabs secureConnect to emit secureConnection - args.once("secureConnect", () => { - super.emit("secureConnection", args); - }); - } - } setSecureContext(options) { if (options instanceof InternalSecureContext) { options = options.context; @@ -535,7 +589,6 @@ class Server extends NetServer { rejectUnauthorized: isClient ? false : this._rejectUnauthorized, requestCert: isClient ? false : this._requestCert, ALPNProtocols: this.ALPNProtocols, - checkServerIdentity: this.#checkServerIdentity, }, SocketClass, ]; |