master
py 458 lines 15 KB
Raw
1 # SPDX-License-Identifier: MIT
2 """
3 SSL with SNI_-support for Python 2. Follow these instructions if you would
4 like to verify SSL certificates in Python 2. Note, the default libraries do
5 *not* do certificate checking; you need to do additional work to validate
6 certificates yourself.
7
8 This needs the following packages installed:
9
10 * pyOpenSSL (tested with 16.0.0)
11 * cryptography (minimum 1.3.4, from pyopenssl)
12 * idna (minimum 2.0, from cryptography)
13
14 However, pyopenssl depends on cryptography, which depends on idna, so while we
15 use all three directly here we end up having relatively few packages required.
16
17 You can install them with the following command:
18
19 pip install pyopenssl cryptography idna
20
21 To activate certificate checking, call
22 :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code
23 before you begin making HTTP requests. This can be done in a ``sitecustomize``
24 module, or at any other time before your application begins using ``urllib3``,
25 like this::
26
27 try:
28 import urllib3.contrib.pyopenssl
29 urllib3.contrib.pyopenssl.inject_into_urllib3()
30 except ImportError:
31 pass
32
33 Now you can use :mod:`urllib3` as you normally would, and it will support SNI
34 when the required modules are installed.
35
36 Activating this module also has the positive side effect of disabling SSL/TLS
37 compression in Python 2 (see `CRIME attack`_).
38
39 If you want to configure the default list of supported cipher suites, you can
40 set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.
41
42 .. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
43 .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
44 """
45 from __future__ import absolute_import
46
47 import OpenSSL.SSL
48 from cryptography import x509
49 from cryptography.hazmat.backends.openssl import backend as openssl_backend
50 from cryptography.hazmat.backends.openssl.x509 import _Certificate
51
52 from socket import timeout, error as SocketError
53 from io import BytesIO
54
55 try: # Platform-specific: Python 2
56 from socket import _fileobject
57 except ImportError: # Platform-specific: Python 3
58 _fileobject = None
59 from ..packages.backports.makefile import backport_makefile
60
61 import logging
62 import ssl
63
64 try:
65 import six
66 except ImportError:
67 from ..packages import six
68
69 import sys
70
71 from .. import util
72
73 __all__ = ['inject_into_urllib3', 'extract_from_urllib3']
74
75 # SNI always works.
76 HAS_SNI = True
77
78 # Map from urllib3 to PyOpenSSL compatible parameter-values.
79 _openssl_versions = {
80 ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
81 ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
82 }
83
84 if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'):
85 _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
86
87 if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'):
88 _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
89
90 try:
91 _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
92 except AttributeError:
93 pass
94
95 _stdlib_to_openssl_verify = {
96 ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
97 ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
98 ssl.CERT_REQUIRED:
99 OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
100 }
101 _openssl_to_stdlib_verify = dict(
102 (v, k) for k, v in _stdlib_to_openssl_verify.items()
103 )
104
105 # OpenSSL will only write 16K at a time
106 SSL_WRITE_BLOCKSIZE = 16384
107
108 orig_util_HAS_SNI = util.HAS_SNI
109 orig_util_SSLContext = util.ssl_.SSLContext
110
111
112 log = logging.getLogger(__name__)
113
114
115 def inject_into_urllib3():
116 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.'
117
118 _validate_dependencies_met()
119
120 util.ssl_.SSLContext = PyOpenSSLContext
121 util.HAS_SNI = HAS_SNI
122 util.ssl_.HAS_SNI = HAS_SNI
123 util.IS_PYOPENSSL = True
124 util.ssl_.IS_PYOPENSSL = True
125
126
127 def extract_from_urllib3():
128 'Undo monkey-patching by :func:`inject_into_urllib3`.'
129
130 util.ssl_.SSLContext = orig_util_SSLContext
131 util.HAS_SNI = orig_util_HAS_SNI
132 util.ssl_.HAS_SNI = orig_util_HAS_SNI
133 util.IS_PYOPENSSL = False
134 util.ssl_.IS_PYOPENSSL = False
135
136
137 def _validate_dependencies_met():
138 """
139 Verifies that PyOpenSSL's package-level dependencies have been met.
140 Throws `ImportError` if they are not met.
141 """
142 # Method added in `cryptography==1.1`; not available in older versions
143 from cryptography.x509.extensions import Extensions
144 if getattr(Extensions, "get_extension_for_class", None) is None:
145 raise ImportError("'cryptography' module missing required functionality. "
146 "Try upgrading to v1.3.4 or newer.")
147
148 # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509
149 # attribute is only present on those versions.
150 from OpenSSL.crypto import X509
151 x509 = X509()
152 if getattr(x509, "_x509", None) is None:
153 raise ImportError("'pyOpenSSL' module missing required functionality. "
154 "Try upgrading to v0.14 or newer.")
155
156
157 def _dnsname_to_stdlib(name):
158 """
159 Converts a dNSName SubjectAlternativeName field to the form used by the
160 standard library on the given Python version.
161
162 Cryptography produces a dNSName as a unicode string that was idna-decoded
163 from ASCII bytes. We need to idna-encode that string to get it back, and
164 then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib
165 uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8).
166 """
167 def idna_encode(name):
168 """
169 Borrowed wholesale from the Python Cryptography Project. It turns out
170 that we can't just safely call `idna.encode`: it can explode for
171 wildcard names. This avoids that problem.
172 """
173 import idna
174
175 for prefix in [u'*.', u'.']:
176 if name.startswith(prefix):
177 name = name[len(prefix):]
178 return prefix.encode('ascii') + idna.encode(name)
179 return idna.encode(name)
180
181 name = idna_encode(name)
182 if sys.version_info >= (3, 0):
183 name = name.decode('utf-8')
184 return name
185
186
187 def get_subj_alt_name(peer_cert):
188 """
189 Given an PyOpenSSL certificate, provides all the subject alternative names.
190 """
191 # Pass the cert to cryptography, which has much better APIs for this.
192 # This is technically using private APIs, but should work across all
193 # relevant versions until PyOpenSSL gets something proper for this.
194 cert = _Certificate(openssl_backend, peer_cert._x509)
195
196 # We want to find the SAN extension. Ask Cryptography to locate it (it's
197 # faster than looping in Python)
198 try:
199 ext = cert.extensions.get_extension_for_class(
200 x509.SubjectAlternativeName
201 ).value
202 except x509.ExtensionNotFound:
203 # No such extension, return the empty list.
204 return []
205 except (x509.DuplicateExtension, x509.UnsupportedExtension,
206 x509.UnsupportedGeneralNameType, UnicodeError) as e:
207 # A problem has been found with the quality of the certificate. Assume
208 # no SAN field is present.
209 log.warning(
210 "A problem was encountered with the certificate that prevented "
211 "urllib3 from finding the SubjectAlternativeName field. This can "
212 "affect certificate validation. The error was %s",
213 e,
214 )
215 return []
216
217 # We want to return dNSName and iPAddress fields. We need to cast the IPs
218 # back to strings because the match_hostname function wants them as
219 # strings.
220 # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8
221 # decoded. This is pretty frustrating, but that's what the standard library
222 # does with certificates, and so we need to attempt to do the same.
223 names = [
224 ('DNS', _dnsname_to_stdlib(name))
225 for name in ext.get_values_for_type(x509.DNSName)
226 ]
227 names.extend(
228 ('IP Address', str(name))
229 for name in ext.get_values_for_type(x509.IPAddress)
230 )
231
232 return names
233
234
235 class WrappedSocket(object):
236 '''API-compatibility wrapper for Python OpenSSL's Connection-class.
237
238 Note: _makefile_refs, _drop() and _reuse() are needed for the garbage
239 collector of pypy.
240 '''
241
242 def __init__(self, connection, socket, suppress_ragged_eofs=True):
243 self.connection = connection
244 self.socket = socket
245 self.suppress_ragged_eofs = suppress_ragged_eofs
246 self._makefile_refs = 0
247 self._closed = False
248
249 def fileno(self):
250 return self.socket.fileno()
251
252 # Copy-pasted from Python 3.5 source code
253 def _decref_socketios(self):
254 if self._makefile_refs > 0:
255 self._makefile_refs -= 1
256 if self._closed:
257 self.close()
258
259 def recv(self, *args, **kwargs):
260 try:
261 data = self.connection.recv(*args, **kwargs)
262 except OpenSSL.SSL.SysCallError as e:
263 if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
264 return b''
265 else:
266 raise SocketError(str(e))
267 except OpenSSL.SSL.ZeroReturnError as e:
268 if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
269 return b''
270 else:
271 raise
272 except OpenSSL.SSL.WantReadError:
273 rd = util.wait_for_read(self.socket, self.socket.gettimeout())
274 if not rd:
275 raise timeout('The read operation timed out')
276 else:
277 return self.recv(*args, **kwargs)
278 else:
279 return data
280
281 def recv_into(self, *args, **kwargs):
282 try:
283 return self.connection.recv_into(*args, **kwargs)
284 except OpenSSL.SSL.SysCallError as e:
285 if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
286 return 0
287 else:
288 raise SocketError(str(e))
289 except OpenSSL.SSL.ZeroReturnError as e:
290 if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
291 return 0
292 else:
293 raise
294 except OpenSSL.SSL.WantReadError:
295 rd = util.wait_for_read(self.socket, self.socket.gettimeout())
296 if not rd:
297 raise timeout('The read operation timed out')
298 else:
299 return self.recv_into(*args, **kwargs)
300
301 def settimeout(self, timeout):
302 return self.socket.settimeout(timeout)
303
304 def _send_until_done(self, data):
305 while True:
306 try:
307 return self.connection.send(data)
308 except OpenSSL.SSL.WantWriteError:
309 wr = util.wait_for_write(self.socket, self.socket.gettimeout())
310 if not wr:
311 raise timeout()
312 continue
313 except OpenSSL.SSL.SysCallError as e:
314 raise SocketError(str(e))
315
316 def sendall(self, data):
317 total_sent = 0
318 while total_sent < len(data):
319 sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE])
320 total_sent += sent
321
322 def shutdown(self):
323 # FIXME rethrow compatible exceptions should we ever use this
324 self.connection.shutdown()
325
326 def close(self):
327 if self._makefile_refs < 1:
328 try:
329 self._closed = True
330 return self.connection.close()
331 except OpenSSL.SSL.Error:
332 return
333 else:
334 self._makefile_refs -= 1
335
336 def getpeercert(self, binary_form=False):
337 x509 = self.connection.get_peer_certificate()
338
339 if not x509:
340 return x509
341
342 if binary_form:
343 return OpenSSL.crypto.dump_certificate(
344 OpenSSL.crypto.FILETYPE_ASN1,
345 x509)
346
347 return {
348 'subject': (
349 (('commonName', x509.get_subject().CN),),
350 ),
351 'subjectAltName': get_subj_alt_name(x509)
352 }
353
354 def _reuse(self):
355 self._makefile_refs += 1
356
357 def _drop(self):
358 if self._makefile_refs < 1:
359 self.close()
360 else:
361 self._makefile_refs -= 1
362
363
364 if _fileobject: # Platform-specific: Python 2
365 def makefile(self, mode, bufsize=-1):
366 self._makefile_refs += 1
367 return _fileobject(self, mode, bufsize, close=True)
368 else: # Platform-specific: Python 3
369 makefile = backport_makefile
370
371 WrappedSocket.makefile = makefile
372
373
374 class PyOpenSSLContext(object):
375 """
376 I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible
377 for translating the interface of the standard library ``SSLContext`` object
378 to calls into PyOpenSSL.
379 """
380 def __init__(self, protocol):
381 self.protocol = _openssl_versions[protocol]
382 self._ctx = OpenSSL.SSL.Context(self.protocol)
383 self._options = 0
384 self.check_hostname = False
385
386 @property
387 def options(self):
388 return self._options
389
390 @options.setter
391 def options(self, value):
392 self._options = value
393 self._ctx.set_options(value)
394
395 @property
396 def verify_mode(self):
397 return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()]
398
399 @verify_mode.setter
400 def verify_mode(self, value):
401 self._ctx.set_verify(
402 _stdlib_to_openssl_verify[value],
403 _verify_callback
404 )
405
406 def set_default_verify_paths(self):
407 self._ctx.set_default_verify_paths()
408
409 def set_ciphers(self, ciphers):
410 if isinstance(ciphers, six.text_type):
411 ciphers = ciphers.encode('utf-8')
412 self._ctx.set_cipher_list(ciphers)
413
414 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
415 if cafile is not None:
416 cafile = cafile.encode('utf-8')
417 if capath is not None:
418 capath = capath.encode('utf-8')
419 self._ctx.load_verify_locations(cafile, capath)
420 if cadata is not None:
421 self._ctx.load_verify_locations(BytesIO(cadata))
422
423 def load_cert_chain(self, certfile, keyfile=None, password=None):
424 self._ctx.use_certificate_file(certfile)
425 if password is not None:
426 self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password)
427 self._ctx.use_privatekey_file(keyfile or certfile)
428
429 def wrap_socket(self, sock, server_side=False,
430 do_handshake_on_connect=True, suppress_ragged_eofs=True,
431 server_hostname=None):
432 cnx = OpenSSL.SSL.Connection(self._ctx, sock)
433
434 if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3
435 server_hostname = server_hostname.encode('utf-8')
436
437 if server_hostname is not None:
438 cnx.set_tlsext_host_name(server_hostname)
439
440 cnx.set_connect_state()
441
442 while True:
443 try:
444 cnx.do_handshake()
445 except OpenSSL.SSL.WantReadError:
446 rd = util.wait_for_read(sock, sock.gettimeout())
447 if not rd:
448 raise timeout('select timed out')
449 continue
450 except OpenSSL.SSL.Error as e:
451 raise ssl.SSLError('bad handshake: %r' % e)
452 break
453
454 return WrappedSocket(cnx, sock)
455
456
457 def _verify_callback(cnx, x509, err_no, err_depth, return_code):
458 return err_no == 0