diff --git a/Lib/ssl.py b/Lib/ssl.py index 61bd775f759..0726caee49a 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -327,12 +327,22 @@ def _inet_paton(ipname): Supports IPv4 addresses on all platforms and IPv6 on platforms with IPv6 support. """ - # inet_aton() also accepts strings like '1' - if ipname.count('.') == 3: - try: - return _socket.inet_aton(ipname) - except OSError: - pass + # inet_aton() also accepts strings like '1', '127.1', some also trailing + # data like '127.0.0.1 whatever'. + try: + addr = _socket.inet_aton(ipname) + except OSError: + # not an IPv4 address + pass + else: + if _socket.inet_ntoa(addr) == ipname: + # only accept injective ipnames + return addr + else: + # refuse for short IPv4 notation and additional trailing data + raise ValueError( + "{!r} is not a quad-dotted IPv4 address.".format(ipname) + ) try: return _socket.inet_pton(_socket.AF_INET6, ipname) @@ -346,14 +356,15 @@ def _inet_paton(ipname): raise ValueError("{!r} is not an IPv4 address.".format(ipname)) -def _ipaddress_match(ipname, host_ip): +def _ipaddress_match(cert_ipaddress, host_ip): """Exact matching of IP addresses. RFC 6125 explicitly doesn't define an algorithm for this (section 1.7.2 - "Out of Scope"). """ - # OpenSSL may add a trailing newline to a subjectAltName's IP address - ip = _inet_paton(ipname.rstrip()) + # OpenSSL may add a trailing newline to a subjectAltName's IP address, + # commonly woth IPv6 addresses. Strip off trailing \n. + ip = _inet_paton(cert_ipaddress.rstrip()) return ip == host_ip diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 1898990fb79..f1b7aa731e9 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -666,9 +666,14 @@ class BasicSocketTests(unittest.TestCase): cert = {'subject': ((('commonName', 'example.com'),),), 'subjectAltName': (('DNS', 'example.com'), ('IP Address', '10.11.12.13'), - ('IP Address', '14.15.16.17'))} + ('IP Address', '14.15.16.17'), + ('IP Address', '127.0.0.1'))} ok(cert, '10.11.12.13') ok(cert, '14.15.16.17') + # socket.inet_ntoa(socket.inet_aton('127.1')) == '127.0.0.1' + fail(cert, '127.1') + fail(cert, '14.15.16.17 ') + fail(cert, '14.15.16.17 extra data') fail(cert, '14.15.16.18') fail(cert, 'example.net') @@ -681,6 +686,8 @@ class BasicSocketTests(unittest.TestCase): ('IP Address', '2003:0:0:0:0:0:0:BABA\n'))} ok(cert, '2001::cafe') ok(cert, '2003::baba') + fail(cert, '2003::baba ') + fail(cert, '2003::baba extra data') fail(cert, '2003::bebe') fail(cert, 'example.net') diff --git a/Misc/NEWS.d/next/Security/2019-07-01-08-46-14.bpo-37463.1CHwjE.rst b/Misc/NEWS.d/next/Security/2019-07-01-08-46-14.bpo-37463.1CHwjE.rst new file mode 100644 index 00000000000..4f4a62e7837 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2019-07-01-08-46-14.bpo-37463.1CHwjE.rst @@ -0,0 +1,4 @@ +ssl.match_hostname() no longer accepts IPv4 addresses with additional text +after the address and only quad-dotted notation without trailing +whitespaces. Some inet_aton() implementations ignore whitespace and all data +after whitespace, e.g. '127.0.0.1 whatever'.