Summary
KiteTicker establishes its WebSocket TLS connection using Twisted's legacy ssl.ClientContextFactory(). This context factory does not send the TLS SNI (Server Name Indication) extension in the ClientHello, and it performs no certificate hostname verification.
This breaks any environment where outbound traffic passes through a firewall/proxy that allows hosts based on SNI (AWS Network Firewall, Squid with SSL-bump/peek, many corporate egress filters). With no server_name in the ClientHello, the SNI-based allow rule never matches, the TLS handshake is dropped, and the connection fails with:
Connection error: 1006 - connection was closed uncleanly
(WebSocket opening handshake timeout (peer did not finish the opening handshake in time))
Environment
kiteconnect (current master)
- Python 3.x, Twisted + Autobahn
- Host behind an egress firewall that whitelists
*.kite.trade by TLS SNI
Steps to reproduce
from kiteconnect import KiteTicker
kws = KiteTicker("api_key", "access_token")
kws.connect() # times out behind an SNI-based egress firewall
Diagnostic showing the connection works only when SNI is present:
# WITH SNI — succeeds
echo | openssl s_client -connect ws.kite.trade:443 -servername ws.kite.trade
# WITHOUT SNI — what KiteTicker does today — hangs / dropped by SNI firewall
echo | timeout 15 openssl s_client -connect ws.kite.trade:443 -noservername
A plain curl https://ws.kite.trade/ succeeds from the same host because curl sends SNI — confirming the network path is fine and the issue is the missing SNI extension from the client.
Root cause
In kiteconnect/ticker.py, _create_connection builds the TLS context with:
from twisted.internet import reactor, ssl
...
context_factory = None
if self.factory.isSecure and not disable_ssl_verification:
context_factory = ssl.ClientContextFactory()
ssl.ClientContextFactory is Twisted's old IOpenSSLContextFactory. It returns a bare SSL.Context and never calls set_tlsext_host_name, so:
- No SNI is sent → SNI-based firewalls drop the handshake.
- No server certificate identity verification is performed (a separate security concern — the connection is encrypted but not authenticated against the hostname).
Proposed fix
Use twisted.internet.ssl.optionsForClientTLS(hostname), which is the modern Twisted client TLS API. It sends SNI and verifies the server certificate against the hostname. connectWS / reactor.connectSSL accept the connection creator it returns.
# kiteconnect/ticker.py — in _create_connection
from twisted.internet import reactor, ssl
try:
from urllib.parse import urlparse
except ImportError: # py2 fallback, if still supported
from urlparse import urlparse
...
context_factory = None
if self.factory.isSecure and not disable_ssl_verification:
# Use optionsForClientTLS so the ClientHello carries SNI and the
# server certificate is verified against the hostname. The legacy
# ssl.ClientContextFactory() sends no SNI, which breaks egress
# firewalls/proxies that allow hosts by SNI.
hostname = urlparse(self.factory.url).hostname
context_factory = ssl.optionsForClientTLS(hostname)
Notes:
optionsForClientTLS requires service_identity (already a transitive dependency of a TLS-enabled Twisted install via Twisted[tls]); if not, it should be added to install requirements.
- The
*.kite.trade certificate matches ws.kite.trade, so hostname verification passes for the default endpoint.
- Deriving the hostname from
self.factory.url keeps it correct when a custom root is supplied.
- The existing
disable_ssl_verification=True path is preserved unchanged for users who need it.
Workaround (until fixed)
For anyone hitting this now, monkeypatch the context factory before connecting:
import kiteconnect.ticker as ticker
from twisted.internet import ssl as _ssl
class _SNIContextFactory:
def __new__(cls):
return _ssl.optionsForClientTLS("ws.kite.trade")
ticker.ssl.ClientContextFactory = _SNIContextFactory
Impact
Without SNI, pykiteconnect cannot be used from any deployment behind SNI-based egress filtering (increasingly common in regulated/production setups). Adding SNI also closes a TLS hostname-verification gap.
Summary
KiteTickerestablishes its WebSocket TLS connection using Twisted's legacyssl.ClientContextFactory(). This context factory does not send the TLS SNI (Server Name Indication) extension in the ClientHello, and it performs no certificate hostname verification.This breaks any environment where outbound traffic passes through a firewall/proxy that allows hosts based on SNI (AWS Network Firewall, Squid with SSL-bump/peek, many corporate egress filters). With no
server_namein the ClientHello, the SNI-based allow rule never matches, the TLS handshake is dropped, and the connection fails with:Environment
kiteconnect(currentmaster)*.kite.tradeby TLS SNISteps to reproduce
Diagnostic showing the connection works only when SNI is present:
A plain
curl https://ws.kite.trade/succeeds from the same host because curl sends SNI — confirming the network path is fine and the issue is the missing SNI extension from the client.Root cause
In
kiteconnect/ticker.py,_create_connectionbuilds the TLS context with:ssl.ClientContextFactoryis Twisted's oldIOpenSSLContextFactory. It returns a bareSSL.Contextand never callsset_tlsext_host_name, so:Proposed fix
Use
twisted.internet.ssl.optionsForClientTLS(hostname), which is the modern Twisted client TLS API. It sends SNI and verifies the server certificate against the hostname.connectWS/reactor.connectSSLaccept the connection creator it returns.Notes:
optionsForClientTLSrequiresservice_identity(already a transitive dependency of a TLS-enabled Twisted install viaTwisted[tls]); if not, it should be added to install requirements.*.kite.tradecertificate matchesws.kite.trade, so hostname verification passes for the default endpoint.self.factory.urlkeeps it correct when a customrootis supplied.disable_ssl_verification=Truepath is preserved unchanged for users who need it.Workaround (until fixed)
For anyone hitting this now, monkeypatch the context factory before connecting:
Impact
Without SNI,
pykiteconnectcannot be used from any deployment behind SNI-based egress filtering (increasingly common in regulated/production setups). Adding SNI also closes a TLS hostname-verification gap.