Introduce QSslConfiguration::backendConfig

With this change it is possible to use all supported
configurations in different backends without any new interfaces.

Change-Id: Ib233539a970681d30ae3907258730e491f8d3531
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
bb10
Lars Schmertmann 2017-05-23 14:47:14 +02:00 committed by Lars Schmertmann
parent dcfb6f9d18
commit f55c73ede2
11 changed files with 300 additions and 0 deletions

View File

@ -221,6 +221,7 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const
d->peerVerifyMode == other.d->peerVerifyMode &&
d->peerVerifyDepth == other.d->peerVerifyDepth &&
d->allowRootCertOnDemandLoading == other.d->allowRootCertOnDemandLoading &&
d->backendConfig == other.d->backendConfig &&
d->sslOptions == other.d->sslOptions &&
d->sslSession == other.d->sslSession &&
d->sslSessionTicketLifeTimeHint == other.d->sslSessionTicketLifeTimeHint &&
@ -263,6 +264,7 @@ bool QSslConfiguration::isNull() const
d->privateKey.isNull() &&
d->peerCertificate.isNull() &&
d->peerCertificateChain.count() == 0 &&
d->backendConfig.isEmpty() &&
d->sslOptions == QSslConfigurationPrivate::defaultSslOptions &&
d->sslSession.isNull() &&
d->sslSessionTicketLifeTimeHint == -1 &&
@ -869,6 +871,60 @@ void QSslConfiguration::setDiffieHellmanParameters(const QSslDiffieHellmanParame
d->dhParams = dhparams;
}
/*!
\since 5.11
Returns the backend-specific configuration.
Only options set by addBackendConfig() or setBackendConfig() will be
returned. The internal standard configuration of the backend is not reported.
\sa setBackendConfigOption(), setBackendConfig()
*/
QMap<QByteArray, QVariant> QSslConfiguration::backendConfig() const
{
return d->backendConfig;
}
/*!
\since 5.11
Sets an option in the backend-specific configuration.
Options supported by the OpenSSL (>= 1.0.2) backend are available in the \l
{https://www.openssl.org/docs/manmaster/man3/SSL_CONF_cmd.html#SUPPORTED-CONFIGURATION-FILE-COMMANDS}
{supported configuration file commands} documentation. The expected type for
the \a value parameter is a QByteArray for all options. The \l
{https://www.openssl.org/docs/manmaster/man3/SSL_CONF_cmd.html#EXAMPLES}{examples}
show how to use some of the options.
\note The backend-specific configuration will be applied after the general
configuration. Using the backend-specific configuration to set a general
configuration option again will overwrite the general configuration option.
\sa backendConfig(), setBackendConfig()
*/
void QSslConfiguration::setBackendConfigOption(const QByteArray &name, const QVariant &value)
{
d->backendConfig[name] = value;
}
/*!
\since 5.11
Sets or clears the backend-specific configuration.
Without a \a backendConfig parameter this function will clear the
backend-specific configuration. More information about the supported
options is available in the documentation of addBackendConfig().
\sa backendConfig(), setBackendConfigOption()
*/
void QSslConfiguration::setBackendConfig(const QMap<QByteArray, QVariant> &backendConfig)
{
d->backendConfig = backendConfig;
}
/*!
\since 5.3

View File

@ -57,6 +57,7 @@
#define QSSLCONFIGURATION_H
#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/qmap.h>
#include <QtCore/qshareddata.h>
#include <QtNetwork/qsslsocket.h>
#include <QtNetwork/qssl.h>
@ -149,6 +150,10 @@ public:
QSslDiffieHellmanParameters diffieHellmanParameters() const;
void setDiffieHellmanParameters(const QSslDiffieHellmanParameters &dhparams);
QMap<QByteArray, QVariant> backendConfig() const;
void setBackendConfigOption(const QByteArray &name, const QVariant &value);
void setBackendConfig(const QMap<QByteArray, QVariant> &backendConfig = QMap<QByteArray, QVariant>());
static QSslConfiguration defaultConfiguration();
static void setDefaultConfiguration(const QSslConfiguration &configuration);

View File

@ -67,6 +67,7 @@
// We mean it.
//
#include <QtCore/qmap.h>
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qsslconfiguration.h"
#include "qlist.h"
@ -123,6 +124,8 @@ public:
QSslDiffieHellmanParameters dhParams;
QMap<QByteArray, QVariant> backendConfig;
QByteArray sslSession;
int sslSessionTicketLifeTimeHint;

View File

@ -49,6 +49,11 @@
QT_BEGIN_NAMESPACE
static inline QString msgErrorSettingBackendConfig(const QString &why)
{
return QSslSocket::tr("Error when setting the OpenSSL configuration (%1)").arg(why);
}
QSslContext::QSslContext()
: ctx(0),
pkey(0),
@ -237,4 +242,70 @@ QString QSslContext::errorString() const
return errorStr;
}
// static
void QSslContext::applyBackendConfig(QSslContext *sslContext)
{
if (sslContext->sslConfiguration.backendConfig().isEmpty())
return;
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
if (QSslSocket::sslLibraryVersionNumber() >= 0x10002000L) {
QSharedPointer<SSL_CONF_CTX> cctx(q_SSL_CONF_CTX_new(), &q_SSL_CONF_CTX_free);
if (cctx) {
q_SSL_CONF_CTX_set_ssl_ctx(cctx.data(), sslContext->ctx);
q_SSL_CONF_CTX_set_flags(cctx.data(), SSL_CONF_FLAG_FILE);
const auto &backendConfig = sslContext->sslConfiguration.backendConfig();
for (auto i = backendConfig.constBegin(); i != backendConfig.constEnd(); ++i) {
if (!i.value().canConvert(QMetaType::QByteArray)) {
sslContext->errorCode = QSslError::UnspecifiedError;
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("Expecting QByteArray for %1").arg(
QString::fromUtf8(i.key())));
return;
}
const QByteArray &value = i.value().toByteArray();
const int result = q_SSL_CONF_cmd(cctx.data(), i.key().constData(), value.constData());
if (result == 2)
continue;
sslContext->errorCode = QSslError::UnspecifiedError;
switch (result) {
case 0:
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("An error occurred attempting to set %1 to %2").arg(
QString::fromUtf8(i.key()), QString::fromUtf8(value)));
return;
case 1:
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("Wrong value for %1 (%2)").arg(
QString::fromUtf8(i.key()), QString::fromUtf8(value)));
return;
default:
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("Unrecognized command %1 = %2").arg(
QString::fromUtf8(i.key()), QString::fromUtf8(value)));
return;
}
}
if (q_SSL_CONF_CTX_finish(cctx.data()) == 0) {
sslContext->errorStr = msgErrorSettingBackendConfig(QSslSocket::tr("SSL_CONF_finish() failed"));
sslContext->errorCode = QSslError::UnspecifiedError;
}
} else {
sslContext->errorStr = msgErrorSettingBackendConfig(QSslSocket::tr("SSL_CONF_CTX_new() failed"));
sslContext->errorCode = QSslError::UnspecifiedError;
}
} else
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
{
// specific algorithms requested, but not possible to set
sslContext->errorCode = QSslError::UnspecifiedError;
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("OpenSSL version too old, need at least v1.0.2"));
}
}
QT_END_NAMESPACE

View File

@ -260,6 +260,7 @@ init_context:
#ifdef OPENSSL_NO_EC
sslContext->errorStr = msgErrorSettingEllipticCurves(QSslSocket::tr("OpenSSL version with disabled elliptic curves"));
sslContext->errorCode = QSslError::UnspecifiedError;
return;
#else
// Set the curves to be used.
std::vector<int> curves;
@ -269,9 +270,12 @@ init_context:
if (!q_SSL_CTX_ctrl(sslContext->ctx, SSL_CTRL_SET_CURVES, long(curves.size()), &curves[0])) {
sslContext->errorStr = msgErrorSettingEllipticCurves(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
sslContext->errorCode = QSslError::UnspecifiedError;
return;
}
#endif
}
applyBackendConfig(sslContext);
}
QT_END_NAMESPACE

View File

@ -107,6 +107,7 @@ protected:
private:
static void initSslContext(QSslContext* sslContext, QSslSocket::SslMode mode, const QSslConfiguration &configuration,
bool allowRootCertOnDemandLoading);
static void applyBackendConfig(QSslContext *sslContext);
private:
SSL_CTX* ctx;

View File

@ -340,6 +340,7 @@ init_context:
const_cast<int *>(reinterpret_cast<const int *>(qcurves.data())))) {
sslContext->errorStr = msgErrorSettingEllipticCurves(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
sslContext->errorCode = QSslError::UnspecifiedError;
return;
}
} else
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_EC)
@ -347,8 +348,11 @@ init_context:
// specific curves requested, but not possible to set -> error
sslContext->errorStr = msgErrorSettingEllipticCurves(QSslSocket::tr("OpenSSL version too old, need at least v1.0.2"));
sslContext->errorCode = QSslError::UnspecifiedError;
return;
}
}
applyBackendConfig(sslContext);
}
QT_END_NAMESPACE

View File

@ -922,6 +922,7 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration)
d->configuration.peerVerifyDepth = configuration.peerVerifyDepth();
d->configuration.peerVerifyMode = configuration.peerVerifyMode();
d->configuration.protocol = configuration.protocol();
d->configuration.backendConfig = configuration.backendConfig();
d->configuration.sslOptions = configuration.d->sslOptions;
d->configuration.sslSession = configuration.sessionTicket();
d->configuration.sslSessionTicketLifeTimeHint = configuration.sessionTicketLifeTimeHint();
@ -2256,6 +2257,7 @@ void QSslConfigurationPrivate::deepCopyDefaultConfiguration(QSslConfigurationPri
ptr->peerVerifyDepth = global->peerVerifyDepth;
ptr->sslOptions = global->sslOptions;
ptr->ellipticCurves = global->ellipticCurves;
ptr->backendConfig = global->backendConfig;
}
/*!

View File

@ -402,6 +402,14 @@ DEFINEFUNC2(int, SSL_CTX_use_PrivateKey, SSL_CTX *a, a, EVP_PKEY *b, b, return -
DEFINEFUNC2(int, SSL_CTX_use_RSAPrivateKey, SSL_CTX *a, a, RSA *b, b, return -1, return)
DEFINEFUNC3(int, SSL_CTX_use_PrivateKey_file, SSL_CTX *a, a, const char *b, b, int c, c, return -1, return)
DEFINEFUNC(X509_STORE *, SSL_CTX_get_cert_store, const SSL_CTX *a, a, return 0, return)
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
DEFINEFUNC(SSL_CONF_CTX *, SSL_CONF_CTX_new, DUMMYARG, DUMMYARG, return 0, return);
DEFINEFUNC(void, SSL_CONF_CTX_free, SSL_CONF_CTX *a, a, return ,return);
DEFINEFUNC2(void, SSL_CONF_CTX_set_ssl_ctx, SSL_CONF_CTX *a, a, SSL_CTX *b, b, return, return);
DEFINEFUNC2(unsigned int, SSL_CONF_CTX_set_flags, SSL_CONF_CTX *a, a, unsigned int b, b, return 0, return);
DEFINEFUNC(int, SSL_CONF_CTX_finish, SSL_CONF_CTX *a, a, return 0, return);
DEFINEFUNC3(int, SSL_CONF_cmd, SSL_CONF_CTX *a, a, const char *b, b, const char *c, c, return 0, return);
#endif
DEFINEFUNC(void, SSL_free, SSL *a, a, return, DUMMYARG)
DEFINEFUNC(STACK_OF(SSL_CIPHER) *, SSL_get_ciphers, const SSL *a, a, return 0, return)
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
@ -1105,6 +1113,14 @@ bool q_resolveOpenSslSymbols()
RESOLVEFUNC(SSL_CTX_use_RSAPrivateKey)
RESOLVEFUNC(SSL_CTX_use_PrivateKey_file)
RESOLVEFUNC(SSL_CTX_get_cert_store);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
RESOLVEFUNC(SSL_CONF_CTX_new);
RESOLVEFUNC(SSL_CONF_CTX_free);
RESOLVEFUNC(SSL_CONF_CTX_set_ssl_ctx);
RESOLVEFUNC(SSL_CONF_CTX_set_flags);
RESOLVEFUNC(SSL_CONF_CTX_finish);
RESOLVEFUNC(SSL_CONF_cmd);
#endif
RESOLVEFUNC(SSL_accept)
RESOLVEFUNC(SSL_clear)
RESOLVEFUNC(SSL_connect)

View File

@ -352,6 +352,14 @@ int q_SSL_CTX_use_PrivateKey(SSL_CTX *a, EVP_PKEY *b);
int q_SSL_CTX_use_RSAPrivateKey(SSL_CTX *a, RSA *b);
int q_SSL_CTX_use_PrivateKey_file(SSL_CTX *a, const char *b, int c);
X509_STORE *q_SSL_CTX_get_cert_store(const SSL_CTX *a);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
SSL_CONF_CTX *q_SSL_CONF_CTX_new();
void q_SSL_CONF_CTX_free(SSL_CONF_CTX *a);
void q_SSL_CONF_CTX_set_ssl_ctx(SSL_CONF_CTX *a, SSL_CTX *b);
unsigned int q_SSL_CONF_CTX_set_flags(SSL_CONF_CTX *a, unsigned int b);
int q_SSL_CONF_CTX_finish(SSL_CONF_CTX *a);
int q_SSL_CONF_cmd(SSL_CONF_CTX *a, const char *b, const char *c);
#endif
void q_SSL_free(SSL *a);
STACK_OF(SSL_CIPHER) *q_SSL_get_ciphers(const SSL *a);
#if OPENSSL_VERSION_NUMBER >= 0x10000000L

View File

@ -238,6 +238,8 @@ private slots:
void allowedProtocolNegotiation();
void pskServer();
void forwardReadChannelFinished();
void signatureAlgorithm_data();
void signatureAlgorithm();
#endif
void setEmptyDefaultConfiguration(); // this test should be last
@ -3908,6 +3910,134 @@ void tst_QSslSocket::pskServer()
QCOMPARE(disconnectedSpy.count(), 1);
}
void tst_QSslSocket::signatureAlgorithm_data()
{
if (!QSslSocket::supportsSsl())
QSKIP("Signature algorithms cannot be tested without SSL support");
if (QSslSocket::sslLibraryVersionNumber() < 0x10002000L)
QSKIP("Signature algorithms cannot be tested with OpenSSL < 1.0.2");
QTest::addColumn<QByteArrayList>("serverSigAlgPairs");
QTest::addColumn<QSsl::SslProtocol>("serverProtocol");
QTest::addColumn<QByteArrayList>("clientSigAlgPairs");
QTest::addColumn<QSsl::SslProtocol>("clientProtocol");
QTest::addColumn<QAbstractSocket::SocketState>("state");
const QByteArray dsaSha1("DSA+SHA1");
const QByteArray ecdsaSha1("ECDSA+SHA1");
const QByteArray ecdsaSha512("ECDSA+SHA512");
const QByteArray rsaSha256("RSA+SHA256");
const QByteArray rsaSha384("RSA+SHA384");
const QByteArray rsaSha512("RSA+SHA512");
QTest::newRow("match_TlsV1_2")
<< QByteArrayList({rsaSha256})
<< QSsl::TlsV1_2
<< QByteArrayList({rsaSha256})
<< QSsl::AnyProtocol
<< QAbstractSocket::ConnectedState;
QTest::newRow("no_hashalg_match_TlsV1_2")
<< QByteArrayList({rsaSha256})
<< QSsl::TlsV1_2
<< QByteArrayList({rsaSha512})
<< QSsl::AnyProtocol
<< QAbstractSocket::UnconnectedState;
QTest::newRow("no_sigalg_match_TlsV1_2")
<< QByteArrayList({ecdsaSha512})
<< QSsl::TlsV1_2
<< QByteArrayList({rsaSha512})
<< QSsl::AnyProtocol
<< QAbstractSocket::UnconnectedState;
QTest::newRow("no_cipher_match_AnyProtocol")
<< QByteArrayList({rsaSha512})
<< QSsl::AnyProtocol
<< QByteArrayList({ecdsaSha512})
<< QSsl::AnyProtocol
<< QAbstractSocket::UnconnectedState;
QTest::newRow("match_multiple-choice")
<< QByteArrayList({dsaSha1, rsaSha256, rsaSha384, rsaSha512})
<< QSsl::AnyProtocol
<< QByteArrayList({ecdsaSha1, rsaSha384, rsaSha512, ecdsaSha512})
<< QSsl::AnyProtocol
<< QAbstractSocket::ConnectedState;
QTest::newRow("match_client_longer")
<< QByteArrayList({dsaSha1, rsaSha256})
<< QSsl::AnyProtocol
<< QByteArrayList({ecdsaSha1, ecdsaSha512, rsaSha256})
<< QSsl::AnyProtocol
<< QAbstractSocket::ConnectedState;
QTest::newRow("match_server_longer")
<< QByteArrayList({ecdsaSha1, ecdsaSha512, rsaSha256})
<< QSsl::AnyProtocol
<< QByteArrayList({dsaSha1, rsaSha256})
<< QSsl::AnyProtocol
<< QAbstractSocket::ConnectedState;
// signature algorithms do not match, but are ignored because the tls version is not v1.2
QTest::newRow("client_ignore_TlsV1_1")
<< QByteArrayList({rsaSha256})
<< QSsl::TlsV1_1
<< QByteArrayList({rsaSha512})
<< QSsl::AnyProtocol
<< QAbstractSocket::ConnectedState;
QTest::newRow("server_ignore_TlsV1_1")
<< QByteArrayList({rsaSha256})
<< QSsl::AnyProtocol
<< QByteArrayList({rsaSha512})
<< QSsl::TlsV1_1
<< QAbstractSocket::ConnectedState;
QTest::newRow("client_ignore_TlsV1_0")
<< QByteArrayList({rsaSha256})
<< QSsl::TlsV1_0
<< QByteArrayList({rsaSha512})
<< QSsl::AnyProtocol
<< QAbstractSocket::ConnectedState;
QTest::newRow("server_ignore_TlsV1_0")
<< QByteArrayList({rsaSha256})
<< QSsl::AnyProtocol
<< QByteArrayList({rsaSha512})
<< QSsl::TlsV1_0
<< QAbstractSocket::ConnectedState;
}
void tst_QSslSocket::signatureAlgorithm()
{
QFETCH_GLOBAL(bool, setProxy);
if (setProxy)
QSKIP("Test not adapted for use with proxying");
QFETCH(QByteArrayList, serverSigAlgPairs);
QFETCH(QSsl::SslProtocol, serverProtocol);
QFETCH(QByteArrayList, clientSigAlgPairs);
QFETCH(QSsl::SslProtocol, clientProtocol);
QFETCH(QAbstractSocket::SocketState, state);
SslServer server;
server.protocol = serverProtocol;
server.config.setCiphers({QSslCipher("ECDHE-RSA-AES256-SHA")});
server.config.setBackendConfigOption(QByteArrayLiteral("SignatureAlgorithms"), serverSigAlgPairs.join(':'));
QVERIFY(server.listen());
QSslConfiguration clientConfig = QSslConfiguration::defaultConfiguration();
clientConfig.setProtocol(clientProtocol);
clientConfig.setBackendConfigOption(QByteArrayLiteral("SignatureAlgorithms"), clientSigAlgPairs.join(':'));
QSslSocket client;
client.setSslConfiguration(clientConfig);
socket = &client;
QEventLoop loop;
QTimer::singleShot(5000, &loop, &QEventLoop::quit);
connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), &loop, &QEventLoop::quit);
connect(socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors), this, &tst_QSslSocket::ignoreErrorSlot);
connect(socket, &QSslSocket::encrypted, &loop, &QEventLoop::quit);
client.connectToHostEncrypted(QHostAddress(QHostAddress::LocalHost).toString(), server.serverPort());
loop.exec();
socket = nullptr;
QCOMPARE(client.state(), state);
}
void tst_QSslSocket::forwardReadChannelFinished()
{
if (!QSslSocket::supportsSsl())