8000 Rely on the standard library for checking hostnames in supported PyPy releases by illia-v · Pull Request #3087 · urllib3/urllib3 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Rely on the standard library for checking hostnames in supported PyPy releases #3087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 15, 2023
Merged
1 change: 1 addition & 0 deletions changelog/3087.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Relied on the standard library for checking hostnames in supported PyPy releases.
41 changes: 22 additions & 19 deletions src/urllib3/util/ssl_.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,41 @@


def _is_bpo_43522_fixed(
implementation_name: str, version_info: _TYPE_VERSION_INFO
implementation_name: str,
version_info: _TYPE_VERSION_INFO,
pypy_version_info: _TYPE_VERSION_INFO | None,
) -> bool:
"""Return True for CPython 3.8.9+, 3.9.3+ or 3.10+ where setting
SSLContext.hostname_checks_common_name to False works.

PyPy 7.3.7 doesn't work as it doesn't ship with OpenSSL 1.1.1l+
so we're waiting for a version of PyPy that works before
allowing this function to return 'True'.
"""Return True for CPython 3.8.9+, 3.9.3+ or 3.10+ and PyPy 7.3.8+ where
setting SSLContext.hostname_checks_common_name to False works.

Outside of CPython and PyPy we don't know which implementations work
or not so we conservatively use our hostname matching as we know that works
on all implementations.

https://github.com/urllib3/urllib3/issues/2192#issuecomment-821832963
https://foss.heptapod.net/pypy/pypy/-/issues/3539#
https://foss.heptapod.net/pypy/pypy/-/issues/3539
"""
if implementation_name != "cpython":
if implementation_name == "pypy":
# https://foss.heptapod.net/pypy/pypy/-/issues/3129
return pypy_version_info >= (7, 3, 8) and version_info >= (3, 8) # type: ignore[operator]
elif implementation_name == "cpython":
major_minor = version_info[:2]
micro = version_info[2]
return (
(major_minor == (3, 8) and micro >= 9)
or (major_minor == (3, 9) and micro >= 3)
or major_minor >= (3, 10)
)
else: # Defensive:
return False

major_minor = version_info[:2]
micro = version_info[2]
return (
(major_minor == (3, 8) and micro >= 9)
or (major_minor == (3, 9) and micro >= 3)
or major_minor >= (3, 10)
)


def _is_has_never_check_common_name_reliable(
openssl_version: str,
openssl_version_number: int,
implementation_name: str,
version_info: _TYPE_VERSION_INFO,
pypy_version_info: _TYPE_VERSION_INFO | None,
) -> bool:
# As of May 2023, all released versions of LibreSSL fail to reject certificates with
# only common names, see https://github.com/urllib3/urllib3/pull/3024
Expand All @@ -71,7 +73,7 @@ def _is_has_never_check_common_name_reliable(

return is_openssl and (
is_openssl_issue_14579_fixed
or _is_bpo_43522_fixed(implementation_name, version_info)
or _is_bpo_43522_fixed(implementation_name, version_info, pypy_version_info)
)


Expand Down Expand Up @@ -117,6 +119,7 @@ class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False):
OPENSSL_VERSION_NUMBER,
sys.implementation.name,
sys.version_info,
sys.pypy_version_info if sys.implementation.name == "pypy" else None, # type: ignore[attr-defined]
):
HAS_NEVER_CHECK_COMMON_NAME = False

Expand Down Expand Up @@ -341,7 +344,7 @@ def create_urllib3_context(

try:
context.hostname_checks_common_name = False
except AttributeError:
except AttributeError: # Defensive: for CPython < 3.8.9 and 3.9.3; for PyPy < 7.3.8
pass

# Enable logging of TLS session keys via defacto standard environment variable
Expand Down
19 changes: 12 additions & 7 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1068,19 +1068,22 @@ def test_ssl_wrap_socket_sni_none_no_warn(self) -> None:
warn.assert_not_called()

@pytest.mark.parametrize(
"openssl_version, openssl_version_number, implementation_name, version_info, reliable",
"openssl_version, openssl_version_number, implementation_name, version_info, pypy_version_info, reliable",
[
# OpenSSL and Python OK -> reliable
("OpenSSL 1.1.1", 0x101010CF, "cpython", (3, 9, 3), True),
("OpenSSL 1.1.1", 0x101010CF, "cpython", (3, 9, 3), None, True),
# Python OK -> reliable
("OpenSSL 1.1.1", 0x10101000, "cpython", (3, 9, 3), True),
("OpenSSL 1.1.1", 0x10101000, "pypy", (3, 6, 9), False),
("OpenSSL 1.1.1", 0x10101000, "cpython", (3, 9, 3), None, True),
# PyPy: depends on the version
("OpenSSL 1.1.1", 0x10101000, "pypy", (3, 6, 9), (7, 3, 7), False),
("OpenSSL 1.1.1", 0x10101000, "pypy", (3, 7, 13), (7, 3, 9), False),
("OpenSSL 1.1.1", 0x101010CF, "pypy", (3, 8, 12), (7, 3, 8), True),
# OpenSSL OK -> reliable
("OpenSSL 1.1.1", 0x101010CF, "cpython", (3, 9, 2), True),
("OpenSSL 1.1.1", 0x101010CF, "cpython", (3, 9, 2), None, True),
# not OpenSSSL -> unreliable
("LibreSSL 2.8.3", 0x101010CF, "cpython", (3, 10, 0), False),
("LibreSSL 2.8.3", 0x101010CF, "cpython", (3, 10, 0), None, False),
# old OpenSSL and old Python, unreliable
("OpenSSL 1.1.0", 0x10101000, "cpython", (3, 9, 2), False),
("OpenSSL 1.1.0", 0x10101000, "cpython", (3, 9, 2), None, False),
],
)
def test_is_has_never_check_common_name_reliable(
Expand All @@ -1089,6 +1092,7 @@ def test_is_has_never_check_common_name_reliable(
openssl_version_number: int,
implementation_name: str,
version_info: _TYPE_VERSION_INFO,
pypy_version_info: _TYPE_VERSION_INFO | None,
reliable: bool,
) -> None:
assert (
Expand All @@ -1097,6 +1101,7 @@ def test_is_has_never_check_common_name_reliable(
openssl_version_number,
implementation_name,
version_info,
pypy_version_info,
)
== reliable
)
Expand Down
0