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.

This commit is contained in:
2025-03-15 17:00:10 +00:00
parent 677d30563f
commit 252a987344
4 changed files with 81 additions and 218 deletions

View File

@ -172,35 +172,46 @@ export class ConnectionHandler {
// Extract SNI for domain-specific NetworkProxy handling
const serverName = this.tlsManager.extractSNI(chunk, connInfo);
if (serverName) {
// If we got an SNI, check for domain-specific NetworkProxy settings
const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
// Save domain config and SNI in connection record
record.domainConfig = domainConfig;
record.lockedDomain = serverName;
// Use domain-specific NetworkProxy port if configured
if (domainConfig && this.domainConfigManager.shouldUseNetworkProxy(domainConfig)) {
const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
);
}
// Forward to NetworkProxy with domain-specific port
this.networkProxyBridge.forwardToNetworkProxy(
connectionId,
socket,
record,
chunk,
networkProxyPort,
(reason) => this.connectionManager.initiateCleanupOnce(record, reason)
);
return;
// If allowSessionTicket is false and we can't determine SNI, terminate the connection
if (!serverName) {
console.log(
`[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
`Terminating connection to force new TLS handshake with SNI.`
);
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = '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;
}
// Save domain config and SNI in connection record
const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
record.domainConfig = domainConfig;
record.lockedDomain = serverName;
// Use domain-specific NetworkProxy port if configured
if (domainConfig && this.domainConfigManager.shouldUseNetworkProxy(domainConfig)) {
const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
);
}
// Forward to NetworkProxy with domain-specific port
this.networkProxyBridge.forwardToNetworkProxy(
connectionId,
socket,
record,
chunk,
networkProxyPort,
(reason) => this.connectionManager.initiateCleanupOnce(record, reason)
);
return;
}
}
@ -531,6 +542,39 @@ export class ConnectionHandler {
// Extract SNI
serverName = this.tlsManager.extractSNI(chunk, connInfo) || '';
// If allowSessionTicket is false and this is a ClientHello with no SNI, terminate the connection
if (this.settings.allowSessionTicket === false &&
this.tlsManager.isClientHello(chunk) &&
!serverName) {
// Check if this is a session resumption
const resumptionInfo = this.tlsManager.handleSessionResumption(
chunk,
connectionId,
false // No SNI
);
if (resumptionInfo.shouldBlock) {
console.log(
`[${connectionId}] Session resumption without SNI detected and allowSessionTicket=false. ` +
`Terminating connection to force new TLS handshake with SNI.`
);
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = resumptionInfo.reason || 'session_ticket_blocked_no_sni';
this.connectionManager.incrementTerminationStat(
'incoming',
resumptionInfo.reason || 'session_ticket_blocked_no_sni'
);
}
socket.end();
this.connectionManager.cleanupConnection(
record,
resumptionInfo.reason || 'session_ticket_blocked_no_sni'
);
return;
}
}
}
// Lock the connection to the negotiated SNI.