@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
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wcC50bHNhbGVydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3NtYXJ0cHJveHkvY2xhc3Nlcy5wcC50bHNhbGVydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssR0FBRyxNQUFNLEtBQUssQ0FBQztBQUUzQjs7R0FFRztBQUNILE1BQU0sT0FBTyxRQUFRO0lBQ25CLG1CQUFtQjthQUNILGtCQUFhLEdBQUcsSUFBSSxDQUFDO2FBQ3JCLGdCQUFXLEdBQUcsSUFBSSxDQUFDO0lBRW5DLHdFQUF3RTthQUN4RCxpQkFBWSxHQUFHLElBQUksQ0FBQzthQUNwQix1QkFBa0IsR0FBRyxJQUFJLENBQUM7YUFDMUIsbUJBQWMsR0FBRyxJQUFJLENBQUM7YUFDdEIsc0JBQWlCLEdBQUcsSUFBSSxDQUFDLEdBQUMsZUFBZTthQUN6QyxvQkFBZSxHQUFHLElBQUksQ0FBQzthQUN2QiwwQkFBcUIsR0FBRyxJQUFJLENBQUMsR0FBQyxvQkFBb0I7YUFDbEQsc0JBQWlCLEdBQUcsSUFBSSxDQUFDO2FBQ3pCLG1CQUFjLEdBQUcsSUFBSSxDQUFDLEdBQUMsYUFBYTthQUNwQyxvQkFBZSxHQUFHLElBQUksQ0FBQzthQUN2Qiw0QkFBdUIsR0FBRyxJQUFJLENBQUM7YUFDL0Isd0JBQW1CLEdBQUcsSUFBSSxDQUFDO2FBQzNCLHdCQUFtQixHQUFHLElBQUksQ0FBQzthQUMzQix3QkFBbUIsR0FBRyxJQUFJLENBQUM7YUFDM0Isc0JBQWlCLEdBQUcsSUFBSSxDQUFDO2FBQ3pCLGVBQVUsR0FBRyxJQUFJLENBQUM7YUFDbEIsa0JBQWEsR0FBRyxJQUFJLENBQUM7YUFDckIsaUJBQVksR0FBRyxJQUFJLENBQUM7YUFDcEIsa0JBQWEsR0FBRyxJQUFJLENBQUM7YUFDckIsdUJBQWtCLEdBQUcsSUFBSSxDQUFDLEdBQUMsZUFBZTthQUMxQyxxQkFBZ0IsR0FBRyxJQUFJLENBQUM7YUFDeEIsMEJBQXFCLEdBQUcsSUFB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