Strip brackets from IPv6 hosts before certificate hostname matching
MODIFY
src/urllib3/util/ssl_.py
src/urllib3/util/ssl_.py
@@ -5,7 +5,7 @@ import sys
import warnings
from binascii import unhexlify
from hashlib import md5, sha1, sha256
-from typing import TYPE_CHECKING, Dict, Optional, Tuple, Union, cast, overload
+from typing import TYPE_CHECKING, Dict, Mapping, Optional, Tuple, Union, cast, overload
from ..exceptions import ProxySchemeUnsupported, SNIMissingWarning, SSLError
from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE
@@ -89,7 +89,7 @@ except ImportError:
_PCTRTT = Tuple[Tuple[str, str], ...]
_PCTRTTT = Tuple[_PCTRTT, ...]
-_TYPE_PEER_CERT_RET_DICT = Dict[str, Union[str, _PCTRTTT, _PCTRTT]]
+_TYPE_PEER_CERT_RET_DICT = Mapping[str, Union[str, _PCTRTTT, _PCTRTT]]
_TYPE_PEER_CERT_RET = Union[_TYPE_PEER_CERT_RET_DICT, bytes, None]
# A secure default.

MODIFY
src/urllib3/util/ssl_match_hostname.py
src/urllib3/util/ssl_match_hostname.py
@@ -101,7 +101,7 @@ def match_hostname(cert: _TYPE_PEER_CERT_RET, hostname: str) -> None:
)
try:
# Divergence from upstream: ipaddress can't handle byte str
- host_ip = ipaddress.ip_address(hostname)
+ host_ip = ipaddress.ip_address(hostname.strip("[]"))
except ValueError:
# Not an IP address (common case)
host_ip = None

MODIFY
test/test_connection.py
test/test_connection.py
@@ -122,6 +122,27 @@ class TestConnection:
)
assert e._peer_cert == cert
+ def test_match_hostname_ip_address_ipv6(self) -> None:
+ cert = {"subjectAltName": (("IP Address", "1:2::2:1"),)}
+ asserted_hostname = "1:2::2:2"
+ try:
+ with mock.patch("urllib3.connection.log.warning") as mock_log:
+ _match_hostname(cert, asserted_hostname)
+ except CertificateError as e:
+ assert "hostname '1:2::2:2' doesn't match '1:2::2:1'" in str(e)
+ mock_log.assert_called_once_with(
+ "Certificate did not match expected hostname: %s. Certificate: %s",
+ "1:2::2:2",
+ {"subjectAltName": (("IP Address", "1:2::2:1"),)},
+ )
+ assert e._peer_cert == cert
+
+ def test_match_hostname_ip_address_ipv6_brackets(self) -> None:
+ cert = {"subjectAltName": (("IP Address", "1:2::2:1"),)}
+ asserted_hostname = "[1:2::2:1]"
+ # Assert no error is raised
+ _match_hostname(cert, asserted_hostname)
+
def test_recent_date(self) -> None:
# This test is to make sure that the RECENT_DATE value
# doesn't get too far behind what the current date is.

Strip brackets from IPv6 hosts before certificate hostname matching

Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>