summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2015-02-15 17:12:20 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2015-02-15 17:12:20 (GMT)
commitc481bfb3f62787e9ef0947785df7383a173a23c3 (patch)
treec7d6b4745a46c9d535ce3f413c4dd0f69f488f84 /Lib
parent2d07b855855c13814c2050f2f062aadf936fa886 (diff)
downloadcpython-c481bfb3f62787e9ef0947785df7383a173a23c3.zip
cpython-c481bfb3f62787e9ef0947785df7383a173a23c3.tar.gz
cpython-c481bfb3f62787e9ef0947785df7383a173a23c3.tar.bz2
Issue #23239: ssl.match_hostname() now supports matching of IP addresses.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/ssl.py23
-rw-r--r--Lib/test/test_ssl.py24
2 files changed, 46 insertions, 1 deletions
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 807e9f2..56bc38e 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -87,6 +87,7 @@ ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE
ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY
"""
+import ipaddress
import textwrap
import re
import sys
@@ -242,6 +243,17 @@ def _dnsname_match(dn, hostname, max_wildcards=1):
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.ip_address(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
@@ -254,11 +266,20 @@ 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.ip_address(hostname)
+ except ValueError:
+ # Not an IP address (common case)
+ host_ip = None
dnsnames = []
san = cert.get('subjectAltName', ())
for key, value in san:
if key == 'DNS':
- if _dnsname_match(value, hostname):
+ if host_ip is None and _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:
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 2f2e739..ce427b4 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -383,6 +383,8 @@ class BasicSocketTests(unittest.TestCase):
self.assertRaises(ssl.CertificateError,
ssl.match_hostname, cert, hostname)
+ # -- Hostname matching --
+
cert = {'subject': ((('commonName', 'example.com'),),)}
ok(cert, 'example.com')
ok(cert, 'ExAmple.cOm')
@@ -468,6 +470,28 @@ class BasicSocketTests(unittest.TestCase):
# Only commonName is considered
fail(cert, 'California')
+ # -- IPv4 matching --
+ cert = {'subject': ((('commonName', 'example.com'),),),
+ 'subjectAltName': (('DNS', 'example.com'),
+ ('IP Address', '10.11.12.13'),
+ ('IP Address', '14.15.16.17'))}
+ ok(cert, '10.11.12.13')
+ ok(cert, '14.15.16.17')
+ fail(cert, '14.15.16.18')
+ fail(cert, 'example.net')
+
+ # -- IPv6 matching --
+ cert = {'subject': ((('commonName', 'example.com'),),),
+ 'subjectAltName': (('DNS', 'example.com'),
+ ('IP Address', '2001:0:0:0:0:0:0:CAFE\n'),
+ ('IP Address', '2003:0:0:0:0:0:0:BABA\n'))}
+ ok(cert, '2001::cafe')
+ ok(cert, '2003::baba')
+ fail(cert, '2003::bebe')
+ fail(cert, 'example.net')
+
+ # -- Miscellaneous --
+
# Neither commonName nor subjectAltName
cert = {'notAfter': 'Dec 18 23:59:59 2011 GMT',
'subject': ((('countryName', 'US'),),