catocli 2.0.3__py3-none-any.whl → 2.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of catocli might be problematic. Click here for more details.

Files changed (291) hide show
  1. build/lib/catocli/Utils/clidriver.py +268 -0
  2. build/lib/catocli/Utils/profile_manager.py +188 -0
  3. build/lib/catocli/Utils/version_checker.py +192 -0
  4. build/lib/catocli/__init__.py +2 -0
  5. build/lib/catocli/__main__.py +12 -0
  6. build/lib/catocli/parsers/configure/__init__.py +115 -0
  7. build/lib/catocli/parsers/configure/configure.py +307 -0
  8. build/lib/catocli/parsers/custom/__init__.py +57 -0
  9. build/lib/catocli/parsers/custom/customLib.py +561 -0
  10. build/lib/catocli/parsers/custom/export_rules/__init__.py +42 -0
  11. build/lib/catocli/parsers/custom/export_rules/export_rules.py +234 -0
  12. build/lib/catocli/parsers/custom/export_sites/__init__.py +21 -0
  13. build/lib/catocli/parsers/custom/export_sites/export_sites.py +372 -0
  14. build/lib/catocli/parsers/custom/import_rules_to_tf/__init__.py +58 -0
  15. build/lib/catocli/parsers/custom/import_rules_to_tf/import_rules_to_tf.py +451 -0
  16. build/lib/catocli/parsers/custom/import_sites_to_tf/__init__.py +45 -0
  17. build/lib/catocli/parsers/custom/import_sites_to_tf/import_sites_to_tf.py +891 -0
  18. build/lib/catocli/parsers/mutation_accountManagement/__init__.py +48 -0
  19. build/lib/catocli/parsers/mutation_admin/__init__.py +48 -0
  20. build/lib/catocli/parsers/mutation_container/__init__.py +138 -0
  21. build/lib/catocli/parsers/mutation_hardware/__init__.py +22 -0
  22. build/lib/catocli/parsers/mutation_policy/__init__.py +1305 -0
  23. build/lib/catocli/parsers/mutation_sandbox/__init__.py +35 -0
  24. build/lib/catocli/parsers/mutation_site/__init__.py +373 -0
  25. build/lib/catocli/parsers/mutation_sites/__init__.py +373 -0
  26. build/lib/catocli/parsers/mutation_xdr/__init__.py +48 -0
  27. build/lib/catocli/parsers/parserApiClient.py +513 -0
  28. build/lib/catocli/parsers/query_accountBySubdomain/__init__.py +16 -0
  29. build/lib/catocli/parsers/query_accountManagement/__init__.py +16 -0
  30. build/lib/catocli/parsers/query_accountMetrics/__init__.py +16 -0
  31. build/lib/catocli/parsers/query_accountRoles/__init__.py +16 -0
  32. build/lib/catocli/parsers/query_accountSnapshot/__init__.py +16 -0
  33. build/lib/catocli/parsers/query_admin/__init__.py +16 -0
  34. build/lib/catocli/parsers/query_admins/__init__.py +16 -0
  35. build/lib/catocli/parsers/query_appStats/__init__.py +16 -0
  36. build/lib/catocli/parsers/query_appStatsTimeSeries/__init__.py +16 -0
  37. build/lib/catocli/parsers/query_auditFeed/__init__.py +16 -0
  38. build/lib/catocli/parsers/query_catalogs/__init__.py +16 -0
  39. build/lib/catocli/parsers/query_container/__init__.py +16 -0
  40. build/lib/catocli/parsers/query_devices/__init__.py +16 -0
  41. build/lib/catocli/parsers/query_entityLookup/__init__.py +16 -0
  42. build/lib/catocli/parsers/query_events/__init__.py +16 -0
  43. build/lib/catocli/parsers/query_eventsFeed/__init__.py +16 -0
  44. build/lib/catocli/parsers/query_eventsTimeSeries/__init__.py +16 -0
  45. build/lib/catocli/parsers/query_hardware/__init__.py +16 -0
  46. build/lib/catocli/parsers/query_hardwareManagement/__init__.py +16 -0
  47. build/lib/catocli/parsers/query_licensing/__init__.py +16 -0
  48. build/lib/catocli/parsers/query_policy/__init__.py +161 -0
  49. build/lib/catocli/parsers/query_sandbox/__init__.py +16 -0
  50. build/lib/catocli/parsers/query_site/__init__.py +100 -0
  51. build/lib/catocli/parsers/query_siteLocation/__init__.py +13 -0
  52. build/lib/catocli/parsers/query_subDomains/__init__.py +16 -0
  53. build/lib/catocli/parsers/query_xdr/__init__.py +35 -0
  54. build/lib/catocli/parsers/raw/__init__.py +12 -0
  55. build/lib/graphql_client/__init__.py +11 -0
  56. build/lib/graphql_client/api/__init__.py +3 -0
  57. build/lib/graphql_client/api/call_api.py +84 -0
  58. build/lib/graphql_client/api_client.py +192 -0
  59. build/lib/graphql_client/api_client_types.py +409 -0
  60. build/lib/graphql_client/configuration.py +232 -0
  61. build/lib/graphql_client/models/__init__.py +13 -0
  62. build/lib/graphql_client/models/no_schema.py +71 -0
  63. build/lib/schema/catolib.py +1141 -0
  64. build/lib/schema/importSchema.py +60 -0
  65. build/lib/schema/remove_policyid.py +89 -0
  66. build/lib/schema/remove_policyid_mutations.py +89 -0
  67. build/lib/scripts/catolib.py +62 -0
  68. build/lib/scripts/export_if_rules_to_json.py +188 -0
  69. build/lib/scripts/export_wf_rules_to_json.py +111 -0
  70. build/lib/scripts/import_wf_rules_to_tfstate.py +331 -0
  71. build/lib/vendor/certifi/__init__.py +4 -0
  72. build/lib/vendor/certifi/__main__.py +12 -0
  73. build/lib/vendor/certifi/core.py +114 -0
  74. build/lib/vendor/certifi/py.typed +0 -0
  75. build/lib/vendor/six.py +998 -0
  76. build/lib/vendor/urllib3/__init__.py +211 -0
  77. build/lib/vendor/urllib3/_base_connection.py +172 -0
  78. build/lib/vendor/urllib3/_collections.py +483 -0
  79. build/lib/vendor/urllib3/_request_methods.py +278 -0
  80. build/lib/vendor/urllib3/_version.py +16 -0
  81. build/lib/vendor/urllib3/connection.py +1033 -0
  82. build/lib/vendor/urllib3/connectionpool.py +1182 -0
  83. build/lib/vendor/urllib3/contrib/__init__.py +0 -0
  84. build/lib/vendor/urllib3/contrib/emscripten/__init__.py +18 -0
  85. build/lib/vendor/urllib3/contrib/emscripten/connection.py +254 -0
  86. build/lib/vendor/urllib3/contrib/emscripten/fetch.py +418 -0
  87. build/lib/vendor/urllib3/contrib/emscripten/request.py +22 -0
  88. build/lib/vendor/urllib3/contrib/emscripten/response.py +285 -0
  89. build/lib/vendor/urllib3/contrib/pyopenssl.py +552 -0
  90. build/lib/vendor/urllib3/contrib/socks.py +228 -0
  91. build/lib/vendor/urllib3/exceptions.py +321 -0
  92. build/lib/vendor/urllib3/fields.py +341 -0
  93. build/lib/vendor/urllib3/filepost.py +89 -0
  94. build/lib/vendor/urllib3/http2/__init__.py +53 -0
  95. build/lib/vendor/urllib3/http2/connection.py +356 -0
  96. build/lib/vendor/urllib3/http2/probe.py +87 -0
  97. build/lib/vendor/urllib3/poolmanager.py +637 -0
  98. build/lib/vendor/urllib3/py.typed +2 -0
  99. build/lib/vendor/urllib3/response.py +1265 -0
  100. build/lib/vendor/urllib3/util/__init__.py +42 -0
  101. build/lib/vendor/urllib3/util/connection.py +137 -0
  102. build/lib/vendor/urllib3/util/proxy.py +43 -0
  103. build/lib/vendor/urllib3/util/request.py +256 -0
  104. build/lib/vendor/urllib3/util/response.py +101 -0
  105. build/lib/vendor/urllib3/util/retry.py +533 -0
  106. build/lib/vendor/urllib3/util/ssl_.py +513 -0
  107. build/lib/vendor/urllib3/util/ssl_match_hostname.py +159 -0
  108. build/lib/vendor/urllib3/util/ssltransport.py +276 -0
  109. build/lib/vendor/urllib3/util/timeout.py +275 -0
  110. build/lib/vendor/urllib3/util/url.py +471 -0
  111. build/lib/vendor/urllib3/util/util.py +42 -0
  112. build/lib/vendor/urllib3/util/wait.py +124 -0
  113. catocli/Utils/clidriver.py +1 -4
  114. catocli/__init__.py +1 -1
  115. catocli/parsers/custom/export_rules/__init__.py +2 -0
  116. catocli/parsers/custom/export_rules/export_rules.py +29 -5
  117. catocli/parsers/custom/export_sites/__init__.py +1 -0
  118. catocli/parsers/custom/export_sites/export_sites.py +10 -3
  119. catocli/parsers/mutation_container/__init__.py +116 -0
  120. catocli/parsers/mutation_container_fqdn/README.md +7 -0
  121. catocli/parsers/mutation_container_fqdn_addValues/README.md +17 -0
  122. catocli/parsers/mutation_container_fqdn_createFromFile/README.md +17 -0
  123. catocli/parsers/mutation_container_fqdn_removeValues/README.md +17 -0
  124. catocli/parsers/mutation_container_fqdn_updateFromFile/README.md +17 -0
  125. catocli/parsers/mutation_container_ipAddressRange/README.md +7 -0
  126. catocli/parsers/mutation_container_ipAddressRange_addValues/README.md +17 -0
  127. catocli/parsers/mutation_container_ipAddressRange_createFromFile/README.md +17 -0
  128. catocli/parsers/mutation_container_ipAddressRange_removeValues/README.md +17 -0
  129. catocli/parsers/mutation_container_ipAddressRange_updateFromFile/README.md +17 -0
  130. catocli/parsers/mutation_policy_internetFirewall_addRule/README.md +1 -1
  131. catocli/parsers/mutation_policy_internetFirewall_updateRule/README.md +1 -1
  132. catocli/parsers/mutation_policy_wanFirewall_addRule/README.md +1 -1
  133. catocli/parsers/mutation_policy_wanFirewall_updateRule/README.md +1 -1
  134. catocli/parsers/parserApiClient.py +183 -7
  135. catocli/parsers/query_policy/README.md +0 -17
  136. catocli/parsers/query_policy/__init__.py +153 -8
  137. catocli/parsers/query_policy_appTenantRestriction/README.md +7 -0
  138. catocli/parsers/query_policy_appTenantRestriction_policy/README.md +17 -0
  139. catocli/parsers/query_policy_dynamicIpAllocation/README.md +7 -0
  140. catocli/parsers/query_policy_dynamicIpAllocation_policy/README.md +17 -0
  141. catocli/parsers/query_policy_internetFirewall/README.md +7 -0
  142. catocli/parsers/query_policy_internetFirewall_policy/README.md +17 -0
  143. catocli/parsers/query_policy_remotePortFwd/README.md +7 -0
  144. catocli/parsers/query_policy_remotePortFwd_policy/README.md +17 -0
  145. catocli/parsers/query_policy_socketLan/README.md +7 -0
  146. catocli/parsers/query_policy_socketLan_policy/README.md +17 -0
  147. catocli/parsers/query_policy_terminalServer/README.md +7 -0
  148. catocli/parsers/query_policy_terminalServer_policy/README.md +17 -0
  149. catocli/parsers/query_policy_wanFirewall/README.md +7 -0
  150. catocli/parsers/query_policy_wanFirewall_policy/README.md +17 -0
  151. catocli/parsers/query_policy_wanNetwork/README.md +7 -0
  152. catocli/parsers/query_policy_wanNetwork_policy/README.md +17 -0
  153. catocli/parsers/query_site/README.md +0 -16
  154. catocli/parsers/query_site/__init__.py +92 -8
  155. catocli/parsers/query_site_availableVersionList/README.md +17 -0
  156. catocli/parsers/query_site_bgpPeer/README.md +17 -0
  157. catocli/parsers/query_site_bgpPeerList/README.md +17 -0
  158. catocli/parsers/query_site_cloudInterconnectConnectionConnectivity/README.md +17 -0
  159. catocli/parsers/query_site_cloudInterconnectPhysicalConnection/README.md +17 -0
  160. catocli/parsers/query_site_cloudInterconnectPhysicalConnectionId/README.md +17 -0
  161. catocli/parsers/query_site_siteBgpStatus/README.md +17 -0
  162. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/METADATA +1 -1
  163. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/RECORD +291 -121
  164. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/top_level.txt +1 -0
  165. graphql_client/api/call_api.py +4 -3
  166. models/mutation.container.fqdn.addValues.json +866 -0
  167. models/mutation.container.fqdn.createFromFile.json +819 -0
  168. models/mutation.container.fqdn.removeValues.json +866 -0
  169. models/mutation.container.fqdn.updateFromFile.json +1045 -0
  170. models/mutation.container.ipAddressRange.addValues.json +1020 -0
  171. models/mutation.container.ipAddressRange.createFromFile.json +819 -0
  172. models/mutation.container.ipAddressRange.removeValues.json +1020 -0
  173. models/mutation.container.ipAddressRange.updateFromFile.json +1045 -0
  174. models/mutation.policy.appTenantRestriction.addRule.json +8 -8
  175. models/mutation.policy.appTenantRestriction.addSection.json +1 -1
  176. models/mutation.policy.appTenantRestriction.createPolicyRevision.json +2 -2
  177. models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +2 -2
  178. models/mutation.policy.appTenantRestriction.moveRule.json +2 -2
  179. models/mutation.policy.appTenantRestriction.moveSection.json +1 -1
  180. models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +2 -2
  181. models/mutation.policy.appTenantRestriction.removeRule.json +2 -2
  182. models/mutation.policy.appTenantRestriction.removeSection.json +1 -1
  183. models/mutation.policy.appTenantRestriction.updatePolicy.json +2 -2
  184. models/mutation.policy.appTenantRestriction.updateRule.json +8 -8
  185. models/mutation.policy.appTenantRestriction.updateSection.json +1 -1
  186. models/mutation.policy.dynamicIpAllocation.addRule.json +1 -1
  187. models/mutation.policy.dynamicIpAllocation.addSection.json +1 -1
  188. models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +1 -1
  189. models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +1 -1
  190. models/mutation.policy.dynamicIpAllocation.moveRule.json +1 -1
  191. models/mutation.policy.dynamicIpAllocation.moveSection.json +1 -1
  192. models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +1 -1
  193. models/mutation.policy.dynamicIpAllocation.removeRule.json +1 -1
  194. models/mutation.policy.dynamicIpAllocation.removeSection.json +1 -1
  195. models/mutation.policy.dynamicIpAllocation.updatePolicy.json +1 -1
  196. models/mutation.policy.dynamicIpAllocation.updateRule.json +1 -1
  197. models/mutation.policy.dynamicIpAllocation.updateSection.json +1 -1
  198. models/mutation.policy.internetFirewall.addRule.json +502 -55
  199. models/mutation.policy.internetFirewall.addSection.json +1 -1
  200. models/mutation.policy.internetFirewall.createPolicyRevision.json +127 -10
  201. models/mutation.policy.internetFirewall.discardPolicyRevision.json +127 -10
  202. models/mutation.policy.internetFirewall.moveRule.json +127 -10
  203. models/mutation.policy.internetFirewall.moveSection.json +1 -1
  204. models/mutation.policy.internetFirewall.publishPolicyRevision.json +127 -10
  205. models/mutation.policy.internetFirewall.removeRule.json +127 -10
  206. models/mutation.policy.internetFirewall.removeSection.json +1 -1
  207. models/mutation.policy.internetFirewall.updatePolicy.json +127 -10
  208. models/mutation.policy.internetFirewall.updateRule.json +493 -55
  209. models/mutation.policy.internetFirewall.updateSection.json +1 -1
  210. models/mutation.policy.remotePortFwd.addRule.json +5 -5
  211. models/mutation.policy.remotePortFwd.addSection.json +1 -1
  212. models/mutation.policy.remotePortFwd.createPolicyRevision.json +2 -2
  213. models/mutation.policy.remotePortFwd.discardPolicyRevision.json +2 -2
  214. models/mutation.policy.remotePortFwd.moveRule.json +2 -2
  215. models/mutation.policy.remotePortFwd.moveSection.json +1 -1
  216. models/mutation.policy.remotePortFwd.publishPolicyRevision.json +2 -2
  217. models/mutation.policy.remotePortFwd.removeRule.json +2 -2
  218. models/mutation.policy.remotePortFwd.removeSection.json +1 -1
  219. models/mutation.policy.remotePortFwd.updatePolicy.json +2 -2
  220. models/mutation.policy.remotePortFwd.updateRule.json +5 -5
  221. models/mutation.policy.remotePortFwd.updateSection.json +1 -1
  222. models/mutation.policy.socketLan.addRule.json +16 -16
  223. models/mutation.policy.socketLan.addSection.json +1 -1
  224. models/mutation.policy.socketLan.createPolicyRevision.json +4 -4
  225. models/mutation.policy.socketLan.discardPolicyRevision.json +4 -4
  226. models/mutation.policy.socketLan.moveRule.json +4 -4
  227. models/mutation.policy.socketLan.moveSection.json +1 -1
  228. models/mutation.policy.socketLan.publishPolicyRevision.json +4 -4
  229. models/mutation.policy.socketLan.removeRule.json +4 -4
  230. models/mutation.policy.socketLan.removeSection.json +1 -1
  231. models/mutation.policy.socketLan.updatePolicy.json +4 -4
  232. models/mutation.policy.socketLan.updateRule.json +16 -16
  233. models/mutation.policy.socketLan.updateSection.json +1 -1
  234. models/mutation.policy.terminalServer.addRule.json +1 -1
  235. models/mutation.policy.terminalServer.addSection.json +1 -1
  236. models/mutation.policy.terminalServer.createPolicyRevision.json +1 -1
  237. models/mutation.policy.terminalServer.discardPolicyRevision.json +1 -1
  238. models/mutation.policy.terminalServer.moveRule.json +1 -1
  239. models/mutation.policy.terminalServer.moveSection.json +1 -1
  240. models/mutation.policy.terminalServer.publishPolicyRevision.json +1 -1
  241. models/mutation.policy.terminalServer.removeRule.json +1 -1
  242. models/mutation.policy.terminalServer.removeSection.json +1 -1
  243. models/mutation.policy.terminalServer.updatePolicy.json +1 -1
  244. models/mutation.policy.terminalServer.updateRule.json +1 -1
  245. models/mutation.policy.terminalServer.updateSection.json +1 -1
  246. models/mutation.policy.wanFirewall.addRule.json +500 -53
  247. models/mutation.policy.wanFirewall.addSection.json +1 -1
  248. models/mutation.policy.wanFirewall.createPolicyRevision.json +128 -11
  249. models/mutation.policy.wanFirewall.discardPolicyRevision.json +128 -11
  250. models/mutation.policy.wanFirewall.moveRule.json +128 -11
  251. models/mutation.policy.wanFirewall.moveSection.json +1 -1
  252. models/mutation.policy.wanFirewall.publishPolicyRevision.json +128 -11
  253. models/mutation.policy.wanFirewall.removeRule.json +128 -11
  254. models/mutation.policy.wanFirewall.removeSection.json +1 -1
  255. models/mutation.policy.wanFirewall.updatePolicy.json +128 -11
  256. models/mutation.policy.wanFirewall.updateRule.json +491 -53
  257. models/mutation.policy.wanFirewall.updateSection.json +1 -1
  258. models/mutation.policy.wanNetwork.addRule.json +13 -13
  259. models/mutation.policy.wanNetwork.addSection.json +1 -1
  260. models/mutation.policy.wanNetwork.createPolicyRevision.json +1 -1
  261. models/mutation.policy.wanNetwork.discardPolicyRevision.json +1 -1
  262. models/mutation.policy.wanNetwork.moveRule.json +1 -1
  263. models/mutation.policy.wanNetwork.moveSection.json +1 -1
  264. models/mutation.policy.wanNetwork.publishPolicyRevision.json +1 -1
  265. models/mutation.policy.wanNetwork.removeRule.json +1 -1
  266. models/mutation.policy.wanNetwork.removeSection.json +1 -1
  267. models/mutation.policy.wanNetwork.updatePolicy.json +1 -1
  268. models/mutation.policy.wanNetwork.updateRule.json +13 -13
  269. models/mutation.policy.wanNetwork.updateSection.json +1 -1
  270. models/query.policy.appTenantRestriction.policy.json +3086 -0
  271. models/query.policy.dynamicIpAllocation.policy.json +1934 -0
  272. models/query.policy.internetFirewall.policy.json +7833 -0
  273. models/query.policy.json +233 -0
  274. models/query.policy.remotePortFwd.policy.json +2387 -0
  275. models/query.policy.socketLan.policy.json +7140 -0
  276. models/query.policy.terminalServer.policy.json +1632 -0
  277. models/query.policy.wanFirewall.policy.json +9212 -0
  278. models/query.policy.wanNetwork.policy.json +8010 -0
  279. models/query.site.availableVersionList.json +365 -0
  280. models/query.site.bgpPeer.json +1917 -0
  281. models/query.site.bgpPeerList.json +2076 -0
  282. models/query.site.cloudInterconnectConnectionConnectivity.json +298 -0
  283. models/query.site.cloudInterconnectPhysicalConnection.json +728 -0
  284. models/query.site.cloudInterconnectPhysicalConnectionId.json +660 -0
  285. models/query.site.siteBgpStatus.json +869 -0
  286. schema/catolib.py +13 -6
  287. schema/remove_policyid.py +89 -0
  288. schema/remove_policyid_mutations.py +89 -0
  289. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/LICENSE +0 -0
  290. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/WHEEL +0 -0
  291. {catocli-2.0.3.dist-info → catocli-2.0.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,513 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import hmac
5
+ import os
6
+ import socket
7
+ import sys
8
+ import typing
9
+ import warnings
10
+ from binascii import unhexlify
11
+
12
+ from ..exceptions import ProxySchemeUnsupported, SSLError
13
+ from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE
14
+
15
+ SSLContext = None
16
+ SSLTransport = None
17
+ HAS_NEVER_CHECK_COMMON_NAME = False
18
+ IS_PYOPENSSL = False
19
+ ALPN_PROTOCOLS = ["http/1.1"]
20
+
21
+ _TYPE_VERSION_INFO = typing.Tuple[int, int, int, str, int]
22
+
23
+ # Maps the length of a digest to a possible hash function producing this digest
24
+ HASHFUNC_MAP = {
25
+ length: getattr(hashlib, algorithm, None)
26
+ for length, algorithm in ((32, "md5"), (40, "sha1"), (64, "sha256"))
27
+ }
28
+
29
+
30
+ def _is_bpo_43522_fixed(
31
+ implementation_name: str,
32
+ version_info: _TYPE_VERSION_INFO,
33
+ pypy_version_info: _TYPE_VERSION_INFO | None,
34
+ ) -> bool:
35
+ """Return True for CPython 3.8.9+, 3.9.3+ or 3.10+ and PyPy 7.3.8+ where
36
+ setting SSLContext.hostname_checks_common_name to False works.
37
+
38
+ Outside of CPython and PyPy we don't know which implementations work
39
+ or not so we conservatively use our hostname matching as we know that works
40
+ on all implementations.
41
+
42
+ https://github.com/urllib3/urllib3/issues/2192#issuecomment-821832963
43
+ https://foss.heptapod.net/pypy/pypy/-/issues/3539
44
+ """
45
+ if implementation_name == "pypy":
46
+ # https://foss.heptapod.net/pypy/pypy/-/issues/3129
47
+ return pypy_version_info >= (7, 3, 8) # type: ignore[operator]
48
+ elif implementation_name == "cpython":
49
+ major_minor = version_info[:2]
50
+ micro = version_info[2]
51
+ return (
52
+ (major_minor == (3, 8) and micro >= 9)
53
+ or (major_minor == (3, 9) and micro >= 3)
54
+ or major_minor >= (3, 10)
55
+ )
56
+ else: # Defensive:
57
+ return False
58
+
59
+
60
+ def _is_has_never_check_common_name_reliable(
61
+ openssl_version: str,
62
+ openssl_version_number: int,
63
+ implementation_name: str,
64
+ version_info: _TYPE_VERSION_INFO,
65
+ pypy_version_info: _TYPE_VERSION_INFO | None,
66
+ ) -> bool:
67
+ # As of May 2023, all released versions of LibreSSL fail to reject certificates with
68
+ # only common names, see https://github.com/urllib3/urllib3/pull/3024
69
+ is_openssl = openssl_version.startswith("OpenSSL ")
70
+ # Before fixing OpenSSL issue #14579, the SSL_new() API was not copying hostflags
71
+ # like X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, which tripped up CPython.
72
+ # https://github.com/openssl/openssl/issues/14579
73
+ # This was released in OpenSSL 1.1.1l+ (>=0x101010cf)
74
+ is_openssl_issue_14579_fixed = openssl_version_number >= 0x101010CF
75
+
76
+ return is_openssl and (
77
+ is_openssl_issue_14579_fixed
78
+ or _is_bpo_43522_fixed(implementation_name, version_info, pypy_version_info)
79
+ )
80
+
81
+
82
+ if typing.TYPE_CHECKING:
83
+ from ssl import VerifyMode
84
+ from typing import TypedDict
85
+
86
+ from .ssltransport import SSLTransport as SSLTransportType
87
+
88
+ class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False):
89
+ subjectAltName: tuple[tuple[str, str], ...]
90
+ subject: tuple[tuple[tuple[str, str], ...], ...]
91
+ serialNumber: str
92
+
93
+
94
+ # Mapping from 'ssl.PROTOCOL_TLSX' to 'TLSVersion.X'
95
+ _SSL_VERSION_TO_TLS_VERSION: dict[int, int] = {}
96
+
97
+ try: # Do we have ssl at all?
98
+ import ssl
99
+ from ssl import ( # type: ignore[assignment]
100
+ CERT_REQUIRED,
101
+ HAS_NEVER_CHECK_COMMON_NAME,
102
+ OP_NO_COMPRESSION,
103
+ OP_NO_TICKET,
104
+ OPENSSL_VERSION,
105
+ OPENSSL_VERSION_NUMBER,
106
+ PROTOCOL_TLS,
107
+ PROTOCOL_TLS_CLIENT,
108
+ OP_NO_SSLv2,
109
+ OP_NO_SSLv3,
110
+ SSLContext,
111
+ TLSVersion,
112
+ )
113
+
114
+ PROTOCOL_SSLv23 = PROTOCOL_TLS
115
+
116
+ # Setting SSLContext.hostname_checks_common_name = False didn't work before CPython
117
+ # 3.8.9, 3.9.3, and 3.10 (but OK on PyPy) or OpenSSL 1.1.1l+
118
+ if HAS_NEVER_CHECK_COMMON_NAME and not _is_has_never_check_common_name_reliable(
119
+ OPENSSL_VERSION,
120
+ OPENSSL_VERSION_NUMBER,
121
+ sys.implementation.name,
122
+ sys.version_info,
123
+ sys.pypy_version_info if sys.implementation.name == "pypy" else None, # type: ignore[attr-defined]
124
+ ):
125
+ HAS_NEVER_CHECK_COMMON_NAME = False
126
+
127
+ # Need to be careful here in case old TLS versions get
128
+ # removed in future 'ssl' module implementations.
129
+ for attr in ("TLSv1", "TLSv1_1", "TLSv1_2"):
130
+ try:
131
+ _SSL_VERSION_TO_TLS_VERSION[getattr(ssl, f"PROTOCOL_{attr}")] = getattr(
132
+ TLSVersion, attr
133
+ )
134
+ except AttributeError: # Defensive:
135
+ continue
136
+
137
+ from .ssltransport import SSLTransport # type: ignore[assignment]
138
+ except ImportError:
139
+ OP_NO_COMPRESSION = 0x20000 # type: ignore[assignment]
140
+ OP_NO_TICKET = 0x4000 # type: ignore[assignment]
141
+ OP_NO_SSLv2 = 0x1000000 # type: ignore[assignment]
142
+ OP_NO_SSLv3 = 0x2000000 # type: ignore[assignment]
143
+ PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 # type: ignore[assignment]
144
+ PROTOCOL_TLS_CLIENT = 16 # type: ignore[assignment]
145
+
146
+
147
+ _TYPE_PEER_CERT_RET = typing.Union["_TYPE_PEER_CERT_RET_DICT", bytes, None]
148
+
149
+
150
+ def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None:
151
+ """
152
+ Checks if given fingerprint matches the supplied certificate.
153
+
154
+ :param cert:
155
+ Certificate as bytes object.
156
+ :param fingerprint:
157
+ Fingerprint as string of hexdigits, can be interspersed by colons.
158
+ """
159
+
160
+ if cert is None:
161
+ raise SSLError("No certificate for the peer.")
162
+
163
+ fingerprint = fingerprint.replace(":", "").lower()
164
+ digest_length = len(fingerprint)
165
+ if digest_length not in HASHFUNC_MAP:
166
+ raise SSLError(f"Fingerprint of invalid length: {fingerprint}")
167
+ hashfunc = HASHFUNC_MAP.get(digest_length)
168
+ if hashfunc is None:
169
+ raise SSLError(
170
+ f"Hash function implementation unavailable for fingerprint length: {digest_length}"
171
+ )
172
+
173
+ # We need encode() here for py32; works on py2 and p33.
174
+ fingerprint_bytes = unhexlify(fingerprint.encode())
175
+
176
+ cert_digest = hashfunc(cert).digest()
177
+
178
+ if not hmac.compare_digest(cert_digest, fingerprint_bytes):
179
+ raise SSLError(
180
+ f'Fingerprints did not match. Expected "{fingerprint}", got "{cert_digest.hex()}"'
181
+ )
182
+
183
+
184
+ def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode:
185
+ """
186
+ Resolves the argument to a numeric constant, which can be passed to
187
+ the wrap_socket function/method from the ssl module.
188
+ Defaults to :data:`ssl.CERT_REQUIRED`.
189
+ If given a string it is assumed to be the name of the constant in the
190
+ :mod:`ssl` module or its abbreviation.
191
+ (So you can specify `REQUIRED` instead of `CERT_REQUIRED`.
192
+ If it's neither `None` nor a string we assume it is already the numeric
193
+ constant which can directly be passed to wrap_socket.
194
+ """
195
+ if candidate is None:
196
+ return CERT_REQUIRED
197
+
198
+ if isinstance(candidate, str):
199
+ res = getattr(ssl, candidate, None)
200
+ if res is None:
201
+ res = getattr(ssl, "CERT_" + candidate)
202
+ return res # type: ignore[no-any-return]
203
+
204
+ return candidate # type: ignore[return-value]
205
+
206
+
207
+ def resolve_ssl_version(candidate: None | int | str) -> int:
208
+ """
209
+ like resolve_cert_reqs
210
+ """
211
+ if candidate is None:
212
+ return PROTOCOL_TLS
213
+
214
+ if isinstance(candidate, str):
215
+ res = getattr(ssl, candidate, None)
216
+ if res is None:
217
+ res = getattr(ssl, "PROTOCOL_" + candidate)
218
+ return typing.cast(int, res)
219
+
220
+ return candidate
221
+
222
+
223
+ def create_urllib3_context(
224
+ ssl_version: int | None = None,
225
+ cert_reqs: int | None = None,
226
+ options: int | None = None,
227
+ ciphers: str | None = None,
228
+ ssl_minimum_version: int | None = None,
229
+ ssl_maximum_version: int | None = None,
230
+ ) -> ssl.SSLContext:
231
+ """Creates and configures an :class:`ssl.SSLContext` instance for use with urllib3.
232
+
233
+ :param ssl_version:
234
+ The desired protocol version to use. This will default to
235
+ PROTOCOL_SSLv23 which will negotiate the highest protocol that both
236
+ the server and your installation of OpenSSL support.
237
+
238
+ This parameter is deprecated instead use 'ssl_minimum_version'.
239
+ :param ssl_minimum_version:
240
+ The minimum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value.
241
+ :param ssl_maximum_version:
242
+ The maximum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value.
243
+ Not recommended to set to anything other than 'ssl.TLSVersion.MAXIMUM_SUPPORTED' which is the
244
+ default value.
245
+ :param cert_reqs:
246
+ Whether to require the certificate verification. This defaults to
247
+ ``ssl.CERT_REQUIRED``.
248
+ :param options:
249
+ Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
250
+ ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``.
251
+ :param ciphers:
252
+ Which cipher suites to allow the server to select. Defaults to either system configured
253
+ ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers.
254
+ :returns:
255
+ Constructed SSLContext object with specified options
256
+ :rtype: SSLContext
257
+ """
258
+ if SSLContext is None:
259
+ raise TypeError("Can't create an SSLContext object without an ssl module")
260
+
261
+ # This means 'ssl_version' was specified as an exact value.
262
+ if ssl_version not in (None, PROTOCOL_TLS, PROTOCOL_TLS_CLIENT):
263
+ # Disallow setting 'ssl_version' and 'ssl_minimum|maximum_version'
264
+ # to avoid conflicts.
265
+ if ssl_minimum_version is not None or ssl_maximum_version is not None:
266
+ raise ValueError(
267
+ "Can't specify both 'ssl_version' and either "
268
+ "'ssl_minimum_version' or 'ssl_maximum_version'"
269
+ )
270
+
271
+ # 'ssl_version' is deprecated and will be removed in the future.
272
+ else:
273
+ # Use 'ssl_minimum_version' and 'ssl_maximum_version' instead.
274
+ ssl_minimum_version = _SSL_VERSION_TO_TLS_VERSION.get(
275
+ ssl_version, TLSVersion.MINIMUM_SUPPORTED
276
+ )
277
+ ssl_maximum_version = _SSL_VERSION_TO_TLS_VERSION.get(
278
+ ssl_version, TLSVersion.MAXIMUM_SUPPORTED
279
+ )
280
+
281
+ # This warning message is pushing users to use 'ssl_minimum_version'
282
+ # instead of both min/max. Best practice is to only set the minimum version and
283
+ # keep the maximum version to be it's default value: 'TLSVersion.MAXIMUM_SUPPORTED'
284
+ warnings.warn(
285
+ "'ssl_version' option is deprecated and will be "
286
+ "removed in urllib3 v2.1.0. Instead use 'ssl_minimum_version'",
287
+ category=DeprecationWarning,
288
+ stacklevel=2,
289
+ )
290
+
291
+ # PROTOCOL_TLS is deprecated in Python 3.10 so we always use PROTOCOL_TLS_CLIENT
292
+ context = SSLContext(PROTOCOL_TLS_CLIENT)
293
+
294
+ if ssl_minimum_version is not None:
295
+ context.minimum_version = ssl_minimum_version
296
+ else: # Python <3.10 defaults to 'MINIMUM_SUPPORTED' so explicitly set TLSv1.2 here
297
+ context.minimum_version = TLSVersion.TLSv1_2
298
+
299
+ if ssl_maximum_version is not None:
300
+ context.maximum_version = ssl_maximum_version
301
+
302
+ # Unless we're given ciphers defer to either system ciphers in
303
+ # the case of OpenSSL 1.1.1+ or use our own secure default ciphers.
304
+ if ciphers:
305
+ context.set_ciphers(ciphers)
306
+
307
+ # Setting the default here, as we may have no ssl module on import
308
+ cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
309
+
310
+ if options is None:
311
+ options = 0
312
+ # SSLv2 is easily broken and is considered harmful and dangerous
313
+ options |= OP_NO_SSLv2
314
+ # SSLv3 has several problems and is now dangerous
315
+ options |= OP_NO_SSLv3
316
+ # Disable compression to prevent CRIME attacks for OpenSSL 1.0+
317
+ # (issue #309)
318
+ options |= OP_NO_COMPRESSION
319
+ # TLSv1.2 only. Unless set explicitly, do not request tickets.
320
+ # This may save some bandwidth on wire, and although the ticket is encrypted,
321
+ # there is a risk associated with it being on wire,
322
+ # if the server is not rotating its ticketing keys properly.
323
+ options |= OP_NO_TICKET
324
+
325
+ context.options |= options
326
+
327
+ # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is
328
+ # necessary for conditional client cert authentication with TLS 1.3.
329
+ # The attribute is None for OpenSSL <= 1.1.0 or does not exist when using
330
+ # an SSLContext created by pyOpenSSL.
331
+ if getattr(context, "post_handshake_auth", None) is not None:
332
+ context.post_handshake_auth = True
333
+
334
+ # The order of the below lines setting verify_mode and check_hostname
335
+ # matter due to safe-guards SSLContext has to prevent an SSLContext with
336
+ # check_hostname=True, verify_mode=NONE/OPTIONAL.
337
+ # We always set 'check_hostname=False' for pyOpenSSL so we rely on our own
338
+ # 'ssl.match_hostname()' implementation.
339
+ if cert_reqs == ssl.CERT_REQUIRED and not IS_PYOPENSSL:
340
+ context.verify_mode = cert_reqs
341
+ context.check_hostname = True
342
+ else:
343
+ context.check_hostname = False
344
+ context.verify_mode = cert_reqs
345
+
346
+ try:
347
+ context.hostname_checks_common_name = False
348
+ except AttributeError: # Defensive: for CPython < 3.8.9 and 3.9.3; for PyPy < 7.3.8
349
+ pass
350
+
351
+ # Enable logging of TLS session keys via defacto standard environment variable
352
+ # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values.
353
+ if hasattr(context, "keylog_filename"):
354
+ sslkeylogfile = os.environ.get("SSLKEYLOGFILE")
355
+ if sslkeylogfile:
356
+ context.keylog_filename = sslkeylogfile
357
+
358
+ return context
359
+
360
+
361
+ @typing.overload
362
+ def ssl_wrap_socket(
363
+ sock: socket.socket,
364
+ keyfile: str | None = ...,
365
+ certfile: str | None = ...,
366
+ cert_reqs: int | None = ...,
367
+ ca_certs: str | None = ...,
368
+ server_hostname: str | None = ...,
369
+ ssl_version: int | None = ...,
370
+ ciphers: str | None = ...,
371
+ ssl_context: ssl.SSLContext | None = ...,
372
+ ca_cert_dir: str | None = ...,
373
+ key_password: str | None = ...,
374
+ ca_cert_data: None | str | bytes = ...,
375
+ tls_in_tls: typing.Literal[False] = ...,
376
+ ) -> ssl.SSLSocket:
377
+ ...
378
+
379
+
380
+ @typing.overload
381
+ def ssl_wrap_socket(
382
+ sock: socket.socket,
383
+ keyfile: str | None = ...,
384
+ certfile: str | None = ...,
385
+ cert_reqs: int | None = ...,
386
+ ca_certs: str | None = ...,
387
+ server_hostname: str | None = ...,
388
+ ssl_version: int | None = ...,
389
+ ciphers: str | None = ...,
390
+ ssl_context: ssl.SSLContext | None = ...,
391
+ ca_cert_dir: str | None = ...,
392
+ key_password: str | None = ...,
393
+ ca_cert_data: None | str | bytes = ...,
394
+ tls_in_tls: bool = ...,
395
+ ) -> ssl.SSLSocket | SSLTransportType:
396
+ ...
397
+
398
+
399
+ def ssl_wrap_socket(
400
+ sock: socket.socket,
401
+ keyfile: str | None = None,
402
+ certfile: str | None = None,
403
+ cert_reqs: int | None = None,
404
+ ca_certs: str | None = None,
405
+ server_hostname: str | None = None,
406
+ ssl_version: int | None = None,
407
+ ciphers: str | None = None,
408
+ ssl_context: ssl.SSLContext | None = None,
409
+ ca_cert_dir: str | None = None,
410
+ key_password: str | None = None,
411
+ ca_cert_data: None | str | bytes = None,
412
+ tls_in_tls: bool = False,
413
+ ) -> ssl.SSLSocket | SSLTransportType:
414
+ """
415
+ All arguments except for server_hostname, ssl_context, tls_in_tls, ca_cert_data and
416
+ ca_cert_dir have the same meaning as they do when using
417
+ :func:`ssl.create_default_context`, :meth:`ssl.SSLContext.load_cert_chain`,
418
+ :meth:`ssl.SSLContext.set_ciphers` and :meth:`ssl.SSLContext.wrap_socket`.
419
+
420
+ :param server_hostname:
421
+ When SNI is supported, the expected hostname of the certificate
422
+ :param ssl_context:
423
+ A pre-made :class:`SSLContext` object. If none is provided, one will
424
+ be created using :func:`create_urllib3_context`.
425
+ :param ciphers:
426
+ A string of ciphers we wish the client to support.
427
+ :param ca_cert_dir:
428
+ A directory containing CA certificates in multiple separate files, as
429
+ supported by OpenSSL's -CApath flag or the capath argument to
430
+ SSLContext.load_verify_locations().
431
+ :param key_password:
432
+ Optional password if the keyfile is encrypted.
433
+ :param ca_cert_data:
434
+ Optional string containing CA certificates in PEM format suitable for
435
+ passing as the cadata parameter to SSLContext.load_verify_locations()
436
+ :param tls_in_tls:
437
+ Use SSLTransport to wrap the existing socket.
438
+ """
439
+ context = ssl_context
440
+ if context is None:
441
+ # Note: This branch of code and all the variables in it are only used in tests.
442
+ # We should consider deprecating and removing this code.
443
+ context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers)
444
+
445
+ if ca_certs or ca_cert_dir or ca_cert_data:
446
+ try:
447
+ context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data)
448
+ except OSError as e:
449
+ raise SSLError(e) from e
450
+
451
+ elif ssl_context is None and hasattr(context, "load_default_certs"):
452
+ # try to load OS default certs; works well on Windows.
453
+ context.load_default_certs()
454
+
455
+ # Attempt to detect if we get the goofy behavior of the
456
+ # keyfile being encrypted and OpenSSL asking for the
457
+ # passphrase via the terminal and instead error out.
458
+ if keyfile and key_password is None and _is_key_file_encrypted(keyfile):
459
+ raise SSLError("Client private key is encrypted, password is required")
460
+
461
+ if certfile:
462
+ if key_password is None:
463
+ context.load_cert_chain(certfile, keyfile)
464
+ else:
465
+ context.load_cert_chain(certfile, keyfile, key_password)
466
+
467
+ context.set_alpn_protocols(ALPN_PROTOCOLS)
468
+
469
+ ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)
470
+ return ssl_sock
471
+
472
+
473
+ def is_ipaddress(hostname: str | bytes) -> bool:
474
+ """Detects whether the hostname given is an IPv4 or IPv6 address.
475
+ Also detects IPv6 addresses with Zone IDs.
476
+
477
+ :param str hostname: Hostname to examine.
478
+ :return: True if the hostname is an IP address, False otherwise.
479
+ """
480
+ if isinstance(hostname, bytes):
481
+ # IDN A-label bytes are ASCII compatible.
482
+ hostname = hostname.decode("ascii")
483
+ return bool(_IPV4_RE.match(hostname) or _BRACELESS_IPV6_ADDRZ_RE.match(hostname))
484
+
485
+
486
+ def _is_key_file_encrypted(key_file: str) -> bool:
487
+ """Detects if a key file is encrypted or not."""
488
+ with open(key_file) as f:
489
+ for line in f:
490
+ # Look for Proc-Type: 4,ENCRYPTED
491
+ if "ENCRYPTED" in line:
492
+ return True
493
+
494
+ return False
495
+
496
+
497
+ def _ssl_wrap_socket_impl(
498
+ sock: socket.socket,
499
+ ssl_context: ssl.SSLContext,
500
+ tls_in_tls: bool,
501
+ server_hostname: str | None = None,
502
+ ) -> ssl.SSLSocket | SSLTransportType:
503
+ if tls_in_tls:
504
+ if not SSLTransport:
505
+ # Import error, ssl is not available.
506
+ raise ProxySchemeUnsupported(
507
+ "TLS in TLS requires support for the 'ssl' module"
508
+ )
509
+
510
+ SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context)
511
+ return SSLTransport(sock, ssl_context, server_hostname)
512
+
513
+ return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
@@ -0,0 +1,159 @@
1
+ """The match_hostname() function from Python 3.5, essential when using SSL."""
2
+
3
+ # Note: This file is under the PSF license as the code comes from the python
4
+ # stdlib. http://docs.python.org/3/license.html
5
+ # It is modified to remove commonName support.
6
+
7
+ from __future__ import annotations
8
+
9
+ import ipaddress
10
+ import re
11
+ import typing
12
+ from ipaddress import IPv4Address, IPv6Address
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from .ssl_ import _TYPE_PEER_CERT_RET_DICT
16
+
17
+ __version__ = "3.5.0.1"
18
+
19
+
20
+ class CertificateError(ValueError):
21
+ pass
22
+
23
+
24
+ def _dnsname_match(
25
+ dn: typing.Any, hostname: str, max_wildcards: int = 1
26
+ ) -> typing.Match[str] | None | bool:
27
+ """Matching according to RFC 6125, section 6.4.3
28
+
29
+ http://tools.ietf.org/html/rfc6125#section-6.4.3
30
+ """
31
+ pats = []
32
+ if not dn:
33
+ return False
34
+
35
+ # Ported from python3-syntax:
36
+ # leftmost, *remainder = dn.split(r'.')
37
+ parts = dn.split(r".")
38
+ leftmost = parts[0]
39
+ remainder = parts[1:]
40
+
41
+ wildcards = leftmost.count("*")
42
+ if wildcards > max_wildcards:
43
+ # Issue #17980: avoid denials of service by refusing more
44
+ # than one wildcard per fragment. A survey of established
45
+ # policy among SSL implementations showed it to be a
46
+ # reasonable choice.
47
+ raise CertificateError(
48
+ "too many wildcards in certificate DNS name: " + repr(dn)
49
+ )
50
+
51
+ # speed up common case w/o wildcards
52
+ if not wildcards:
53
+ return bool(dn.lower() == hostname.lower())
54
+
55
+ # RFC 6125, section 6.4.3, subitem 1.
56
+ # The client SHOULD NOT attempt to match a presented identifier in which
57
+ # the wildcard character comprises a label other than the left-most label.
58
+ if leftmost == "*":
59
+ # When '*' is a fragment by itself, it matches a non-empty dotless
60
+ # fragment.
61
+ pats.append("[^.]+")
62
+ elif leftmost.startswith("xn--") or hostname.startswith("xn--"):
63
+ # RFC 6125, section 6.4.3, subitem 3.
64
+ # The client SHOULD NOT attempt to match a presented identifier
65
+ # where the wildcard character is embedded within an A-label or
66
+ # U-label of an internationalized domain name.
67
+ pats.append(re.escape(leftmost))
68
+ else:
69
+ # Otherwise, '*' matches any dotless string, e.g. www*
70
+ pats.append(re.escape(leftmost).replace(r"\*", "[^.]*"))
71
+
72
+ # add the remaining fragments, ignore any wildcards
73
+ for frag in remainder:
74
+ pats.append(re.escape(frag))
75
+
76
+ pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE)
77
+ return pat.match(hostname)
78
+
79
+
80
+ def _ipaddress_match(ipname: str, host_ip: IPv4Address | IPv6Address) -> bool:
81
+ """Exact matching of IP addresses.
82
+
83
+ RFC 9110 section 4.3.5: "A reference identity of IP-ID contains the decoded
84
+ bytes of the IP address. An IP version 4 address is 4 octets, and an IP
85
+ version 6 address is 16 octets. [...] A reference identity of type IP-ID
86
+ matches if the address is identical to an iPAddress value of the
87
+ subjectAltName extension of the certificate."
88
+ """
89
+ # OpenSSL may add a trailing newline to a subjectAltName's IP address
90
+ # Divergence from upstream: ipaddress can't handle byte str
91
+ ip = ipaddress.ip_address(ipname.rstrip())
92
+ return bool(ip.packed == host_ip.packed)
93
+
94
+
95
+ def match_hostname(
96
+ cert: _TYPE_PEER_CERT_RET_DICT | None,
97
+ hostname: str,
98
+ hostname_checks_common_name: bool = False,
99
+ ) -> None:
100
+ """Verify that *cert* (in decoded format as returned by
101
+ SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
102
+ rules are followed, but IP addresses are not accepted for *hostname*.
103
+
104
+ CertificateError is raised on failure. On success, the function
105
+ returns nothing.
106
+ """
107
+ if not cert:
108
+ raise ValueError(
109
+ "empty or no certificate, match_hostname needs a "
110
+ "SSL socket or SSL context with either "
111
+ "CERT_OPTIONAL or CERT_REQUIRED"
112
+ )
113
+ try:
114
+ # Divergence from upstream: ipaddress can't handle byte str
115
+ #
116
+ # The ipaddress module shipped with Python < 3.9 does not support
117
+ # scoped IPv6 addresses so we unconditionally strip the Zone IDs for
118
+ # now. Once we drop support for Python 3.9 we can remove this branch.
119
+ if "%" in hostname:
120
+ host_ip = ipaddress.ip_address(hostname[: hostname.rfind("%")])
121
+ else:
122
+ host_ip = ipaddress.ip_address(hostname)
123
+
124
+ except ValueError:
125
+ # Not an IP address (common case)
126
+ host_ip = None
127
+ dnsnames = []
128
+ san: tuple[tuple[str, str], ...] = cert.get("subjectAltName", ())
129
+ key: str
130
+ value: str
131
+ for key, value in san:
132
+ if key == "DNS":
133
+ if host_ip is None and _dnsname_match(value, hostname):
134
+ return
135
+ dnsnames.append(value)
136
+ elif key == "IP Address":
137
+ if host_ip is not None and _ipaddress_match(value, host_ip):
138
+ return
139
+ dnsnames.append(value)
140
+
141
+ # We only check 'commonName' if it's enabled and we're not verifying
142
+ # an IP address. IP addresses aren't valid within 'commonName'.
143
+ if hostname_checks_common_name and host_ip is None and not dnsnames:
144
+ for sub in cert.get("subject", ()):
145
+ for key, value in sub:
146
+ if key == "commonName":
147
+ if _dnsname_match(value, hostname):
148
+ return
149
+ dnsnames.append(value)
150
+
151
+ if len(dnsnames) > 1:
152
+ raise CertificateError(
153
+ "hostname %r "
154
+ "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames)))
155
+ )
156
+ elif len(dnsnames) == 1:
157
+ raise CertificateError(f"hostname {hostname!r} doesn't match {dnsnames[0]!r}")
158
+ else:
159
+ raise CertificateError("no appropriate subjectAltName fields were found")