fix(connectionhandler): Send proper TLS alert before terminating connections when SNI is missing and session tickets are disallowed.

This commit is contained in:
Philipp Kunz 2025-03-15 17:16:18 +00:00
parent fe60f88746
commit 97982976c8
3 changed files with 65 additions and 26 deletions

View File

@ -1,5 +1,12 @@
# Changelog # Changelog
## 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

@ -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.2',
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,40 @@ 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
// Don't even check for session resumption - be strict
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
// This helps browsers like Chrome properly recognize the error
const alertData = Buffer.from([
0x15, // Alert record type
0x03, 0x03, // TLS 1.2 version
0x00, 0x02, // Length
0x02, // Fatal alert level
0x40 // Handshake failure alert
]);
try {
socket.write(alertData);
} catch (err) {
// Ignore write errors, we're closing anyway
}
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'); // Add a small delay before ending to allow alert to be sent
setTimeout(() => {
socket.end();
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}, 50);
return; return;
} }
@ -548,32 +572,40 @@ 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( // Don't even check for session resumption - be strict
chunk, console.log(
connectionId, `[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
false // No SNI `Terminating connection to force new TLS handshake with SNI.`
); );
if (resumptionInfo.shouldBlock) { // Send a proper TLS alert before ending the connection
console.log( const alertData = Buffer.from([
`[${connectionId}] Session resumption without SNI detected and allowSessionTicket=false. ` + 0x15, // Alert record type
`Terminating connection to force new TLS handshake with SNI.` 0x03, 0x03, // TLS 1.2 version
); 0x00, 0x02, // Length
if (record.incomingTerminationReason === null) { 0x02, // Fatal alert level
record.incomingTerminationReason = resumptionInfo.reason || 'session_ticket_blocked_no_sni'; 0x40 // Handshake failure alert
this.connectionManager.incrementTerminationStat( ]);
'incoming',
resumptionInfo.reason || 'session_ticket_blocked_no_sni' try {
); socket.write(alertData);
} } catch (err) {
socket.end(); // Ignore write errors, we're closing anyway
this.connectionManager.cleanupConnection(
record,
resumptionInfo.reason || 'session_ticket_blocked_no_sni'
);
return;
} }
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
}
// Add a small delay before ending to allow alert to be sent
setTimeout(() => {
socket.end();
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}, 50);
return;
} }
} }