@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
JavaScript
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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