Add the ability to prefer the cipher preferences specified by the server.

Currently the cipher preferred by the client will always be used for SSL
connections. This change makes it so that by default the ciphers
specified by the server will be used (like the Apache SSLHonorCipherOrder
option). This behavior can be disabled using a new SslOption.

[ChangeLog][QtNetwork][QSslSocket] QSslSocket will now default to using
the cipher preferences of the server socket when used as an SSL server.
This can be disabled using the QSslConfiguration.

Change-Id: I2d16d10145cf88a7412f30ef960d87024777de1c
Reviewed-by: Peter Hartmann <peter-qt@hartmann.tk>
This commit is contained in:
Richard J. Moore 2015-04-18 11:34:26 +01:00
parent 7b97f53e71
commit 00f0a4119c
4 changed files with 94 additions and 8 deletions

View File

@ -166,6 +166,10 @@ Q_LOGGING_CATEGORY(lcSsl, "qt.network.ssl");
in ASN.1 format as returned by QSslConfiguration::sessionTicket(). Enabling
this feature adds memory overhead of approximately 1K per used session
ticket.
\value SslOptionDisableServerCipherPreference Disables selecting the cipher
chosen based on the servers preferences rather than the order ciphers were
sent by the client. This option is only relevant to server sockets, and is
only honored by the OpenSSL backend.
By default, SslOptionDisableEmptyFragments is turned on since this causes
problems with a large number of servers. SslOptionDisableLegacyRenegotiation

View File

@ -95,7 +95,8 @@ namespace QSsl {
SslOptionDisableServerNameIndication = 0x08,
SslOptionDisableLegacyRenegotiation = 0x10,
SslOptionDisableSessionSharing = 0x20,
SslOptionDisableSessionPersistence = 0x40
SslOptionDisableSessionPersistence = 0x40,
SslOptionDisableServerCipherPreference = 0x80
};
Q_DECLARE_FLAGS(SslOptions, SslOption)
}

View File

@ -344,6 +344,9 @@ long QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SslProtocol protocol, Q
options |= SSL_OP_NO_COMPRESSION;
#endif
if (!(sslOptions & QSsl::SslOptionDisableServerCipherPreference))
options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
return options;
}

View File

@ -170,6 +170,9 @@ private slots:
void protocol();
void protocolServerSide_data();
void protocolServerSide();
#ifndef QT_NO_OPENSSL
void serverCipherPreferences();
#endif // QT_NO_OPENSSL
void setCaCertificates();
void setLocalCertificate();
void localCertificateChain();
@ -1063,6 +1066,7 @@ public:
const QString &certFile = SRCDIR "certs/fluke.cert",
const QString &interFile = QString())
: socket(0),
config(QSslConfiguration::defaultConfiguration()),
ignoreSslErrors(true),
peerVerifyMode(QSslSocket::AutoVerifyPeer),
protocol(QSsl::TlsV1_0),
@ -1071,6 +1075,7 @@ public:
m_interFile(interFile)
{ }
QSslSocket *socket;
QSslConfiguration config;
QString addCaCertificates;
bool ignoreSslErrors;
QSslSocket::PeerVerifyMode peerVerifyMode;
@ -1084,6 +1089,7 @@ protected:
void incomingConnection(qintptr socketDescriptor)
{
socket = new QSslSocket(this);
socket->setSslConfiguration(config);
socket->setPeerVerifyMode(peerVerifyMode);
socket->setProtocol(protocol);
if (ignoreSslErrors)
@ -1254,6 +1260,78 @@ void tst_QSslSocket::protocolServerSide()
QCOMPARE(client->isEncrypted(), works);
}
#ifndef QT_NO_OPENSSL
void tst_QSslSocket::serverCipherPreferences()
{
if (!QSslSocket::supportsSsl()) {
qWarning("SSL not supported, skipping test");
return;
}
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
return;
// First using the default (server preference)
{
SslServer server;
server.ciphers = QString("AES128-SHA:AES256-SHA");
QVERIFY(server.listen());
QEventLoop loop;
QTimer::singleShot(5000, &loop, SLOT(quit()));
QSslSocketPtr client(new QSslSocket);
socket = client.data();
socket->setCiphers("AES256-SHA:AES128-SHA");
// upon SSL wrong version error, error will be triggered, not sslErrors
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), &loop, SLOT(quit()));
connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot()));
connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit()));
client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort());
loop.exec();
QVERIFY(client->isEncrypted());
QCOMPARE(client->sessionCipher().name(), QString("AES128-SHA"));
}
{
// Now using the client preferences
SslServer server;
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setSslOption(QSsl::SslOptionDisableServerCipherPreference, true);
server.config = config;
server.ciphers = QString("AES128-SHA:AES256-SHA");
QVERIFY(server.listen());
QEventLoop loop;
QTimer::singleShot(5000, &loop, SLOT(quit()));
QSslSocketPtr client(new QSslSocket);
socket = client.data();
socket->setCiphers("AES256-SHA:AES128-SHA");
// upon SSL wrong version error, error will be triggered, not sslErrors
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), &loop, SLOT(quit()));
connect(socket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(ignoreErrorSlot()));
connect(socket, SIGNAL(encrypted()), &loop, SLOT(quit()));
client->connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort());
loop.exec();
QVERIFY(client->isEncrypted());
QCOMPARE(client->sessionCipher().name(), QString("AES256-SHA"));
}
}
#endif // QT_NO_OPENSSL
void tst_QSslSocket::setCaCertificates()
{
if (!QSslSocket::supportsSsl())
@ -2354,28 +2432,28 @@ void tst_QSslSocket::sslOptions()
#ifdef SSL_OP_NO_COMPRESSION
QCOMPARE(QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SecureProtocols,
QSslConfigurationPrivate::defaultSslOptions),
long(SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION));
long(SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION|SSL_OP_CIPHER_SERVER_PREFERENCE));
#else
QCOMPARE(QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SecureProtocols,
QSslConfigurationPrivate::defaultSslOptions),
long(SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3));
long(SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_CIPHER_SERVER_PREFERENCE));
#endif
QCOMPARE(QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SecureProtocols,
QSsl::SslOptionDisableEmptyFragments
|QSsl::SslOptionDisableLegacyRenegotiation),
long(SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3));
long(SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_CIPHER_SERVER_PREFERENCE));
#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
QCOMPARE(QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SecureProtocols,
QSsl::SslOptionDisableEmptyFragments),
long((SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)));
long((SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION|SSL_OP_CIPHER_SERVER_PREFERENCE)));
#endif
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
QCOMPARE(QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SecureProtocols,
QSsl::SslOptionDisableLegacyRenegotiation),
long((SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3) & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS));
long((SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_CIPHER_SERVER_PREFERENCE) & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS));
#endif
#ifdef SSL_OP_NO_TICKET
@ -2383,7 +2461,7 @@ void tst_QSslSocket::sslOptions()
QSsl::SslOptionDisableEmptyFragments
|QSsl::SslOptionDisableLegacyRenegotiation
|QSsl::SslOptionDisableSessionTickets),
long((SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TICKET)));
long((SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TICKET|SSL_OP_CIPHER_SERVER_PREFERENCE)));
#endif
#ifdef SSL_OP_NO_TICKET
@ -2393,7 +2471,7 @@ void tst_QSslSocket::sslOptions()
|QSsl::SslOptionDisableLegacyRenegotiation
|QSsl::SslOptionDisableSessionTickets
|QSsl::SslOptionDisableCompression),
long((SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TICKET|SSL_OP_NO_COMPRESSION)));
long((SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TICKET|SSL_OP_NO_COMPRESSION|SSL_OP_CIPHER_SERVER_PREFERENCE)));
#endif
#endif
}