1 from __future__
import absolute_import
7 from binascii
import hexlify
, unhexlify
8 from hashlib
import md5
, sha1
, sha256
10 from ..exceptions
import (
11 InsecurePlatformWarning
,
12 ProxySchemeUnsupported
,
16 from ..packages
import six
17 from .url
import BRACELESS_IPV6_ADDRZ_RE
, IPV4_RE
23 IS_SECURETRANSPORT
= False
24 ALPN_PROTOCOLS
= ["http/1.1"]
26 # Maps the length of a digest to a possible hash function producing this digest
27 HASHFUNC_MAP
= {32: md5, 40: sha1, 64: sha256}
30 def _const_compare_digest_backport(a
, b
):
32 Compare two digests of equal length in constant time.
34 The digests must be of type str/bytes.
35 Returns True if the digests match, and False otherwise.
37 result
= abs(len(a
) - len(b
))
38 for left
, right
in zip(bytearray(a
), bytearray(b
)):
39 result |
= left ^ right
43 _const_compare_digest
= getattr(hmac
, "compare_digest", _const_compare_digest_backport
)
45 try: # Test for SSL features
47 from ssl
import CERT_REQUIRED
, wrap_socket
52 from ssl
import HAS_SNI
# Has SNI?
57 from .ssltransport
import SSLTransport
62 try: # Platform-specific: Python 3.6
63 from ssl
import PROTOCOL_TLS
65 PROTOCOL_SSLv23
= PROTOCOL_TLS
68 from ssl
import PROTOCOL_SSLv23
as PROTOCOL_TLS
70 PROTOCOL_SSLv23
= PROTOCOL_TLS
72 PROTOCOL_SSLv23
= PROTOCOL_TLS
= 2
75 from ssl
import PROTOCOL_TLS_CLIENT
77 PROTOCOL_TLS_CLIENT
= PROTOCOL_TLS
81 from ssl
import OP_NO_COMPRESSION
, OP_NO_SSLv2
, OP_NO_SSLv3
83 OP_NO_SSLv2
, OP_NO_SSLv3
= 0x1000000, 0x2000000
84 OP_NO_COMPRESSION
= 0x20000
87 try: # OP_NO_TICKET was added in Python 3.6
88 from ssl
import OP_NO_TICKET
94 # Sources for more information on TLS ciphers:
96 # - https://wiki.mozilla.org/Security/Server_Side_TLS
97 # - https://www.ssllabs.com/projects/best-practices/index.html
98 # - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
100 # The general intent is:
101 # - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
102 # - prefer ECDHE over DHE for better performance,
103 # - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and
105 # - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common,
106 # - disable NULL authentication, MD5 MACs, DSS, and other
107 # insecure ciphers for security reasons.
108 # - NOTE: TLS 1.3 cipher suites are managed through a different interface
109 # not exposed by CPython (yet!) and are enabled by default if they're available.
110 DEFAULT_CIPHERS
= ":".join(
130 from ssl
import SSLContext
# Modern SSL?
133 class SSLContext(object): # Platform-specific: Python 2
134 def __init__(self
, protocol_version
):
135 self
.protocol
= protocol_version
136 # Use default values from a real SSLContext
137 self
.check_hostname
= False
138 self
.verify_mode
= ssl
.CERT_NONE
145 def load_cert_chain(self
, certfile
, keyfile
):
146 self
.certfile
= certfile
147 self
.keyfile
= keyfile
149 def load_verify_locations(self
, cafile
=None, capath
=None, cadata
=None):
150 self
.ca_certs
= cafile
152 if capath
is not None:
153 raise SSLError("CA directories not supported in older Pythons")
155 if cadata
is not None:
156 raise SSLError("CA data not supported in older Pythons")
158 def set_ciphers(self
, cipher_suite
):
159 self
.ciphers
= cipher_suite
161 def wrap_socket(self
, socket
, server_hostname
=None, server_side
=False):
163 "A true SSLContext object is not available. This prevents "
164 "urllib3 from configuring SSL appropriately and may cause "
165 "certain SSL connections to fail. You can upgrade to a newer "
166 "version of Python to solve this. For more information, see "
167 "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html"
169 InsecurePlatformWarning
,
172 "keyfile": self
.keyfile
,
173 "certfile": self
.certfile
,
174 "ca_certs": self
.ca_certs
,
175 "cert_reqs": self
.verify_mode
,
176 "ssl_version": self
.protocol
,
177 "server_side": server_side
,
179 return wrap_socket(socket
, ciphers
=self
.ciphers
, **kwargs
)
182 def assert_fingerprint(cert
, fingerprint
):
184 Checks if given fingerprint matches the supplied certificate.
187 Certificate as bytes object.
189 Fingerprint as string of hexdigits, can be interspersed by colons.
192 fingerprint
= fingerprint
.replace(":", "").lower()
193 digest_length
= len(fingerprint
)
194 hashfunc
= HASHFUNC_MAP
.get(digest_length
)
196 raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint
))
198 # We need encode() here for py32; works on py2 and p33.
199 fingerprint_bytes
= unhexlify(fingerprint
.encode())
201 cert_digest
= hashfunc(cert
).digest()
203 if not _const_compare_digest(cert_digest
, fingerprint_bytes
):
205 'Fingerprints did not match. Expected "{0}", got "{1}".'.format(
206 fingerprint
, hexlify(cert_digest
)
211 def resolve_cert_reqs(candidate
):
213 Resolves the argument to a numeric constant, which can be passed to
214 the wrap_socket function/method from the ssl module.
215 Defaults to :data:`ssl.CERT_REQUIRED`.
216 If given a string it is assumed to be the name of the constant in the
217 :mod:`ssl` module or its abbreviation.
218 (So you can specify `REQUIRED` instead of `CERT_REQUIRED`.
219 If it's neither `None` nor a string we assume it is already the numeric
220 constant which can directly be passed to wrap_socket.
222 if candidate
is None:
225 if isinstance(candidate
, str):
226 res
= getattr(ssl
, candidate
, None)
228 res
= getattr(ssl
, "CERT_" + candidate
)
234 def resolve_ssl_version(candidate
):
236 like resolve_cert_reqs
238 if candidate
is None:
241 if isinstance(candidate
, str):
242 res
= getattr(ssl
, candidate
, None)
244 res
= getattr(ssl
, "PROTOCOL_" + candidate
)
250 def create_urllib3_context(
251 ssl_version
=None, cert_reqs
=None, options
=None, ciphers
=None
253 """All arguments have the same meaning as ``ssl_wrap_socket``.
255 By default, this function does a lot of the same work that
256 ``ssl.create_default_context`` does on Python 3.4+. It:
258 - Disables SSLv2, SSLv3, and compression
259 - Sets a restricted set of server ciphers
261 If you wish to enable SSLv3, you can do::
263 from pip._vendor.urllib3.util import ssl_
264 context = ssl_.create_urllib3_context()
265 context.options &= ~ssl_.OP_NO_SSLv3
267 You can do the same to enable compression (substituting ``COMPRESSION``
268 for ``SSLv3`` in the last line above).
271 The desired protocol version to use. This will default to
272 PROTOCOL_SSLv23 which will negotiate the highest protocol that both
273 the server and your installation of OpenSSL support.
275 Whether to require the certificate verification. This defaults to
276 ``ssl.CERT_REQUIRED``.
278 Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
279 ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``.
281 Which cipher suites to allow the server to select.
283 Constructed SSLContext object with specified options
286 # PROTOCOL_TLS is deprecated in Python 3.10
287 if not ssl_version
or ssl_version
== PROTOCOL_TLS
:
288 ssl_version
= PROTOCOL_TLS_CLIENT
290 context
= SSLContext(ssl_version
)
292 context
.set_ciphers(ciphers
or DEFAULT_CIPHERS
)
294 # Setting the default here, as we may have no ssl module on import
295 cert_reqs
= ssl
.CERT_REQUIRED
if cert_reqs
is None else cert_reqs
299 # SSLv2 is easily broken and is considered harmful and dangerous
300 options |
= OP_NO_SSLv2
301 # SSLv3 has several problems and is now dangerous
302 options |
= OP_NO_SSLv3
303 # Disable compression to prevent CRIME attacks for OpenSSL 1.0+
305 options |
= OP_NO_COMPRESSION
306 # TLSv1.2 only. Unless set explicitly, do not request tickets.
307 # This may save some bandwidth on wire, and although the ticket is encrypted,
308 # there is a risk associated with it being on wire,
309 # if the server is not rotating its ticketing keys properly.
310 options |
= OP_NO_TICKET
312 context
.options |
= options
314 # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is
315 # necessary for conditional client cert authentication with TLS 1.3.
316 # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older
317 # versions of Python. We only enable on Python 3.7.4+ or if certificate
318 # verification is enabled to work around Python issue #37428
319 # See: https://bugs.python.org/issue37428
320 if (cert_reqs
== ssl
.CERT_REQUIRED
or sys
.version_info
>= (3, 7, 4)) and getattr(
321 context
, "post_handshake_auth", None
323 context
.post_handshake_auth
= True
325 def disable_check_hostname():
327 getattr(context
, "check_hostname", None) is not None
328 ): # Platform-specific: Python 3.2
329 # We do our own verification, including fingerprints and alternative
330 # hostnames. So disable it here
331 context
.check_hostname
= False
333 # The order of the below lines setting verify_mode and check_hostname
334 # matter due to safe-guards SSLContext has to prevent an SSLContext with
335 # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more
336 # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used
337 # or not so we don't know the initial state of the freshly created SSLContext.
338 if cert_reqs
== ssl
.CERT_REQUIRED
:
339 context
.verify_mode
= cert_reqs
340 disable_check_hostname()
342 disable_check_hostname()
343 context
.verify_mode
= cert_reqs
345 # Enable logging of TLS session keys via defacto standard environment variable
346 # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values.
347 if hasattr(context
, "keylog_filename"):
348 sslkeylogfile
= os
.environ
.get("SSLKEYLOGFILE")
350 context
.keylog_filename
= sslkeylogfile
361 server_hostname
=None,
371 All arguments except for server_hostname, ssl_context, and ca_cert_dir have
372 the same meaning as they do when using :func:`ssl.wrap_socket`.
374 :param server_hostname:
375 When SNI is supported, the expected hostname of the certificate
377 A pre-made :class:`SSLContext` object. If none is provided, one will
378 be created using :func:`create_urllib3_context`.
380 A string of ciphers we wish the client to support.
382 A directory containing CA certificates in multiple separate files, as
383 supported by OpenSSL's -CApath flag or the capath argument to
384 SSLContext.load_verify_locations().
386 Optional password if the keyfile is encrypted.
388 Optional string containing CA certificates in PEM format suitable for
389 passing as the cadata parameter to SSLContext.load_verify_locations()
391 Use SSLTransport to wrap the existing socket.
393 context
= ssl_context
395 # Note: This branch of code and all the variables in it are no longer
396 # used by urllib3 itself. We should consider deprecating and removing
398 context
= create_urllib3_context(ssl_version
, cert_reqs
, ciphers
=ciphers
)
400 if ca_certs
or ca_cert_dir
or ca_cert_data
:
402 context
.load_verify_locations(ca_certs
, ca_cert_dir
, ca_cert_data
)
403 except (IOError, OSError) as e
:
406 elif ssl_context
is None and hasattr(context
, "load_default_certs"):
407 # try to load OS default certs; works well on Windows (require Python3.4+)
408 context
.load_default_certs()
410 # Attempt to detect if we get the goofy behavior of the
411 # keyfile being encrypted and OpenSSL asking for the
412 # passphrase via the terminal and instead error out.
413 if keyfile
and key_password
is None and _is_key_file_encrypted(keyfile
):
414 raise SSLError("Client private key is encrypted, password is required")
417 if key_password
is None:
418 context
.load_cert_chain(certfile
, keyfile
)
420 context
.load_cert_chain(certfile
, keyfile
, key_password
)
423 if hasattr(context
, "set_alpn_protocols"):
424 context
.set_alpn_protocols(ALPN_PROTOCOLS
)
425 except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols
428 # If we detect server_hostname is an IP address then the SNI
429 # extension should not be used according to RFC3546 Section 3.1
430 use_sni_hostname
= server_hostname
and not is_ipaddress(server_hostname
)
431 # SecureTransport uses server_hostname in certificate verification.
432 send_sni
= (use_sni_hostname
and HAS_SNI
) or (
433 IS_SECURETRANSPORT
and server_hostname
435 # Do not warn the user if server_hostname is an invalid SNI hostname.
436 if not HAS_SNI
and use_sni_hostname
:
438 "An HTTPS request has been made, but the SNI (Server Name "
439 "Indication) extension to TLS is not available on this platform. "
440 "This may cause the server to present an incorrect TLS "
441 "certificate, which can cause validation failures. You can upgrade to "
442 "a newer version of Python to solve this. For more information, see "
443 "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html"
449 ssl_sock
= _ssl_wrap_socket_impl(
450 sock
, context
, tls_in_tls
, server_hostname
=server_hostname
453 ssl_sock
= _ssl_wrap_socket_impl(sock
, context
, tls_in_tls
)
457 def is_ipaddress(hostname
):
458 """Detects whether the hostname given is an IPv4 or IPv6 address.
459 Also detects IPv6 addresses with Zone IDs.
461 :param str hostname: Hostname to examine.
462 :return: True if the hostname is an IP address, False otherwise.
464 if not six
.PY2
and isinstance(hostname
, bytes):
465 # IDN A-label bytes are ASCII compatible.
466 hostname
= hostname
.decode("ascii")
467 return bool(IPV4_RE
.match(hostname
) or BRACELESS_IPV6_ADDRZ_RE
.match(hostname
))
470 def _is_key_file_encrypted(key_file
):
471 """Detects if a key file is encrypted or not."""
472 with open(key_file
, "r") as f
:
474 # Look for Proc-Type: 4,ENCRYPTED
475 if "ENCRYPTED" in line
:
481 def _ssl_wrap_socket_impl(sock
, ssl_context
, tls_in_tls
, server_hostname
=None):
484 # Import error, ssl is not available.
485 raise ProxySchemeUnsupported(
486 "TLS in TLS requires support for the 'ssl' module"
489 SSLTransport
._validate
_ssl
_context
_for
_tls
_in
_tls
(ssl_context
)
490 return SSLTransport(sock
, ssl_context
, server_hostname
)
493 return ssl_context
.wrap_socket(sock
, server_hostname
=server_hostname
)
495 return ssl_context
.wrap_socket(sock
)