You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@thrift.apache.org by ns...@apache.org on 2016/01/02 18:59:06 UTC

[4/9] thrift git commit: THRIFT-3505 Enhance Python TSSLSocket

THRIFT-3505 Enhance Python TSSLSocket

This closes #760


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

Branch: refs/heads/master
Commit: ad8358664e6aa2de9094ca68c8b626b953b572c0
Parents: b78d781
Author: Nobuaki Sukegawa <ns...@apache.org>
Authored: Wed Dec 23 23:32:09 2015 +0900
Committer: Nobuaki Sukegawa <ns...@apache.org>
Committed: Sun Jan 3 02:39:51 2016 +0900

----------------------------------------------------------------------
 appveyor.yml                        |   2 +-
 lib/py/CMakeLists.txt               |   5 +
 lib/py/Makefile.am                  |   6 +-
 lib/py/src/transport/TSSLSocket.py  | 327 ++++++++++++++++++++++---------
 lib/py/test/_import_local_thrift.py |  13 ++
 lib/py/test/test_sslsocket.py       | 275 ++++++++++++++++++++++++++
 lib/py/test/thrift_json.py          |  25 ++-
 test/keys/README.md                 |   8 +-
 test/keys/client.crt                |  27 +--
 test/keys/client.key                |  38 ++--
 test/keys/client.p12                | Bin 1877 -> 2589 bytes
 test/keys/client.pem                |  74 ++++---
 test/known_failures_Linux.json      |   6 +
 13 files changed, 653 insertions(+), 153 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/appveyor.yml
----------------------------------------------------------------------
diff --git a/appveyor.yml b/appveyor.yml
index 711654e..ee114ae 100755
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -74,6 +74,6 @@ build_script:
 - cmake --build . --config Release
 # TODO: Fix cpack
 # - cpack
-- ctest -C Release -VV -E "(concurrency_test|processor_test|python_test$)"
+- ctest -C Release -VV -E "(concurrency_test|processor_test|python_test$|^Python)"
 
 #TODO make it perfect ;-r

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/lib/py/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/lib/py/CMakeLists.txt b/lib/py/CMakeLists.txt
index ef987b6..7bb91fe 100755
--- a/lib/py/CMakeLists.txt
+++ b/lib/py/CMakeLists.txt
@@ -24,3 +24,8 @@ add_custom_target(python_build ALL
     WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
     COMMENT "Building Python library"
 )
+
+if(BUILD_TESTING)
+    add_test(PythonTestSSLSocket ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_sslsocket.py)
+    add_test(PythonThriftJson ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/thrift_json.py)
+endif()

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/lib/py/Makefile.am
----------------------------------------------------------------------
diff --git a/lib/py/Makefile.am b/lib/py/Makefile.am
index c0db871..5183b9c 100755
--- a/lib/py/Makefile.am
+++ b/lib/py/Makefile.am
@@ -23,7 +23,8 @@ if WITH_PY3
 py3-build:
 	$(PYTHON3) setup.py build
 py3-test: py3-build
-	PYTHONPATH=build/lib $(PYTHON3) test/thrift_json.py
+	$(PYTHON3) test/thrift_json.py
+	$(PYTHON3) test/test_sslsocket.py
 else
 py3-build:
 py3-test:
@@ -43,7 +44,8 @@ clean-local:
 	$(RM) -r build
 
 check-local: all py3-test
-	PYTHONPATH=$(shell ls build/lib* -dr | head -n1) $(PYTHON) test/thrift_json.py
+	$(PYTHON) test/thrift_json.py
+	$(PYTHON) test/test_sslsocket.py
 
 EXTRA_DIST = \
 	CMakeLists.txt \

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/lib/py/src/transport/TSSLSocket.py
----------------------------------------------------------------------
diff --git a/lib/py/src/transport/TSSLSocket.py b/lib/py/src/transport/TSSLSocket.py
index 6ad8d50..9be0912 100644
--- a/lib/py/src/transport/TSSLSocket.py
+++ b/lib/py/src/transport/TSSLSocket.py
@@ -17,73 +17,221 @@
 # under the License.
 #
 
+import logging
 import os
 import socket
 import ssl
+import sys
+import warnings
 
 from thrift.transport import TSocket
 from thrift.transport.TTransport import TTransportException
 
+logger = logging.getLogger(__name__)
+warnings.filterwarnings('default', category=DeprecationWarning, module=__name__)
 
-class TSSLSocket(TSocket.TSocket):
+
+class TSSLBase(object):
+  # SSLContext is not available for Python < 2.7.9
+  _has_ssl_context = sys.hexversion >= 0x020709F0
+
+  # ciphers argument is not available for Python < 2.7.0
+  _has_ciphers = sys.hexversion >= 0x020700F0
+
+  # For pythoon >= 2.7.9, use latest TLS that both client and server supports.
+  # SSL 2.0 and 3.0 are disabled via ssl.OP_NO_SSLv2 and ssl.OP_NO_SSLv3.
+  # For pythoon < 2.7.9, use TLS 1.0 since TLSv1_X nare OP_NO_SSLvX are unavailable.
+  _default_protocol = ssl.PROTOCOL_SSLv23 if _has_ssl_context else ssl.PROTOCOL_TLSv1
+
+  def _init_context(self, ssl_version):
+    if self._has_ssl_context:
+      self._context = ssl.SSLContext(ssl_version)
+      if self._context.protocol == ssl.PROTOCOL_SSLv23:
+        self._context.options |= ssl.OP_NO_SSLv2
+        self._context.options |= ssl.OP_NO_SSLv3
+    else:
+      self._context = None
+      self._ssl_version = ssl_version
+
+  @property
+  def ssl_version(self):
+    if self._has_ssl_context:
+      return self.ssl_context.protocol
+    else:
+      return self._ssl_version
+
+  @property
+  def ssl_context(self):
+    return self._context
+
+  SSL_VERSION = _default_protocol
   """
-  SSL implementation of client-side TSocket
+  Default SSL version.
+  For backword compatibility, it can be modified.
+  Use __init__ keywoard argument "ssl_version" instead.
+  """
+
+  def _deprecated_arg(self, args, kwargs, pos, key):
+    if len(args) <= pos:
+      return
+    real_pos = pos + 3
+    warnings.warn(
+        '%dth positional argument is deprecated. Use keyward argument insteand.' % real_pos,
+        DeprecationWarning)
+    if key in kwargs:
+      raise TypeError('Duplicate argument: %dth argument and %s keyward argument.', (real_pos, key))
+    kwargs[key] = args[pos]
+
+  def _unix_socket_arg(self, host, port, args, kwargs):
+    key = 'unix_socket'
+    if host is None and port is None and len(args) == 1 and key not in kwargs:
+      kwargs[key] = args[0]
+      return True
+    return False
+
+  def __getattr__(self, key):
+    if key == 'SSL_VERSION':
+      warnings.warn('Use ssl_version attribute instead.', DeprecationWarning)
+      return self.ssl_version
+
+  def __init__(self, server_side, host, ssl_opts):
+    self._server_side = server_side
+    if TSSLBase.SSL_VERSION != self._default_protocol:
+      warnings.warn('SSL_VERSION is deprecated. Use ssl_version keyward argument instead.', DeprecationWarning)
+    self._context = ssl_opts.pop('ssl_context', None)
+    self._server_hostname = None
+    if not self._server_side:
+      self._server_hostname = ssl_opts.pop('server_hostname', host)
+    if self._context:
+      self._custom_context = True
+      if ssl_opts:
+        raise ValueError('Incompatible arguments: ssl_context and %s' % ' '.join(ssl_opts.keys()))
+      if not self._has_ssl_context:
+        raise ValueError('ssl_context is not available for this version of Python')
+    else:
+      self._custom_context = False
+      ssl_version = ssl_opts.pop('ssl_version', TSSLBase.SSL_VERSION)
+      self._init_context(ssl_version)
+      self.cert_reqs = ssl_opts.pop('cert_reqs', ssl.CERT_REQUIRED)
+      self.ca_certs = ssl_opts.pop('ca_certs', None)
+      self.keyfile = ssl_opts.pop('keyfile', None)
+      self.certfile = ssl_opts.pop('certfile', None)
+      self.ciphers = ssl_opts.pop('ciphers', None)
+
+      if ssl_opts:
+        raise ValueError('Unknown keyword arguments: ', ' '.join(ssl_opts.keys()))
+
+      if self.cert_reqs != ssl.CERT_NONE:
+        if not self.ca_certs:
+          raise ValueError('ca_certs is needed when cert_reqs is not ssl.CERT_NONE')
+        if not os.access(self.ca_certs, os.R_OK):
+          raise IOError('Certificate Authority ca_certs file "%s" '
+                        'is not readable, cannot validate SSL '
+                        'certificates.' % (self.ca_certs))
+
+  @property
+  def certfile(self):
+    return self._certfile
+
+  @certfile.setter
+  def certfile(self, certfile):
+    if self._server_side and not certfile:
+      raise ValueError('certfile is needed for server-side')
+    if certfile and not os.access(certfile, os.R_OK):
+      raise IOError('No such certfile found: %s' % (certfile))
+    self._certfile = certfile
+
+  def _wrap_socket(self, sock):
+    if self._has_ssl_context:
+      if not self._custom_context:
+        self.ssl_context.verify_mode = self.cert_reqs
+        if self.certfile:
+          self.ssl_context.load_cert_chain(self.certfile, self.keyfile)
+        if self.ciphers:
+          self.ssl_context.set_ciphers(self.ciphers)
+        if self.ca_certs:
+          self.ssl_context.load_verify_locations(self.ca_certs)
+      return self.ssl_context.wrap_socket(sock, server_side=self._server_side,
+                                          server_hostname=self._server_hostname)
+    else:
+      ssl_opts = {
+        'ssl_version': self._ssl_version,
+        'server_side': self._server_side,
+        'ca_certs': self.ca_certs,
+        'keyfile': self.keyfile,
+        'certfile': self.certfile,
+        'cert_reqs': self.cert_reqs,
+      }
+      if self.ciphers:
+        if self._has_ciphers:
+          ssl_opts['ciphers'] = self.ciphers
+        else:
+          logger.warning('ciphers is specified but ignored due to old Python version')
+      return ssl.wrap_socket(sock, **ssl_opts)
+
+
+class TSSLSocket(TSocket.TSocket, TSSLBase):
+  """
+  SSL implementation of TSocket
 
   This class creates outbound sockets wrapped using the
   python standard ssl module for encrypted connections.
-
-  The protocol used is set using the class variable
-  SSL_VERSION, which must be one of ssl.PROTOCOL_* and
-  defaults to  ssl.PROTOCOL_TLSv1 for greatest security.
   """
-  SSL_VERSION = ssl.PROTOCOL_TLSv1
-
-  def __init__(self,
-               host='localhost',
-               port=9090,
-               validate=True,
-               ca_certs=None,
-               keyfile=None,
-               certfile=None,
-               unix_socket=None,
-               ciphers=None):
-    """Create SSL TSocket
-
-    @param validate: Set to False to disable SSL certificate validation
-    @type validate: bool
-    @param ca_certs: Filename to the Certificate Authority pem file, possibly a
-    file downloaded from: http://curl.haxx.se/ca/cacert.pem  This is passed to
-    the ssl_wrap function as the 'ca_certs' parameter.
-    @type ca_certs: str
-    @param keyfile: The private key
-    @type keyfile: str
-    @param certfile: The cert file
-    @type certfile: str
-    @param ciphers: The cipher suites to allow. This is passed to
-                    the ssl_wrap function as the 'ciphers' parameter.
-    @type ciphers: str
-    
-    Raises an IOError exception if validate is True and the ca_certs file is
-    None, not present or unreadable.
+
+  # New signature
+  # def __init__(self, host='localhost', port=9090, unix_socket=None, **ssl_args):
+  # Deprecated signature
+  # def __init__(self, host='localhost', port=9090, validate=True, ca_certs=None, keyfile=None, certfile=None, unix_socket=None, ciphers=None):
+  def __init__(self, host='localhost', port=9090, *args, **kwargs):
+    """Positional arguments: ``host``, ``port``, ``unix_socket``
+
+    Keyword arguments: ``keyfile``, ``certfile``, ``cert_reqs``, ``ssl_version``,
+                       ``ca_certs``, ``ciphers`` (Python 2.7.0 or later),
+                       ``server_hostname`` (Python 2.7.9 or later)
+    Passed to ssl.wrap_socket. See ssl.wrap_socket documentation.
+
+    Alternative keywoard arguments: (Python 2.7.9 or later)
+      ``ssl_context``: ssl.SSLContext to be used for SSLContext.wrap_socket
+      ``server_hostname``: Passed to SSLContext.wrap_socket
     """
-    self.validate = validate
     self.is_valid = False
     self.peercert = None
-    if not validate:
-      self.cert_reqs = ssl.CERT_NONE
-    else:
-      self.cert_reqs = ssl.CERT_REQUIRED
-    self.ca_certs = ca_certs
-    self.keyfile = keyfile
-    self.certfile = certfile
-    self.ciphers = ciphers
-    if validate:
-      if ca_certs is None or not os.access(ca_certs, os.R_OK):
-        raise IOError('Certificate Authority ca_certs file "%s" '
-                      'is not readable, cannot validate SSL '
-                      'certificates.' % (ca_certs))
+
+    if args:
+      if len(args) > 6:
+        raise TypeError('Too many positional argument')
+      if not self._unix_socket_arg(host, port, args, kwargs):
+        self._deprecated_arg(args, kwargs, 0, 'validate')
+      self._deprecated_arg(args, kwargs, 1, 'ca_certs')
+      self._deprecated_arg(args, kwargs, 2, 'keyfile')
+      self._deprecated_arg(args, kwargs, 3, 'certfile')
+      self._deprecated_arg(args, kwargs, 4, 'unix_socket')
+      self._deprecated_arg(args, kwargs, 5, 'ciphers')
+
+    validate = kwargs.pop('validate', None)
+    if validate is not None:
+      cert_reqs_name = 'CERT_REQUIRED' if validate else 'CERT_NONE'
+      warnings.warn(
+          'validate is deprecated. Use cert_reqs=ssl.%s instead' % cert_reqs_name,
+          DeprecationWarning)
+      if 'cert_reqs' in kwargs:
+        raise TypeError('Cannot specify both validate and cert_reqs')
+      kwargs['cert_reqs'] = ssl.CERT_REQUIRED if validate else ssl.CERT_NONE
+
+    unix_socket = kwargs.pop('unix_socket', None)
+    TSSLBase.__init__(self, False, host, kwargs)
     TSocket.TSocket.__init__(self, host, port, unix_socket)
 
+  @property
+  def validate(self):
+    warnings.warn('Use cert_reqs instead', DeprecationWarning)
+    return self.cert_reqs != ssl.CERT_NONE
+
+  @validate.setter
+  def validate(self, value):
+    warnings.warn('Use cert_reqs instead', DeprecationWarning)
+    self.cert_reqs = ssl.CERT_REQUIRED if value else ssl.CERT_NONE
+
   def open(self):
     try:
       res0 = self._resolveAddr()
@@ -91,29 +239,24 @@ class TSSLSocket(TSocket.TSocket):
         sock_family, sock_type = res[0:2]
         ip_port = res[4]
         plain_sock = socket.socket(sock_family, sock_type)
-        self.handle = ssl.wrap_socket(plain_sock,
-                                      ssl_version=self.SSL_VERSION,
-                                      do_handshake_on_connect=True,
-                                      ca_certs=self.ca_certs,
-                                      keyfile=self.keyfile,
-                                      certfile=self.certfile,
-                                      cert_reqs=self.cert_reqs,
-                                      ciphers=self.ciphers)
+        self.handle = self._wrap_socket(plain_sock)
         self.handle.settimeout(self._timeout)
         try:
           self.handle.connect(ip_port)
         except socket.error as e:
           if res is not res0[-1]:
+            logger.warning('Error while connecting with %s. Trying next one.', ip_port, exc_info=True)
             continue
           else:
-            raise e
+            raise
         break
     except socket.error as e:
       if self._unix_socket:
         message = 'Could not connect to secure socket %s: %s' \
-                % (self._unix_socket, e)
+                  % (self._unix_socket, e)
       else:
         message = 'Could not connect to %s:%d: %s' % (self.host, self.port, e)
+      logger.error('Error while connecting with %s.', ip_port, exc_info=True)
       raise TTransportException(type=TTransportException.NOT_OPEN,
                                 message=message)
     if self.validate:
@@ -161,38 +304,46 @@ class TSSLSocket(TSocket.TSocket):
               'host "%s".  Cert=%s' % (self.host, cert))
 
 
-class TSSLServerSocket(TSocket.TServerSocket):
+class TSSLServerSocket(TSocket.TServerSocket, TSSLBase):
   """SSL implementation of TServerSocket
 
   This uses the ssl module's wrap_socket() method to provide SSL
   negotiated encryption.
   """
-  SSL_VERSION = ssl.PROTOCOL_TLSv1
 
-  def __init__(self,
-               host=None,
-               port=9090,
-               certfile='cert.pem',
-               unix_socket=None,
-               ciphers=None):
-    """Initialize a TSSLServerSocket
+  # New signature
+  # def __init__(self, host='localhost', port=9090, unix_socket=None, **ssl_args):
+  # Deprecated signature
+  # def __init__(self, host=None, port=9090, certfile='cert.pem', unix_socket=None, ciphers=None):
+  def __init__(self, host=None, port=9090, *args, **kwargs):
+    """Positional arguments: ``host``, ``port``, ``unix_socket``
 
-    @param certfile: filename of the server certificate, defaults to cert.pem
-    @type certfile: str
-    @param host: The hostname or IP to bind the listen socket to,
-                 i.e. 'localhost' for only allowing local network connections.
-                 Pass None to bind to all interfaces.
-    @type host: str
-    @param port: The port to listen on for inbound connections.
-    @type port: int
-    @param ciphers: The cipher suites to allow. This is passed to
-                    the ssl_wrap function as the 'ciphers' parameter.
-    @type ciphers: str
+    Keyword arguments: ``keyfile``, ``certfile``, ``cert_reqs``, ``ssl_version``,
+                       ``ca_certs``, ``ciphers`` (Python 2.7.0 or later)
+    See ssl.wrap_socket documentation.
 
+    Alternative keywoard arguments: (Python 2.7.9 or later)
+      ``ssl_context``: ssl.SSLContext to be used for SSLContext.wrap_socket
+      ``server_hostname``: Passed to SSLContext.wrap_socket
     """
-    self.setCertfile(certfile)
-    TSocket.TServerSocket.__init__(self, host, port)
-    self.ciphers = ciphers
+    if args:
+      if len(args) > 3:
+        raise TypeError('Too many positional argument')
+      if not self._unix_socket_arg(host, port, args, kwargs):
+        self._deprecated_arg(args, kwargs, 0, 'certfile')
+      self._deprecated_arg(args, kwargs, 1, 'unix_socket')
+      self._deprecated_arg(args, kwargs, 2, 'ciphers')
+
+    if 'ssl_context' not in kwargs:
+      # Preserve existing behaviors for default values
+      if 'cert_reqs' not in kwargs:
+        kwargs['cert_reqs'] = ssl.CERT_NONE
+      if'certfile' not in kwargs:
+        kwargs['certfile'] = 'cert.pem'
+
+    unix_socket = kwargs.pop('unix_socket', None)
+    TSSLBase.__init__(self, True, None, kwargs)
+    TSocket.TServerSocket.__init__(self, host, port, unix_socket)
 
   def setCertfile(self, certfile):
     """Set or change the server certificate file used to wrap new connections.
@@ -203,20 +354,18 @@ class TSSLServerSocket(TSocket.TServerSocket):
 
     Raises an IOError exception if the certfile is not present or unreadable.
     """
-    if not os.access(certfile, os.R_OK):
-      raise IOError('No such certfile found: %s' % (certfile))
+    warnings.warn('Use certfile property instead.', DeprecationWarning)
     self.certfile = certfile
 
   def accept(self):
     plain_client, addr = self.handle.accept()
     try:
-      client = ssl.wrap_socket(plain_client, certfile=self.certfile,
-                      server_side=True, ssl_version=self.SSL_VERSION,
-                      ciphers=self.ciphers)
-    except ssl.SSLError as ssl_exc:
+      client = self._wrap_socket(plain_client)
+    except ssl.SSLError:
+      logger.error('Error while accepting from %s', addr, exc_info=True)
       # failed handshake/ssl wrap, close socket to client
       plain_client.close()
-      # raise ssl_exc
+      # raise
       # We can't raise the exception, because it kills most TServer derived
       # serve() methods.
       # Instead, return None, and let the TServer instance deal with it in

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/lib/py/test/_import_local_thrift.py
----------------------------------------------------------------------
diff --git a/lib/py/test/_import_local_thrift.py b/lib/py/test/_import_local_thrift.py
new file mode 100644
index 0000000..30c1abc
--- /dev/null
+++ b/lib/py/test/_import_local_thrift.py
@@ -0,0 +1,13 @@
+import os
+import sys
+
+
+SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(SCRIPT_DIR)))
+
+if sys.version_info[0] == 2:
+  import glob
+  libdir = glob.glob(os.path.join(ROOT_DIR, 'lib', 'py', 'build', 'lib.*'))[0]
+  sys.path.insert(0, libdir)
+else:
+  sys.path.insert(0, os.path.join(ROOT_DIR, 'lib', 'py', 'build', 'lib'))

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/lib/py/test/test_sslsocket.py
----------------------------------------------------------------------
diff --git a/lib/py/test/test_sslsocket.py b/lib/py/test/test_sslsocket.py
new file mode 100644
index 0000000..bd2a70a
--- /dev/null
+++ b/lib/py/test/test_sslsocket.py
@@ -0,0 +1,275 @@
+#
+# 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.
+#
+
+import os
+import platform
+import select
+import ssl
+import sys
+import threading
+import time
+import unittest
+import warnings
+
+import _import_local_thrift
+from thrift.transport.TSSLSocket import TSSLSocket, TSSLServerSocket
+
+SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(SCRIPT_DIR)))
+SERVER_PEM = os.path.join(ROOT_DIR, 'test', 'keys', 'server.pem')
+SERVER_CERT = os.path.join(ROOT_DIR, 'test', 'keys', 'server.crt')
+SERVER_KEY = os.path.join(ROOT_DIR, 'test', 'keys', 'server.key')
+CLIENT_CERT = os.path.join(ROOT_DIR, 'test', 'keys', 'client.crt')
+CLIENT_KEY = os.path.join(ROOT_DIR, 'test', 'keys', 'client.key')
+
+TEST_PORT = 23458
+TEST_ADDR = '/tmp/.thrift.domain.sock.%d' % TEST_PORT
+CONNECT_TIMEOUT = 10.0
+TEST_CIPHERS = 'DES-CBC3-SHA'
+
+
+class ServerAcceptor(threading.Thread):
+  def __init__(self, server):
+    super(ServerAcceptor, self).__init__()
+    self._server = server
+    self.client = None
+
+  def run(self):
+    self._server.listen()
+    self.client = self._server.accept()
+
+
+# Python 2.6 compat
+class AssertRaises(object):
+  def __init__(self, expected):
+    self._expected = expected
+
+  def __enter__(self):
+    pass
+
+  def __exit__(self, exc_type, exc_value, traceback):
+    if not exc_type or not issubclass(exc_type, self._expected):
+      raise Exception('fail')
+    return True
+
+
+class TSSLSocketTest(unittest.TestCase):
+  def _assert_connection_failure(self, server, client):
+    try:
+      acc = ServerAcceptor(server)
+      acc.start()
+      time.sleep(0.15)
+      client.setTimeout(CONNECT_TIMEOUT)
+      with self._assert_raises(Exception):
+        client.open()
+        select.select([], [client.handle], [], CONNECT_TIMEOUT)
+      # self.assertIsNone(acc.client)
+      self.assertTrue(acc.client is None)
+    finally:
+      server.close()
+      client.close()
+
+  def _assert_raises(self, exc):
+    if sys.hexversion >= 0x020700F0:
+      return self.assertRaises(exc)
+    else:
+      return AssertRaises(exc)
+
+  def _assert_connection_success(self, server, client):
+    try:
+      acc = ServerAcceptor(server)
+      acc.start()
+      time.sleep(0.15)
+      client.setTimeout(CONNECT_TIMEOUT)
+      client.open()
+      select.select([], [client.handle], [], CONNECT_TIMEOUT)
+      # self.assertIsNotNone(acc.client)
+      self.assertTrue(acc.client is not None)
+    finally:
+      server.close()
+      client.close()
+
+  # deprecated feature
+  def test_deprecation(self):
+    with warnings.catch_warnings(record=True) as w:
+      warnings.filterwarnings('always', category=DeprecationWarning, module='thrift.*SSL.*')
+      TSSLSocket('localhost', TEST_PORT, validate=True, ca_certs=SERVER_CERT)
+      self.assertEqual(len(w), 1)
+
+    with warnings.catch_warnings(record=True) as w:
+      warnings.filterwarnings('always', category=DeprecationWarning, module='thrift.*SSL.*')
+      # Deprecated signature
+      # def __init__(self, host='localhost', port=9090, validate=True, ca_certs=None, keyfile=None, certfile=None, unix_socket=None, ciphers=None):
+      client = TSSLSocket('localhost', TEST_PORT, True, SERVER_CERT, CLIENT_KEY, CLIENT_CERT, None, TEST_CIPHERS)
+      self.assertEqual(len(w), 7)
+
+    with warnings.catch_warnings(record=True) as w:
+      warnings.filterwarnings('always', category=DeprecationWarning, module='thrift.*SSL.*')
+      # Deprecated signature
+      # def __init__(self, host=None, port=9090, certfile='cert.pem', unix_socket=None, ciphers=None):
+      server = TSSLServerSocket(None, TEST_PORT, SERVER_PEM, None, TEST_CIPHERS)
+      self.assertEqual(len(w), 3)
+
+    self._assert_connection_success(server, client)
+
+  # deprecated feature
+  def test_set_cert_reqs_by_validate(self):
+    c1 = TSSLSocket('localhost', TEST_PORT, validate=True, ca_certs=SERVER_CERT)
+    self.assertEqual(c1.cert_reqs, ssl.CERT_REQUIRED)
+
+    c1 = TSSLSocket('localhost', TEST_PORT, validate=False)
+    self.assertEqual(c1.cert_reqs, ssl.CERT_NONE)
+
+  # deprecated feature
+  def test_set_validate_by_cert_reqs(self):
+    c1 = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_NONE)
+    self.assertFalse(c1.validate)
+
+    c2 = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_REQUIRED, ca_certs=SERVER_CERT)
+    self.assertTrue(c2.validate)
+
+    c3 = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_OPTIONAL, ca_certs=SERVER_CERT)
+    self.assertTrue(c3.validate)
+
+  def test_unix_domain_socket(self):
+    if platform.system() == 'Windows':
+      print('skipping test_unix_domain_socket')
+      return
+    server = TSSLServerSocket(unix_socket=TEST_ADDR, keyfile=SERVER_KEY, certfile=SERVER_CERT)
+    client = TSSLSocket(None, None, TEST_ADDR, cert_reqs=ssl.CERT_NONE)
+    self._assert_connection_success(server, client)
+
+  def test_server_cert(self):
+    server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT)
+    client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_REQUIRED, ca_certs=SERVER_CERT)
+    self._assert_connection_success(server, client)
+
+    server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT)
+    # server cert on in ca_certs
+    client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_REQUIRED, ca_certs=CLIENT_CERT)
+    self._assert_connection_failure(server, client)
+
+    server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT)
+    client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_NONE)
+    self._assert_connection_success(server, client)
+
+  def test_set_server_cert(self):
+    server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=CLIENT_CERT)
+    with self._assert_raises(Exception):
+      server.certfile = 'foo'
+    with self._assert_raises(Exception):
+      server.certfile = None
+    server.certfile = SERVER_CERT
+    client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_REQUIRED, ca_certs=SERVER_CERT)
+    self._assert_connection_success(server, client)
+
+  def test_client_cert(self):
+    server = TSSLServerSocket(
+        port=TEST_PORT, cert_reqs=ssl.CERT_REQUIRED, keyfile=SERVER_KEY,
+        certfile=SERVER_CERT, ca_certs=CLIENT_CERT)
+    client = TSSLSocket('localhost', TEST_PORT, cert_reqs=ssl.CERT_NONE, certfile=CLIENT_CERT, keyfile=CLIENT_KEY)
+    self._assert_connection_success(server, client)
+
+  def test_ciphers(self):
+    server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT, ciphers=TEST_CIPHERS)
+    client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT, ciphers=TEST_CIPHERS)
+    self._assert_connection_success(server, client)
+
+    if not TSSLSocket._has_ciphers:
+      # unittest.skip is not available for Python 2.6
+      print('skipping test_ciphers')
+      return
+    server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT)
+    client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT, ciphers='NULL')
+    self._assert_connection_failure(server, client)
+
+    server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT, ciphers=TEST_CIPHERS)
+    client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT, ciphers='NULL')
+    self._assert_connection_failure(server, client)
+
+  def test_ssl2_and_ssl3_disabled(self):
+    if not hasattr(ssl, 'PROTOCOL_SSLv3'):
+      print('PROTOCOL_SSLv3 is not available')
+    else:
+      server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT)
+      client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT, ssl_version=ssl.PROTOCOL_SSLv3)
+      self._assert_connection_failure(server, client)
+
+      server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT, ssl_version=ssl.PROTOCOL_SSLv3)
+      client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT)
+      self._assert_connection_failure(server, client)
+
+    if not hasattr(ssl, 'PROTOCOL_SSLv2'):
+      print('PROTOCOL_SSLv2 is not available')
+    else:
+      server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT)
+      client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT, ssl_version=ssl.PROTOCOL_SSLv2)
+      self._assert_connection_failure(server, client)
+
+      server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT, ssl_version=ssl.PROTOCOL_SSLv2)
+      client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT)
+      self._assert_connection_failure(server, client)
+
+  def test_newer_tls(self):
+    if not TSSLSocket._has_ssl_context:
+      # unittest.skip is not available for Python 2.6
+      print('skipping test_newer_tls')
+      return
+    if not hasattr(ssl, 'PROTOCOL_TLSv1_2'):
+      print('PROTOCOL_TLSv1_2 is not available')
+    else:
+      server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT, ssl_version=ssl.PROTOCOL_TLSv1_2)
+      client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT, ssl_version=ssl.PROTOCOL_TLSv1_2)
+      self._assert_connection_success(server, client)
+
+    if not hasattr(ssl, 'PROTOCOL_TLSv1_1'):
+      print('PROTOCOL_TLSv1_1 is not available')
+    else:
+      server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT, ssl_version=ssl.PROTOCOL_TLSv1_1)
+      client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT, ssl_version=ssl.PROTOCOL_TLSv1_1)
+      self._assert_connection_success(server, client)
+
+    if not hasattr(ssl, 'PROTOCOL_TLSv1_1') or not hasattr(ssl, 'PROTOCOL_TLSv1_2'):
+      print('PROTOCOL_TLSv1_1 and/or PROTOCOL_TLSv1_2 is not available')
+    else:
+      server = TSSLServerSocket(port=TEST_PORT, keyfile=SERVER_KEY, certfile=SERVER_CERT, ssl_version=ssl.PROTOCOL_TLSv1_2)
+      client = TSSLSocket('localhost', TEST_PORT, ca_certs=SERVER_CERT, ssl_version=ssl.PROTOCOL_TLSv1_1)
+      self._assert_connection_failure(server, client)
+
+  def test_ssl_context(self):
+    if not TSSLSocket._has_ssl_context:
+      # unittest.skip is not available for Python 2.6
+      print('skipping test_ssl_context')
+      return
+    server_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+    server_context.load_cert_chain(SERVER_CERT, SERVER_KEY)
+    server_context.load_verify_locations(CLIENT_CERT)
+
+    client_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
+    client_context.load_cert_chain(CLIENT_CERT, CLIENT_KEY)
+    client_context.load_verify_locations(SERVER_CERT)
+
+    server = TSSLServerSocket(port=TEST_PORT, ssl_context=server_context)
+    client = TSSLSocket('localhost', TEST_PORT, ssl_context=client_context)
+    self._assert_connection_success(server, client)
+
+if __name__ == '__main__':
+  # import logging
+  # logging.basicConfig(level=logging.DEBUG)
+  unittest.main()

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/lib/py/test/thrift_json.py
----------------------------------------------------------------------
diff --git a/lib/py/test/thrift_json.py b/lib/py/test/thrift_json.py
index 6d6c8fa..e60aaba 100644
--- a/lib/py/test/thrift_json.py
+++ b/lib/py/test/thrift_json.py
@@ -1,10 +1,29 @@
-from thrift import Thrift
-from thrift.protocol.TJSONProtocol import TJSONProtocol
-from thrift.transport import TTransport
+#
+# 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.
+#
 
 import sys
 import unittest
 
+import _import_local_thrift
+from thrift.protocol.TJSONProtocol import TJSONProtocol
+from thrift.transport import TTransport
+
 #
 # In order to run the test under Windows. We need to create symbolic link
 # name 'thrift' to '../src' folder by using:

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/test/keys/README.md
----------------------------------------------------------------------
diff --git a/test/keys/README.md b/test/keys/README.md
index 53fe0cc..15faa51 100755
--- a/test/keys/README.md
+++ b/test/keys/README.md
@@ -26,7 +26,7 @@ we use the following parameters for test key and certificate creation
     openssl x509 -in server.crt -text > CA.pem
     cat server.crt server.key > server.pem
 
-Export password is **thrift**
+Export password is "thrift" without the quotes
 
     openssl pkcs12 -export -clcerts -in server.crt -inkey server.key -out server.p12
 
@@ -40,9 +40,9 @@ create a signing request:
 
 sign the client certificate with the server.key
 
-    openssl x509 -req -days 365 -in client.csr -CA CA.pem -CAkey server.key -set_serial 01 -out client.crt
+    openssl x509 -req -days 3000 -in client.csr -CA CA.pem -CAkey server.key -set_serial 01 -out client.crt
 
-export certificate in PKCS12 format (Export password is **thrift**)
+export certificate in PKCS12 format (Export password is "thrift" without the quotes)
 
     openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
 
@@ -52,7 +52,7 @@ export certificate in PEM format for OpenSSL usage
 
 
 ## Java key and certificate import
-Java Test Environment uses key and trust store password **thrift**
+Java Test Environment uses key and trust store password "thrift" without the quotes
 
 list keystore entries
 

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/test/keys/client.crt
----------------------------------------------------------------------
diff --git a/test/keys/client.crt b/test/keys/client.crt
index 64526f6..80a9ad0 100644
--- a/test/keys/client.crt
+++ b/test/keys/client.crt
@@ -1,20 +1,23 @@
 -----BEGIN CERTIFICATE-----
-MIIDVDCCAjwCAQEwDQYJKoZIhvcNAQEFBQAwgbExCzAJBgNVBAYTAlVTMREwDwYD
+MIID2DCCAsACAQEwDQYJKoZIhvcNAQELBQAwgbExCzAJBgNVBAYTAlVTMREwDwYD
 VQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0IEhpbGwxJzAlBgNVBAoMHlRo
 ZSBBcGFjaGUgU29mdHdhcmUgRm91bmRhdGlvbjEWMBQGA1UECwwNQXBhY2hlIFRo
 cmlmdDESMBAGA1UEAwwJbG9jYWxob3N0MSQwIgYJKoZIhvcNAQkBFhVkZXZAdGhy
-aWZ0LmFwYWNoZS5vcmcwHhcNMTQwNDA3MTkwMDMzWhcNMTUwNDA3MTkwMDMzWjCB
+aWZ0LmFwYWNoZS5vcmcwHhcNMTUxMjIzMDcwNzUwWhcNMjQwMzEwMDcwNzUwWjCB
 sTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1hcnlsYW5kMRQwEgYDVQQHDAtGb3Jl
 c3QgSGlsbDEnMCUGA1UECgweVGhlIEFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW9u
 MRYwFAYDVQQLDA1BcGFjaGUgVGhyaWZ0MRIwEAYDVQQDDAlsb2NhbGhvc3QxJDAi
-BgkqhkiG9w0BCQEWFWRldkB0aHJpZnQuYXBhY2hlLm9yZzCBnzANBgkqhkiG9w0B
-AQEFAAOBjQAwgYkCgYEArrM2HiTf5LT1Qh1JAALWUlJxVJNc1uC8//wZIW8Ekk6z
-H2XkrAOW8Cs7rVfz6Q+x00q7xSH825v9RL6pv4l7sPDSGK5lvc+WkTxDpiR2EjIm
-uWStUzCRq7EXhV50pUno6MFABVtqpRP87TiE1l7Yb8S33v+gAVdsrpJewYIDwWcC
-AwEAATANBgkqhkiG9w0BAQUFAAOCAQEAbGjHLamDm1FQpgatYiZ/ic7Z8DFB+CJo
-FcZH4hww27BD/WpQLsj6T1540B35hsmZ73yev4xgLybc/SEIducT9BHyc1DrDZtf
-CFeSq6OOJu/1pJZ9m/d0i+sBJaWg5w1yT8+aEKJaWYfF+C9jZ6+3+I9agID5OplE
-Wwwzg3xXllz3jfmtNlc0f+hE1/XLWFE2nY+5cBhlxReWH3HAhU/qZL9n/WdxCjHd
-NyeWxlDlmzc2+uOeVF5sIGzFOj/qjGxc+UyUXaaEuSvh7j3rvYlZtnhvhJ+tMkoR
-Kbxl1VUYxx+jzfhBy+bKu5uGZB3F1qtyY9fI5DQut75nNbueQPG+qw==
+BgkqhkiG9w0BCQEWFWRldkB0aHJpZnQuYXBhY2hlLm9yZzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALl/bJhg17Pu1t785JIuwlh7e2E51eoWFvuxMWfH
+dsUAVqDaOz99O5sv8pgWKXrkPBttE98T+Mx/Pwz5JHs4Qcre3A5adm6cdab0AKug
+eVG1PvHrWzV4aheg4KUfVSiMz8BG2K3Q1VGvZkgMRa/Q409FKjU6Z4D9vBG4Pas3
+PN5FxnAL6P70yWCEr2Vu/tWJlCN+qrRDfSaE2hqmT1irxaoPa61GvuRVsGQ3TeoE
+mGfENtvFqC6qQWuSCbG5aI47Rv66pqoWWxqkrcolNc7+vhx6rW+wdFO3IlqIT8IG
+sdQVp+Y4ir/dGp2ejzqGZCremPJ1uEKjLMpIukdg25ymHC0CAwEAATANBgkqhkiG
+9w0BAQsFAAOCAQEABcRdwc9mdY9zOvTixtThHrciVvqwp6RaiQqMEVomozYilZDR
+J90M3H6KnlADJbCv0FXujsaApB53awq5I7sObAYWFhDJZ9LKlw0lDF7KchLXiAlk
+XVyvL2nJjuJcxPCFPWktwrw5bTguRCBzG12Hzikn+xs6uPur7VQYM33jbXEda5PS
+UEqvAVWaONJCX8MDYOscVoJF0ESyI+fN92ipFaXR7fGajod/rc0AfeN2GVMZETve
+21pkUyDwwCGA44uvNUCd1e32NxE3ah9UApCRn5sGJ64R6urpFTXT3IALF3kaOO7U
+VsMhFrUiZ7+Bw5nIeiK0QOF6Eipj+bJvrLZpSg==
 -----END CERTIFICATE-----

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/test/keys/client.key
----------------------------------------------------------------------
diff --git a/test/keys/client.key b/test/keys/client.key
index 703e045..25dcfd7 100644
--- a/test/keys/client.key
+++ b/test/keys/client.key
@@ -1,15 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/test/keys/client.p12
----------------------------------------------------------------------
diff --git a/test/keys/client.p12 b/test/keys/client.p12
index a7d95df..5d2669c 100644
Binary files a/test/keys/client.p12 and b/test/keys/client.p12 differ

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/test/keys/client.pem
----------------------------------------------------------------------
diff --git a/test/keys/client.pem b/test/keys/client.pem
index ee5bc61..66ef626 100644
--- a/test/keys/client.pem
+++ b/test/keys/client.pem
@@ -1,44 +1,60 @@
 Bag Attributes
-    localKeyID: 05 3C 6A 9D 6D EC A0 FA 2F AE 41 32 0D 24 3A 21 34 F6 08 15 
+    localKeyID: 39 EC 3D 5B 28 17 DA DD 09 7A 62 68 D5 44 1F C7 E2 F8 E0 CD 
 subject=/C=US/ST=Maryland/L=Forest Hill/O=The Apache Software Foundation/OU=Apache Thrift/CN=localhost/emailAddress=dev@thrift.apache.org
 issuer=/C=US/ST=Maryland/L=Forest Hill/O=The Apache Software Foundation/OU=Apache Thrift/CN=localhost/emailAddress=dev@thrift.apache.org
 -----BEGIN CERTIFICATE-----
-MIIDVDCCAjwCAQEwDQYJKoZIhvcNAQEFBQAwgbExCzAJBgNVBAYTAlVTMREwDwYD
+MIID2DCCAsACAQEwDQYJKoZIhvcNAQELBQAwgbExCzAJBgNVBAYTAlVTMREwDwYD
 VQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0IEhpbGwxJzAlBgNVBAoMHlRo
 ZSBBcGFjaGUgU29mdHdhcmUgRm91bmRhdGlvbjEWMBQGA1UECwwNQXBhY2hlIFRo
 cmlmdDESMBAGA1UEAwwJbG9jYWxob3N0MSQwIgYJKoZIhvcNAQkBFhVkZXZAdGhy
-aWZ0LmFwYWNoZS5vcmcwHhcNMTQwNDA3MTkwMDMzWhcNMTUwNDA3MTkwMDMzWjCB
+aWZ0LmFwYWNoZS5vcmcwHhcNMTUxMjIzMDcwNzUwWhcNMjQwMzEwMDcwNzUwWjCB
 sTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1hcnlsYW5kMRQwEgYDVQQHDAtGb3Jl
 c3QgSGlsbDEnMCUGA1UECgweVGhlIEFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW9u
 MRYwFAYDVQQLDA1BcGFjaGUgVGhyaWZ0MRIwEAYDVQQDDAlsb2NhbGhvc3QxJDAi
-BgkqhkiG9w0BCQEWFWRldkB0aHJpZnQuYXBhY2hlLm9yZzCBnzANBgkqhkiG9w0B
-AQEFAAOBjQAwgYkCgYEArrM2HiTf5LT1Qh1JAALWUlJxVJNc1uC8//wZIW8Ekk6z
-H2XkrAOW8Cs7rVfz6Q+x00q7xSH825v9RL6pv4l7sPDSGK5lvc+WkTxDpiR2EjIm
-uWStUzCRq7EXhV50pUno6MFABVtqpRP87TiE1l7Yb8S33v+gAVdsrpJewYIDwWcC
-AwEAATANBgkqhkiG9w0BAQUFAAOCAQEAbGjHLamDm1FQpgatYiZ/ic7Z8DFB+CJo
-FcZH4hww27BD/WpQLsj6T1540B35hsmZ73yev4xgLybc/SEIducT9BHyc1DrDZtf
-CFeSq6OOJu/1pJZ9m/d0i+sBJaWg5w1yT8+aEKJaWYfF+C9jZ6+3+I9agID5OplE
-Wwwzg3xXllz3jfmtNlc0f+hE1/XLWFE2nY+5cBhlxReWH3HAhU/qZL9n/WdxCjHd
-NyeWxlDlmzc2+uOeVF5sIGzFOj/qjGxc+UyUXaaEuSvh7j3rvYlZtnhvhJ+tMkoR
-Kbxl1VUYxx+jzfhBy+bKu5uGZB3F1qtyY9fI5DQut75nNbueQPG+qw==
+BgkqhkiG9w0BCQEWFWRldkB0aHJpZnQuYXBhY2hlLm9yZzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALl/bJhg17Pu1t785JIuwlh7e2E51eoWFvuxMWfH
+dsUAVqDaOz99O5sv8pgWKXrkPBttE98T+Mx/Pwz5JHs4Qcre3A5adm6cdab0AKug
+eVG1PvHrWzV4aheg4KUfVSiMz8BG2K3Q1VGvZkgMRa/Q409FKjU6Z4D9vBG4Pas3
+PN5FxnAL6P70yWCEr2Vu/tWJlCN+qrRDfSaE2hqmT1irxaoPa61GvuRVsGQ3TeoE
+mGfENtvFqC6qQWuSCbG5aI47Rv66pqoWWxqkrcolNc7+vhx6rW+wdFO3IlqIT8IG
+sdQVp+Y4ir/dGp2ejzqGZCremPJ1uEKjLMpIukdg25ymHC0CAwEAATANBgkqhkiG
+9w0BAQsFAAOCAQEABcRdwc9mdY9zOvTixtThHrciVvqwp6RaiQqMEVomozYilZDR
+J90M3H6KnlADJbCv0FXujsaApB53awq5I7sObAYWFhDJZ9LKlw0lDF7KchLXiAlk
+XVyvL2nJjuJcxPCFPWktwrw5bTguRCBzG12Hzikn+xs6uPur7VQYM33jbXEda5PS
+UEqvAVWaONJCX8MDYOscVoJF0ESyI+fN92ipFaXR7fGajod/rc0AfeN2GVMZETve
+21pkUyDwwCGA44uvNUCd1e32NxE3ah9UApCRn5sGJ64R6urpFTXT3IALF3kaOO7U
+VsMhFrUiZ7+Bw5nIeiK0QOF6Eipj+bJvrLZpSg==
 -----END CERTIFICATE-----
 Bag Attributes
-    localKeyID: 05 3C 6A 9D 6D EC A0 FA 2F AE 41 32 0D 24 3A 21 34 F6 08 15 
+    localKeyID: 39 EC 3D 5B 28 17 DA DD 09 7A 62 68 D5 44 1F C7 E2 F8 E0 CD 
 Key Attributes: <No Attributes>
 -----BEGIN ENCRYPTED PRIVATE KEY-----
-MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIWyeYAGRBWvwCAggA
-MBQGCCqGSIb3DQMHBAjXHlBG+NmWDwSCAoBRgIb2Ni8qGhruYW7BiKMlVKPnDdnr
-sMgSTgzelwALUazd59B7pA1mdCVazTZ2bqPZYJ0vRomnu4uosn24sXSNwdYg7pJd
-CRnttE2BGlxC4PqFrVM1J7oO5Tfno1rrXVsRi0J4Qr6Gtj5xwZRZiGnLGtnQ9G/X
-TsRtNH/pNRncmu+20xmTC2F8Es8q//4sco5YeEclcmcBr7goO+TusIH3ghlf0jbd
-M9oTvEG7WY3lSarhZp4QYlWWkGfGfkd7rP3yxhLChijdVEOLL6gftDOs0ALBntAR
-NYeSxFoyTEBCz8F+WjVt7QQzAyRNSKNDI8qMxMm/KDKaE92hS76K8PcBlbdy118s
-LfHNb3v/v4WFdjmRJqPdWx5x3cTwGWwOF4MqZd9+Xn+/isu8SfQd2WmSm2qawbQP
-BrjoQSuUQrZyL5AQmFKkdQ875fLfNw3I2ckhpgMi+WCWx11/8k2dYZoP9VTZ1/yT
-l32FM4unt7wafU0vUU2tPcsEq4STdwWR5Q4FBPF3JRiMiNy8KzvFeUtnSX08m/sB
-B3Uiw1jKwwoBx1gfPpq3/UBvmBlvGmwBx+7V+hMfXBAiIoFZFAMruWVgo4GcO8Zv
-se9crOObipcR8r/q9VOF4OlwCyhkl2yDwEjQlRBPkQ1cpO4FWke7VAoRRtaCov8K
-oQYExRwc141jjhlZvkXIa0TstZpHGZZmByFqbrpcAhyRwDJY+wqC3UFk+MALT6Gw
-TcbQO3yIIAzfeaMYkw6isiAa94dOjrYHqOKAbXrw0btt3vKETV5Xx0jognuOcCC8
-i1R5S0cLsZm2j4kWV8mXuCvXkvuq/WgPC162+/GRrhEp1wCsSo8DwG2I
+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
+zLc=
 -----END ENCRYPTED PRIVATE KEY-----

http://git-wip-us.apache.org/repos/asf/thrift/blob/ad835866/test/known_failures_Linux.json
----------------------------------------------------------------------
diff --git a/test/known_failures_Linux.json b/test/known_failures_Linux.json
index aabbfbf..648215a 100644
--- a/test/known_failures_Linux.json
+++ b/test/known_failures_Linux.json
@@ -41,6 +41,12 @@
   "csharp-nodejs_json_framed-ip-ssl",
   "csharp-perl_binary_buffered-ip-ssl",
   "csharp-perl_binary_framed-ip-ssl",
+  "csharp-py3_binary_buffered-ip-ssl",
+  "csharp-py3_binary_framed-ip-ssl",
+  "csharp-py3_compact_buffered-ip-ssl",
+  "csharp-py3_compact_framed-ip-ssl",
+  "csharp-py3_json_buffered-ip-ssl",
+  "csharp-py3_json_framed-ip-ssl",
   "erl-cpp_compact_buffered-ip",
   "erl-cpp_compact_buffered-ip-ssl",
   "erl-cpp_compact_framed-ip",