Path: blob/master/venv/Lib/site-packages/urllib3/contrib/pyopenssl.py
811 views
"""1SSL with SNI_-support for Python 2. Follow these instructions if you would2like to verify SSL certificates in Python 2. Note, the default libraries do3*not* do certificate checking; you need to do additional work to validate4certificates yourself.56This needs the following packages installed:78* pyOpenSSL (tested with 16.0.0)9* cryptography (minimum 1.3.4, from pyopenssl)10* idna (minimum 2.0, from cryptography)1112However, pyopenssl depends on cryptography, which depends on idna, so while we13use all three directly here we end up having relatively few packages required.1415You can install them with the following command:1617pip install pyopenssl cryptography idna1819To activate certificate checking, call20:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code21before you begin making HTTP requests. This can be done in a ``sitecustomize``22module, or at any other time before your application begins using ``urllib3``,23like this::2425try:26import urllib3.contrib.pyopenssl27urllib3.contrib.pyopenssl.inject_into_urllib3()28except ImportError:29pass3031Now you can use :mod:`urllib3` as you normally would, and it will support SNI32when the required modules are installed.3334Activating this module also has the positive side effect of disabling SSL/TLS35compression in Python 2 (see `CRIME attack`_).3637If you want to configure the default list of supported cipher suites, you can38set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.3940.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication41.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)42"""43from __future__ import absolute_import4445import OpenSSL.SSL46from cryptography import x50947from cryptography.hazmat.backends.openssl import backend as openssl_backend48from cryptography.hazmat.backends.openssl.x509 import _Certificate4950try:51from cryptography.x509 import UnsupportedExtension52except ImportError:53# UnsupportedExtension is gone in cryptography >= 2.1.054class UnsupportedExtension(Exception):55pass565758from socket import timeout, error as SocketError59from io import BytesIO6061try: # Platform-specific: Python 262from socket import _fileobject63except ImportError: # Platform-specific: Python 364_fileobject = None65from ..packages.backports.makefile import backport_makefile6667import logging68import ssl69from ..packages import six70import sys7172from .. import util737475__all__ = ["inject_into_urllib3", "extract_from_urllib3"]7677# SNI always works.78HAS_SNI = True7980# Map from urllib3 to PyOpenSSL compatible parameter-values.81_openssl_versions = {82util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD,83ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,84}8586if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"):87_openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD8889if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"):90_openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD9192if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"):93_openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD949596_stdlib_to_openssl_verify = {97ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,98ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,99ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER100+ OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,101}102_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items())103104# OpenSSL will only write 16K at a time105SSL_WRITE_BLOCKSIZE = 16384106107orig_util_HAS_SNI = util.HAS_SNI108orig_util_SSLContext = util.ssl_.SSLContext109110111log = logging.getLogger(__name__)112113114def inject_into_urllib3():115"Monkey-patch urllib3 with PyOpenSSL-backed SSL-support."116117_validate_dependencies_met()118119util.SSLContext = PyOpenSSLContext120util.ssl_.SSLContext = PyOpenSSLContext121util.HAS_SNI = HAS_SNI122util.ssl_.HAS_SNI = HAS_SNI123util.IS_PYOPENSSL = True124util.ssl_.IS_PYOPENSSL = True125126127def extract_from_urllib3():128"Undo monkey-patching by :func:`inject_into_urllib3`."129130util.SSLContext = orig_util_SSLContext131util.ssl_.SSLContext = orig_util_SSLContext132util.HAS_SNI = orig_util_HAS_SNI133util.ssl_.HAS_SNI = orig_util_HAS_SNI134util.IS_PYOPENSSL = False135util.ssl_.IS_PYOPENSSL = False136137138def _validate_dependencies_met():139"""140Verifies that PyOpenSSL's package-level dependencies have been met.141Throws `ImportError` if they are not met.142"""143# Method added in `cryptography==1.1`; not available in older versions144from cryptography.x509.extensions import Extensions145146if getattr(Extensions, "get_extension_for_class", None) is None:147raise ImportError(148"'cryptography' module missing required functionality. "149"Try upgrading to v1.3.4 or newer."150)151152# pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509153# attribute is only present on those versions.154from OpenSSL.crypto import X509155156x509 = X509()157if getattr(x509, "_x509", None) is None:158raise ImportError(159"'pyOpenSSL' module missing required functionality. "160"Try upgrading to v0.14 or newer."161)162163164def _dnsname_to_stdlib(name):165"""166Converts a dNSName SubjectAlternativeName field to the form used by the167standard library on the given Python version.168169Cryptography produces a dNSName as a unicode string that was idna-decoded170from ASCII bytes. We need to idna-encode that string to get it back, and171then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib172uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8).173174If the name cannot be idna-encoded then we return None signalling that175the name given should be skipped.176"""177178def idna_encode(name):179"""180Borrowed wholesale from the Python Cryptography Project. It turns out181that we can't just safely call `idna.encode`: it can explode for182wildcard names. This avoids that problem.183"""184import idna185186try:187for prefix in [u"*.", u"."]:188if name.startswith(prefix):189name = name[len(prefix) :]190return prefix.encode("ascii") + idna.encode(name)191return idna.encode(name)192except idna.core.IDNAError:193return None194195# Don't send IPv6 addresses through the IDNA encoder.196if ":" in name:197return name198199name = idna_encode(name)200if name is None:201return None202elif sys.version_info >= (3, 0):203name = name.decode("utf-8")204return name205206207def get_subj_alt_name(peer_cert):208"""209Given an PyOpenSSL certificate, provides all the subject alternative names.210"""211# Pass the cert to cryptography, which has much better APIs for this.212if hasattr(peer_cert, "to_cryptography"):213cert = peer_cert.to_cryptography()214else:215# This is technically using private APIs, but should work across all216# relevant versions before PyOpenSSL got a proper API for this.217cert = _Certificate(openssl_backend, peer_cert._x509)218219# We want to find the SAN extension. Ask Cryptography to locate it (it's220# faster than looping in Python)221try:222ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value223except x509.ExtensionNotFound:224# No such extension, return the empty list.225return []226except (227x509.DuplicateExtension,228UnsupportedExtension,229x509.UnsupportedGeneralNameType,230UnicodeError,231) as e:232# A problem has been found with the quality of the certificate. Assume233# no SAN field is present.234log.warning(235"A problem was encountered with the certificate that prevented "236"urllib3 from finding the SubjectAlternativeName field. This can "237"affect certificate validation. The error was %s",238e,239)240return []241242# We want to return dNSName and iPAddress fields. We need to cast the IPs243# back to strings because the match_hostname function wants them as244# strings.245# Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8246# decoded. This is pretty frustrating, but that's what the standard library247# does with certificates, and so we need to attempt to do the same.248# We also want to skip over names which cannot be idna encoded.249names = [250("DNS", name)251for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName))252if name is not None253]254names.extend(255("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress)256)257258return names259260261class WrappedSocket(object):262"""API-compatibility wrapper for Python OpenSSL's Connection-class.263264Note: _makefile_refs, _drop() and _reuse() are needed for the garbage265collector of pypy.266"""267268def __init__(self, connection, socket, suppress_ragged_eofs=True):269self.connection = connection270self.socket = socket271self.suppress_ragged_eofs = suppress_ragged_eofs272self._makefile_refs = 0273self._closed = False274275def fileno(self):276return self.socket.fileno()277278# Copy-pasted from Python 3.5 source code279def _decref_socketios(self):280if self._makefile_refs > 0:281self._makefile_refs -= 1282if self._closed:283self.close()284285def recv(self, *args, **kwargs):286try:287data = self.connection.recv(*args, **kwargs)288except OpenSSL.SSL.SysCallError as e:289if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"):290return b""291else:292raise SocketError(str(e))293except OpenSSL.SSL.ZeroReturnError:294if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:295return b""296else:297raise298except OpenSSL.SSL.WantReadError:299if not util.wait_for_read(self.socket, self.socket.gettimeout()):300raise timeout("The read operation timed out")301else:302return self.recv(*args, **kwargs)303304# TLS 1.3 post-handshake authentication305except OpenSSL.SSL.Error as e:306raise ssl.SSLError("read error: %r" % e)307else:308return data309310def recv_into(self, *args, **kwargs):311try:312return self.connection.recv_into(*args, **kwargs)313except OpenSSL.SSL.SysCallError as e:314if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"):315return 0316else:317raise SocketError(str(e))318except OpenSSL.SSL.ZeroReturnError:319if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:320return 0321else:322raise323except OpenSSL.SSL.WantReadError:324if not util.wait_for_read(self.socket, self.socket.gettimeout()):325raise timeout("The read operation timed out")326else:327return self.recv_into(*args, **kwargs)328329# TLS 1.3 post-handshake authentication330except OpenSSL.SSL.Error as e:331raise ssl.SSLError("read error: %r" % e)332333def settimeout(self, timeout):334return self.socket.settimeout(timeout)335336def _send_until_done(self, data):337while True:338try:339return self.connection.send(data)340except OpenSSL.SSL.WantWriteError:341if not util.wait_for_write(self.socket, self.socket.gettimeout()):342raise timeout()343continue344except OpenSSL.SSL.SysCallError as e:345raise SocketError(str(e))346347def sendall(self, data):348total_sent = 0349while total_sent < len(data):350sent = self._send_until_done(351data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]352)353total_sent += sent354355def shutdown(self):356# FIXME rethrow compatible exceptions should we ever use this357self.connection.shutdown()358359def close(self):360if self._makefile_refs < 1:361try:362self._closed = True363return self.connection.close()364except OpenSSL.SSL.Error:365return366else:367self._makefile_refs -= 1368369def getpeercert(self, binary_form=False):370x509 = self.connection.get_peer_certificate()371372if not x509:373return x509374375if binary_form:376return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509)377378return {379"subject": ((("commonName", x509.get_subject().CN),),),380"subjectAltName": get_subj_alt_name(x509),381}382383def version(self):384return self.connection.get_protocol_version_name()385386def _reuse(self):387self._makefile_refs += 1388389def _drop(self):390if self._makefile_refs < 1:391self.close()392else:393self._makefile_refs -= 1394395396if _fileobject: # Platform-specific: Python 2397398def makefile(self, mode, bufsize=-1):399self._makefile_refs += 1400return _fileobject(self, mode, bufsize, close=True)401402403else: # Platform-specific: Python 3404makefile = backport_makefile405406WrappedSocket.makefile = makefile407408409class PyOpenSSLContext(object):410"""411I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible412for translating the interface of the standard library ``SSLContext`` object413to calls into PyOpenSSL.414"""415416def __init__(self, protocol):417self.protocol = _openssl_versions[protocol]418self._ctx = OpenSSL.SSL.Context(self.protocol)419self._options = 0420self.check_hostname = False421422@property423def options(self):424return self._options425426@options.setter427def options(self, value):428self._options = value429self._ctx.set_options(value)430431@property432def verify_mode(self):433return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()]434435@verify_mode.setter436def verify_mode(self, value):437self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback)438439def set_default_verify_paths(self):440self._ctx.set_default_verify_paths()441442def set_ciphers(self, ciphers):443if isinstance(ciphers, six.text_type):444ciphers = ciphers.encode("utf-8")445self._ctx.set_cipher_list(ciphers)446447def load_verify_locations(self, cafile=None, capath=None, cadata=None):448if cafile is not None:449cafile = cafile.encode("utf-8")450if capath is not None:451capath = capath.encode("utf-8")452try:453self._ctx.load_verify_locations(cafile, capath)454if cadata is not None:455self._ctx.load_verify_locations(BytesIO(cadata))456except OpenSSL.SSL.Error as e:457raise ssl.SSLError("unable to load trusted certificates: %r" % e)458459def load_cert_chain(self, certfile, keyfile=None, password=None):460self._ctx.use_certificate_chain_file(certfile)461if password is not None:462if not isinstance(password, six.binary_type):463password = password.encode("utf-8")464self._ctx.set_passwd_cb(lambda *_: password)465self._ctx.use_privatekey_file(keyfile or certfile)466467def wrap_socket(468self,469sock,470server_side=False,471do_handshake_on_connect=True,472suppress_ragged_eofs=True,473server_hostname=None,474):475cnx = OpenSSL.SSL.Connection(self._ctx, sock)476477if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3478server_hostname = server_hostname.encode("utf-8")479480if server_hostname is not None:481cnx.set_tlsext_host_name(server_hostname)482483cnx.set_connect_state()484485while True:486try:487cnx.do_handshake()488except OpenSSL.SSL.WantReadError:489if not util.wait_for_read(sock, sock.gettimeout()):490raise timeout("select timed out")491continue492except OpenSSL.SSL.Error as e:493raise ssl.SSLError("bad handshake: %r" % e)494break495496return WrappedSocket(cnx, sock)497498499def _verify_callback(cnx, x509, err_no, err_depth, return_code):500return err_no == 0501502503