ssl_.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. from __future__ import absolute_import
  2. import hmac
  3. import os
  4. import sys
  5. import warnings
  6. from binascii import hexlify, unhexlify
  7. from hashlib import md5, sha1, sha256
  8. from ..exceptions import (
  9. InsecurePlatformWarning,
  10. ProxySchemeUnsupported,
  11. SNIMissingWarning,
  12. SSLError,
  13. )
  14. from ..packages import six
  15. from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE
  16. SSLContext = None
  17. SSLTransport = None
  18. HAS_SNI = False
  19. IS_PYOPENSSL = False
  20. IS_SECURETRANSPORT = False
  21. ALPN_PROTOCOLS = ["http/1.1"]
  22. # Maps the length of a digest to a possible hash function producing this digest
  23. HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256}
  24. def _const_compare_digest_backport(a, b):
  25. """
  26. Compare two digests of equal length in constant time.
  27. The digests must be of type str/bytes.
  28. Returns True if the digests match, and False otherwise.
  29. """
  30. result = abs(len(a) - len(b))
  31. for left, right in zip(bytearray(a), bytearray(b)):
  32. result |= left ^ right
  33. return result == 0
  34. _const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport)
  35. try: # Test for SSL features
  36. import ssl
  37. from ssl import CERT_REQUIRED, wrap_socket
  38. except ImportError:
  39. pass
  40. try:
  41. from ssl import HAS_SNI # Has SNI?
  42. except ImportError:
  43. pass
  44. try:
  45. from .ssltransport import SSLTransport
  46. except ImportError:
  47. pass
  48. try: # Platform-specific: Python 3.6
  49. from ssl import PROTOCOL_TLS
  50. PROTOCOL_SSLv23 = PROTOCOL_TLS
  51. except ImportError:
  52. try:
  53. from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS
  54. PROTOCOL_SSLv23 = PROTOCOL_TLS
  55. except ImportError:
  56. PROTOCOL_SSLv23 = PROTOCOL_TLS = 2
  57. try:
  58. from ssl import PROTOCOL_TLS_CLIENT
  59. except ImportError:
  60. PROTOCOL_TLS_CLIENT = PROTOCOL_TLS
  61. try:
  62. from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3
  63. except ImportError:
  64. OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000
  65. OP_NO_COMPRESSION = 0x20000
  66. try: # OP_NO_TICKET was added in Python 3.6
  67. from ssl import OP_NO_TICKET
  68. except ImportError:
  69. OP_NO_TICKET = 0x4000
  70. # A secure default.
  71. # Sources for more information on TLS ciphers:
  72. #
  73. # - https://wiki.mozilla.org/Security/Server_Side_TLS
  74. # - https://www.ssllabs.com/projects/best-practices/index.html
  75. # - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
  76. #
  77. # The general intent is:
  78. # - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
  79. # - prefer ECDHE over DHE for better performance,
  80. # - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and
  81. # security,
  82. # - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common,
  83. # - disable NULL authentication, MD5 MACs, DSS, and other
  84. # insecure ciphers for security reasons.
  85. # - NOTE: TLS 1.3 cipher suites are managed through a different interface
  86. # not exposed by CPython (yet!) and are enabled by default if they're available.
  87. DEFAULT_CIPHERS = ":".join(
  88. [
  89. "ECDHE+AESGCM",
  90. "ECDHE+CHACHA20",
  91. "DHE+AESGCM",
  92. "DHE+CHACHA20",
  93. "ECDH+AESGCM",
  94. "DH+AESGCM",
  95. "ECDH+AES",
  96. "DH+AES",
  97. "RSA+AESGCM",
  98. "RSA+AES",
  99. "!aNULL",
  100. "!eNULL",
  101. "!MD5",
  102. "!DSS",
  103. ]
  104. )
  105. try:
  106. from ssl import SSLContext # Modern SSL?
  107. except ImportError:
  108. class SSLContext(object): # Platform-specific: Python 2
  109. def __init__(self, protocol_version):
  110. self.protocol = protocol_version
  111. # Use default values from a real SSLContext
  112. self.check_hostname = False
  113. self.verify_mode = ssl.CERT_NONE
  114. self.ca_certs = None
  115. self.options = 0
  116. self.certfile = None
  117. self.keyfile = None
  118. self.ciphers = None
  119. def load_cert_chain(self, certfile, keyfile):
  120. self.certfile = certfile
  121. self.keyfile = keyfile
  122. def load_verify_locations(self, cafile=None, capath=None, cadata=None):
  123. self.ca_certs = cafile
  124. if capath is not None:
  125. raise SSLError("CA directories not supported in older Pythons")
  126. if cadata is not None:
  127. raise SSLError("CA data not supported in older Pythons")
  128. def set_ciphers(self, cipher_suite):
  129. self.ciphers = cipher_suite
  130. def wrap_socket(self, socket, server_hostname=None, server_side=False):
  131. warnings.warn(
  132. "A true SSLContext object is not available. This prevents "
  133. "urllib3 from configuring SSL appropriately and may cause "
  134. "certain SSL connections to fail. You can upgrade to a newer "
  135. "version of Python to solve this. For more information, see "
  136. "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html"
  137. "#ssl-warnings",
  138. InsecurePlatformWarning,
  139. )
  140. kwargs = {
  141. "keyfile": self.keyfile,
  142. "certfile": self.certfile,
  143. "ca_certs": self.ca_certs,
  144. "cert_reqs": self.verify_mode,
  145. "ssl_version": self.protocol,
  146. "server_side": server_side,
  147. }
  148. return wrap_socket(socket, ciphers=self.ciphers, **kwargs)
  149. def assert_fingerprint(cert, fingerprint):
  150. """
  151. Checks if given fingerprint matches the supplied certificate.
  152. :param cert:
  153. Certificate as bytes object.
  154. :param fingerprint:
  155. Fingerprint as string of hexdigits, can be interspersed by colons.
  156. """
  157. fingerprint = fingerprint.replace(":", "").lower()
  158. digest_length = len(fingerprint)
  159. hashfunc = HASHFUNC_MAP.get(digest_length)
  160. if not hashfunc:
  161. raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint))
  162. # We need encode() here for py32; works on py2 and p33.
  163. fingerprint_bytes = unhexlify(fingerprint.encode())
  164. cert_digest = hashfunc(cert).digest()
  165. if not _const_compare_digest(cert_digest, fingerprint_bytes):
  166. raise SSLError(
  167. 'Fingerprints did not match. Expected "{0}", got "{1}".'.format(
  168. fingerprint, hexlify(cert_digest)
  169. )
  170. )
  171. def resolve_cert_reqs(candidate):
  172. """
  173. Resolves the argument to a numeric constant, which can be passed to
  174. the wrap_socket function/method from the ssl module.
  175. Defaults to :data:`ssl.CERT_REQUIRED`.
  176. If given a string it is assumed to be the name of the constant in the
  177. :mod:`ssl` module or its abbreviation.
  178. (So you can specify `REQUIRED` instead of `CERT_REQUIRED`.
  179. If it's neither `None` nor a string we assume it is already the numeric
  180. constant which can directly be passed to wrap_socket.
  181. """
  182. if candidate is None:
  183. return CERT_REQUIRED
  184. if isinstance(candidate, str):
  185. res = getattr(ssl, candidate, None)
  186. if res is None:
  187. res = getattr(ssl, "CERT_" + candidate)
  188. return res
  189. return candidate
  190. def resolve_ssl_version(candidate):
  191. """
  192. like resolve_cert_reqs
  193. """
  194. if candidate is None:
  195. return PROTOCOL_TLS
  196. if isinstance(candidate, str):
  197. res = getattr(ssl, candidate, None)
  198. if res is None:
  199. res = getattr(ssl, "PROTOCOL_" + candidate)
  200. return res
  201. return candidate
  202. def create_urllib3_context(
  203. ssl_version=None, cert_reqs=None, options=None, ciphers=None
  204. ):
  205. """All arguments have the same meaning as ``ssl_wrap_socket``.
  206. By default, this function does a lot of the same work that
  207. ``ssl.create_default_context`` does on Python 3.4+. It:
  208. - Disables SSLv2, SSLv3, and compression
  209. - Sets a restricted set of server ciphers
  210. If you wish to enable SSLv3, you can do::
  211. from urllib3.util import ssl_
  212. context = ssl_.create_urllib3_context()
  213. context.options &= ~ssl_.OP_NO_SSLv3
  214. You can do the same to enable compression (substituting ``COMPRESSION``
  215. for ``SSLv3`` in the last line above).
  216. :param ssl_version:
  217. The desired protocol version to use. This will default to
  218. PROTOCOL_SSLv23 which will negotiate the highest protocol that both
  219. the server and your installation of OpenSSL support.
  220. :param cert_reqs:
  221. Whether to require the certificate verification. This defaults to
  222. ``ssl.CERT_REQUIRED``.
  223. :param options:
  224. Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
  225. ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``.
  226. :param ciphers:
  227. Which cipher suites to allow the server to select.
  228. :returns:
  229. Constructed SSLContext object with specified options
  230. :rtype: SSLContext
  231. """
  232. # PROTOCOL_TLS is deprecated in Python 3.10
  233. if not ssl_version or ssl_version == PROTOCOL_TLS:
  234. ssl_version = PROTOCOL_TLS_CLIENT
  235. context = SSLContext(ssl_version)
  236. context.set_ciphers(ciphers or DEFAULT_CIPHERS)
  237. # Setting the default here, as we may have no ssl module on import
  238. cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
  239. if options is None:
  240. options = 0
  241. # SSLv2 is easily broken and is considered harmful and dangerous
  242. options |= OP_NO_SSLv2
  243. # SSLv3 has several problems and is now dangerous
  244. options |= OP_NO_SSLv3
  245. # Disable compression to prevent CRIME attacks for OpenSSL 1.0+
  246. # (issue #309)
  247. options |= OP_NO_COMPRESSION
  248. # TLSv1.2 only. Unless set explicitly, do not request tickets.
  249. # This may save some bandwidth on wire, and although the ticket is encrypted,
  250. # there is a risk associated with it being on wire,
  251. # if the server is not rotating its ticketing keys properly.
  252. options |= OP_NO_TICKET
  253. context.options |= options
  254. # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is
  255. # necessary for conditional client cert authentication with TLS 1.3.
  256. # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older
  257. # versions of Python. We only enable on Python 3.7.4+ or if certificate
  258. # verification is enabled to work around Python issue #37428
  259. # See: https://bugs.python.org/issue37428
  260. if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr(
  261. context, "post_handshake_auth", None
  262. ) is not None:
  263. context.post_handshake_auth = True
  264. def disable_check_hostname():
  265. if (
  266. getattr(context, "check_hostname", None) is not None
  267. ): # Platform-specific: Python 3.2
  268. # We do our own verification, including fingerprints and alternative
  269. # hostnames. So disable it here
  270. context.check_hostname = False
  271. # The order of the below lines setting verify_mode and check_hostname
  272. # matter due to safe-guards SSLContext has to prevent an SSLContext with
  273. # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more
  274. # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used
  275. # or not so we don't know the initial state of the freshly created SSLContext.
  276. if cert_reqs == ssl.CERT_REQUIRED:
  277. context.verify_mode = cert_reqs
  278. disable_check_hostname()
  279. else:
  280. disable_check_hostname()
  281. context.verify_mode = cert_reqs
  282. # Enable logging of TLS session keys via defacto standard environment variable
  283. # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values.
  284. if hasattr(context, "keylog_filename"):
  285. sslkeylogfile = os.environ.get("SSLKEYLOGFILE")
  286. if sslkeylogfile:
  287. context.keylog_filename = sslkeylogfile
  288. return context
  289. def ssl_wrap_socket(
  290. sock,
  291. keyfile=None,
  292. certfile=None,
  293. cert_reqs=None,
  294. ca_certs=None,
  295. server_hostname=None,
  296. ssl_version=None,
  297. ciphers=None,
  298. ssl_context=None,
  299. ca_cert_dir=None,
  300. key_password=None,
  301. ca_cert_data=None,
  302. tls_in_tls=False,
  303. ):
  304. """
  305. All arguments except for server_hostname, ssl_context, and ca_cert_dir have
  306. the same meaning as they do when using :func:`ssl.wrap_socket`.
  307. :param server_hostname:
  308. When SNI is supported, the expected hostname of the certificate
  309. :param ssl_context:
  310. A pre-made :class:`SSLContext` object. If none is provided, one will
  311. be created using :func:`create_urllib3_context`.
  312. :param ciphers:
  313. A string of ciphers we wish the client to support.
  314. :param ca_cert_dir:
  315. A directory containing CA certificates in multiple separate files, as
  316. supported by OpenSSL's -CApath flag or the capath argument to
  317. SSLContext.load_verify_locations().
  318. :param key_password:
  319. Optional password if the keyfile is encrypted.
  320. :param ca_cert_data:
  321. Optional string containing CA certificates in PEM format suitable for
  322. passing as the cadata parameter to SSLContext.load_verify_locations()
  323. :param tls_in_tls:
  324. Use SSLTransport to wrap the existing socket.
  325. """
  326. context = ssl_context
  327. if context is None:
  328. # Note: This branch of code and all the variables in it are no longer
  329. # used by urllib3 itself. We should consider deprecating and removing
  330. # this code.
  331. context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers)
  332. if ca_certs or ca_cert_dir or ca_cert_data:
  333. try:
  334. context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data)
  335. except (IOError, OSError) as e:
  336. raise SSLError(e)
  337. elif ssl_context is None and hasattr(context, "load_default_certs"):
  338. # try to load OS default certs; works well on Windows (require Python3.4+)
  339. context.load_default_certs()
  340. # Attempt to detect if we get the goofy behavior of the
  341. # keyfile being encrypted and OpenSSL asking for the
  342. # passphrase via the terminal and instead error out.
  343. if keyfile and key_password is None and _is_key_file_encrypted(keyfile):
  344. raise SSLError("Client private key is encrypted, password is required")
  345. if certfile:
  346. if key_password is None:
  347. context.load_cert_chain(certfile, keyfile)
  348. else:
  349. context.load_cert_chain(certfile, keyfile, key_password)
  350. try:
  351. if hasattr(context, "set_alpn_protocols"):
  352. context.set_alpn_protocols(ALPN_PROTOCOLS)
  353. except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols
  354. pass
  355. # If we detect server_hostname is an IP address then the SNI
  356. # extension should not be used according to RFC3546 Section 3.1
  357. use_sni_hostname = server_hostname and not is_ipaddress(server_hostname)
  358. # SecureTransport uses server_hostname in certificate verification.
  359. send_sni = (use_sni_hostname and HAS_SNI) or (
  360. IS_SECURETRANSPORT and server_hostname
  361. )
  362. # Do not warn the user if server_hostname is an invalid SNI hostname.
  363. if not HAS_SNI and use_sni_hostname:
  364. warnings.warn(
  365. "An HTTPS request has been made, but the SNI (Server Name "
  366. "Indication) extension to TLS is not available on this platform. "
  367. "This may cause the server to present an incorrect TLS "
  368. "certificate, which can cause validation failures. You can upgrade to "
  369. "a newer version of Python to solve this. For more information, see "
  370. "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html"
  371. "#ssl-warnings",
  372. SNIMissingWarning,
  373. )
  374. if send_sni:
  375. ssl_sock = _ssl_wrap_socket_impl(
  376. sock, context, tls_in_tls, server_hostname=server_hostname
  377. )
  378. else:
  379. ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls)
  380. return ssl_sock
  381. def is_ipaddress(hostname):
  382. """Detects whether the hostname given is an IPv4 or IPv6 address.
  383. Also detects IPv6 addresses with Zone IDs.
  384. :param str hostname: Hostname to examine.
  385. :return: True if the hostname is an IP address, False otherwise.
  386. """
  387. if not six.PY2 and isinstance(hostname, bytes):
  388. # IDN A-label bytes are ASCII compatible.
  389. hostname = hostname.decode("ascii")
  390. return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname))
  391. def _is_key_file_encrypted(key_file):
  392. """Detects if a key file is encrypted or not."""
  393. with open(key_file, "r") as f:
  394. for line in f:
  395. # Look for Proc-Type: 4,ENCRYPTED
  396. if "ENCRYPTED" in line:
  397. return True
  398. return False
  399. def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None):
  400. if tls_in_tls:
  401. if not SSLTransport:
  402. # Import error, ssl is not available.
  403. raise ProxySchemeUnsupported(
  404. "TLS in TLS requires support for the 'ssl' module"
  405. )
  406. SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context)
  407. return SSLTransport(sock, ssl_context, server_hostname)
  408. if server_hostname:
  409. return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
  410. else:
  411. return ssl_context.wrap_socket(sock)