codekingpro/portable-devtools
114k
1"""2TLS with SNI_-support for Python 2. Follow these instructions if you would3like to verify TLS certificates in Python 2. Note, the default libraries do4*not* do certificate checking; you need to do additional work to validate5certificates yourself.6 7This 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 13However, pyopenssl depends on cryptography, which depends on idna, so while we14use all three directly here we end up having relatively few packages required.15 16You can install them with the following command:17 18.. code-block:: bash19 20 $ python -m pip install pyopenssl cryptography idna21 22To activate certificate checking, call23:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code24before you begin making HTTP requests. This can be done in a ``sitecustomize``25module, or at any other time before your application begins using ``urllib3``,26like this:27 28.. code-block:: python29 30 try:31 import urllib3.contrib.pyopenssl32 urllib3.contrib.pyopenssl.inject_into_urllib3()33 except ImportError:34 pass35 36Now you can use :mod:`urllib3` as you normally would, and it will support SNI37when the required modules are installed.38 39Activating this module also has the positive side effect of disabling SSL/TLS40compression in Python 2 (see `CRIME attack`_).41 42.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication43.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)44.. _pyopenssl: https://www.pyopenssl.org45.. _cryptography: https://cryptography.io46.. _idna: https://github.com/kjd/idna47"""48from __future__ import absolute_import49 50import OpenSSL.crypto51import OpenSSL.SSL52from cryptography import x50953from cryptography.hazmat.backends.openssl import backend as openssl_backend54 55try:56 from cryptography.x509 import UnsupportedExtension57except ImportError:58 # UnsupportedExtension is gone in cryptography >= 2.1.059 class UnsupportedExtension(Exception):60 pass61 62 63from io import BytesIO64from socket import error as SocketError65from socket import timeout66 67try: # Platform-specific: Python 268 from socket import _fileobject69except ImportError: # Platform-specific: Python 370 _fileobject = None71 from ..packages.backports.makefile import backport_makefile72 73import logging74import ssl75import sys76import warnings77 78from .. import util79from ..packages import six80from ..util.ssl_ import PROTOCOL_TLS_CLIENT81 82warnings.warn(83 "'urllib3.contrib.pyopenssl' module is deprecated and will be removed "84 "in a future release of urllib3 2.x. Read more in this issue: "85 "https://github.com/urllib3/urllib3/issues/2680",86 category=DeprecationWarning,87 stacklevel=2,88)89 90__all__ = ["inject_into_urllib3", "extract_from_urllib3"]91 92# SNI always works.93HAS_SNI = True94 95# Map from urllib3 to PyOpenSSL compatible parameter-values.96_openssl_versions = {97 util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD,98 PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD,99 ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,100}101 102if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"):103 _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD104 105if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"):106 _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD107 108if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"):109 _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD110 111 112_stdlib_to_openssl_verify = {113 ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,114 ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,115 ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER116 + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,117}118_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items())119 120# OpenSSL will only write 16K at a time121SSL_WRITE_BLOCKSIZE = 16384122 123orig_util_HAS_SNI = util.HAS_SNI124orig_util_SSLContext = util.ssl_.SSLContext125 126 127log = logging.getLogger(__name__)128 129 130def inject_into_urllib3():131 "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support."132 133 _validate_dependencies_met()134 135 util.SSLContext = PyOpenSSLContext136 util.ssl_.SSLContext = PyOpenSSLContext137 util.HAS_SNI = HAS_SNI138 util.ssl_.HAS_SNI = HAS_SNI139 util.IS_PYOPENSSL = True140 util.ssl_.IS_PYOPENSSL = True141 142 143def extract_from_urllib3():144 "Undo monkey-patching by :func:`inject_into_urllib3`."145 146 util.SSLContext = orig_util_SSLContext147 util.ssl_.SSLContext = orig_util_SSLContext148 util.HAS_SNI = orig_util_HAS_SNI149 util.ssl_.HAS_SNI = orig_util_HAS_SNI150 util.IS_PYOPENSSL = False151 util.ssl_.IS_PYOPENSSL = False152 153 154def _validate_dependencies_met():155 """156 Verifies that PyOpenSSL's package-level dependencies have been met.157 Throws `ImportError` if they are not met.158 """159 # Method added in `cryptography==1.1`; not available in older versions160 from cryptography.x509.extensions import Extensions161 162 if getattr(Extensions, "get_extension_for_class", None) is None:163 raise ImportError(164 "'cryptography' module missing required functionality. "165 "Try upgrading to v1.3.4 or newer."166 )167 168 # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509169 # attribute is only present on those versions.170 from OpenSSL.crypto import X509171 172 x509 = X509()173 if getattr(x509, "_x509", None) is None:174 raise ImportError(175 "'pyOpenSSL' module missing required functionality. "176 "Try upgrading to v0.14 or newer."177 )178 179 180def _dnsname_to_stdlib(name):181 """182 Converts a dNSName SubjectAlternativeName field to the form used by the183 standard library on the given Python version.184 185 Cryptography produces a dNSName as a unicode string that was idna-decoded186 from ASCII bytes. We need to idna-encode that string to get it back, and187 then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib188 uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8).189 190 If the name cannot be idna-encoded then we return None signalling that191 the name given should be skipped.192 """193 194 def idna_encode(name):195 """196 Borrowed wholesale from the Python Cryptography Project. It turns out197 that we can't just safely call `idna.encode`: it can explode for198 wildcard names. This avoids that problem.199 """200 import idna201 202 try:203 for prefix in [u"*.", u"."]:204 if name.startswith(prefix):205 name = name[len(prefix) :]206 return prefix.encode("ascii") + idna.encode(name)207 return idna.encode(name)208 except idna.core.IDNAError:209 return None210 211 # Don't send IPv6 addresses through the IDNA encoder.212 if ":" in name:213 return name214 215 name = idna_encode(name)216 if name is None:217 return None218 elif sys.version_info >= (3, 0):219 name = name.decode("utf-8")220 return name221 222 223def get_subj_alt_name(peer_cert):224 """225 Given an PyOpenSSL certificate, provides all the subject alternative names.226 """227 # Pass the cert to cryptography, which has much better APIs for this.228 if hasattr(peer_cert, "to_cryptography"):229 cert = peer_cert.to_cryptography()230 else:231 der = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, peer_cert)232 cert = x509.load_der_x509_certificate(der, openssl_backend)233 234 # We want to find the SAN extension. Ask Cryptography to locate it (it's235 # faster than looping in Python)236 try:237 ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value238 except x509.ExtensionNotFound:239 # No such extension, return the empty list.240 return []241 except (242 x509.DuplicateExtension,243 UnsupportedExtension,244 x509.UnsupportedGeneralNameType,245 UnicodeError,246 ) as e:247 # A problem has been found with the quality of the certificate. Assume248 # no SAN field is present.249 log.warning(250 "A problem was encountered with the certificate that prevented "251 "urllib3 from finding the SubjectAlternativeName field. This can "252 "affect certificate validation. The error was %s",253 e,254 )255 return []256 257 # We want to return dNSName and iPAddress fields. We need to cast the IPs258 # back to strings because the match_hostname function wants them as259 # strings.260 # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8261 # decoded. This is pretty frustrating, but that's what the standard library262 # does with certificates, and so we need to attempt to do the same.263 # We also want to skip over names which cannot be idna encoded.264 names = [265 ("DNS", name)266 for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName))267 if name is not None268 ]269 names.extend(270 ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress)271 )272 273 return names274 275 276class WrappedSocket(object):277 """API-compatibility wrapper for Python OpenSSL's Connection-class.278 279 Note: _makefile_refs, _drop() and _reuse() are needed for the garbage280 collector of pypy.281 """282 283 def __init__(self, connection, socket, suppress_ragged_eofs=True):284 self.connection = connection285 self.socket = socket286 self.suppress_ragged_eofs = suppress_ragged_eofs287 self._makefile_refs = 0288 self._closed = False289 290 def fileno(self):291 return self.socket.fileno()292 293 # Copy-pasted from Python 3.5 source code294 def _decref_socketios(self):295 if self._makefile_refs > 0:296 self._makefile_refs -= 1297 if self._closed:298 self.close()299 300 def recv(self, *args, **kwargs):301 try:302 data = self.connection.recv(*args, **kwargs)303 except OpenSSL.SSL.SysCallError as e:304 if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"):305 return b""306 else:307 raise SocketError(str(e))308 except OpenSSL.SSL.ZeroReturnError:309 if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:310 return b""311 else:312 raise313 except OpenSSL.SSL.WantReadError:314 if not util.wait_for_read(self.socket, self.socket.gettimeout()):315 raise timeout("The read operation timed out")316 else:317 return self.recv(*args, **kwargs)318 319 # TLS 1.3 post-handshake authentication320 except OpenSSL.SSL.Error as e:321 raise ssl.SSLError("read error: %r" % e)322 else:323 return data324 325 def recv_into(self, *args, **kwargs):326 try:327 return self.connection.recv_into(*args, **kwargs)328 except OpenSSL.SSL.SysCallError as e:329 if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"):330 return 0331 else:332 raise SocketError(str(e))333 except OpenSSL.SSL.ZeroReturnError:334 if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:335 return 0336 else:337 raise338 except OpenSSL.SSL.WantReadError:339 if not util.wait_for_read(self.socket, self.socket.gettimeout()):340 raise timeout("The read operation timed out")341 else:342 return self.recv_into(*args, **kwargs)343 344 # TLS 1.3 post-handshake authentication345 except OpenSSL.SSL.Error as e:346 raise ssl.SSLError("read error: %r" % e)347 348 def settimeout(self, timeout):349 return self.socket.settimeout(timeout)350 351 def _send_until_done(self, data):352 while True:353 try:354 return self.connection.send(data)355 except OpenSSL.SSL.WantWriteError:356 if not util.wait_for_write(self.socket, self.socket.gettimeout()):357 raise timeout()358 continue359 except OpenSSL.SSL.SysCallError as e:360 raise SocketError(str(e))361 362 def sendall(self, data):363 total_sent = 0364 while total_sent < len(data):365 sent = self._send_until_done(366 data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]367 )368 total_sent += sent369 370 def shutdown(self):371 # FIXME rethrow compatible exceptions should we ever use this372 self.connection.shutdown()373 374 def close(self):375 if self._makefile_refs < 1:376 try:377 self._closed = True378 return self.connection.close()379 except OpenSSL.SSL.Error:380 return381 else:382 self._makefile_refs -= 1383 384 def getpeercert(self, binary_form=False):385 x509 = self.connection.get_peer_certificate()386 387 if not x509:388 return x509389 390 if binary_form:391 return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509)392 393 return {394 "subject": ((("commonName", x509.get_subject().CN),),),395 "subjectAltName": get_subj_alt_name(x509),396 }397 398 def version(self):399 return self.connection.get_protocol_version_name()400 401 def _reuse(self):402 self._makefile_refs += 1403 404 def _drop(self):405 if self._makefile_refs < 1:406 self.close()407 else:408 self._makefile_refs -= 1409 410 411if _fileobject: # Platform-specific: Python 2412 413 def makefile(self, mode, bufsize=-1):414 self._makefile_refs += 1415 return _fileobject(self, mode, bufsize, close=True)416 417else: # Platform-specific: Python 3418 makefile = backport_makefile419 420WrappedSocket.makefile = makefile421 422 423class PyOpenSSLContext(object):424 """425 I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible426 for translating the interface of the standard library ``SSLContext`` object427 to calls into PyOpenSSL.428 """429 430 def __init__(self, protocol):431 self.protocol = _openssl_versions[protocol]432 self._ctx = OpenSSL.SSL.Context(self.protocol)433 self._options = 0434 self.check_hostname = False435 436 @property437 def options(self):438 return self._options439 440 @options.setter441 def options(self, value):442 self._options = value443 self._ctx.set_options(value)444 445 @property446 def verify_mode(self):447 return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()]448 449 @verify_mode.setter450 def verify_mode(self, value):451 self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback)452 453 def set_default_verify_paths(self):454 self._ctx.set_default_verify_paths()455 456 def set_ciphers(self, ciphers):457 if isinstance(ciphers, six.text_type):458 ciphers = ciphers.encode("utf-8")459 self._ctx.set_cipher_list(ciphers)460 461 def load_verify_locations(self, cafile=None, capath=None, cadata=None):462 if cafile is not None:463 cafile = cafile.encode("utf-8")464 if capath is not None:465 capath = capath.encode("utf-8")466 try:467 self._ctx.load_verify_locations(cafile, capath)468 if cadata is not None:469 self._ctx.load_verify_locations(BytesIO(cadata))470 except OpenSSL.SSL.Error as e:471 raise ssl.SSLError("unable to load trusted certificates: %r" % e)472 473 def load_cert_chain(self, certfile, keyfile=None, password=None):474 self._ctx.use_certificate_chain_file(certfile)475 if password is not None:476 if not isinstance(password, six.binary_type):477 password = password.encode("utf-8")478 self._ctx.set_passwd_cb(lambda *_: password)479 self._ctx.use_privatekey_file(keyfile or certfile)480 481 def set_alpn_protocols(self, protocols):482 protocols = [six.ensure_binary(p) for p in protocols]483 return self._ctx.set_alpn_protos(protocols)484 485 def wrap_socket(486 self,487 sock,488 server_side=False,489 do_handshake_on_connect=True,490 suppress_ragged_eofs=True,491 server_hostname=None,492 ):493 cnx = OpenSSL.SSL.Connection(self._ctx, sock)494 495 if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3496 server_hostname = server_hostname.encode("utf-8")497 498 if server_hostname is not None:499 cnx.set_tlsext_host_name(server_hostname)500 501 cnx.set_connect_state()502 503 while True:504 try:505 cnx.do_handshake()506 except OpenSSL.SSL.WantReadError:507 if not util.wait_for_read(sock, sock.gettimeout()):508 raise timeout("select timed out")509 continue510 except OpenSSL.SSL.Error as e:511 raise ssl.SSLError("bad handshake: %r" % e)512 break513 514 return WrappedSocket(cnx, sock)515 516 517def _verify_callback(cnx, x509, err_no, err_depth, return_code):518 return err_no == 0519 