ssl_.py 16 KB

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