comparison env/lib/python3.7/site-packages/urllib3/contrib/pyopenssl.py @ 0:26e78fe6e8c4 draft

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