Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ssl_.py 18KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. from __future__ import annotations
  2. import hmac
  3. import os
  4. import socket
  5. import sys
  6. import typing
  7. import warnings
  8. from binascii import unhexlify
  9. from hashlib import md5, sha1, sha256
  10. from ..exceptions import ProxySchemeUnsupported, SSLError
  11. from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE
  12. SSLContext = None
  13. SSLTransport = None
  14. HAS_NEVER_CHECK_COMMON_NAME = False
  15. IS_PYOPENSSL = False
  16. IS_SECURETRANSPORT = False
  17. ALPN_PROTOCOLS = ["http/1.1"]
  18. _TYPE_VERSION_INFO = typing.Tuple[int, int, int, str, int]
  19. # Maps the length of a digest to a possible hash function producing this digest
  20. HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256}
  21. def _is_bpo_43522_fixed(
  22. implementation_name: str, version_info: _TYPE_VERSION_INFO
  23. ) -> bool:
  24. """Return True for CPython 3.8.9+, 3.9.3+ or 3.10+ where setting
  25. SSLContext.hostname_checks_common_name to False works.
  26. PyPy 7.3.7 doesn't work as it doesn't ship with OpenSSL 1.1.1l+
  27. so we're waiting for a version of PyPy that works before
  28. allowing this function to return 'True'.
  29. Outside of CPython and PyPy we don't know which implementations work
  30. or not so we conservatively use our hostname matching as we know that works
  31. on all implementations.
  32. https://github.com/urllib3/urllib3/issues/2192#issuecomment-821832963
  33. https://foss.heptapod.net/pypy/pypy/-/issues/3539#
  34. """
  35. if implementation_name != "cpython":
  36. return False
  37. major_minor = version_info[:2]
  38. micro = version_info[2]
  39. return (
  40. (major_minor == (3, 8) and micro >= 9)
  41. or (major_minor == (3, 9) and micro >= 3)
  42. or major_minor >= (3, 10)
  43. )
  44. def _is_has_never_check_common_name_reliable(
  45. openssl_version: str,
  46. openssl_version_number: int,
  47. implementation_name: str,
  48. version_info: _TYPE_VERSION_INFO,
  49. ) -> bool:
  50. # As of May 2023, all released versions of LibreSSL fail to reject certificates with
  51. # only common names, see https://github.com/urllib3/urllib3/pull/3024
  52. is_openssl = openssl_version.startswith("OpenSSL ")
  53. # Before fixing OpenSSL issue #14579, the SSL_new() API was not copying hostflags
  54. # like X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, which tripped up CPython.
  55. # https://github.com/openssl/openssl/issues/14579
  56. # This was released in OpenSSL 1.1.1l+ (>=0x101010cf)
  57. is_openssl_issue_14579_fixed = openssl_version_number >= 0x101010CF
  58. return is_openssl and (
  59. is_openssl_issue_14579_fixed
  60. or _is_bpo_43522_fixed(implementation_name, version_info)
  61. )
  62. if typing.TYPE_CHECKING:
  63. from ssl import VerifyMode
  64. from typing_extensions import Literal, TypedDict
  65. from .ssltransport import SSLTransport as SSLTransportType
  66. class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False):
  67. subjectAltName: tuple[tuple[str, str], ...]
  68. subject: tuple[tuple[tuple[str, str], ...], ...]
  69. serialNumber: str
  70. # Mapping from 'ssl.PROTOCOL_TLSX' to 'TLSVersion.X'
  71. _SSL_VERSION_TO_TLS_VERSION: dict[int, int] = {}
  72. try: # Do we have ssl at all?
  73. import ssl
  74. from ssl import ( # type: ignore[assignment]
  75. CERT_REQUIRED,
  76. HAS_NEVER_CHECK_COMMON_NAME,
  77. OP_NO_COMPRESSION,
  78. OP_NO_TICKET,
  79. OPENSSL_VERSION,
  80. OPENSSL_VERSION_NUMBER,
  81. PROTOCOL_TLS,
  82. PROTOCOL_TLS_CLIENT,
  83. OP_NO_SSLv2,
  84. OP_NO_SSLv3,
  85. SSLContext,
  86. TLSVersion,
  87. )
  88. PROTOCOL_SSLv23 = PROTOCOL_TLS
  89. # Setting SSLContext.hostname_checks_common_name = False didn't work before CPython
  90. # 3.8.9, 3.9.3, and 3.10 (but OK on PyPy) or OpenSSL 1.1.1l+
  91. if HAS_NEVER_CHECK_COMMON_NAME and not _is_has_never_check_common_name_reliable(
  92. OPENSSL_VERSION,
  93. OPENSSL_VERSION_NUMBER,
  94. sys.implementation.name,
  95. sys.version_info,
  96. ):
  97. HAS_NEVER_CHECK_COMMON_NAME = False
  98. # Need to be careful here in case old TLS versions get
  99. # removed in future 'ssl' module implementations.
  100. for attr in ("TLSv1", "TLSv1_1", "TLSv1_2"):
  101. try:
  102. _SSL_VERSION_TO_TLS_VERSION[getattr(ssl, f"PROTOCOL_{attr}")] = getattr(
  103. TLSVersion, attr
  104. )
  105. except AttributeError: # Defensive:
  106. continue
  107. from .ssltransport import SSLTransport # type: ignore[assignment]
  108. except ImportError:
  109. OP_NO_COMPRESSION = 0x20000 # type: ignore[assignment]
  110. OP_NO_TICKET = 0x4000 # type: ignore[assignment]
  111. OP_NO_SSLv2 = 0x1000000 # type: ignore[assignment]
  112. OP_NO_SSLv3 = 0x2000000 # type: ignore[assignment]
  113. PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 # type: ignore[assignment]
  114. PROTOCOL_TLS_CLIENT = 16 # type: ignore[assignment]
  115. _TYPE_PEER_CERT_RET = typing.Union["_TYPE_PEER_CERT_RET_DICT", bytes, None]
  116. def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None:
  117. """
  118. Checks if given fingerprint matches the supplied certificate.
  119. :param cert:
  120. Certificate as bytes object.
  121. :param fingerprint:
  122. Fingerprint as string of hexdigits, can be interspersed by colons.
  123. """
  124. if cert is None:
  125. raise SSLError("No certificate for the peer.")
  126. fingerprint = fingerprint.replace(":", "").lower()
  127. digest_length = len(fingerprint)
  128. hashfunc = HASHFUNC_MAP.get(digest_length)
  129. if not hashfunc:
  130. raise SSLError(f"Fingerprint of invalid length: {fingerprint}")
  131. # We need encode() here for py32; works on py2 and p33.
  132. fingerprint_bytes = unhexlify(fingerprint.encode())
  133. cert_digest = hashfunc(cert).digest()
  134. if not hmac.compare_digest(cert_digest, fingerprint_bytes):
  135. raise SSLError(
  136. f'Fingerprints did not match. Expected "{fingerprint}", got "{cert_digest.hex()}"'
  137. )
  138. def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode:
  139. """
  140. Resolves the argument to a numeric constant, which can be passed to
  141. the wrap_socket function/method from the ssl module.
  142. Defaults to :data:`ssl.CERT_REQUIRED`.
  143. If given a string it is assumed to be the name of the constant in the
  144. :mod:`ssl` module or its abbreviation.
  145. (So you can specify `REQUIRED` instead of `CERT_REQUIRED`.
  146. If it's neither `None` nor a string we assume it is already the numeric
  147. constant which can directly be passed to wrap_socket.
  148. """
  149. if candidate is None:
  150. return CERT_REQUIRED
  151. if isinstance(candidate, str):
  152. res = getattr(ssl, candidate, None)
  153. if res is None:
  154. res = getattr(ssl, "CERT_" + candidate)
  155. return res # type: ignore[no-any-return]
  156. return candidate # type: ignore[return-value]
  157. def resolve_ssl_version(candidate: None | int | str) -> int:
  158. """
  159. like resolve_cert_reqs
  160. """
  161. if candidate is None:
  162. return PROTOCOL_TLS
  163. if isinstance(candidate, str):
  164. res = getattr(ssl, candidate, None)
  165. if res is None:
  166. res = getattr(ssl, "PROTOCOL_" + candidate)
  167. return typing.cast(int, res)
  168. return candidate
  169. def create_urllib3_context(
  170. ssl_version: int | None = None,
  171. cert_reqs: int | None = None,
  172. options: int | None = None,
  173. ciphers: str | None = None,
  174. ssl_minimum_version: int | None = None,
  175. ssl_maximum_version: int | None = None,
  176. ) -> ssl.SSLContext:
  177. """Creates and configures an :class:`ssl.SSLContext` instance for use with urllib3.
  178. :param ssl_version:
  179. The desired protocol version to use. This will default to
  180. PROTOCOL_SSLv23 which will negotiate the highest protocol that both
  181. the server and your installation of OpenSSL support.
  182. This parameter is deprecated instead use 'ssl_minimum_version'.
  183. :param ssl_minimum_version:
  184. The minimum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value.
  185. :param ssl_maximum_version:
  186. The maximum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value.
  187. Not recommended to set to anything other than 'ssl.TLSVersion.MAXIMUM_SUPPORTED' which is the
  188. default value.
  189. :param cert_reqs:
  190. Whether to require the certificate verification. This defaults to
  191. ``ssl.CERT_REQUIRED``.
  192. :param options:
  193. Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
  194. ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``.
  195. :param ciphers:
  196. Which cipher suites to allow the server to select. Defaults to either system configured
  197. ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers.
  198. :returns:
  199. Constructed SSLContext object with specified options
  200. :rtype: SSLContext
  201. """
  202. if SSLContext is None:
  203. raise TypeError("Can't create an SSLContext object without an ssl module")
  204. # This means 'ssl_version' was specified as an exact value.
  205. if ssl_version not in (None, PROTOCOL_TLS, PROTOCOL_TLS_CLIENT):
  206. # Disallow setting 'ssl_version' and 'ssl_minimum|maximum_version'
  207. # to avoid conflicts.
  208. if ssl_minimum_version is not None or ssl_maximum_version is not None:
  209. raise ValueError(
  210. "Can't specify both 'ssl_version' and either "
  211. "'ssl_minimum_version' or 'ssl_maximum_version'"
  212. )
  213. # 'ssl_version' is deprecated and will be removed in the future.
  214. else:
  215. # Use 'ssl_minimum_version' and 'ssl_maximum_version' instead.
  216. ssl_minimum_version = _SSL_VERSION_TO_TLS_VERSION.get(
  217. ssl_version, TLSVersion.MINIMUM_SUPPORTED
  218. )
  219. ssl_maximum_version = _SSL_VERSION_TO_TLS_VERSION.get(
  220. ssl_version, TLSVersion.MAXIMUM_SUPPORTED
  221. )
  222. # This warning message is pushing users to use 'ssl_minimum_version'
  223. # instead of both min/max. Best practice is to only set the minimum version and
  224. # keep the maximum version to be it's default value: 'TLSVersion.MAXIMUM_SUPPORTED'
  225. warnings.warn(
  226. "'ssl_version' option is deprecated and will be "
  227. "removed in urllib3 v2.1.0. Instead use 'ssl_minimum_version'",
  228. category=DeprecationWarning,
  229. stacklevel=2,
  230. )
  231. # PROTOCOL_TLS is deprecated in Python 3.10 so we always use PROTOCOL_TLS_CLIENT
  232. context = SSLContext(PROTOCOL_TLS_CLIENT)
  233. if ssl_minimum_version is not None:
  234. context.minimum_version = ssl_minimum_version
  235. else: # Python <3.10 defaults to 'MINIMUM_SUPPORTED' so explicitly set TLSv1.2 here
  236. context.minimum_version = TLSVersion.TLSv1_2
  237. if ssl_maximum_version is not None:
  238. context.maximum_version = ssl_maximum_version
  239. # Unless we're given ciphers defer to either system ciphers in
  240. # the case of OpenSSL 1.1.1+ or use our own secure default ciphers.
  241. if ciphers:
  242. context.set_ciphers(ciphers)
  243. # Setting the default here, as we may have no ssl module on import
  244. cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
  245. if options is None:
  246. options = 0
  247. # SSLv2 is easily broken and is considered harmful and dangerous
  248. options |= OP_NO_SSLv2
  249. # SSLv3 has several problems and is now dangerous
  250. options |= OP_NO_SSLv3
  251. # Disable compression to prevent CRIME attacks for OpenSSL 1.0+
  252. # (issue #309)
  253. options |= OP_NO_COMPRESSION
  254. # TLSv1.2 only. Unless set explicitly, do not request tickets.
  255. # This may save some bandwidth on wire, and although the ticket is encrypted,
  256. # there is a risk associated with it being on wire,
  257. # if the server is not rotating its ticketing keys properly.
  258. options |= OP_NO_TICKET
  259. context.options |= options
  260. # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is
  261. # necessary for conditional client cert authentication with TLS 1.3.
  262. # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older
  263. # versions of Python. We only enable on Python 3.7.4+ or if certificate
  264. # verification is enabled to work around Python issue #37428
  265. # See: https://bugs.python.org/issue37428
  266. if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr(
  267. context, "post_handshake_auth", None
  268. ) is not None:
  269. context.post_handshake_auth = True
  270. # The order of the below lines setting verify_mode and check_hostname
  271. # matter due to safe-guards SSLContext has to prevent an SSLContext with
  272. # check_hostname=True, verify_mode=NONE/OPTIONAL.
  273. # We always set 'check_hostname=False' for pyOpenSSL so we rely on our own
  274. # 'ssl.match_hostname()' implementation.
  275. if cert_reqs == ssl.CERT_REQUIRED and not IS_PYOPENSSL:
  276. context.verify_mode = cert_reqs
  277. context.check_hostname = True
  278. else:
  279. context.check_hostname = False
  280. context.verify_mode = cert_reqs
  281. try:
  282. context.hostname_checks_common_name = False
  283. except AttributeError:
  284. pass
  285. # Enable logging of TLS session keys via defacto standard environment variable
  286. # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values.
  287. if hasattr(context, "keylog_filename"):
  288. sslkeylogfile = os.environ.get("SSLKEYLOGFILE")
  289. if sslkeylogfile:
  290. context.keylog_filename = sslkeylogfile
  291. return context
  292. @typing.overload
  293. def ssl_wrap_socket(
  294. sock: socket.socket,
  295. keyfile: str | None = ...,
  296. certfile: str | None = ...,
  297. cert_reqs: int | None = ...,
  298. ca_certs: str | None = ...,
  299. server_hostname: str | None = ...,
  300. ssl_version: int | None = ...,
  301. ciphers: str | None = ...,
  302. ssl_context: ssl.SSLContext | None = ...,
  303. ca_cert_dir: str | None = ...,
  304. key_password: str | None = ...,
  305. ca_cert_data: None | str | bytes = ...,
  306. tls_in_tls: Literal[False] = ...,
  307. ) -> ssl.SSLSocket:
  308. ...
  309. @typing.overload
  310. def ssl_wrap_socket(
  311. sock: socket.socket,
  312. keyfile: str | None = ...,
  313. certfile: str | None = ...,
  314. cert_reqs: int | None = ...,
  315. ca_certs: str | None = ...,
  316. server_hostname: str | None = ...,
  317. ssl_version: int | None = ...,
  318. ciphers: str | None = ...,
  319. ssl_context: ssl.SSLContext | None = ...,
  320. ca_cert_dir: str | None = ...,
  321. key_password: str | None = ...,
  322. ca_cert_data: None | str | bytes = ...,
  323. tls_in_tls: bool = ...,
  324. ) -> ssl.SSLSocket | SSLTransportType:
  325. ...
  326. def ssl_wrap_socket(
  327. sock: socket.socket,
  328. keyfile: str | None = None,
  329. certfile: str | None = None,
  330. cert_reqs: int | None = None,
  331. ca_certs: str | None = None,
  332. server_hostname: str | None = None,
  333. ssl_version: int | None = None,
  334. ciphers: str | None = None,
  335. ssl_context: ssl.SSLContext | None = None,
  336. ca_cert_dir: str | None = None,
  337. key_password: str | None = None,
  338. ca_cert_data: None | str | bytes = None,
  339. tls_in_tls: bool = False,
  340. ) -> ssl.SSLSocket | SSLTransportType:
  341. """
  342. All arguments except for server_hostname, ssl_context, and ca_cert_dir have
  343. the same meaning as they do when using :func:`ssl.wrap_socket`.
  344. :param server_hostname:
  345. When SNI is supported, the expected hostname of the certificate
  346. :param ssl_context:
  347. A pre-made :class:`SSLContext` object. If none is provided, one will
  348. be created using :func:`create_urllib3_context`.
  349. :param ciphers:
  350. A string of ciphers we wish the client to support.
  351. :param ca_cert_dir:
  352. A directory containing CA certificates in multiple separate files, as
  353. supported by OpenSSL's -CApath flag or the capath argument to
  354. SSLContext.load_verify_locations().
  355. :param key_password:
  356. Optional password if the keyfile is encrypted.
  357. :param ca_cert_data:
  358. Optional string containing CA certificates in PEM format suitable for
  359. passing as the cadata parameter to SSLContext.load_verify_locations()
  360. :param tls_in_tls:
  361. Use SSLTransport to wrap the existing socket.
  362. """
  363. context = ssl_context
  364. if context is None:
  365. # Note: This branch of code and all the variables in it are only used in tests.
  366. # We should consider deprecating and removing this code.
  367. context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers)
  368. if ca_certs or ca_cert_dir or ca_cert_data:
  369. try:
  370. context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data)
  371. except OSError as e:
  372. raise SSLError(e) from e
  373. elif ssl_context is None and hasattr(context, "load_default_certs"):
  374. # try to load OS default certs; works well on Windows.
  375. context.load_default_certs()
  376. # Attempt to detect if we get the goofy behavior of the
  377. # keyfile being encrypted and OpenSSL asking for the
  378. # passphrase via the terminal and instead error out.
  379. if keyfile and key_password is None and _is_key_file_encrypted(keyfile):
  380. raise SSLError("Client private key is encrypted, password is required")
  381. if certfile:
  382. if key_password is None:
  383. context.load_cert_chain(certfile, keyfile)
  384. else:
  385. context.load_cert_chain(certfile, keyfile, key_password)
  386. try:
  387. context.set_alpn_protocols(ALPN_PROTOCOLS)
  388. except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols
  389. pass
  390. ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)
  391. return ssl_sock
  392. def is_ipaddress(hostname: str | bytes) -> bool:
  393. """Detects whether the hostname given is an IPv4 or IPv6 address.
  394. Also detects IPv6 addresses with Zone IDs.
  395. :param str hostname: Hostname to examine.
  396. :return: True if the hostname is an IP address, False otherwise.
  397. """
  398. if isinstance(hostname, bytes):
  399. # IDN A-label bytes are ASCII compatible.
  400. hostname = hostname.decode("ascii")
  401. return bool(_IPV4_RE.match(hostname) or _BRACELESS_IPV6_ADDRZ_RE.match(hostname))
  402. def _is_key_file_encrypted(key_file: str) -> bool:
  403. """Detects if a key file is encrypted or not."""
  404. with open(key_file) as f:
  405. for line in f:
  406. # Look for Proc-Type: 4,ENCRYPTED
  407. if "ENCRYPTED" in line:
  408. return True
  409. return False
  410. def _ssl_wrap_socket_impl(
  411. sock: socket.socket,
  412. ssl_context: ssl.SSLContext,
  413. tls_in_tls: bool,
  414. server_hostname: str | None = None,
  415. ) -> ssl.SSLSocket | SSLTransportType:
  416. if tls_in_tls:
  417. if not SSLTransport:
  418. # Import error, ssl is not available.
  419. raise ProxySchemeUnsupported(
  420. "TLS in TLS requires support for the 'ssl' module"
  421. )
  422. SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context)
  423. return SSLTransport(sock, ssl_context, server_hostname)
  424. return ssl_context.wrap_socket(sock, server_hostname=server_hostname)