Compare commits

...

24 Commits

Author SHA1 Message Date
0db4b07b22 4.2.0
Some checks failed
Default (tags) / security (push) Successful in 58s
Default (tags) / test (push) Failing after 14m46s
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-03-17 14:27:10 +00:00
b55e2da23e feat(tlsalert): add sendForceSniSequence and sendFatalAndClose helper functions to TlsAlert for improved SNI enforcement 2025-03-17 14:27:10 +00:00
3593e411cf 4.1.16
Some checks failed
Default (tags) / security (push) Successful in 29s
Default (tags) / test (push) Failing after 1m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-17 13:37:48 +00:00
ca6f6de798 fix(tls): Improve TLS alert handling in connection handler: use the new TlsAlert class to send proper unrecognized_name alerts when a ClientHello is missing SNI and wait for a retry on the same connection before closing. Also, add alertFallbackTimeout tracking to connection records for better timeout management. 2025-03-17 13:37:48 +00:00
80d2f30804 4.1.15
Some checks failed
Default (tags) / security (push) Failing after 14m48s
Default (tags) / test (push) Has been cancelled
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-03-17 13:23:07 +00:00
22f46700f1 fix(connectionhandler): Delay socket termination in TLS session resumption handling to allow proper alert processing 2025-03-17 13:23:07 +00:00
1611f65455 4.1.14
Some checks failed
Default (tags) / security (push) Successful in 21s
Default (tags) / test (push) Failing after 1m9s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-17 13:19:18 +00:00
c6350e271a fix(ConnectionHandler): Use the correct TLS alert data and increase the delay before socket termination when session resumption without SNI is detected. 2025-03-17 13:19:18 +00:00
0fb5e5ea50 4.1.13
Some checks failed
Default (tags) / security (push) Successful in 29s
Default (tags) / test (push) Failing after 59s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-17 13:15:12 +00:00
35f6739b3c fix(tls-handshake): Set certificate_expired TLS alert level to warning instead of fatal to allow graceful termination. 2025-03-17 13:15:12 +00:00
4634c68ea6 4.1.12
Some checks failed
Default (tags) / security (push) Successful in 30s
Default (tags) / test (push) Failing after 59s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-17 13:09:54 +00:00
e126032b61 fix(classes.pp.connectionhandler): Replace unrecognized_name alert data with certificate_expired alert in TLS handshake handling for session resumption without SNI 2025-03-17 13:09:54 +00:00
7797c799dd 4.1.11
Some checks failed
Default (tags) / security (push) Successful in 37s
Default (tags) / test (push) Failing after 59s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-17 13:00:02 +00:00
e8639e1b01 fix(connectionhandler): Increase delay before cleaning up connections when session resumption is blocked due to missing SNI, allowing more natural socket termination. 2025-03-17 13:00:02 +00:00
60a0ad106d 4.1.10
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Failing after 1m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-16 14:49:25 +00:00
a70c123007 fix(connectionhandler): Increase delay timings for TLS alert transmission in session ticket blocking to allow graceful socket termination 2025-03-16 14:49:25 +00:00
46aa7620b0 4.1.9
Some checks failed
Default (tags) / security (push) Successful in 35s
Default (tags) / test (push) Failing after 1m1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-16 14:13:36 +00:00
f72db86e37 fix(ConnectionHandler): Replace closeNotify alert with handshake failure alert in TLS ClientHello handling to properly signal missing SNI and enforce session ticket restrictions. 2025-03-16 14:13:35 +00:00
d612df107e 4.1.8
Some checks failed
Default (tags) / security (push) Successful in 30s
Default (tags) / test (push) Failing after 1m1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-16 14:02:18 +00:00
1c34578c36 fix(ConnectionHandler/tls): Change the TLS alert sent when a ClientHello lacks SNI: use the close_notify alert instead of handshake_failure to prompt immediate retry with SNI. 2025-03-16 14:02:18 +00:00
1f9943b5a7 4.1.7
Some checks failed
Default (tags) / security (push) Successful in 34s
Default (tags) / test (push) Failing after 59s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-16 13:47:34 +00:00
67ddf97547 fix(classes.pp.connectionhandler): Improve TLS alert handling in ClientHello when SNI is missing and session tickets are disallowed 2025-03-16 13:47:34 +00:00
8a96b45ece 4.1.6
Some checks failed
Default (tags) / security (push) Successful in 29s
Default (tags) / test (push) Failing after 1m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-16 13:28:48 +00:00
2b6464acd5 fix(tls): Refine TLS ClientHello handling when allowSessionTicket is false by replacing extensive alert timeout logic with a concise warning alert and short delay, encouraging immediate client retry with proper SNI 2025-03-16 13:28:48 +00:00
6 changed files with 374 additions and 86 deletions

View File

@ -1,5 +1,84 @@
# Changelog # Changelog
## 2025-03-17 - 4.2.0 - feat(tlsalert)
add sendForceSniSequence and sendFatalAndClose helper functions to TlsAlert for improved SNI enforcement
- Introduce sendForceSniSequence to combine multiple alerts and force clients to provide SNI
- Add sendFatalAndClose to immediately send a fatal alert and close the connection
- Enhance TLS alert handling for better browser compatibility and error management
## 2025-03-17 - 4.1.16 - fix(tls)
Improve TLS alert handling in connection handler: use the new TlsAlert class to send proper unrecognized_name alerts when a ClientHello is missing SNI and wait for a retry on the same connection before closing. Also, add alertFallbackTimeout tracking to connection records for better timeout management.
- Replaced hardcoded alert buffers in ConnectionHandler with calls to the TlsAlert class.
- Removed old warnings and implemented a mechanism to remove existing 'data' listeners and await a new ClientHello.
- Introduced alertFallbackTimeout property in connection records to track fallback timeout and ensure proper cleanup.
- Extended the delay before closing the connection after sending an alert, providing the client more time to retry.
## 2025-03-17 - 4.1.15 - fix(connectionhandler)
Delay socket termination in TLS session resumption handling to allow proper alert processing
- Removed the immediate socket.end() call in finishConnection and moved it inside the setTimeout, ensuring that clients (especially Chrome) have additional time to process the TLS alert before connection termination
- This prevents premature socket closure on ClientHello without SNI when session tickets are disallowed
## 2025-03-17 - 4.1.14 - fix(ConnectionHandler)
Use the correct TLS alert data and increase the delay before socket termination when session resumption without SNI is detected.
- Replaced certificateExpiredAlert with serverNameUnknownAlertData for sending the appropriate alert.
- Increased the cleanup delay from 1000ms to 5000ms to allow a more graceful termination.
## 2025-03-17 - 4.1.13 - fix(tls-handshake)
Set certificate_expired TLS alert level to warning instead of fatal to allow graceful termination.
- In the TLS handshake alert for certificate_expired (0x2F), changed the alert level from 0x02 (fatal) to 0x01 (warning).
- This change avoids abrupt connection termination, enabling a smoother handling of certificate expiration alerts.
## 2025-03-17 - 4.1.12 - fix(classes.pp.connectionhandler)
Replace unrecognized_name alert data with certificate_expired alert in TLS handshake handling for session resumption without SNI
- Switched the alert payload from serverNameUnknownAlertData to a new certificateExpiredAlert buffer
- Now sends a fatal certificate_expired alert (code 47) instead of a warning unrecognized_name alert
- Improves TLS error reporting and encourages immediate disconnection when a ClientHello lacks SNI and session tickets are disallowed
## 2025-03-17 - 4.1.11 - fix(connectionhandler)
Increase delay before cleaning up connections when session resumption is blocked due to missing SNI, allowing more natural socket termination.
- Changed cleanup delay in ts/classes.pp.connectionhandler.ts from 300ms to 1000ms.
- This fix ensures that sockets get sufficient time to terminate gracefully.
## 2025-03-16 - 4.1.10 - fix(connectionhandler)
Increase delay timings for TLS alert transmission in session ticket blocking to allow graceful socket termination
- Updated finishConnection: replaced immediate socket.destroy with a graceful end call
- Increased delay after successful write from 50ms to 200ms to allow alert processing
- Raised safety timeout from 250ms to 400ms when waiting for 'drain' event
## 2025-03-16 - 4.1.9 - fix(ConnectionHandler)
Replace closeNotify alert with handshake failure alert in TLS ClientHello handling to properly signal missing SNI and enforce session ticket restrictions.
- Switched alert data sent on missing SNI from closeNotifyAlert to sslHandshakeFailureAlertData.
- Ensures consistent TLS alert behavior during handshake failure.
## 2025-03-16 - 4.1.8 - fix(ConnectionHandler/tls)
Change the TLS alert sent when a ClientHello lacks SNI: use the close_notify alert instead of handshake_failure to prompt immediate retry with SNI.
- Replaced the previously sent handshake_failure alert (code 0x28) with a close_notify alert (code 0x00) in the TLS session resumption handling in ConnectionHandler.
- This change encourages clients to immediately retry and include SNI when allowSessionTicket is false.
## 2025-03-16 - 4.1.7 - fix(classes.pp.connectionhandler)
Improve TLS alert handling in ClientHello when SNI is missing and session tickets are disallowed
- Replace the unrecognized_name alert with a handshake_failure alert to ensure better client behavior.
- Refactor the alert sending mechanism using cork/uncork and add a safety timeout for the drain event.
- Enhance logging for debugging TLS handshake failures when SNI is absent.
## 2025-03-16 - 4.1.6 - fix(tls)
Refine TLS ClientHello handling when allowSessionTicket is false by replacing extensive alert timeout logic with a concise warning alert and short delay, encouraging immediate client retry with proper SNI
- Update the TLS alert sending mechanism to use cork/uncork and a short, fixed delay instead of long timeouts
- Remove redundant event listeners and excessive cleanup logic after sending the alert
- Improve code clarity and encourage clients (e.g., Chrome) to retry handshake with SNI more responsively
## 2025-03-16 - 4.1.5 - fix(TLS/ConnectionHandler) ## 2025-03-16 - 4.1.5 - fix(TLS/ConnectionHandler)
Improve handling of TLS session resumption without SNI by sending an 'unrecognized_name' alert instead of immediately terminating the connection. This change adds a grace period for the client to retry the handshake with proper SNI and cleans up the connection if no valid response is received. Improve handling of TLS session resumption without SNI by sending an 'unrecognized_name' alert instead of immediately terminating the connection. This change adds a grace period for the client to retry the handshake with proper SNI and cleans up the connection if no valid response is received.

View File

@ -1,6 +1,6 @@
{ {
"name": "@push.rocks/smartproxy", "name": "@push.rocks/smartproxy",
"version": "4.1.5", "version": "4.2.0",
"private": false, "private": false,
"description": "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.", "description": "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.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartproxy', name: '@push.rocks/smartproxy',
version: '4.1.5', version: '4.2.0',
description: '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.' description: '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.'
} }

View File

@ -557,13 +557,13 @@ export class ConnectionHandler {
this.tlsManager.isClientHello(chunk) && this.tlsManager.isClientHello(chunk) &&
!serverName !serverName
) { ) {
// Always block ClientHello without SNI when allowSessionTicket is false // Block ClientHello without SNI when allowSessionTicket is false
console.log( console.log(
`[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` + `[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
`Sending unrecognized_name alert to encourage client to retry with SNI.` `Sending warning unrecognized_name alert to encourage immediate retry with SNI.`
); );
// Set the termination reason first to avoid races // Set the termination reason first
if (record.incomingTerminationReason === null) { if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'session_ticket_blocked_no_sni'; record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
this.connectionManager.incrementTerminationStat( this.connectionManager.incrementTerminationStat(
@ -572,10 +572,9 @@ export class ConnectionHandler {
); );
} }
// Send a proper TLS alert before ending the connection // Create a warning-level alert for unrecognized_name
// Using "unrecognized_name" (112) alert which is a warning level alert (1) // This encourages Chrome to retry immediately with SNI
// that encourages clients to retry with proper SNI const serverNameUnknownAlertData = Buffer.from([
const alertData = Buffer.from([
0x15, // Alert record type 0x15, // Alert record type
0x03, 0x03,
0x03, // TLS 1.2 version 0x03, // TLS 1.2 version
@ -586,88 +585,39 @@ export class ConnectionHandler {
]); ]);
try { try {
// Make sure the alert is sent as a single packet // Use cork/uncork to ensure the alert is sent as a single packet
socket.cork(); socket.cork();
socket.write(alertData); const writeSuccessful = socket.write(serverNameUnknownAlertData);
socket.uncork(); socket.uncork();
// Set a longer timeout to allow the client to properly handle the alert // Function to handle the clean socket termination - but more gradually
// The client might respond by closing the connection or initiating a new handshake const finishConnection = () => {
const alertTimeout = setTimeout(() => { // Give Chrome more time to process the alert before closing
if (!socket.destroyed) { // We won't call destroy() at all - just end() and let the socket close naturally
console.log(`[${connectionId}] Client didn't respond to TLS alert, closing connection gracefully.`);
// Gracefully end the connection
socket.end();
// Only destroy after a delay if it's still hanging
setTimeout(() => {
if (!socket.destroyed) {
console.log(`[${connectionId}] Forcibly closing connection that didn't terminate properly.`);
socket.destroy();
}
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}, 2000);
}
}, 5000); // Give the client 5 seconds to respond to our alert
// Don't let this timeout keep the process alive
if (alertTimeout.unref) {
alertTimeout.unref();
}
// Handle a proper close from the client
socket.once('close', () => {
clearTimeout(alertTimeout);
console.log(`[${connectionId}] Client closed connection after receiving TLS alert.`);
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni_client_closed');
});
// Also handle if the client sends more data (possibly a new handshake with SNI)
socket.once('data', (newChunk) => {
clearTimeout(alertTimeout);
console.log(`[${connectionId}] Client sent new data after TLS alert, checking for SNI...`);
// This would normally be handled by our renegotiation handler, // Log the cleanup but wait for natural closure
// but since we're in a special case, we'll check for SNI again setTimeout(() => {
if (this.tlsManager.isTlsHandshake(newChunk) && this.tlsManager.isClientHello(newChunk)) {
const newServerName = this.tlsManager.extractSNI(newChunk, connInfo);
if (newServerName) {
console.log(`[${connectionId}] Client provided SNI in new handshake: ${newServerName}`);
// Update the record
record.incomingTerminationReason = null;
// Remove termination stats increment
// Note: This is a little hacky as we don't have a proper way to decrement stats
// Process the new handshake with SNI
record.lockedDomain = newServerName;
setupConnection(newServerName, newChunk);
return;
} else {
console.log(`[${connectionId}] Client sent new handshake but still without SNI, closing connection.`);
socket.end();
setTimeout(() => {
if (!socket.destroyed) {
socket.destroy();
}
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni_retry_failed');
}, 500);
}
} else {
console.log(`[${connectionId}] Client sent non-handshake data after TLS alert, closing connection.`);
socket.end(); socket.end();
setTimeout(() => { this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
if (!socket.destroyed) { }, 1000); // Longer delay to let socket cleanup happen naturally
socket.destroy(); };
}
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_invalid_response');
}, 500);
}
});
if (writeSuccessful) {
// Wait longer before ending connection to ensure alert is processed by client
setTimeout(finishConnection, 200); // Increased from 50ms to 200ms
} else {
// If the kernel buffer was full, wait for the drain event
socket.once('drain', () => {
// Wait longer after drain as well
setTimeout(finishConnection, 200);
});
// Safety timeout is increased too
setTimeout(() => {
socket.removeAllListeners('drain');
finishConnection();
}, 400); // Increased from 250ms to 400ms
}
} catch (err) { } catch (err) {
// If we can't send the alert, fall back to immediate termination // If we can't send the alert, fall back to immediate termination
console.log(`[${connectionId}] Error sending TLS alert: ${err.message}`); console.log(`[${connectionId}] Error sending TLS alert: ${err.message}`);
@ -1122,4 +1072,4 @@ export class ConnectionHandler {
} }
}); });
} }
} }

View File

@ -104,6 +104,7 @@ export interface IConnectionRecord {
lockedDomain?: string; // Used to lock this connection to the initial SNI lockedDomain?: string; // Used to lock this connection to the initial SNI
connectionClosed: boolean; // Flag to prevent multiple cleanup attempts connectionClosed: boolean; // Flag to prevent multiple cleanup attempts
cleanupTimer?: NodeJS.Timeout; // Timer for max lifetime/inactivity cleanupTimer?: NodeJS.Timeout; // Timer for max lifetime/inactivity
alertFallbackTimeout?: NodeJS.Timeout; // Timer for fallback after alert
lastActivity: number; // Last activity timestamp for inactivity detection lastActivity: number; // Last activity timestamp for inactivity detection
pendingData: Buffer[]; // Buffer to hold data during connection setup pendingData: Buffer[]; // Buffer to hold data during connection setup
pendingDataSize: number; // Track total size of pending data pendingDataSize: number; // Track total size of pending data

258
ts/classes.pp.tlsalert.ts Normal file
View File

@ -0,0 +1,258 @@
import * as net from 'net';
/**
* TlsAlert class for managing TLS alert messages
*/
export class TlsAlert {
// TLS Alert Levels
static readonly LEVEL_WARNING = 0x01;
static readonly LEVEL_FATAL = 0x02;
// TLS Alert Description Codes - RFC 8446 (TLS 1.3) / RFC 5246 (TLS 1.2)
static readonly CLOSE_NOTIFY = 0x00;
static readonly UNEXPECTED_MESSAGE = 0x0A;
static readonly BAD_RECORD_MAC = 0x14;
static readonly DECRYPTION_FAILED = 0x15; // TLS 1.0 only
static readonly RECORD_OVERFLOW = 0x16;
static readonly DECOMPRESSION_FAILURE = 0x1E; // TLS 1.2 and below
static readonly HANDSHAKE_FAILURE = 0x28;
static readonly NO_CERTIFICATE = 0x29; // SSLv3 only
static readonly BAD_CERTIFICATE = 0x2A;
static readonly UNSUPPORTED_CERTIFICATE = 0x2B;
static readonly CERTIFICATE_REVOKED = 0x2C;
static readonly CERTIFICATE_EXPIRED = 0x2F;
static readonly CERTIFICATE_UNKNOWN = 0x30;
static readonly ILLEGAL_PARAMETER = 0x2F;
static readonly UNKNOWN_CA = 0x30;
static readonly ACCESS_DENIED = 0x31;
static readonly DECODE_ERROR = 0x32;
static readonly DECRYPT_ERROR = 0x33;
static readonly EXPORT_RESTRICTION = 0x3C; // TLS 1.0 only
static readonly PROTOCOL_VERSION = 0x46;
static readonly INSUFFICIENT_SECURITY = 0x47;
static readonly INTERNAL_ERROR = 0x50;
static readonly INAPPROPRIATE_FALLBACK = 0x56;
static readonly USER_CANCELED = 0x5A;
static readonly NO_RENEGOTIATION = 0x64; // TLS 1.2 and below
static readonly MISSING_EXTENSION = 0x6D; // TLS 1.3
static readonly UNSUPPORTED_EXTENSION = 0x6E; // TLS 1.3
static readonly CERTIFICATE_REQUIRED = 0x6F; // TLS 1.3
static readonly UNRECOGNIZED_NAME = 0x70;
static readonly BAD_CERTIFICATE_STATUS_RESPONSE = 0x71;
static readonly BAD_CERTIFICATE_HASH_VALUE = 0x72; // TLS 1.2 and below
static readonly UNKNOWN_PSK_IDENTITY = 0x73;
static readonly CERTIFICATE_REQUIRED_1_3 = 0x74; // TLS 1.3
static readonly 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: number,
description: number,
tlsVersion: [number, number] = [0x03, 0x03]
): Buffer {
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: number): Buffer {
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: number): Buffer {
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: net.Socket,
level: number,
description: number,
closeAfterSend: boolean = false,
closeDelay: number = 200
): Promise<void> {
const alert = this.create(level, description);
return new Promise<void>((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 readonly 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: net.Socket): Promise<void> {
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: net.Socket, closeDelay: number = 200): Promise<void> {
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: net.Socket,
fatal: boolean = false,
closeAfterSend: boolean = true,
closeDelay: number = 200
): Promise<void> {
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: net.Socket): Promise<void> {
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: net.Socket,
description: number,
closeDelay: number = 100
): Promise<void> {
return this.send(socket, this.LEVEL_FATAL, description, true, closeDelay);
}
}