aboutsummaryrefslogtreecommitdiff
path: root/src/js/node
diff options
context:
space:
mode:
authorGravatar Ciro Spaciari <ciro.spaciari@gmail.com> 2023-07-17 23:39:09 -0300
committerGravatar GitHub <noreply@github.com> 2023-07-17 19:39:09 -0700
commit13b54fbdb8cc36bbe027238654360f159ecaefbb (patch)
tree9f905b3520a01ba64cf10df0eefe2fe35c196648 /src/js/node
parent9273e29f0eba9c9d185a602d30def5a6b981ad55 (diff)
downloadbun-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.js86
-rw-r--r--src/js/node/tls.js135
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,
];