Rev 6614: (vila) Use ssl.match_hostname instead of our own. (Vincent Ladeuil) in file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/

Patch Queue Manager pqm at pqm.ubuntu.com
Sun Jan 31 13:36:59 UTC 2016


At file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 6614 [merge]
revision-id: pqm at pqm.ubuntu.com-20160131133659-ouy92ee2wlv9xz8m
parent: pqm at pqm.ubuntu.com-20160122083200-1mrbddrmyxim19hg
parent: v.ladeuil+lp at free.fr-20160131125531-5magd1q1njwkal3a
committer: Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Sun 2016-01-31 13:36:59 +0000
message:
  (vila) Use ssl.match_hostname instead of our own. (Vincent Ladeuil)
modified:
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/tests/test_https_urllib.py test_https_urllib.py-20111220105828-v3g3fknv8inj2jqv-1
  bzrlib/transport/http/_urllib2_wrappers.py _urllib2_wrappers.py-20060913231729-ha9ugi48ktx481ao-1
  doc/en/release-notes/bzr-2.7.txt bzr2.7.txt-20130727124539-wnx897hy9l2h9f7x-1
=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2013-10-04 09:56:23 +0000
+++ b/bzrlib/errors.py	2016-01-27 13:36:17 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2005-2011 Canonical Ltd
+# Copyright (C) 2005-2013, 2016 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -1686,14 +1686,6 @@
         TransportError.__init__(self, msg, orig_error=orig_error)
 
 
-class CertificateError(TransportError):
-
-    _fmt = "Certificate error: %(error)s"
-
-    def __init__(self, error):
-        self.error = error
-
-
 class InvalidHttpRange(InvalidHttpResponse):
 
     _fmt = "Invalid http range %(range)r for %(path)s: %(msg)s"

=== modified file 'bzrlib/tests/test_https_urllib.py'
--- a/bzrlib/tests/test_https_urllib.py	2013-05-20 16:38:11 +0000
+++ b/bzrlib/tests/test_https_urllib.py	2016-01-31 12:55:31 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2011,2012 Canonical Ltd
+# Copyright (C) 2011, 2012, 2013, 2016 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -19,24 +19,21 @@
 """
 
 import os
-import ssl
+import sys
 
 from bzrlib import (
     config,
     trace,
-    )
+)
 from bzrlib.errors import (
-    CertificateError,
     ConfigOptionValueError,
-    )
-from bzrlib.tests import (
-    TestCase,
-    TestCaseInTempDir,
-    )
+)
+from bzrlib import tests
 from bzrlib.transport.http import _urllib2_wrappers
-
-
-class CaCertsConfigTests(TestCaseInTempDir):
+from bzrlib.transport.http._urllib2_wrappers import ssl
+
+
+class CaCertsConfigTests(tests.TestCaseInTempDir):
 
     def get_stack(self, content):
         return config.MemoryStack(content.encode('utf-8'))
@@ -58,6 +55,7 @@
         self.overrideAttr(_urllib2_wrappers.opt_ssl_ca_certs, 'default',
                           os.path.join(self.test_dir, u"nonexisting.pem"))
         self.warnings = []
+
         def warning(*args):
             self.warnings.append(args[0] % args[1:])
         self.overrideAttr(trace, 'warning', warning)
@@ -67,7 +65,7 @@
                               "is not valid for \"ssl.ca_certs\"")
 
 
-class CertReqsConfigTests(TestCaseInTempDir):
+class CertReqsConfigTests(tests.TestCaseInTempDir):
 
     def test_default(self):
         stack = config.MemoryStack("")
@@ -82,35 +80,41 @@
         self.assertRaises(ConfigOptionValueError, stack.get, "ssl.cert_reqs")
 
 
-class MatchHostnameTests(TestCase):
+class MatchHostnameTests(tests.TestCase):
+
+    def setUp(self):
+        super(MatchHostnameTests, self).setUp()
+        if sys.version_info < (2, 7, 9):
+            raise tests.TestSkipped(
+                'python version too old to provide proper'
+                ' https hostname verification')
 
     def test_no_certificate(self):
         self.assertRaises(ValueError,
-                          _urllib2_wrappers.match_hostname, {}, "example.com")
+                          ssl.match_hostname, {}, "example.com")
 
     def test_wildcards_in_cert(self):
         def ok(cert, hostname):
-            _urllib2_wrappers.match_hostname(cert, hostname)
+            ssl.match_hostname(cert, hostname)
+
+        def not_ok(cert, hostname):
+            self.assertRaises(
+                ssl.CertificateError,
+                ssl.match_hostname, cert, hostname)
 
         # Python Issue #17980: avoid denials of service by refusing more than
         # one wildcard per fragment.
-        cert = {'subject': ((('commonName', 'a*b.com'),),)}
-        ok(cert, 'axxb.com')
-        cert = {'subject': ((('commonName', 'a*b.co*'),),)}
-        ok(cert, 'axxb.com')
-        cert = {'subject': ((('commonName', 'a*b*.com'),),)}
-        try:
-            _urllib2_wrappers.match_hostname(cert, 'axxbxxc.com')
-        except ValueError as e:
-            self.assertIn("too many wildcards", str(e))
+        ok({'subject': ((('commonName', 'a*b.com'),),)}, 'axxb.com')
+        not_ok({'subject': ((('commonName', 'a*b.co*'),),)}, 'axxb.com')
+        not_ok({'subject': ((('commonName', 'a*b*.com'),),)}, 'axxbxxc.com')
 
     def test_no_valid_attributes(self):
-        self.assertRaises(CertificateError, _urllib2_wrappers.match_hostname,
+        self.assertRaises(ssl.CertificateError, ssl.match_hostname,
                           {"Problem": "Solved"}, "example.com")
 
     def test_common_name(self):
         cert = {'subject': ((('commonName', 'example.com'),),)}
         self.assertIs(None,
-                      _urllib2_wrappers.match_hostname(cert, "example.com"))
-        self.assertRaises(CertificateError, _urllib2_wrappers.match_hostname,
+                      ssl.match_hostname(cert, "example.com"))
+        self.assertRaises(ssl.CertificateError, ssl.match_hostname,
                           cert, "example.org")

=== modified file 'bzrlib/transport/http/_urllib2_wrappers.py'
--- a/bzrlib/transport/http/_urllib2_wrappers.py	2013-05-20 16:38:11 +0000
+++ b/bzrlib/transport/http/_urllib2_wrappers.py	2016-01-31 12:55:31 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2012 Canonical Ltd
+# Copyright (C) 2006-2013, 2016 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -56,6 +56,7 @@
 import urllib2
 import urlparse
 import re
+import ssl
 import sys
 import time
 
@@ -70,23 +71,33 @@
     transport,
     ui,
     urlutils,
-    )
-lazy_import.lazy_import(globals(), """
-import ssl
-""")
+)
+
+try:
+    _ = (ssl.match_hostname, ssl.CertificateError)
+except AttributeError:
+    # Provide fallbacks for python < 2.7.9
+    def match_hostname(cert, host):
+        trace.warning(
+            '%s cannot be verified, https certificates verification is only'
+            ' available for python versions >= 2.7.9' % (host,))
+    ssl.match_hostname = match_hostname
+    ssl.CertificateError = ValueError
 
 
 # Note for packagers: if there is no package providing certs for your platform,
 # the curl project produces http://curl.haxx.se/ca/cacert.pem weekly.
 _ssl_ca_certs_known_locations = [
-    u'/etc/ssl/certs/ca-certificates.crt', # Ubuntu/debian/gentoo
-    u'/etc/pki/tls/certs/ca-bundle.crt', # Fedora/CentOS/RH
-    u'/etc/ssl/ca-bundle.pem', # OpenSuse
-    u'/etc/ssl/cert.pem', # OpenSuse
-    u"/usr/local/share/certs/ca-root-nss.crt", # FreeBSD
+    u'/etc/ssl/certs/ca-certificates.crt',  # Ubuntu/debian/gentoo
+    u'/etc/pki/tls/certs/ca-bundle.crt',  # Fedora/CentOS/RH
+    u'/etc/ssl/ca-bundle.pem',  # OpenSuse
+    u'/etc/ssl/cert.pem',  # OpenSuse
+    u"/usr/local/share/certs/ca-root-nss.crt",  # FreeBSD
     # XXX: Needs checking, can't trust the interweb ;) -- vila 2012-01-25
-    u'/etc/openssl/certs/ca-certificates.crt', # Solaris
-    ]
+    u'/etc/openssl/certs/ca-certificates.crt',  # Solaris
+]
+
+
 def default_ca_certs():
     if sys.platform == 'win32':
         return os.path.join(os.path.dirname(sys.executable), u"cacert.pem")
@@ -115,13 +126,12 @@
 def cert_reqs_from_store(unicode_str):
     import ssl
     try:
-        return {
-            "required": ssl.CERT_REQUIRED,
-            "none": ssl.CERT_NONE
-            }[unicode_str]
+        return {"required": ssl.CERT_REQUIRED,
+                "none": ssl.CERT_NONE}[unicode_str]
     except KeyError:
         raise ValueError("invalid value %s" % unicode_str)
 
+
 def default_ca_reqs():
     if sys.platform in ('win32', 'darwin'):
         # FIXME: Once we get a native access to root certificates there, this
@@ -131,10 +141,10 @@
         return u'required'
 
 opt_ssl_ca_certs = config.Option('ssl.ca_certs',
-        from_unicode=ca_certs_from_store,
-        default=default_ca_certs,
-        invalid='warning',
-        help="""\
+                                 from_unicode=ca_certs_from_store,
+                                 default=default_ca_certs,
+                                 invalid='warning',
+                                 help="""\
 Path to certification authority certificates to trust.
 
 This should be a valid path to a bundle containing all root Certificate
@@ -144,10 +154,10 @@
 """)
 
 opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
-        default=default_ca_reqs,
-        from_unicode=cert_reqs_from_store,
-        invalid='error',
-        help="""\
+                                  default=default_ca_reqs,
+                                  from_unicode=cert_reqs_from_store,
+                                  invalid='error',
+                                  help="""\
 Whether to require a certificate from the remote side. (default:required)
 
 Possible values:
@@ -398,68 +408,6 @@
         self._wrap_socket_for_reporting(self.sock)
 
 
-# These two methods were imported from Python 3.2's ssl module
-
-def _dnsname_to_pat(dn, max_wildcards=1):
-    pats = []
-    for frag in dn.split(r'.'):
-        if frag.count('*') > max_wildcards:
-            # Python Issue #17980: avoid denials of service by refusing more
-            # than one wildcard per fragment.  A survery of established
-            # policy among SSL implementations showed it to be a
-            # reasonable choice.
-            raise ValueError(
-                "too many wildcards in certificate DNS name: " + repr(dn))
-        if frag == '*':
-            # When '*' is a fragment by itself, it matches a non-empty dotless
-            # fragment.
-            pats.append('[^.]+')
-        else:
-            # Otherwise, '*' matches any dotless fragment.
-            frag = re.escape(frag)
-            pats.append(frag.replace(r'\*', '[^.]*'))
-    return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
-
-
-def match_hostname(cert, hostname):
-    """Verify that *cert* (in decoded format as returned by
-    SSLSocket.getpeercert()) matches the *hostname*.  RFC 2818 rules
-    are mostly followed, but IP addresses are not accepted for *hostname*.
-
-    CertificateError is raised on failure. On success, the function
-    returns nothing.
-    """
-    if not cert:
-        raise ValueError("empty or no certificate")
-    dnsnames = []
-    san = cert.get('subjectAltName', ())
-    for key, value in san:
-        if key == 'DNS':
-            if _dnsname_to_pat(value).match(hostname):
-                return
-            dnsnames.append(value)
-    if not san:
-        # The subject is only checked when subjectAltName is empty
-        for sub in cert.get('subject', ()):
-            for key, value in sub:
-                # XXX according to RFC 2818, the most specific Common Name
-                # must be used.
-                if key == 'commonName':
-                    if _dnsname_to_pat(value).match(hostname):
-                        return
-                    dnsnames.append(value)
-    if len(dnsnames) > 1:
-        raise errors.CertificateError(
-            "hostname %r doesn't match either of %s"
-            % (hostname, ', '.join(map(repr, dnsnames))))
-    elif len(dnsnames) == 1:
-        raise errors.CertificateError("hostname %r doesn't match %r" %
-                                      (hostname, dnsnames[0]))
-    else:
-        raise errors.CertificateError("no appropriate commonName or "
-            "subjectAltName fields were found")
-
-
 class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
 
     def __init__(self, host, port=None, key_file=None, cert_file=None,
@@ -503,9 +451,10 @@
                     "'bzr help ssl.ca_certs' for more information on setting "
                     "trusted CAs.")
         try:
-            ssl_sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file,
+            ssl_sock = ssl.wrap_socket(
+                self.sock, self.key_file, self.cert_file,
                 cert_reqs=cert_reqs, ca_certs=ca_certs)
-        except ssl.SSLError, e:
+        except ssl.SSLError:
             trace.note(
                 "\n"
                 "See `bzr help ssl.ca_certs` for how to specify trusted CA"
@@ -515,7 +464,7 @@
             raise
         if cert_reqs == ssl.CERT_REQUIRED:
             peer_cert = ssl_sock.getpeercert()
-            match_hostname(peer_cert, host)
+            ssl.match_hostname(peer_cert, host)
 
         # Wrap the ssl socket before anybody use it
         self._wrap_socket_for_reporting(ssl_sock)
@@ -833,7 +782,7 @@
                     % (request, request.connection.sock.getsockname())
             response = connection.getresponse()
             convert_to_addinfourl = True
-        except (ssl.SSLError, errors.CertificateError):
+        except (ssl.SSLError, ssl.CertificateError):
             # Something is wrong with either the certificate or the hostname,
             # re-trying won't help
             raise

=== modified file 'doc/en/release-notes/bzr-2.7.txt'
--- a/doc/en/release-notes/bzr-2.7.txt	2016-01-22 08:02:12 +0000
+++ b/doc/en/release-notes/bzr-2.7.txt	2016-01-28 13:13:28 +0000
@@ -70,6 +70,9 @@
   or UnicodeEncodeError when given unicode strings rather than bytes.
   (Vincent Ladeuil, #106898)
 
+* Use ssl.match_hostname from the python ssl module and stop carrying a
+  specific version that has become obsolete. (Vincent Ladeuil, #1538480)
+
 Changed Behaviour
 *****************
 




More information about the bazaar-commits mailing list