UNPKG

@push.rocks/smartproxy

Version:

A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.

225 lines 17.7 kB
import * as net from 'net'; /** * TlsAlert class for managing TLS alert messages */ export class TlsAlert { // TLS Alert Levels static { this.LEVEL_WARNING = 0x01; } static { this.LEVEL_FATAL = 0x02; } // TLS Alert Description Codes - RFC 8446 (TLS 1.3) / RFC 5246 (TLS 1.2) static { this.CLOSE_NOTIFY = 0x00; } static { this.UNEXPECTED_MESSAGE = 0x0A; } static { this.BAD_RECORD_MAC = 0x14; } static { this.DECRYPTION_FAILED = 0x15; } // TLS 1.0 only static { this.RECORD_OVERFLOW = 0x16; } static { this.DECOMPRESSION_FAILURE = 0x1E; } // TLS 1.2 and below static { this.HANDSHAKE_FAILURE = 0x28; } static { this.NO_CERTIFICATE = 0x29; } // SSLv3 only static { this.BAD_CERTIFICATE = 0x2A; } static { this.UNSUPPORTED_CERTIFICATE = 0x2B; } static { this.CERTIFICATE_REVOKED = 0x2C; } static { this.CERTIFICATE_EXPIRED = 0x2F; } static { this.CERTIFICATE_UNKNOWN = 0x30; } static { this.ILLEGAL_PARAMETER = 0x2F; } static { this.UNKNOWN_CA = 0x30; } static { this.ACCESS_DENIED = 0x31; } static { this.DECODE_ERROR = 0x32; } static { this.DECRYPT_ERROR = 0x33; } static { this.EXPORT_RESTRICTION = 0x3C; } // TLS 1.0 only static { this.PROTOCOL_VERSION = 0x46; } static { this.INSUFFICIENT_SECURITY = 0x47; } static { this.INTERNAL_ERROR = 0x50; } static { this.INAPPROPRIATE_FALLBACK = 0x56; } static { this.USER_CANCELED = 0x5A; } static { this.NO_RENEGOTIATION = 0x64; } // TLS 1.2 and below static { this.MISSING_EXTENSION = 0x6D; } // TLS 1.3 static { this.UNSUPPORTED_EXTENSION = 0x6E; } // TLS 1.3 static { this.CERTIFICATE_REQUIRED = 0x6F; } // TLS 1.3 static { this.UNRECOGNIZED_NAME = 0x70; } static { this.BAD_CERTIFICATE_STATUS_RESPONSE = 0x71; } static { this.BAD_CERTIFICATE_HASH_VALUE = 0x72; } // TLS 1.2 and below static { this.UNKNOWN_PSK_IDENTITY = 0x73; } static { this.CERTIFICATE_REQUIRED_1_3 = 0x74; } // TLS 1.3 static { this.NO_APPLICATION_PROTOCOL = 0x78; } /** * Create a TLS alert buffer with the specified level and description code * * @param level Alert level (warning or fatal) * @param description Alert description code * @param tlsVersion TLS version bytes (default is TLS 1.2: 0x0303) * @returns Buffer containing the TLS alert message */ static create(level, description, tlsVersion = [0x03, 0x03]) { return Buffer.from([ 0x15, // Alert record type tlsVersion[0], tlsVersion[1], // TLS version (default to TLS 1.2: 0x0303) 0x00, 0x02, // Length level, // Alert level description, // Alert description ]); } /** * Create a warning-level TLS alert * * @param description Alert description code * @returns Buffer containing the warning-level TLS alert message */ static createWarning(description) { return this.create(this.LEVEL_WARNING, description); } /** * Create a fatal-level TLS alert * * @param description Alert description code * @returns Buffer containing the fatal-level TLS alert message */ static createFatal(description) { return this.create(this.LEVEL_FATAL, description); } /** * Send a TLS alert to a socket and optionally close the connection * * @param socket The socket to send the alert to * @param level Alert level (warning or fatal) * @param description Alert description code * @param closeAfterSend Whether to close the connection after sending the alert * @param closeDelay Milliseconds to wait before closing the connection (default: 200ms) * @returns Promise that resolves when the alert has been sent */ static async send(socket, level, description, closeAfterSend = false, closeDelay = 200) { const alert = this.create(level, description); return new Promise((resolve, reject) => { try { // Ensure the alert is written as a single packet socket.cork(); const writeSuccessful = socket.write(alert, (err) => { if (err) { reject(err); return; } if (closeAfterSend) { setTimeout(() => { socket.end(); resolve(); }, closeDelay); } else { resolve(); } }); socket.uncork(); // If write wasn't successful immediately, wait for drain if (!writeSuccessful && !closeAfterSend) { socket.once('drain', () => { resolve(); }); } } catch (err) { reject(err); } }); } /** * Pre-defined TLS alert messages */ static { this.alerts = { // Warning level alerts closeNotify: TlsAlert.createWarning(TlsAlert.CLOSE_NOTIFY), unsupportedExtension: TlsAlert.createWarning(TlsAlert.UNSUPPORTED_EXTENSION), certificateRequired: TlsAlert.createWarning(TlsAlert.CERTIFICATE_REQUIRED), unrecognizedName: TlsAlert.createWarning(TlsAlert.UNRECOGNIZED_NAME), noRenegotiation: TlsAlert.createWarning(TlsAlert.NO_RENEGOTIATION), userCanceled: TlsAlert.createWarning(TlsAlert.USER_CANCELED), // Warning level alerts for session resumption certificateExpiredWarning: TlsAlert.createWarning(TlsAlert.CERTIFICATE_EXPIRED), handshakeFailureWarning: TlsAlert.createWarning(TlsAlert.HANDSHAKE_FAILURE), insufficientSecurityWarning: TlsAlert.createWarning(TlsAlert.INSUFFICIENT_SECURITY), // Fatal level alerts unexpectedMessage: TlsAlert.createFatal(TlsAlert.UNEXPECTED_MESSAGE), badRecordMac: TlsAlert.createFatal(TlsAlert.BAD_RECORD_MAC), recordOverflow: TlsAlert.createFatal(TlsAlert.RECORD_OVERFLOW), handshakeFailure: TlsAlert.createFatal(TlsAlert.HANDSHAKE_FAILURE), badCertificate: TlsAlert.createFatal(TlsAlert.BAD_CERTIFICATE), certificateExpired: TlsAlert.createFatal(TlsAlert.CERTIFICATE_EXPIRED), certificateUnknown: TlsAlert.createFatal(TlsAlert.CERTIFICATE_UNKNOWN), illegalParameter: TlsAlert.createFatal(TlsAlert.ILLEGAL_PARAMETER), unknownCA: TlsAlert.createFatal(TlsAlert.UNKNOWN_CA), accessDenied: TlsAlert.createFatal(TlsAlert.ACCESS_DENIED), decodeError: TlsAlert.createFatal(TlsAlert.DECODE_ERROR), decryptError: TlsAlert.createFatal(TlsAlert.DECRYPT_ERROR), protocolVersion: TlsAlert.createFatal(TlsAlert.PROTOCOL_VERSION), insufficientSecurity: TlsAlert.createFatal(TlsAlert.INSUFFICIENT_SECURITY), internalError: TlsAlert.createFatal(TlsAlert.INTERNAL_ERROR), unrecognizedNameFatal: TlsAlert.createFatal(TlsAlert.UNRECOGNIZED_NAME), }; } /** * Utility method to send a warning-level unrecognized_name alert * Specifically designed for SNI issues to encourage the client to retry with SNI * * @param socket The socket to send the alert to * @returns Promise that resolves when the alert has been sent */ static async sendSniRequired(socket) { return this.send(socket, this.LEVEL_WARNING, this.UNRECOGNIZED_NAME); } /** * Utility method to send a close_notify alert and close the connection * * @param socket The socket to send the alert to * @param closeDelay Milliseconds to wait before closing the connection (default: 200ms) * @returns Promise that resolves when the alert has been sent and the connection closed */ static async sendCloseNotify(socket, closeDelay = 200) { return this.send(socket, this.LEVEL_WARNING, this.CLOSE_NOTIFY, true, closeDelay); } /** * Utility method to send a certificate_expired alert to force new TLS session * * @param socket The socket to send the alert to * @param fatal Whether to send as a fatal alert (default: false) * @param closeAfterSend Whether to close the connection after sending the alert (default: true) * @param closeDelay Milliseconds to wait before closing the connection (default: 200ms) * @returns Promise that resolves when the alert has been sent */ static async sendCertificateExpired(socket, fatal = false, closeAfterSend = true, closeDelay = 200) { const level = fatal ? this.LEVEL_FATAL : this.LEVEL_WARNING; return this.send(socket, level, this.CERTIFICATE_EXPIRED, closeAfterSend, closeDelay); } /** * Send a sequence of alerts to force SNI from clients * This combines multiple alerts to ensure maximum browser compatibility * * @param socket The socket to send the alerts to * @returns Promise that resolves when all alerts have been sent */ static async sendForceSniSequence(socket) { try { // Send unrecognized_name (warning) socket.cork(); socket.write(this.alerts.unrecognizedName); socket.uncork(); // Give the socket time to send the alert return new Promise((resolve) => { setTimeout(resolve, 50); }); } catch (err) { return Promise.reject(err); } } /** * Send a fatal level alert that immediately terminates the connection * * @param socket The socket to send the alert to * @param description Alert description code * @param closeDelay Milliseconds to wait before closing the connection (default: 100ms) * @returns Promise that resolves when the alert has been sent and the connection closed */ static async sendFatalAndClose(socket, description, closeDelay = 100) { return this.send(socket, this.LEVEL_FATAL, description, true, closeDelay); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wcC50bHNhbGVydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucHAudGxzYWxlcnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEdBQUcsTUFBTSxLQUFLLENBQUM7QUFFM0I7O0dBRUc7QUFDSCxNQUFNLE9BQU8sUUFBUTtJQUNuQixtQkFBbUI7YUFDSCxrQkFBYSxHQUFHLElBQUksQ0FBQzthQUNyQixnQkFBVyxHQUFHLElBQUksQ0FBQztJQUVuQyx3RUFBd0U7YUFDeEQsaUJBQVksR0FBRyxJQUFJLENBQUM7YUFDcEIsdUJBQWtCLEdBQUcsSUFBSSxDQUFDO2FBQzFCLG1CQUFjLEdBQUcsSUFBSSxDQUFDO2FBQ3RCLHNCQUFpQixHQUFHLElBQUksQ0FBQyxHQUFDLGVBQWU7YUFDekMsb0JBQWUsR0FBRyxJQUFJLENBQUM7YUFDdkIsMEJBQXFCLEdBQUcsSUFBSSxDQUFDLEdBQUMsb0JBQW9CO2FBQ2xELHNCQUFpQixHQUFHLElBQUksQ0FBQzthQUN6QixtQkFBYyxHQUFHLElBQUksQ0FBQyxHQUFDLGFBQWE7YUFDcEMsb0JBQWUsR0FBRyxJQUFJLENBQUM7YUFDdkIsNEJBQXVCLEdBQUcsSUFBSSxDQUFDO2FBQy9CLHdCQUFtQixHQUFHLElBQUksQ0FBQzthQUMzQix3QkFBbUIsR0FBRyxJQUFJLENBQUM7YUFDM0Isd0JBQW1CLEdBQUcsSUFBSSxDQUFDO2FBQzNCLHNCQUFpQixHQUFHLElBQUksQ0FBQzthQUN6QixlQUFVLEdBQUcsSUFBSSxDQUFDO2FBQ2xCLGtCQUFhLEdBQUcsSUFBSSxDQUFDO2FBQ3JCLGlCQUFZLEdBQUcsSUFBSSxDQUFDO2FBQ3BCLGtCQUFhLEdBQUcsSUFBSSxDQUFDO2FBQ3JCLHVCQUFrQixHQUFHLElBQUksQ0FBQyxHQUFDLGVBQWU7YUFDMUMscUJBQWdCLEdBQUcsSUFBSSxDQUFDO2FBQ3hCLDBCQUFxQixHQUFHLElBQUksQ0FBQzthQUM3Qixt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