Reputation: 6780
I'm trying to connect to the company's MySQL database. The policy was that I am to connect only using SSL.
I am provided the username
, CA certificate
, certificate
, and private key
.
If I use HeidiSQL, I can connect without problem.
However, I cannot connect using Python 2.7.11 with the mysql-connector-python-rf
(v2.1.3).
Here's my simple connection tester program:
from __future__ import print_function, division, unicode_literals
import mysql.connector
from mysql.connector.constants import ClientFlag
cnx = mysql.connector.connect(
user='myusername',
host='myserver.example.com',
port=3306,
client_flags=[ClientFlag.SSL],
ssl_ca='/path/to/ca.crt',
ssl_cert='/path/to/user.crt',
ssl_key='/path/to/user.key'
)
cnx.close()
I always end up with this exception:
mysql.connector.errors.InterfaceError: 2055: Lost connection to MySQL server at
'myserver.example.com:3306', system error: 1 [SSL: SSL_NEGATIVE_LENGTH] dh key too small
(_ssl.c:590)
I've tried searching for answers, but there doesn't seem to be a solution except changing the settings on the server side, which is simply a no-no.
How can I fix and/or work around this issue?
Update for More Info: I'm creating my program on Windows 10, using PyCharm 5.0.3
Upvotes: 3
Views: 5934
Reputation: 3442
As of this writing, with mysql-connector-python 2.1.4 pure python module from Oracle (the one you are using is a fork of version 2.1.3) supports the undocumented connection configuration kwarg ssl_cipher
in your connection string (since it basically passes it to python's ssl
module). So strip all Diffie-Hellman ciphers from the cipher list and you may be able to work around this problem, depending on whether your mysql server supports non-Diffie-Hellman ciphers. The following works on official Python 2.7.12 amd64 win32 against MySQL 5.6.17-65.0-rel65.0-log . Note: This only works when dealing with the pure python version of the module!. If you use the compiled C extension module, it may not accept ssl_cipher, YMMV.
import mysql.connector
import getpass
dsn = {
'database': 'INFORMATION_SCHEMA',
'host': 'mysqlserver',
'port': '3306',
'ssl_ca': '',
# ^^^ this sets cert_reqs = ssl.CERT_NONE
# in mysql/connector/network.py:415
# so no server cert verification is attempted
'use_pure': True
# setting ssl_cipher may only work with the pure python driver
# but this is the default anyway
}
dsn['user'] = raw_input('Enter Username: ')
dsn['password'] = getpass.getpass('Enter password: ')
try:
dbconn = mysql.connector.connect(**dsn)
# this will raise the 'Weak DH key' exception
except mysql.connector.errors.InterfaceError as e:
print e
dsn['ssl_cipher'] = 'HIGH:!DH:!aNULL'
# this is a standard openssl ciphersuite string
# where !DH and !aNULL means don't use any DH ciphers or null ciphers
# this option is officially undocumented
try:
dbconn = mysql.connector.connect(**dsn)
except mysql.connector.errors.InterfaceError:
raise
else:
assert isinstance(dbconn, mysql.connector.connection.MySQLConnection)
In mysql-connector-python 2.1.4, the following lines in the module's source show how this works:
mysql/connector/abstracts.py: Lines 298 - 313:
for key, value in config.items():
try:
DEFAULT_CONFIGURATION[key]
except KeyError:
raise AttributeError("Unsupported argument '{0}'".format(key))
# SSL Configuration
if key.startswith('ssl_'):
set_ssl_flag = True
self._ssl.update({key.replace('ssl_', ''): value})
else:
attribute = '_' + key
try:
setattr(self, attribute, value.strip())
except AttributeError:
setattr(self, attribute, value)
Then in mysql/connector/connection.py lines 130-134
:
if client_flags & ClientFlag.SSL and ssl_options:
packet = self._protocol.make_auth_ssl(charset=charset,
client_flags=client_flags)
self._socket.send(packet)
self._socket.switch_to_ssl(**ssl_options)
_socket.switch_to_ssl()
is found in mysql/connector/network.py lines 406-421
:
def switch_to_ssl(self, ca, cert, key, verify_cert=False, cipher=None):
"""Switch the socket to use SSL"""
if not self.sock:
raise errors.InterfaceError(errno=2048)
try:
if verify_cert:
cert_reqs = ssl.CERT_REQUIRED
else:
cert_reqs = ssl.CERT_NONE
self.sock = ssl.wrap_socket(
self.sock, keyfile=key, certfile=cert, ca_certs=ca,
cert_reqs=cert_reqs, do_handshake_on_connect=False,
ssl_version=ssl.PROTOCOL_TLSv1, ciphers=cipher)
self.sock.do_handshake()
Upvotes: 4
Reputation: 6780
Out of desperation, I installed the module MySQL-python
and replaced the connection method as follows:
import MySQLdb
ssl = {
'cert': '/path/to/user.crt',
'key': '/path/to/user.key'
}
cnx = MySQLdb.connect(
host='myserver.example.com', port=3306, user='myusername',
ssl=ssl
)
... lo and behold! I have no problems with DH whatsoever!! It works!!!
So one alternative solution if someone happens upon this problem, is to change the module used to connect.
Upvotes: 0
Reputation: 123270
This is a security issue and the server side is affected by a weak DH key. Current versions of OpenSSL enforce a minimal length of the DH key to protect against attacks using weak DH keys.
If your company is really interested in security and not just believe that magically sprinkling some (insecure) SSL around will do it, then they should fix the problem on the server side. To work around the problem on the client side your Python would need to be linked against an older version of OpenSSL which does not yet enforce a minimal DH key length.
Upvotes: 3