1 '''SSL with SNI_-support for Python 2.
3 This needs the following packages installed:
5 * pyOpenSSL (tested with 0.13)
6 * ndg-httpsclient (tested with 0.3.2)
7 * pyasn1 (tested with 0.1.6)
9 To activate it call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3`.
10 This can be done in a ``sitecustomize`` module, or at any other time before
11 your application begins using ``urllib3``, like this::
14 import urllib3.contrib.pyopenssl
15 urllib3.contrib.pyopenssl.inject_into_urllib3()
19 Now you can use :mod:`urllib3` as you normally would, and it will support SNI
20 when the required modules are installed.
22 Activating this module also has the positive side effect of disabling SSL/TLS
23 encryption in Python 2 (see `CRIME attack`_).
25 If you want to configure the default list of supported cipher suites, you can
26 set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.
31 :var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites.
32 Default: ``EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256
33 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA EECDH RC4 !aNULL !eNULL !LOW !3DES
34 !MD5 !EXP !PSK !SRP !DSS'``
36 .. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
37 .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
41 from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
42 from ndg.httpsclient.subj_alt_name import SubjectAltName as BaseSubjectAltName
44 from pyasn1.codec.der import decoder as der_decoder
45 from pyasn1.type import univ, constraint
46 from socket import _fileobject
49 from cStringIO import StringIO
51 from .. import connection
54 __all__ = ['inject_into_urllib3', 'extract_from_urllib3']
56 # SNI only *really* works if we can read the subjectAltName of certificates.
57 HAS_SNI = SUBJ_ALT_NAME_SUPPORT
59 # Map from urllib3 to PyOpenSSL compatible parameter-values.
61 ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
62 ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD,
63 ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
66 ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
67 ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
68 ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER
69 + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
72 # Default SSL/TLS cipher list.
73 # Recommendation by https://community.qualys.com/blogs/securitylabs/2013/08/05/
74 # configuring-apache-nginx-and-openssl-for-forward-secrecy
75 DEFAULT_SSL_CIPHER_LIST = 'EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM ' + \
76 'EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA ' + \
77 'EECDH RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS'
80 orig_util_HAS_SNI = util.HAS_SNI
81 orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket
84 def inject_into_urllib3():
85 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.'
87 connection.ssl_wrap_socket = ssl_wrap_socket
88 util.HAS_SNI = HAS_SNI
91 def extract_from_urllib3():
92 'Undo monkey-patching by :func:`inject_into_urllib3`.'
94 connection.ssl_wrap_socket = orig_connection_ssl_wrap_socket
95 util.HAS_SNI = orig_util_HAS_SNI
98 ### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
99 class SubjectAltName(BaseSubjectAltName):
100 '''ASN.1 implementation for subjectAltNames support'''
102 # There is no limit to how many SAN certificates a certificate may have,
103 # however this needs to have some limit so we'll set an arbitrarily high
105 sizeSpec = univ.SequenceOf.sizeSpec + \
106 constraint.ValueSizeConstraint(1, 1024)
109 ### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
110 def get_subj_alt_name(peer_cert):
111 # Search through extensions
113 if not SUBJ_ALT_NAME_SUPPORT:
116 general_names = SubjectAltName()
117 for i in range(peer_cert.get_extension_count()):
118 ext = peer_cert.get_extension(i)
119 ext_name = ext.get_short_name()
120 if ext_name != 'subjectAltName':
123 # PyOpenSSL returns extension data in ASN.1 encoded form
124 ext_dat = ext.get_data()
125 decoded_dat = der_decoder.decode(ext_dat,
126 asn1Spec=general_names)
128 for name in decoded_dat:
129 if not isinstance(name, SubjectAltName):
131 for entry in range(len(name)):
132 component = name.getComponentByPosition(entry)
133 if component.getName() != 'dNSName':
135 dns_name.append(str(component.getComponent()))
140 class fileobject(_fileobject):
142 def read(self, size=-1):
143 # Use max, disallow tiny reads in a loop as they are very inefficient.
144 # We never leave read() with any leftover data from a new recv() call
145 # in our internal buffer.
146 rbufsize = max(self._rbufsize, self.default_bufsize)
147 # Our use of StringIO rather than lists of string objects returned by
148 # recv() minimizes memory usage and fragmentation that occurs when
149 # rbufsize is large compared to the typical return value of recv().
151 buf.seek(0, 2) # seek end
154 self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
157 data = self._sock.recv(rbufsize)
158 except OpenSSL.SSL.WantReadError:
163 return buf.getvalue()
165 # Read until size bytes or EOF seen, whichever comes first
168 # Already have size bytes in our buffer? Extract and return.
171 self._rbuf = StringIO()
172 self._rbuf.write(buf.read())
175 self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
177 left = size - buf_len
178 # recv() will malloc the amount of memory given as its
179 # parameter even though it often returns much less data
180 # than that. The returned data string is short lived
181 # as we copy it into a StringIO and free it. This avoids
182 # fragmentation issues on many platforms.
184 data = self._sock.recv(left)
185 except OpenSSL.SSL.WantReadError:
190 if n == size and not buf_len:
191 # Shortcut. Avoid buffer data copies when:
192 # - We have no data in our buffer.
194 # - Our call to recv returned exactly the
195 # number of bytes we were asked to read.
199 del data # explicit free
201 assert n <= left, "recv(%d) returned %d bytes" % (left, n)
204 del data # explicit free
205 #assert buf_len == buf.tell()
206 return buf.getvalue()
208 def readline(self, size=-1):
210 buf.seek(0, 2) # seek end
212 # check if we already have it in our buffer
214 bline = buf.readline(size)
215 if bline.endswith('\n') or len(bline) == size:
216 self._rbuf = StringIO()
217 self._rbuf.write(buf.read())
221 # Read until \n or EOF, whichever comes first
222 if self._rbufsize <= 1:
223 # Speed up unbuffered case
225 buffers = [buf.read()]
226 self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
228 recv = self._sock.recv
236 except OpenSSL.SSL.WantReadError:
239 return "".join(buffers)
241 buf.seek(0, 2) # seek end
242 self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
245 data = self._sock.recv(self._rbufsize)
246 except OpenSSL.SSL.WantReadError:
254 self._rbuf.write(data[nl:])
258 return buf.getvalue()
260 # Read until size bytes or \n or EOF seen, whichever comes first
261 buf.seek(0, 2) # seek end
266 self._rbuf = StringIO()
267 self._rbuf.write(buf.read())
269 self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
272 data = self._sock.recv(self._rbufsize)
273 except OpenSSL.SSL.WantReadError:
277 left = size - buf_len
278 # did we just receive a newline?
279 nl = data.find('\n', 0, left)
282 # save the excess data to _rbuf
283 self._rbuf.write(data[nl:])
288 # Shortcut. Avoid data copy through buf when returning
289 # a substring of our first recv().
292 if n == size and not buf_len:
293 # Shortcut. Avoid data copy through buf when
294 # returning exactly all of our first recv().
297 buf.write(data[:left])
298 self._rbuf.write(data[left:])
302 #assert buf_len == buf.tell()
303 return buf.getvalue()
306 class WrappedSocket(object):
307 '''API-compatibility wrapper for Python OpenSSL's Connection-class.'''
309 def __init__(self, connection, socket):
310 self.connection = connection
314 return self.socket.fileno()
316 def makefile(self, mode, bufsize=-1):
317 return fileobject(self.connection, mode, bufsize)
319 def settimeout(self, timeout):
320 return self.socket.settimeout(timeout)
322 def sendall(self, data):
323 return self.connection.sendall(data)
326 return self.connection.shutdown()
328 def getpeercert(self, binary_form=False):
329 x509 = self.connection.get_peer_certificate()
335 return OpenSSL.crypto.dump_certificate(
336 OpenSSL.crypto.FILETYPE_ASN1,
341 (('commonName', x509.get_subject().CN),),
345 for value in get_subj_alt_name(x509)
350 def _verify_callback(cnx, x509, err_no, err_depth, return_code):
354 def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
355 ca_certs=None, server_hostname=None,
357 ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version])
359 ctx.use_certificate_file(certfile)
361 ctx.use_privatekey_file(keyfile)
362 if cert_reqs != ssl.CERT_NONE:
363 ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback)
366 ctx.load_verify_locations(ca_certs, None)
367 except OpenSSL.SSL.Error as e:
368 raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e)
370 # Disable TLS compression to migitate CRIME attack (issue #309)
371 OP_NO_COMPRESSION = 0x20000
372 ctx.set_options(OP_NO_COMPRESSION)
374 # Set list of supported ciphersuites.
375 ctx.set_cipher_list(DEFAULT_SSL_CIPHER_LIST)
377 cnx = OpenSSL.SSL.Connection(ctx, sock)
378 cnx.set_tlsext_host_name(server_hostname)
379 cnx.set_connect_state()
383 except OpenSSL.SSL.WantReadError:
384 select.select([sock], [], [])
386 except OpenSSL.SSL.Error as e:
387 raise ssl.SSLError('bad handshake', e)
390 return WrappedSocket(cnx, sock)