--- a/Lib/ssl.py 2016-12-17 12:05:06.000000000 -0800 +++ b/Lib/ssl.py 2017-03-22 17:24:55.165374501 -0700 @@ -146,6 +146,11 @@ 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'] @@ -245,7 +250,15 @@ 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 @@ -258,6 +271,13 @@ 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: @@ -265,6 +285,10 @@ 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