Matej Stuchlik 0d234b
--- a/Doc/library/ssl.rst
Matej Stuchlik 0d234b
+++ b/Doc/library/ssl.rst
Matej Stuchlik 0d234b
@@ -283,10 +283,10 @@ Certificate handling
Matej Stuchlik 0d234b
    Verify that *cert* (in decoded format as returned by
Matej Stuchlik 0d234b
    :meth:`SSLSocket.getpeercert`) matches the given *hostname*.  The rules
Matej Stuchlik 0d234b
    applied are those for checking the identity of HTTPS servers as outlined
Matej Stuchlik 0d234b
-   in :rfc:`2818`, except that IP addresses are not currently supported.
Matej Stuchlik 0d234b
-   In addition to HTTPS, this function should be suitable for checking the
Matej Stuchlik 0d234b
-   identity of servers in various SSL-based protocols such as FTPS, IMAPS,
Matej Stuchlik 0d234b
-   POPS and others.
Matej Stuchlik 0d234b
+   in :rfc:`2818` and :rfc:`6125`, except that IP addresses are not currently
Matej Stuchlik 0d234b
+   supported. In addition to HTTPS, this function should be suitable for
Matej Stuchlik 0d234b
+   checking the identity of servers in various SSL-based protocols such as
Matej Stuchlik 0d234b
+   FTPS, IMAPS, POPS and others.
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
    :exc:`CertificateError` is raised on failure. On success, the function
Matej Stuchlik 0d234b
    returns nothing::
Matej Stuchlik 0d234b
@@ -301,6 +301,13 @@ Certificate handling
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
    .. versionadded:: 3.2
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
+   .. versionchanged:: 3.3.3
Matej Stuchlik 0d234b
+      The function now follows :rfc:`6125`, section 6.4.3 and does neither
Matej Stuchlik 0d234b
+      match multiple wildcards (e.g. ``*.*.com`` or ``*a*.example.org``) nor
Matej Stuchlik 0d234b
+      a wildcard inside an internationalized domain names (IDN) fragment.
Matej Stuchlik 0d234b
+      IDN A-labels such as ``www*.xn--pthon-kva.org`` are still supported,
Matej Stuchlik 0d234b
+      but ``x*.python.org`` no longer matches ``xn--tda.python.org``.
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
 .. function:: cert_time_to_seconds(timestring)
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
    Returns a floating-point value containing a normal seconds-after-the-epoch
Matej Stuchlik 0d234b
unchanged:
Matej Stuchlik 0d234b
--- a/Lib/ssl.py
Matej Stuchlik 0d234b
+++ b/Lib/ssl.py
Matej Stuchlik 0d234b
@@ -129,25 +129,53 @@ class CertificateError(ValueError):
Matej Stuchlik 0d234b
     pass
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
-def _dnsname_to_pat(dn, max_wildcards=1):
Matej Stuchlik 0d234b
+def _dnsname_match(dn, hostname, max_wildcards=1):
Matej Stuchlik 0d234b
+    """Matching according to RFC 6125, section 6.4.3
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+    http://tools.ietf.org/html/rfc6125#section-6.4.3
Matej Stuchlik 0d234b
+    """
Matej Stuchlik 0d234b
     pats = []
Matej Stuchlik 0d234b
-    for frag in dn.split(r'.'):
Matej Stuchlik 0d234b
-        if frag.count('*') > max_wildcards:
Matej Stuchlik 0d234b
-            # Issue #17980: avoid denials of service by refusing more
Matej Stuchlik 0d234b
-            # than one wildcard per fragment.  A survery of established
Matej Stuchlik 0d234b
-            # policy among SSL implementations showed it to be a
Matej Stuchlik 0d234b
-            # reasonable choice.
Matej Stuchlik 0d234b
-            raise CertificateError(
Matej Stuchlik 0d234b
-                "too many wildcards in certificate DNS name: " + repr(dn))
Matej Stuchlik 0d234b
-        if frag == '*':
Matej Stuchlik 0d234b
-            # When '*' is a fragment by itself, it matches a non-empty dotless
Matej Stuchlik 0d234b
-            # fragment.
Matej Stuchlik 0d234b
-            pats.append('[^.]+')
Matej Stuchlik 0d234b
-        else:
Matej Stuchlik 0d234b
-            # Otherwise, '*' matches any dotless fragment.
Matej Stuchlik 0d234b
-            frag = re.escape(frag)
Matej Stuchlik 0d234b
-            pats.append(frag.replace(r'\*', '[^.]*'))
Matej Stuchlik 0d234b
-    return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
Matej Stuchlik 0d234b
+    if not dn:
Matej Stuchlik 0d234b
+        return False
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+    leftmost, *remainder = dn.split(r'.')
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+    wildcards = leftmost.count('*')
Matej Stuchlik 0d234b
+    if wildcards > max_wildcards:
Matej Stuchlik 0d234b
+        # Issue #17980: avoid denials of service by refusing more
Matej Stuchlik 0d234b
+        # than one wildcard per fragment.  A survery of established
Matej Stuchlik 0d234b
+        # policy among SSL implementations showed it to be a
Matej Stuchlik 0d234b
+        # reasonable choice.
Matej Stuchlik 0d234b
+        raise CertificateError(
Matej Stuchlik 0d234b
+            "too many wildcards in certificate DNS name: " + repr(dn))
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+    # speed up common case w/o wildcards
Matej Stuchlik 0d234b
+    if not wildcards:
Matej Stuchlik 0d234b
+        return dn.lower() == hostname.lower()
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+    # RFC 6125, section 6.4.3, subitem 1.
Matej Stuchlik 0d234b
+    # The client SHOULD NOT attempt to match a presented identifier in which
Matej Stuchlik 0d234b
+    # the wildcard character comprises a label other than the left-most label.
Matej Stuchlik 0d234b
+    if leftmost == '*':
Matej Stuchlik 0d234b
+        # When '*' is a fragment by itself, it matches a non-empty dotless
Matej Stuchlik 0d234b
+        # fragment.
Matej Stuchlik 0d234b
+        pats.append('[^.]+')
Matej Stuchlik 0d234b
+    elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
Matej Stuchlik 0d234b
+        # RFC 6125, section 6.4.3, subitem 3.
Matej Stuchlik 0d234b
+        # The client SHOULD NOT attempt to match a presented identifier
Matej Stuchlik 0d234b
+        # where the wildcard character is embedded within an A-label or
Matej Stuchlik 0d234b
+        # U-label of an internationalized domain name.
Matej Stuchlik 0d234b
+        pats.append(re.escape(leftmost))
Matej Stuchlik 0d234b
+    else:
Matej Stuchlik 0d234b
+        # Otherwise, '*' matches any dotless string, e.g. www*
Matej Stuchlik 0d234b
+        pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+    # add the remaining fragments, ignore any wildcards
Matej Stuchlik 0d234b
+    for frag in remainder:
Matej Stuchlik 0d234b
+        pats.append(re.escape(frag))
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+    pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
Matej Stuchlik 0d234b
+    return pat.match(hostname)
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
 def match_hostname(cert, hostname):
Matej Stuchlik 0d234b
unchanged:
Matej Stuchlik 0d234b
--- a/Lib/test/test_ssl.py
Matej Stuchlik 0d234b
+++ b/Lib/test/test_ssl.py
Matej Stuchlik 0d234b
@@ -304,11 +304,7 @@ class BasicSocketTests(unittest.TestCase
Matej Stuchlik 0d234b
         fail(cert, 'Xa.com')
Matej Stuchlik 0d234b
         fail(cert, '.a.com')
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
-        cert = {'subject': ((('commonName', 'a.*.com'),),)}
Matej Stuchlik 0d234b
-        ok(cert, 'a.foo.com')
Matej Stuchlik 0d234b
-        fail(cert, 'a..com')
Matej Stuchlik 0d234b
-        fail(cert, 'a.com')
Matej Stuchlik 0d234b
-
Matej Stuchlik 0d234b
+        # only match one left-most wildcard
Matej Stuchlik 0d234b
         cert = {'subject': ((('commonName', 'f*.com'),),)}
Matej Stuchlik 0d234b
         ok(cert, 'foo.com')
Matej Stuchlik 0d234b
         ok(cert, 'f.com')
Matej Stuchlik 0d234b
@@ -323,6 +319,36 @@ class BasicSocketTests(unittest.TestCase
Matej Stuchlik 0d234b
         fail(cert, 'example.org')
Matej Stuchlik 0d234b
         fail(cert, 'null.python.org')
Matej Stuchlik 0d234b
 
Matej Stuchlik 0d234b
+        # error cases with wildcards
Matej Stuchlik 0d234b
+        cert = {'subject': ((('commonName', '*.*.a.com'),),)}
Matej Stuchlik 0d234b
+        fail(cert, 'bar.foo.a.com')
Matej Stuchlik 0d234b
+        fail(cert, 'a.com')
Matej Stuchlik 0d234b
+        fail(cert, 'Xa.com')
Matej Stuchlik 0d234b
+        fail(cert, '.a.com')
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+        cert = {'subject': ((('commonName', 'a.*.com'),),)}
Matej Stuchlik 0d234b
+        fail(cert, 'a.foo.com')
Matej Stuchlik 0d234b
+        fail(cert, 'a..com')
Matej Stuchlik 0d234b
+        fail(cert, 'a.com')
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+        # wildcard doesn't match IDNA prefix 'xn--'
Matej Stuchlik 0d234b
+        idna = 'püthon.python.org'.encode("idna").decode("ascii")
Matej Stuchlik 0d234b
+        cert = {'subject': ((('commonName', idna),),)}
Matej Stuchlik 0d234b
+        ok(cert, idna)
Matej Stuchlik 0d234b
+        cert = {'subject': ((('commonName', 'x*.python.org'),),)}
Matej Stuchlik 0d234b
+        fail(cert, idna)
Matej Stuchlik 0d234b
+        cert = {'subject': ((('commonName', 'xn--p*.python.org'),),)}
Matej Stuchlik 0d234b
+        fail(cert, idna)
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
+        # wildcard in first fragment and  IDNA A-labels in sequent fragments
Matej Stuchlik 0d234b
+        # are supported.
Matej Stuchlik 0d234b
+        idna = 'www*.pythön.org'.encode("idna").decode("ascii")
Matej Stuchlik 0d234b
+        cert = {'subject': ((('commonName', idna),),)}
Matej Stuchlik 0d234b
+        ok(cert, 'www.pythön.org'.encode("idna").decode("ascii"))
Matej Stuchlik 0d234b
+        ok(cert, 'www1.pythön.org'.encode("idna").decode("ascii"))
Matej Stuchlik 0d234b
+        fail(cert, 'ftp.pythön.org'.encode("idna").decode("ascii"))
Matej Stuchlik 0d234b
+        fail(cert, 'pythön.org'.encode("idna").decode("ascii"))
Matej Stuchlik 0d234b
+
Matej Stuchlik 0d234b
         # Slightly fake real-world example
Matej Stuchlik 0d234b
         cert = {'notAfter': 'Jun 26 21:41:46 2011 GMT',
Matej Stuchlik 0d234b
                 'subject': ((('commonName', 'linuxfrz.org'),),),
Matej Stuchlik 0d234b
@@ -383,7 +409,7 @@ class BasicSocketTests(unittest.TestCase
Matej Stuchlik 0d234b
         cert = {'subject': ((('commonName', 'a*b.com'),),)}
Matej Stuchlik 0d234b
         ok(cert, 'axxb.com')
Matej Stuchlik 0d234b
         cert = {'subject': ((('commonName', 'a*b.co*'),),)}
Matej Stuchlik 0d234b
-        ok(cert, 'axxb.com')
Matej Stuchlik 0d234b
+        fail(cert, 'axxb.com')
Matej Stuchlik 0d234b
         cert = {'subject': ((('commonName', 'a*b*.com'),),)}
Matej Stuchlik 0d234b
         with self.assertRaises(ssl.CertificateError) as cm:
Matej Stuchlik 0d234b
             ssl.match_hostname(cert, 'axxbxxc.com')
Matej Stuchlik 0d234b
--- a/Lib/ssl.py
Matej Stuchlik 0d234b
+++ b/Lib/ssl.py
Matej Stuchlik 0d234b
@@ -192,7 +192,7 @@ def match_hostname(cert, hostname):
Matej Stuchlik 0d234b
     san = cert.get('subjectAltName', ())
Matej Stuchlik 0d234b
     for key, value in san:
Matej Stuchlik 0d234b
         if key == 'DNS':
Matej Stuchlik 0d234b
-            if _dnsname_to_pat(value).match(hostname):
Matej Stuchlik 0d234b
+            if _dnsname_match(value, hostname):
Matej Stuchlik 0d234b
                 return
Matej Stuchlik 0d234b
             dnsnames.append(value)
Matej Stuchlik 0d234b
     if not dnsnames:
Matej Stuchlik 0d234b
@@ -203,7 +203,7 @@ def match_hostname(cert, hostname):
Matej Stuchlik 0d234b
                 # XXX according to RFC 2818, the most specific Common Name
Matej Stuchlik 0d234b
                 # must be used.
Matej Stuchlik 0d234b
                 if key == 'commonName':
Matej Stuchlik 0d234b
-                    if _dnsname_to_pat(value).match(hostname):
Matej Stuchlik 0d234b
+                    if _dnsname_match(value, hostname):
Matej Stuchlik 0d234b
                         return
Matej Stuchlik 0d234b
                     dnsnames.append(value)
Matej Stuchlik 0d234b
     if len(dnsnames) > 1: