From 3ab62356b250a403af2255c4d2863c23446c6c6c Mon Sep 17 00:00:00 2001 From: Kimmo Kinnunen Date: Mon, 30 Mar 2015 14:23:00 +0300 Subject: [PATCH] Use TLS SNI extension with pyOpenSSL Use TLS SNI extension in the client, if pyOpenSSL is available. This fixes resource fetches to https servers that require the extension. These are typically cdn servers. Example of such server, without SNI: $ openssl s_client -connect cdn3.vox-cdn.com:443 CONNECTED(00000003) 140434795792032:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:732: ... With SNI: $ openssl s_client -connect cdn3.vox-cdn.com:443 -servername cdn3.vox-cdn.com CONNECTED(00000003) depth=3 C = US, O = "The Go Daddy Group, Inc.", OU = Go Daddy Class 2 Certification Authority .... --- httpclient.py | 50 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/httpclient.py b/httpclient.py index 01c4b39..1510d56 100644 --- a/httpclient.py +++ b/httpclient.py @@ -174,15 +174,54 @@ class DetailedHTTPConnection(httplib.HTTPConnection): """Preserve details relevant to replaying connections.""" response_class = DetailedHTTPResponse - class DetailedHTTPSResponse(DetailedHTTPResponse): """Preserve details relevant to replaying SSL responses.""" pass - -class DetailedHTTPSConnection(httplib.HTTPSConnection): - """Preserve details relevant to replaying SSL connections.""" - response_class = DetailedHTTPSResponse +try: + from distutils.version import StrictVersion + from OpenSSL import SSL + import OpenSSL.version + from socket import _fileobject + if StrictVersion(OpenSSL.__version__) < StrictVersion('0.13'): + raise ImportError('Need pyOpenSSL >= 0.13 for client usage') + + SSLConnection = SSL.Connection + + class FOSSLConnection(SSLConnection): + """A connection object that can be accessed as a file object. + Needed so that httplib can use SSL.Connection. + """ + def makefile(self, mode, bufsize=-1): + return _fileobject(self, mode, bufsize) + + class DetailedHTTPSConnection(DetailedHTTPConnection): + """Preserve details relevant to replaying SSL connections. + A pyOpenSSL implementation of DetailedHTTPConnection. This + supports TLS SNI extension, which is needed for some content + distribution networks.""" + response_class = DetailedHTTPSResponse + default_port = httplib.HTTPS_PORT + def __init__(self, host, port): + DetailedHTTPConnection.__init__(self, host, port) + self.context = SSL.Context(SSL.SSLv23_METHOD) + + def connect(self): + DetailedHTTPConnection.connect(self) + self.sock = FOSSLConnection(self.context, self.sock) + self.sock.set_connect_state() + self.sock.set_tlsext_host_name(self._host_name) + self.sock.do_handshake() + + def set_host_name(self, host_name): + self._host_name = host_name + +except ImportError, e: + class DetailedHTTPSConnection(httplib.HTTPSConnection): + """Preserve details relevant to replaying SSL connections.""" + response_class = DetailedHTTPSResponse + def set_host_name(self, dummy): + pass class RealHttpFetch(object): @@ -293,6 +332,7 @@ def _get_connection(self, request_host, request_port, is_ssl): if is_ssl: connection = DetailedHTTPSConnection(connection_ip, connection_port) + connection.set_host_name(connection_host) if system_proxy: connection.set_tunnel(self, request_host, request_port) else: