Path: blob/master/venv/Lib/site-packages/urllib3/util/ssl_.py
811 views
from __future__ import absolute_import1import errno2import warnings3import hmac4import sys56from binascii import hexlify, unhexlify7from hashlib import md5, sha1, sha25689from .url import IPV4_RE, BRACELESS_IPV6_ADDRZ_RE10from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning11from ..packages import six121314SSLContext = None15HAS_SNI = False16IS_PYOPENSSL = False17IS_SECURETRANSPORT = False1819# Maps the length of a digest to a possible hash function producing this digest20HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256}212223def _const_compare_digest_backport(a, b):24"""25Compare two digests of equal length in constant time.2627The digests must be of type str/bytes.28Returns True if the digests match, and False otherwise.29"""30result = abs(len(a) - len(b))31for l, r in zip(bytearray(a), bytearray(b)):32result |= l ^ r33return result == 0343536_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport)3738try: # Test for SSL features39import ssl40from ssl import wrap_socket, CERT_REQUIRED41from ssl import HAS_SNI # Has SNI?42except ImportError:43pass4445try: # Platform-specific: Python 3.646from ssl import PROTOCOL_TLS4748PROTOCOL_SSLv23 = PROTOCOL_TLS49except ImportError:50try:51from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS5253PROTOCOL_SSLv23 = PROTOCOL_TLS54except ImportError:55PROTOCOL_SSLv23 = PROTOCOL_TLS = 2565758try:59from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION60except ImportError:61OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x200000062OP_NO_COMPRESSION = 0x20000636465# A secure default.66# Sources for more information on TLS ciphers:67#68# - https://wiki.mozilla.org/Security/Server_Side_TLS69# - https://www.ssllabs.com/projects/best-practices/index.html70# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/71#72# The general intent is:73# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),74# - prefer ECDHE over DHE for better performance,75# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and76# security,77# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common,78# - disable NULL authentication, MD5 MACs, DSS, and other79# insecure ciphers for security reasons.80# - NOTE: TLS 1.3 cipher suites are managed through a different interface81# not exposed by CPython (yet!) and are enabled by default if they're available.82DEFAULT_CIPHERS = ":".join(83[84"ECDHE+AESGCM",85"ECDHE+CHACHA20",86"DHE+AESGCM",87"DHE+CHACHA20",88"ECDH+AESGCM",89"DH+AESGCM",90"ECDH+AES",91"DH+AES",92"RSA+AESGCM",93"RSA+AES",94"!aNULL",95"!eNULL",96"!MD5",97"!DSS",98]99)100101try:102from ssl import SSLContext # Modern SSL?103except ImportError:104105class SSLContext(object): # Platform-specific: Python 2106def __init__(self, protocol_version):107self.protocol = protocol_version108# Use default values from a real SSLContext109self.check_hostname = False110self.verify_mode = ssl.CERT_NONE111self.ca_certs = None112self.options = 0113self.certfile = None114self.keyfile = None115self.ciphers = None116117def load_cert_chain(self, certfile, keyfile):118self.certfile = certfile119self.keyfile = keyfile120121def load_verify_locations(self, cafile=None, capath=None, cadata=None):122self.ca_certs = cafile123124if capath is not None:125raise SSLError("CA directories not supported in older Pythons")126127if cadata is not None:128raise SSLError("CA data not supported in older Pythons")129130def set_ciphers(self, cipher_suite):131self.ciphers = cipher_suite132133def wrap_socket(self, socket, server_hostname=None, server_side=False):134warnings.warn(135"A true SSLContext object is not available. This prevents "136"urllib3 from configuring SSL appropriately and may cause "137"certain SSL connections to fail. You can upgrade to a newer "138"version of Python to solve this. For more information, see "139"https://urllib3.readthedocs.io/en/latest/advanced-usage.html"140"#ssl-warnings",141InsecurePlatformWarning,142)143kwargs = {144"keyfile": self.keyfile,145"certfile": self.certfile,146"ca_certs": self.ca_certs,147"cert_reqs": self.verify_mode,148"ssl_version": self.protocol,149"server_side": server_side,150}151return wrap_socket(socket, ciphers=self.ciphers, **kwargs)152153154def assert_fingerprint(cert, fingerprint):155"""156Checks if given fingerprint matches the supplied certificate.157158:param cert:159Certificate as bytes object.160:param fingerprint:161Fingerprint as string of hexdigits, can be interspersed by colons.162"""163164fingerprint = fingerprint.replace(":", "").lower()165digest_length = len(fingerprint)166hashfunc = HASHFUNC_MAP.get(digest_length)167if not hashfunc:168raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint))169170# We need encode() here for py32; works on py2 and p33.171fingerprint_bytes = unhexlify(fingerprint.encode())172173cert_digest = hashfunc(cert).digest()174175if not _const_compare_digest(cert_digest, fingerprint_bytes):176raise SSLError(177'Fingerprints did not match. Expected "{0}", got "{1}".'.format(178fingerprint, hexlify(cert_digest)179)180)181182183def resolve_cert_reqs(candidate):184"""185Resolves the argument to a numeric constant, which can be passed to186the wrap_socket function/method from the ssl module.187Defaults to :data:`ssl.CERT_REQUIRED`.188If given a string it is assumed to be the name of the constant in the189:mod:`ssl` module or its abbreviation.190(So you can specify `REQUIRED` instead of `CERT_REQUIRED`.191If it's neither `None` nor a string we assume it is already the numeric192constant which can directly be passed to wrap_socket.193"""194if candidate is None:195return CERT_REQUIRED196197if isinstance(candidate, str):198res = getattr(ssl, candidate, None)199if res is None:200res = getattr(ssl, "CERT_" + candidate)201return res202203return candidate204205206def resolve_ssl_version(candidate):207"""208like resolve_cert_reqs209"""210if candidate is None:211return PROTOCOL_TLS212213if isinstance(candidate, str):214res = getattr(ssl, candidate, None)215if res is None:216res = getattr(ssl, "PROTOCOL_" + candidate)217return res218219return candidate220221222def create_urllib3_context(223ssl_version=None, cert_reqs=None, options=None, ciphers=None224):225"""All arguments have the same meaning as ``ssl_wrap_socket``.226227By default, this function does a lot of the same work that228``ssl.create_default_context`` does on Python 3.4+. It:229230- Disables SSLv2, SSLv3, and compression231- Sets a restricted set of server ciphers232233If you wish to enable SSLv3, you can do::234235from urllib3.util import ssl_236context = ssl_.create_urllib3_context()237context.options &= ~ssl_.OP_NO_SSLv3238239You can do the same to enable compression (substituting ``COMPRESSION``240for ``SSLv3`` in the last line above).241242:param ssl_version:243The desired protocol version to use. This will default to244PROTOCOL_SSLv23 which will negotiate the highest protocol that both245the server and your installation of OpenSSL support.246:param cert_reqs:247Whether to require the certificate verification. This defaults to248``ssl.CERT_REQUIRED``.249:param options:250Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,251``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``.252:param ciphers:253Which cipher suites to allow the server to select.254:returns:255Constructed SSLContext object with specified options256:rtype: SSLContext257"""258context = SSLContext(ssl_version or PROTOCOL_TLS)259260context.set_ciphers(ciphers or DEFAULT_CIPHERS)261262# Setting the default here, as we may have no ssl module on import263cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs264265if options is None:266options = 0267# SSLv2 is easily broken and is considered harmful and dangerous268options |= OP_NO_SSLv2269# SSLv3 has several problems and is now dangerous270options |= OP_NO_SSLv3271# Disable compression to prevent CRIME attacks for OpenSSL 1.0+272# (issue #309)273options |= OP_NO_COMPRESSION274275context.options |= options276277# Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is278# necessary for conditional client cert authentication with TLS 1.3.279# The attribute is None for OpenSSL <= 1.1.0 or does not exist in older280# versions of Python. We only enable on Python 3.7.4+ or if certificate281# verification is enabled to work around Python issue #37428282# See: https://bugs.python.org/issue37428283if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr(284context, "post_handshake_auth", None285) is not None:286context.post_handshake_auth = True287288context.verify_mode = cert_reqs289if (290getattr(context, "check_hostname", None) is not None291): # Platform-specific: Python 3.2292# We do our own verification, including fingerprints and alternative293# hostnames. So disable it here294context.check_hostname = False295return context296297298def ssl_wrap_socket(299sock,300keyfile=None,301certfile=None,302cert_reqs=None,303ca_certs=None,304server_hostname=None,305ssl_version=None,306ciphers=None,307ssl_context=None,308ca_cert_dir=None,309key_password=None,310ca_cert_data=None,311):312"""313All arguments except for server_hostname, ssl_context, and ca_cert_dir have314the same meaning as they do when using :func:`ssl.wrap_socket`.315316:param server_hostname:317When SNI is supported, the expected hostname of the certificate318:param ssl_context:319A pre-made :class:`SSLContext` object. If none is provided, one will320be created using :func:`create_urllib3_context`.321:param ciphers:322A string of ciphers we wish the client to support.323:param ca_cert_dir:324A directory containing CA certificates in multiple separate files, as325supported by OpenSSL's -CApath flag or the capath argument to326SSLContext.load_verify_locations().327:param key_password:328Optional password if the keyfile is encrypted.329:param ca_cert_data:330Optional string containing CA certificates in PEM format suitable for331passing as the cadata parameter to SSLContext.load_verify_locations()332"""333context = ssl_context334if context is None:335# Note: This branch of code and all the variables in it are no longer336# used by urllib3 itself. We should consider deprecating and removing337# this code.338context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers)339340if ca_certs or ca_cert_dir or ca_cert_data:341try:342context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data)343except IOError as e: # Platform-specific: Python 2.7344raise SSLError(e)345# Py33 raises FileNotFoundError which subclasses OSError346# These are not equivalent unless we check the errno attribute347except OSError as e: # Platform-specific: Python 3.3 and beyond348if e.errno == errno.ENOENT:349raise SSLError(e)350raise351352elif ssl_context is None and hasattr(context, "load_default_certs"):353# try to load OS default certs; works well on Windows (require Python3.4+)354context.load_default_certs()355356# Attempt to detect if we get the goofy behavior of the357# keyfile being encrypted and OpenSSL asking for the358# passphrase via the terminal and instead error out.359if keyfile and key_password is None and _is_key_file_encrypted(keyfile):360raise SSLError("Client private key is encrypted, password is required")361362if certfile:363if key_password is None:364context.load_cert_chain(certfile, keyfile)365else:366context.load_cert_chain(certfile, keyfile, key_password)367368# If we detect server_hostname is an IP address then the SNI369# extension should not be used according to RFC3546 Section 3.1370# We shouldn't warn the user if SNI isn't available but we would371# not be using SNI anyways due to IP address for server_hostname.372if (373server_hostname is not None and not is_ipaddress(server_hostname)374) or IS_SECURETRANSPORT:375if HAS_SNI and server_hostname is not None:376return context.wrap_socket(sock, server_hostname=server_hostname)377378warnings.warn(379"An HTTPS request has been made, but the SNI (Server Name "380"Indication) extension to TLS is not available on this platform. "381"This may cause the server to present an incorrect TLS "382"certificate, which can cause validation failures. You can upgrade to "383"a newer version of Python to solve this. For more information, see "384"https://urllib3.readthedocs.io/en/latest/advanced-usage.html"385"#ssl-warnings",386SNIMissingWarning,387)388389return context.wrap_socket(sock)390391392def is_ipaddress(hostname):393"""Detects whether the hostname given is an IPv4 or IPv6 address.394Also detects IPv6 addresses with Zone IDs.395396:param str hostname: Hostname to examine.397:return: True if the hostname is an IP address, False otherwise.398"""399if not six.PY2 and isinstance(hostname, bytes):400# IDN A-label bytes are ASCII compatible.401hostname = hostname.decode("ascii")402return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname))403404405def _is_key_file_encrypted(key_file):406"""Detects if a key file is encrypted or not."""407with open(key_file, "r") as f:408for line in f:409# Look for Proc-Type: 4,ENCRYPTED410if "ENCRYPTED" in line:411return True412413return False414415416