Compare commits

...

4 Commits

Author SHA1 Message Date
794e1292e5 4.1.3
Some checks failed
Default (tags) / security (push) Successful in 38s
Default (tags) / test (push) Failing after 1m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-15 18:51:50 +00:00
ee79f9ab7c fix(connectionhandler): Improve handling of TLS ClientHello messages when allowSessionTicket is disabled and no SNI is provided by sending a warning alert (unrecognized_name, code 0x70) with a proper callback and delay to ensure the alert is transmitted before closing the connection. 2025-03-15 18:51:50 +00:00
107bc3b50b 4.1.2
Some checks failed
Default (tags) / security (push) Successful in 30s
Default (tags) / test (push) Failing after 1m2s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-15 17:16:18 +00:00
97982976c8 fix(connectionhandler): Send proper TLS alert before terminating connections when SNI is missing and session tickets are disallowed. 2025-03-15 17:16:18 +00:00
4 changed files with 93 additions and 26 deletions

View File

@ -1,5 +1,19 @@
# Changelog # Changelog
## 2025-03-15 - 4.1.3 - fix(connectionhandler)
Improve handling of TLS ClientHello messages when allowSessionTicket is disabled and no SNI is provided by sending a warning alert (unrecognized_name, code 0x70) with a proper callback and delay to ensure the alert is transmitted before closing the connection.
- Replace the fatal alert (0x02/0x40) with a warning alert (0x01/0x70) to notify clients to send SNI.
- Use socket.write callback to wait 100ms after sending the alert before terminating the connection.
- Remove the previous short (50ms) delay in favor of a more reliable delay mechanism before cleanup.
## 2025-03-15 - 4.1.2 - fix(connectionhandler)
Send proper TLS alert before terminating connections when SNI is missing and session tickets are disallowed.
- Added logic to transmit a fatal TLS alert (Handshake Failure) before closing the connection when no SNI is present with allowSessionTicket=false.
- Introduced a slight 50ms delay after sending the alert to ensure the client receives the alert properly.
- Applied these changes both for the initial ClientHello and when handling subsequent TLS data.
## 2025-03-15 - 4.1.1 - fix(tls) ## 2025-03-15 - 4.1.1 - fix(tls)
Enforce strict SNI handling in TLS connections by terminating ClientHello messages lacking SNI when session tickets are disallowed and removing legacy session cache code. Enforce strict SNI handling in TLS connections by terminating ClientHello messages lacking SNI when session tickets are disallowed and removing legacy session cache code.

View File

@ -1,6 +1,6 @@
{ {
"name": "@push.rocks/smartproxy", "name": "@push.rocks/smartproxy",
"version": "4.1.1", "version": "4.1.3",
"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.1', version: '4.1.3',
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

@ -174,16 +174,50 @@ export class ConnectionHandler {
// If allowSessionTicket is false and we can't determine SNI, terminate the connection // If allowSessionTicket is false and we can't determine SNI, terminate the connection
if (!serverName) { if (!serverName) {
// Always block when allowSessionTicket is false and there's no SNI
console.log( console.log(
`[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` + `[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
`Terminating connection to force new TLS handshake with SNI.` `Terminating connection to force new TLS handshake with SNI.`
); );
// Send a proper TLS alert before ending the connection
// Using "unrecognized_name" (112) alert which is a warning level alert (1)
// that encourages clients to retry with proper SNI
const alertData = Buffer.from([
0x15, // Alert record type
0x03, 0x03, // TLS 1.2 version
0x00, 0x02, // Length
0x01, // Warning alert level (not fatal)
0x70 // unrecognized_name alert (code 112)
]);
try {
socket.write(alertData, () => {
// Only close the socket after we're sure the alert was sent
// Give the alert time to be processed by the client
setTimeout(() => {
socket.end();
// Ensure complete cleanup happens a bit later
setTimeout(() => {
if (!socket.destroyed) {
socket.destroy();
}
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}, 100);
}, 100);
});
} catch (err) {
// If we can't send the alert, fall back to immediate termination
socket.end();
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}
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('incoming', 'session_ticket_blocked_no_sni'); this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
} }
socket.end();
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
return; return;
} }
@ -548,32 +582,51 @@ export class ConnectionHandler {
this.tlsManager.isClientHello(chunk) && this.tlsManager.isClientHello(chunk) &&
!serverName) { !serverName) {
// Check if this is a session resumption // Always block ClientHello without SNI when allowSessionTicket is false
const resumptionInfo = this.tlsManager.handleSessionResumption(
chunk,
connectionId,
false // No SNI
);
if (resumptionInfo.shouldBlock) {
console.log( console.log(
`[${connectionId}] Session resumption without SNI detected and allowSessionTicket=false. ` + `[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
`Terminating connection to force new TLS handshake with SNI.` `Terminating connection to force new TLS handshake with SNI.`
); );
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = resumptionInfo.reason || 'session_ticket_blocked_no_sni'; // Send a proper TLS alert before ending the connection
this.connectionManager.incrementTerminationStat( // Using "unrecognized_name" (112) alert which is a warning level alert (1)
'incoming', // that encourages clients to retry with proper SNI
resumptionInfo.reason || 'session_ticket_blocked_no_sni' const alertData = Buffer.from([
); 0x15, // Alert record type
} 0x03, 0x03, // TLS 1.2 version
0x00, 0x02, // Length
0x01, // Warning alert level (not fatal)
0x70 // unrecognized_name alert (code 112)
]);
try {
socket.write(alertData, () => {
// Only close the socket after we're sure the alert was sent
// Give the alert time to be processed by the client
setTimeout(() => {
socket.end(); socket.end();
this.connectionManager.cleanupConnection(
record, // Ensure complete cleanup happens a bit later
resumptionInfo.reason || 'session_ticket_blocked_no_sni' setTimeout(() => {
); if (!socket.destroyed) {
return; socket.destroy();
} }
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}, 100);
}, 100);
});
} catch (err) {
// If we can't send the alert, fall back to immediate termination
socket.end();
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
}
return;
} }
} }