From f55c73ede28d4455f555a28e401407326ac9b954 Mon Sep 17 00:00:00 2001 From: Lars Schmertmann Date: Tue, 23 May 2017 14:47:14 +0200 Subject: 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 --- src/network/ssl/qsslconfiguration.cpp | 56 +++++++++ src/network/ssl/qsslconfiguration.h | 5 + src/network/ssl/qsslconfiguration_p.h | 3 + src/network/ssl/qsslcontext_openssl.cpp | 71 +++++++++++ src/network/ssl/qsslcontext_openssl11.cpp | 4 + src/network/ssl/qsslcontext_openssl_p.h | 1 + src/network/ssl/qsslcontext_opensslpre11.cpp | 4 + src/network/ssl/qsslsocket.cpp | 2 + src/network/ssl/qsslsocket_openssl_symbols.cpp | 16 +++ src/network/ssl/qsslsocket_openssl_symbols_p.h | 8 ++ .../auto/network/ssl/qsslsocket/tst_qsslsocket.cpp | 130 +++++++++++++++++++++ 11 files changed, 300 insertions(+) diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp index 75a880f115..cbbbac85fe 100644 --- a/src/network/ssl/qsslconfiguration.cpp +++ b/src/network/ssl/qsslconfiguration.cpp @@ -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 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 &backendConfig) +{ + d->backendConfig = backendConfig; +} + /*! \since 5.3 diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h index 1c57bebd65..b3264126dd 100644 --- a/src/network/ssl/qsslconfiguration.h +++ b/src/network/ssl/qsslconfiguration.h @@ -57,6 +57,7 @@ #define QSSLCONFIGURATION_H #include +#include #include #include #include @@ -149,6 +150,10 @@ public: QSslDiffieHellmanParameters diffieHellmanParameters() const; void setDiffieHellmanParameters(const QSslDiffieHellmanParameters &dhparams); + QMap backendConfig() const; + void setBackendConfigOption(const QByteArray &name, const QVariant &value); + void setBackendConfig(const QMap &backendConfig = QMap()); + static QSslConfiguration defaultConfiguration(); static void setDefaultConfiguration(const QSslConfiguration &configuration); diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h index 6adf2c9b54..38a98239db 100644 --- a/src/network/ssl/qsslconfiguration_p.h +++ b/src/network/ssl/qsslconfiguration_p.h @@ -67,6 +67,7 @@ // We mean it. // +#include #include #include "qsslconfiguration.h" #include "qlist.h" @@ -123,6 +124,8 @@ public: QSslDiffieHellmanParameters dhParams; + QMap backendConfig; + QByteArray sslSession; int sslSessionTicketLifeTimeHint; diff --git a/src/network/ssl/qsslcontext_openssl.cpp b/src/network/ssl/qsslcontext_openssl.cpp index cef503710c..386c280659 100644 --- a/src/network/ssl/qsslcontext_openssl.cpp +++ b/src/network/ssl/qsslcontext_openssl.cpp @@ -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 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 diff --git a/src/network/ssl/qsslcontext_openssl11.cpp b/src/network/ssl/qsslcontext_openssl11.cpp index 787b6ae3f5..7be7be46b8 100644 --- a/src/network/ssl/qsslcontext_openssl11.cpp +++ b/src/network/ssl/qsslcontext_openssl11.cpp @@ -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 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 diff --git a/src/network/ssl/qsslcontext_openssl_p.h b/src/network/ssl/qsslcontext_openssl_p.h index 06a31af5e5..48beebf134 100644 --- a/src/network/ssl/qsslcontext_openssl_p.h +++ b/src/network/ssl/qsslcontext_openssl_p.h @@ -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; diff --git a/src/network/ssl/qsslcontext_opensslpre11.cpp b/src/network/ssl/qsslcontext_opensslpre11.cpp index 9c01c2f2dc..eea821804f 100644 --- a/src/network/ssl/qsslcontext_opensslpre11.cpp +++ b/src/network/ssl/qsslcontext_opensslpre11.cpp @@ -340,6 +340,7 @@ init_context: const_cast(reinterpret_cast(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 diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 9f07b53e4b..833d676192 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -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; } /*! diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp index 1b73135935..9bb67771fd 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -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) diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h index 4cad0231cd..be67f38b64 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -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 diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index 01a7465564..f77afd2364 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -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("serverSigAlgPairs"); + QTest::addColumn("serverProtocol"); + QTest::addColumn("clientSigAlgPairs"); + QTest::addColumn("clientProtocol"); + QTest::addColumn("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::of(&QAbstractSocket::error), &loop, &QEventLoop::quit); + connect(socket, QOverload &>::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()) -- cgit v1.2.3