From 2941cd03a12e9fb471b3b934e82dcf1d1324c17e Mon Sep 17 00:00:00 2001 From: bplost Date: Tue, 23 Aug 2016 10:32:16 -0500 Subject: [PATCH 1/8] Add support for TLS/SSL mutual authentication Add support in the TLS_FTPHandler to check a client certificate. This type of support strengthens the security between the client and the server, only allowing clients with a valid certificate to connect to the server. Updated the api.rst file with the two new configurable options to make client authentication work --- docs/api.rst | 10 ++++++++++ pyftpdlib/handlers.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index fa5c9303..7121d5f8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -556,7 +556,17 @@ Extended handlers When True requires SSL/TLS to be established on the data channel. This means the user will have to issue PROT before PASV or PORT (default ``False``). + + .. data:: tls_mutual_authentication + When True requires the client to send a valid certificate to connect to the server + (default ``False``). + + .. data:: tls_mutual_auth_certfile + + The path of the certificate to check the client certificate against. Must be provided + when tls_mutual_authentication is set to ``True`` (default ``None``). + Extended authorizers -------------------- diff --git a/pyftpdlib/handlers.py b/pyftpdlib/handlers.py index c9515b3b..0c531390 100644 --- a/pyftpdlib/handlers.py +++ b/pyftpdlib/handlers.py @@ -22,6 +22,7 @@ try: from OpenSSL import SSL # requires "pip install pyopenssl" + from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE, SESS_CACHE_OFF, OP_NO_TICKET except ImportError: SSL = None @@ -3414,6 +3415,9 @@ class TLS_FTPHandler(SSLConnection, FTPHandler): certfile = None keyfile = None ssl_protocol = SSL.SSLv23_METHOD + # client certificate configurable attributes + tls_mutual_authentication = False + tls_mutual_auth_certfile = None # - SSLv2 is easily broken and is considered harmful and dangerous # - SSLv3 has several problems and is now dangerous # - Disable compression to prevent CRIME attacks for OpenSSL 1.0+ @@ -3449,6 +3453,14 @@ def __init__(self, conn, server, ioloop=None): def __repr__(self): return FTPHandler.__repr__(self) + + @staticmethod + def verify_certs_callback(connection, x509, errnum, errdepth, ok): + if not ok: + print "Bad client certificate detected" + else: + print "Client certificate ok" + return ok @classmethod def get_ssl_context(cls): @@ -3464,6 +3476,11 @@ def get_ssl_context(cls): if not cls.keyfile: cls.keyfile = cls.certfile cls.ssl_context.use_privatekey_file(cls.keyfile) + if cls.tls_mutual_authentication: + cls.ssl_context.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE, cls.verify_certs_callback) + cls.ssl_context.load_verify_locations(cls.tls_mutual_auth_certfile) + cls.ssl_context.set_session_cache_mode(SESS_CACHE_OFF) + cls.ssl_options = cls.ssl_options | OP_NO_TICKET if cls.ssl_options: cls.ssl_context.set_options(cls.ssl_options) return cls.ssl_context From bb52b92c87aa356326e24fccf65a836e73260085 Mon Sep 17 00:00:00 2001 From: bplost Date: Tue, 23 Aug 2016 10:47:15 -0500 Subject: [PATCH 2/8] Fix indentation errors in handlers.py --- pyftpdlib/handlers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyftpdlib/handlers.py b/pyftpdlib/handlers.py index 0c531390..4a470fab 100644 --- a/pyftpdlib/handlers.py +++ b/pyftpdlib/handlers.py @@ -22,7 +22,7 @@ try: from OpenSSL import SSL # requires "pip install pyopenssl" - from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE, SESS_CACHE_OFF, OP_NO_TICKET + from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE, SESS_CACHE_OFF, OP_NO_TICKET except ImportError: SSL = None @@ -3454,7 +3454,7 @@ def __init__(self, conn, server, ioloop=None): def __repr__(self): return FTPHandler.__repr__(self) - @staticmethod + @staticmethod def verify_certs_callback(connection, x509, errnum, errdepth, ok): if not ok: print "Bad client certificate detected" @@ -3476,7 +3476,7 @@ def get_ssl_context(cls): if not cls.keyfile: cls.keyfile = cls.certfile cls.ssl_context.use_privatekey_file(cls.keyfile) - if cls.tls_mutual_authentication: + if cls.tls_mutual_authentication: cls.ssl_context.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE, cls.verify_certs_callback) cls.ssl_context.load_verify_locations(cls.tls_mutual_auth_certfile) cls.ssl_context.set_session_cache_mode(SESS_CACHE_OFF) From 933257d18e6ff121ac11a0baafc691ed4bf63c55 Mon Sep 17 00:00:00 2001 From: bplost Date: Tue, 23 Aug 2016 11:06:17 -0500 Subject: [PATCH 3/8] Update 'print' statements to use logger --- pyftpdlib/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyftpdlib/handlers.py b/pyftpdlib/handlers.py index 4a470fab..e2f19416 100644 --- a/pyftpdlib/handlers.py +++ b/pyftpdlib/handlers.py @@ -3457,9 +3457,9 @@ def __repr__(self): @staticmethod def verify_certs_callback(connection, x509, errnum, errdepth, ok): if not ok: - print "Bad client certificate detected" + self.log("Bad client certificate detected.") else: - print "Client certificate ok" + self.log("Client certificate ok.") return ok @classmethod From 128b61ff3b44d460d06cbae69816f5271a16747b Mon Sep 17 00:00:00 2001 From: bplost Date: Tue, 23 Aug 2016 11:21:58 -0500 Subject: [PATCH 4/8] Remove print/log statements --- pyftpdlib/handlers.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyftpdlib/handlers.py b/pyftpdlib/handlers.py index e2f19416..126459b6 100644 --- a/pyftpdlib/handlers.py +++ b/pyftpdlib/handlers.py @@ -3415,7 +3415,7 @@ class TLS_FTPHandler(SSLConnection, FTPHandler): certfile = None keyfile = None ssl_protocol = SSL.SSLv23_METHOD - # client certificate configurable attributes + # client certificate configurable attributes tls_mutual_authentication = False tls_mutual_auth_certfile = None # - SSLv2 is easily broken and is considered harmful and dangerous @@ -3453,13 +3453,14 @@ def __init__(self, conn, server, ioloop=None): def __repr__(self): return FTPHandler.__repr__(self) - + + #commented out prints - for different Python version support @staticmethod def verify_certs_callback(connection, x509, errnum, errdepth, ok): - if not ok: - self.log("Bad client certificate detected.") - else: - self.log("Client certificate ok.") + #if not ok: + #print "Bad client certificate detected." + #else: + #print "Client certificate ok." return ok @classmethod From c3675b9b956e17c84e75007b7c82599936269066 Mon Sep 17 00:00:00 2001 From: bplost Date: Thu, 25 Aug 2016 13:25:14 -0500 Subject: [PATCH 5/8] Clean up client-certfile support Cleaned up the support for client certificate checking Added test file for specific client authentication tests - Good cert (allows connection) - Bad cert (does not allow connection) - No cert (does not allow connection) --- docs/api.rst | 7 +- pyftpdlib/handlers.py | 38 +++-- pyftpdlib/test/test_functional_ssl.py | 2 +- .../test_functional_ssl_client_certfile.py | 150 ++++++++++++++++++ 4 files changed, 176 insertions(+), 21 deletions(-) create mode 100644 pyftpdlib/test/test_functional_ssl_client_certfile.py diff --git a/docs/api.rst b/docs/api.rst index 7121d5f8..f3ede3cc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -557,12 +557,7 @@ Extended handlers means the user will have to issue PROT before PASV or PORT (default ``False``). - .. data:: tls_mutual_authentication - - When True requires the client to send a valid certificate to connect to the server - (default ``False``). - - .. data:: tls_mutual_auth_certfile + .. data:: client_certfile The path of the certificate to check the client certificate against. Must be provided when tls_mutual_authentication is set to ``True`` (default ``None``). diff --git a/pyftpdlib/handlers.py b/pyftpdlib/handlers.py index 126459b6..b9f6eac7 100644 --- a/pyftpdlib/handlers.py +++ b/pyftpdlib/handlers.py @@ -22,7 +22,11 @@ try: from OpenSSL import SSL # requires "pip install pyopenssl" - from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE, SESS_CACHE_OFF, OP_NO_TICKET + from OpenSSL.SSL import OP_NO_TICKET + from OpenSSL.SSL import SESS_CACHE_OFF + from OpenSSL.SSL import VERIFY_CLIENT_ONCE + from OpenSSL.SSL import VERIFY_FAIL_IF_NO_PEER_CERT + from OpenSSL.SSL import VERIFY_PEER except ImportError: SSL = None @@ -3416,8 +3420,7 @@ class TLS_FTPHandler(SSLConnection, FTPHandler): keyfile = None ssl_protocol = SSL.SSLv23_METHOD # client certificate configurable attributes - tls_mutual_authentication = False - tls_mutual_auth_certfile = None + client_certfile = None # - SSLv2 is easily broken and is considered harmful and dangerous # - SSLv3 has several problems and is now dangerous # - Disable compression to prevent CRIME attacks for OpenSSL 1.0+ @@ -3450,17 +3453,21 @@ def __init__(self, conn, server, ioloop=None): self._pbsz = False self._prot = False self.ssl_context = self.get_ssl_context() + if self.client_certfile is not None: + self.ssl_context.set_verify(VERIFY_PEER | + VERIFY_FAIL_IF_NO_PEER_CERT | + VERIFY_CLIENT_ONCE, + self.verify_certs_callback) def __repr__(self): return FTPHandler.__repr__(self) - - #commented out prints - for different Python version support - @staticmethod - def verify_certs_callback(connection, x509, errnum, errdepth, ok): - #if not ok: - #print "Bad client certificate detected." - #else: - #print "Client certificate ok." + + # Cannot be @classmethod, need instance to log + def verify_certs_callback(self, connection, x509, errnum, errdepth, ok): + if not ok: + self.log("Bad client certificate detected.") + else: + self.log("Client certificate is valid.") return ok @classmethod @@ -3477,9 +3484,12 @@ def get_ssl_context(cls): if not cls.keyfile: cls.keyfile = cls.certfile cls.ssl_context.use_privatekey_file(cls.keyfile) - if cls.tls_mutual_authentication: - cls.ssl_context.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE, cls.verify_certs_callback) - cls.ssl_context.load_verify_locations(cls.tls_mutual_auth_certfile) + if cls.client_certfile is not None: + cls.ssl_context.set_verify(VERIFY_PEER | + VERIFY_FAIL_IF_NO_PEER_CERT | + VERIFY_CLIENT_ONCE, + cls.verify_certs_callback) + cls.ssl_context.load_verify_locations(cls.client_certfile) cls.ssl_context.set_session_cache_mode(SESS_CACHE_OFF) cls.ssl_options = cls.ssl_options | OP_NO_TICKET if cls.ssl_options: diff --git a/pyftpdlib/test/test_functional_ssl.py b/pyftpdlib/test/test_functional_ssl.py index 16ce4c35..55e26f00 100644 --- a/pyftpdlib/test/test_functional_ssl.py +++ b/pyftpdlib/test/test_functional_ssl.py @@ -210,7 +210,7 @@ class TestCornerCasesTLSMixin(TLSTestMixin, TestCornerCases): @unittest.skipUnless(FTPS_SUPPORT, FTPS_UNSUPPORT_REASON) class TestFTPS(unittest.TestCase): - """Specific tests fot TSL_FTPHandler class.""" + """Specific tests for TLS_FTPHandler class.""" def setUp(self): self.server = FTPSServer() diff --git a/pyftpdlib/test/test_functional_ssl_client_certfile.py b/pyftpdlib/test/test_functional_ssl_client_certfile.py new file mode 100644 index 00000000..d029b0dd --- /dev/null +++ b/pyftpdlib/test/test_functional_ssl_client_certfile.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + +# Copyright (C) 2007-2016 Giampaolo Rodola' . +# Use of this source code is governed by MIT license that can be +# found in the LICENSE file. + +import contextlib +import ftplib +import os +import socket +import sys +import ssl + +import OpenSSL # requires "pip install pyopenssl" + +from pyftpdlib.handlers import TLS_FTPHandler +from pyftpdlib.test import configure_logging +from pyftpdlib.test import PASSWD +from pyftpdlib.test import remove_test_files +from pyftpdlib.test import ThreadedTestFTPd +from pyftpdlib.test import TIMEOUT +from pyftpdlib.test import TRAVIS +from pyftpdlib.test import unittest +from pyftpdlib.test import USER +from pyftpdlib.test import VERBOSITY +from pyftpdlib.test.test_functional import TestCallbacks +from pyftpdlib.test.test_functional import TestConfigurableOptions +from pyftpdlib.test.test_functional import TestCornerCases +from pyftpdlib.test.test_functional import TestFtpAbort +from pyftpdlib.test.test_functional import TestFtpAuthentication +from pyftpdlib.test.test_functional import TestFtpCmdsSemantic +from pyftpdlib.test.test_functional import TestFtpDummyCmds +from pyftpdlib.test.test_functional import TestFtpFsOperations +from pyftpdlib.test.test_functional import TestFtpListingCmds +from pyftpdlib.test.test_functional import TestFtpRetrieveData +from pyftpdlib.test.test_functional import TestFtpStoreData +from pyftpdlib.test.test_functional import TestIPv4Environment +from pyftpdlib.test.test_functional import TestIPv6Environment +from pyftpdlib.test.test_functional import TestSendfile +from pyftpdlib.test.test_functional import TestTimeouts +from _ssl import SSLError + + +FTPS_SUPPORT = hasattr(ftplib, 'FTP_TLS') +if sys.version_info < (2, 7): + FTPS_UNSUPPORT_REASON = "requires python 2.7+" +else: + FTPS_UNSUPPORT_REASON = "FTPS test skipped" + +CERTFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'keycert.pem')) +CLIENT_CERTFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'clientcert.pem')) + +del OpenSSL + + +if FTPS_SUPPORT: + class FTPSClient(ftplib.FTP_TLS): + """A modified version of ftplib.FTP_TLS class which implicitly + secure the data connection after login(). + """ + + def login(self, *args, **kwargs): + ftplib.FTP_TLS.login(self, *args, **kwargs) + self.prot_p() + + class FTPSServerAuth(ThreadedTestFTPd): + """A threaded FTPS server that forces client certificate + authentication used for functional testing. + """ + handler = TLS_FTPHandler + handler.certfile = CERTFILE + handler.client_certfile = CLIENT_CERTFILE + + +# ===================================================================== +# dedicated FTPS tests with client authentication +# ===================================================================== + + +@unittest.skipUnless(FTPS_SUPPORT, FTPS_UNSUPPORT_REASON) +class TestFTPS(unittest.TestCase): + """Specific tests for TLS_FTPHandler class.""" + + def setUp(self): + self.server = FTPSServerAuth() + self.server.start() + + def tearDown(self): + self.client.close() + self.server.stop() + + def assertRaisesWithMsg(self, excClass, msg, callableObj, *args, **kwargs): + try: + callableObj(*args, **kwargs) + except excClass as err: + if str(err) == msg: + return + raise self.failureException("%s != %s" % (str(err), msg)) + else: + if hasattr(excClass, '__name__'): + excName = excClass.__name__ + else: + excName = str(excClass) + raise self.failureException("%s not raised" % excName) + + def test_auth_client_cert(self): + self.client = ftplib.FTP_TLS(timeout=TIMEOUT, certfile=CLIENT_CERTFILE) + self.client.connect(self.server.host, self.server.port) + # secured + try: + self.client.login() + except Exception as e: + self.fail("login with certificate should work") + + def test_auth_client_nocert(self): + self.client = ftplib.FTP_TLS(timeout=TIMEOUT) + self.client.connect(self.server.host, self.server.port) + try: + self.client.login() + except SSLError as e: + # client should not be able to log in + if "SSLV3_ALERT_HANDSHAKE_FAILURE" in e.reason: + pass + else: + self.fail("Incorrect SSL error with missing client certificate") + else: + self.fail("Client able to log in with no certificate") + + def test_auth_client_badcert(self): + self.client = ftplib.FTP_TLS(timeout=TIMEOUT, certfile=CERTFILE) + self.client.connect(self.server.host, self.server.port) + try: + self.client.login() + except Exception as e: + # client should not be able to log in + if "TLSV1_ALERT_UNKNOWN_CA" in e.reason: + pass + else: + self.fail("Incorrect SSL error with bad client certificate") + else: + self.fail("Client able to log in with bad certificate") + +configure_logging() +remove_test_files() + + +if __name__ == '__main__': + unittest.main(verbosity=VERBOSITY) From aa41ce3533b7f275b8ddd8c415abe2fd869950a9 Mon Sep 17 00:00:00 2001 From: bplost Date: Fri, 26 Aug 2016 07:05:24 -0500 Subject: [PATCH 6/8] Updates per comments Fix description in api.rst Add clientcert.pem Rename test file to avoid tests attempting to run two servers on the same port, causing all sorts of SSL mayhem and timeout issues --- docs/api.rst | 3 +- pyftpdlib/test/clientcert.pem | 50 +++++++++++++++++++ ...> functional_ssl_client_certfile_tests.py} | 6 +++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 pyftpdlib/test/clientcert.pem rename pyftpdlib/test/{test_functional_ssl_client_certfile.py => functional_ssl_client_certfile_tests.py} (95%) diff --git a/docs/api.rst b/docs/api.rst index f3ede3cc..cb506948 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -559,8 +559,7 @@ Extended handlers .. data:: client_certfile - The path of the certificate to check the client certificate against. Must be provided - when tls_mutual_authentication is set to ``True`` (default ``None``). + The path of the certificate to check the client certificate against. (default ``None``). Extended authorizers -------------------- diff --git a/pyftpdlib/test/clientcert.pem b/pyftpdlib/test/clientcert.pem new file mode 100644 index 00000000..4c755845 --- /dev/null +++ b/pyftpdlib/test/clientcert.pem @@ -0,0 +1,50 @@ +-----BEGIN CERTIFICATE----- +MIIDqzCCApOgAwIBAgIBADANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwJVUzEO +MAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xDDAKBgNVBAoMA1NMQjEM +MAoGA1UECwwDU1dUMQwwCgYDVQQDDANzd3QxFTATBgkqhkiG9w0BCQEWBmJwbG9z +dDAeFw0xNjA4MjMxMzQyMDZaFw0xNzA4MjMxMzQyMDZaMHAxCzAJBgNVBAYTAlVT +MQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEMMAoGA1UECgwDU0xC +MQwwCgYDVQQLDANTV1QxDDAKBgNVBAMMA3N3dDEVMBMGCSqGSIb3DQEJARYGYnBs +b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArNnEnnUGpXLqnTTx +3td4OWQoKFBppifL6+r5y839CWT6GR3Xj5OlcJPXMKBO40H0StHOctshPL0/ZZeC +sNHT6O6c47nBc/pw3YX4GJjLJno0k1+xTSvmk+yhTF/i1ThDIq2YrlGWXHyo/jOe +gJc0T3L2u1Ivx+iOEAIP9uqBpAi3rhfZKBvVdDXb5J0TqouXt4jx5l8Fq577D9y4 +W61nmj7FlquxClhGIgsNbMtlBIMkALNLq3kY+TqYatjRy6aS6mb55TfObjP+IOgz +8Jln937hb89eJopirwKMzD1EnJgBamMPNJjIhDQlHklMwkgynIKnSJghbjKBusaE +2xZEPQIDAQABo1AwTjAdBgNVHQ4EFgQU2hMnpneH1sZ79iWoBLue1+7f4NwwHwYD +VR0jBBgwFoAU2hMnpneH1sZ79iWoBLue1+7f4NwwDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAQEAdmPairr/lt63J3dfTSFNJTytvV1WW7Ak8NwH1hdheaYy +Tx9ffjRIZv9WyHEWb/1YCkKo08LqgnVh07HfW0JY1hqD0SwoHtPexTIgBnOsKvCr +q1gQjFuDg2wVV7cecYPQFYv9jweIe62OCapKl8PjmXii+qnxY/Qbbyx9bGYbR1k4 +KJm073WwiqXXCS1JgOj9WH3I1Qa2Ptb6RO+Woy7ItA5ftQSp4EMTwhygOK0j0w9V +11MAPUtZ8/rTiD17HHVzKfbNmx4E6dtBV9E/gn464lrdNhxEaeN2wW42FU+CzeVa +/aYQbQTSXpI5tX7QTAcQrMAqp75EBMdYj5+9bRXIvw== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCs2cSedQalcuqd +NPHe13g5ZCgoUGmmJ8vr6vnLzf0JZPoZHdePk6Vwk9cwoE7jQfRK0c5y2yE8vT9l +l4Kw0dPo7pzjucFz+nDdhfgYmMsmejSTX7FNK+aT7KFMX+LVOEMirZiuUZZcfKj+ +M56AlzRPcva7Ui/H6I4QAg/26oGkCLeuF9koG9V0NdvknROqi5e3iPHmXwWrnvsP +3LhbrWeaPsWWq7EKWEYiCw1sy2UEgyQAs0ureRj5Ophq2NHLppLqZvnlN85uM/4g +6DPwmWf3fuFvz14mimKvAozMPUScmAFqYw80mMiENCUeSUzCSDKcgqdImCFuMoG6 +xoTbFkQ9AgMBAAECggEBAKRQ3G36N9g+VzQdKbU6xipgwSAZ2WU/vcZG+TI6Xsp4 +eJw51zrBE+viTxYFvxihETezHXvoPj98dHECSBYJUlbDxtdhNbsoH/UmrwPK9Ixe +be6PcIA5NJf4whlVqdAiDQhBWLyWCMdhJlGJBqudkffZBR5r8corlCk5nK2Qnq8s +ncaaO1A7RLFXXCiwWdAi7bJEHZB2XFQYMctfJ+/m02s0IhIRmusH4XovdTucfykI +FstXtd5+AOfqsOkTn4kGOmG4ObKOXOp1XAjTTIV3xxU0CzYxE1CPhlimkAVLuRPs +bInojdtm80ZQY68xCOvUsq+WMIB+dKkeJKeTI6e/b+0CgYEA3nDQFto7pqtVZLUu +YVTlR6vxrXEYl8FIes8WlICBWXPnviFoiSLTB5SPF1Yvv/LKFkYQ96i2ep8FvZEy +Rbzt1xOOF9GWtO4lc6cBIzh31POJgY2B2PAo7qmhpyhutCaaotf5ukpx35eGlj5v +RT0wDN7Xt6S4arqgbngUdtHLhqsCgYEAxu2rv/GVU/DL3VuBHIky/OKFeL94EnIA +UUm5SRvd0SeHgcjd+EHG/u1nH1XC+Dwiw75Dsv/nqhekFqaQczYZlUrKa+mkdofV +i8DbXHies6qTwvYUIorhaiAoY+iRwJ/6d20oep+3fDSPPcVj7PakZpCK0sAesWX7 +09muN0vLALcCgYAKr0iPkHQFEX3MlJdhvX417yBwwFn6ECK3I3NmNrX/4f1juJ8Y +1z9jwdMNv+oTQkpKv5rZCpWZVkIkVPEhQG38Qsg0hLDEiBvsbj0zv+ahqAEW5AE0 +tnSA4k0Nhneq15/d6pnoROMrZk/kr6MQpFvGgn3CKHtjRQunwsTY4ELyeQKBgQCN +zlNGqvJmOhs5msc5Dly4hMncv7DahUXQrJtWkHTZajJgxE3ncQxoIdgHMF2iE0w8 ++V7NNTtxtxSTyPzkBEbMc9pEfvNsQ3xo+XvmOV34ebqHml/UF+iEfJQOVHXCOMiV +Zc0bTMvB0L3jrNiEzXV4X8V2Ytn+X9LavCxC4ta9lQKBgHTQNDz+qdiLoeX68HKp +b5jd1H7nozWdcbcAO5tKNbSZvnY05QCjC+WkuIeUkKc2zcctIy306/iFRApguElm +z8sm8NnJIkJeRx2XmEE5Lcn64se3ml7qVlcFaYrVW8hDrrvlUAwzu+ZoPVD3DSiQ +EOnPOa2mEwR9YPyPaKdq+fkt +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/pyftpdlib/test/test_functional_ssl_client_certfile.py b/pyftpdlib/test/functional_ssl_client_certfile_tests.py similarity index 95% rename from pyftpdlib/test/test_functional_ssl_client_certfile.py rename to pyftpdlib/test/functional_ssl_client_certfile_tests.py index d029b0dd..0e006807 100644 --- a/pyftpdlib/test/test_functional_ssl_client_certfile.py +++ b/pyftpdlib/test/functional_ssl_client_certfile_tests.py @@ -3,6 +3,12 @@ # Copyright (C) 2007-2016 Giampaolo Rodola' . # Use of this source code is governed by MIT license that can be # found in the LICENSE file. +# +# +# Does not follow naming convention of other tests because this +# CANNOT be run in the same test suite with test_functional_ssl. +# The test parallelism causes SSL errors when there should be none +# Please run these tests separately import contextlib import ftplib From 0928806fb4f92e7351e5695eda18fe96fc2aca23 Mon Sep 17 00:00:00 2001 From: bplost Date: Fri, 26 Aug 2016 07:31:04 -0500 Subject: [PATCH 7/8] Flake8 fixes Fix syntax per Flake8 comments https://travis-ci.org/giampaolo/pyftpdlib/jobs/155320579 --- pyftpdlib/handlers.py | 15 +++++---- .../functional_ssl_client_certfile_tests.py | 32 ++++--------------- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/pyftpdlib/handlers.py b/pyftpdlib/handlers.py index b9f6eac7..f4246e43 100644 --- a/pyftpdlib/handlers.py +++ b/pyftpdlib/handlers.py @@ -3454,16 +3454,17 @@ def __init__(self, conn, server, ioloop=None): self._prot = False self.ssl_context = self.get_ssl_context() if self.client_certfile is not None: - self.ssl_context.set_verify(VERIFY_PEER | - VERIFY_FAIL_IF_NO_PEER_CERT | - VERIFY_CLIENT_ONCE, + self.ssl_context.set_verify(VERIFY_PEER | + VERIFY_FAIL_IF_NO_PEER_CERT | + VERIFY_CLIENT_ONCE, self.verify_certs_callback) def __repr__(self): return FTPHandler.__repr__(self) # Cannot be @classmethod, need instance to log - def verify_certs_callback(self, connection, x509, errnum, errdepth, ok): + def verify_certs_callback(self, connection, x509, + errnum, errdepth, ok): if not ok: self.log("Bad client certificate detected.") else: @@ -3485,9 +3486,9 @@ def get_ssl_context(cls): cls.keyfile = cls.certfile cls.ssl_context.use_privatekey_file(cls.keyfile) if cls.client_certfile is not None: - cls.ssl_context.set_verify(VERIFY_PEER | - VERIFY_FAIL_IF_NO_PEER_CERT | - VERIFY_CLIENT_ONCE, + cls.ssl_context.set_verify(VERIFY_PEER | + VERIFY_FAIL_IF_NO_PEER_CERT | + VERIFY_CLIENT_ONCE, cls.verify_certs_callback) cls.ssl_context.load_verify_locations(cls.client_certfile) cls.ssl_context.set_session_cache_mode(SESS_CACHE_OFF) diff --git a/pyftpdlib/test/functional_ssl_client_certfile_tests.py b/pyftpdlib/test/functional_ssl_client_certfile_tests.py index 0e006807..aae87d68 100644 --- a/pyftpdlib/test/functional_ssl_client_certfile_tests.py +++ b/pyftpdlib/test/functional_ssl_client_certfile_tests.py @@ -3,47 +3,26 @@ # Copyright (C) 2007-2016 Giampaolo Rodola' . # Use of this source code is governed by MIT license that can be # found in the LICENSE file. -# -# +# +# # Does not follow naming convention of other tests because this # CANNOT be run in the same test suite with test_functional_ssl. # The test parallelism causes SSL errors when there should be none # Please run these tests separately -import contextlib import ftplib import os -import socket import sys -import ssl import OpenSSL # requires "pip install pyopenssl" from pyftpdlib.handlers import TLS_FTPHandler from pyftpdlib.test import configure_logging -from pyftpdlib.test import PASSWD from pyftpdlib.test import remove_test_files from pyftpdlib.test import ThreadedTestFTPd from pyftpdlib.test import TIMEOUT -from pyftpdlib.test import TRAVIS from pyftpdlib.test import unittest -from pyftpdlib.test import USER from pyftpdlib.test import VERBOSITY -from pyftpdlib.test.test_functional import TestCallbacks -from pyftpdlib.test.test_functional import TestConfigurableOptions -from pyftpdlib.test.test_functional import TestCornerCases -from pyftpdlib.test.test_functional import TestFtpAbort -from pyftpdlib.test.test_functional import TestFtpAuthentication -from pyftpdlib.test.test_functional import TestFtpCmdsSemantic -from pyftpdlib.test.test_functional import TestFtpDummyCmds -from pyftpdlib.test.test_functional import TestFtpFsOperations -from pyftpdlib.test.test_functional import TestFtpListingCmds -from pyftpdlib.test.test_functional import TestFtpRetrieveData -from pyftpdlib.test.test_functional import TestFtpStoreData -from pyftpdlib.test.test_functional import TestIPv4Environment -from pyftpdlib.test.test_functional import TestIPv6Environment -from pyftpdlib.test.test_functional import TestSendfile -from pyftpdlib.test.test_functional import TestTimeouts from _ssl import SSLError @@ -56,7 +35,7 @@ CERTFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), 'keycert.pem')) CLIENT_CERTFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), - 'clientcert.pem')) + 'clientcert.pem')) del OpenSSL @@ -117,7 +96,7 @@ def test_auth_client_cert(self): # secured try: self.client.login() - except Exception as e: + except Exception: self.fail("login with certificate should work") def test_auth_client_nocert(self): @@ -130,7 +109,8 @@ def test_auth_client_nocert(self): if "SSLV3_ALERT_HANDSHAKE_FAILURE" in e.reason: pass else: - self.fail("Incorrect SSL error with missing client certificate") + self.fail("Incorrect SSL error with" + + " missing client certificate") else: self.fail("Client able to log in with no certificate") From 099630fa1646de5b90ec4d5e107222a1515ca3e2 Mon Sep 17 00:00:00 2001 From: bplost Date: Fri, 26 Aug 2016 07:45:23 -0500 Subject: [PATCH 8/8] Small flake8 fix remove trailing whitespace --- pyftpdlib/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyftpdlib/handlers.py b/pyftpdlib/handlers.py index f4246e43..4bf23e7b 100644 --- a/pyftpdlib/handlers.py +++ b/pyftpdlib/handlers.py @@ -3463,7 +3463,7 @@ def __repr__(self): return FTPHandler.__repr__(self) # Cannot be @classmethod, need instance to log - def verify_certs_callback(self, connection, x509, + def verify_certs_callback(self, connection, x509, errnum, errdepth, ok): if not ok: self.log("Bad client certificate detected.")