def is_ssl_sasl_client_accepted(self, listener_port, tls_protocol): """ Attempts to connect a proton client to the management address on the given listener_port using the specific tls_protocol provided. If connection was established and accepted, returns True and False otherwise. :param listener_port: :param tls_protocol: :return: """ # Management address to connect using the given TLS protocol url = Url("amqps://0.0.0.0:%d/$management" % listener_port) # Preparing SSLDomain (client cert) and SASL authentication info domain = SSLDomain(SSLDomain.MODE_CLIENT) domain.set_credentials(self.ssl_file('client-certificate.pem'), self.ssl_file('client-private-key.pem'), 'client-password') # Enforcing given TLS protocol cproton.pn_ssl_domain_set_protocols(domain._domain, tls_protocol) # Try opening the secure and authenticated connection try: connection = BlockingConnection(url, sasl_enabled=True, ssl_domain=domain, allowed_mechs='PLAIN', user='******', password='******') except proton.ConnectionException: return False # TLS version provided was accepted connection.close() return True
def is_proto_allowed(self, listener_port, tls_protocol): """ Opens a simple proton client connection to the provided TCP port using a specific TLS protocol version and returns True in case connection was established and accepted or False otherwise. :param listener_port: TCP port number :param tls_protocol: TLSv1, TLSv1.1 or TLSv1.2 (string) :return: """ # Management address to connect using the given TLS protocol url = Url("amqps://0.0.0.0:%d/$management" % listener_port) # Preparing SSLDomain (client cert) and SASL authentication info domain = SSLDomain(SSLDomain.MODE_CLIENT) # Enforcing given TLS protocol cproton.pn_ssl_domain_set_protocols(domain._domain, tls_protocol) # Try opening the secure and authenticated connection try: connection = BlockingConnection(url, sasl_enabled=False, ssl_domain=domain, timeout=self.TIMEOUT) except proton.Timeout: return False except proton.ConnectionException: return False # TLS version provided was accepted connection.close() return True
def is_proto_allowed(self, listener_port, tls_protocol): """ Opens a simple proton client connection to the provided TCP port using a specific TLS protocol version and returns True in case connection was established and accepted or False otherwise. :param listener_port: TCP port number :param tls_protocol: TLSv1, TLSv1.1 or TLSv1.2 (string) :return: """ # Management address to connect using the given TLS protocol url = Url("amqps://0.0.0.0:%d/$management" % listener_port) # Preparing SSLDomain (client cert) and SASL authentication info domain = SSLDomain(SSLDomain.MODE_CLIENT) # Enforcing given TLS protocol cproton.pn_ssl_domain_set_protocols(domain._domain, tls_protocol) # Try opening the secure and authenticated connection try: connection = BlockingConnection(url, sasl_enabled=False, ssl_domain=domain, timeout=self.TIMEOUT) except proton.Timeout: return False except proton.ConnectionException: return False except: return False # TLS version provided was accepted connection.close() return True
class RouterTestSslClient(RouterTestSslBase): """ Starts a router with multiple listeners, all of them using an sslProfile. Then it runs multiple tests to validate that only the allowed protocol versions are being accepted through the related listener. """ # Listener ports for each TLS protocol definition PORT_TLS1 = 0 PORT_TLS11 = 0 PORT_TLS12 = 0 PORT_TLS13 = 0 PORT_TLS1_TLS11 = 0 PORT_TLS1_TLS12 = 0 PORT_TLS11_TLS12 = 0 PORT_TLS_ALL = 0 PORT_TLS_SASL = 0 PORT_SSL3 = 0 TIMEOUT = 3 # If using OpenSSL 1.1 or greater, TLSv1.2 is always being allowed OPENSSL_OUT_VER = None try: OPENSSL_VER_1_1_GT = ssl.OPENSSL_VERSION_INFO[:2] >= (1, 1) except AttributeError: OPENSSL_VER_1_1_GT = False # If still False, try getting it from "openssl version" (command output) # The version from ssl.OPENSSL_VERSION_INFO reflects OpenSSL version in which # Python was compiled with, not the one installed in the system. if not OPENSSL_VER_1_1_GT: print("Python libraries SSL Version < 1.1") try: p = Popen(['openssl', 'version'], stdout=PIPE, universal_newlines=True) openssl_out = p.communicate()[0] m = re.search(r'[0-9]+\.[0-9]+\.[0-9]+', openssl_out) OPENSSL_OUT_VER = m.group(0) OPENSSL_VER_1_1_GT = StrictVersion( OPENSSL_OUT_VER) >= StrictVersion('1.1') print("OpenSSL Version found = %s" % OPENSSL_OUT_VER) except: pass # Following variables define TLS versions allowed by openssl OPENSSL_MIN_VER = 0 OPENSSL_MAX_VER = 9999 OPENSSL_ALLOW_TLSV1 = True OPENSSL_ALLOW_TLSV1_1 = True OPENSSL_ALLOW_TLSV1_2 = True OPENSSL_ALLOW_TLSV1_3 = False # Test if OpenSSL has TLSv1_3 OPENSSL_HAS_TLSV1_3 = False if OPENSSL_VER_1_1_GT: try: ssl.TLSVersion.TLSv1_3 OPENSSL_HAS_TLSV1_3 = True except: pass # Test if Proton supports TLSv1_3 try: dummydomain = SSLDomain(SSLDomain.MODE_CLIENT) PROTON_HAS_TLSV1_3 = cproton.PN_OK == cproton.pn_ssl_domain_set_protocols( dummydomain._domain, "TLSv1.3") print("TLSV1_3? Proton has: %s, OpenSSL has: %s" % (PROTON_HAS_TLSV1_3, OPENSSL_HAS_TLSV1_3)) except SSLUnavailable: PROTON_HAS_TLSV1_3 = False # When using OpenSSL >= 1.1 and python >= 3.7, we can retrieve OpenSSL min and max protocols if OPENSSL_VER_1_1_GT: if sys.version_info >= (3, 7): if OPENSSL_HAS_TLSV1_3 and not PROTON_HAS_TLSV1_3: # If OpenSSL has 1.3 but proton won't let us turn it on and off then # this test fails because v1.3 runs unexpectedly. RouterTestSslBase.DISABLE_SSL_TESTING = True RouterTestSslBase.DISABLE_REASON = "Proton version does not support TLSv1.3 but OpenSSL does" else: OPENSSL_CTX = ssl.create_default_context() OPENSSL_MIN_VER = OPENSSL_CTX.minimum_version OPENSSL_MAX_VER = OPENSSL_CTX.maximum_version if OPENSSL_CTX.maximum_version > 0 else 9999 OPENSSL_ALLOW_TLSV1 = OPENSSL_MIN_VER <= ssl.TLSVersion.TLSv1 <= OPENSSL_MAX_VER OPENSSL_ALLOW_TLSV1_1 = OPENSSL_MIN_VER <= ssl.TLSVersion.TLSv1_1 <= OPENSSL_MAX_VER OPENSSL_ALLOW_TLSV1_2 = OPENSSL_MIN_VER <= ssl.TLSVersion.TLSv1_2 <= OPENSSL_MAX_VER OPENSSL_ALLOW_TLSV1_3 = OPENSSL_HAS_TLSV1_3 and PROTON_HAS_TLSV1_3 \ and OPENSSL_MIN_VER <= ssl.TLSVersion.TLSv1_3 <= OPENSSL_MAX_VER else: # At this point we are not able to precisely determine what are the minimum and maximum # TLS versions allowed in the system, so tests will be disabled RouterTestSslBase.DISABLE_SSL_TESTING = True RouterTestSslBase.DISABLE_REASON = "OpenSSL >= 1.1 but Python < 3.7 - Unable to determine MinProtocol" else: if OPENSSL_HAS_TLSV1_3 and not PROTON_HAS_TLSV1_3: # If OpenSSL has 1.3 but proton won't let us turn it on and off then # this test fails because v1.3 runs unexpectedly. RouterTestSslBase.DISABLE_SSL_TESTING = True RouterTestSslBase.DISABLE_REASON = "Proton version does not support TLSv1.3 but OpenSSL does" @classmethod def setUpClass(cls): """ Prepares a single router with multiple listeners, each one associated with a particular sslProfile and each sslProfile has its own specific set of allowed protocols. """ super(RouterTestSslClient, cls).setUpClass() cls.routers = [] if SASL.extended(): router = ('router', { 'id': 'QDR.A', 'mode': 'interior', 'saslConfigName': 'tests-mech-PLAIN', 'saslConfigDir': os.getcwd() }) # Generate authentication DB super(RouterTestSslClient, cls).create_sasl_files() else: router = ('router', {'id': 'QDR.A', 'mode': 'interior'}) # Saving listener ports for each TLS definition cls.PORT_TLS1 = cls.tester.get_port() cls.PORT_TLS11 = cls.tester.get_port() cls.PORT_TLS12 = cls.tester.get_port() cls.PORT_TLS13 = cls.tester.get_port() cls.PORT_TLS1_TLS11 = cls.tester.get_port() cls.PORT_TLS1_TLS12 = cls.tester.get_port() cls.PORT_TLS11_TLS12 = cls.tester.get_port() cls.PORT_TLS_ALL = cls.tester.get_port() cls.PORT_TLS_SASL = cls.tester.get_port() cls.PORT_SSL3 = cls.tester.get_port() conf = [ router, # TLSv1 only ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS1, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-tls1' }), # TLSv1.1 only ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS11, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-tls11' }), # TLSv1.2 only ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS12, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-tls12' }), # TLSv1 and TLSv1.1 only ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS1_TLS11, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-tls1-tls11' }), # TLSv1 and TLSv1.2 only ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS1_TLS12, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-tls1-tls12' }), # TLSv1.1 and TLSv1.2 only ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS11_TLS12, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-tls11-tls12' }), # All TLS versions ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS_ALL, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-tls-all' }), # Invalid protocol version ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_SSL3, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-ssl3' }) ] # Adding SASL listener only when SASL is available if SASL.extended(): conf += [ # TLS 1 and 1.2 with SASL PLAIN authentication for proton client validation ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS_SASL, 'authenticatePeer': 'yes', 'saslMechanisms': 'PLAIN', 'requireSsl': 'yes', 'requireEncryption': 'yes', 'sslProfile': 'ssl-profile-tls1-tls12' }) ] # Adding SSL profiles conf += [ # SSL Profile for TLSv1 ('sslProfile', {'name': 'ssl-profile-tls1', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' \ 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', 'protocols': 'TLSv1', 'password': '******'}), # SSL Profile for TLSv1.1 ('sslProfile', {'name': 'ssl-profile-tls11', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' \ 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', 'protocols': 'TLSv1.1', 'password': '******'}), # SSL Profile for TLSv1.2 ('sslProfile', {'name': 'ssl-profile-tls12', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' \ 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', 'protocols': 'TLSv1.2', 'password': '******'}), # SSL Profile for TLSv1 and TLSv1.1 ('sslProfile', {'name': 'ssl-profile-tls1-tls11', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' \ 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', 'protocols': 'TLSv1 TLSv1.1', 'password': '******'}), # SSL Profile for TLSv1 and TLSv1.2 ('sslProfile', {'name': 'ssl-profile-tls1-tls12', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' \ 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', 'protocols': 'TLSv1 TLSv1.2', 'password': '******'}), # SSL Profile for TLSv1.1 and TLSv1.2 ('sslProfile', {'name': 'ssl-profile-tls11-tls12', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' \ 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', 'protocols': 'TLSv1.1 TLSv1.2', 'password': '******'}), # SSL Profile for all TLS versions (protocols element not defined) ('sslProfile', {'name': 'ssl-profile-tls-all', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' \ 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', 'password': '******'}), # SSL Profile for invalid protocol version SSLv23 ('sslProfile', {'name': 'ssl-profile-ssl3', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' \ 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', 'protocols': 'SSLv23', 'password': '******'}) ] if cls.OPENSSL_ALLOW_TLSV1_3: conf += [ # TLSv1.3 only ('listener', { 'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_TLS13, 'authenticatePeer': 'no', 'sslProfile': 'ssl-profile-tls13' }), # SSL Profile for TLSv1.3 ('sslProfile', { 'name': 'ssl-profile-tls13', 'caCertFile': cls.ssl_file('ca-certificate.pem'), 'certFile': cls.ssl_file('server-certificate.pem'), 'privateKeyFile': cls.ssl_file('server-private-key.pem'), 'protocols': 'TLSv1.3', 'password': '******' }) ] config = Qdrouterd.Config(conf) cls.routers.append(cls.tester.qdrouterd("A", config, wait=False)) cls.routers[0].wait_ports() def get_allowed_protocols(self, listener_port): """ Loops through TLSv1, TLSv1.1 and TLSv1.2 and attempts to connect to the listener_port using each version. The result is a boolean list with results in respective order for TLSv1 [0], TLSv1.1 [1] and TLSv1.2 [2]. :param listener_port: :return: """ results = [] for proto in ['TLSv1', 'TLSv1.1', 'TLSv1.2']: results.append(self.is_proto_allowed(listener_port, proto)) if self.OPENSSL_ALLOW_TLSV1_3: results.append(self.is_proto_allowed(listener_port, 'TLSv1.3')) else: results.append(False) return results def is_proto_allowed(self, listener_port, tls_protocol): """ Opens a simple proton client connection to the provided TCP port using a specific TLS protocol version and returns True in case connection was established and accepted or False otherwise. :param listener_port: TCP port number :param tls_protocol: TLSv1, TLSv1.1 or TLSv1.2 (string) :return: """ # Management address to connect using the given TLS protocol url = Url("amqps://0.0.0.0:%d/$management" % listener_port) # Preparing SSLDomain (client cert) and SASL authentication info domain = SSLDomain(SSLDomain.MODE_CLIENT) # Enforcing given TLS protocol cproton.pn_ssl_domain_set_protocols(domain._domain, tls_protocol) # Try opening the secure and authenticated connection try: connection = BlockingConnection(url, sasl_enabled=False, ssl_domain=domain, timeout=self.TIMEOUT) except proton.Timeout: return False except proton.ConnectionException: return False except: return False # TLS version provided was accepted connection.close() return True def is_ssl_sasl_client_accepted(self, listener_port, tls_protocol): """ Attempts to connect a proton client to the management address on the given listener_port using the specific tls_protocol provided. If connection was established and accepted, returns True and False otherwise. :param listener_port: :param tls_protocol: :return: """ # Management address to connect using the given TLS protocol url = Url("amqps://0.0.0.0:%d/$management" % listener_port) # Preparing SSLDomain (client cert) and SASL authentication info domain = SSLDomain(SSLDomain.MODE_CLIENT) domain.set_credentials(self.ssl_file('client-certificate.pem'), self.ssl_file('client-private-key.pem'), 'client-password') # Enforcing given TLS protocol cproton.pn_ssl_domain_set_protocols(domain._domain, tls_protocol) # Try opening the secure and authenticated connection try: connection = BlockingConnection(url, sasl_enabled=True, ssl_domain=domain, allowed_mechs='PLAIN', user='******', password='******') except proton.ConnectionException: return False # TLS version provided was accepted connection.close() return True def get_expected_tls_result(self, expected_results): """ Expects a list with three boolean elements, representing TLSv1, TLSv1.1 and TLSv1.2 (in the respective order). When using OpenSSL >= 1.1.x, allowance of a given TLS version is based on MinProtocol / MaxProtocol definitions. It is also important to mention that TLSv1.2 is being allowed even when not specified in a listener when using OpenSSL >= 1.1.x. :param expected_results: :return: """ (tlsv1, tlsv1_1, tlsv1_2, tlsv1_3) = expected_results return [ self.OPENSSL_ALLOW_TLSV1 and tlsv1, self.OPENSSL_ALLOW_TLSV1_1 and tlsv1_1, self.OPENSSL_ALLOW_TLSV1_2 and tlsv1_2, self.OPENSSL_ALLOW_TLSV1_3 and tlsv1_3 ] @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_tls1_only(self): """ Expects TLSv1 only is allowed """ self.assertEqual( self.get_expected_tls_result([True, False, False, False]), self.get_allowed_protocols(self.PORT_TLS1)) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_tls11_only(self): """ Expects TLSv1.1 only is allowed """ self.assertEqual( self.get_expected_tls_result([False, True, False, False]), self.get_allowed_protocols(self.PORT_TLS11)) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_tls12_only(self): """ Expects TLSv1.2 only is allowed """ self.assertEqual( self.get_expected_tls_result([False, False, True, False]), self.get_allowed_protocols(self.PORT_TLS12)) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_tls13_only(self): """ Expects TLSv1.3 only is allowed """ self.assertEqual( self.get_expected_tls_result([False, False, False, True]), self.get_allowed_protocols(self.PORT_TLS13)) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_tls1_tls11_only(self): """ Expects TLSv1 and TLSv1.1 only are allowed """ self.assertEqual( self.get_expected_tls_result([True, True, False, False]), self.get_allowed_protocols(self.PORT_TLS1_TLS11)) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_tls1_tls12_only(self): """ Expects TLSv1 and TLSv1.2 only are allowed """ self.assertEqual( self.get_expected_tls_result([True, False, True, False]), self.get_allowed_protocols(self.PORT_TLS1_TLS12)) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_tls11_tls12_only(self): """ Expects TLSv1.1 and TLSv1.2 only are allowed """ self.assertEqual( self.get_expected_tls_result([False, True, True, False]), self.get_allowed_protocols(self.PORT_TLS11_TLS12)) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_tls_all(self): """ Expects all supported versions: TLSv1, TLSv1.1, TLSv1.2 and TLSv1.3 to be allowed """ self.assertEqual( self.get_expected_tls_result([True, True, True, True]), self.get_allowed_protocols(self.PORT_TLS_ALL)) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING, RouterTestSslBase.DISABLE_REASON) def test_ssl_invalid(self): """ Expects connection is rejected as SSL is no longer supported """ self.assertEqual(False, self.is_proto_allowed(self.PORT_SSL3, 'SSLv3')) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING or not SASL.extended(), "Cyrus library not available. skipping test") def test_ssl_sasl_client_valid(self): """ Attempts to connect a Proton client using a valid SASL authentication info and forcing the TLS protocol version, which should be accepted by the listener. :return: """ if not SASL.extended(): self.skipTest("Cyrus library not available. skipping test") exp_tls_results = self.get_expected_tls_result( [True, False, True, False]) self.assertEqual( exp_tls_results[0], self.is_ssl_sasl_client_accepted(self.PORT_TLS_SASL, "TLSv1")) self.assertEqual( exp_tls_results[2], self.is_ssl_sasl_client_accepted(self.PORT_TLS_SASL, "TLSv1.2")) @SkipIfNeeded(RouterTestSslBase.DISABLE_SSL_TESTING or not SASL.extended(), "Cyrus library not available. skipping test") def test_ssl_sasl_client_invalid(self): """ Attempts to connect a Proton client using a valid SASL authentication info and forcing the TLS protocol version, which should be rejected by the listener. :return: """ if not SASL.extended(): self.skipTest("Cyrus library not available. skipping test") exp_tls_results = self.get_expected_tls_result( [True, False, True, False]) self.assertEqual( exp_tls_results[1], self.is_ssl_sasl_client_accepted(self.PORT_TLS_SASL, "TLSv1.1"))