Hello community,

here is the log from the commit of package python-backports.ssl_match_hostname 
for openSUSE:Factory checked in at 2019-03-08 11:59:12
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-backports.ssl_match_hostname (Old)
 and      
/work/SRC/openSUSE:Factory/.python-backports.ssl_match_hostname.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-backports.ssl_match_hostname"

Fri Mar  8 11:59:12 2019 rev:8 rq:681234 version:3.7.0.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-backports.ssl_match_hostname/python-backports.ssl_match_hostname.changes
  2018-09-04 22:57:39.973307341 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-backports.ssl_match_hostname.new.28833/python-backports.ssl_match_hostname.changes
       2019-03-08 11:59:15.099976739 +0100
@@ -1,0 +2,14 @@
+Sun Mar  3 11:59:42 UTC 2019 - John Vandenberg <[email protected]>
+
+- Activate new test added in v3.7.0.1
+- Enable builds on all Python 3, as Python 3.6 now benefits from
+  this backport, and it is valid to use it on Python 3.7 also.
+- Update to v3.7.0.1
+  * Support universal wheels
+  * Move the README and LICENSE from the package directory to the toplevel
+  * Update the code to what is inside of Python-3.7
+  * Add helper for determining when upstream changes 
+  * Fix some problems found by unittesting
+  * Add backport of python-3.7's unittests
+
+-------------------------------------------------------------------

Old:
----
  backports.ssl_match_hostname-3.5.0.1.tar.gz

New:
----
  backports.ssl_match_hostname-3.7.0.1.tar.gz
  test_ssl.py

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-backports.ssl_match_hostname.spec ++++++
--- /var/tmp/diff_new_pack.B7lw9u/_old  2019-03-08 11:59:16.267976541 +0100
+++ /var/tmp/diff_new_pack.B7lw9u/_new  2019-03-08 11:59:16.267976541 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-backports.ssl_match_hostname
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,18 +12,15 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
-%if %{python3_version_nodots} >= 35
-%define skip_python3 1
-%endif
 Name:           python-backports.ssl_match_hostname
-Version:        3.5.0.1
+Version:        3.7.0.1
 Release:        0
-Summary:        The ssl.match_hostname() function from Python 3.5
+Summary:        The ssl.match_hostname() function from Python 3.7
 License:        Python-2.0
 Group:          Development/Languages/Python
 URL:            http://bitbucket.org/brandon/backports.ssl_match_hostname
@@ -35,8 +32,10 @@
 #    https://pypi.python.org/pypi/backports/
 #    https://www.python.org/dev/peps/pep-0420/%23namespace-packages-today
 # If you need to link, the python-backports package is built as a subpackage 
of python-configparser
+Source1:        
https://bitbucket.org/brandon/backports.ssl_match_hostname/raw/312648aec6f01702e8704a99b54445ffc2458033/tests/test_ssl.py
 BuildRequires:  %{python_module backports}
 BuildRequires:  %{python_module setuptools}
+BuildRequires:  %{python_module unittest2}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-backports
@@ -73,6 +72,7 @@
 
 %prep
 %setup -q -n backports.ssl_match_hostname-%{version}
+cp %{SOURCE1} .
 
 %build
 %python_build
@@ -83,9 +83,12 @@
 %python_expand rm -rf %{buildroot}%{$python_sitelib}/backports/__pycache__/
 %python_expand %fdupes 
%{buildroot}%{$python_sitelib}/backports/ssl_match_hostname/
 
+%check
+%python_exec -m unittest2 test_ssl.py
+
 %files %{python_files}
-%doc backports/ssl_match_hostname/README.txt
-%license backports/ssl_match_hostname/LICENSE.txt
+%doc README.txt
+%license LICENSE.txt
 %{python_sitelib}/backports/ssl_match_hostname/
 %{python_sitelib}/backports.ssl_match_hostname-%{version}-py*.egg-info
 

++++++ backports.ssl_match_hostname-3.5.0.1.tar.gz -> 
backports.ssl_match_hostname-3.7.0.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/backports.ssl_match_hostname-3.5.0.1/LICENSE.txt 
new/backports.ssl_match_hostname-3.7.0.1/LICENSE.txt
--- old/backports.ssl_match_hostname-3.5.0.1/LICENSE.txt        1970-01-01 
01:00:00.000000000 +0100
+++ new/backports.ssl_match_hostname-3.7.0.1/LICENSE.txt        2018-03-22 
20:02:53.000000000 +0100
@@ -0,0 +1,51 @@
+Python License (Python-2.0)
+
+Python License, Version 2 (Python-2.0)
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python
+alone or in any derivative version, provided, however, that PSF's
+License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
+2001-2013 Python Software Foundation; All Rights Reserved" are retained in
+Python alone or in any derivative version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/backports.ssl_match_hostname-3.5.0.1/PKG-INFO 
new/backports.ssl_match_hostname-3.7.0.1/PKG-INFO
--- old/backports.ssl_match_hostname-3.5.0.1/PKG-INFO   2015-12-19 
23:33:16.000000000 +0100
+++ new/backports.ssl_match_hostname-3.7.0.1/PKG-INFO   2019-01-12 
23:25:54.000000000 +0100
@@ -1,13 +1,13 @@
 Metadata-Version: 1.1
 Name: backports.ssl_match_hostname
-Version: 3.5.0.1
+Version: 3.7.0.1
 Summary: The ssl.match_hostname() function from Python 3.5
 Home-page: http://bitbucket.org/brandon/backports.ssl_match_hostname
 Author: Toshio Kuratomi
 Author-email: [email protected]
 License: Python Software Foundation License
 Description: 
-        The ssl.match_hostname() function from Python 3.5
+        The ssl.match_hostname() function from Python 3.7
         =================================================
         
         The Secure Sockets Layer is only actually *secure*
@@ -38,25 +38,17 @@
                 ...
         
         Brandon Craig Rhodes is merely the packager of this distribution;
-        the actual code inside comes from Python 3.5 with small changes for
+        the actual code inside comes from Python 3.7 with small changes for
         portability.
         
         
         Requirements
         ------------
         
-        * If you want to verify hosts match with certificates via ServerAltname
-          IPAddress fields, you need to install the `ipaddress module`_.
-          backports.ssl_match_hostname will continue to work without ipaddress 
but
-          will only be able to handle ServerAltName DNSName fields, not 
IPAddress.
-          System packagers (Linux distributions, et al) are encouraged to add
-          this as a hard dependency in their packages.
-        
         * If you need to use this on Python versions earlier than 2.6 you will 
need to
           install the `ssl module`_.  From Python 2.6 upwards ``ssl`` is 
included in
           the Python Standard Library so you do not need to install it 
separately.
         
-        .. _`ipaddress module`:: https://pypi.python.org/pypi/ipaddress
         .. _`ssl module`:: https://pypi.python.org/pypi/ssl
         
         History
@@ -71,11 +63,13 @@
         * It was updated in python-3.5 to handle IPAddresses in ServerAltName 
fields
           (something that backports.ssl_match_hostname will do if you also 
install the
           ipaddress library from pypi).
+        * It was updated in python-3.7 to handle IPAddresses without the 
ipaddress library and dropped
+          support for partial wildcards
         
+        .. _`ipaddress module`:: https://pypi.python.org/pypi/ipaddress
         
         .. _RFC2818: http://tools.ietf.org/html/rfc2818.html
         
-        
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: License :: OSI Approved :: Python Software Foundation License
@@ -86,4 +80,10 @@
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.0
 Classifier: Programming Language :: Python :: 3.1
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
 Classifier: Topic :: Security :: Cryptography
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/backports.ssl_match_hostname-3.5.0.1/README.txt 
new/backports.ssl_match_hostname-3.7.0.1/README.txt
--- old/backports.ssl_match_hostname-3.5.0.1/README.txt 1970-01-01 
01:00:00.000000000 +0100
+++ new/backports.ssl_match_hostname-3.7.0.1/README.txt 2018-03-22 
20:06:08.000000000 +0100
@@ -0,0 +1,63 @@
+
+The ssl.match_hostname() function from Python 3.7
+=================================================
+
+The Secure Sockets Layer is only actually *secure*
+if you check the hostname in the certificate returned
+by the server to which you are connecting,
+and verify that it matches to hostname
+that you are trying to reach.
+
+But the matching logic, defined in `RFC2818`_,
+can be a bit tricky to implement on your own.
+So the ``ssl`` package in the Standard Library of Python 3.2
+and greater now includes a ``match_hostname()`` function
+for performing this check instead of requiring every application
+to implement the check separately.
+
+This backport brings ``match_hostname()`` to users
+of earlier versions of Python.
+Simply make this distribution a dependency of your package,
+and then use it like this::
+
+    from backports.ssl_match_hostname import match_hostname, CertificateError
+    [...]
+    sslsock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv23,
+                              cert_reqs=ssl.CERT_REQUIRED, ca_certs=...)
+    try:
+        match_hostname(sslsock.getpeercert(), hostname)
+    except CertificateError, ce:
+        ...
+
+Brandon Craig Rhodes is merely the packager of this distribution;
+the actual code inside comes from Python 3.7 with small changes for
+portability.
+
+
+Requirements
+------------
+
+* If you need to use this on Python versions earlier than 2.6 you will need to
+  install the `ssl module`_.  From Python 2.6 upwards ``ssl`` is included in
+  the Python Standard Library so you do not need to install it separately.
+
+.. _`ssl module`:: https://pypi.python.org/pypi/ssl
+
+History
+-------
+
+* This function was introduced in python-3.2
+* It was updated for python-3.4a1 for a CVE 
+  (backports-ssl_match_hostname-3.4.0.1)
+* It was updated from RFC2818 to RFC 6125 compliance in order to fix another
+  security flaw for python-3.3.3 and python-3.4a5
+  (backports-ssl_match_hostname-3.4.0.2)
+* It was updated in python-3.5 to handle IPAddresses in ServerAltName fields
+  (something that backports.ssl_match_hostname will do if you also install the
+  ipaddress library from pypi).
+* It was updated in python-3.7 to handle IPAddresses without the ipaddress 
library and dropped
+  support for partial wildcards
+
+.. _`ipaddress module`:: https://pypi.python.org/pypi/ipaddress
+
+.. _RFC2818: http://tools.ietf.org/html/rfc2818.html
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/backports.ssl_match_hostname-3.5.0.1/backports/ssl_match_hostname/LICENSE.txt
 
new/backports.ssl_match_hostname-3.7.0.1/backports/ssl_match_hostname/LICENSE.txt
--- 
old/backports.ssl_match_hostname-3.5.0.1/backports/ssl_match_hostname/LICENSE.txt
   2015-12-19 23:30:28.000000000 +0100
+++ 
new/backports.ssl_match_hostname-3.7.0.1/backports/ssl_match_hostname/LICENSE.txt
   1970-01-01 01:00:00.000000000 +0100
@@ -1,51 +0,0 @@
-Python License (Python-2.0)
-
-Python License, Version 2 (Python-2.0)
-
-PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
---------------------------------------------
-
-1. This LICENSE AGREEMENT is between the Python Software Foundation
-("PSF"), and the Individual or Organization ("Licensee") accessing and
-otherwise using this software ("Python") in source or binary form and
-its associated documentation.
-
-2. Subject to the terms and conditions of this License Agreement, PSF
-hereby grants Licensee a nonexclusive, royalty-free, world-wide
-license to reproduce, analyze, test, perform and/or display publicly,
-prepare derivative works, distribute, and otherwise use Python
-alone or in any derivative version, provided, however, that PSF's
-License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
-2001-2013 Python Software Foundation; All Rights Reserved" are retained in
-Python alone or in any derivative version prepared by Licensee.
-
-3. In the event Licensee prepares a derivative work that is based on
-or incorporates Python or any part thereof, and wants to make
-the derivative work available to others as provided herein, then
-Licensee hereby agrees to include in any such work a brief summary of
-the changes made to Python.
-
-4. PSF is making Python available to Licensee on an "AS IS"
-basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
-IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
-DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
-FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
-INFRINGE ANY THIRD PARTY RIGHTS.
-
-5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
-FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
-A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
-OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-6. This License Agreement will automatically terminate upon a material
-breach of its terms and conditions.
-
-7. Nothing in this License Agreement shall be deemed to create any
-relationship of agency, partnership, or joint venture between PSF and
-Licensee. This License Agreement does not grant permission to use PSF
-trademarks or trade name in a trademark sense to endorse or promote
-products or services of Licensee, or any third party.
-
-8. By copying, installing or otherwise using Python, Licensee
-agrees to be bound by the terms and conditions of this License
-Agreement.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/backports.ssl_match_hostname-3.5.0.1/backports/ssl_match_hostname/README.txt
 
new/backports.ssl_match_hostname-3.7.0.1/backports/ssl_match_hostname/README.txt
--- 
old/backports.ssl_match_hostname-3.5.0.1/backports/ssl_match_hostname/README.txt
    2015-12-19 23:30:28.000000000 +0100
+++ 
new/backports.ssl_match_hostname-3.7.0.1/backports/ssl_match_hostname/README.txt
    1970-01-01 01:00:00.000000000 +0100
@@ -1,69 +0,0 @@
-
-The ssl.match_hostname() function from Python 3.5
-=================================================
-
-The Secure Sockets Layer is only actually *secure*
-if you check the hostname in the certificate returned
-by the server to which you are connecting,
-and verify that it matches to hostname
-that you are trying to reach.
-
-But the matching logic, defined in `RFC2818`_,
-can be a bit tricky to implement on your own.
-So the ``ssl`` package in the Standard Library of Python 3.2
-and greater now includes a ``match_hostname()`` function
-for performing this check instead of requiring every application
-to implement the check separately.
-
-This backport brings ``match_hostname()`` to users
-of earlier versions of Python.
-Simply make this distribution a dependency of your package,
-and then use it like this::
-
-    from backports.ssl_match_hostname import match_hostname, CertificateError
-    [...]
-    sslsock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv23,
-                              cert_reqs=ssl.CERT_REQUIRED, ca_certs=...)
-    try:
-        match_hostname(sslsock.getpeercert(), hostname)
-    except CertificateError, ce:
-        ...
-
-Brandon Craig Rhodes is merely the packager of this distribution;
-the actual code inside comes from Python 3.5 with small changes for
-portability.
-
-
-Requirements
-------------
-
-* If you want to verify hosts match with certificates via ServerAltname
-  IPAddress fields, you need to install the `ipaddress module`_.
-  backports.ssl_match_hostname will continue to work without ipaddress but
-  will only be able to handle ServerAltName DNSName fields, not IPAddress.
-  System packagers (Linux distributions, et al) are encouraged to add
-  this as a hard dependency in their packages.
-
-* If you need to use this on Python versions earlier than 2.6 you will need to
-  install the `ssl module`_.  From Python 2.6 upwards ``ssl`` is included in
-  the Python Standard Library so you do not need to install it separately.
-
-.. _`ipaddress module`:: https://pypi.python.org/pypi/ipaddress
-.. _`ssl module`:: https://pypi.python.org/pypi/ssl
-
-History
--------
-
-* This function was introduced in python-3.2
-* It was updated for python-3.4a1 for a CVE 
-  (backports-ssl_match_hostname-3.4.0.1)
-* It was updated from RFC2818 to RFC 6125 compliance in order to fix another
-  security flaw for python-3.3.3 and python-3.4a5
-  (backports-ssl_match_hostname-3.4.0.2)
-* It was updated in python-3.5 to handle IPAddresses in ServerAltName fields
-  (something that backports.ssl_match_hostname will do if you also install the
-  ipaddress library from pypi).
-
-
-.. _RFC2818: http://tools.ietf.org/html/rfc2818.html
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/backports.ssl_match_hostname-3.5.0.1/backports/ssl_match_hostname/__init__.py
 
new/backports.ssl_match_hostname-3.7.0.1/backports/ssl_match_hostname/__init__.py
--- 
old/backports.ssl_match_hostname-3.5.0.1/backports/ssl_match_hostname/__init__.py
   2015-12-19 23:30:28.000000000 +0100
+++ 
new/backports.ssl_match_hostname-3.7.0.1/backports/ssl_match_hostname/__init__.py
   2018-03-22 20:10:11.000000000 +0100
@@ -1,82 +1,134 @@
-"""The match_hostname() function from Python 3.3.3, essential when using 
SSL."""
+"""The match_hostname() function from Python 3.7.0, essential when using 
SSL."""
 
-import re
 import sys
+import socket as _socket
 
-# ipaddress has been backported to 2.6+ in pypi.  If it is installed on the
-# system, use it to handle IPAddress ServerAltnames (this was added in
-# python-3.5) otherwise only do DNS matching.  This allows
-# backports.ssl_match_hostname to continue to be used all the way back to
-# python-2.4.
 try:
-    import ipaddress
-except ImportError:
-    ipaddress = None
+    # Divergence: Python-3.7+'s _ssl has this exception type but older Pythons 
do not
+    from _ssl import SSLCertVerificationError
+    CertificateError = SSLCertVerificationError
+except:
+    class CertificateError(ValueError):
+        pass
 
-__version__ = '3.5.0.1'
 
+__version__ = '3.7.0.1'
 
-class CertificateError(ValueError):
-    pass
 
+# Divergence: Added to deal with ipaddess as bytes on python2
+def _to_text(obj):
+    if isinstance(obj, str) and sys.version_info < (3,):
+        obj = unicode(obj, encoding='ascii', errors='strict')
+    elif sys.version_info >= (3,) and isinstance(obj, bytes):
+        obj = str(obj, encoding='ascii', errors='strict')
+    return obj
 
-def _dnsname_match(dn, hostname, max_wildcards=1):
+
+def _to_bytes(obj):
+    if isinstance(obj, str) and sys.version_info >= (3,):
+        obj = bytes(obj, encoding='ascii', errors='strict')
+    elif sys.version_info < (3,) and isinstance(obj, unicode):
+        obj = obj.encode('ascii', 'strict')
+    return obj
+
+
+def _dnsname_match(dn, hostname):
     """Matching according to RFC 6125, section 6.4.3
 
-    http://tools.ietf.org/html/rfc6125#section-6.4.3
+    - Hostnames are compared lower case.
+    - For IDNA, both dn and hostname must be encoded as IDN A-label (ACE).
+    - Partial wildcards like 'www*.example.org', multiple wildcards, sole
+      wildcard or wildcards in labels other then the left-most label are not
+      supported and a CertificateError is raised.
+    - A wildcard must match at least one character.
     """
-    pats = []
     if not dn:
         return False
 
-    # Ported from python3-syntax:
-    # leftmost, *remainder = dn.split(r'.')
-    parts = dn.split(r'.')
-    leftmost = parts[0]
-    remainder = parts[1:]
-
-    wildcards = leftmost.count('*')
-    if wildcards > max_wildcards:
-        # Issue #17980: avoid denials of service by refusing more
-        # than one wildcard per fragment.  A survey of established
-        # policy among SSL implementations showed it to be a
-        # reasonable choice.
-        raise CertificateError(
-            "too many wildcards in certificate DNS name: " + repr(dn))
-
+    wildcards = dn.count('*')
     # speed up common case w/o wildcards
     if not wildcards:
         return dn.lower() == hostname.lower()
 
-    # RFC 6125, section 6.4.3, subitem 1.
-    # The client SHOULD NOT attempt to match a presented identifier in which
-    # the wildcard character comprises a label other than the left-most label.
-    if leftmost == '*':
-        # When '*' is a fragment by itself, it matches a non-empty dotless
-        # fragment.
-        pats.append('[^.]+')
-    elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
-        # RFC 6125, section 6.4.3, subitem 3.
-        # The client SHOULD NOT attempt to match a presented identifier
-        # where the wildcard character is embedded within an A-label or
-        # U-label of an internationalized domain name.
-        pats.append(re.escape(leftmost))
+    if wildcards > 1:
+        # Divergence .format() to percent formatting for Python < 2.6
+        raise CertificateError(
+            "too many wildcards in certificate DNS name: %s" % repr(dn))
+
+    dn_leftmost, sep, dn_remainder = dn.partition('.')
+
+    if '*' in dn_remainder:
+        # Only match wildcard in leftmost segment.
+        # Divergence .format() to percent formatting for Python < 2.6
+        raise CertificateError(
+            "wildcard can only be present in the leftmost label: "
+            "%s." % repr(dn))
+
+    if not sep:
+        # no right side
+        # Divergence .format() to percent formatting for Python < 2.6
+        raise CertificateError(
+            "sole wildcard without additional labels are not support: "
+            "%s." % repr(dn))
+
+    if dn_leftmost != '*':
+        # no partial wildcard matching
+        # Divergence .format() to percent formatting for Python < 2.6
+        raise CertificateError(
+            "partial wildcards in leftmost label are not supported: "
+            "%s." % repr(dn))
+
+    hostname_leftmost, sep, hostname_remainder = hostname.partition('.')
+    if not hostname_leftmost or not sep:
+        # wildcard must match at least one char
+        return False
+    return dn_remainder.lower() == hostname_remainder.lower()
+
+
+def _inet_paton(ipname):
+    """Try to convert an IP address to packed binary form
+
+    Supports IPv4 addresses on all platforms and IPv6 on platforms with IPv6
+    support.
+    """
+    # inet_aton() also accepts strings like '1'
+    # Divergence: We make sure we have native string type for all python 
versions
+    try:
+        b_ipname = _to_bytes(ipname)
+    except UnicodeError:
+        raise ValueError("%s must be an all-ascii string." % repr(ipname))
+
+    # Set ipname in native string format
+    if sys.version_info < (3,):
+        n_ipname = b_ipname
     else:
-        # Otherwise, '*' matches any dotless string, e.g. www*
-        pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
+        n_ipname = ipname
 
-    # add the remaining fragments, ignore any wildcards
-    for frag in remainder:
-        pats.append(re.escape(frag))
+    if n_ipname.count('.') == 3:
+        try:
+            return _socket.inet_aton(n_ipname)
+        # Divergence: OSError on late python3.  socket.error earlier.
+        # Null bytes generate ValueError on python3(we want to raise
+        # ValueError anyway), TypeError # earlier
+        except (OSError, _socket.error, TypeError):
+            pass
 
-    pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
-    return pat.match(hostname)
+    try:
+        return _socket.inet_pton(_socket.AF_INET6, n_ipname)
+    # Divergence: OSError on late python3.  socket.error earlier.
+    # Null bytes generate ValueError on python3(we want to raise
+    # ValueError anyway), TypeError # earlier
+    except (OSError, _socket.error, TypeError):
+        # Divergence .format() to percent formatting for Python < 2.6
+        raise ValueError("%s is neither an IPv4 nor an IP6 "
+                         "address." % repr(ipname))
+    except AttributeError:
+        # AF_INET6 not available
+        pass
 
+    # Divergence .format() to percent formatting for Python < 2.6
+    raise ValueError("%s is not an IPv4 address." % repr(ipname))
 
-def _to_unicode(obj):
-    if isinstance(obj, str) and sys.version_info < (3,):
-        obj = unicode(obj, encoding='ascii', errors='strict')
-    return obj
 
 def _ipaddress_match(ipname, host_ip):
     """Exact matching of IP addresses.
@@ -85,15 +137,19 @@
     (section 1.7.2 - "Out of Scope").
     """
     # OpenSSL may add a trailing newline to a subjectAltName's IP address
-    # Divergence from upstream: ipaddress can't handle byte str
-    ip = ipaddress.ip_address(_to_unicode(ipname).rstrip())
+    ip = _inet_paton(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
-    rules are followed, but IP addresses are not accepted for *hostname*.
+    rules are followed.
+
+    The function matches IP addresses rather than dNSNames if hostname is a
+    valid ipaddress string. IPv4 addresses are supported on all platforms.
+    IPv6 addresses are supported on platforms with IPv6 support (AF_INET6
+    and inet_pton).
 
     CertificateError is raised on failure. On success, the function
     returns nothing.
@@ -103,22 +159,16 @@
                          "SSL socket or SSL context with either "
                          "CERT_OPTIONAL or CERT_REQUIRED")
     try:
-        # Divergence from upstream: ipaddress can't handle byte str
-        host_ip = ipaddress.ip_address(_to_unicode(hostname))
+        # Divergence: Deal with hostname as bytes
+        host_ip = _inet_paton(_to_text(hostname))
     except ValueError:
         # Not an IP address (common case)
         host_ip = None
     except UnicodeError:
-        # Divergence from upstream: Have to deal with ipaddress not taking
-        # byte strings.  addresses should be all ascii, so we consider it not
-        # an ipaddress in this case
+        # Divergence: Deal with hostname as byte strings.
+        # IP addresses should be all ascii, so we consider it not
+        # an IP address if this fails
         host_ip = None
-    except AttributeError:
-        # Divergence from upstream: Make ipaddress library optional
-        if ipaddress is None:
-            host_ip = None
-        else:
-            raise
     dnsnames = []
     san = cert.get('subjectAltName', ())
     for key, value in san:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/backports.ssl_match_hostname-3.5.0.1/setup.cfg 
new/backports.ssl_match_hostname-3.7.0.1/setup.cfg
--- old/backports.ssl_match_hostname-3.5.0.1/setup.cfg  1970-01-01 
01:00:00.000000000 +0100
+++ new/backports.ssl_match_hostname-3.7.0.1/setup.cfg  2018-03-22 
20:02:53.000000000 +0100
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal=1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/backports.ssl_match_hostname-3.5.0.1/setup.py 
new/backports.ssl_match_hostname-3.7.0.1/setup.py
--- old/backports.ssl_match_hostname-3.5.0.1/setup.py   2015-12-19 
23:30:28.000000000 +0100
+++ new/backports.ssl_match_hostname-3.7.0.1/setup.py   2018-03-22 
20:06:08.000000000 +0100
@@ -4,12 +4,12 @@
 from distutils.core import setup
 
 long_description = open(os.path.join(
-    os.path.dirname(__file__), 'backports', 'ssl_match_hostname', 'README.txt',
+    os.path.dirname(__file__), 'README.txt'
     )).read()
 
 setup(
     name='backports.ssl_match_hostname',
-    version='3.5.0.1',
+    version='3.7.0.1',
     description='The ssl.match_hostname() function from Python 3.5',
     long_description=long_description,
     author='Brandon Rhodes',
@@ -28,6 +28,12 @@
         'Programming Language :: Python :: 3',
         'Programming Language :: Python :: 3.0',
         'Programming Language :: Python :: 3.1',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
         'Topic :: Security :: Cryptography',
         ],
     packages=['backports', 'backports.ssl_match_hostname'],

++++++ test_ssl.py ++++++
# coding: utf-8
# Test the support for SSL and sockets

import sys
import socket

# Divergence: unittest2 so that we have assertRaisesRegexp
if sys.version_info < (3,):
    import unittest2 as unittest
else:
    import unittest

from backports import ssl_match_hostname as ssl


class BasicSocketTests(unittest.TestCase):

    def test_match_hostname(self):
        def ok(cert, hostname):
            ssl.match_hostname(cert, hostname)
        def fail(cert, hostname):
            self.assertRaises(ssl.CertificateError,
                              ssl.match_hostname, cert, hostname)

        # -- Hostname matching --

        cert = {'subject': ((('commonName', 'example.com'),),)}
        ok(cert, 'example.com')
        ok(cert, 'ExAmple.cOm')
        fail(cert, 'www.example.com')
        fail(cert, '.example.com')
        fail(cert, 'example.org')
        fail(cert, 'exampleXcom')

        cert = {'subject': ((('commonName', '*.a.com'),),)}
        ok(cert, 'foo.a.com')
        fail(cert, 'bar.foo.a.com')
        fail(cert, 'a.com')
        fail(cert, 'Xa.com')
        fail(cert, '.a.com')

        # only match wildcards when they are the only thing
        # in left-most segment
        cert = {'subject': ((('commonName', 'f*.com'),),)}
        fail(cert, 'foo.com')
        fail(cert, 'f.com')
        fail(cert, 'bar.com')
        fail(cert, 'foo.a.com')
        fail(cert, 'bar.foo.com')

        # NULL bytes are bad, CVE-2013-4073
        cert = {'subject': ((('commonName',
                              'null.python.org\x00example.org'),),)}
        ok(cert, 'null.python.org\x00example.org') # or raise an error?
        fail(cert, 'example.org')
        fail(cert, 'null.python.org')

        # error cases with wildcards
        cert = {'subject': ((('commonName', '*.*.a.com'),),)}
        fail(cert, 'bar.foo.a.com')
        fail(cert, 'a.com')
        fail(cert, 'Xa.com')
        fail(cert, '.a.com')

        cert = {'subject': ((('commonName', 'a.*.com'),),)}
        fail(cert, 'a.foo.com')
        fail(cert, 'a..com')
        fail(cert, 'a.com')

        # wildcard doesn't match IDNA prefix 'xn--'
        # Divergence: explicitly mark text string with u
        idna = u'püthon.python.org'.encode("idna").decode("ascii")
        cert = {'subject': ((('commonName', idna),),)}
        ok(cert, idna)
        cert = {'subject': ((('commonName', 'x*.python.org'),),)}
        fail(cert, idna)
        cert = {'subject': ((('commonName', 'xn--p*.python.org'),),)}
        fail(cert, idna)

        # wildcard in first fragment and  IDNA A-labels in sequent fragments
        # are supported.
        # Divergence: explicitly mark text strings with u
        idna = u'www*.pythön.org'.encode("idna").decode("ascii")
        cert = {'subject': ((('commonName', idna),),)}
        fail(cert, u'www.pythön.org'.encode("idna").decode("ascii"))
        fail(cert, u'www1.pythön.org'.encode("idna").decode("ascii"))
        fail(cert, u'ftp.pythön.org'.encode("idna").decode("ascii"))
        fail(cert, u'pythön.org'.encode("idna").decode("ascii"))

        # Slightly fake real-world example
        cert = {'notAfter': 'Jun 26 21:41:46 2011 GMT',
                'subject': ((('commonName', 'linuxfrz.org'),),),
                'subjectAltName': (('DNS', 'linuxfr.org'),
                                   ('DNS', 'linuxfr.com'),
                                   ('othername', '<unsupported>'))}
        ok(cert, 'linuxfr.org')
        ok(cert, 'linuxfr.com')
        # Not a "DNS" entry
        fail(cert, '<unsupported>')
        # When there is a subjectAltName, commonName isn't used
        fail(cert, 'linuxfrz.org')

        # A pristine real-world example
        cert = {'notAfter': 'Dec 18 23:59:59 2011 GMT',
                'subject': ((('countryName', 'US'),),
                            (('stateOrProvinceName', 'California'),),
                            (('localityName', 'Mountain View'),),
                            (('organizationName', 'Google Inc'),),
                            (('commonName', 'mail.google.com'),))}
        ok(cert, 'mail.google.com')
        fail(cert, 'gmail.com')
        # 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 --
        if hasattr(socket, 'AF_INET6'):
            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'),),
                            (('stateOrProvinceName', 'California'),),
                            (('localityName', 'Mountain View'),),
                            (('organizationName', 'Google Inc'),))}
        fail(cert, 'mail.google.com')

        # No DNS entry in subjectAltName but a commonName
        cert = {'notAfter': 'Dec 18 23:59:59 2099 GMT',
                'subject': ((('countryName', 'US'),),
                            (('stateOrProvinceName', 'California'),),
                            (('localityName', 'Mountain View'),),
                            (('commonName', 'mail.google.com'),)),
                'subjectAltName': (('othername', 'blabla'), )}
        ok(cert, 'mail.google.com')

        # No DNS entry subjectAltName and no commonName
        cert = {'notAfter': 'Dec 18 23:59:59 2099 GMT',
                'subject': ((('countryName', 'US'),),
                            (('stateOrProvinceName', 'California'),),
                            (('localityName', 'Mountain View'),),
                            (('organizationName', 'Google Inc'),)),
                'subjectAltName': (('othername', 'blabla'),)}
        fail(cert, 'google.com')

        # Empty cert / no cert
        self.assertRaises(ValueError, ssl.match_hostname, None, 'example.com')
        self.assertRaises(ValueError, ssl.match_hostname, {}, 'example.com')

        # Issue #17980: avoid denials of service by refusing more than one
        # wildcard per fragment.
        cert = {'subject': ((('commonName', 'a*b.example.com'),),)}
        with self.assertRaisesRegex(
                ssl.CertificateError,
                "partial wildcards in leftmost label are not supported"):
            ssl.match_hostname(cert, 'axxb.example.com')

        cert = {'subject': ((('commonName', 'www.*.example.com'),),)}
        with self.assertRaisesRegex(
                ssl.CertificateError,
                "wildcard can only be present in the leftmost label"):
            ssl.match_hostname(cert, 'www.sub.example.com')

        cert = {'subject': ((('commonName', 'a*b*.example.com'),),)}
        with self.assertRaisesRegex(
                ssl.CertificateError,
                "too many wildcards"):
            ssl.match_hostname(cert, 'axxbxxc.example.com')

        cert = {'subject': ((('commonName', '*'),),)}
        with self.assertRaisesRegex(
                ssl.CertificateError,
                "sole wildcard without additional labels are not support"):
            ssl.match_hostname(cert, 'host')

        cert = {'subject': ((('commonName', '*.com'),),)}
        with self.assertRaisesRegex(
                ssl.CertificateError,
                r"hostname 'com' doesn't match '\*.com'"):
            ssl.match_hostname(cert, 'com')

        # extra checks for _inet_paton()
        for invalid in ['1', '', '1.2.3', '256.0.0.1', '127.0.0.1/24']:
            with self.assertRaises(ValueError):
                ssl._inet_paton(invalid)
        for ipaddr in ['127.0.0.1', '192.168.0.1']:
            self.assertTrue(ssl._inet_paton(ipaddr))
        if hasattr(socket, 'AF_INET6'):
            for ipaddr in ['::1', '2001:db8:85a3::8a2e:370:7334']:
                self.assertTrue(ssl._inet_paton(ipaddr))

Reply via email to