Skip to content

Commit 30cd233

Browse files
committed
BUG26484601: unable to connect to a server using other than TLSv1
The connection to a server that is configured to use a different version of TLSv1 (as TLSv1.1 and TLSv1.2) is not possible, and ends with "system error: 1 [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:590)". This happens because the connector is wrongly restricting the connection to use only TLSv1. This patch changes the behavior to allow the server to specify which version should be used in the secured connection. However the the connector can still restrict which version it wants to use but this should be used for testing purposes since is more secure to leave the server to choose the version, who naturally chooses the latest available between the versions allowed in tsl_version configuration option. v2
1 parent 70a8da7 commit 30cd233

File tree

5 files changed

+108
-11
lines changed

5 files changed

+108
-11
lines changed

lib/mysql/connector/connection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ def _do_auth(self, username=None, password=None, database=None,
148148
ssl_options.get('cert'),
149149
ssl_options.get('key'),
150150
ssl_options.get('verify_cert') or False,
151-
ssl_options.get('cipher'))
151+
ssl_options.get('cipher'),
152+
ssl_options.get('version', None))
152153
self._ssl_active = True
153154

154155
packet = self._protocol.make_auth(

lib/mysql/connector/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
'ssl_verify_cert': False,
5959
'ssl_cipher': None,
6060
'ssl_disabled': False,
61+
'ssl_version': None,
6162
'passwd': None,
6263
'db': None,
6364
'connect_timeout': None,

lib/mysql/connector/network.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ def set_connection_timeout(self, timeout):
403403
self._connection_timeout = timeout
404404

405405
# pylint: disable=C0103
406-
def switch_to_ssl(self, ca, cert, key, verify_cert=False, cipher=None):
406+
def switch_to_ssl(self, ca, cert, key, verify_cert=False, cipher=None,
407+
ssl_version=None):
407408
"""Switch the socket to use SSL"""
408409
if not self.sock:
409410
raise errors.InterfaceError(errno=2048)
@@ -414,10 +415,16 @@ def switch_to_ssl(self, ca, cert, key, verify_cert=False, cipher=None):
414415
else:
415416
cert_reqs = ssl.CERT_NONE
416417

417-
self.sock = ssl.wrap_socket(
418-
self.sock, keyfile=key, certfile=cert, ca_certs=ca,
419-
cert_reqs=cert_reqs, do_handshake_on_connect=False,
420-
ssl_version=ssl.PROTOCOL_TLSv1, ciphers=cipher)
418+
if ssl_version is None:
419+
self.sock = ssl.wrap_socket(
420+
self.sock, keyfile=key, certfile=cert, ca_certs=ca,
421+
cert_reqs=cert_reqs, do_handshake_on_connect=False,
422+
ciphers=cipher)
423+
else:
424+
self.sock = ssl.wrap_socket(
425+
self.sock, keyfile=key, certfile=cert, ca_certs=ca,
426+
cert_reqs=cert_reqs, do_handshake_on_connect=False,
427+
ssl_version=ssl_version, ciphers=cipher)
421428
self.sock.do_handshake()
422429
except NameError:
423430
raise errors.NotSupportedError(

tests/test_bugs.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
import sys
4949

5050
import tests
51+
if tests.SSL_AVAILABLE:
52+
import ssl
53+
5154
from tests import foreach_cnx, cnx_config
5255
from . import PY2
5356
from mysql.connector import (connection, cursor, conversion, protocol,
@@ -64,6 +67,10 @@
6467
CMySQLConnection = None
6568

6669
ERR_NO_CEXT = "C Extension not available"
70+
if tests.SSL_AVAILABLE:
71+
TLS_VERSIONS = {"TLSv1": ssl.PROTOCOL_TLSv1,
72+
"TLSv1.1": ssl.PROTOCOL_TLSv1_1,
73+
"TLSv1.2": ssl.PROTOCOL_TLSv1_2}
6774

6875

6976
@unittest.skipIf(tests.MYSQL_VERSION == (5, 7, 4),
@@ -4486,7 +4493,8 @@ def test_pool_exhaustion(self):
44864493
self.mysql_server.stop()
44874494
self.mysql_server.wait_down()
44884495
cur.execute(sql)
4489-
except mysql.connector.errors.OperationalError:
4496+
except (mysql.connector.errors.OperationalError,
4497+
mysql.connector.errors.ProgrammingError):
44904498
try:
44914499
cur.close()
44924500
cnx.close()
@@ -4932,3 +4940,83 @@ def test_cursor_prepared_statement_with_charset_utf8(self):
49324940
@foreach_cnx()
49334941
def test_cursor_prepared_statement_with_charset_latin1(self):
49344942
self._test_charset('latin1', [u'ñ', u'Ñ'])
4943+
4944+
4945+
@unittest.skipIf(tests.MYSQL_VERSION < (5, 7, 21),
4946+
"Not support for TLSv1.2 or not available by default")
4947+
class Bug26484601(tests.MySQLConnectorTests):
4948+
"""UNABLE TO CONNECT TO A MYSQL SERVER USING TLSV1.2"""
4949+
4950+
def try_connect(self, tls_version, expected_ssl_version):
4951+
config = tests.get_mysql_config().copy()
4952+
config['ssl_version'] = tls_version
4953+
config['ssl_ca'] = ''
4954+
cnx = connection.MySQLConnection(**config)
4955+
query = "SHOW STATUS LIKE 'ssl_version%'"
4956+
cur = cnx.cursor()
4957+
cur.execute(query)
4958+
res = cur.fetchall()
4959+
msg = ("Not using the expected TLS version: {}, instead the "
4960+
"connection used: {}.")
4961+
self.assertEqual(res[0][1], expected_ssl_version,
4962+
msg.format(expected_ssl_version, res))
4963+
4964+
def test_get_connection_using_given_TLS_version(self):
4965+
"""Test connect using the given TLS version
4966+
4967+
The system variable tls_version determines which protocols the
4968+
server is permitted to use from those that are available (note#3).
4969+
+---------------+-----------------------+
4970+
| Variable_name | Value |
4971+
+---------------+-----------------------+
4972+
| tls_version | TLSv1,TLSv1.1,TLSv1.2 |
4973+
+---------------+-----------------------+
4974+
4975+
To restrict and permit only connections with a specific version, the
4976+
variable can be set with those specific versions that will be allowed,
4977+
changing the configuration file.
4978+
4979+
[mysqld]
4980+
tls_version=TLSv1.1,TLSv1.2
4981+
4982+
This test will take adventage of the fact that the connector can
4983+
request to use a defined version of TLS to test that the connector can
4984+
connect to the server using such version instead of changing the
4985+
configuration of the server that will imply the stoping and restarting
4986+
of the server incrementing the time to run the test. In addition the
4987+
test relay in the default value of the 'tls_version' variable is set to
4988+
'TLSv1,TLSv1.1,TLSv1.2' (note#2).
4989+
4990+
On this test a connection will be
4991+
attempted forcing to use a determined version of TLS, (all of them
4992+
must be successfully) finally making sure that the connection was done
4993+
using the given TLS_version using the ssl.version() method (note#3).
4994+
4995+
Notes:
4996+
1.- tls_version is only available on MySQL 5.7
4997+
2.- 5.6.39 does not support TLSv1.2 so for test will be skip. Currently
4998+
in 5.7.21 is set to default values TLSv1,TLSv1.1,TLSv1.2 same as in
4999+
8.0.11+. This test will be only run in such versions and above.
5000+
3.- The ssl.version() method returns the version of tls used in during
5001+
the connection, however the version returned using ssl.cipher() is
5002+
not correct on windows, only indicates the newer version supported.
5003+
5004+
"""
5005+
for tls_v_name, tls_version in TLS_VERSIONS.items():
5006+
self.try_connect(tls_version, tls_v_name)
5007+
5008+
def test_get_connection_using_servers_TLS_version(self):
5009+
"""Test connect using the servers default TLS version
5010+
5011+
The TLS version used during the secured connection is chosen by the
5012+
server at the time the ssl handshake is made if the connector does not
5013+
specifies any specific version to use. The default value of the
5014+
ssl_version is None, however this only mean to the connector that none
5015+
specific version will be chosen by the server when the ssl handshake
5016+
occurs.
5017+
"""
5018+
# The default value for the connector 'ssl_version' is None
5019+
# For the expected version, the server will use the latest version of
5020+
# TLS available "TLSv1.2".
5021+
tls_version = None
5022+
self.try_connect(tls_version, "TLSv1.2")

tests/test_connection.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ def test_caching_sha2_password(self):
818818
self.cnx._handshake['auth_plugin'] = 'caching_sha2_password'
819819
self.cnx._handshake['auth_data'] = b'h4i6oP!OLng9&PD@WrYH'
820820
self.cnx._socket.switch_to_ssl = \
821-
lambda ca, cert, key, verify_cert, cipher: None
821+
lambda ca, cert, key, verify_cert, cipher, ssl_version: None
822822

823823
# Test perform_full_authentication
824824
# Exchange:
@@ -904,7 +904,7 @@ def test_auth_plugin_is_not_supported_but_required(self):
904904
self.cnx._handshake['auth_plugin'] = 'unsupported_auth_plugin'
905905
self.cnx._handshake['auth_data'] = b'abcdef!012345'
906906
self.cnx._socket.switch_to_ssl = \
907-
lambda ca, cert, key, verify_cert, cipher: None
907+
lambda ca, cert, key, verify_cert, cipher, ssl_version: None
908908

909909
# Test perform_full_authentication
910910
# Exchange:
@@ -950,7 +950,7 @@ def test_auth_plugin_fall_back_if_not_supported(self):
950950
self.cnx._handshake['auth_plugin'] = 'unsupported_auth_plugin'
951951
self.cnx._handshake['auth_data'] = b'abcdef!012345'
952952
self.cnx._socket.switch_to_ssl = \
953-
lambda ca, cert, key, verify_cert, cipher: None
953+
lambda ca, cert, key, verify_cert, cipher, ssl_version: None
954954

955955
# Test perform_full_authentication
956956
# Exchange:
@@ -1019,7 +1019,7 @@ def test__do_auth_ssl(self):
10191019
ssl_enabled=True),
10201020
]
10211021
self.cnx._socket.switch_to_ssl = \
1022-
lambda ca, cert, key, verify_cert, cipher: None
1022+
lambda ca, cert, key, verify_cert, cipher, ssl_version: None
10231023
self.cnx._socket.sock.reset()
10241024
self.cnx._socket.sock.add_packets([
10251025
bytearray(b'\x07\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00'),

0 commit comments

Comments
 (0)