THRIFT-3651 Make backports.match_hostname and ipaddress optional
Client: Python
Patch: Nobuaki Sukegawa

This closes #880


Project: http://git-wip-us.apache.org/repos/asf/thrift/repo
Commit: http://git-wip-us.apache.org/repos/asf/thrift/commit/f32bae7a
Tree: http://git-wip-us.apache.org/repos/asf/thrift/tree/f32bae7a
Diff: http://git-wip-us.apache.org/repos/asf/thrift/diff/f32bae7a

Branch: refs/heads/master
Commit: f32bae7a89a47047fbed9a47b78d6b69d70a3764
Parents: a72ffbe
Author: Nobuaki Sukegawa <ns...@apache.org>
Authored: Sat Feb 20 08:51:33 2016 +0900
Committer: Nobuaki Sukegawa <ns...@apache.org>
Committed: Mon Feb 22 23:25:32 2016 +0900

----------------------------------------------------------------------
 lib/py/src/transport/TSSLSocket.py | 48 +++-----------------
 lib/py/src/transport/sslcompat.py  | 77 +++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/thrift/blob/f32bae7a/lib/py/src/transport/TSSLSocket.py
----------------------------------------------------------------------
diff --git a/lib/py/src/transport/TSSLSocket.py 
b/lib/py/src/transport/TSSLSocket.py
index 4cae27c..e57a0d4 100644
--- a/lib/py/src/transport/TSSLSocket.py
+++ b/lib/py/src/transport/TSSLSocket.py
@@ -23,8 +23,8 @@ import socket
 import ssl
 import sys
 import warnings
-from backports.ssl_match_hostname import match_hostname
 
+from .sslcompat import _match_hostname, _match_has_ipaddress
 from thrift.transport import TSocket
 from thrift.transport.TTransport import TTransportException
 
@@ -259,7 +259,7 @@ class TSSLSocket(TSocket.TSocket, TSSLBase):
             kwargs['cert_reqs'] = ssl.CERT_REQUIRED if validate else 
ssl.CERT_NONE
 
         unix_socket = kwargs.pop('unix_socket', None)
-        self._validate_callback = kwargs.pop('validate_callback', 
match_hostname)
+        self._validate_callback = kwargs.pop('validate_callback', 
_match_hostname)
         TSSLBase.__init__(self, False, host, kwargs)
         TSocket.TSocket.__init__(self, host, port, unix_socket)
 
@@ -297,45 +297,6 @@ class TSSLSocket(TSocket.TSocket, TSSLBase):
             except Exception as ex:
                 raise TTransportException(TTransportException.UNKNOWN, str(ex))
 
-    @staticmethod
-    def legacy_validate_callback(self, cert, hostname):
-        """legacy method to validate the peer's SSL certificate, and to check
-        the commonName of the certificate to ensure it matches the hostname we
-        used to make this connection.  Does not support subjectAltName records
-        in certificates.
-
-        raises TTransportException if the certificate fails validation.
-        """
-        if 'subject' not in cert:
-            raise TTransportException(
-                TTransportException.NOT_OPEN,
-                'No SSL certificate found from %s:%s' % (self.host, self.port))
-        fields = cert['subject']
-        for field in fields:
-            # ensure structure we get back is what we expect
-            if not isinstance(field, tuple):
-                continue
-            cert_pair = field[0]
-            if len(cert_pair) < 2:
-                continue
-            cert_key, cert_value = cert_pair[0:2]
-            if cert_key != 'commonName':
-                continue
-            certhost = cert_value
-            # this check should be performed by some sort of Access Manager
-            if certhost == hostname:
-                # success, cert commonName matches desired hostname
-                return
-            else:
-                raise TTransportException(
-                    TTransportException.UNKNOWN,
-                    'Hostname we connected to "%s" doesn\'t match certificate '
-                    'provided commonName "%s"' % (self.host, certhost))
-        raise TTransportException(
-            TTransportException.UNKNOWN,
-            'Could not validate SSL certificate from host "%s".  Cert=%s'
-            % (hostname, cert))
-
 
 class TSSLServerSocket(TSocket.TServerSocket, TSSLBase):
     """SSL implementation of TServerSocket
@@ -381,9 +342,12 @@ class TSSLServerSocket(TSocket.TServerSocket, TSSLBase):
 
         unix_socket = kwargs.pop('unix_socket', None)
         self._validate_callback = \
-            kwargs.pop('validate_callback', match_hostname)
+            kwargs.pop('validate_callback', _match_hostname)
         TSSLBase.__init__(self, True, None, kwargs)
         TSocket.TServerSocket.__init__(self, host, port, unix_socket)
+        if self._should_verify and not _match_has_ipaddress:
+            raise ValueError('Need ipaddress and backports.ssl_match_hostname'
+                             'module to verify client certificate')
 
     def setCertfile(self, certfile):
         """Set or change the server certificate file used to wrap new

http://git-wip-us.apache.org/repos/asf/thrift/blob/f32bae7a/lib/py/src/transport/sslcompat.py
----------------------------------------------------------------------
diff --git a/lib/py/src/transport/sslcompat.py 
b/lib/py/src/transport/sslcompat.py
new file mode 100644
index 0000000..2d778d2
--- /dev/null
+++ b/lib/py/src/transport/sslcompat.py
@@ -0,0 +1,77 @@
+#
+# licensed to the apache software foundation (asf) under one
+# or more contributor license agreements. see the notice file
+# distributed with this work for additional information
+# regarding copyright ownership. the asf licenses this file
+# to you under the apache license, version 2.0 (the
+# "license"); you may not use this file except in compliance
+# with the license. you may obtain a copy of the license at
+#
+#   http://www.apache.org/licenses/license-2.0
+#
+# unless required by applicable law or agreed to in writing,
+# software distributed under the license is distributed on an
+# "as is" basis, without warranties or conditions of any
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from thrift.transport.TTransport import TTransportException
+
+
+def legacy_validate_callback(self, cert, hostname):
+    """legacy method to validate the peer's SSL certificate, and to check
+    the commonName of the certificate to ensure it matches the hostname we
+    used to make this connection.  Does not support subjectAltName records
+    in certificates.
+
+    raises TTransportException if the certificate fails validation.
+    """
+    if 'subject' not in cert:
+        raise TTransportException(
+            TTransportException.NOT_OPEN,
+            'No SSL certificate found from %s:%s' % (self.host, self.port))
+    fields = cert['subject']
+    for field in fields:
+        # ensure structure we get back is what we expect
+        if not isinstance(field, tuple):
+            continue
+        cert_pair = field[0]
+        if len(cert_pair) < 2:
+            continue
+        cert_key, cert_value = cert_pair[0:2]
+        if cert_key != 'commonName':
+            continue
+        certhost = cert_value
+        # this check should be performed by some sort of Access Manager
+        if certhost == hostname:
+            # success, cert commonName matches desired hostname
+            return
+        else:
+            raise TTransportException(
+                TTransportException.UNKNOWN,
+                'Hostname we connected to "%s" doesn\'t match certificate '
+                'provided commonName "%s"' % (self.host, certhost))
+    raise TTransportException(
+        TTransportException.UNKNOWN,
+        'Could not validate SSL certificate from host "%s".  Cert=%s'
+        % (hostname, cert))
+
+
+try:
+    import ipaddress  # noqa
+    _match_has_ipaddress = True
+except ImportError:
+    _match_has_ipaddress = False
+
+try:
+    from backports.ssl_match_hostname import match_hostname
+    _match_hostname = match_hostname
+except ImportError:
+    try:
+        from ssl import match_hostname
+        _match_hostname = match_hostname
+    except ImportError:
+        _match_hostname = legacy_validate_callback
+        _match_has_ipaddress = False

Reply via email to