[Python-checkins] cpython: Issue #23239: ssl.match_hostname() now supports matching of IP addresses.

antoine.pitrou python-checkins at python.org
Sun Feb 15 18:12:37 CET 2015


https://hg.python.org/cpython/rev/b15a5f239e8a
changeset: 94637:b15a5f239e8a
user: Antoine Pitrou <solipsis at pitrou.net>
date: Sun Feb 15 18:12:20 2015 +0100
summary:
 Issue #23239: ssl.match_hostname() now supports matching of IP addresses.
files:
 Doc/library/ssl.rst | 11 +++++++----
 Lib/ssl.py | 23 ++++++++++++++++++++++-
 Lib/test/test_ssl.py | 24 ++++++++++++++++++++++++
 Misc/NEWS | 2 ++
 4 files changed, 55 insertions(+), 5 deletions(-)
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -344,10 +344,9 @@
 Verify that *cert* (in decoded format as returned by
 :meth:`SSLSocket.getpeercert`) matches the given *hostname*. The rules
 applied are those for checking the identity of HTTPS servers as outlined
- in :rfc:`2818` and :rfc:`6125`, except that IP addresses are not currently
- supported. In addition to HTTPS, this function should be suitable for
- checking the identity of servers in various SSL-based protocols such as
- FTPS, IMAPS, POPS and others.
+ in :rfc:`2818` and :rfc:`6125`. In addition to HTTPS, this function
+ should be suitable for checking the identity of servers in various
+ SSL-based protocols such as FTPS, IMAPS, POPS and others.
 
 :exc:`CertificateError` is raised on failure. On success, the function
 returns nothing::
@@ -369,6 +368,10 @@
 IDN A-labels such as ``www*.xn--pthon-kva.org`` are still supported,
 but ``x*.python.org`` no longer matches ``xn--tda.python.org``.
 
+ .. versionchanged:: 3.5
+ Matching of IP addresses, when present in the subjectAltName field
+ of the certificate, is now supported.
+
 .. function:: cert_time_to_seconds(cert_time)
 
 Return the time in seconds since the Epoch, given the ``cert_time``
diff --git a/Lib/ssl.py b/Lib/ssl.py
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -87,6 +87,7 @@
 ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY
 """
 
+import ipaddress
 import textwrap
 import re
 import sys
@@ -242,6 +243,17 @@
 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 @@
 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
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -383,6 +383,8 @@
 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 @@
 # 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'),),
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,8 @@
 Library
 -------
 
+- Issue #23239: ssl.match_hostname() now supports matching of IP addresses.
+
 - Issue #23146: Fix mishandling of absolute Windows paths with forward
 slashes in pathlib.
 
-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /