@push.rocks/smartproxy
Version:
A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.
226 lines • 19.6 kB
JavaScript
import * as plugins from '../../../plugins.js';
import { TlsAlertLevel, TlsAlertDescription, TlsVersion } from '../utils/tls-utils.js';
/**
* TlsAlert class for creating and sending TLS alert messages
*/
export class TlsAlert {
// Use enum values from TlsAlertLevel
static { this.LEVEL_WARNING = TlsAlertLevel.WARNING; }
static { this.LEVEL_FATAL = TlsAlertLevel.FATAL; }
// Use enum values from TlsAlertDescription
static { this.CLOSE_NOTIFY = TlsAlertDescription.CLOSE_NOTIFY; }
static { this.UNEXPECTED_MESSAGE = TlsAlertDescription.UNEXPECTED_MESSAGE; }
static { this.BAD_RECORD_MAC = TlsAlertDescription.BAD_RECORD_MAC; }
static { this.DECRYPTION_FAILED = TlsAlertDescription.DECRYPTION_FAILED; }
static { this.RECORD_OVERFLOW = TlsAlertDescription.RECORD_OVERFLOW; }
static { this.DECOMPRESSION_FAILURE = TlsAlertDescription.DECOMPRESSION_FAILURE; }
static { this.HANDSHAKE_FAILURE = TlsAlertDescription.HANDSHAKE_FAILURE; }
static { this.NO_CERTIFICATE = TlsAlertDescription.NO_CERTIFICATE; }
static { this.BAD_CERTIFICATE = TlsAlertDescription.BAD_CERTIFICATE; }
static { this.UNSUPPORTED_CERTIFICATE = TlsAlertDescription.UNSUPPORTED_CERTIFICATE; }
static { this.CERTIFICATE_REVOKED = TlsAlertDescription.CERTIFICATE_REVOKED; }
static { this.CERTIFICATE_EXPIRED = TlsAlertDescription.CERTIFICATE_EXPIRED; }
static { this.CERTIFICATE_UNKNOWN = TlsAlertDescription.CERTIFICATE_UNKNOWN; }
static { this.ILLEGAL_PARAMETER = TlsAlertDescription.ILLEGAL_PARAMETER; }
static { this.UNKNOWN_CA = TlsAlertDescription.UNKNOWN_CA; }
static { this.ACCESS_DENIED = TlsAlertDescription.ACCESS_DENIED; }
static { this.DECODE_ERROR = TlsAlertDescription.DECODE_ERROR; }
static { this.DECRYPT_ERROR = TlsAlertDescription.DECRYPT_ERROR; }
static { this.EXPORT_RESTRICTION = TlsAlertDescription.EXPORT_RESTRICTION; }
static { this.PROTOCOL_VERSION = TlsAlertDescription.PROTOCOL_VERSION; }
static { this.INSUFFICIENT_SECURITY = TlsAlertDescription.INSUFFICIENT_SECURITY; }
static { this.INTERNAL_ERROR = TlsAlertDescription.INTERNAL_ERROR; }
static { this.INAPPROPRIATE_FALLBACK = TlsAlertDescription.INAPPROPRIATE_FALLBACK; }
static { this.USER_CANCELED = TlsAlertDescription.USER_CANCELED; }
static { this.NO_RENEGOTIATION = TlsAlertDescription.NO_RENEGOTIATION; }
static { this.MISSING_EXTENSION = TlsAlertDescription.MISSING_EXTENSION; }
static { this.UNSUPPORTED_EXTENSION = TlsAlertDescription.UNSUPPORTED_EXTENSION; }
static { this.CERTIFICATE_REQUIRED = TlsAlertDescription.CERTIFICATE_REQUIRED; }
static { this.UNRECOGNIZED_NAME = TlsAlertDescription.UNRECOGNIZED_NAME; }
static { this.BAD_CERTIFICATE_STATUS_RESPONSE = TlsAlertDescription.BAD_CERTIFICATE_STATUS_RESPONSE; }
static { this.BAD_CERTIFICATE_HASH_VALUE = TlsAlertDescription.BAD_CERTIFICATE_HASH_VALUE; }
static { this.UNKNOWN_PSK_IDENTITY = TlsAlertDescription.UNKNOWN_PSK_IDENTITY; }
static { this.CERTIFICATE_REQUIRED_1_3 = TlsAlertDescription.CERTIFICATE_REQUIRED_1_3; }
static { this.NO_APPLICATION_PROTOCOL = TlsAlertDescription.NO_APPLICATION_PROTOCOL; }
/**
* 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 = [TlsVersion.TLS1_2[0], TlsVersion.TLS1_2[1]]) {
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGxzLWFsZXJ0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vdHMvcHJvdG9jb2xzL3Rscy9hbGVydHMvdGxzLWFsZXJ0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFDL0MsT0FBTyxFQUFFLGFBQWEsRUFBRSxtQkFBbUIsRUFBRSxVQUFVLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUV2Rjs7R0FFRztBQUNILE1BQU0sT0FBTyxRQUFRO0lBQ25CLHFDQUFxQzthQUNyQixrQkFBYSxHQUFHLGFBQWEsQ0FBQyxPQUFPLENBQUM7YUFDdEMsZ0JBQVcsR0FBRyxhQUFhLENBQUMsS0FBSyxDQUFDO0lBRWxELDJDQUEyQzthQUMzQixpQkFBWSxHQUFHLG1CQUFtQixDQUFDLFlBQVksQ0FBQzthQUNoRCx1QkFBa0IsR0FBRyxtQkFBbUIsQ0FBQyxrQkFBa0IsQ0FBQzthQUM1RCxtQkFBYyxHQUFHLG1CQUFtQixDQUFDLGNBQWMsQ0FBQzthQUNwRCxzQkFBaUIsR0FBRyxtQkFBbUIsQ0FBQyxpQkFBaUIsQ0FBQzthQUMxRCxvQkFBZSxHQUFHLG1CQUFtQixDQUFDLGVBQWUsQ0FBQzthQUN0RCwwQkFBcUIsR0FBRyxtQkFBbUIsQ0FBQyxxQkFBcUIsQ0FBQzthQUNsRSxzQkFBaUIsR0FBRyxtQkFBbUIsQ0FBQyxpQkFBaUIsQ0FBQzthQUMxRCxtQkFBYyxHQUFHLG1CQUFtQixDQUFDLGNBQWMsQ0FBQzthQUNwRCxvQkFBZSxHQUFHLG1CQUFtQixDQUFDLGVBQWUsQ0FBQzthQUN0RCw0QkFBdUIsR0FBRyxtQkFBbUIsQ0FBQyx1QkFBdUIsQ0FBQzthQUN0RSx3QkFBbUIsR0FBRyxtQkFBbUIsQ0FBQyxtQkFBbUIsQ0FBQzthQUM5RCx3QkFBbUIsR0FBRyxtQkFBbUIsQ0FBQyxtQkFBbUIsQ0FBQzthQUM5RCx3QkFBbUIsR0FBRyxtQkFBbUIsQ0FBQyxtQkFBbUIsQ0FB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