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.8 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wcC50bHNhbGVydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3NtYXJ0cHJveHkvY2xhc3Nlcy5wcC50bHNhbGVydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssR0FBRyxNQUFNLEtBQUssQ0FBQztBQUUzQjs7R0FFRztBQUNILE1BQU0sT0FBTyxRQUFRO0lBQ25CLG1CQUFtQjthQUNILGtCQUFhLEdBQUcsSUFBSSxDQUFDO2FBQ3JCLGdCQUFXLEdBQUcsSUFBSSxDQUFDO0lBRW5DLHdFQUF3RTthQUN4RCxpQkFBWSxHQUFHLElBQUksQ0FBQzthQUNwQix1QkFBa0IsR0FBRyxJQUFJLENBQUM7YUFDMUIsbUJBQWMsR0FBRyxJQUFJLENBQUM7YUFDdEIsc0JBQWlCLEdBQUcsSUFBSSxDQUFDLEdBQUMsZUFBZTthQUN6QyxvQkFBZSxHQUFHLElBQUksQ0FBQzthQUN2QiwwQkFBcUIsR0FBRyxJQUFJLENBQUMsR0FBQyxvQkFBb0I7YUFDbEQsc0JBQWlCLEdBQUcsSUFBSSxDQUFDO2FBQ3pCLG1CQUFjLEdBQUcsSUFBSSxDQUFDLEdBQUMsYUFBYTthQUNwQyxvQkFBZSxHQUFHLElBQUksQ0FBQzthQUN2Qiw0QkFBdUIsR0FBRyxJQUFJLENBQUM7YUFDL0Isd0JBQW1CLEdBQUcsSUFBSSxDQUFDO2FBQzNCLHdCQUFtQixHQUFHLElBQUksQ0FBQzthQUMzQix3QkFBbUIsR0FBRyxJQUFJLENBQUM7YUFDM0Isc0JBQWlCLEdBQUcsSUFBSSxDQUFDO2FBQ3pCLGVBQVUsR0FBRyxJQUFJLENBQUM7YUFDbEIsa0JBQWEsR0FBRyxJQUFJLENBQUM7YUFDckIsaUJBQVksR0FBRyxJQUFJLENBQUM7YUFDcEIsa0JBQWEsR0FBRyxJQUFJLENBQUM7YUFDckIsdUJBQWtCLEdBQUcsSUFBSSxDQUFDLEdBQUMsZUFBZTthQUMxQyxxQkFBZ0IsR0FBRyxJQUFJLENBQUM7YUFDeEIsMEJBQXFCLEdBQUcsSUFB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