mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
711 lines
23 KiB
TypeScript
711 lines
23 KiB
TypeScript
// Hardcoded module "node:tls"
|
|
const { isArrayBufferView, isTypedArray } = require("node:util/types");
|
|
const net = require("node:net");
|
|
const { Duplex } = require("node:stream");
|
|
const [addServerName] = $zig("socket.zig", "createNodeTLSBinding");
|
|
const { throwNotImplemented } = require("internal/shared");
|
|
const { throwOnInvalidTLSArray } = require("internal/tls");
|
|
|
|
const { Server: NetServer, Socket: NetSocket } = net;
|
|
|
|
const { rootCertificates, canonicalizeIP } = $cpp("NodeTLS.cpp", "createNodeTLSBinding");
|
|
|
|
const SymbolReplace = Symbol.replace;
|
|
const RegExpPrototypeSymbolReplace = RegExp.prototype[SymbolReplace];
|
|
const RegExpPrototypeExec = RegExp.prototype.exec;
|
|
const ObjectAssign = Object.assign;
|
|
|
|
const StringPrototypeStartsWith = String.prototype.startsWith;
|
|
const StringPrototypeSlice = String.prototype.slice;
|
|
const StringPrototypeIncludes = String.prototype.includes;
|
|
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;
|
|
const ArrayPrototypeForEach = Array.prototype.forEach;
|
|
const ArrayPrototypePush = Array.prototype.push;
|
|
const ArrayPrototypeSome = Array.prototype.some;
|
|
const ArrayPrototypeReduce = Array.prototype.reduce;
|
|
function parseCertString() {
|
|
// Removed since JAN 2022 Node v18.0.0+ https://github.com/nodejs/node/pull/41479
|
|
throwNotImplemented("Not implemented");
|
|
}
|
|
|
|
const rejectUnauthorizedDefault =
|
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "false";
|
|
|
|
function unfqdn(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.$call(/[A-Z]/g, unfqdn(host), toLowerCase), ".");
|
|
}
|
|
|
|
function check(hostParts, pattern, wildcards) {
|
|
// Empty strings, null, undefined, etc. never match.
|
|
if (!pattern) return false;
|
|
|
|
const patternParts = splitHost(pattern);
|
|
|
|
if (hostParts.length !== patternParts.length) return false;
|
|
|
|
// Pattern has empty components, e.g. "bad..example.com".
|
|
if (ArrayPrototypeIncludes.$call(patternParts, "")) return false;
|
|
|
|
// RFC 6125 allows IDNA U-labels (Unicode) in names but we have no
|
|
// good way to detect their encoding or normalize them so we simply
|
|
// reject them. Control characters and blanks are rejected as well
|
|
// because nothing good can come from accepting them.
|
|
const isBad = s => RegExpPrototypeExec.$call(/[^\u0021-\u007F]/u, s) !== null;
|
|
if (ArrayPrototypeSome.$call(patternParts, isBad)) return false;
|
|
|
|
// Check host parts from right to left first.
|
|
for (let i = hostParts.length - 1; i > 0; i -= 1) {
|
|
if (hostParts[i] !== patternParts[i]) return false;
|
|
}
|
|
|
|
const hostSubdomain = hostParts[0];
|
|
const patternSubdomain = patternParts[0];
|
|
const patternSubdomainParts = StringPrototypeSplit.$call(patternSubdomain, "*");
|
|
|
|
// Short-circuit when the subdomain does not contain a wildcard.
|
|
// RFC 6125 does not allow wildcard substitution for components
|
|
// containing IDNA A-labels (Punycode) so match those verbatim.
|
|
if (patternSubdomainParts.length === 1 || StringPrototypeIncludes.$call(patternSubdomain, "xn--"))
|
|
return hostSubdomain === patternSubdomain;
|
|
|
|
if (!wildcards) return false;
|
|
|
|
// More than one wildcard is always wrong.
|
|
if (patternSubdomainParts.length > 2) return false;
|
|
|
|
// *.tld wildcards are not allowed.
|
|
if (patternParts.length <= 2) return false;
|
|
|
|
const { 0: prefix, 1: suffix } = patternSubdomainParts;
|
|
|
|
if (prefix.length + suffix.length > hostSubdomain.length) return false;
|
|
|
|
if (!StringPrototypeStartsWith.$call(hostSubdomain, prefix)) return false;
|
|
|
|
if (!StringPrototypeEndsWith.$call(hostSubdomain, suffix)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// This pattern is used to determine the length of escaped sequences within
|
|
// the subject alt names string. It allows any valid JSON string literal.
|
|
// This MUST match the JSON specification (ECMA-404 / RFC8259) exactly.
|
|
const jsonStringPattern =
|
|
// eslint-disable-next-line no-control-regex
|
|
/^"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"/;
|
|
|
|
function splitEscapedAltNames(altNames) {
|
|
const result = [];
|
|
let currentToken = "";
|
|
let offset = 0;
|
|
while (offset !== altNames.length) {
|
|
const nextSep = StringPrototypeIndexOf.$call(altNames, ", ", offset);
|
|
const nextQuote = StringPrototypeIndexOf.$call(altNames, '"', offset);
|
|
if (nextQuote !== -1 && (nextSep === -1 || nextQuote < nextSep)) {
|
|
// There is a quote character and there is no separator before the quote.
|
|
currentToken += StringPrototypeSubstring.$call(altNames, offset, nextQuote);
|
|
const match = RegExpPrototypeExec.$call(jsonStringPattern, StringPrototypeSubstring.$call(altNames, nextQuote));
|
|
if (!match) {
|
|
throw $ERR_TLS_CERT_ALTNAME_FORMAT();
|
|
}
|
|
currentToken += JSON.parse(match[0]);
|
|
offset = nextQuote + match[0].length;
|
|
} else if (nextSep !== -1) {
|
|
// There is a separator and no quote before it.
|
|
currentToken += StringPrototypeSubstring.$call(altNames, offset, nextSep);
|
|
ArrayPrototypePush.$call(result, currentToken);
|
|
currentToken = "";
|
|
offset = nextSep + 2;
|
|
} else {
|
|
currentToken += StringPrototypeSubstring.$call(altNames, offset);
|
|
offset = altNames.length;
|
|
}
|
|
}
|
|
ArrayPrototypePush.$call(result, currentToken);
|
|
return result;
|
|
}
|
|
|
|
function checkServerIdentity(hostname, cert) {
|
|
const subject = cert.subject;
|
|
const altNames = cert.subjectaltname;
|
|
const dnsNames = [];
|
|
const ips = [];
|
|
|
|
hostname = "" + hostname;
|
|
|
|
if (altNames) {
|
|
const splitAltNames = StringPrototypeIncludes.$call(altNames, '"')
|
|
? splitEscapedAltNames(altNames)
|
|
: StringPrototypeSplit.$call(altNames, ", ");
|
|
ArrayPrototypeForEach.$call(splitAltNames, name => {
|
|
if (StringPrototypeStartsWith.$call(name, "DNS:")) {
|
|
ArrayPrototypePush.$call(dnsNames, StringPrototypeSlice.$call(name, 4));
|
|
} else if (StringPrototypeStartsWith.$call(name, "IP Address:")) {
|
|
ArrayPrototypePush.$call(ips, canonicalizeIP(StringPrototypeSlice.$call(name, 11)));
|
|
}
|
|
});
|
|
}
|
|
|
|
let valid = false;
|
|
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, ", ");
|
|
} else if (dnsNames.length > 0 || subject?.CN) {
|
|
const hostParts = splitHost(hostname);
|
|
const wildcard = pattern => check(hostParts, pattern, true);
|
|
|
|
if (dnsNames.length > 0) {
|
|
valid = ArrayPrototypeSome.$call(dnsNames, wildcard);
|
|
if (!valid) reason = `Host: ${hostname}. is not in the cert's altnames: ${altNames}`;
|
|
} else {
|
|
// Match against Common Name only if no supported identifiers exist.
|
|
const cn = subject.CN;
|
|
|
|
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}`;
|
|
}
|
|
} else {
|
|
reason = "Cert does not contain a DNS name";
|
|
}
|
|
if (!valid) {
|
|
return $ERR_TLS_CERT_ALTNAME_INVALID(reason, hostname, cert);
|
|
}
|
|
}
|
|
|
|
var InternalSecureContext = class SecureContext {
|
|
context;
|
|
key;
|
|
cert;
|
|
ca;
|
|
passphrase;
|
|
servername;
|
|
secureOptions;
|
|
|
|
constructor(options) {
|
|
const context = {};
|
|
|
|
if (options) {
|
|
let cert = options.cert;
|
|
if (cert) {
|
|
throwOnInvalidTLSArray("options.cert", cert);
|
|
this.cert = cert;
|
|
}
|
|
|
|
let key = options.key;
|
|
if (key) {
|
|
throwOnInvalidTLSArray("options.key", key);
|
|
this.key = key;
|
|
}
|
|
|
|
let ca = options.ca;
|
|
if (ca) {
|
|
throwOnInvalidTLSArray("options.ca", ca);
|
|
this.ca = ca;
|
|
}
|
|
|
|
let passphrase = options.passphrase;
|
|
if (passphrase && typeof passphrase !== "string") {
|
|
throw new TypeError("passphrase argument must be an string");
|
|
}
|
|
this.passphrase = passphrase;
|
|
|
|
let servername = options.servername;
|
|
if (servername && typeof servername !== "string") {
|
|
throw new TypeError("servername argument must be an string");
|
|
}
|
|
this.servername = servername;
|
|
|
|
let secureOptions = options.secureOptions || 0;
|
|
if (secureOptions && typeof secureOptions !== "number") {
|
|
throw new TypeError("secureOptions argument must be an number");
|
|
}
|
|
this.secureOptions = secureOptions;
|
|
}
|
|
this.context = context;
|
|
}
|
|
};
|
|
|
|
function SecureContext(options) {
|
|
return new InternalSecureContext(options);
|
|
}
|
|
|
|
function createSecureContext(options) {
|
|
return new SecureContext(options);
|
|
}
|
|
|
|
// Translate some fields from the handle's C-friendly format into more idiomatic
|
|
// javascript object representations before passing them back to the user. Can
|
|
// be used on any cert object, but changing the name would be semver-major.
|
|
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::");
|
|
|
|
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;
|
|
|
|
const isNetSocketOrDuplex = socket instanceof Duplex;
|
|
|
|
options = isNetSocketOrDuplex ? { ...options, allowHalfOpen: false } : options || socket || {};
|
|
|
|
NetSocket.$call(this, options);
|
|
|
|
if (typeof options === "object") {
|
|
const { ALPNProtocols } = options;
|
|
if (ALPNProtocols) {
|
|
convertALPNProtocols(ALPNProtocols, this);
|
|
}
|
|
|
|
if (isNetSocketOrDuplex) {
|
|
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;
|
|
}
|
|
}
|
|
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);
|
|
|
|
TLSSocket.prototype._start = function _start() {
|
|
// some frameworks uses this _start internal implementation is suposed to start TLS handshake/connect
|
|
this.connect();
|
|
};
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
TLSSocket.prototype.disableRenegotiation = function disableRenegotiation() {
|
|
this[krenegotiationDisabled] = true;
|
|
// disable renegotiation on the socket
|
|
return this._handle?.disableRenegotiation?.();
|
|
};
|
|
|
|
TLSSocket.prototype.getTLSTicket = function getTLSTicket() {
|
|
return this._handle?.getTLSTicket?.();
|
|
};
|
|
|
|
TLSSocket.prototype.exportKeyingMaterial = function exportKeyingMaterial(length, label, context) {
|
|
if (context) {
|
|
return this._handle?.exportKeyingMaterial?.(length, label, context);
|
|
}
|
|
return this._handle?.exportKeyingMaterial?.(length, label);
|
|
};
|
|
|
|
TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(size) {
|
|
return this._handle?.setMaxSendFragment?.(size) || false;
|
|
};
|
|
|
|
TLSSocket.prototype.enableTrace = function enableTrace() {
|
|
// only for debug purposes so we just mock for now
|
|
};
|
|
|
|
TLSSocket.prototype.setServername = function setServername(name) {
|
|
if (this.isServer) {
|
|
throw $ERR_TLS_SNI_FROM_SERVER();
|
|
}
|
|
// 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);
|
|
};
|
|
|
|
TLSSocket.prototype.setSession = function setSession(session) {
|
|
this[ksession] = session;
|
|
if (typeof session === "string") session = Buffer.from(session, "latin1");
|
|
return this._handle?.setSession?.(session);
|
|
};
|
|
|
|
TLSSocket.prototype.getPeerCertificate = function getPeerCertificate(abbreviated) {
|
|
const cert =
|
|
arguments.length < 1 ? this._handle?.getPeerCertificate?.() : this._handle?.getPeerCertificate?.(abbreviated);
|
|
if (cert) {
|
|
return translatePeerCertificate(cert);
|
|
}
|
|
};
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
TLSSocket.prototype.getPeerX509Certificate = function getPeerX509Certificate() {
|
|
return this._handle?.getPeerX509Certificate?.();
|
|
};
|
|
|
|
TLSSocket.prototype.getX509Certificate = function getX509Certificate() {
|
|
return this._handle?.getX509Certificate?.();
|
|
};
|
|
|
|
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],
|
|
};
|
|
};
|
|
|
|
let CLIENT_RENEG_LIMIT = 3,
|
|
CLIENT_RENEG_WINDOW = 600;
|
|
|
|
function Server(options, secureConnectionListener): void {
|
|
if (!(this instanceof Server)) {
|
|
return new Server(options, secureConnectionListener);
|
|
}
|
|
|
|
NetServer.$apply(this, [options, secureConnectionListener]);
|
|
|
|
this.key = undefined;
|
|
this.cert = undefined;
|
|
this.ca = undefined;
|
|
this.passphrase = undefined;
|
|
this.secureOptions = undefined;
|
|
this._rejectUnauthorized = rejectUnauthorizedDefault;
|
|
this._requestCert = undefined;
|
|
this.servername = undefined;
|
|
this.ALPNProtocols = undefined;
|
|
|
|
let contexts: Map<string, typeof InternalSecureContext> | null = null;
|
|
|
|
this.addContext = function (hostname, context) {
|
|
if (typeof hostname !== "string") {
|
|
throw new TypeError("hostname must be a string");
|
|
}
|
|
if (!(context instanceof InternalSecureContext)) {
|
|
context = createSecureContext(context);
|
|
}
|
|
if (this._handle) {
|
|
addServerName(this._handle, hostname, context);
|
|
} else {
|
|
if (!contexts) contexts = new Map();
|
|
contexts.set(hostname, context);
|
|
}
|
|
};
|
|
|
|
this.setSecureContext = function (options) {
|
|
if (options instanceof InternalSecureContext) {
|
|
options = options.context;
|
|
}
|
|
if (options) {
|
|
const { ALPNProtocols } = options;
|
|
|
|
if (ALPNProtocols) {
|
|
convertALPNProtocols(ALPNProtocols, this);
|
|
}
|
|
|
|
let cert = options.cert;
|
|
if (cert) {
|
|
throwOnInvalidTLSArray("options.cert", cert);
|
|
this.cert = cert;
|
|
}
|
|
|
|
let key = options.key;
|
|
if (key) {
|
|
throwOnInvalidTLSArray("options.key", key);
|
|
this.key = key;
|
|
}
|
|
|
|
let ca = options.ca;
|
|
if (ca) {
|
|
throwOnInvalidTLSArray("options.ca", ca);
|
|
this.ca = ca;
|
|
}
|
|
|
|
let passphrase = options.passphrase;
|
|
if (passphrase && typeof passphrase !== "string") {
|
|
throw $ERR_INVALID_ARG_TYPE("options.passphrase", "string", passphrase);
|
|
}
|
|
this.passphrase = passphrase;
|
|
|
|
let servername = options.servername;
|
|
if (servername && typeof servername !== "string") {
|
|
throw $ERR_INVALID_ARG_TYPE("options.servername", "string", servername);
|
|
}
|
|
this.servername = servername;
|
|
|
|
let secureOptions = options.secureOptions || 0;
|
|
if (secureOptions && typeof secureOptions !== "number") {
|
|
throw $ERR_INVALID_ARG_TYPE("options.secureOptions", "number", secureOptions);
|
|
}
|
|
this.secureOptions = secureOptions;
|
|
|
|
const requestCert = options.requestCert || false;
|
|
|
|
if (requestCert) this._requestCert = requestCert;
|
|
else this._requestCert = undefined;
|
|
|
|
const rejectUnauthorized = options.rejectUnauthorized;
|
|
|
|
if (typeof rejectUnauthorized !== "undefined") {
|
|
this._rejectUnauthorized = rejectUnauthorized;
|
|
} else this._rejectUnauthorized = rejectUnauthorizedDefault;
|
|
}
|
|
};
|
|
|
|
Server.prototype.getTicketKeys = function () {
|
|
throw Error("Not implented in Bun yet");
|
|
};
|
|
|
|
Server.prototype.setTicketKeys = function () {
|
|
throw Error("Not implented in Bun yet");
|
|
};
|
|
|
|
this[buntls] = function (port, host, isClient) {
|
|
return [
|
|
{
|
|
serverName: this.servername || host || "localhost",
|
|
key: this.key,
|
|
cert: this.cert,
|
|
ca: this.ca,
|
|
passphrase: this.passphrase,
|
|
secureOptions: this.secureOptions,
|
|
rejectUnauthorized: this._rejectUnauthorized,
|
|
requestCert: isClient ? true : this._requestCert,
|
|
ALPNProtocols: this.ALPNProtocols,
|
|
clientRenegotiationLimit: CLIENT_RENEG_LIMIT,
|
|
clientRenegotiationWindow: CLIENT_RENEG_WINDOW,
|
|
contexts: contexts,
|
|
},
|
|
TLSSocket,
|
|
];
|
|
};
|
|
|
|
this.setSecureContext(options);
|
|
}
|
|
$toClass(Server, "Server", NetServer);
|
|
|
|
function createServer(options, connectionListener) {
|
|
return new Server(options, connectionListener);
|
|
}
|
|
const DEFAULT_ECDH_CURVE = "auto",
|
|
// https://github.com/Jarred-Sumner/uSockets/blob/fafc241e8664243fc0c51d69684d5d02b9805134/src/crypto/openssl.c#L519-L523
|
|
DEFAULT_CIPHERS =
|
|
"DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256",
|
|
DEFAULT_MIN_VERSION = "TLSv1.2",
|
|
DEFAULT_MAX_VERSION = "TLSv1.3";
|
|
|
|
function normalizeConnectArgs(listArgs) {
|
|
const args = net._normalizeArgs(listArgs);
|
|
$assert($isObject(args[0]));
|
|
|
|
// If args[0] was options, then normalize dealt with it.
|
|
// If args[0] is port, or args[0], args[1] is host, port, we need to
|
|
// find the options and merge them in, normalize's options has only
|
|
// the host/port/path args that it knows about, not the tls options.
|
|
// This means that options.host overrides a host arg.
|
|
if (listArgs[1] !== null && typeof listArgs[1] === "object") {
|
|
ObjectAssign(args[0], listArgs[1]);
|
|
} else if (listArgs[2] !== null && typeof listArgs[2] === "object") {
|
|
ObjectAssign(args[0], listArgs[2]);
|
|
}
|
|
|
|
return args;
|
|
}
|
|
|
|
// tls.connect(options[, callback])
|
|
// tls.connect(path[, options][, callback])
|
|
// tls.connect(port[, host][, options][, callback])
|
|
function connect(...args) {
|
|
let normal = normalizeConnectArgs(args);
|
|
const options = normal[0];
|
|
const { ALPNProtocols } = options;
|
|
if (ALPNProtocols) {
|
|
convertALPNProtocols(ALPNProtocols, options);
|
|
}
|
|
return new TLSSocket(options).connect(normal);
|
|
}
|
|
|
|
function getCiphers() {
|
|
return DEFAULT_CIPHERS.split(":");
|
|
}
|
|
|
|
// Convert protocols array into valid OpenSSL protocols list
|
|
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
|
|
function convertProtocols(protocols) {
|
|
const lens = new Array(protocols.length);
|
|
const buff = Buffer.allocUnsafe(
|
|
ArrayPrototypeReduce.$call(
|
|
protocols,
|
|
(p, c, i) => {
|
|
const len = Buffer.byteLength(c);
|
|
if (len > 255) {
|
|
throw new RangeError(
|
|
`The byte length of the protocol at index ${i} exceeds the maximum length. It must be <= 255. Received ${len}`,
|
|
);
|
|
}
|
|
lens[i] = len;
|
|
return p + 1 + len;
|
|
},
|
|
0,
|
|
),
|
|
);
|
|
|
|
let offset = 0;
|
|
for (let i = 0, c = protocols.length; i < c; i++) {
|
|
buff[offset++] = lens[i];
|
|
buff.write(protocols[i], offset);
|
|
offset += lens[i];
|
|
}
|
|
|
|
return buff;
|
|
}
|
|
|
|
function convertALPNProtocols(protocols, out) {
|
|
// If protocols is Array - translate it into buffer
|
|
if (Array.isArray(protocols)) {
|
|
out.ALPNProtocols = convertProtocols(protocols);
|
|
} else if (isTypedArray(protocols)) {
|
|
// Copy new buffer not to be modified by user.
|
|
out.ALPNProtocols = Buffer.from(protocols);
|
|
} else if (isArrayBufferView(protocols)) {
|
|
out.ALPNProtocols = Buffer.from(
|
|
protocols.buffer.slice(protocols.byteOffset, protocols.byteOffset + protocols.byteLength),
|
|
);
|
|
} else if (Buffer.isBuffer(protocols)) {
|
|
out.ALPNProtocols = protocols;
|
|
}
|
|
}
|
|
|
|
export default {
|
|
CLIENT_RENEG_LIMIT,
|
|
CLIENT_RENEG_WINDOW,
|
|
connect,
|
|
convertALPNProtocols,
|
|
createSecureContext,
|
|
createServer,
|
|
DEFAULT_CIPHERS,
|
|
DEFAULT_ECDH_CURVE,
|
|
DEFAULT_MAX_VERSION,
|
|
DEFAULT_MIN_VERSION,
|
|
getCiphers,
|
|
parseCertString,
|
|
SecureContext,
|
|
Server,
|
|
TLSSocket,
|
|
checkServerIdentity,
|
|
rootCertificates,
|
|
} as any as typeof import("node:tls");
|