Repository: urllib3/urllib3

Issue:
Must remove square brackets from IPv6 addresses when using match_hostname
Spotted in [OpenStack](https://bugs.launchpad.net/nova/+bug/1526413). Introduced by my fix #708.

Apparently, while `httplib` itself doesn't care about the square brackets, the `match_hostname` function needs them gone. I suggest we move the stripping down into our `ssl.py` module.



Discussion:
This is the openstack bug, FYI: https://bugs.launchpad.net/nova/+bug/1526413

@Lukasa seems to be working on this so I'm labelling it in progress and assigning him :-P


Resolving patch:
diff --git a/dummyserver/server.py b/dummyserver/server.py
--- a/dummyserver/server.py
+++ b/dummyserver/server.py
@@ -34,10 +34,15 @@
     'certfile': os.path.join(CERTS_PATH, 'server.no_san.crt'),
     'keyfile': DEFAULT_CERTS['keyfile']
 }
+IPV6_ADDR_CERTS = {
+    'certfile': os.path.join(CERTS_PATH, 'server.ipv6addr.crt'),
+    'keyfile': os.path.join(CERTS_PATH, 'server.ipv6addr.key'),
+}
 DEFAULT_CA = os.path.join(CERTS_PATH, 'cacert.pem')
 DEFAULT_CA_BAD = os.path.join(CERTS_PATH, 'client_bad.pem')
 NO_SAN_CA = os.path.join(CERTS_PATH, 'cacert.no_san.pem')
 DEFAULT_CA_DIR = os.path.join(CERTS_PATH, 'ca_path_test')
+IPV6_ADDR_CA = os.path.join(CERTS_PATH, 'server.ipv6addr.crt')
 
 
 def _has_ipv6(host):
diff --git a/dummyserver/testcase.py b/dummyserver/testcase.py
--- a/dummyserver/testcase.py
+++ b/dummyserver/testcase.py
@@ -113,6 +113,17 @@ class HTTPSDummyServerTestCase(HTTPDummyServerTestCase):
     certs = DEFAULT_CERTS
 
 
+class IPV6HTTPSDummyServerTestCase(HTTPSDummyServerTestCase):
+    host = '::1'
+
+    @classmethod
+    def setUpClass(cls):
+        if not socket.has_ipv6:
+            raise SkipTest('IPv6 not available')
+        else:
+            super(IPV6HTTPSDummyServerTestCase, cls).setUpClass()
+
+
 class HTTPDummyProxyTestCase(unittest.TestCase):
 
     http_host = 'localhost'
diff --git a/urllib3/connection.py b/urllib3/connection.py
--- a/urllib3/connection.py
+++ b/urllib3/connection.py
@@ -265,7 +265,16 @@ def connect(self):
                     'for details.)'.format(hostname)),
                     SubjectAltNameWarning
                 )
-            match_hostname(cert, self.assert_hostname or hostname)
+
+            # In case the hostname is an IPv6 address, strip the square
+            # brackets from it before using it to validate. This is because
+            # a certificate with an IPv6 address in it won't have square
+            # brackets around that address. Sadly, match_hostname won't do this
+            # for us: it expects the plain host part without any extra work
+            # that might have been done to make it palatable to httplib.
+            asserted_hostname = self.assert_hostname or hostname
+            asserted_hostname = asserted_hostname.strip('[]')
+            match_hostname(cert, asserted_hostname)
 
         self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or
                             self.assert_fingerprint is not None)


Test patch:
diff --git a/test/with_dummyserver/test_https.py b/test/with_dummyserver/test_https.py
--- a/test/with_dummyserver/test_https.py
+++ b/test/with_dummyserver/test_https.py
@@ -8,9 +8,12 @@
 import mock
 from nose.plugins.skip import SkipTest
 
-from dummyserver.testcase import HTTPSDummyServerTestCase
+from dummyserver.testcase import (
+    HTTPSDummyServerTestCase, IPV6HTTPSDummyServerTestCase
+)
 from dummyserver.server import (DEFAULT_CA, DEFAULT_CA_BAD, DEFAULT_CERTS,
-                                NO_SAN_CERTS, NO_SAN_CA, DEFAULT_CA_DIR)
+                                NO_SAN_CERTS, NO_SAN_CA, DEFAULT_CA_DIR,
+                                IPV6_ADDR_CERTS, IPV6_ADDR_CA, HAS_IPV6)
 
 from test import (
     onlyPy26OrOlder,
@@ -466,5 +469,20 @@ def test_warning_for_certs_without_a_san(self):
             self.assertTrue(warn.called)
 
 
+
+class TestHTTPS_IPv6Addr(IPV6HTTPSDummyServerTestCase):
+    certs = IPV6_ADDR_CERTS
+
+    def test_strip_square_brackets_before_validating(self):
+        """Test that the fix for #760 works."""
+        if not HAS_IPV6:
+            raise SkipTest("Only runs on IPv6 systems")
+        https_pool = HTTPSConnectionPool('[::1]', self.port,
+                                         cert_reqs='CERT_REQUIRED',
+                                         ca_certs=IPV6_ADDR_CA)
+        r = https_pool.request('GET', '/')
+        self.assertEqual(r.status, 200)
+
+
 if __name__ == '__main__':
     unittest.main()