1 '''SSL with SNI_-support for Python 2. Follow these instructions if you would
2 like to verify SSL certificates in Python 2. Note, the default libraries do
3 *not* do certificate checking; you need to do additional work to validate
6 This needs the following packages installed:
8 * pyOpenSSL (tested with 0.13)
9 * ndg-httpsclient (tested with 0.3.2)
10 * pyasn1 (tested with 0.1.6)
12 You can install them with the following command:
14 pip install pyopenssl ndg-httpsclient pyasn1
16 To activate certificate checking, call
17 :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code
18 before you begin making HTTP requests. This can be done in a ``sitecustomize``
19 module, or at any other time before your application begins using ``urllib3``,
23 import urllib3.contrib.pyopenssl
24 urllib3.contrib.pyopenssl.inject_into_urllib3()
28 Now you can use :mod:`urllib3` as you normally would, and it will support SNI
29 when the required modules are installed.
31 Activating this module also has the positive side effect of disabling SSL/TLS
32 compression in Python 2 (see `CRIME attack`_).
34 If you want to configure the default list of supported cipher suites, you can
35 set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.
40 :var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites.
42 .. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
43 .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
46 from __future__ import absolute_import
49 from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
50 from ndg.httpsclient.subj_alt_name import SubjectAltName as BaseSubjectAltName
51 except SyntaxError as e:
55 from pyasn1.codec.der import decoder as der_decoder
56 from pyasn1.type import univ, constraint
57 from socket import timeout, error as SocketError
59 try: # Platform-specific: Python 2
60 from socket import _fileobject
61 except ImportError: # Platform-specific: Python 3
63 from urllib3.packages.backports.makefile import backport_makefile
69 from .. import connection
72 __all__ = ['inject_into_urllib3', 'extract_from_urllib3']
74 # SNI only *really* works if we can read the subjectAltName of certificates.
75 HAS_SNI = SUBJ_ALT_NAME_SUPPORT
77 # Map from urllib3 to PyOpenSSL compatible parameter-values.
79 ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
80 ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
83 if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'):
84 _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
86 if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'):
87 _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
90 _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
91 except AttributeError:
95 ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
96 ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
98 OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
101 DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS.encode('ascii')
103 # OpenSSL will only write 16K at a time
104 SSL_WRITE_BLOCKSIZE = 16384
106 orig_util_HAS_SNI = util.HAS_SNI
107 orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket
110 def inject_into_urllib3():
111 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.'
113 connection.ssl_wrap_socket = ssl_wrap_socket
114 util.HAS_SNI = HAS_SNI
115 util.IS_PYOPENSSL = True
118 def extract_from_urllib3():
119 'Undo monkey-patching by :func:`inject_into_urllib3`.'
121 connection.ssl_wrap_socket = orig_connection_ssl_wrap_socket
122 util.HAS_SNI = orig_util_HAS_SNI
123 util.IS_PYOPENSSL = False
126 # Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
127 class SubjectAltName(BaseSubjectAltName):
128 '''ASN.1 implementation for subjectAltNames support'''
130 # There is no limit to how many SAN certificates a certificate may have,
131 # however this needs to have some limit so we'll set an arbitrarily high
133 sizeSpec = univ.SequenceOf.sizeSpec + \
134 constraint.ValueSizeConstraint(1, 1024)
137 # Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
138 def get_subj_alt_name(peer_cert):
139 # Search through extensions
141 if not SUBJ_ALT_NAME_SUPPORT:
144 general_names = SubjectAltName()
145 for i in range(peer_cert.get_extension_count()):
146 ext = peer_cert.get_extension(i)
147 ext_name = ext.get_short_name()
148 if ext_name != b'subjectAltName':
151 # PyOpenSSL returns extension data in ASN.1 encoded form
152 ext_dat = ext.get_data()
153 decoded_dat = der_decoder.decode(ext_dat,
154 asn1Spec=general_names)
156 for name in decoded_dat:
157 if not isinstance(name, SubjectAltName):
159 for entry in range(len(name)):
160 component = name.getComponentByPosition(entry)
161 if component.getName() != 'dNSName':
163 dns_name.append(str(component.getComponent()))
168 class WrappedSocket(object):
169 '''API-compatibility wrapper for Python OpenSSL's Connection-class.
171 Note: _makefile_refs, _drop() and _reuse() are needed for the garbage
175 def __init__(self, connection, socket, suppress_ragged_eofs=True):
176 self.connection = connection
178 self.suppress_ragged_eofs = suppress_ragged_eofs
179 self._makefile_refs = 0
183 return self.socket.fileno()
185 # Copy-pasted from Python 3.5 source code
186 def _decref_socketios(self):
187 if self._makefile_refs > 0:
188 self._makefile_refs -= 1
192 def recv(self, *args, **kwargs):
194 data = self.connection.recv(*args, **kwargs)
195 except OpenSSL.SSL.SysCallError as e:
196 if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
199 raise SocketError(str(e))
200 except OpenSSL.SSL.ZeroReturnError as e:
201 if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
205 except OpenSSL.SSL.WantReadError:
206 rd, wd, ed = select.select(
207 [self.socket], [], [], self.socket.gettimeout())
209 raise timeout('The read operation timed out')
211 return self.recv(*args, **kwargs)
215 def recv_into(self, *args, **kwargs):
217 return self.connection.recv_into(*args, **kwargs)
218 except OpenSSL.SSL.SysCallError as e:
219 if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
222 raise SocketError(str(e))
223 except OpenSSL.SSL.ZeroReturnError as e:
224 if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
228 except OpenSSL.SSL.WantReadError:
229 rd, wd, ed = select.select(
230 [self.socket], [], [], self.socket.gettimeout())
232 raise timeout('The read operation timed out')
234 return self.recv_into(*args, **kwargs)
236 def settimeout(self, timeout):
237 return self.socket.settimeout(timeout)
239 def _send_until_done(self, data):
242 return self.connection.send(data)
243 except OpenSSL.SSL.WantWriteError:
244 _, wlist, _ = select.select([], [self.socket], [],
245 self.socket.gettimeout())
250 def sendall(self, data):
252 while total_sent < len(data):
253 sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE])
257 # FIXME rethrow compatible exceptions should we ever use this
258 self.connection.shutdown()
261 if self._makefile_refs < 1:
264 return self.connection.close()
265 except OpenSSL.SSL.Error:
268 self._makefile_refs -= 1
270 def getpeercert(self, binary_form=False):
271 x509 = self.connection.get_peer_certificate()
277 return OpenSSL.crypto.dump_certificate(
278 OpenSSL.crypto.FILETYPE_ASN1,
283 (('commonName', x509.get_subject().CN),),
287 for value in get_subj_alt_name(x509)
292 self._makefile_refs += 1
295 if self._makefile_refs < 1:
298 self._makefile_refs -= 1
301 if _fileobject: # Platform-specific: Python 2
302 def makefile(self, mode, bufsize=-1):
303 self._makefile_refs += 1
304 return _fileobject(self, mode, bufsize, close=True)
305 else: # Platform-specific: Python 3
306 makefile = backport_makefile
308 WrappedSocket.makefile = makefile
311 def _verify_callback(cnx, x509, err_no, err_depth, return_code):
315 def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
316 ca_certs=None, server_hostname=None,
317 ssl_version=None, ca_cert_dir=None):
318 ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version])
320 keyfile = keyfile or certfile # Match behaviour of the normal python ssl library
321 ctx.use_certificate_file(certfile)
323 ctx.use_privatekey_file(keyfile)
324 if cert_reqs != ssl.CERT_NONE:
325 ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback)
326 if ca_certs or ca_cert_dir:
328 ctx.load_verify_locations(ca_certs, ca_cert_dir)
329 except OpenSSL.SSL.Error as e:
330 raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e)
332 ctx.set_default_verify_paths()
334 # Disable TLS compression to mitigate CRIME attack (issue #309)
335 OP_NO_COMPRESSION = 0x20000
336 ctx.set_options(OP_NO_COMPRESSION)
338 # Set list of supported ciphersuites.
339 ctx.set_cipher_list(DEFAULT_SSL_CIPHER_LIST)
341 cnx = OpenSSL.SSL.Connection(ctx, sock)
342 if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3
343 server_hostname = server_hostname.encode('utf-8')
344 cnx.set_tlsext_host_name(server_hostname)
345 cnx.set_connect_state()
349 except OpenSSL.SSL.WantReadError:
350 rd, _, _ = select.select([sock], [], [], sock.gettimeout())
352 raise timeout('select timed out')
354 except OpenSSL.SSL.Error as e:
355 raise ssl.SSLError('bad handshake: %r' % e)
358 return WrappedSocket(cnx, sock)