--- a/Lib/ssl.py 2018-04-30 04:17:33.000000000 +0530 +++ b/Lib/ssl.py 2018-08-17 05:48:06.389881269 +0530 @@ -146,6 +146,11 @@ from socket import SOL_SOCKET, SO_TYPE import base64 # for DER-to-PEM translation import errno import warnings +try: + from ipaddr import IPAddress +except ImportError: + # ipaddr is missing. Make ip address cert match functionality to behave as before. + def IPAddress(*_args): raise ValueError("Not supported") if _ssl.HAS_TLS_UNIQUE: CHANNEL_BINDING_TYPES = ['tls-unique'] @@ -251,7 +250,15 @@ def _dnsname_match(dn, hostname, max_wil pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) return pat.match(hostname) +def _ipaddress_match(ipname, 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 = IPAddress(ipname.rstrip()) + return ip == host_ip def match_hostname(cert, hostname): """Verify that *cert* (in decoded format as returned by SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 @@ -264,6 +271,13 @@ def match_hostname(cert, hostname): raise ValueError("empty or no certificate, match_hostname needs a " "SSL socket or SSL context with either " "CERT_OPTIONAL or CERT_REQUIRED") + + try: + host_ip = IPAddress(hostname) + except ValueError: + # Not an IP address (common case) + host_ip = None + dnsnames = [] san = cert.get('subjectAltName', ()) for key, value in san: @@ -271,6 +285,10 @@ def match_hostname(cert, hostname): if _dnsname_match(value, hostname): return dnsnames.append(value) + elif key == 'IP Address': + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) if not dnsnames: # The subject is only checked when there is no dNSName entry # in subjectAltName