UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

1,809 lines 51.9 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/_tls_wrap.js

import {
  assertCrypto,
  deprecate,
  kEmptyObject,
} from "nstdlib/lib/internal/util";
import { setImmediate } from "nstdlib/lib/timers";
import * as assert from "nstdlib/lib/internal/assert";
import * as crypto from "nstdlib/lib/crypto";
import * as EE from "nstdlib/lib/events";
import * as net from "nstdlib/lib/net";
import * as tls from "nstdlib/lib/tls";
import * as common from "nstdlib/lib/_tls_common";
import { kReinitializeHandle } from "nstdlib/lib/internal/net";
import * as JSStreamSocket from "nstdlib/lib/internal/js_stream_socket";
import { Buffer } from "nstdlib/lib/buffer";
import { TCP, constants as TCPConstants } from "nstdlib/stub/binding/tcp_wrap";
import tls_wrap from "nstdlib/stub/binding/tls_wrap";
import {
  Pipe,
  constants as PipeConstants,
} from "nstdlib/stub/binding/pipe_wrap";
import { symbols as __symbols__ } from "nstdlib/lib/internal/async_hooks";
import { isArrayBufferView } from "nstdlib/lib/internal/util/types";
import { SecureContext as NativeSecureContext } from "nstdlib/stub/binding/crypto";
import {
  ConnResetException,
  codes as __codes__,
} from "nstdlib/lib/internal/errors";
import { onpskexchange as kOnPskExchange } from "nstdlib/stub/binding/symbols";
import {
  getOptionValue,
  getAllowUnauthorized,
} from "nstdlib/lib/internal/options";
import {
  validateBoolean,
  validateBuffer,
  validateFunction,
  validateInt32,
  validateNumber,
  validateObject,
  validateString,
  validateUint32,
} from "nstdlib/lib/internal/validators";
import { InternalX509Certificate } from "nstdlib/lib/internal/crypto/x509";
import { appendFile } from "nstdlib/lib/fs";
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

assertCrypto();

const { owner_symbol } = __symbols__;

const {
  ERR_INVALID_ARG_TYPE,
  ERR_INVALID_ARG_VALUE,
  ERR_MULTIPLE_CALLBACK,
  ERR_SOCKET_CLOSED,
  ERR_TLS_ALPN_CALLBACK_INVALID_RESULT,
  ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS,
  ERR_TLS_DH_PARAM_SIZE,
  ERR_TLS_HANDSHAKE_TIMEOUT,
  ERR_TLS_INVALID_CONTEXT,
  ERR_TLS_INVALID_STATE,
  ERR_TLS_RENEGOTIATION_DISABLED,
  ERR_TLS_REQUIRED_SERVER_NAME,
  ERR_TLS_SESSION_ATTACK,
  ERR_TLS_SNI_FROM_SERVER,
} = __codes__;
const traceTls = getOptionValue("--trace-tls");
const tlsKeylog = getOptionValue("--tls-keylog");
const kConnectOptions = Symbol("connect-options");
const kDisableRenegotiation = Symbol("disable-renegotiation");
const kErrorEmitted = Symbol("error-emitted");
const kHandshakeTimeout = Symbol("handshake-timeout");
const kRes = Symbol("res");
const kSNICallback = Symbol("snicallback");
const kALPNCallback = Symbol("alpncallback");
const kEnableTrace = Symbol("enableTrace");
const kPskCallback = Symbol("pskcallback");
const kPskIdentityHint = Symbol("pskidentityhint");
const kPendingSession = Symbol("pendingSession");
const kIsVerified = Symbol("verified");

const noop = () => {};

let ipServernameWarned = false;
let tlsTracingWarned = false;

// Server side times how long a handshake is taking to protect against slow
// handshakes being used for DoS.
function onhandshakestart(now) {
  {
    /* debug */
  }

  const { lastHandshakeTime } = this;
  assert(
    now >= lastHandshakeTime,
    `now (${now}) < lastHandshakeTime (${lastHandshakeTime})`,
  );

  this.lastHandshakeTime = now;

  // If this is the first handshake we can skip the rest of the checks.
  if (lastHandshakeTime === 0) return;

  if (now - lastHandshakeTime >= tls.CLIENT_RENEG_WINDOW * 1000)
    this.handshakes = 1;
  else this.handshakes++;

  const owner = this[owner_symbol];

  assert(owner._tlsOptions.isServer);

  if (this.handshakes > tls.CLIENT_RENEG_LIMIT) {
    owner._emitTLSError(new ERR_TLS_SESSION_ATTACK());
    return;
  }

  if (owner[kDisableRenegotiation])
    owner._emitTLSError(new ERR_TLS_RENEGOTIATION_DISABLED());
}

function onhandshakedone() {
  {
    /* debug */
  }

  const owner = this[owner_symbol];
  assert(owner._tlsOptions.isServer);

  // `newSession` callback wasn't called yet
  if (owner._newSessionPending) {
    owner._securePending = true;
    return;
  }

  owner._finishInit();
}

function loadSession(hello) {
  {
    /* debug */
  }
  const owner = this[owner_symbol];

  let once = false;
  function onSession(err, session) {
    {
      /* debug */
    }
    if (once) return owner.destroy(new ERR_MULTIPLE_CALLBACK());
    once = true;

    if (err) return owner.destroy(err);

    if (owner._handle === null) return owner.destroy(new ERR_SOCKET_CLOSED());

    owner._handle.loadSession(session);
    // Session is loaded. End the parser to allow handshaking to continue.
    owner._handle.endParser();
  }

  if (
    hello.sessionId.length <= 0 ||
    hello.tlsTicket ||
    (owner.server &&
      !owner.server.emit("resumeSession", hello.sessionId, onSession))
  ) {
    // Sessions without identifiers can't be resumed.
    // Sessions with tickets can be resumed directly from the ticket, no server
    // session storage is necessary.
    // Without a call to a resumeSession listener, a session will never be
    // loaded, so end the parser to allow handshaking to continue.
    owner._handle.endParser();
  }
}

function loadSNI(info) {
  const owner = this[owner_symbol];
  const servername = info.servername;
  if (!servername || !owner._SNICallback) return requestOCSP(owner, info);

  let once = false;
  owner._SNICallback(servername, (err, context) => {
    if (once) return owner.destroy(new ERR_MULTIPLE_CALLBACK());
    once = true;

    if (err) return owner.destroy(err);

    if (owner._handle === null) return owner.destroy(new ERR_SOCKET_CLOSED());

    // TODO(indutny): eventually disallow raw `SecureContext`
    if (context) owner._handle.sni_context = context.context || context;

    requestOCSP(owner, info);
  });
}

function callALPNCallback(protocolsBuffer) {
  const handle = this;
  const socket = handle[owner_symbol];

  const servername = handle.getServername();

  // Collect all the protocols from the given buffer:
  const protocols = [];
  let offset = 0;
  while (offset < protocolsBuffer.length) {
    const protocolLen = protocolsBuffer[offset];
    offset += 1;

    const protocol = protocolsBuffer.slice(offset, offset + protocolLen);
    offset += protocolLen;

    protocols.push(protocol.toString("ascii"));
  }

  const selectedProtocol = socket[kALPNCallback]({
    servername,
    protocols,
  });

  // Undefined -> all proposed protocols rejected
  if (selectedProtocol === undefined) return undefined;

  const protocolIndex = protocols.indexOf(selectedProtocol);
  if (protocolIndex === -1) {
    throw new ERR_TLS_ALPN_CALLBACK_INVALID_RESULT(selectedProtocol, protocols);
  }
  let protocolOffset = 0;
  for (let i = 0; i < protocolIndex; i++) {
    protocolOffset += 1 + protocols[i].length;
  }

  return protocolOffset;
}

function requestOCSP(socket, info) {
  if (!info.OCSPRequest || !socket.server) return requestOCSPDone(socket);

  let ctx = socket._handle.sni_context;

  if (!ctx) {
    ctx = socket.server._sharedCreds;

    // TLS socket is using a `net.Server` instead of a tls.TLSServer.
    // Some TLS properties like `server._sharedCreds` will not be present
    if (!ctx) return requestOCSPDone(socket);
  }

  // TODO(indutny): eventually disallow raw `SecureContext`
  if (ctx.context) ctx = ctx.context;

  if (socket.server.listenerCount("OCSPRequest") === 0) {
    return requestOCSPDone(socket);
  }

  let once = false;
  const onOCSP = (err, response) => {
    {
      /* debug */
    }
    if (once) return socket.destroy(new ERR_MULTIPLE_CALLBACK());
    once = true;

    if (err) return socket.destroy(err);

    if (socket._handle === null) return socket.destroy(new ERR_SOCKET_CLOSED());

    if (response) socket._handle.setOCSPResponse(response);
    requestOCSPDone(socket);
  };

  {
    /* debug */
  }
  socket.server.emit(
    "OCSPRequest",
    ctx.getCertificate(),
    ctx.getIssuer(),
    onOCSP,
  );
}

function requestOCSPDone(socket) {
  {
    /* debug */
  }
  try {
    socket._handle.certCbDone();
  } catch (e) {
    {
      /* debug */
    }
    socket.destroy(e);
  }
}

function onnewsessionclient(sessionId, session) {
  {
    /* debug */
  }
  const owner = this[owner_symbol];
  if (owner[kIsVerified]) {
    owner.emit("session", session);
  } else {
    owner[kPendingSession] = session;
  }
}

function onnewsession(sessionId, session) {
  {
    /* debug */
  }
  const owner = this[owner_symbol];

  // TODO(@sam-github) no server to emit the event on, but handshake won't
  // continue unless newSessionDone() is called, should it be, or is that
  // situation unreachable, or only occurring during shutdown?
  if (!owner.server) return;

  let once = false;
  const done = () => {
    {
      /* debug */
    }
    if (once) return;
    once = true;

    if (owner._handle === null) return owner.destroy(new ERR_SOCKET_CLOSED());

    this.newSessionDone();

    owner._newSessionPending = false;
    if (owner._securePending) owner._finishInit();
    owner._securePending = false;
  };

  owner._newSessionPending = true;
  if (!owner.server.emit("newSession", sessionId, session, done)) done();
}

function onPskServerCallback(identity, maxPskLen) {
  const owner = this[owner_symbol];
  const ret = owner[kPskCallback](owner, identity);
  if (ret == null) return undefined;

  let psk;
  if (isArrayBufferView(ret)) {
    psk = ret;
  } else {
    if (typeof ret !== "object") {
      throw new ERR_INVALID_ARG_TYPE(
        "ret",
        ["Object", "Buffer", "TypedArray", "DataView"],
        ret,
      );
    }
    psk = ret.psk;
    validateBuffer(psk, "psk");
  }

  if (psk.length > maxPskLen) {
    throw new ERR_INVALID_ARG_VALUE(
      "psk",
      psk,
      `Pre-shared key exceeds ${maxPskLen} bytes`,
    );
  }

  return psk;
}

function onPskClientCallback(hint, maxPskLen, maxIdentityLen) {
  const owner = this[owner_symbol];
  const ret = owner[kPskCallback](hint);
  if (ret == null) return undefined;

  validateObject(ret, "ret");

  validateBuffer(ret.psk, "psk");
  if (ret.psk.length > maxPskLen) {
    throw new ERR_INVALID_ARG_VALUE(
      "psk",
      ret.psk,
      `Pre-shared key exceeds ${maxPskLen} bytes`,
    );
  }

  validateString(ret.identity, "identity");
  if (Buffer.byteLength(ret.identity) > maxIdentityLen) {
    throw new ERR_INVALID_ARG_VALUE(
      "identity",
      ret.identity,
      `PSK identity exceeds ${maxIdentityLen} bytes`,
    );
  }

  return { psk: ret.psk, identity: ret.identity };
}

function onkeylog(line) {
  {
    /* debug */
  }
  this[owner_symbol].emit("keylog", line);
}

function onocspresponse(resp) {
  {
    /* debug */
  }
  this[owner_symbol].emit("OCSPResponse", resp);
}

function onerror(err) {
  const owner = this[owner_symbol];
  {
    /* debug */
  }

  if (owner._hadError) return;

  owner._hadError = true;

  // Destroy socket if error happened before handshake's finish
  if (!owner._secureEstablished) {
    // When handshake fails control is not yet released,
    // so self._tlsError will return null instead of actual error

    // Set closing the socket after emitting an event since the socket needs to
    // be accessible when the `tlsClientError` event is emitted.
    owner._closeAfterHandlingError = true;
    owner.destroy(err);
  } else if (
    owner._tlsOptions?.isServer &&
    owner._rejectUnauthorized &&
    /peer did not return a certificate/.test(err.message)
  ) {
    // Ignore server's authorization errors
    owner.destroy();
  } else {
    // Emit error
    owner._emitTLSError(err);
  }
}

// Used by both client and server TLSSockets to start data flowing from _handle,
// read(0) causes a StreamBase::ReadStart, via Socket._read.
function initRead(tlsSocket, socket) {
  {
    /* debug */
  }
  // If we were destroyed already don't bother reading
  if (!tlsSocket._handle) return;

  // Socket already has some buffered data - emulate receiving it
  if (socket && socket.readableLength) {
    let buf;
    while ((buf = socket.read()) !== null) tlsSocket._handle.receive(buf);
  }

  tlsSocket.read(0);
}

/**
 * Provides a wrap of socket stream to do encrypted communication.
 */

function TLSSocket(socket, opts) {
  const tlsOptions = { ...opts };
  let enableTrace = tlsOptions.enableTrace;

  if (enableTrace == null) {
    enableTrace = traceTls;

    if (enableTrace && !tlsTracingWarned) {
      tlsTracingWarned = true;
      process.emitWarning(
        "Enabling --trace-tls can expose sensitive data in " +
          "the resulting log.",
      );
    }
  } else {
    validateBoolean(enableTrace, "options.enableTrace");
  }

  if (tlsOptions.ALPNProtocols)
    tls.convertALPNProtocols(tlsOptions.ALPNProtocols, tlsOptions);

  this._tlsOptions = tlsOptions;
  this._secureEstablished = false;
  this._securePending = false;
  this._newSessionPending = false;
  this._controlReleased = false;
  this.secureConnecting = true;
  this._SNICallback = null;
  this[kALPNCallback] = null;
  this.servername = null;
  this.alpnProtocol = null;
  this.authorized = false;
  this.authorizationError = null;
  this[kRes] = null;
  this[kIsVerified] = false;
  this[kPendingSession] = null;

  let wrap;
  let handle;
  let wrapHasActiveWriteFromPrevOwner;

  if (socket) {
    if (socket instanceof net.Socket && socket._handle) {
      // 1. connected socket
      wrap = socket;
    } else {
      // 2. socket has no handle so it is js not c++
      // 3. unconnected sockets are wrapped
      // TLS expects to interact from C++ with a net.Socket that has a C++ stream
      // handle, but a JS stream doesn't have one. Wrap it up to make it look like
      // a socket.
      wrap = new JSStreamSocket(socket);
    }

    handle = wrap._handle;
    wrapHasActiveWriteFromPrevOwner = wrap.writableLength > 0;
  } else {
    // 4. no socket, one will be created with net.Socket().connect
    wrap = null;
    wrapHasActiveWriteFromPrevOwner = false;
  }

  // Just a documented property to make secure sockets
  // distinguishable from regular ones.
  this.encrypted = true;

  ReflectApply(net.Socket, this, [
    {
      handle: this._wrapHandle(wrap, handle, wrapHasActiveWriteFromPrevOwner),
      allowHalfOpen: socket ? socket.allowHalfOpen : tlsOptions.allowHalfOpen,
      pauseOnCreate: tlsOptions.pauseOnConnect,
      manualStart: true,
      highWaterMark: tlsOptions.highWaterMark,
      onread: !socket ? tlsOptions.onread : null,
      signal: tlsOptions.signal,
    },
  ]);

  // Proxy for API compatibility
  this.ssl = this._handle; // C++ TLSWrap object

  this.on("error", this._tlsError);

  this._init(socket, wrap);

  if (enableTrace && this._handle) this._handle.enableTrace();

  if (wrapHasActiveWriteFromPrevOwner) {
    // `wrap` is a streams.Writable in JS. This empty write will be queued
    // and hence finish after all existing writes, which is the timing
    // we want to start to send any tls data to `wrap`.
    wrap.write("", (err) => {
      if (err) {
        {
          /* debug */
        }
        this.destroy(err);
        return;
      }

      this._handle.writesIssuedByPrevListenerDone();
    });
  }

  // Read on next tick so the caller has a chance to setup listeners
  process.nextTick(initRead, this, socket);
}
Object.setPrototypeOf(TLSSocket.prototype, net.Socket.prototype);
Object.setPrototypeOf(TLSSocket, net.Socket);
export { TLSSocket };

const proxiedMethods = [
  "ref",
  "unref",
  "open",
  "bind",
  "listen",
  "connect",
  "bind6",
  "connect6",
  "getsockname",
  "getpeername",
  "setNoDelay",
  "setKeepAlive",
  "setSimultaneousAccepts",
  "setBlocking",

  // PipeWrap
  "setPendingInstances",
];

// Proxy HandleWrap, PipeWrap and TCPWrap methods
function makeMethodProxy(name) {
  return function methodProxy(...args) {
    if (this._parent[name])
      return ReflectApply(this._parent[name], this._parent, args);
  };
}
for (const proxiedMethod of proxiedMethods) {
  tls_wrap.TLSWrap.prototype[proxiedMethod] = makeMethodProxy(proxiedMethod);
}

tls_wrap.TLSWrap.prototype.close = function close(cb) {
  let ssl;
  if (this[owner_symbol]) {
    ssl = this[owner_symbol].ssl;
    this[owner_symbol].ssl = null;
  }

  // Invoke `destroySSL` on close to clean up possibly pending write requests
  // that may self-reference TLSWrap, leading to leak
  const done = () => {
    if (ssl) {
      ssl.destroySSL();
      if (ssl._secureContext.singleUse) {
        ssl._secureContext.context.close();
        ssl._secureContext.context = null;
      }
    }
    if (cb) cb();
  };

  if (this._parentWrap) {
    if (this._parentWrap._handle === null) {
      // The socket handle was already closed.
      done();
      return;
    }

    if (this._parentWrap._handle === this._parent) {
      this._parentWrap.once("close", done);
      this._parentWrap.destroy();
      return;
    }
  }

  return this._parent.close(done);
};

TLSSocket.prototype.disableRenegotiation = function disableRenegotiation() {
  this[kDisableRenegotiation] = true;
};

/**
 *
 * @param {null|net.Socket} wrap
 * @param {null|object} handle
 * @param {boolean} wrapHasActiveWriteFromPrevOwner
 * @returns {object}
 */
TLSSocket.prototype._wrapHandle = function (
  wrap,
  handle,
  wrapHasActiveWriteFromPrevOwner,
) {
  const options = this._tlsOptions;
  if (!handle) {
    handle = options.pipe
      ? new Pipe(PipeConstants.SOCKET)
      : new TCP(TCPConstants.SOCKET);
    handle[owner_symbol] = this;
  }

  // Wrap socket's handle
  const context =
    options.secureContext ||
    options.credentials ||
    tls.createSecureContext(options);
  assert(handle.isStreamBase, "handle must be a StreamBase");
  if (!(context.context instanceof NativeSecureContext)) {
    throw new ERR_TLS_INVALID_CONTEXT("context");
  }

  const res = tls_wrap.wrap(
    handle,
    context.context,
    !!options.isServer,
    wrapHasActiveWriteFromPrevOwner,
  );
  res._parent = handle; // C++ "wrap" object: TCPWrap, JSStream, ...
  res._parentWrap = wrap; // JS object: net.Socket, JSStreamSocket, ...
  res._secureContext = context;
  res.reading = handle.reading;
  this[kRes] = res;
  defineHandleReading(this, handle);

  // Guard against adding multiple listeners, as this method may be called
  // repeatedly on the same socket by reinitializeHandle
  if (this.listenerCount("close", onSocketCloseDestroySSL) === 0) {
    this.on("close", onSocketCloseDestroySSL);
  }

  if (wrap) {
    wrap.on("close", () => this.destroy());
  }

  return res;
};

TLSSocket.prototype[kReinitializeHandle] = function reinitializeHandle(handle) {
  const originalServername = this.ssl ? this._handle.getServername() : null;
  const originalSession = this.ssl ? this._handle.getSession() : null;

  this.handle = this._wrapHandle(null, handle, false);
  this.ssl = this._handle;

  net.Socket.prototype[kReinitializeHandle].call(this, this.handle);
  this._init();

  if (this._tlsOptions.enableTrace) {
    this._handle.enableTrace();
  }

  if (originalSession) {
    this.setSession(originalSession);
  }

  if (originalServername) {
    this.setServername(originalServername);
  }
};

// This eliminates a cyclic reference to TLSWrap
// Ref: https://github.com/nodejs/node/commit/f7620fb96d339f704932f9bb9a0dceb9952df2d4
function defineHandleReading(socket, handle) {
  Object.defineProperty(handle, "reading", {
    __proto__: null,
    get: () => {
      return socket[kRes].reading;
    },
    set: (value) => {
      socket[kRes].reading = value;
    },
  });
}

function onSocketCloseDestroySSL() {
  // Make sure we are not doing it on OpenSSL's stack
  setImmediate(destroySSL, this);
  this[kRes] = null;
}

function destroySSL(self) {
  self._destroySSL();
}

TLSSocket.prototype._destroySSL = function _destroySSL() {
  if (!this.ssl) return;
  this.ssl.destroySSL();
  if (this.ssl._secureContext.singleUse) {
    this.ssl._secureContext.context.close();
    this.ssl._secureContext.context = null;
  }
  this.ssl = null;
  this[kPendingSession] = null;
  this[kIsVerified] = false;
};

function keylogNewListener(event) {
  if (event !== "keylog") return;

  // Guard against enableKeylogCallback after destroy
  if (!this._handle) return;
  this._handle.enableKeylogCallback();

  // Remove this listener since it's no longer needed.
  this.removeListener("newListener", keylogNewListener);
}

function newListener(event) {
  if (event !== "session") return;

  // Guard against enableSessionCallbacks after destroy
  if (!this._handle) return;
  this._handle.enableSessionCallbacks();

  // Remove this listener since it's no longer needed.
  this.removeListener("newListener", newListener);
}

// Constructor guts, arbitrarily factored out.
let warnOnTlsKeylog = true;
let warnOnTlsKeylogError = true;
TLSSocket.prototype._init = function (socket, wrap) {
  const options = this._tlsOptions;
  const ssl = this._handle;
  this.server = options.server;

  {
    /* debug */
  }

  // Clients (!isServer) always request a cert, servers request a client cert
  // only on explicit configuration.
  const requestCert = !!options.requestCert || !options.isServer;
  const rejectUnauthorized = !!options.rejectUnauthorized;

  this._requestCert = requestCert;
  this._rejectUnauthorized = rejectUnauthorized;
  if (requestCert || rejectUnauthorized)
    ssl.setVerifyMode(requestCert, rejectUnauthorized);

  // Only call .onkeylog if there is a keylog listener.
  ssl.onkeylog = onkeylog;

  if (this.listenerCount("newListener", keylogNewListener) === 0) {
    this.on("newListener", keylogNewListener);
  }

  if (options.isServer) {
    ssl.onhandshakestart = onhandshakestart;
    ssl.onhandshakedone = onhandshakedone;
    ssl.onclienthello = loadSession;
    ssl.oncertcb = loadSNI;
    ssl.onnewsession = onnewsession;
    ssl.lastHandshakeTime = 0;
    ssl.handshakes = 0;

    if (options.ALPNCallback) {
      validateFunction(options.ALPNCallback, "options.ALPNCallback");
      this[kALPNCallback] = options.ALPNCallback;
      ssl.ALPNCallback = callALPNCallback;
      ssl.enableALPNCb();
    }

    if (this.server) {
      if (
        this.server.listenerCount("resumeSession") > 0 ||
        this.server.listenerCount("newSession") > 0
      ) {
        // Also starts the client hello parser as a side effect.
        ssl.enableSessionCallbacks();
      }
      if (this.server.listenerCount("OCSPRequest") > 0) ssl.enableCertCb();
    }
  } else {
    ssl.onhandshakestart = noop;
    ssl.onhandshakedone = () => {
      {
        /* debug */
      }
      this._finishInit();
    };
    ssl.onocspresponse = onocspresponse;

    if (options.session) ssl.setSession(options.session);

    ssl.onnewsession = onnewsessionclient;

    // Only call .onnewsession if there is a session listener.
    if (this.listenerCount("newListener", newListener) === 0) {
      this.on("newListener", newListener);
    }
  }

  if (tlsKeylog) {
    if (warnOnTlsKeylog) {
      warnOnTlsKeylog = false;
      process.emitWarning(
        "Using --tls-keylog makes TLS connections insecure " +
          "by writing secret key material to file " +
          tlsKeylog,
      );
    }
    this.on("keylog", (line) => {
      appendFile(tlsKeylog, line, { mode: 0o600 }, (err) => {
        if (err && warnOnTlsKeylogError) {
          warnOnTlsKeylogError = false;
          process.emitWarning(
            "Failed to write TLS keylog (this warning " +
              "will not be repeated): " +
              err,
          );
        }
      });
    });
  }

  ssl.onerror = onerror;

  // If custom SNICallback was given, or if
  // there're SNI contexts to perform match against -
  // set `.onsniselect` callback.
  if (
    options.isServer &&
    options.SNICallback &&
    (options.SNICallback !== SNICallback ||
      (options.server && options.server._contexts.length))
  ) {
    validateFunction(options.SNICallback, "options.SNICallback");
    this._SNICallback = options.SNICallback;
    ssl.enableCertCb();
  }

  if (options.ALPNProtocols) ssl.setALPNProtocols(options.ALPNProtocols);

  if (options.pskCallback && ssl.enablePskCallback) {
    validateFunction(options.pskCallback, "pskCallback");

    ssl[kOnPskExchange] = options.isServer
      ? onPskServerCallback
      : onPskClientCallback;

    this[kPskCallback] = options.pskCallback;
    ssl.enablePskCallback();

    if (options.pskIdentityHint) {
      validateString(options.pskIdentityHint, "options.pskIdentityHint");
      ssl.setPskIdentityHint(options.pskIdentityHint);
    }
  }

  // We can only come here via [kWrapConnectedHandle]() call that happens
  // if the connection is established with `autoSelectFamily` set to `true`.
  const connectOptions = this[kConnectOptions];
  if (!options.isServer && connectOptions) {
    if (connectOptions.servername) {
      this.setServername(connectOptions.servername);
    }
  }

  if (options.handshakeTimeout > 0)
    this.setTimeout(options.handshakeTimeout, this._handleTimeout);

  if (socket instanceof net.Socket) {
    this._parent = socket;

    // To prevent assertion in afterConnect() and properly kick off readStart
    this.connecting = socket.connecting || !socket._handle;
    socket.once("connect", () => {
      this.connecting = false;
      this.emit("connect");
    });
  }

  // Assume `tls.connect()`
  if (wrap) {
    wrap.on("error", (err) => this._emitTLSError(err));
  } else {
    assert(!socket);
    this.connecting = true;
  }
};

TLSSocket.prototype.renegotiate = function (options, callback) {
  validateObject(options, "options");
  if (callback !== undefined) {
    validateFunction(callback, "callback");
  }

  {
    /* debug */
  }

  if (this.destroyed) return;

  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
  ) {
    this._handle.setVerifyMode(requestCert, rejectUnauthorized);
    this._requestCert = requestCert;
    this._rejectUnauthorized = rejectUnauthorized;
  }
  // Ensure that we'll cycle through internal openssl's state
  this.write("");

  try {
    this._handle.renegotiate();
  } catch (err) {
    if (callback) {
      process.nextTick(callback, err);
    }
    return false;
  }

  // Ensure that we'll cycle through internal openssl's state
  this.write("");

  if (callback) {
    this.once("secure", () => callback(null));
  }

  return true;
};

TLSSocket.prototype.exportKeyingMaterial = function (length, label, context) {
  validateUint32(length, "length", true);
  validateString(label, "label");
  if (context !== undefined) validateBuffer(context, "context");

  if (!this._secureEstablished) throw new ERR_TLS_INVALID_STATE();

  return this._handle.exportKeyingMaterial(length, label, context);
};

TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(size) {
  validateInt32(size, "size");
  return this._handle.setMaxSendFragment(size) === 1;
};

TLSSocket.prototype._handleTimeout = function () {
  this._emitTLSError(new ERR_TLS_HANDSHAKE_TIMEOUT());
};

TLSSocket.prototype._emitTLSError = function (err) {
  const e = this._tlsError(err);
  if (e) this.emit("error", e);
};

TLSSocket.prototype._tlsError = function (err) {
  this.emit("_tlsError", err);
  if (this._controlReleased) return err;
  return null;
};

TLSSocket.prototype._releaseControl = function () {
  if (this._controlReleased) return false;
  this._controlReleased = true;
  this.removeListener("error", this._tlsError);
  return true;
};

TLSSocket.prototype._finishInit = function () {
  // Guard against getting onhandshakedone() after .destroy().
  // * 1.2: If destroy() during onocspresponse(), then write of next handshake
  // record fails, the handshake done info callbacks does not occur, and the
  // socket closes.
  // * 1.3: The OCSP response comes in the same record that finishes handshake,
  // so even after .destroy(), the handshake done info callback occurs
  // immediately after onocspresponse(). Ignore it.
  if (!this._handle) return;

  this.alpnProtocol = this._handle.getALPNNegotiatedProtocol();
  // The servername could be set by TLSWrap::SelectSNIContextCallback().
  if (this.servername === null) {
    this.servername = this._handle.getServername();
  }

  {
    /* debug */
  }

  this._secureEstablished = true;
  if (this._tlsOptions.handshakeTimeout > 0)
    this.setTimeout(0, this._handleTimeout);
  this.emit("secure");
};

TLSSocket.prototype._start = function () {
  {
    /* debug */
  }
  if (this.connecting) {
    this.once("connect", this._start);
    return;
  }

  // Socket was destroyed before the connection was established
  if (!this._handle) return;

  if (this._tlsOptions.requestOCSP) this._handle.requestOCSP();
  this._handle.start();
};

TLSSocket.prototype.setServername = function (name) {
  validateString(name, "name");

  if (this._tlsOptions.isServer) {
    throw new ERR_TLS_SNI_FROM_SERVER();
  }

  this._handle.setServername(name);
};

TLSSocket.prototype.setSession = function (session) {
  if (typeof session === "string") session = Buffer.from(session, "latin1");
  this._handle.setSession(session);
};

TLSSocket.prototype.getPeerCertificate = function (detailed) {
  if (this._handle) {
    return (
      common.translatePeerCertificate(
        this._handle.getPeerCertificate(detailed),
      ) || {}
    );
  }

  return null;
};

TLSSocket.prototype.getCertificate = function () {
  if (this._handle) {
    // It's not a peer cert, but the formatting is identical.
    return common.translatePeerCertificate(this._handle.getCertificate()) || {};
  }

  return null;
};

TLSSocket.prototype.getPeerX509Certificate = function (detailed) {
  const cert = this._handle?.getPeerX509Certificate();
  return cert ? new InternalX509Certificate(cert) : undefined;
};

TLSSocket.prototype.getX509Certificate = function () {
  const cert = this._handle?.getX509Certificate();
  return cert ? new InternalX509Certificate(cert) : undefined;
};

TLSSocket.prototype.setKeyCert = function (context) {
  if (this._handle) {
    let secureContext;
    if (context instanceof common.SecureContext) secureContext = context;
    else secureContext = tls.createSecureContext(context);
    this._handle.setKeyCert(secureContext.context);
  }
};

// Proxy TLSSocket handle methods
function makeSocketMethodProxy(name) {
  return function socketMethodProxy(...args) {
    if (this._handle)
      return ReflectApply(this._handle[name], this._handle, args);
    return null;
  };
}

[
  "getCipher",
  "getSharedSigalgs",
  "getEphemeralKeyInfo",
  "getFinished",
  "getPeerFinished",
  "getProtocol",
  "getSession",
  "getTLSTicket",
  "isSessionReused",
  "enableTrace",
].forEach((method) => {
  TLSSocket.prototype[method] = makeSocketMethodProxy(method);
});

// TODO: support anonymous (nocert)

function onServerSocketSecure() {
  if (this._requestCert) {
    const verifyError = this._handle.verifyError();
    if (verifyError) {
      this.authorizationError = verifyError.code;

      if (this._rejectUnauthorized) this.destroy();
    } else {
      this.authorized = true;
    }
  }

  if (!this.destroyed && this._releaseControl()) {
    {
      /* debug */
    }
    this.secureConnecting = false;
    this._tlsOptions.server.emit("secureConnection", this);
  }
}

function onSocketTLSError(err) {
  if (!this._controlReleased && !this[kErrorEmitted]) {
    this[kErrorEmitted] = true;
    {
      /* debug */
    }
    this._tlsOptions.server.emit("tlsClientError", err, this);
  }
}

function onSocketKeylog(line) {
  this._tlsOptions.server.emit("keylog", line, this);
}

function onSocketClose(err) {
  // Closed because of error - no need to emit it twice
  if (err) return;

  // Emit ECONNRESET
  if (!this._controlReleased && !this[kErrorEmitted]) {
    this[kErrorEmitted] = true;
    const connReset = new ConnResetException("socket hang up");
    this._tlsOptions.server.emit("tlsClientError", connReset, this);
  }
}

function tlsConnectionListener(rawSocket) {
  {
    /* debug */
  }
  const socket = new TLSSocket(rawSocket, {
    secureContext: this._sharedCreds,
    isServer: true,
    server: this,
    requestCert: this.requestCert,
    rejectUnauthorized: this.rejectUnauthorized,
    handshakeTimeout: this[kHandshakeTimeout],
    ALPNProtocols: this.ALPNProtocols,
    ALPNCallback: this.ALPNCallback,
    SNICallback: this[kSNICallback] || SNICallback,
    enableTrace: this[kEnableTrace],
    pauseOnConnect: this.pauseOnConnect,
    pskCallback: this[kPskCallback],
    pskIdentityHint: this[kPskIdentityHint],
  });

  socket.on("secure", onServerSocketSecure);

  if (this.listenerCount("keylog") > 0) socket.on("keylog", onSocketKeylog);

  socket[kErrorEmitted] = false;
  socket.on("close", onSocketClose);
  socket.on("_tlsError", onSocketTLSError);
}

// AUTHENTICATION MODES
//
// There are several levels of authentication that TLS/SSL supports.
// Read more about this in "man SSL_set_verify".
//
// 1. The server sends a certificate to the client but does not request a
// cert from the client. This is common for most HTTPS servers. The browser
// can verify the identity of the server, but the server does not know who
// the client is. Authenticating the client is usually done over HTTP using
// login boxes and cookies and stuff.
//
// 2. The server sends a cert to the client and requests that the client
// also send it a cert. The client knows who the server is and the server is
// requesting the client also identify themselves. There are several
// outcomes:
//
//   A) verifyError returns null meaning the client's certificate is signed
//   by one of the server's CAs. The server now knows the client's identity
//   and the client is authorized.
//
//   B) For some reason the client's certificate is not acceptable -
//   verifyError returns a string indicating the problem. The server can
//   either (i) reject the client or (ii) allow the client to connect as an
//   unauthorized connection.
//
// The mode is controlled by two boolean variables.
//
// requestCert
//   If true the server requests a certificate from client connections. For
//   the common HTTPS case, users will want this to be false, which is what
//   it defaults to.
//
// rejectUnauthorized
//   If true clients whose certificates are invalid for any reason will not
//   be allowed to make connections. If false, they will simply be marked as
//   unauthorized but secure communication will continue. By default this is
//   true.
//
//
//
// Options:
// - requestCert. Send verify request. Default to false.
// - rejectUnauthorized. Boolean, default to true.
// - key. string.
// - cert: string.
// - clientCertEngine: string.
// - ca: string or array of strings.
// - sessionTimeout: integer.
//
// emit 'secureConnection'
//   function (tlsSocket) { }
//
//   "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL",
//   "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
//   "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE",
//   "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED",
//   "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD",
//   "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD",
//   "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM",
//   "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
//   "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
//   "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA",
//   "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED",
//   "CERT_REJECTED"
//
function Server(options, listener) {
  if (!(this instanceof Server)) return new Server(options, listener);

  if (typeof options === "function") {
    listener = options;
    options = kEmptyObject;
  } else if (options == null || typeof options === "object") {
    options = options ?? kEmptyObject;
  } else {
    throw new ERR_INVALID_ARG_TYPE("options", "Object", options);
  }

  this._contexts = [];
  this.requestCert = options.requestCert === true;
  this.rejectUnauthorized = options.rejectUnauthorized !== false;

  this.ALPNCallback = options.ALPNCallback;
  if (this.ALPNCallback && options.ALPNProtocols) {
    throw new ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS();
  }

  if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;

  if (options.ticketKeys) this.ticketKeys = options.ticketKeys;

  if (options.ALPNProtocols)
    tls.convertALPNProtocols(options.ALPNProtocols, this);

  this.setSecureContext(options);

  this[kHandshakeTimeout] = options.handshakeTimeout || 120 * 1000;
  this[kSNICallback] = options.SNICallback;
  this[kPskCallback] = options.pskCallback;
  this[kPskIdentityHint] = options.pskIdentityHint;

  validateNumber(this[kHandshakeTimeout], "options.handshakeTimeout");

  if (this[kSNICallback]) {
    validateFunction(this[kSNICallback], "options.SNICallback");
  }

  if (this[kPskCallback]) {
    validateFunction(this[kPskCallback], "options.pskCallback");
  }

  if (this[kPskIdentityHint]) {
    validateString(this[kPskIdentityHint], "options.pskIdentityHint");
  }

  // constructor call
  ReflectApply(net.Server, this, [options, tlsConnectionListener]);

  if (listener) {
    this.on("secureConnection", listener);
  }

  this[kEnableTrace] = options.enableTrace;
}

Object.setPrototypeOf(Server.prototype, net.Server.prototype);
Object.setPrototypeOf(Server, net.Server);
export { Server };
const _export_createServer_ = function createServer(options, listener) {
  return new Server(options, listener);
};
export { _export_createServer_ as createServer };

Server.prototype.setSecureContext = function (options) {
  validateObject(options, "options");

  if (options.pfx) this.pfx = options.pfx;
  else this.pfx = undefined;

  if (options.key) this.key = options.key;
  else this.key = undefined;

  if (options.passphrase) this.passphrase = options.passphrase;
  else this.passphrase = undefined;

  if (options.cert) this.cert = options.cert;
  else this.cert = undefined;

  if (options.clientCertEngine)
    this.clientCertEngine = options.clientCertEngine;
  else this.clientCertEngine = undefined;

  if (options.ca) this.ca = options.ca;
  else this.ca = undefined;

  if (options.minVersion) this.minVersion = options.minVersion;
  else this.minVersion = undefined;

  if (options.maxVersion) this.maxVersion = options.maxVersion;
  else this.maxVersion = undefined;

  if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
  else this.secureProtocol = undefined;

  if (options.crl) this.crl = options.crl;
  else this.crl = undefined;

  this.sigalgs = options.sigalgs;

  if (options.ciphers) this.ciphers = options.ciphers;
  else this.ciphers = undefined;

  this.ecdhCurve = options.ecdhCurve;

  if (options.dhparam) this.dhparam = options.dhparam;
  else this.dhparam = undefined;

  if (options.honorCipherOrder !== undefined)
    this.honorCipherOrder = !!options.honorCipherOrder;
  else this.honorCipherOrder = true;

  const secureOptions = options.secureOptions || 0;

  if (secureOptions) this.secureOptions = secureOptions;
  else this.secureOptions = undefined;

  if (options.sessionIdContext) {
    this.sessionIdContext = options.sessionIdContext;
  } else {
    this.sessionIdContext = crypto
      .createHash("sha1")
      .update(process.argv.join(" "))
      .digest("hex")
      .slice(0, 32);
  }

  if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;

  if (options.ticketKeys) this.ticketKeys = options.ticketKeys;

  this.privateKeyIdentifier = options.privateKeyIdentifier;
  this.privateKeyEngine = options.privateKeyEngine;

  this._sharedCreds = tls.createSecureContext({
    pfx: this.pfx,
    key: this.key,
    passphrase: this.passphrase,
    cert: this.cert,
    clientCertEngine: this.clientCertEngine,
    ca: this.ca,
    ciphers: this.ciphers,
    sigalgs: this.sigalgs,
    ecdhCurve: this.ecdhCurve,
    dhparam: this.dhparam,
    minVersion: this.minVersion,
    maxVersion: this.maxVersion,
    secureProtocol: this.secureProtocol,
    secureOptions: this.secureOptions,
    honorCipherOrder: this.honorCipherOrder,
    crl: this.crl,
    sessionIdContext: this.sessionIdContext,
    ticketKeys: this.ticketKeys,
    sessionTimeout: this.sessionTimeout,
    privateKeyIdentifier: this.privateKeyIdentifier,
    privateKeyEngine: this.privateKeyEngine,
  });
};

Server.prototype._getServerData = function () {
  return {
    ticketKeys: this.getTicketKeys().toString("hex"),
  };
};

Server.prototype._setServerData = function (data) {
  this.setTicketKeys(Buffer.from(data.ticketKeys, "hex"));
};

Server.prototype.getTicketKeys = function getTicketKeys() {
  return this._sharedCreds.context.getTicketKeys();
};

Server.prototype.setTicketKeys = function setTicketKeys(keys) {
  validateBuffer(keys);
  assert(
    keys.byteLength === 48,
    "Session ticket keys must be a 48-byte buffer",
  );
  this._sharedCreds.context.setTicketKeys(keys);
};

Server.prototype.setOptions = deprecate(
  function (options) {
    this.requestCert = options.requestCert === true;
    this.rejectUnauthorized = options.rejectUnauthorized !== false;

    if (options.pfx) this.pfx = options.pfx;
    if (options.key) this.key = options.key;
    if (options.passphrase) this.passphrase = options.passphrase;
    if (options.cert) this.cert = options.cert;
    if (options.clientCertEngine)
      this.clientCertEngine = options.clientCertEngine;
    if (options.ca) this.ca = options.ca;
    if (options.minVersion) this.minVersion = options.minVersion;
    if (options.maxVersion) this.maxVersion = options.maxVersion;
    if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
    if (options.crl) this.crl = options.crl;
    if (options.ciphers) this.ciphers = options.ciphers;
    if (options.ecdhCurve !== undefined) this.ecdhCurve = options.ecdhCurve;
    if (options.dhparam) this.dhparam = options.dhparam;
    if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;
    if (options.ticketKeys) this.ticketKeys = options.ticketKeys;
    const secureOptions = options.secureOptions || 0;
    if (options.honorCipherOrder !== undefined)
      this.honorCipherOrder = !!options.honorCipherOrder;
    else this.honorCipherOrder = true;
    if (secureOptions) this.secureOptions = secureOptions;
    if (options.ALPNProtocols)
      tls.convertALPNProtocols(options.ALPNProtocols, this);
    if (options.sessionIdContext) {
      this.sessionIdContext = options.sessionIdContext;
    } else {
      this.sessionIdContext = crypto
        .createHash("sha1")
        .update(process.argv.join(" "))
        .digest("hex")
        .slice(0, 32);
    }
    if (options.pskCallback) this[kPskCallback] = options.pskCallback;
    if (options.pskIdentityHint)
      this[kPskIdentityHint] = options.pskIdentityHint;
    if (options.sigalgs) this.sigalgs = options.sigalgs;
    if (options.privateKeyIdentifier !== undefined)
      this.privateKeyIdentifier = options.privateKeyIdentifier;
    if (options.privateKeyEngine !== undefined)
      this.privateKeyEngine = options.privateKeyEngine;
  },
  "Server.prototype.setOptions() is deprecated",
  "DEP0122",
);

// SNI Contexts High-Level API
Server.prototype.addContext = function (servername, context) {
  if (!servername) {
    throw new ERR_TLS_REQUIRED_SERVER_NAME();
  }

  const re = new RegExp(
    `^${servername
      .replace(/([.^$+?\-\\[\]{}])/g, "\\$1")
      .replaceAll("*", "[^.]*")}$`,
  );

  const secureContext =
    context instanceof common.SecureContext
      ? context
      : tls.createSecureContext(context);
  this._contexts.push([re, secureContext.context]);
};

Server.prototype[EE.captureRejectionSymbol] = function (err, event, sock) {
  switch (event) {
    case "secureConnection":
      sock.destroy(err);
      break;
    default:
      ReflectApply(net.Server.prototype[Symbol.for("nodejs.rejection")], this, [
        err,
        event,
        sock,
      ]);
  }
};

function SNICallback(servername, callback) {
  const contexts = this.server._contexts;

  for (let i = contexts.length - 1; i >= 0; --i) {
    const elem = contexts[i];
    if (elem[0].test(servername)) {
      callback(null, elem[1]);
      return;
    }
  }

  callback(null, undefined);
}

// Target API:
//
//  let s = tls.connect({port: 8000, host: "google.com"}, function() {
//    if (!s.authorized) {
//      s.destroy();
//      return;
//    }
//
//    // s.socket;
//
//    s.end("hello world\n");
//  });
//
//
function normalizeConnectArgs(listArgs) {
  const args = net._normalizeArgs(listArgs);
  const options = args[0];
  const cb = args[1];

  // 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") {
    Object.assign(options, listArgs[1]);
  } else if (listArgs[2] !== null && typeof listArgs[2] === "object") {
    Object.assign(options, listArgs[2]);
  }

  return cb ? [options, cb] : [options];
}

function onConnectSecure() {
  const options = this[kConnectOptions];

  // Check the size of DHE parameter above minimum requirement
  // specified in options.
  const ekeyinfo = this.getEphemeralKeyInfo();
  if (ekeyinfo.type === "DH" && ekeyinfo.size < options.minDHSize) {
    const err = new ERR_TLS_DH_PARAM_SIZE(ekeyinfo.size);
    {
      /* debug */
    }
    this.emit("error", err);
    this.destroy();
    return;
  }

  let verifyError = this._handle.verifyError();

  // Verify that server's identity matches it's certificate's names
  // Unless server has resumed our existing session
  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 (verifyError) {
    this.authorized = false;
    this.authorizationError = verifyError.code || verifyError.message;

    // rejectUnauthorized property can be explicitly defined as `undefined`
    // causing the assignment to default value (`true`) fail. Before assigning
    // it to the tlssock connection options, explicitly check if it is false
    // and update rejectUnauthorized property. The property gets used by
    // TLSSocket connection handler to allow or reject connection if
    // unauthorized.
    // This check is potentially redundant, however it is better to keep it
    // in case the option object gets modified somewhere.
    if (options.rejectUnauthorized !== false) {
      this.destroy(verifyError);
      return;
    }
    {
      /* debug */
    }
  } else {
    this.authorized = true;
    {
      /* debug */
    }
  }
  this.secureConnecting = false;
  this.emit("secureConnect");

  this[kIsVerified] = true;
  const session = this[kPendingSession];
  this[kPendingSession] = null;
  if (session) this.emit("session", session);

  this.removeListener("end", onConnectEnd);
}

function onConnectEnd() {
  // NOTE: This logic is shared with _http_client.js
  if (!this._hadError) {
    const options = this[kConnectOptions];
    this._hadError = true;
    const error = new ConnResetException(
      "Client network socket disconnected " +
        "before secure TLS connection was " +
        "established",
    );
    error.path = options.path;
    error.host = options.host;
    error.port = options.port;
    error.localAddress = options.localAddress;
    this.destroy(error);
  }
}

// Arguments: [port,] [host,] [options,] [cb]
const _export_connect_ = function connect(...args) {
  args = normalizeConnectArgs(args);
  let options = args[0];
  const cb = args[1];
  const allowUnauthorized = getAllowUnauthorized();

  options = {
    rejectUnauthorized: !allowUnauthorized,
    ciphers: tls.DEFAULT_CIPHERS,
    checkServerIdentity: tls.checkServerIdentity,
    minDHSize: 1024,
    ...options,
  };

  if (!options.keepAlive) options.singleUse = true;

  validateFunction(options.checkServerIdentity, "options.checkServerIdentity");
  validateNumber(options.minDHSize, "options.minDHSize", 1);

  const context = options.secureContext || tls.createSecureContext(options);

  const tlssock = new TLSSocket(options.socket, {
    allowHalfOpen: options.allowHalfOpen,
    pipe: !!options.path,
    secureContext: context,
    isServer: false,
    requestCert: true,
    rejectUnauthorized: options.rejectUnauthorized !== false,
    session: options.session,
    ALPNProtocols: options.ALPNProtocols,
    requestOCSP: options.requestOCSP,
    enableTrace: options.enableTrace,
    pskCallback: options.pskCallback,
    highWaterMark: options.highWaterMark,
    onread: options.onread,
    signal: options.signal,
  });

  // rejectUnauthorized property can be explicitly defined as `undefined`
  // causing the assignment to default value (`true`) fail. Before assigning
  // it to the tlssock connection options, explicitly check if it is false
  // and update rejectUnauthorized property. The property gets used by TLSSocket
  // connection handler to allow or reject connection if unauthorized
  options.rejectUnauthorized = options.rejectUnauthorized !== false;

  tlssock[kConnectOptions] = options;

  if (cb) tlssock.once("secureConnect", cb);

  if (!options.socket) {
    // If user provided the socket, it's their responsibility to manage its
    // connectivity. If we created one internally, we connect it.
    if (options.timeout) {
      tlssock.setTimeout(options.timeout);
    }

    tlssock.connect(options, tlssock._start);
  }

  tlssock._releaseControl();

  if (options.session) tlssock.setSession(options.session);

  if (options.servername) {
    if (!ipServernameWarned && net.isIP(options.servername)) {
      process.emitWarning(
        "Setting the TLS ServerName to an IP address is not permitted by " +
          "RFC 6066. This will be ignored in a future version.",
        "DeprecationWarning",
        "DEP0123",
      );
      ipServernameWarned = true;
    }
    tlssock.setServername(options.servername);
  }

  if (options.socket) tlssock._start();

  tlssock.on("secure", onConnectSecure);
  tlssock.prependListener("end", onConnectEnd);

  return tlssock;
};
export { _export_connect_ as connect };