Skip to content
File

Blob: src/node/internal/internal_tls_wrap.ts

typescript755 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26import {
27 Socket,
28 type SocketOptions,
29 _normalizeArgs,
30 onConnectionOpened,
31 onConnectionClosed,
32 tryReadStart,
33} from 'node-internal:internal_net';
34import { JSStreamSocket } from 'node-internal:internal_tls_jsstream';
35import { checkServerIdentity } from 'node-internal:internal_tls';
36import type {
37 ConnectionOptions,
38 TlsOptions,
39 TLSSocket as TLSSocketType,
40} from 'node:tls';
41import type { Duplex } from 'node-internal:streams_duplex';
42import type { OnReadOpts, TcpSocketConnectOpts } from 'node:net';
43import {
44 validateBuffer,
45 validateString,
46 validateObject,
47 validateNumber,
48 validateFunction,
49 validateInt32,
50 validateUint32,
51} from 'node-internal:validators';
52import {
53 ConnResetException,
54 ERR_TLS_HANDSHAKE_TIMEOUT,
55 ERR_OPTION_NOT_IMPLEMENTED,
56 ERR_TLS_INVALID_CONTEXT,
57} from 'node-internal:internal_errors';
58import { SecureContext } from 'node-internal:internal_tls_common';
59import { ok } from 'node-internal:internal_assert';
60 
61const kConnectOptions = Symbol('connect-options');
62const kErrorEmitted = Symbol('error-emitted');
63const kRes = Symbol('res');
64const kPendingSession = Symbol('pendingSession');
65const kIsVerified = Symbol('verified');
66 
67// @ts-expect-error TS2323 Cannot redeclare error.
68export declare class TLSSocket extends Socket {
69 _hadError: boolean;
70 _handle: Socket['_handle'];
71 _init(): void;
72 _tlsOptions: TlsOptions &
73 ConnectionOptions &
74 SocketOptions &
75 TcpSocketConnectOpts & {
76 isServer?: boolean;
77 requestOCSP?: boolean;
78 server?: unknown;
79 onread?: OnReadOpts;
80 };
81 _secureEstablished: boolean;
82 _securePending: boolean;
83 _newSessionPending: boolean;
84 _controlReleased: boolean;
85 authorized: boolean;
86 encrypted: boolean;
87 handle: ReturnType<TLSSocket['_wrapHandle']>;
88 servername: null | string;
89 secureConnecting: boolean;
90 ssl: TLSSocket['_handle'];
91 [kRes]: null | Socket['_handle'];
92 [kIsVerified]: boolean;
93 [kPendingSession]: null | Buffer;
94 [kErrorEmitted]: boolean;
95 [kConnectOptions]?: NormalizedConnectionOptions;
96 
97 constructor(
98 socket: Socket | Duplex | undefined,
99 opts: TLSSocket['_tlsOptions']
100 );
101 prototype: TLSSocket;
102 
103 _destroySSL(): void;
104 _emitTLSError(error: Error): void;
105 _finishInit(): void;
106 _handleTimeout(): void;
107 _releaseControl(): boolean;
108 _start(): void;
109 _tlsError(error: Error): Error | null;
110 _wrapHandle(
111 wrap: null | Socket,
112 handle: Socket['_handle'] | null | undefined,
113 wrapHasActiveWriteFromPrevOwner: boolean
114 ): unknown;
115 disableRenegotiation(): void;
116 getX509Certificate(): ReturnType<TLSSocketType['getX509Certificate']>;
117 setKeyCert(context: unknown): void;
118 setServername(name: string): void;
119 setSession(session: string | Buffer): void;
120 setMaxSendFragment(size: number): boolean;
121 getCertificate(): ReturnType<TLSSocketType['getCertificate']>;
122 getPeerX509Certificate(): ReturnType<TLSSocketType['getPeerX509Certificate']>;
123 renegotiate(
124 options: {
125 rejectUnauthorized?: boolean | undefined;
126 requestCert?: boolean | undefined;
127 },
128 callback?: (error: Error | null) => void
129 ): boolean;
130 exportKeyingMaterial(length: number, label: string, context?: Buffer): Buffer;
131}
132 
133function onnewsessionclient(
134 this: TLSSocket,
135 _sessionId: string,
136 session: Buffer
137): void {
138 if (this[kIsVerified]) {
139 this.emit('session', session);
140 } else {
141 this[kPendingSession] = session;
142 }
143}
144 
145function onerror(this: TLSSocket, err: Error): void {
146 if (this._hadError) return;
147 
148 this._hadError = true;
149 
150 // Destroy socket if error happened before handshake's finish
151 if (!this._secureEstablished) {
152 // When handshake fails control is not yet released,
153 // so self._tlsError will return null instead of actual error
154 
155 // Set closing the socket after emitting an event since the socket needs to
156 // be accessible when the `tlsClientError` event is emitted.
157 this.destroy(err);
158 } else {
159 // Emit error
160 this._emitTLSError(err);
161 }
162}
163 
164// We are using old style function classes for node.js compat.
165// @ts-expect-error TS2323 Cannot redeclare error.
166export function TLSSocket(
167 this: TLSSocket,
168 socket: Socket | Duplex | undefined,
169 opts: TLSSocket['_tlsOptions']
170): void {
171 const tlsOptions = { ...opts };
172 
173 if (tlsOptions.enableTrace) {
174 throw new ERR_OPTION_NOT_IMPLEMENTED('options.enableTrace');
175 }
176 
177 if (tlsOptions.isServer) {
178 throw new ERR_OPTION_NOT_IMPLEMENTED('options.isServer');
179 }
180 
181 if (tlsOptions.server) {
182 throw new ERR_OPTION_NOT_IMPLEMENTED('options.server');
183 }
184 
185 if (tlsOptions.requestCert) {
186 // Servers will request certificate from clients.
187 // Does not apply to Cloudflare Workers.
188 throw new ERR_OPTION_NOT_IMPLEMENTED('options.requestCert');
189 }
190 
191 if (tlsOptions.rejectUnauthorized === false) {
192 // TODO(soon): We don't support rejectUnauthorized=false
193 throw new ERR_OPTION_NOT_IMPLEMENTED('options.rejectUnauthorized');
194 }
195 
196 if (tlsOptions.ALPNProtocols !== undefined) {
197 // Does not apply to Cloudflare Workers.
198 throw new ERR_OPTION_NOT_IMPLEMENTED('options.ALPNProtocols');
199 }
200 
201 if (tlsOptions.SNICallback !== undefined) {
202 // Does not apply to Cloudflare Workers.
203 throw new ERR_OPTION_NOT_IMPLEMENTED('options.SNICallback');
204 }
205 
206 if (tlsOptions.requestOCSP) {
207 // Not yet supported. Can be implemented in the future.
208 throw new ERR_OPTION_NOT_IMPLEMENTED('options.requestOCSP');
209 }
210 
211 if (
212 tlsOptions.secureContext !== undefined &&
213 !(tlsOptions.secureContext instanceof SecureContext)
214 ) {
215 throw new ERR_TLS_INVALID_CONTEXT('context');
216 }
217 
218 if (tlsOptions.pskCallback !== undefined) {
219 // Used for TLS-PSK negotiation. We do not support it.
220 throw new ERR_OPTION_NOT_IMPLEMENTED('options.pskCallback');
221 }
222 
223 // TODO(soon): Call this on secureConnect once connect() api supports
224 // getting peer certificate.
225 if (tlsOptions.checkServerIdentity !== undefined) {
226 validateFunction(
227 tlsOptions.checkServerIdentity,
228 'options.checkServerIdentity'
229 );
230 }
231 
232 this._tlsOptions = tlsOptions;
233 this._secureEstablished = false;
234 this._securePending = false;
235 this._newSessionPending = false;
236 this._controlReleased = false;
237 this.secureConnecting = true;
238 this.servername = null;
239 this.authorized = false;
240 this[kRes] = null;
241 this[kIsVerified] = false;
242 this[kPendingSession] = null;
243 this[kErrorEmitted] = false;
244 
245 let wrap: Socket | null = null;
246 let handle: Socket['_handle'] | null = null;
247 let wrapHasActiveWriteFromPrevOwner = false;
248 
249 if (socket) {
250 if (socket instanceof Socket) {
251 wrap = socket;
252 } else {
253 wrap = new JSStreamSocket(socket);
254 }
255 
256 handle = wrap._handle;
257 wrapHasActiveWriteFromPrevOwner = wrap.writableLength > 0;
258 }
259 
260 // Just a documented property to make secure sockets
261 // distinguishable from regular ones.
262 this.encrypted = true;
263 
264 Reflect.apply(Socket, this, [
265 {
266 handle: this._wrapHandle(wrap, handle, wrapHasActiveWriteFromPrevOwner),
267 allowHalfOpen: socket ? socket.allowHalfOpen : tlsOptions.allowHalfOpen,
268 pauseOnCreate: tlsOptions.pauseOnConnect,
269 manualStart: true,
270 highWaterMark: tlsOptions.highWaterMark,
271 onread: !socket ? tlsOptions.onread : null,
272 signal: tlsOptions.signal,
273 lookup: tlsOptions.lookup,
274 } as SocketOptions,
275 ]);
276 
277 this._parent = handle;
278 this._parentWrap = wrap;
279 
280 // Proxy for API compatibility
281 this.ssl = this._handle; // C++ TLSWrap object
282 
283 this.on('error', this._tlsError.bind(this));
284 
285 this._init();
286}
287Object.setPrototypeOf(TLSSocket.prototype, Socket.prototype);
288Object.setPrototypeOf(TLSSocket, Socket);
289 
290TLSSocket.prototype.disableRenegotiation = function disableRenegotiation(
291 this: TLSSocket
292): void {
293 // Do nothing.
294};
295 
296TLSSocket.prototype._wrapHandle = function _wrapHandle(
297 this: TLSSocket,
298 wrap: null | Socket,
299 handle: Socket['_handle'] | null | undefined,
300 _wrapHasActiveWriteFromPrevOwner: boolean
301): unknown {
302 if (!handle) {
303 return null;
304 }
305 this[kRes] = handle;
306 
307 // Guard against adding multiple listeners, as this method may be called
308 // repeatedly on the same socket by reinitializeHandle
309 if (this.listenerCount('close', onSocketCloseDestroySSL) === 0) {
310 this.once('close', onSocketCloseDestroySSL);
311 }
312 
313 wrap?.once('close', () => this.destroy());
314 
315 return handle;
316};
317 
318function onSocketCloseDestroySSL(this: TLSSocket): void {
319 // We call destroySSL directly because it is already an async process.
320 this._destroySSL();
321 this[kRes] = null;
322}
323 
324TLSSocket.prototype._destroySSL = function _destroySSL(this: TLSSocket): void {
325 // We disable floating promises rule because we don't want to change the
326 // function signature and return Promise<void>
327 //
328 // eslint-disable-next-line @typescript-eslint/no-floating-promises
329 this._handle?.socket.close().then(() => {
330 this[kPendingSession] = null;
331 this[kIsVerified] = false;
332 });
333};
334 
335TLSSocket.prototype._init = function _init(this: TLSSocket): void {
336 const options = this._tlsOptions;
337 
338 this.on('error', onerror.bind(this));
339 
340 // This is emitted from net.Socket class.
341 this.on('connectionAttempt', onnewsessionclient.bind(this));
342 
343 if (options.handshakeTimeout && options.handshakeTimeout > 0)
344 this.setTimeout(options.handshakeTimeout, this._handleTimeout.bind(this));
345 
346 // TLSSocket can be initialized with 2 different handles.
347 //
348 // 1. Socket instance created by "node:net". In this scenario, we need
349 // to wait for 'connect' event to be emitted in order to trigger _finishInit().
350 // 2. Duplex stream. Duplex streams are initialized through JSStreamSocket class.
351 // If that's the scenario, we can trigger _finishInit() immediately. Since, there
352 // is no async calls required to wait.
353 if (this._parentWrap != null && this._parentWrap instanceof JSStreamSocket) {
354 queueMicrotask(() => {
355 this._finishInit();
356 ok(this._parentWrap instanceof JSStreamSocket);
357 this._parentWrap.readStart();
358 tryReadStart(this);
359 });
360 } else {
361 this.on('connect', () => {
362 this._finishInit();
363 });
364 }
365};
366 
367TLSSocket.prototype.renegotiate = function (
368 this: TLSSocket,
369 options: {
370 rejectUnauthorized?: boolean | undefined;
371 requestCert?: boolean | undefined;
372 },
373 callback?: (error: Error | null) => void
374): boolean {
375 validateObject(options, 'options');
376 if (callback !== undefined) {
377 validateFunction(callback, 'callback');
378 }
379 // TLS renegotiation is not supported.
380 return false;
381};
382 
383TLSSocket.prototype.exportKeyingMaterial = function exportKeyingMaterial(
384 this: TLSSocket,
385 length: number,
386 label: string,
387 context?: Buffer
388): Buffer {
389 validateUint32(length, 'length', true);
390 validateString(label, 'label');
391 if (context !== undefined) {
392 validateBuffer(context, 'context');
393 }
394 
395 throw new Error('exportKeyingMaterial is not implemented');
396};
397 
398TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(
399 this: TLSSocket,
400 size: number
401): boolean {
402 validateInt32(size, 'size');
403 // Setting maximum TLS fragment size is not supported.
404 return false;
405};
406 
407TLSSocket.prototype._handleTimeout = function _handleTimeout(
408 this: TLSSocket
409): void {
410 this._emitTLSError(new ERR_TLS_HANDSHAKE_TIMEOUT());
411};
412 
413TLSSocket.prototype._emitTLSError = function _emitTLSError(
414 this: TLSSocket,
415 err: Error
416): void {
417 const e = this._tlsError(err);
418 if (e) this.emit('error', e);
419};
420 
421TLSSocket.prototype._tlsError = function _tlsError(
422 this: TLSSocket,
423 err: Error
424): Error | null {
425 this.emit('_tlsError', err);
426 if (this._controlReleased) return err;
427 return null;
428};
429 
430TLSSocket.prototype._releaseControl = function _releaseControl(
431 this: TLSSocket
432): boolean {
433 if (this._controlReleased) return false;
434 this._controlReleased = true;
435 this.removeListener('error', this._tlsError.bind(this));
436 return true;
437};
438 
439// This function is called from net.Socket onConnectionOpened() handler.
440TLSSocket.prototype._finishInit = function _finishInit(this: TLSSocket): void {
441 // Guard against getting onhandshakedone() after .destroy().
442 // * 1.2: If destroy() during onocspresponse(), then write of next handshake
443 // record fails, the handshake done info callbacks does not occur, and the
444 // socket closes.
445 // * 1.3: The OCSP response comes in the same record that finishes handshake,
446 // so even after .destroy(), the handshake done info callback occurs
447 // immediately after onocspresponse(). Ignore it.
448 if (!this._handle) return;
449 
450 this._secureEstablished = true;
451 if (
452 this._tlsOptions.handshakeTimeout &&
453 this._tlsOptions.handshakeTimeout > 0
454 ) {
455 this.setTimeout(0, this._handleTimeout.bind(this));
456 }
457 
458 this.emit('secure');
459};
460 
461TLSSocket.prototype._start = function _start(this: TLSSocket): void {
462 if (this.connecting) {
463 this.once('connect', this._start.bind(this));
464 return;
465 }
466 
467 // If a user calls tls.connect({ socket }) with a socket that is not initialized
468 // and the socket is not connected, we need to wait for the socket to connect
469 // before we can complete the process.
470 //
471 // Take a look at the following test for this particular edge case:
472 // https://github.com/nodejs/node/blob/91d8a524ada001103a2d1c6825ca17b8393c183f/test/parallel/test-tls-on-empty-socket.js
473 if (this._parentWrap != null && this._parentWrap._handle == null) {
474 this._parentWrap.once('connect', () => {
475 // We need to update the Socket handle of this TLSSocket
476 // since it was created after TLSSocket is initialized.
477 if (this._parentWrap?._handle != null) {
478 this._handle = this._parentWrap._handle;
479 }
480 this._start();
481 });
482 return;
483 }
484 
485 // Guard against the following cases:
486 // - Socket was destroyed before the connection was established
487 // - TLSSocket can not be upgraded if the secureTransport does not support 'starttls'
488 if (this._handle?.socket.secureTransport !== 'starttls') {
489 return;
490 }
491 
492 // We first need to release the lock
493 this._handle.writer.releaseLock();
494 this._handle.reader.releaseLock();
495 
496 try {
497 const { host, port, addressType } = this._handle.options;
498 const socket = this._handle.socket.startTls();
499 
500 this._handle = {
501 socket: socket,
502 writer: socket.writable.getWriter(),
503 reader: socket.readable.getReader({ mode: 'byob' }),
504 bytesRead: 0,
505 bytesWritten: 0,
506 reading: true,
507 options: this._handle.options,
508 };
509 
510 // This is now an encrypted connection.
511 // There are cases where in node:net we have to distinguish between
512 // encrypted and unencrypted connections.
513 this.encrypted = true;
514 
515 this._handle.socket.opened.then(
516 onConnectionOpened.bind(this),
517 (err: unknown) => {
518 this.emit('connectionAttemptFailed', host, port, addressType, err);
519 this.destroy(err as Error);
520 }
521 );
522 
523 this._handle.socket.closed.then(
524 onConnectionClosed.bind(this),
525 (error: unknown): void => {
526 // Do not call this.destroy.bind(this) since user can override it.
527 this.destroy(error as Error);
528 }
529 );
530 } catch (error) {
531 this.destroy(error as Error);
532 }
533};
534 
535TLSSocket.prototype.setServername = function setServername(
536 this: TLSSocket,
537 name: string
538): void {
539 validateString(name, 'name');
540 // Pipefitter currently does not provide us a way on the internal
541 // system and possibly KJ's TLS implementation doesn't provides a way,
542 // but it is something we will need sooner than later.
543};
544 
545TLSSocket.prototype.setSession = function (_session: string | Buffer): void {
546 // Do nothing. We don't support setting session.
547};
548 
549// @ts-expect-error TS2322 Inconsistencies between @types/node
550TLSSocket.prototype.getPeerCertificate = function (
551 _detailed?: boolean
552): ReturnType<TLSSocketType['getPeerCertificate']> {
553 // Returns an object representing a peer certificate.
554 // This function is not supported.
555 throw new Error('getPeerCertificate is not implemented');
556};
557 
558TLSSocket.prototype.getCertificate = function (
559 this: TLSSocket
560): ReturnType<TLSSocketType['getCertificate']> {
561 // Returns an object representing the local certificate.
562 // This function is not supported.
563 throw new Error('TLSSocket.getCertificate is not implemented');
564};
565 
566TLSSocket.prototype.getPeerX509Certificate = function (
567 this: TLSSocket,
568 _detailed?: boolean
569): ReturnType<TLSSocketType['getPeerX509Certificate']> {
570 // Returns the peer certificate as an X509 certificate.
571 // This function is not supported.
572 throw new Error('TLSSocket.getPeerX509Certificate is not implemented');
573};
574 
575TLSSocket.prototype.getX509Certificate = function (
576 this: TLSSocket
577): ReturnType<TLSSocketType['getX509Certificate']> {
578 // Returns the local certificate as an X509 certificate.
579 // This function is not supported.
580 throw new Error('TLSSocket.getX509Certificate is not implemented');
581};
582 
583TLSSocket.prototype.setKeyCert = function (
584 this: TLSSocket,
585 _context: unknown
586): void {
587 // Changing private key and certificate to be used with TCP connection
588 // is not supported due to the limitations of connect() api.
589 throw new Error('TLSSocket.setKeyCert is not implemented');
590};
591 
592// We have this syntax because of the original Node.js implementation.
593// They are not supported by Cloudflare Workers but in Node.js
594// they are all properties of this._handle[PROP_NAME]
595[
596 'getCipher',
597 'getSharedSigalgs',
598 'getEphemeralKeyInfo',
599 'getFinished',
600 'getPeerFinished',
601 'getProtocol',
602 'getSession',
603 'getTLSTicket',
604 'isSessionReused',
605 'enableTrace',
606].forEach((method) => {
607 // @ts-expect-error TS7053 Omitting...
608 TLSSocket.prototype[method] = function (): null {
609 // None of these functions are supported by connect() api.
610 return null;
611 };
612});
613 
614type NormalizedConnectionOptions = ConnectionOptions &
615 SocketOptions &
616 TlsOptions & {
617 host?: string;
618 port: number;
619 };
620function normalizeConnectArgs(
621 listArgs: unknown[]
622): [NormalizedConnectionOptions] | [NormalizedConnectionOptions, VoidFunction] {
623 const args = _normalizeArgs(listArgs);
624 const options = args[0] as NormalizedConnectionOptions;
625 const cb = args[1] as VoidFunction | undefined;
626 
627 // If args[0] was options, then normalize dealt with it.
628 // If args[0] is port, or args[0], args[1] is host, port, we need to
629 // find the options and merge them in, normalize's options has only
630 // the host/port/path args that it knows about, not the tls options.
631 // This means that options.host overrides a host arg.
632 if (listArgs[1] !== null && typeof listArgs[1] === 'object') {
633 Object.assign(options, listArgs[1]);
634 } else if (listArgs[2] !== null && typeof listArgs[2] === 'object') {
635 Object.assign(options, listArgs[2]);
636 }
637 
638 return cb ? [options, cb] : [options];
639}
640 
641function onConnectSecure(this: TLSSocket): void {
642 this.authorized = true;
643 this.secureConnecting = false;
644 this.emit('secureConnect');
645 
646 this[kIsVerified] = true;
647 const session = this[kPendingSession];
648 this[kPendingSession] = null;
649 if (session) this.emit('session', session);
650 
651 this.removeListener('end', onConnectEnd);
652}
653 
654function onConnectEnd(this: TLSSocket): void {
655 // NOTE: This logic is shared with _http_client.js
656 if (!this._hadError) {
657 const options = this[kConnectOptions];
658 this._hadError = true;
659 const error = new ConnResetException(
660 'Client network socket disconnected ' +
661 'before secure TLS connection was ' +
662 'established'
663 );
664 error.path = options?.path;
665 error.host = options?.host;
666 error.port = options?.port;
667 // @ts-expect-error TS2339 Missing types
668 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
669 error.localAddress = options?.localAddress;
670 this.destroy(error);
671 }
672}
673 
674// Arguments: [port,] [host,] [options,] [cb]
675export function connect(...args: unknown[]): TLSSocket {
676 args = normalizeConnectArgs(args);
677 const options = args[0] as NormalizedConnectionOptions;
678 const cb = args[1] as VoidFunction | undefined;
679 
680 if (options.minDHSize !== undefined) {
681 // We leave this validation for node.js compat.
682 validateNumber(options.minDHSize, 'options.minDHSize', 1);
683 // Not supported.
684 throw new ERR_OPTION_NOT_IMPLEMENTED('options.minDHSize');
685 }
686 
687 if (options.rejectUnauthorized === false) {
688 // TODO(soon): We don't support rejectUnauthorized=false
689 throw new ERR_OPTION_NOT_IMPLEMENTED('options.rejectUnauthorized');
690 }
691 
692 if (options.pskCallback !== undefined) {
693 // Used for TLS-PSK negotiation.
694 // Does not make sense for Cloudflare Workers.
695 throw new ERR_OPTION_NOT_IMPLEMENTED('options.pskCallback');
696 }
697 
698 if (options.checkServerIdentity !== undefined) {
699 validateFunction(
700 options.checkServerIdentity,
701 'options.checkServerIdentity'
702 );
703 }
704 
705 // @ts-expect-error TS2345 Type incompatibility between Node.js Duplex and internal Duplex
706 const tlssock = new TLSSocket(options.socket, {
707 allowHalfOpen: options.allowHalfOpen,
708 pipe: !!options.path,
709 ALPNProtocols: options.ALPNProtocols,
710 enableTrace: options.enableTrace,
711 highWaterMark: options.highWaterMark,
712 secureContext: options.secureContext,
713 checkServerIdentity: options.checkServerIdentity ?? checkServerIdentity,
714 onread: options.onread,
715 signal: options.signal,
716 lookup: options.lookup,
717 rejectUnauthorized:
718 options.rejectUnauthorized !== undefined
719 ? Boolean(options.rejectUnauthorized) // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion
720 : true,
721 });
722 
723 tlssock[kConnectOptions] = options;
724 
725 if (cb) {
726 tlssock.once('secureConnect', cb);
727 }
728 
729 if (!options.socket) {
730 // If user provided the socket, it's their responsibility to manage its
731 // connectivity. If we created one internally, we connect it.
732 if (options.timeout) {
733 tlssock.setTimeout(options.timeout);
734 }
735 
736 tlssock.connect(options, tlssock._start.bind(tlssock));
737 }
738 
739 tlssock._releaseControl();
740 
741 if (options.session) {
742 tlssock.setSession(options.session);
743 }
744 
745 if (options.socket) {
746 tlssock._start();
747 }
748 
749 // The 'secure' event is emitted by the SecurePair object once a secure connection has been established.
750 tlssock.on('secure', onConnectSecure);
751 tlssock.prependListener('end', onConnectEnd);
752 
753 return tlssock;
754}