Search code examples
pythonmysqlsslmysql-connector-python

Set and verify SSL/TLS version used in Python MySQL connection


  • How can I tell the Python MySQL connector which SSL/TLS protocol to use? Either specific (e.g. TLS1.2) or minimum.

  • How can I check which protocol is used on an established connection?

I've got an app that uses mysql-connector-python (8.0.18). I connect something like this:

cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz')

Usually this gives me no trouble, but recently on a web hosting providers server it stopped working. The error I'm now getting is along the lines of:

mysql.connector.errors.InterfaceError: 2026 (HY000): SSL connection error: error:1408F10B:SSL routines:ssl3_get_record:wrong version number

And (connecting through Flask-SQLAlchemy setup):

_mysql_connector.MySQLInterfaceError: SSL connection error: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol

What I can confirm is that if I instead do ssl_disabled=True, as below, it connects properly (but without SSL/TLS I assume):

cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz', ssl_disabled=True)

I cannot alter the providers server, but they say that if I specify a specific version to use, for example TLS1.2, then it should connect properly. They also mention using the ssl.OP_NO_SSLv3 flag, however that is part of the SSLContext setup which I'm unsure how to apply to my connection.

I see that on their MySQL instance (which I cannot edit) they have no value set for:

  • SHOW VARIABLES LIKE 'tls_version'
  • SHOW STATUS LIKE 'Ssl_cipher'
  • SHOW STATUS LIKE 'Ssl_version'

Solution

  • From my recent understanding and help from Andreas answer I ended up with the following connect:

    cnx = mysql.connector.connect(user='u', password='p', host='localhost', database='db', ssl_ca='', ssl_version=ssl.PROTOCOL_TLSv1_2)
    

    Looking at the source, apparently all kwargs provided that are also in mysql.connector.constants.DEFAULT_CONFIGURATION are accepted. This includes several that are not included in the documentation, like ssl_version and ssl_cipher. This mapping from kwargs to the connection appears to happen in MySQLConnectionAbstract.connect. Note that setting ssl_version might require some other kwargs as well. I needed to provide ssl_ca along with it (might vary depending on what your MySQL instance has in SHOW VARIABLES LIKE '%ssl%').

    With that in mind, I've got the following MVCE code:

    import mysql.connector
    import ssl
    
    cnx = mysql.connector.connect(user='u', password='p', host='localhost', database='db', ssl_ca='', ssl_version=ssl.PROTOCOL_TLSv1_2)
    cursor = cnx.cursor()
    cursor.execute("SELECT 1")
    
    for (number,) in cursor:
        print('Number:', number)
    print('SSL active:', cnx._ssl_active)
    print('Connection SSL version:', cnx._ssl.get("version"))
    print("Socket SSL version:", cnx._socket.sock.ssl_version)
    
    cursor.close()
    cnx.close()
    

    Which for me outputs:

    Number: 1
    SSL active: True
    Connection SSL version: _SSLMethod.PROTOCOL_TLSv1_2
    Socket SSL version: _SSLMethod.PROTOCOL_TLSv1_2
    

    If I do the same connection without specifying ssl_version I get:

    Number: 1
    SSL active: True
    Connection SSL version: None
    Socket SSL version: _SSLMethod.PROTOCOL_TLS
    

    If you replace ssl.PROTOCOL_TLSv1_2 with ssl.PROTOCOL_SSLv23 it should at least attempt a different protocol, but might fail if there are issues (e.g. unsupported).