diff options
Diffstat (limited to 'src/plugins/tls/openssl/qsslcontext_openssl.cpp')
-rw-r--r-- | src/plugins/tls/openssl/qsslcontext_openssl.cpp | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/src/plugins/tls/openssl/qsslcontext_openssl.cpp b/src/plugins/tls/openssl/qsslcontext_openssl.cpp new file mode 100644 index 0000000000..75c192bd01 --- /dev/null +++ b/src/plugins/tls/openssl/qsslcontext_openssl.cpp @@ -0,0 +1,815 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2014 BlackBerry Limited. All rights reserved. +// Copyright (C) 2014 Governikus GmbH & Co. KG. +// Copyright (C) 2016 Richard J. Moore <rich@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtNetwork/qsslsocket.h> +#include <QtNetwork/qssldiffiehellmanparameters.h> + +#include "qsslsocket_openssl_symbols_p.h" +#include "qsslcontext_openssl_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qtlskey_openssl_p.h" +#include "qopenssl_p.h" + +#include <QtNetwork/private/qssl_p.h> +#include <QtNetwork/private/qsslsocket_p.h> +#include <QtNetwork/private/qtlsbackend_p.h> + +#include <QtNetwork/private/qssldiffiehellmanparameters_p.h> + +#include <vector> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(bool, forceSecurityLevel) + +namespace QTlsPrivate +{ +// These callback functions are defined in qtls_openssl.cpp. +extern "C" int q_X509Callback(int ok, X509_STORE_CTX *ctx); +extern "C" int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx); + +#if QT_CONFIG(ocsp) +extern "C" int qt_OCSP_status_server_callback(SSL *ssl, void *); +#endif // ocsp + +} // namespace QTlsPrivate + +#if QT_CONFIG(dtls) +// defined in qdtls_openssl.cpp: +namespace dtlscallbacks +{ +extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx); +extern "C" int q_generate_cookie_callback(SSL *ssl, unsigned char *dst, + unsigned *cookieLength); +extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie, + unsigned cookieLength); +} +#endif // dtls + +#ifdef TLS1_3_VERSION +extern "C" int q_ssl_sess_set_new_cb(SSL *context, SSL_SESSION *session); +#endif // TLS1_3_VERSION + +static inline QString msgErrorSettingBackendConfig(const QString &why) +{ + return QSslSocket::tr("Error when setting the OpenSSL configuration (%1)").arg(why); +} + +static inline QString msgErrorSettingEllipticCurves(const QString &why) +{ + return QSslSocket::tr("Error when setting the elliptic curves (%1)").arg(why); +} + +qssloptions QSslContext::setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions) +{ + qssloptions options; + switch (protocol) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + case QSsl::TlsV1_0OrLater: + options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + break; + case QSsl::TlsV1_1OrLater: + options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; + break; +QT_WARNING_POP + case QSsl::SecureProtocols: + case QSsl::TlsV1_2OrLater: + options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1; + break; + case QSsl::TlsV1_3OrLater: + options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2; + break; + default: + options = SSL_OP_ALL; + } + + // This option is disabled by default, so we need to be able to clear it + if (sslOptions & QSsl::SslOptionDisableEmptyFragments) + options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + else + options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + +#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + // This option is disabled by default, so we need to be able to clear it + if (sslOptions & QSsl::SslOptionDisableLegacyRenegotiation) + options &= ~SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; + else + options |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; +#endif + +#ifdef SSL_OP_NO_TICKET + if (sslOptions & QSsl::SslOptionDisableSessionTickets) + options |= SSL_OP_NO_TICKET; +#endif +#ifdef SSL_OP_NO_COMPRESSION + if (sslOptions & QSsl::SslOptionDisableCompression) + options |= SSL_OP_NO_COMPRESSION; +#endif + + if (!(sslOptions & QSsl::SslOptionDisableServerCipherPreference)) + options |= SSL_OP_CIPHER_SERVER_PREFERENCE; + + return options; +} + +QSslContext::QSslContext() + : ctx(nullptr), + pkey(nullptr), + session(nullptr), + m_sessionTicketLifeTimeHint(-1) +{ +} + +QSslContext::~QSslContext() +{ + if (ctx) + // This will decrement the reference count by 1 and free the context eventually when possible + q_SSL_CTX_free(ctx); + + if (pkey) + q_EVP_PKEY_free(pkey); + + if (session) + q_SSL_SESSION_free(session); +} + +std::shared_ptr<QSslContext> QSslContext::sharedFromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading) +{ + struct AccessToPrivateCtor : QSslContext {}; + std::shared_ptr<QSslContext> sslContext = std::make_shared<AccessToPrivateCtor>(); + initSslContext(sslContext.get(), mode, configuration, allowRootCertOnDemandLoading); + return sslContext; +} + +std::shared_ptr<QSslContext> QSslContext::sharedFromPrivateConfiguration(QSslSocket::SslMode mode, QSslConfigurationPrivate *privConfiguration, + bool allowRootCertOnDemandLoading) +{ + return sharedFromConfiguration(mode, privConfiguration, allowRootCertOnDemandLoading); +} + +#ifndef OPENSSL_NO_NEXTPROTONEG + +static int next_proto_cb(SSL *, unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ + QSslContext::NPNContext *ctx = reinterpret_cast<QSslContext::NPNContext *>(arg); + + // comment out to debug: +// QList<QByteArray> supportedVersions; +// for (unsigned int i = 0; i < inlen; ) { +// QByteArray version(reinterpret_cast<const char *>(&in[i+1]), in[i]); +// supportedVersions << version; +// i += in[i] + 1; +// } + + int proto = q_SSL_select_next_proto(out, outlen, in, inlen, ctx->data, ctx->len); + switch (proto) { + case OPENSSL_NPN_UNSUPPORTED: + ctx->status = QSslConfiguration::NextProtocolNegotiationNone; + break; + case OPENSSL_NPN_NEGOTIATED: + ctx->status = QSslConfiguration::NextProtocolNegotiationNegotiated; + break; + case OPENSSL_NPN_NO_OVERLAP: + ctx->status = QSslConfiguration::NextProtocolNegotiationUnsupported; + break; + default: + qCWarning(lcTlsBackend, "OpenSSL sent unknown NPN status"); + } + + return SSL_TLSEXT_ERR_OK; +} + +QSslContext::NPNContext QSslContext::npnContext() const +{ + return m_npnContext; +} +#endif // !OPENSSL_NO_NEXTPROTONEG + + + +// Needs to be deleted by caller +SSL* QSslContext::createSsl() +{ + SSL* ssl = q_SSL_new(ctx); + q_SSL_clear(ssl); + + if (!session && !sessionASN1().isEmpty() + && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) { + const unsigned char *data = reinterpret_cast<const unsigned char *>(m_sessionASN1.constData()); + session = q_d2i_SSL_SESSION(nullptr, &data, m_sessionASN1.size()); + // 'session' has refcount 1 already, set by the function above + } + + if (session) { + // Try to resume the last session we cached + if (!q_SSL_set_session(ssl, session)) { + qCWarning(lcTlsBackend, "could not set SSL session"); + q_SSL_SESSION_free(session); + session = nullptr; + } + } + +#ifndef OPENSSL_NO_NEXTPROTONEG + QList<QByteArray> protocols = sslConfiguration.d.constData()->nextAllowedProtocols; + if (!protocols.isEmpty()) { + m_supportedNPNVersions.clear(); + for (int a = 0; a < protocols.size(); ++a) { + if (protocols.at(a).size() > 255) { + qCWarning(lcTlsBackend) << "TLS NPN extension" << protocols.at(a) + << "is too long and will be ignored."; + continue; + } else if (protocols.at(a).isEmpty()) { + continue; + } + m_supportedNPNVersions.append(protocols.at(a).size()).append(protocols.at(a)); + } + if (m_supportedNPNVersions.size()) { + m_npnContext.data = reinterpret_cast<unsigned char *>(m_supportedNPNVersions.data()); + m_npnContext.len = m_supportedNPNVersions.size(); + m_npnContext.status = QSslConfiguration::NextProtocolNegotiationNone; + // Callback's type has a parameter 'const unsigned char ** out' + // since it was introduced in 1.0.2. Internally, OpenSSL's own code + // (tests/examples) cast it to unsigned char * (since it's 'out'). + // We just re-use our NPN callback and cast here: + typedef int (*alpn_callback_t) (SSL *, const unsigned char **, unsigned char *, + const unsigned char *, unsigned int, void *); + // With ALPN callback is for a server side only, for a client m_npnContext.status + // will stay in NextProtocolNegotiationNone. + q_SSL_CTX_set_alpn_select_cb(ctx, alpn_callback_t(next_proto_cb), &m_npnContext); + // Client: + q_SSL_set_alpn_protos(ssl, m_npnContext.data, m_npnContext.len); + // And in case our peer does not support ALPN, but supports NPN: + q_SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &m_npnContext); + } + } +#endif // !OPENSSL_NO_NEXTPROTONEG + + return ssl; +} + +// We cache exactly one session here +bool QSslContext::cacheSession(SSL* ssl) +{ + // don't cache the same session again + if (session && session == q_SSL_get_session(ssl)) + return true; + + // decrease refcount of currently stored session + // (this might happen if there are several concurrent handshakes in flight) + if (session) + q_SSL_SESSION_free(session); + + // cache the session the caller gave us and increase reference count + session = q_SSL_get1_session(ssl); + + if (session && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) { + int sessionSize = q_i2d_SSL_SESSION(session, nullptr); + if (sessionSize > 0) { + m_sessionASN1.resize(sessionSize); + unsigned char *data = reinterpret_cast<unsigned char *>(m_sessionASN1.data()); + if (!q_i2d_SSL_SESSION(session, &data)) + qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); + m_sessionTicketLifeTimeHint = q_SSL_SESSION_get_ticket_lifetime_hint(session); + } + } + + return (session != nullptr); +} + +QByteArray QSslContext::sessionASN1() const +{ + return m_sessionASN1; +} + +void QSslContext::setSessionASN1(const QByteArray &session) +{ + m_sessionASN1 = session; +} + +int QSslContext::sessionTicketLifeTimeHint() const +{ + return m_sessionTicketLifeTimeHint; +} + +void QSslContext::forceAutoTestSecurityLevel() +{ + *forceSecurityLevel() = true; +} + +QSslError::SslError QSslContext::error() const +{ + return errorCode; +} + +QString QSslContext::errorString() const +{ + return errorStr; +} + +void QSslContext::initSslContext(QSslContext *sslContext, QSslSocket::SslMode mode, + const QSslConfiguration &configuration, + bool allowRootCertOnDemandLoading) +{ + sslContext->sslConfiguration = configuration; + sslContext->errorCode = QSslError::NoError; + + bool client = (mode == QSslSocket::SslClientMode); + + bool reinitialized = false; + bool unsupportedProtocol = false; + bool isDtls = false; +init_context: + switch (sslContext->sslConfiguration.protocol()) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + case QSsl::DtlsV1_0: + case QSsl::DtlsV1_0OrLater: +QT_WARNING_POP + case QSsl::DtlsV1_2: + case QSsl::DtlsV1_2OrLater: +#if QT_CONFIG(dtls) + isDtls = true; + sslContext->ctx = q_SSL_CTX_new(client ? q_DTLS_client_method() : q_DTLS_server_method()); +#else // dtls + sslContext->ctx = nullptr; + unsupportedProtocol = true; + qCWarning(lcTlsBackend, "DTLS protocol requested, but feature 'dtls' is disabled"); +#endif // dtls + break; + case QSsl::TlsV1_3: + case QSsl::TlsV1_3OrLater: +#if !defined(TLS1_3_VERSION) + qCWarning(lcTlsBackend, "TLS 1.3 is not supported"); + sslContext->ctx = nullptr; + unsupportedProtocol = true; + break; +#endif // TLS1_3_VERSION + default: + // The ssl options will actually control the supported methods + sslContext->ctx = q_SSL_CTX_new(client ? q_TLS_client_method() : q_TLS_server_method()); + } + + if (!sslContext->ctx) { + // After stopping Flash 10 the SSL library loses its ciphers. Try re-adding them + // by re-initializing the library. + if (!reinitialized) { + reinitialized = true; + if (q_OPENSSL_init_ssl(0, nullptr) == 1) + goto init_context; + } + + sslContext->errorStr = QSslSocket::tr("Error creating SSL context (%1)").arg( + unsupportedProtocol ? QSslSocket::tr("unsupported protocol") : QTlsBackendOpenSSL::getErrorsFromOpenSsl() + ); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // A nasty hacked OpenSSL using a level that will make our auto-tests fail: + if (q_SSL_CTX_get_security_level(sslContext->ctx) > 1 && *forceSecurityLevel()) + q_SSL_CTX_set_security_level(sslContext->ctx, 1); + + const long anyVersion = +#if QT_CONFIG(dtls) + isDtls ? DTLS_ANY_VERSION : TLS_ANY_VERSION; +#else + TLS_ANY_VERSION; +#endif // dtls + long minVersion = anyVersion; + long maxVersion = anyVersion; + + switch (sslContext->sslConfiguration.protocol()) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + case QSsl::TlsV1_0: + minVersion = TLS1_VERSION; + maxVersion = TLS1_VERSION; + break; + case QSsl::TlsV1_1: + minVersion = TLS1_1_VERSION; + maxVersion = TLS1_1_VERSION; + break; +QT_WARNING_POP + case QSsl::TlsV1_2: + minVersion = TLS1_2_VERSION; + maxVersion = TLS1_2_VERSION; + break; + case QSsl::TlsV1_3: +#ifdef TLS1_3_VERSION + minVersion = TLS1_3_VERSION; + maxVersion = TLS1_3_VERSION; +#else + // This protocol is not supported by OpenSSL 1.1 and we handle + // it as an error (see the code above). + Q_UNREACHABLE(); +#endif // TLS1_3_VERSION + break; + // Ranges: + case QSsl::AnyProtocol: +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + case QSsl::TlsV1_0OrLater: + minVersion = TLS1_VERSION; + maxVersion = 0; + break; + case QSsl::TlsV1_1OrLater: + minVersion = TLS1_1_VERSION; + maxVersion = 0; + break; +QT_WARNING_POP + case QSsl::SecureProtocols: + case QSsl::TlsV1_2OrLater: + minVersion = TLS1_2_VERSION; + maxVersion = 0; + break; +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + case QSsl::DtlsV1_0: + minVersion = DTLS1_VERSION; + maxVersion = DTLS1_VERSION; + break; + case QSsl::DtlsV1_0OrLater: + minVersion = DTLS1_VERSION; + maxVersion = 0; + break; +QT_WARNING_POP + case QSsl::DtlsV1_2: + minVersion = DTLS1_2_VERSION; + maxVersion = DTLS1_2_VERSION; + break; + case QSsl::DtlsV1_2OrLater: + minVersion = DTLS1_2_VERSION; + maxVersion = 0; + break; + case QSsl::TlsV1_3OrLater: +#ifdef TLS1_3_VERSION + minVersion = TLS1_3_VERSION; + maxVersion = 0; + break; +#else + // This protocol is not supported by OpenSSL 1.1 and we handle + // it as an error (see the code above). + Q_UNREACHABLE(); + break; +#endif // TLS1_3_VERSION + case QSsl::UnknownProtocol: + break; + } + + if (minVersion != anyVersion + && !q_SSL_CTX_set_min_proto_version(sslContext->ctx, minVersion)) { + sslContext->errorStr = QSslSocket::tr("Error while setting the minimal protocol version"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + if (maxVersion != anyVersion + && !q_SSL_CTX_set_max_proto_version(sslContext->ctx, maxVersion)) { + sslContext->errorStr = QSslSocket::tr("Error while setting the maximum protocol version"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // Enable bug workarounds. + const qssloptions options = setupOpenSslOptions(configuration.protocol(), configuration.d->sslOptions); + q_SSL_CTX_set_options(sslContext->ctx, options); + + // Tell OpenSSL to release memory early + // http://www.openssl.org/docs/ssl/SSL_CTX_set_mode.html + q_SSL_CTX_set_mode(sslContext->ctx, SSL_MODE_RELEASE_BUFFERS); + + auto filterCiphers = [](const QList<QSslCipher> &ciphers, bool selectTls13) + { + QByteArray cipherString; + + for (const QSslCipher &cipher : ciphers) { + const bool isTls13Cipher = cipher.protocol() == QSsl::TlsV1_3 || cipher.protocol() == QSsl::TlsV1_3OrLater; + if (selectTls13 != isTls13Cipher) + continue; + + if (cipherString.size()) + cipherString.append(':'); + cipherString.append(cipher.name().toLatin1()); + } + return cipherString; + }; + + // Initialize ciphers + QList<QSslCipher> ciphers = sslContext->sslConfiguration.ciphers(); + if (ciphers.isEmpty()) + ciphers = isDtls ? QTlsBackend::defaultDtlsCiphers() : QTlsBackend::defaultCiphers(); + + const QByteArray preTls13Ciphers = filterCiphers(ciphers, false); + + if (preTls13Ciphers.size()) { + if (!q_SSL_CTX_set_cipher_list(sslContext->ctx, preTls13Ciphers.data())) { + sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + } + + const QByteArray tls13Ciphers = filterCiphers(ciphers, true); +#ifdef TLS1_3_VERSION + if (tls13Ciphers.size()) { + if (!q_SSL_CTX_set_ciphersuites(sslContext->ctx, tls13Ciphers.data())) { + sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + } +#endif // TLS1_3_VERSION + if (!preTls13Ciphers.size() && !tls13Ciphers.size()) { + sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QStringLiteral("")); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + const QDateTime now = QDateTime::currentDateTimeUtc(); + + // Add all our CAs to this store. + const auto caCertificates = sslContext->sslConfiguration.caCertificates(); + for (const QSslCertificate &caCertificate : caCertificates) { + // From https://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html: + // + // If several CA certificates matching the name, key identifier, and + // serial number condition are available, only the first one will be + // examined. This may lead to unexpected results if the same CA + // certificate is available with different expiration dates. If a + // ``certificate expired'' verification error occurs, no other + // certificate will be searched. Make sure to not have expired + // certificates mixed with valid ones. + // + // See also: QSslSocketBackendPrivate::verify() + if (caCertificate.expiryDate() >= now) { + q_X509_STORE_add_cert(q_SSL_CTX_get_cert_store(sslContext->ctx), (X509 *)caCertificate.handle()); + } + } + + if (QSslSocketPrivate::rootCertOnDemandLoadingSupported() && allowRootCertOnDemandLoading) { + // tell OpenSSL the directories where to look up the root certs on demand + const QList<QByteArray> unixDirs = QSslSocketPrivate::unixRootCertDirectories(); + int success = 1; +#if OPENSSL_VERSION_MAJOR < 3 + for (const QByteArray &unixDir : unixDirs) { + if ((success = q_SSL_CTX_load_verify_locations(sslContext->ctx, nullptr, unixDir.constData())) != 1) + break; + } +#else + for (const QByteArray &unixDir : unixDirs) { + if ((success = q_SSL_CTX_load_verify_dir(sslContext->ctx, unixDir.constData())) != 1) + break; + } +#endif // OPENSSL_VERSION_MAJOR + if (success != 1) { + const auto qtErrors = QTlsBackendOpenSSL::getErrorsFromOpenSsl(); + qCWarning(lcTlsBackend) << "An error encountered while to set root certificates location:" + << qtErrors; + } + } + + if (!sslContext->sslConfiguration.localCertificate().isNull()) { + // Require a private key as well. + if (sslContext->sslConfiguration.privateKey().isNull()) { + sslContext->errorStr = QSslSocket::tr("Cannot provide a certificate with no key"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // Load certificate + if (!q_SSL_CTX_use_certificate(sslContext->ctx, (X509 *)sslContext->sslConfiguration.localCertificate().handle())) { + sslContext->errorStr = QSslSocket::tr("Error loading local certificate, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + if (configuration.d->privateKey.algorithm() == QSsl::Opaque) { + sslContext->pkey = reinterpret_cast<EVP_PKEY *>(configuration.d->privateKey.handle()); + } else { +#ifdef OPENSSL_NO_DEPRECATED_3_0 + auto qtKey = QTlsBackend::backend<QTlsPrivate::TlsKeyOpenSSL>(configuration.d->privateKey); + Q_ASSERT(qtKey); + sslContext->pkey = qtKey->genericKey; + Q_ASSERT(sslContext->pkey); + q_EVP_PKEY_up_ref(sslContext->pkey); +#else + // Load private key + sslContext->pkey = q_EVP_PKEY_new(); + // before we were using EVP_PKEY_assign_R* functions and did not use EVP_PKEY_free. + // this lead to a memory leak. Now we use the *_set1_* functions which do not + // take ownership of the RSA/DSA key instance because the QSslKey already has ownership. + if (configuration.d->privateKey.algorithm() == QSsl::Rsa) + q_EVP_PKEY_set1_RSA(sslContext->pkey, reinterpret_cast<RSA *>(configuration.d->privateKey.handle())); + else if (configuration.d->privateKey.algorithm() == QSsl::Dsa) + q_EVP_PKEY_set1_DSA(sslContext->pkey, reinterpret_cast<DSA *>(configuration.d->privateKey.handle())); +#ifndef OPENSSL_NO_EC + else if (configuration.d->privateKey.algorithm() == QSsl::Ec) + q_EVP_PKEY_set1_EC_KEY(sslContext->pkey, reinterpret_cast<EC_KEY *>(configuration.d->privateKey.handle())); +#endif // OPENSSL_NO_EC +#endif // OPENSSL_NO_DEPRECATED_3_0 + } + auto pkey = sslContext->pkey; + if (configuration.d->privateKey.algorithm() == QSsl::Opaque) + sslContext->pkey = nullptr; // Don't free the private key, it belongs to QSslKey + + if (!q_SSL_CTX_use_PrivateKey(sslContext->ctx, pkey)) { + sslContext->errorStr = QSslSocket::tr("Error loading private key, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // Check if the certificate matches the private key. + if (!q_SSL_CTX_check_private_key(sslContext->ctx)) { + sslContext->errorStr = QSslSocket::tr("Private key does not certify public key, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // If we have any intermediate certificates then we need to add them to our chain + bool first = true; + for (const QSslCertificate &cert : std::as_const(configuration.d->localCertificateChain)) { + if (first) { + first = false; + continue; + } + q_SSL_CTX_ctrl(sslContext->ctx, SSL_CTRL_EXTRA_CHAIN_CERT, 0, + q_X509_dup(reinterpret_cast<X509 *>(cert.handle()))); + } + } + + // Initialize peer verification, different callbacks, TLS/DTLS verification first + // (note, all these set_some_callback do not have return value): + if (sslContext->sslConfiguration.peerVerifyMode() == QSslSocket::VerifyNone) { + q_SSL_CTX_set_verify(sslContext->ctx, SSL_VERIFY_NONE, nullptr); + } else { + auto verificationCallback = + #if QT_CONFIG(dtls) + isDtls ? dtlscallbacks::q_X509DtlsCallback : + #endif // dtls + QTlsPrivate::q_X509Callback; + + if (!isDtls && configuration.handshakeMustInterruptOnError()) + verificationCallback = QTlsPrivate::q_X509CallbackDirect; + + auto verificationMode = SSL_VERIFY_PEER; + if (!isDtls && sslContext->sslConfiguration.missingCertificateIsFatal()) + verificationMode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + + q_SSL_CTX_set_verify(sslContext->ctx, verificationMode, verificationCallback); + } + +#ifdef TLS1_3_VERSION + // NewSessionTicket callback: + if (mode == QSslSocket::SslClientMode && !isDtls) { + q_SSL_CTX_sess_set_new_cb(sslContext->ctx, q_ssl_sess_set_new_cb); + q_SSL_CTX_set_session_cache_mode(sslContext->ctx, SSL_SESS_CACHE_CLIENT); + } + +#endif // TLS1_3_VERSION + +#if QT_CONFIG(dtls) + // DTLS cookies: + if (mode == QSslSocket::SslServerMode && isDtls && configuration.dtlsCookieVerificationEnabled()) { + q_SSL_CTX_set_cookie_generate_cb(sslContext->ctx, dtlscallbacks::q_generate_cookie_callback); + q_SSL_CTX_set_cookie_verify_cb(sslContext->ctx, dtlscallbacks::q_verify_cookie_callback); + } +#endif // dtls + + // Set verification depth. + if (sslContext->sslConfiguration.peerVerifyDepth() != 0) + q_SSL_CTX_set_verify_depth(sslContext->ctx, sslContext->sslConfiguration.peerVerifyDepth()); + + // set persisted session if the user set it + if (!configuration.sessionTicket().isEmpty()) + sslContext->setSessionASN1(configuration.sessionTicket()); + + // Set temp DH params + QSslDiffieHellmanParameters dhparams = configuration.diffieHellmanParameters(); + + if (!dhparams.isValid()) { + sslContext->errorStr = QSslSocket::tr("Diffie-Hellman parameters are not valid"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + if (dhparams.isEmpty()) { + q_SSL_CTX_set_dh_auto(sslContext->ctx, 1); + } else { +#ifndef OPENSSL_NO_DEPRECATED_3_0 + const QByteArray ¶ms = dhparams.d->derData; + const char *ptr = params.constData(); + DH *dh = q_d2i_DHparams(nullptr, reinterpret_cast<const unsigned char **>(&ptr), + params.size()); + if (dh == nullptr) + qFatal("q_d2i_DHparams failed to convert QSslDiffieHellmanParameters to DER form"); + q_SSL_CTX_set_tmp_dh(sslContext->ctx, dh); + q_DH_free(dh); +#else + qCWarning(lcTlsBackend, "Diffie-Hellman parameters are not supported, because OpenSSL v3 was built with deprecated API removed"); +#endif + } + +#ifndef OPENSSL_NO_PSK + if (!client) + q_SSL_CTX_use_psk_identity_hint(sslContext->ctx, sslContext->sslConfiguration.preSharedKeyIdentityHint().constData()); +#endif // !OPENSSL_NO_PSK + + const auto qcurves = sslContext->sslConfiguration.ellipticCurves(); + if (!qcurves.isEmpty()) { +#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; + curves.reserve(qcurves.size()); + for (const auto &sslCurve : qcurves) + curves.push_back(sslCurve.id); + if (!q_SSL_CTX_ctrl(sslContext->ctx, SSL_CTRL_SET_CURVES, long(curves.size()), &curves[0])) { + sslContext->errorStr = msgErrorSettingEllipticCurves(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } +#endif + } + + applyBackendConfig(sslContext); +} + +void QSslContext::applyBackendConfig(QSslContext *sslContext) +{ + const QMap<QByteArray, QVariant> &conf = sslContext->sslConfiguration.backendConfiguration(); + if (conf.isEmpty()) + return; + +#if QT_CONFIG(ocsp) + auto ocspResponsePos = conf.find("Qt-OCSP-response"); + if (ocspResponsePos != conf.end()) { + // This is our private, undocumented configuration option, existing only for + // the purpose of testing OCSP status responses. We don't even check this + // callback was set. If no - the test must fail. + q_SSL_CTX_set_tlsext_status_cb(sslContext->ctx, QTlsPrivate::qt_OCSP_status_server_callback); + if (conf.size() == 1) + return; + } +#endif // ocsp + + 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); + + for (auto i = conf.constBegin(); i != conf.constEnd(); ++i) { + if (i.key() == "Qt-OCSP-response") // This never goes to SSL_CONF_cmd(). + continue; + + if (!i.value().canConvert(QMetaType(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; + } +} + +QT_END_NAMESPACE |