Added SSL support for MySQL database connections

Addition of new options SSL_KEY, SSL_CERT, SSL_CA, SSL_CAPATH and SSL_CIPHER
to allow SSL-encrypted connections to MySQL databases.
When needed, these options must be specified in the function call
QSqlDatabase::setConnectOptions() before the call to QSqlDatabase::open().

SSL_KEY = the path name to the key file
SSL_CERT = the path name to the certificate file
SSL_CA = the path name to the certificate authority file
SSL_CAPATH = the path name to a directory that contains trusted SSL CA
certificates in PEM format.
SSL_CIPHER = a list of permissible ciphers to use for SSL encryption.

These options replace CLIENT_SSL (which should not be used any more).

Example:
    db.setConnectOptions("SSL_KEY=client-key.pem;" \
                         "SSL_CERT=client-cert.pem;" \
                         "SSL_CA=server-ca.pem");

[ChangeLog][QtSql] SSL support for MySQL database connections has been added.
Option CLIENT_SSL replaced by SSL_KEY, SSL_CERT, SSL_CA, SSL_CAPATH and
SSL_CIPHER, so that the keys, certificates and cipher can be specified.

Task-number: QtBUG-3500
Change-Id: I8197234b169a818658678d6fcc953c90e83db23e
Reviewed-by: Mark Brand <mabrand@mabrand.nl>
This commit is contained in:
Olivier Delbeke 2015-06-08 20:28:23 +02:00 committed by Mark Brand
parent bca41b1b85
commit 28ceb2ea5e
4 changed files with 76 additions and 35 deletions

View File

@ -80,7 +80,7 @@ if (db.open()) {
//! [4]
...
// MySQL connection
db.setConnectOptions("CLIENT_SSL=1;CLIENT_IGNORE_SPACE=1"); // use an SSL connection to the server
db.setConnectOptions("SSL_KEY=client-key.pem;SSL_CERT=client-cert.pem;SSL_CA=ca-cert.pem;CLIENT_IGNORE_SPACE=1"); // use an SSL connection to the server
if (!db.open()) {
db.setConnectOptions(); // clears the connect option string
...

View File

@ -45,6 +45,7 @@
#include <qstringlist.h>
#include <qtextcodec.h>
#include <qvector.h>
#include <qfile.h>
#include <qdebug.h>
@ -1211,7 +1212,7 @@ static void setOptionFlag(uint &optionFlags, const QString &opt)
else if (opt == QLatin1String("CLIENT_ODBC"))
optionFlags |= CLIENT_ODBC;
else if (opt == QLatin1String("CLIENT_SSL"))
optionFlags |= CLIENT_SSL;
qWarning("QMYSQLDriver: SSL_KEY, SSL_CERT and SSL_CA should be used instead of CLIENT_SSL.");
else
qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData());
}
@ -1235,6 +1236,11 @@ bool QMYSQLDriver::open(const QString& db,
unsigned int optionFlags = Q_CLIENT_MULTI_STATEMENTS;
const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
QString unixSocket;
QString sslCert;
QString sslCA;
QString sslKey;
QString sslCAPath;
QString sslCipher;
#if MYSQL_VERSION_ID >= 50000
my_bool reconnect=false;
uint connectTimeout = 0;
@ -1263,6 +1269,16 @@ bool QMYSQLDriver::open(const QString& db,
writeTimeout = val.toInt();
}
#endif
else if (opt == QLatin1String("SSL_KEY"))
sslKey = val;
else if (opt == QLatin1String("SSL_CERT"))
sslCert = val;
else if (opt == QLatin1String("SSL_CA"))
sslCA = val;
else if (opt == QLatin1String("SSL_CAPATH"))
sslCAPath = val;
else if (opt == QLatin1String("SSL_CIPHER"))
sslCipher = val;
else if (val == QLatin1String("TRUE") || val == QLatin1String("1"))
setOptionFlag(optionFlags, tmp.left(idx).simplified());
else
@ -1273,39 +1289,60 @@ bool QMYSQLDriver::open(const QString& db,
}
}
if ((d->mysql = mysql_init((MYSQL*) 0))) {
if (!(d->mysql = mysql_init((MYSQL*) 0))) {
setLastError(qMakeError(tr("Unable to allocate a MYSQL object"),
QSqlError::ConnectionError, d));
setOpenError(true);
return false;
}
if (!sslKey.isNull() || !sslCert.isNull() || !sslCA.isNull() ||
!sslCAPath.isNull() || !sslCipher.isNull()) {
mysql_ssl_set(d->mysql,
sslKey.isNull() ? static_cast<const char *>(0)
: QFile::encodeName(sslKey).constData(),
sslCert.isNull() ? static_cast<const char *>(0)
: QFile::encodeName(sslCert).constData(),
sslCA.isNull() ? static_cast<const char *>(0)
: QFile::encodeName(sslCA).constData(),
sslCAPath.isNull() ? static_cast<const char *>(0)
: QFile::encodeName(sslCAPath).constData(),
sslCipher.isNull() ? static_cast<const char *>(0)
: sslCipher.toLocal8Bit().constData());
}
#if MYSQL_VERSION_ID >= 50000
if (connectTimeout != 0)
mysql_options(d->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connectTimeout);
if (readTimeout != 0)
mysql_options(d->mysql, MYSQL_OPT_READ_TIMEOUT, &readTimeout);
if (writeTimeout != 0)
mysql_options(d->mysql, MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout);
#endif
if (mysql_real_connect(d->mysql,
host.isNull() ? static_cast<const char *>(0)
: host.toLocal8Bit().constData(),
user.isNull() ? static_cast<const char *>(0)
: user.toLocal8Bit().constData(),
password.isNull() ? static_cast<const char *>(0)
: password.toLocal8Bit().constData(),
db.isNull() ? static_cast<const char *>(0)
: db.toLocal8Bit().constData(),
(port > -1) ? port : 0,
unixSocket.isNull() ? static_cast<const char *>(0)
: unixSocket.toLocal8Bit().constData(),
optionFlags)) {
if (!db.isEmpty() && mysql_select_db(d->mysql, db.toLocal8Bit().constData())) {
setLastError(qMakeError(tr("Unable to open database '%1'").arg(db), QSqlError::ConnectionError, d));
mysql_close(d->mysql);
setOpenError(true);
return false;
}
#if MYSQL_VERSION_ID >= 50000
if (reconnect)
mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect);
if (connectTimeout != 0)
mysql_options(d->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connectTimeout);
if (readTimeout != 0)
mysql_options(d->mysql, MYSQL_OPT_READ_TIMEOUT, &readTimeout);
if (writeTimeout != 0)
mysql_options(d->mysql, MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout);
#endif
if (mysql_real_connect(d->mysql,
host.isNull() ? static_cast<const char *>(0)
: host.toLocal8Bit().constData(),
user.isNull() ? static_cast<const char *>(0)
: user.toLocal8Bit().constData(),
password.isNull() ? static_cast<const char *>(0)
: password.toLocal8Bit().constData(),
db.isNull() ? static_cast<const char *>(0)
: db.toLocal8Bit().constData(),
(port > -1) ? port : 0,
unixSocket.isNull() ? static_cast<const char *>(0)
: unixSocket.toLocal8Bit().constData(),
optionFlags))
{
if (!db.isEmpty() && mysql_select_db(d->mysql, db.toLocal8Bit().constData())) {
setLastError(qMakeError(tr("Unable to open database '%1'").arg(db), QSqlError::ConnectionError, d));
mysql_close(d->mysql);
setOpenError(true);
return false;
}
#if MYSQL_VERSION_ID >= 50000
if (reconnect)
mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect);
#endif
} else {
setLastError(qMakeError(tr("Unable to connect"),
QSqlError::ConnectionError, d));

View File

@ -1218,7 +1218,6 @@ QSqlRecord QSqlDatabase::record(const QString& tablename) const
\li CLIENT_COMPRESS
\li CLIENT_FOUND_ROWS
\li CLIENT_IGNORE_SPACE
\li CLIENT_SSL
\li CLIENT_ODBC
\li CLIENT_NO_SCHEMA
\li CLIENT_INTERACTIVE
@ -1227,6 +1226,11 @@ QSqlRecord QSqlDatabase::record(const QString& tablename) const
\li MYSQL_OPT_CONNECT_TIMEOUT
\li MYSQL_OPT_READ_TIMEOUT
\li MYSQL_OPT_WRITE_TIMEOUT
\li SSL_KEY
\li SSL_CERT
\li SSL_CA
\li SSL_CAPATH
\li SSL_CIPHER
\endlist
\li

View File

@ -261,8 +261,8 @@ public:
// addDb( "QMYSQL3", "testdb", "troll", "trond", "horsehead.qt-project.org" );
// addDb( "QMYSQL3", "testdb", "troll", "trond", "horsehead.qt-project.org", 3307 );
// addDb( "QMYSQL3", "testdb", "troll", "trond", "horsehead.qt-project.org", 3308, "CLIENT_COMPRESS=1;CLIENT_SSL=1" ); // MySQL 4.1.1
// addDb( "QMYSQL3", "testdb", "troll", "trond", "horsehead.qt-project.org", 3309, "CLIENT_COMPRESS=1;CLIENT_SSL=1" ); // MySQL 5.0.18 Linux
// addDb( "QMYSQL3", "testdb", "troll", "trond", "horsehead.qt-project.org", 3308, "CLIENT_COMPRESS=1" ); // MySQL 4.1.1
// addDb( "QMYSQL3", "testdb", "troll", "trond", "horsehead.qt-project.org", 3309, "CLIENT_COMPRESS=1" ); // MySQL 5.0.18 Linux
// addDb( "QMYSQL3", "testdb", "troll", "trond", "silence.qt-project.org" ); // MySQL 5.1.36 Windows
// addDb( "QMYSQL3", "testdb", "testuser", "Ee4Gabf6_", "bq-mysql41.qt-project.org" ); // MySQL 4.1.22-2.el4 linux