watchd: adaptive ssl for secondary checks
Use SSL error reason from primary handshake to decide whether the secondary check should use SSL or plain HTTP. Protocol errors (proxy can't TLS) fall back to plain HTTP; other failures retry with SSL sans cert verification.
This commit is contained in:
@@ -800,8 +800,9 @@ class TargetTestJob(object):
|
||||
protos = [detected] + [p for p in protos if p != detected]
|
||||
|
||||
# Phase 1: SSL handshake (if ssl_first enabled or SSL-only mode)
|
||||
ssl_reason = None
|
||||
if config.watchd.ssl_first or self.checktype == 'none':
|
||||
result = self._try_ssl_handshake(protos, pool)
|
||||
result, ssl_reason = self._try_ssl_handshake(protos, pool)
|
||||
if result is not None:
|
||||
return result # SSL succeeded or MITM detected
|
||||
# SSL failed for all protocols
|
||||
@@ -811,17 +812,20 @@ class TargetTestJob(object):
|
||||
_dbg('SSL failed, trying secondary check: %s' % self.checktype, ps.proxy)
|
||||
|
||||
# Phase 2: Secondary check (configured checktype)
|
||||
return self._try_secondary_check(protos, pool)
|
||||
return self._try_secondary_check(protos, pool, ssl_reason)
|
||||
|
||||
def _try_ssl_handshake(self, protos, pool):
|
||||
"""Attempt SSL handshake to verify proxy works with TLS.
|
||||
|
||||
Returns:
|
||||
Tuple on success/MITM, None on failure (should try secondary check)
|
||||
(result, ssl_reason) where result is a tuple on success/MITM
|
||||
or None on failure, and ssl_reason is the last SSL error reason
|
||||
string (for secondary check SSL/plain decision).
|
||||
"""
|
||||
ps = self.proxy_state
|
||||
ssl_target = random.choice(ssl_targets)
|
||||
last_error_category = None
|
||||
last_ssl_reason = None
|
||||
|
||||
for proto in protos:
|
||||
if pool:
|
||||
@@ -864,13 +868,19 @@ class TargetTestJob(object):
|
||||
pool.record_success(torhost, elapsed)
|
||||
sock.disconnect()
|
||||
_dbg('SSL handshake OK', ps.proxy)
|
||||
return None, proto, duration, torhost, ssl_target, 0, 1, 'ssl_ok'
|
||||
return (None, proto, duration, torhost, ssl_target, 0, 1, 'ssl_ok'), None
|
||||
|
||||
except rocksock.RocksockException as e:
|
||||
last_error_category = categorize_error(e)
|
||||
et = e.get_errortype()
|
||||
err = e.get_error()
|
||||
|
||||
# Track SSL reason for secondary check decision
|
||||
if et == rocksock.RS_ET_SSL:
|
||||
reason = e.get_failedproxy()
|
||||
if isinstance(reason, str):
|
||||
last_ssl_reason = reason
|
||||
|
||||
try:
|
||||
sock.disconnect()
|
||||
except:
|
||||
@@ -883,7 +893,7 @@ class TargetTestJob(object):
|
||||
if pool:
|
||||
pool.record_success(torhost, elapsed)
|
||||
_dbg('SSL MITM detected', ps.proxy)
|
||||
return None, proto, duration, torhost, ssl_target, 0, 1, 'ssl_mitm'
|
||||
return (None, proto, duration, torhost, ssl_target, 0, 1, 'ssl_mitm'), None
|
||||
|
||||
if config.watchd.debug:
|
||||
_log('SSL handshake failed: %s://%s:%d: %s' % (
|
||||
@@ -899,10 +909,16 @@ class TargetTestJob(object):
|
||||
raise
|
||||
|
||||
# All protocols failed SSL
|
||||
return None
|
||||
return None, last_ssl_reason
|
||||
|
||||
def _try_secondary_check(self, protos, pool):
|
||||
"""Try the configured secondary checktype (head, judges, irc)."""
|
||||
def _try_secondary_check(self, protos, pool, ssl_reason=None):
|
||||
"""Try the configured secondary checktype (head, judges, irc).
|
||||
|
||||
ssl_reason: last SSL error reason from _try_ssl_handshake, used to
|
||||
decide whether to use SSL or plain HTTP for the secondary check.
|
||||
Protocol errors (proxy doesn't speak TLS) -> plain HTTP.
|
||||
Other errors (cert, timeout, etc.) -> SSL without cert verification.
|
||||
"""
|
||||
ps = self.proxy_state
|
||||
_sample_dbg('TEST START: proxy=%s target=%s check=%s' % (
|
||||
ps.proxy, self.target_srv, self.checktype), ps.proxy)
|
||||
@@ -914,13 +930,26 @@ class TargetTestJob(object):
|
||||
else:
|
||||
connect_host = srvname
|
||||
|
||||
# Secondary checks: always use plain HTTP
|
||||
use_ssl = 0
|
||||
# Decide SSL based on why the primary handshake failed:
|
||||
# - protocol error (proxy can't TLS) -> plain HTTP
|
||||
# - other error (cert, timeout) -> SSL without cert verification
|
||||
# - no ssl_reason (ssl_first off) -> plain HTTP (no prior info)
|
||||
protocol_error = is_ssl_protocol_error(ssl_reason) if ssl_reason else True
|
||||
verifycert = False
|
||||
if self.checktype == 'irc':
|
||||
server_port = 6667
|
||||
if protocol_error:
|
||||
use_ssl = 0
|
||||
if self.checktype == 'irc':
|
||||
server_port = 6667
|
||||
else:
|
||||
server_port = 80
|
||||
_dbg('secondary: plain (ssl protocol error)', ps.proxy)
|
||||
else:
|
||||
server_port = 80
|
||||
use_ssl = 1
|
||||
if self.checktype == 'irc':
|
||||
server_port = 6697
|
||||
else:
|
||||
server_port = 443
|
||||
_dbg('secondary: ssl/no-verify (non-protocol ssl error)', ps.proxy)
|
||||
|
||||
last_error_category = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user