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