diff options
Diffstat (limited to 'src/plugins/tls/openssl')
20 files changed, 9634 insertions, 0 deletions
diff --git a/src/plugins/tls/openssl/CMakeLists.txt b/src/plugins/tls/openssl/CMakeLists.txt new file mode 100644 index 0000000000..0e0a7a1552 --- /dev/null +++ b/src/plugins/tls/openssl/CMakeLists.txt @@ -0,0 +1,63 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(QTlsBackendOpenSSLPlugin + OUTPUT_NAME qopensslbackend + CLASS_NAME QTlsBackendOpenSSL + PLUGIN_TYPE tls + SOURCES + ../shared/qx509_base.cpp ../shared/qx509_base_p.h + ../shared/qtlskey_base.cpp ../shared/qtlskey_base_p.h + ../shared/qasn1element.cpp ../shared/qasn1element_p.h + qtlsbackend_openssl.cpp qtlsbackend_openssl_p.h + qx509_openssl.cpp qx509_openssl_p.h + qtlskey_openssl.cpp qtlskey_openssl_p.h + qtls_openssl.cpp qtls_openssl_p.h + qssldiffiehellmanparameters_openssl.cpp + qsslcontext_openssl.cpp qsslcontext_openssl_p.h + qsslsocket_openssl_symbols.cpp qsslsocket_openssl_symbols_p.h + qopenssl_p.h + LIBRARIES + Qt::NetworkPrivate + Qt::CorePrivate + DEFINES + OPENSSL_API_COMPAT=0x10100000L +) + +if (WIN32) # Windows header issues + set_target_properties(QTlsBackendOpenSSLPlugin PROPERTIES UNITY_BUILD OFF) +endif() + +qt_internal_extend_target(QTlsBackendOpenSSLPlugin CONDITION QT_FEATURE_dtls + SOURCES + qdtls_openssl.cpp qdtls_openssl_p.h + ../shared/qdtls_base.cpp ../shared/qdtls_base_p.h +) + +qt_internal_extend_target(QTlsBackendOpenSSLPlugin CONDITION APPLE + SOURCES + ../shared/qsslsocket_mac_shared.cpp + LIBRARIES + ${FWCoreFoundation} + ${FWSecurity} +) + +qt_internal_extend_target(QTlsBackendOpenSSLPlugin CONDITION ANDROID + SOURCES + qsslsocket_openssl_android.cpp +) + +qt_internal_extend_target(QTlsBackendOpenSSLPlugin CONDITION WIN32 + SOURCES + qwindowscarootfetcher.cpp qwindowscarootfetcher_p.h + ../shared/qwincrypt_p.h + LIBRARIES + crypt32 +) + +if(QT_FEATURE_openssl_linked) + target_link_libraries(QTlsBackendOpenSSLPlugin PRIVATE WrapOpenSSL::WrapOpenSSL) +else() + qt_internal_add_target_include_dirs(QTlsBackendOpenSSLPlugin + WrapOpenSSLHeaders::WrapOpenSSLHeaders) +endif() diff --git a/src/plugins/tls/openssl/qdtls_openssl.cpp b/src/plugins/tls/openssl/qdtls_openssl.cpp new file mode 100644 index 0000000000..fc07a29ec8 --- /dev/null +++ b/src/plugins/tls/openssl/qdtls_openssl.cpp @@ -0,0 +1,1417 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtNetwork/private/qnativesocketengine_p_p.h> + +#include "qsslsocket_openssl_symbols_p.h" +#include "qdtls_openssl_p.h" +#include "qx509_openssl_p.h" + +#include <QtNetwork/private/qsslpresharedkeyauthenticator_p.h> +#include <QtNetwork/private/qsslcertificate_p.h> +#include <QtNetwork/private/qssl_p.h> + +#include <QtNetwork/qudpsocket.h> + +#include <QtCore/qmessageauthenticationcode.h> +#include <QtCore/qcryptographichash.h> + +#include <QtCore/qdebug.h> + +#include <cstring> +#include <cstddef> + +QT_BEGIN_NAMESPACE + +#define QT_DTLS_VERBOSE 0 + +#if QT_DTLS_VERBOSE + +#define qDtlsWarning(arg) qWarning(arg) +#define qDtlsDebug(arg) qDebug(arg) + +#else + +#define qDtlsWarning(arg) +#define qDtlsDebug(arg) + +#endif // QT_DTLS_VERBOSE + +namespace dtlsutil +{ + +QByteArray cookie_for_peer(SSL *ssl) +{ + Q_ASSERT(ssl); + + // SSL_get_rbio does not increment the reference count + BIO *readBIO = q_SSL_get_rbio(ssl); + if (!readBIO) { + qCWarning(lcTlsBackend, "No BIO (dgram) found in SSL object"); + return {}; + } + + auto listener = static_cast<dtlsopenssl::DtlsState *>(q_BIO_get_app_data(readBIO)); + if (!listener) { + qCWarning(lcTlsBackend, "BIO_get_app_data returned invalid (nullptr) value"); + return {}; + } + + const QHostAddress peerAddress(listener->remoteAddress); + const quint16 peerPort(listener->remotePort); + QByteArray peerData; + if (peerAddress.protocol() == QAbstractSocket::IPv6Protocol) { + const Q_IPV6ADDR sin6_addr(peerAddress.toIPv6Address()); + peerData.resize(int(sizeof sin6_addr + sizeof peerPort)); + char *dst = peerData.data(); + std::memcpy(dst, &peerPort, sizeof peerPort); + dst += sizeof peerPort; + std::memcpy(dst, &sin6_addr, sizeof sin6_addr); + } else if (peerAddress.protocol() == QAbstractSocket::IPv4Protocol) { + const quint32 sin_addr(peerAddress.toIPv4Address()); + peerData.resize(int(sizeof sin_addr + sizeof peerPort)); + char *dst = peerData.data(); + std::memcpy(dst, &peerPort, sizeof peerPort); + dst += sizeof peerPort; + std::memcpy(dst, &sin_addr, sizeof sin_addr); + } else { + Q_UNREACHABLE(); + } + + return peerData; +} + +struct FallbackCookieSecret +{ + FallbackCookieSecret() + { + key.resize(32); + const int status = q_RAND_bytes(reinterpret_cast<unsigned char *>(key.data()), + key.size()); + if (status <= 0) + key.clear(); + } + + QByteArray key; + + Q_DISABLE_COPY_MOVE(FallbackCookieSecret) +}; + +QByteArray fallbackSecret() +{ + static const FallbackCookieSecret generator; + return generator.key; +} + +int next_timeoutMs(SSL *tlsConnection) +{ + Q_ASSERT(tlsConnection); + timeval timeLeft = {}; + q_DTLSv1_get_timeout(tlsConnection, &timeLeft); + return timeLeft.tv_sec * 1000; +} + + +void delete_connection(SSL *ssl) +{ + // The 'deleter' for QSharedPointer<SSL>. + if (ssl) + q_SSL_free(ssl); +} + +void delete_BIO_ADDR(BIO_ADDR *bio) +{ + // A deleter for QSharedPointer<BIO_ADDR> + if (bio) + q_BIO_ADDR_free(bio); +} + +void delete_bio_method(BIO_METHOD *method) +{ + // The 'deleter' for QSharedPointer<BIO_METHOD>. + if (method) + q_BIO_meth_free(method); +} + +// The path MTU discovery is non-trivial: it's a mix of getsockopt/setsockopt +// (IP_MTU/IP6_MTU/IP_MTU_DISCOVER) and fallback MTU values. It's not +// supported on all platforms, worse so - imposes specific requirements on +// underlying UDP socket etc. So for now, we either try a user-proposed MTU +// hint or rely on our own fallback value. As a fallback mtu OpenSSL uses 576 +// for IPv4 and 1280 for IPv6 (RFC 791, RFC 2460). To KIS we use 576. This +// rather small MTU value does not affect the size that can be read/written +// by QDtls, only a handshake (which is allowed to fragment). +enum class MtuGuess : long +{ + defaultMtu = 576 +}; + +} // namespace dtlsutil + +namespace dtlscallbacks +{ + +extern "C" int q_generate_cookie_callback(SSL *ssl, unsigned char *dst, + unsigned *cookieLength) +{ + if (!ssl || !dst || !cookieLength) { + qCWarning(lcTlsBackend, + "Failed to generate cookie - invalid (nullptr) parameter(s)"); + return 0; + } + + void *generic = q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData); + if (!generic) { + qCWarning(lcTlsBackend, "SSL_get_ex_data returned nullptr, cannot generate cookie"); + return 0; + } + + *cookieLength = 0; + + auto dtls = static_cast<dtlsopenssl::DtlsState *>(generic); + if (!dtls->secret.size()) + return 0; + + const QByteArray peerData(dtlsutil::cookie_for_peer(ssl)); + if (!peerData.size()) + return 0; + + QMessageAuthenticationCode hmac(dtls->hashAlgorithm, dtls->secret); + hmac.addData(peerData); + const QByteArrayView cookie = hmac.resultView(); + Q_ASSERT(cookie.size() >= 0); + // DTLS1_COOKIE_LENGTH is erroneously 256 bytes long, must be 255 - RFC 6347, 4.2.1. + *cookieLength = qMin(DTLS1_COOKIE_LENGTH - 1, cookie.size()); + std::memcpy(dst, cookie.constData(), *cookieLength); + + return 1; +} + +extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie, + unsigned cookieLength) +{ + if (!ssl || !cookie || !cookieLength) { + qCWarning(lcTlsBackend, "Could not verify cookie, invalid (nullptr or zero) parameters"); + return 0; + } + + unsigned char newCookie[DTLS1_COOKIE_LENGTH] = {}; + unsigned newCookieLength = 0; + if (q_generate_cookie_callback(ssl, newCookie, &newCookieLength) != 1) + return 0; + + return newCookieLength == cookieLength + && !q_CRYPTO_memcmp(cookie, newCookie, size_t(cookieLength)); +} + +extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx) +{ + if (!ok) { + // Store the error and at which depth the error was detected. + SSL *ssl = static_cast<SSL *>(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx())); + if (!ssl) { + qCWarning(lcTlsBackend, "X509_STORE_CTX_get_ex_data returned nullptr, handshake failure"); + return 0; + } + + void *generic = q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData); + if (!generic) { + qCWarning(lcTlsBackend, "SSL_get_ex_data returned nullptr, handshake failure"); + return 0; + } + + auto dtls = static_cast<dtlsopenssl::DtlsState *>(generic); + dtls->x509Errors.append(QTlsPrivate::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); + } + + // Always return 1 (OK) to allow verification to continue. We handle the + // errors gracefully after collecting all errors, after verification has + // completed. + return 1; +} + +extern "C" unsigned q_PSK_client_callback(SSL *ssl, const char *hint, char *identity, + unsigned max_identity_len, unsigned char *psk, + unsigned max_psk_len) +{ + auto *dtls = static_cast<dtlsopenssl::DtlsState *>(q_SSL_get_ex_data(ssl, + QTlsBackendOpenSSL::s_indexForSSLExtraData)); + if (!dtls) + return 0; + + Q_ASSERT(dtls->dtlsPrivate); + return dtls->dtlsPrivate->pskClientCallback(hint, identity, max_identity_len, psk, max_psk_len); +} + +extern "C" unsigned q_PSK_server_callback(SSL *ssl, const char *identity, unsigned char *psk, + unsigned max_psk_len) +{ + auto *dtls = static_cast<dtlsopenssl::DtlsState *>(q_SSL_get_ex_data(ssl, + QTlsBackendOpenSSL::s_indexForSSLExtraData)); + if (!dtls) + return 0; + + Q_ASSERT(dtls->dtlsPrivate); + return dtls->dtlsPrivate->pskServerCallback(identity, psk, max_psk_len); +} + +} // namespace dtlscallbacks + +namespace dtlsbio +{ + +extern "C" int q_dgram_read(BIO *bio, char *dst, int bytesToRead) +{ + if (!bio || !dst || bytesToRead <= 0) { + qCWarning(lcTlsBackend, "invalid input parameter(s)"); + return 0; + } + + q_BIO_clear_retry_flags(bio); + + auto dtls = static_cast<dtlsopenssl::DtlsState *>(q_BIO_get_app_data(bio)); + // It's us who set data, if OpenSSL does too, the logic here is wrong + // then and we have to use BIO_set_app_data then! + Q_ASSERT(dtls); + int bytesRead = 0; + if (dtls->dgram.size()) { + bytesRead = qMin(dtls->dgram.size(), bytesToRead); + std::memcpy(dst, dtls->dgram.constData(), bytesRead); + + if (!dtls->peeking) + dtls->dgram = dtls->dgram.mid(bytesRead); + } else { + bytesRead = -1; + } + + if (bytesRead <= 0) + q_BIO_set_retry_read(bio); + + return bytesRead; +} + +extern "C" int q_dgram_write(BIO *bio, const char *src, int bytesToWrite) +{ + if (!bio || !src || bytesToWrite <= 0) { + qCWarning(lcTlsBackend, "invalid input parameter(s)"); + return 0; + } + + q_BIO_clear_retry_flags(bio); + + auto dtls = static_cast<dtlsopenssl::DtlsState *>(q_BIO_get_app_data(bio)); + Q_ASSERT(dtls); + if (dtls->writeSuppressed) { + // See the comment in QDtls::startHandshake. + return bytesToWrite; + } + + QUdpSocket *udpSocket = dtls->udpSocket; + Q_ASSERT(udpSocket); + + const QByteArray dgram(QByteArray::fromRawData(src, bytesToWrite)); + qint64 bytesWritten = -1; + if (udpSocket->state() == QAbstractSocket::ConnectedState) { + bytesWritten = udpSocket->write(dgram); + } else { + bytesWritten = udpSocket->writeDatagram(dgram, dtls->remoteAddress, + dtls->remotePort); + } + + if (bytesWritten <= 0) + q_BIO_set_retry_write(bio); + + Q_ASSERT(bytesWritten <= std::numeric_limits<int>::max()); + return int(bytesWritten); +} + +extern "C" int q_dgram_puts(BIO *bio, const char *src) +{ + if (!bio || !src) { + qCWarning(lcTlsBackend, "invalid input parameter(s)"); + return 0; + } + + return q_dgram_write(bio, src, int(std::strlen(src))); +} + +extern "C" long q_dgram_ctrl(BIO *bio, int cmd, long num, void *ptr) +{ + // This is our custom BIO_ctrl. bio.h defines a lot of BIO_CTRL_* + // and BIO_* constants and BIO_somename macros that expands to BIO_ctrl + // call with one of those constants as argument. What exactly BIO_ctrl + // does - depends on the 'cmd' and the type of BIO (so BIO_ctrl does + // not even have a single well-defined value meaning success or failure). + // We handle only the most generic commands - the ones documented for + // BIO_ctrl - and also DGRAM specific ones. And even for them - in most + // cases we do nothing but report a success or some non-error value. + // Documents also state: "Source/sink BIOs return an 0 if they do not + // recognize the BIO_ctrl() operation." - these are covered by 'default' + // label in the switch-statement below. Debug messages in the switch mean: + // 1) we got a command that is unexpected for dgram BIO, or: + // 2) we do not call any function that would lead to OpenSSL using this + // command. + + if (!bio) { + qDebug(lcTlsBackend, "invalid 'bio' parameter (nullptr)"); + return -1; + } + + auto dtls = static_cast<dtlsopenssl::DtlsState *>(q_BIO_get_app_data(bio)); + Q_ASSERT(dtls); + + switch (cmd) { + // Let's start from the most generic ones, in the order in which they are + // documented (as BIO_ctrl): + case BIO_CTRL_RESET: + // BIO_reset macro. + // From documentation: + // "BIO_reset() normally returns 1 for success and 0 or -1 for failure. + // File BIOs are an exception, they return 0 for success and -1 for + // failure." + // We have nothing to reset and we are not file BIO. + return 1; + case BIO_C_FILE_SEEK: + case BIO_C_FILE_TELL: + qDtlsWarning("Unexpected cmd (BIO_C_FILE_SEEK/BIO_C_FILE_TELL)"); + // These are for BIO_seek, BIO_tell. We are not a file BIO. + // Non-negative return value means success. + return 0; + case BIO_CTRL_FLUSH: + // BIO_flush, nothing to do, we do not buffer any data. + // 0 or -1 means error, 1 - success. + return 1; + case BIO_CTRL_EOF: + qDtlsWarning("Unexpected cmd (BIO_CTRL_EOF)"); + // BIO_eof, 1 means EOF read. Makes no sense for us. + return 0; + case BIO_CTRL_SET_CLOSE: + // BIO_set_close with BIO_CLOSE/BIO_NOCLOSE flags. Documented as + // always returning 1. + // From the documentation: + // "Typically BIO_CLOSE is used in a source/sink BIO to indicate that + // the underlying I/O stream should be closed when the BIO is freed." + // + // QUdpSocket we work with is not BIO's business, ignoring. + return 1; + case BIO_CTRL_GET_CLOSE: + // BIO_get_close. No, never, see the comment above. + return 0; + case BIO_CTRL_PENDING: + qDtlsWarning("Unexpected cmd (BIO_CTRL_PENDING)"); + // BIO_pending. Not used by DTLS/OpenSSL (we are not buffering). + return 0; + case BIO_CTRL_WPENDING: + // No, we have nothing buffered. + return 0; + // The constants below are not documented as a part BIO_ctrl documentation, + // but they are also not type-specific. + case BIO_CTRL_DUP: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DUP)"); + // BIO_dup_state, not used by DTLS (and socket-related BIOs in general). + // For some very specific BIO type this 'cmd' would copy some state + // from 'bio' to (BIO*)'ptr'. 1 means success. + return 0; + case BIO_CTRL_SET_CALLBACK: + qDtlsWarning("Unexpected cmd (BIO_CTRL_SET_CALLBACK)"); + // BIO_set_info_callback. We never call this, OpenSSL does not do this + // on its own (normally it's used if client code wants to have some + // debug information, for example, dumping handshake state via + // BIO_printf from SSL info_callback). + return 0; + case BIO_CTRL_GET_CALLBACK: + qDtlsWarning("Unexpected cmd (BIO_CTRL_GET_CALLBACK)"); + // BIO_get_info_callback. We never call this. + if (ptr) + *static_cast<bio_info_cb **>(ptr) = nullptr; + return 0; + case BIO_CTRL_SET: + case BIO_CTRL_GET: + qDtlsWarning("Unexpected cmd (BIO_CTRL_SET/BIO_CTRL_GET)"); + // Somewhat 'documented' as setting/getting IO type. Not used anywhere + // except BIO_buffer_get_num_lines (which contradics 'get IO type'). + // Ignoring. + return 0; + // DGRAM-specific operation, we have to return some reasonable value + // (so far, I've encountered only peek mode switching, connect). + case BIO_CTRL_DGRAM_CONNECT: + // BIO_ctrl_dgram_connect. Not needed. Our 'dtls' already knows + // the peer's address/port. Report success though. + return 1; + case BIO_CTRL_DGRAM_SET_CONNECTED: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_CONNECTED)"); + // BIO_ctrl_dgram_set_connected. We never call it, OpenSSL does + // not call it on its own (so normally it's done by client code). + // Similar to BIO_CTRL_DGRAM_CONNECT, but it also informs the BIO + // that its UDP socket is connected. We never need it though. + return -1; + case BIO_CTRL_DGRAM_SET_RECV_TIMEOUT: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_RECV_TIMEOUT)"); + // Essentially setsockopt with SO_RCVTIMEO, not needed, our sockets + // are non-blocking. + return -1; + case BIO_CTRL_DGRAM_GET_RECV_TIMEOUT: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_RECV_TIMEOUT)"); + // getsockopt with SO_RCVTIMEO, not needed, our sockets are + // non-blocking. ptr is timeval *. + return -1; + case BIO_CTRL_DGRAM_SET_SEND_TIMEOUT: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_SEND_TIMEOUT)"); + // setsockopt, SO_SNDTIMEO, cannot happen. + return -1; + case BIO_CTRL_DGRAM_GET_SEND_TIMEOUT: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_SEND_TIMEOUT)"); + // getsockopt, SO_SNDTIMEO, cannot happen. + return -1; + case BIO_CTRL_DGRAM_GET_RECV_TIMER_EXP: + // BIO_dgram_recv_timedout. No, we are non-blocking. + return 0; + case BIO_CTRL_DGRAM_GET_SEND_TIMER_EXP: + // BIO_dgram_send_timedout. No, we are non-blocking. + return 0; + case BIO_CTRL_DGRAM_MTU_DISCOVER: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_MTU_DISCOVER)"); + // setsockopt, IP_MTU_DISCOVER/IP6_MTU_DISCOVER, to be done + // in QUdpSocket instead. OpenSSL never calls it, only client + // code. + return 1; + case BIO_CTRL_DGRAM_QUERY_MTU: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_QUERY_MTU)"); + // To be done in QUdpSocket instead. + return 1; + case BIO_CTRL_DGRAM_GET_FALLBACK_MTU: + qDtlsWarning("Unexpected command *BIO_CTRL_DGRAM_GET_FALLBACK_MTU)"); + // Without SSL_OP_NO_QUERY_MTU set on SSL, OpenSSL can request for + // fallback MTU after several re-transmissions. + // Should never happen in our case. + return long(dtlsutil::MtuGuess::defaultMtu); + case BIO_CTRL_DGRAM_GET_MTU: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_MTU)"); + return -1; + case BIO_CTRL_DGRAM_SET_MTU: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_MTU)"); + // Should not happen (we don't call BIO_ctrl with this parameter) + // and set MTU on SSL instead. + return -1; // num is mtu and it's a return value meaning success. + case BIO_CTRL_DGRAM_MTU_EXCEEDED: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_MTU_EXCEEDED)"); + return 0; + case BIO_CTRL_DGRAM_GET_PEER: + qDtlsDebug("BIO_CTRL_DGRAM_GET_PEER"); + // BIO_dgram_get_peer. We do not return a real address (DTLS is not + // using this address), but let's pretend a success. + switch (dtls->remoteAddress.protocol()) { + case QAbstractSocket::IPv6Protocol: + return sizeof(sockaddr_in6); + case QAbstractSocket::IPv4Protocol: + return sizeof(sockaddr_in); + default: + return -1; + } + case BIO_CTRL_DGRAM_SET_PEER: + // Similar to BIO_CTRL_DGRAM_CONNECTED. + return 1; + case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: + // DTLSTODO: I'm not sure yet, how it's used by OpenSSL. + return 1; + case BIO_CTRL_DGRAM_SET_DONT_FRAG: + qDtlsDebug("BIO_CTRL_DGRAM_SET_DONT_FRAG"); + // To be done in QUdpSocket, it's about IP_DONTFRAG etc. + return 1; + case BIO_CTRL_DGRAM_GET_MTU_OVERHEAD: + // AFAIK it's 28 for IPv4 and 48 for IPv6, but let's pretend it's 0 + // so that OpenSSL does not start suddenly fragmenting the first + // client hello (which will result in DTLSv1_listen rejecting it). + return 0; + case BIO_CTRL_DGRAM_SET_PEEK_MODE: + dtls->peeking = num; + return 1; + default:; +#if QT_DTLS_VERBOSE + qWarning() << "Unexpected cmd (" << cmd << ")"; +#endif + } + + return 0; +} + +extern "C" int q_dgram_create(BIO *bio) +{ + + q_BIO_set_init(bio, 1); + // With a custom BIO you'd normally allocate some implementation-specific + // data and append it to this new BIO using BIO_set_data. We don't need + // it and thus q_dgram_destroy below is a noop. + return 1; +} + +extern "C" int q_dgram_destroy(BIO *bio) +{ + Q_UNUSED(bio); + return 1; +} + +const char * const qdtlsMethodName = "qdtlsbio"; + +} // namespace dtlsbio + +namespace dtlsopenssl +{ + +bool DtlsState::init(QDtlsBasePrivate *dtlsBase, QUdpSocket *socket, + const QHostAddress &remote, quint16 port, + const QByteArray &receivedMessage) +{ + Q_ASSERT(dtlsBase); + Q_ASSERT(socket); + + if (!tlsContext && !initTls(dtlsBase)) + return false; + + udpSocket = socket; + + setLinkMtu(dtlsBase); + + dgram = receivedMessage; + remoteAddress = remote; + remotePort = port; + + // SSL_get_rbio does not increment a reference count. + BIO *bio = q_SSL_get_rbio(tlsConnection.data()); + Q_ASSERT(bio); + q_BIO_set_app_data(bio, this); + + return true; +} + +void DtlsState::reset() +{ + tlsConnection.reset(); + tlsContext.reset(); +} + +bool DtlsState::initTls(QDtlsBasePrivate *dtlsBase) +{ + if (tlsContext) + return true; + + if (!QSslSocket::supportsSsl()) + return false; + + if (!initCtxAndConnection(dtlsBase)) + return false; + + if (!initBIO(dtlsBase)) { + tlsConnection.reset(); + tlsContext.reset(); + return false; + } + + return true; +} + +static QString msgFunctionFailed(const char *function) +{ + //: %1: Some function + return QDtls::tr("%1 failed").arg(QLatin1StringView(function)); +} + +bool DtlsState::initCtxAndConnection(QDtlsBasePrivate *dtlsBase) +{ + Q_ASSERT(dtlsBase); + Q_ASSERT(QSslSocket::supportsSsl()); + + if (dtlsBase->mode == QSslSocket::UnencryptedMode) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + QDtls::tr("Invalid SslMode, SslServerMode or SslClientMode expected")); + return false; + } + + if (!QDtlsBasePrivate::isDtlsProtocol(dtlsBase->dtlsConfiguration.protocol())) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + QDtls::tr("Invalid protocol version, DTLS protocol expected")); + return false; + } + + const bool rootsOnDemand = QTlsBackend::rootLoadingOnDemandAllowed(dtlsBase->dtlsConfiguration); + TlsContext newContext(QSslContext::sharedFromConfiguration(dtlsBase->mode, dtlsBase->dtlsConfiguration, + rootsOnDemand)); + + if (newContext->error() != QSslError::NoError) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, newContext->errorString()); + return false; + } + + TlsConnection newConnection(newContext->createSsl(), dtlsutil::delete_connection); + if (!newConnection.data()) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + msgFunctionFailed("SSL_new")); + return false; + } + + const int set = q_SSL_set_ex_data(newConnection.data(), + QTlsBackendOpenSSL::s_indexForSSLExtraData, + this); + + if (set != 1 && dtlsBase->dtlsConfiguration.peerVerifyMode() != QSslSocket::VerifyNone) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + msgFunctionFailed("SSL_set_ex_data")); + return false; + } + + if (dtlsBase->mode == QSslSocket::SslServerMode) { + if (dtlsBase->dtlsConfiguration.dtlsCookieVerificationEnabled()) + q_SSL_set_options(newConnection.data(), SSL_OP_COOKIE_EXCHANGE); + q_SSL_set_psk_server_callback(newConnection.data(), dtlscallbacks::q_PSK_server_callback); + } else { + q_SSL_set_psk_client_callback(newConnection.data(), dtlscallbacks::q_PSK_client_callback); + } + + tlsContext.swap(newContext); + tlsConnection.swap(newConnection); + + return true; +} + +bool DtlsState::initBIO(QDtlsBasePrivate *dtlsBase) +{ + Q_ASSERT(dtlsBase); + Q_ASSERT(tlsContext && tlsConnection); + + BioMethod customMethod(q_BIO_meth_new(BIO_TYPE_DGRAM, dtlsbio::qdtlsMethodName), + dtlsutil::delete_bio_method); + if (!customMethod.data()) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + msgFunctionFailed("BIO_meth_new")); + return false; + } + + BIO_METHOD *biom = customMethod.data(); + q_BIO_meth_set_create(biom, dtlsbio::q_dgram_create); + q_BIO_meth_set_destroy(biom, dtlsbio::q_dgram_destroy); + q_BIO_meth_set_read(biom, dtlsbio::q_dgram_read); + q_BIO_meth_set_write(biom, dtlsbio::q_dgram_write); + q_BIO_meth_set_puts(biom, dtlsbio::q_dgram_puts); + q_BIO_meth_set_ctrl(biom, dtlsbio::q_dgram_ctrl); + + BIO *bio = q_BIO_new(biom); + if (!bio) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + msgFunctionFailed("BIO_new")); + return false; + } + + q_SSL_set_bio(tlsConnection.data(), bio, bio); + + bioMethod.swap(customMethod); + + return true; +} + +void DtlsState::setLinkMtu(QDtlsBasePrivate *dtlsBase) +{ + Q_ASSERT(dtlsBase); + Q_ASSERT(udpSocket); + Q_ASSERT(tlsConnection.data()); + + long mtu = dtlsBase->mtuHint; + if (!mtu) { + // If the underlying QUdpSocket was connected, getsockopt with + // IP_MTU/IP6_MTU can give us some hint: + bool optionFound = false; + if (udpSocket->state() == QAbstractSocket::ConnectedState) { + const QVariant val(udpSocket->socketOption(QAbstractSocket::PathMtuSocketOption)); + if (val.isValid() && val.canConvert<int>()) + mtu = val.toInt(&optionFound); + } + + if (!optionFound || mtu <= 0) { + // OK, our own initial guess. + mtu = long(dtlsutil::MtuGuess::defaultMtu); + } + } + + // For now, we disable this option. + q_SSL_set_options(tlsConnection.data(), SSL_OP_NO_QUERY_MTU); + + q_DTLS_set_link_mtu(tlsConnection.data(), mtu); +} + +} // namespace dtlsopenssl + +QDtlsClientVerifierOpenSSL::QDtlsClientVerifierOpenSSL() + : QDtlsBasePrivate(QSslSocket::SslServerMode, dtlsutil::fallbackSecret()) +{ +} + +bool QDtlsClientVerifierOpenSSL::verifyClient(QUdpSocket *socket, const QByteArray &dgram, + const QHostAddress &address, quint16 port) +{ + Q_ASSERT(socket); + Q_ASSERT(dgram.size()); + Q_ASSERT(!address.isNull()); + Q_ASSERT(port); + + clearDtlsError(); + verifiedClientHello.clear(); + + if (!dtls.init(this, socket, address, port, dgram)) + return false; + + dtls.secret = secret; + dtls.hashAlgorithm = hashAlgorithm; + + Q_ASSERT(dtls.tlsConnection.data()); + QSharedPointer<BIO_ADDR> peer(q_BIO_ADDR_new(), dtlsutil::delete_BIO_ADDR); + if (!peer.data()) { + setDtlsError(QDtlsError::TlsInitializationError, + QDtlsClientVerifier::tr("BIO_ADDR_new failed, ignoring client hello")); + return false; + } + + const int ret = q_DTLSv1_listen(dtls.tlsConnection.data(), peer.data()); + if (ret < 0) { + // Since 1.1 - it's a fatal error (not so in 1.0.2 for non-blocking socket) + setDtlsError(QDtlsError::TlsFatalError, QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + return false; + } + + if (ret > 0) { + verifiedClientHello = dgram; + return true; + } + + return false; +} + +QByteArray QDtlsClientVerifierOpenSSL::verifiedHello() const +{ + return verifiedClientHello; +} + +void QDtlsPrivateOpenSSL::TimeoutHandler::start(int hintMs) +{ + Q_ASSERT(timerId == -1); + timerId = startTimer(hintMs > 0 ? hintMs : timeoutMs, Qt::PreciseTimer); +} + +void QDtlsPrivateOpenSSL::TimeoutHandler::doubleTimeout() +{ + if (timeoutMs * 2 < 60000) + timeoutMs *= 2; + else + timeoutMs = 60000; +} + +void QDtlsPrivateOpenSSL::TimeoutHandler::stop() +{ + if (timerId != -1) { + killTimer(timerId); + timerId = -1; + } +} + +void QDtlsPrivateOpenSSL::TimeoutHandler::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + Q_ASSERT(timerId != -1); + + killTimer(timerId); + timerId = -1; + + Q_ASSERT(dtlsConnection); + dtlsConnection->reportTimeout(); +} + +QDtlsPrivateOpenSSL::QDtlsPrivateOpenSSL(QDtls *qObject, QSslSocket::SslMode side) + : QDtlsBasePrivate(side, dtlsutil::fallbackSecret()), q(qObject) +{ + Q_ASSERT(qObject); + + dtls.dtlsPrivate = this; +} + +QSslSocket::SslMode QDtlsPrivateOpenSSL::cryptographMode() const +{ + return mode; +} + +void QDtlsPrivateOpenSSL::setPeer(const QHostAddress &addr, quint16 port, const QString &name) +{ + remoteAddress = addr; + remotePort = port; + peerVfyName = name; +} + +QHostAddress QDtlsPrivateOpenSSL::peerAddress() const +{ + return remoteAddress; +} + +quint16 QDtlsPrivateOpenSSL::peerPort() const +{ + return remotePort; +} + +void QDtlsPrivateOpenSSL::setPeerVerificationName(const QString &name) +{ + peerVfyName = name; +} + +QString QDtlsPrivateOpenSSL::peerVerificationName() const +{ + return peerVfyName; +} + +void QDtlsPrivateOpenSSL::setDtlsMtuHint(quint16 mtu) +{ + mtuHint = mtu; +} + +quint16 QDtlsPrivateOpenSSL::dtlsMtuHint() const +{ + return mtuHint; +} + +QDtls::HandshakeState QDtlsPrivateOpenSSL::state() const +{ + return handshakeState; +} + +bool QDtlsPrivateOpenSSL::isConnectionEncrypted() const +{ + return connectionEncrypted; +} + +bool QDtlsPrivateOpenSSL::startHandshake(QUdpSocket *socket, const QByteArray &dgram) +{ + Q_ASSERT(socket); + Q_ASSERT(handshakeState == QDtls::HandshakeNotStarted); + + clearDtlsError(); + connectionEncrypted = false; + + if (!dtls.init(this, socket, remoteAddress, remotePort, dgram)) + return false; + + if (mode == QSslSocket::SslServerMode && dtlsConfiguration.dtlsCookieVerificationEnabled()) { + dtls.secret = secret; + dtls.hashAlgorithm = hashAlgorithm; + // Let's prepare the state machine so that message sequence 1 does not + // surprise DTLS/OpenSSL (such a message would be disregarded as + // 'stale or future' in SSL_accept otherwise): + int result = 0; + QSharedPointer<BIO_ADDR> peer(q_BIO_ADDR_new(), dtlsutil::delete_BIO_ADDR); + if (!peer.data()) { + setDtlsError(QDtlsError::TlsInitializationError, + QDtls::tr("BIO_ADD_new failed, cannot start handshake")); + return false; + } + + // If it's an invalid/unexpected ClientHello, we don't want to send + // VerifyClientRequest - it's a job of QDtlsClientVerifier - so we + // suppress any attempts to write into socket: + dtls.writeSuppressed = true; + result = q_DTLSv1_listen(dtls.tlsConnection.data(), peer.data()); + dtls.writeSuppressed = false; + + if (result <= 0) { + setDtlsError(QDtlsError::TlsFatalError, + QDtls::tr("Cannot start the handshake, verified client hello expected")); + dtls.reset(); + return false; + } + } + + handshakeState = QDtls::HandshakeInProgress; + opensslErrors.clear(); + tlsErrors.clear(); + + return continueHandshake(socket, dgram); +} + +bool QDtlsPrivateOpenSSL::continueHandshake(QUdpSocket *socket, const QByteArray &dgram) +{ + Q_ASSERT(socket); + + Q_ASSERT(handshakeState == QDtls::HandshakeInProgress); + + clearDtlsError(); + + if (timeoutHandler.data()) + timeoutHandler->stop(); + + if (!dtls.init(this, socket, remoteAddress, remotePort, dgram)) + return false; + + dtls.x509Errors.clear(); + + int result = 0; + if (mode == QSslSocket::SslServerMode) + result = q_SSL_accept(dtls.tlsConnection.data()); + else + result = q_SSL_connect(dtls.tlsConnection.data()); + + // DTLSTODO: Investigate/test if it makes sense - QSslSocket can emit + // peerVerifyError at this point (and thus potentially client code + // will close the underlying TCP connection immediately), but we are using + // QUdpSocket, no connection to close, our verification callback returns 1 + // (verified OK) and this probably means OpenSSL has already sent a reply + // to the server's hello/certificate. + + opensslErrors << dtls.x509Errors; + + if (result <= 0) { + const auto code = q_SSL_get_error(dtls.tlsConnection.data(), result); + switch (code) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // DTLSTODO: to be tested - in principle, if it was the first call to + // continueHandshake and server for some reason discards the client + // hello message (even the verified one) - our 'this' will probably + // forever stay in this strange InProgress state? (the client + // will dully re-transmit the same hello and we discard it again?) + // SSL_get_state can provide more information about state + // machine and we can switch to NotStarted (since we have not + // replied with our hello ...) + if (!timeoutHandler.data()) { + timeoutHandler.reset(new TimeoutHandler); + timeoutHandler->dtlsConnection = this; + } else { + // Back to 1s. + timeoutHandler->resetTimeout(); + } + + timeoutHandler->start(); + + return true; // The handshake is not yet complete. + default: + storePeerCertificates(); + setDtlsError(QDtlsError::TlsFatalError, + QTlsBackendOpenSSL::msgErrorsDuringHandshake()); + dtls.reset(); + handshakeState = QDtls::HandshakeNotStarted; + return false; + } + } + + storePeerCertificates(); + fetchNegotiatedParameters(); + + const bool doVerifyPeer = dtlsConfiguration.peerVerifyMode() == QSslSocket::VerifyPeer + || (dtlsConfiguration.peerVerifyMode() == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + + if (!doVerifyPeer || verifyPeer() || tlsErrorsWereIgnored()) { + connectionEncrypted = true; + handshakeState = QDtls::HandshakeComplete; + return true; + } + + setDtlsError(QDtlsError::PeerVerificationError, QDtls::tr("Peer verification failed")); + handshakeState = QDtls::PeerVerificationFailed; + return false; +} + + +bool QDtlsPrivateOpenSSL::handleTimeout(QUdpSocket *socket) +{ + Q_ASSERT(socket); + + Q_ASSERT(timeoutHandler.data()); + Q_ASSERT(dtls.tlsConnection.data()); + + clearDtlsError(); + + dtls.udpSocket = socket; + + if (q_DTLSv1_handle_timeout(dtls.tlsConnection.data()) > 0) { + timeoutHandler->doubleTimeout(); + timeoutHandler->start(); + } else { + timeoutHandler->start(dtlsutil::next_timeoutMs(dtls.tlsConnection.data())); + } + + return true; +} + +bool QDtlsPrivateOpenSSL::resumeHandshake(QUdpSocket *socket) +{ + Q_UNUSED(socket); + Q_ASSERT(socket); + Q_ASSERT(handshakeState == QDtls::PeerVerificationFailed); + + clearDtlsError(); + + if (tlsErrorsWereIgnored()) { + handshakeState = QDtls::HandshakeComplete; + connectionEncrypted = true; + tlsErrors.clear(); + tlsErrorsToIgnore.clear(); + return true; + } + + return false; +} + +void QDtlsPrivateOpenSSL::abortHandshake(QUdpSocket *socket) +{ + Q_ASSERT(socket); + Q_ASSERT(handshakeState == QDtls::PeerVerificationFailed + || handshakeState == QDtls::HandshakeInProgress); + + clearDtlsError(); + + if (handshakeState == QDtls::PeerVerificationFailed) { + // Yes, while peer verification failed, we were actually encrypted. + // Let's play it nice - inform our peer about connection shut down. + sendShutdownAlert(socket); + } else { + resetDtls(); + } +} + +void QDtlsPrivateOpenSSL::sendShutdownAlert(QUdpSocket *socket) +{ + Q_ASSERT(socket); + + clearDtlsError(); + + if (connectionEncrypted && !connectionWasShutdown) { + dtls.udpSocket = socket; + Q_ASSERT(dtls.tlsConnection.data()); + q_SSL_shutdown(dtls.tlsConnection.data()); + } + + resetDtls(); +} + +QList<QSslError> QDtlsPrivateOpenSSL::peerVerificationErrors() const +{ + return tlsErrors; +} + +void QDtlsPrivateOpenSSL::ignoreVerificationErrors(const QList<QSslError> &errorsToIgnore) +{ + tlsErrorsToIgnore = errorsToIgnore; +} + +QSslCipher QDtlsPrivateOpenSSL::dtlsSessionCipher() const +{ + return sessionCipher; +} + +QSsl::SslProtocol QDtlsPrivateOpenSSL::dtlsSessionProtocol() const +{ + return sessionProtocol; +} + +qint64 QDtlsPrivateOpenSSL::writeDatagramEncrypted(QUdpSocket *socket, + const QByteArray &dgram) +{ + Q_ASSERT(socket); + Q_ASSERT(dtls.tlsConnection.data()); + Q_ASSERT(connectionEncrypted); + + clearDtlsError(); + + dtls.udpSocket = socket; + const int written = q_SSL_write(dtls.tlsConnection.data(), + dgram.constData(), dgram.size()); + if (written > 0) + return written; + + const unsigned long errorCode = q_ERR_get_error(); + if (!dgram.size() && errorCode == SSL_ERROR_NONE) { + // With OpenSSL <= 1.1 this can happen. For example, DTLS client + // tries to reconnect (while re-using the same address/port) - + // DTLS server drops a message with unexpected epoch but says - no + // error. We leave to client code to resolve such problems until + // OpenSSL provides something better. + return 0; + } + + switch (errorCode) { + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + // We do not set any error/description ... a user can probably re-try + // sending a datagram. + break; + case SSL_ERROR_ZERO_RETURN: + connectionWasShutdown = true; + setDtlsError(QDtlsError::TlsFatalError, QDtls::tr("The DTLS connection has been closed")); + handshakeState = QDtls::HandshakeNotStarted; + dtls.reset(); + break; + case SSL_ERROR_SYSCALL: + case SSL_ERROR_SSL: + default: + // DTLSTODO: we don't know yet what to do. Tests needed - probably, + // some errors can be just ignored (it's UDP, not TCP after all). + // Unlike QSslSocket we do not abort though. + QString description(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + if (socket->error() != QAbstractSocket::UnknownSocketError && description.isEmpty()) { + setDtlsError(QDtlsError::UnderlyingSocketError, socket->errorString()); + } else { + setDtlsError(QDtlsError::TlsFatalError, + QDtls::tr("Error while writing: %1").arg(description)); + } + } + + return -1; +} + +QByteArray QDtlsPrivateOpenSSL::decryptDatagram(QUdpSocket *socket, const QByteArray &tlsdgram) +{ + Q_ASSERT(socket); + Q_ASSERT(tlsdgram.size()); + + Q_ASSERT(dtls.tlsConnection.data()); + Q_ASSERT(connectionEncrypted); + + dtls.dgram = tlsdgram; + dtls.udpSocket = socket; + + clearDtlsError(); + + QByteArray dgram; + dgram.resize(tlsdgram.size()); + const int read = q_SSL_read(dtls.tlsConnection.data(), dgram.data(), + dgram.size()); + + if (read > 0) { + dgram.resize(read); + return dgram; + } + + dgram.clear(); + unsigned long errorCode = q_ERR_get_error(); + if (errorCode == SSL_ERROR_NONE) { + const int shutdown = q_SSL_get_shutdown(dtls.tlsConnection.data()); + if (shutdown & SSL_RECEIVED_SHUTDOWN) + errorCode = SSL_ERROR_ZERO_RETURN; + else + return dgram; + } + + switch (errorCode) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return dgram; + case SSL_ERROR_ZERO_RETURN: + // "The connection was shut down cleanly" ... hmm, whatever, + // needs testing (DTLSTODO). + connectionWasShutdown = true; + setDtlsError(QDtlsError::RemoteClosedConnectionError, + QDtls::tr("The DTLS connection has been shutdown")); + dtls.reset(); + connectionEncrypted = false; + handshakeState = QDtls::HandshakeNotStarted; + return dgram; + case SSL_ERROR_SYSCALL: // some IO error + case SSL_ERROR_SSL: // error in the SSL library + // DTLSTODO: Apparently, some errors can be ignored, for example, + // ECONNRESET etc. This all needs a lot of testing!!! + default: + setDtlsError(QDtlsError::TlsNonFatalError, + QDtls::tr("Error while reading: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return dgram; + } +} + +unsigned QDtlsPrivateOpenSSL::pskClientCallback(const char *hint, char *identity, + unsigned max_identity_len, + unsigned char *psk, + unsigned max_psk_len) +{ + // The code below is taken (with some modifications) from qsslsocket_openssl + // - alas, we cannot simply re-use it, it's in QSslSocketPrivate. + { + QSslPreSharedKeyAuthenticator authenticator; + // Fill in some read-only fields (for client code) + if (hint) { + identityHint.clear(); + identityHint.append(hint); + } + + QTlsBackend::setupClientPskAuth(&authenticator, hint ? identityHint.constData() : nullptr, + hint ? int(std::strlen(hint)) : 0, max_identity_len, max_psk_len); + pskAuthenticator.swap(authenticator); + } + + // Let the client provide the remaining bits... + emit q->pskRequired(&pskAuthenticator); + + // No PSK set? Return now to make the handshake fail + if (pskAuthenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int identityLength = qMin(pskAuthenticator.identity().size(), + pskAuthenticator.maximumIdentityLength()); + std::memcpy(identity, pskAuthenticator.identity().constData(), identityLength); + identity[identityLength] = 0; + + const int pskLength = qMin(pskAuthenticator.preSharedKey().size(), + pskAuthenticator.maximumPreSharedKeyLength()); + std::memcpy(psk, pskAuthenticator.preSharedKey().constData(), pskLength); + + return pskLength; +} + +unsigned QDtlsPrivateOpenSSL::pskServerCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len) +{ + { + QSslPreSharedKeyAuthenticator authenticator; + // Fill in some read-only fields (for the user) + QTlsBackend::setupServerPskAuth(&authenticator, identity, dtlsConfiguration.preSharedKeyIdentityHint(), + max_psk_len); + pskAuthenticator.swap(authenticator); + } + + // Let the client provide the remaining bits... + emit q->pskRequired(&pskAuthenticator); + + // No PSK set? Return now to make the handshake fail + if (pskAuthenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int pskLength = qMin(pskAuthenticator.preSharedKey().size(), + pskAuthenticator.maximumPreSharedKeyLength()); + + std::memcpy(psk, pskAuthenticator.preSharedKey().constData(), pskLength); + + return pskLength; +} + +bool QDtlsPrivateOpenSSL::verifyPeer() +{ + QList<QSslError> errors; + + // Check the whole chain for blacklisting (including root, as we check for + // subjectInfo and issuer) + const auto &peerCertificateChain = dtlsConfiguration.peerCertificateChain(); + for (const QSslCertificate &cert : peerCertificateChain) { + if (QSslCertificatePrivate::isBlacklisted(cert)) + errors << QSslError(QSslError::CertificateBlacklisted, cert); + } + + const auto peerCertificate = dtlsConfiguration.peerCertificate(); + if (peerCertificate.isNull()) { + errors << QSslError(QSslError::NoPeerCertificate); + } else if (mode == QSslSocket::SslClientMode) { + // Check the peer certificate itself. First try the subject's common name + // (CN) as a wildcard, then try all alternate subject name DNS entries the + // same way. + + // QSslSocket has a rather twisted logic: if verificationPeerName + // is empty, we call QAbstractSocket::peerName(), which returns + // either peerName (can be set by setPeerName) or host name + // (can be set as a result of connectToHost). + QString name = peerVfyName; + if (name.isEmpty()) { + Q_ASSERT(dtls.udpSocket); + name = dtls.udpSocket->peerName(); + } + + if (!QTlsPrivate::TlsCryptograph::isMatchingHostname(peerCertificate, name)) + errors << QSslError(QSslError::HostNameMismatch, peerCertificate); + } + + // Translate errors from the error list into QSslErrors + using CertClass = QTlsPrivate::X509CertificateOpenSSL; + errors.reserve(errors.size() + opensslErrors.size()); + for (const auto &error : std::as_const(opensslErrors)) { + const auto value = peerCertificateChain.value(error.depth); + errors << CertClass::openSSLErrorToQSslError(error.code, value); + } + + tlsErrors = errors; + return tlsErrors.isEmpty(); +} + +void QDtlsPrivateOpenSSL::storePeerCertificates() +{ + Q_ASSERT(dtls.tlsConnection.data()); + // Store the peer certificate and chain. For clients, the peer certificate + // chain includes the peer certificate; for servers, it doesn't. Both the + // peer certificate and the chain may be empty if the peer didn't present + // any certificate. + X509 *x509 = q_SSL_get_peer_certificate(dtls.tlsConnection.data()); + const auto peerCertificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + QTlsBackend::storePeerCertificate(dtlsConfiguration, peerCertificate); + q_X509_free(x509); + + auto peerCertificateChain = dtlsConfiguration.peerCertificateChain(); + if (peerCertificateChain.isEmpty()) { + auto stack = q_SSL_get_peer_cert_chain(dtls.tlsConnection.data()); + peerCertificateChain = QTlsPrivate::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(stack); + if (!peerCertificate.isNull() && mode == QSslSocket::SslServerMode) + peerCertificateChain.prepend(peerCertificate); + QTlsBackend::storePeerCertificateChain(dtlsConfiguration, peerCertificateChain); + } +} + +bool QDtlsPrivateOpenSSL::tlsErrorsWereIgnored() const +{ + // check whether the errors we got are all in the list of expected errors + // (applies only if the method QDtlsConnection::ignoreTlsErrors(const + // QList<QSslError> &errors) was called) + for (const QSslError &error : tlsErrors) { + if (!tlsErrorsToIgnore.contains(error)) + return false; + } + + return !tlsErrorsToIgnore.empty(); +} + +void QDtlsPrivateOpenSSL::fetchNegotiatedParameters() +{ + Q_ASSERT(dtls.tlsConnection.data()); + + if (const SSL_CIPHER *cipher = q_SSL_get_current_cipher(dtls.tlsConnection.data())) + sessionCipher = QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(cipher); + else + sessionCipher = {}; + + // Note: cipher's protocol version will be reported as either TLS 1.0 or + // TLS 1.2, that's how it's set by OpenSSL (and that's what they are?). + + switch (q_SSL_version(dtls.tlsConnection.data())) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + case DTLS1_VERSION: + sessionProtocol = QSsl::DtlsV1_0; + break; +QT_WARNING_POP + case DTLS1_2_VERSION: + sessionProtocol = QSsl::DtlsV1_2; + break; + default: + qCWarning(lcTlsBackend, "unknown protocol version"); + sessionProtocol = QSsl::UnknownProtocol; + } +} + +void QDtlsPrivateOpenSSL::reportTimeout() +{ + emit q->handshakeTimeout(); +} + +void QDtlsPrivateOpenSSL::resetDtls() +{ + dtls.reset(); + connectionEncrypted = false; + tlsErrors.clear(); + tlsErrorsToIgnore.clear(); + QTlsBackend::clearPeerCertificates(dtlsConfiguration); + connectionWasShutdown = false; + handshakeState = QDtls::HandshakeNotStarted; + sessionCipher = {}; + sessionProtocol = QSsl::UnknownProtocol; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qdtls_openssl_p.h b/src/plugins/tls/openssl/qdtls_openssl_p.h new file mode 100644 index 0000000000..44be86f1ed --- /dev/null +++ b/src/plugins/tls/openssl/qdtls_openssl_p.h @@ -0,0 +1,214 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QDTLS_OPENSSL_P_H +#define QDTLS_OPENSSL_P_H + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include "qsslcontext_openssl_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qtls_openssl_p.h" +#include "qopenssl_p.h" + +#include "../shared/qdtls_base_p.h" + +#include <QtNetwork/private/qdtls_p.h> + +#include <QtNetwork/qsslpresharedkeyauthenticator.h> +#include <QtNetwork/qhostaddress.h> + +#include <QtCore/qsharedpointer.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> + +#include <openssl/ossl_typ.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_REQUIRE_CONFIG(openssl); +QT_REQUIRE_CONFIG(dtls); + +QT_BEGIN_NAMESPACE + +class QDtlsPrivateOpenSSL; +class QDtlsBasePrivate; +class QUdpSocket; + +namespace dtlsopenssl +{ + +class DtlsState +{ +public: + // Note, bioMethod _must_ outlive BIOs it was used to create. Thus + // the order of declarations here matters. + using BioMethod = QSharedPointer<BIO_METHOD>; + BioMethod bioMethod; + + using TlsContext = std::shared_ptr<QSslContext>; + TlsContext tlsContext; + + using TlsConnection = QSharedPointer<SSL>; + TlsConnection tlsConnection; + + QByteArray dgram; + + QHostAddress remoteAddress; + quint16 remotePort = 0; + + QList<QSslErrorEntry> x509Errors; + + long peeking = false; + QUdpSocket *udpSocket = nullptr; + bool writeSuppressed = false; + + bool init(QDtlsBasePrivate *dtlsBase, QUdpSocket *socket, + const QHostAddress &remote, quint16 port, + const QByteArray &receivedMessage); + + void reset(); + + QDtlsPrivateOpenSSL *dtlsPrivate = nullptr; + QByteArray secret; + +#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; +#else + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; +#endif + +private: + + bool initTls(QDtlsBasePrivate *dtlsBase); + bool initCtxAndConnection(QDtlsBasePrivate *dtlsBase); + bool initBIO(QDtlsBasePrivate *dtlsBase); + void setLinkMtu(QDtlsBasePrivate *dtlsBase); +}; + +} // namespace dtlsopenssl + +// The trick with 'right' ancestor in the tree overriding (only once) some shared +// virtual functions is intentional. Too bad MSVC warns me about ... exactly the +// feature of C++ that I want to use. + +QT_WARNING_PUSH +QT_WARNING_DISABLE_MSVC(4250) + +class QDtlsClientVerifierOpenSSL : public QTlsPrivate::DtlsCookieVerifier, public QDtlsBasePrivate +{ +public: + QDtlsClientVerifierOpenSSL(); + + bool verifyClient(QUdpSocket *socket, const QByteArray &dgram, + const QHostAddress &address, quint16 port) override; + QByteArray verifiedHello() const override; + +private: + dtlsopenssl::DtlsState dtls; + QByteArray verifiedClientHello; +}; + +class QDtlsPrivateOpenSSL : public QTlsPrivate::DtlsCryptograph, public QDtlsBasePrivate +{ +public: + + QDtlsPrivateOpenSSL(QDtls *qObject, QSslSocket::SslMode mode); + +private: + + QSslSocket::SslMode cryptographMode() const override; + void setPeer(const QHostAddress &addr, quint16 port, const QString &name) override; + QHostAddress peerAddress() const override; + quint16 peerPort() const override; + void setPeerVerificationName(const QString &name) override; + QString peerVerificationName() const override; + + virtual void setDtlsMtuHint(quint16 mtu) override; + virtual quint16 dtlsMtuHint() const override; + + virtual QDtls::HandshakeState state() const override; + virtual bool isConnectionEncrypted() const override; + + bool startHandshake(QUdpSocket *socket, const QByteArray &datagram) override; + bool continueHandshake(QUdpSocket *socket, const QByteArray &datagram) override; + bool resumeHandshake(QUdpSocket *socket) override; + void abortHandshake(QUdpSocket *socket) override; + bool handleTimeout(QUdpSocket *socket) override; + void sendShutdownAlert(QUdpSocket *socket) override; + + QList<QSslError> peerVerificationErrors() const override; + void ignoreVerificationErrors(const QList<QSslError> &errorsToIgnore) override; + + QSslCipher dtlsSessionCipher() const override; + QSsl::SslProtocol dtlsSessionProtocol() const override; + + qint64 writeDatagramEncrypted(QUdpSocket *socket, const QByteArray &datagram) override; + QByteArray decryptDatagram(QUdpSocket *socket, const QByteArray &tlsdgram) override; + +public: + unsigned pskClientCallback(const char *hint, char *identity, unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len); + unsigned pskServerCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len); + +private: + + bool verifyPeer(); + void storePeerCertificates(); + bool tlsErrorsWereIgnored() const; + void fetchNegotiatedParameters(); + void reportTimeout(); + void resetDtls(); + + QList<QSslErrorEntry> opensslErrors; + dtlsopenssl::DtlsState dtls; + + // We have to externally handle timeouts since we have non-blocking + // sockets and OpenSSL(DTLS) with non-blocking UDP sockets does not + // know if a timeout has occurred. + struct TimeoutHandler : QObject + { + TimeoutHandler() = default; + + void start(int hintMs = 0); + void doubleTimeout(); + void resetTimeout() {timeoutMs = 1000;} + void stop(); + void timerEvent(QTimerEvent *event) override; + + int timerId = -1; + int timeoutMs = 1000; + + QDtlsPrivateOpenSSL *dtlsConnection = nullptr; + }; + + QDtls *q = nullptr; + QDtls::HandshakeState handshakeState = QDtls::HandshakeNotStarted; + + QList<QSslError> tlsErrors; + QList<QSslError> tlsErrorsToIgnore; + bool connectionEncrypted = false; + // We will initialize it 'lazily', just in case somebody wants to move + // QDtls to another thread. + QScopedPointer<TimeoutHandler> timeoutHandler; + bool connectionWasShutdown = false; + QSslPreSharedKeyAuthenticator pskAuthenticator; + QByteArray identityHint; +}; + +QT_WARNING_POP // C4250 + +QT_END_NAMESPACE + +#endif // QDTLS_OPENSSL_P_H diff --git a/src/plugins/tls/openssl/qopenssl_p.h b/src/plugins/tls/openssl/qopenssl_p.h new file mode 100644 index 0000000000..370b974630 --- /dev/null +++ b/src/plugins/tls/openssl/qopenssl_p.h @@ -0,0 +1,82 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#ifndef QSSLSOCKET_OPENSSL_P_H +#define QSSLSOCKET_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtNetwork/private/qsslsocket_p.h> + +#include <QtNetwork/qsslcipher.h> + +#ifdef Q_OS_WIN +#include <qt_windows.h> +#if defined(OCSP_RESPONSE) +#undef OCSP_RESPONSE +#endif +#if defined(X509_NAME) +#undef X509_NAME +#endif +#endif // Q_OS_WIN + +// This file is included in several *.cpp files and provides different +// openssl declarations where they are needed. +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/pkcs12.h> +#include <openssl/pkcs7.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> +#include <openssl/stack.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/x509_vfy.h> +#include <openssl/dsa.h> +#include <openssl/rsa.h> +#include <openssl/crypto.h> +#include <openssl/tls1.h> +#include <openssl/dh.h> + +QT_BEGIN_NAMESPACE + +struct QSslErrorEntry { + int code = 0; + int depth = 0; +}; + +Q_DECLARE_TYPEINFO(QSslErrorEntry, Q_PRIMITIVE_TYPE); + +QT_END_NAMESPACE + +#endif 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 diff --git a/src/plugins/tls/openssl/qsslcontext_openssl_p.h b/src/plugins/tls/openssl/qsslcontext_openssl_p.h new file mode 100644 index 0000000000..3bd39baf0c --- /dev/null +++ b/src/plugins/tls/openssl/qsslcontext_openssl_p.h @@ -0,0 +1,96 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2014 BlackBerry Limited. All rights reserved. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#ifndef QSSLCONTEXT_OPENSSL_P_H +#define QSSLCONTEXT_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> +#include <QtCore/qvariant.h> +#include <QtNetwork/qsslcertificate.h> +#include <QtNetwork/qsslconfiguration.h> +#include <openssl/ssl.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SSL + +class QSslContext +{ +public: + + ~QSslContext(); + + static std::shared_ptr<QSslContext> sharedFromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, + bool allowRootCertOnDemandLoading); + static std::shared_ptr<QSslContext> sharedFromPrivateConfiguration(QSslSocket::SslMode mode, QSslConfigurationPrivate *privConfiguration, + bool allowRootCertOnDemandLoading); + + static qssloptions setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions); + + QSslError::SslError error() const; + QString errorString() const; + + SSL* createSsl(); + bool cacheSession(SSL*); // should be called when handshake completed + + QByteArray sessionASN1() const; + void setSessionASN1(const QByteArray &sessionASN1); + int sessionTicketLifeTimeHint() const; + + static void forceAutoTestSecurityLevel(); + +#ifndef OPENSSL_NO_NEXTPROTONEG + // must be public because we want to use it from an OpenSSL callback + struct NPNContext { + NPNContext() : data(nullptr), + len(0), + status(QSslConfiguration::NextProtocolNegotiationNone) + { } + unsigned char *data; + unsigned short len; + QSslConfiguration::NextProtocolNegotiationStatus status; + }; + NPNContext npnContext() const; +#endif // !OPENSSL_NO_NEXTPROTONEG + +protected: + QSslContext(); + +private: + static void initSslContext(QSslContext* sslContext, QSslSocket::SslMode mode, const QSslConfiguration &configuration, + bool allowRootCertOnDemandLoading); + static void applyBackendConfig(QSslContext *sslContext); + +private: + SSL_CTX* ctx; + EVP_PKEY *pkey; + SSL_SESSION *session; + QByteArray m_sessionASN1; + int m_sessionTicketLifeTimeHint; + QSslError::SslError errorCode; + QString errorStr; + QSslConfiguration sslConfiguration; +#ifndef OPENSSL_NO_NEXTPROTONEG + QByteArray m_supportedNPNVersions; + NPNContext m_npnContext; +#endif // !OPENSSL_NO_NEXTPROTONEG +}; + +#endif // QT_NO_SSL + +QT_END_NAMESPACE + +#endif // QSSLCONTEXT_OPENSSL_P_H diff --git a/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp b/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp new file mode 100644 index 0000000000..16e31e605f --- /dev/null +++ b/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp @@ -0,0 +1,159 @@ +// Copyright (C) 2015 Mikkel Krautz <mikkel@krautz.dk> +// 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 "qsslsocket_openssl_symbols_p.h" +#include "qtlsbackend_openssl_p.h" + +#include <QtNetwork/private/qsslsocket_p.h> + +#include <QtCore/qscopeguard.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qdebug.h> + +#include <openssl/bn.h> +#include <openssl/dh.h> + +QT_BEGIN_NAMESPACE + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + +namespace { + +bool isSafeDH(DH *dh) +{ + int status = 0; + int bad = 0; + + // TLSTODO: check it's needed or if supportsSsl() + // is enough. + QSslSocketPrivate::ensureInitialized(); + + // From https://wiki.openssl.org/index.php/Diffie-Hellman_parameters: + // + // The additional call to BN_mod_word(dh->p, 24) + // (and unmasking of DH_NOT_SUITABLE_GENERATOR) + // is performed to ensure your program accepts + // IETF group parameters. OpenSSL checks the prime + // is congruent to 11 when g = 2; while the IETF's + // primes are congruent to 23 when g = 2. + // Without the test, the IETF parameters would + // fail validation. For details, see Diffie-Hellman + // Parameter Check (when g = 2, must p mod 24 == 11?). + // Mark p < 1024 bits as unsafe. + if (q_DH_bits(dh) < 1024) + return false; + + if (q_DH_check(dh, &status) != 1) + return false; + + const BIGNUM *p = nullptr; + const BIGNUM *q = nullptr; + const BIGNUM *g = nullptr; + q_DH_get0_pqg(dh, &p, &q, &g); + + if (q_BN_is_word(const_cast<BIGNUM *>(g), DH_GENERATOR_2)) { + const unsigned long residue = q_BN_mod_word(p, 24); + if (residue == 11 || residue == 23) + status &= ~DH_NOT_SUITABLE_GENERATOR; + } + + bad |= DH_CHECK_P_NOT_PRIME; + bad |= DH_CHECK_P_NOT_SAFE_PRIME; + bad |= DH_NOT_SUITABLE_GENERATOR; + + return !(status & bad); +} + +} // unnamed namespace + +#endif + +int QTlsBackendOpenSSL::dhParametersFromDer(const QByteArray &der, QByteArray *derData) const +{ +#ifndef OPENSSL_NO_DEPRECATED_3_0 + Q_ASSERT(derData); + + if (der.isEmpty()) + return DHParams::InvalidInputDataError; + + const unsigned char *data = reinterpret_cast<const unsigned char *>(der.data()); + const int len = der.size(); + + // TLSTODO: check it's needed (loading ciphers and certs in + // addition to the library!) + QSslSocketPrivate::ensureInitialized(); + + DH *dh = q_d2i_DHparams(nullptr, &data, len); + if (dh) { + const auto dhRaii = qScopeGuard([dh] {q_DH_free(dh);}); + + if (isSafeDH(dh)) + *derData = der; + else + return DHParams::UnsafeParametersError; + } else { + return DHParams::InvalidInputDataError; + } +#else + Q_UNUSED(der); + Q_UNUSED(derData); + qCWarning(lcTlsBackend, "Diffie-Hellman parameters are not supported, because OpenSSL v3 was built with deprecated API removed"); +#endif + return DHParams::NoError; +} + +int QTlsBackendOpenSSL::dhParametersFromPem(const QByteArray &pem, QByteArray *data) const +{ +#ifndef OPENSSL_NO_DEPRECATED_3_0 + Q_ASSERT(data); + + if (pem.isEmpty()) + return DHParams::InvalidInputDataError; + + // TLSTODO: check it was not a cargo-cult programming in case of + // DH ... + QSslSocketPrivate::ensureInitialized(); + + BIO *bio = q_BIO_new_mem_buf(const_cast<char *>(pem.data()), pem.size()); + if (!bio) + return DHParams::InvalidInputDataError; + + const auto bioRaii = qScopeGuard([bio] + { + q_BIO_free(bio); + }); + + DH *dh = nullptr; + q_PEM_read_bio_DHparams(bio, &dh, nullptr, nullptr); + + if (dh) { + const auto dhGuard = qScopeGuard([dh] + { + q_DH_free(dh); + }); + + if (isSafeDH(dh)) { + char *buf = nullptr; + const int len = q_i2d_DHparams(dh, reinterpret_cast<unsigned char **>(&buf)); + const auto freeBuf = qScopeGuard([&] { q_OPENSSL_free(buf); }); + if (len > 0) + data->assign({buf, len}); + else + return DHParams::InvalidInputDataError; + } else { + return DHParams::UnsafeParametersError; + } + } else { + return DHParams::InvalidInputDataError; + } +#else + Q_UNUSED(pem); + Q_UNUSED(data); + qCWarning(lcTlsBackend, "Diffie-Hellman parameters are not supported, because OpenSSL v3 was built with deprecated API removed"); +#endif + return DHParams::NoError; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_android.cpp b/src/plugins/tls/openssl/qsslsocket_openssl_android.cpp new file mode 100644 index 0000000000..6c02215c55 --- /dev/null +++ b/src/plugins/tls/openssl/qsslsocket_openssl_android.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#include <QtCore/QJniEnvironment> +#include <QtCore/QJniObject> +#include <QtCore/QList> +#include <QtCore/QByteArray> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +QList<QByteArray> fetchSslCertificateData() +{ + QList<QByteArray> certificateData; + + QJniObject certificates = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", + "getSSLCertificates", + "()[[B"); + if (!certificates.isValid()) + return certificateData; + + QJniEnvironment env; + jobjectArray jcertificates = certificates.object<jobjectArray>(); + const jint nCertificates = env->GetArrayLength(jcertificates); + certificateData.reserve(static_cast<int>(nCertificates)); + + for (int i = 0; i < nCertificates; ++i) { + jbyteArray jCert = static_cast<jbyteArray>(env->GetObjectArrayElement(jcertificates, i)); + const uint sz = env->GetArrayLength(jCert); + jbyte *buffer = env->GetByteArrayElements(jCert, 0); + certificateData.append(QByteArray(reinterpret_cast<char*>(buffer), sz)); + + env->ReleaseByteArrayElements(jCert, buffer, JNI_ABORT); // don't copy back the elements + env->DeleteLocalRef(jCert); + } + + return certificateData; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp b/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp new file mode 100644 index 0000000000..4aa9ca6fb1 --- /dev/null +++ b/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp @@ -0,0 +1,1266 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2014 BlackBerry Limited. All rights reserved. +// 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 + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#include "qsslsocket_openssl_symbols_p.h" +#include "qtlsbackend_openssl_p.h" + +#include <QtNetwork/private/qssl_p.h> + +#ifdef Q_OS_WIN +# include <QtCore/private/qsystemlibrary_p.h> +#elif QT_CONFIG(library) +# include <QtCore/qlibrary.h> +#endif +#include <QtCore/qdatetime.h> +#if defined(Q_OS_UNIX) +#include <QtCore/qdir.h> +#endif +#include <QtCore/private/qduplicatetracker_p.h> +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +#include <link.h> +#endif +#ifdef Q_OS_DARWIN +#include <QtCore/private/qcore_mac_p.h> +#endif + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +/* + Note to maintainer: + ------------------- + + We load OpenSSL symbols dynamically. Because symbols are known to + disappear, and signatures sometimes change, between releases, we need to + be careful about how this is done. To ensure we don't end up dereferencing + null function pointers, and continue running even if certain functions are + missing, we define helper functions for each of the symbols we load from + OpenSSL, all prefixed with "q_" (declared in + qsslsocket_openssl_symbols_p.h). So instead of calling SSL_connect + directly, we call q_SSL_connect, which is a function that checks if the + actual SSL_connect fptr is null, and returns a failure if it is, or calls + SSL_connect if it isn't. + + This requires a somewhat tedious process of declaring each function we + want to call in OpenSSL thrice: once with the q_, in _p.h, once using the + DEFINEFUNC macros below, and once in the function that actually resolves + the symbols, below the DEFINEFUNC declarations below. + + There's one DEFINEFUNC macro declared for every number of arguments + exposed by OpenSSL (feel free to extend when needed). The easiest thing to + do is to find an existing entry that matches the arg count of the function + you want to import, and do the same. + + The first macro arg is the function return type. The second is the + verbatim name of the function/symbol. Then follows a list of N pairs of + argument types with a variable name, and just the variable name (char *a, + a, char *b, b, etc). Finally there's two arguments - a suitable return + statement for the error case (for an int function, return 0 or return -1 + is usually right). Then either just "return" or DUMMYARG, the latter being + for void functions. + + Note: Take into account that these macros and declarations are processed + at compile-time, and the result depends on the OpenSSL headers the + compiling host has installed, but the symbols are resolved at run-time, + possibly with a different version of OpenSSL. +*/ + +#ifndef QT_LINKED_OPENSSL + +namespace { +void qsslSocketUnresolvedSymbolWarning(const char *functionName) +{ + qCWarning(lcTlsBackend, "QSslSocket: cannot call unresolved function %s", functionName); +} + +#if QT_CONFIG(library) +void qsslSocketCannotResolveSymbolWarning(const char *functionName) +{ + qCWarning(lcTlsBackend, "QSslSocket: cannot resolve %s", functionName); +} +#endif + +} + +#endif // QT_LINKED_OPENSSL + +DEFINEFUNC(const unsigned char *, ASN1_STRING_get0_data, const ASN1_STRING *a, a, return nullptr, return) +DEFINEFUNC2(int, OPENSSL_init_ssl, uint64_t opts, opts, const OPENSSL_INIT_SETTINGS *settings, settings, return 0, return) +DEFINEFUNC2(int, OPENSSL_init_crypto, uint64_t opts, opts, const OPENSSL_INIT_SETTINGS *settings, settings, return 0, return) +DEFINEFUNC(BIO *, BIO_new, const BIO_METHOD *a, a, return nullptr, return) +DEFINEFUNC(const BIO_METHOD *, BIO_s_mem, void, DUMMYARG, return nullptr, return) +DEFINEFUNC2(int, BN_is_word, BIGNUM *a, a, BN_ULONG w, w, return 0, return) +DEFINEFUNC(int, EVP_CIPHER_CTX_reset, EVP_CIPHER_CTX *c, c, return 0, return) +DEFINEFUNC(int, EVP_PKEY_up_ref, EVP_PKEY *a, a, return 0, return) +DEFINEFUNC2(EVP_PKEY_CTX *, EVP_PKEY_CTX_new, EVP_PKEY *pkey, pkey, ENGINE *e, e, return nullptr, return) +DEFINEFUNC(int, EVP_PKEY_param_check, EVP_PKEY_CTX *ctx, ctx, return 0, return) +DEFINEFUNC(void, EVP_PKEY_CTX_free, EVP_PKEY_CTX *ctx, ctx, return, return) +DEFINEFUNC(int, OPENSSL_sk_num, OPENSSL_STACK *a, a, return -1, return) +DEFINEFUNC2(void, OPENSSL_sk_pop_free, OPENSSL_STACK *a, a, void (*b)(void*), b, return, DUMMYARG) +DEFINEFUNC(OPENSSL_STACK *, OPENSSL_sk_new_null, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(void, OPENSSL_sk_push, OPENSSL_STACK *a, a, void *b, b, return, DUMMYARG) +DEFINEFUNC(void, OPENSSL_sk_free, OPENSSL_STACK *a, a, return, DUMMYARG) +DEFINEFUNC2(void *, OPENSSL_sk_value, OPENSSL_STACK *a, a, int b, b, return nullptr, return) +DEFINEFUNC(int, SSL_session_reused, SSL *a, a, return 0, return) +DEFINEFUNC2(qssloptions, SSL_CTX_set_options, SSL_CTX *ctx, ctx, qssloptions op, op, return 0, return) +using info_callback = void (*) (const SSL *ssl, int type, int val); +DEFINEFUNC2(void, SSL_set_info_callback, SSL *ssl, ssl, info_callback cb, cb, return, return) +DEFINEFUNC(const char *, SSL_alert_type_string, int value, value, return nullptr, return) +DEFINEFUNC(const char *, SSL_alert_desc_string_long, int value, value, return nullptr, return) +DEFINEFUNC(int, SSL_CTX_get_security_level, const SSL_CTX *ctx, ctx, return -1, return) +DEFINEFUNC2(void, SSL_CTX_set_security_level, SSL_CTX *ctx, ctx, int level, level, return, return) +#ifdef TLS1_3_VERSION +DEFINEFUNC2(int, SSL_CTX_set_ciphersuites, SSL_CTX *ctx, ctx, const char *str, str, return 0, return) +DEFINEFUNC2(void, SSL_set_psk_use_session_callback, SSL *ssl, ssl, q_SSL_psk_use_session_cb_func_t callback, callback, return, DUMMYARG) +DEFINEFUNC2(void, SSL_CTX_sess_set_new_cb, SSL_CTX *ctx, ctx, NewSessionCallback cb, cb, return, return) +DEFINEFUNC(int, SSL_SESSION_is_resumable, const SSL_SESSION *s, s, return 0, return) +#endif +DEFINEFUNC3(size_t, SSL_get_client_random, SSL *a, a, unsigned char *out, out, size_t outlen, outlen, return 0, return) +DEFINEFUNC3(size_t, SSL_SESSION_get_master_key, const SSL_SESSION *ses, ses, unsigned char *out, out, size_t outlen, outlen, return 0, return) +DEFINEFUNC6(int, CRYPTO_get_ex_new_index, int class_index, class_index, long argl, argl, void *argp, argp, CRYPTO_EX_new *new_func, new_func, CRYPTO_EX_dup *dup_func, dup_func, CRYPTO_EX_free *free_func, free_func, return -1, return) +DEFINEFUNC2(unsigned long, SSL_set_options, SSL *ssl, ssl, unsigned long op, op, return 0, return) + +DEFINEFUNC(const SSL_METHOD *, TLS_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const SSL_METHOD *, TLS_client_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const SSL_METHOD *, TLS_server_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, X509_up_ref, X509 *a, a, return, DUMMYARG) +DEFINEFUNC(ASN1_TIME *, X509_getm_notBefore, X509 *a, a, return nullptr, return) +DEFINEFUNC(ASN1_TIME *, X509_getm_notAfter, X509 *a, a, return nullptr, return) +DEFINEFUNC2(void, ASN1_item_free, ASN1_VALUE *val, val, const ASN1_ITEM *it, it, return, return) +DEFINEFUNC(void, X509V3_conf_free, CONF_VALUE *val, val, return, return) +DEFINEFUNC(long, X509_get_version, X509 *a, a, return -1, return) +DEFINEFUNC(EVP_PKEY *, X509_get_pubkey, X509 *a, a, return nullptr, return) +DEFINEFUNC2(void, X509_STORE_set_verify_cb, X509_STORE *a, a, X509_STORE_CTX_verify_cb verify_cb, verify_cb, return, DUMMYARG) +DEFINEFUNC3(int, X509_STORE_set_ex_data, X509_STORE *a, a, int idx, idx, void *data, data, return 0, return) +DEFINEFUNC2(void *, X509_STORE_get_ex_data, X509_STORE *r, r, int idx, idx, return nullptr, return) +DEFINEFUNC(STACK_OF(X509) *, X509_STORE_CTX_get0_chain, X509_STORE_CTX *a, a, return nullptr, return) +DEFINEFUNC3(void, CRYPTO_free, void *str, str, const char *file, file, int line, line, return, DUMMYARG) +DEFINEFUNC3(int, CRYPTO_memcmp, const void * in_a, in_a, const void * in_b, in_b, size_t len, len, return 1, return); +DEFINEFUNC(long, OpenSSL_version_num, void, DUMMYARG, return 0, return) +DEFINEFUNC(const char *, OpenSSL_version, int a, a, return nullptr, return) +DEFINEFUNC(unsigned long, SSL_SESSION_get_ticket_lifetime_hint, const SSL_SESSION *session, session, return 0, return) + +#if QT_CONFIG(dtls) +DEFINEFUNC2(int, DTLSv1_listen, SSL *s, s, BIO_ADDR *c, c, return -1, return) +DEFINEFUNC(BIO_ADDR *, BIO_ADDR_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, BIO_ADDR_free, BIO_ADDR *ap, ap, return, DUMMYARG) +DEFINEFUNC2(BIO_METHOD *, BIO_meth_new, int type, type, const char *name, name, return nullptr, return) +DEFINEFUNC(void, BIO_meth_free, BIO_METHOD *biom, biom, return, DUMMYARG) +DEFINEFUNC2(int, BIO_meth_set_write, BIO_METHOD *biom, biom, DgramWriteCallback write, write, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_read, BIO_METHOD *biom, biom, DgramReadCallback read, read, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_puts, BIO_METHOD *biom, biom, DgramPutsCallback puts, puts, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_ctrl, BIO_METHOD *biom, biom, DgramCtrlCallback ctrl, ctrl, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_create, BIO_METHOD *biom, biom, DgramCreateCallback crt, crt, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_destroy, BIO_METHOD *biom, biom, DgramDestroyCallback dtr, dtr, return 0, return) +#endif // dtls + +#if QT_CONFIG(ocsp) +DEFINEFUNC(const OCSP_CERTID *, OCSP_SINGLERESP_get0_id, const OCSP_SINGLERESP *x, x, return nullptr, return) +DEFINEFUNC3(OCSP_RESPONSE *, d2i_OCSP_RESPONSE, OCSP_RESPONSE **a, a, const unsigned char **in, in, long len, len, return nullptr, return) +DEFINEFUNC(void, OCSP_RESPONSE_free, OCSP_RESPONSE *rs, rs, return, DUMMYARG) +DEFINEFUNC(OCSP_BASICRESP *, OCSP_response_get1_basic, OCSP_RESPONSE *resp, resp, return nullptr, return) +DEFINEFUNC(void, OCSP_BASICRESP_free, OCSP_BASICRESP *bs, bs, return, DUMMYARG) +DEFINEFUNC(int, OCSP_response_status, OCSP_RESPONSE *resp, resp, return OCSP_RESPONSE_STATUS_INTERNALERROR, return) +DEFINEFUNC4(int, OCSP_basic_verify, OCSP_BASICRESP *bs, bs, STACK_OF(X509) *certs, certs, X509_STORE *st, st, unsigned long flags, flags, return -1, return) +DEFINEFUNC(int, OCSP_resp_count, OCSP_BASICRESP *bs, bs, return 0, return) +DEFINEFUNC2(OCSP_SINGLERESP *, OCSP_resp_get0, OCSP_BASICRESP *bs, bs, int idx, idx, return nullptr, return) +DEFINEFUNC5(int, OCSP_single_get0_status, OCSP_SINGLERESP *single, single, int *reason, reason, ASN1_GENERALIZEDTIME **revtime, revtime, + ASN1_GENERALIZEDTIME **thisupd, thisupd, ASN1_GENERALIZEDTIME **nextupd, nextupd, return -1, return) +DEFINEFUNC4(int, OCSP_check_validity, ASN1_GENERALIZEDTIME *thisupd, thisupd, ASN1_GENERALIZEDTIME *nextupd, nextupd, long nsec, nsec, long maxsec, maxsec, return 0, return) +DEFINEFUNC3(OCSP_CERTID *, OCSP_cert_to_id, const EVP_MD *dgst, dgst, X509 *subject, subject, X509 *issuer, issuer, return nullptr, return) +DEFINEFUNC(void, OCSP_CERTID_free, OCSP_CERTID *cid, cid, return, DUMMYARG) +DEFINEFUNC5(int, OCSP_id_get0_info, ASN1_OCTET_STRING **piNameHash, piNameHash, ASN1_OBJECT **pmd, pmd, + ASN1_OCTET_STRING **piKeyHash, piKeyHash, ASN1_INTEGER **pserial, pserial, OCSP_CERTID *cid, cid, + return 0, return) +DEFINEFUNC2(OCSP_RESPONSE *, OCSP_response_create, int status, status, OCSP_BASICRESP *bs, bs, return nullptr, return) +DEFINEFUNC(const STACK_OF(X509) *, OCSP_resp_get0_certs, const OCSP_BASICRESP *bs, bs, return nullptr, return) +DEFINEFUNC2(int, OCSP_id_cmp, OCSP_CERTID *a, a, OCSP_CERTID *b, b, return -1, return) +DEFINEFUNC7(OCSP_SINGLERESP *, OCSP_basic_add1_status, OCSP_BASICRESP *r, r, OCSP_CERTID *c, c, int s, s, + int re, re, ASN1_TIME *rt, rt, ASN1_TIME *t, t, ASN1_TIME *n, n, return nullptr, return) +DEFINEFUNC(OCSP_BASICRESP *, OCSP_BASICRESP_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(int, i2d_OCSP_RESPONSE, OCSP_RESPONSE *r, r, unsigned char **ppout, ppout, return 0, return) +DEFINEFUNC6(int, OCSP_basic_sign, OCSP_BASICRESP *br, br, X509 *signer, signer, EVP_PKEY *key, key, + const EVP_MD *dg, dg, STACK_OF(X509) *cs, cs, unsigned long flags, flags, return 0, return) +#endif // ocsp + +DEFINEFUNC(void, AUTHORITY_INFO_ACCESS_free, AUTHORITY_INFO_ACCESS *p, p, return, return) +DEFINEFUNC2(void, BIO_set_data, BIO *a, a, void *ptr, ptr, return, DUMMYARG) +DEFINEFUNC(void *, BIO_get_data, BIO *a, a, return nullptr, return) +DEFINEFUNC2(void, BIO_set_init, BIO *a, a, int init, init, return, DUMMYARG) +DEFINEFUNC(int, BIO_get_shutdown, BIO *a, a, return -1, return) +DEFINEFUNC2(void, BIO_set_shutdown, BIO *a, a, int shut, shut, return, DUMMYARG) + +DEFINEFUNC(long, ASN1_INTEGER_get, ASN1_INTEGER *a, a, return 0, return) +DEFINEFUNC2(int, ASN1_INTEGER_cmp, const ASN1_INTEGER *a, a, const ASN1_INTEGER *b, b, return 1, return) +DEFINEFUNC(int, ASN1_STRING_length, ASN1_STRING *a, a, return 0, return) +DEFINEFUNC2(int, ASN1_STRING_to_UTF8, unsigned char **a, a, ASN1_STRING *b, b, return 0, return) +DEFINEFUNC2(int, ASN1_TIME_to_tm, const ASN1_TIME *s, s, struct tm *tm, tm, return 0, return) +DEFINEFUNC4(long, BIO_ctrl, BIO *a, a, int b, b, long c, c, void *d, d, return -1, return) +DEFINEFUNC(int, BIO_free, BIO *a, a, return 0, return) +DEFINEFUNC2(BIO *, BIO_new_mem_buf, void *a, a, int b, b, return nullptr, return) +DEFINEFUNC3(int, BIO_read, BIO *a, a, void *b, b, int c, c, return -1, return) + +DEFINEFUNC3(int, BIO_write, BIO *a, a, const void *b, b, int c, c, return -1, return) +DEFINEFUNC(int, BN_num_bits, const BIGNUM *a, a, return 0, return) +DEFINEFUNC2(BN_ULONG, BN_mod_word, const BIGNUM *a, a, BN_ULONG w, w, return static_cast<BN_ULONG>(-1), return) +DEFINEFUNC3(X509 *, d2i_X509, X509 **a, a, const unsigned char **b, b, long c, c, return nullptr, return) +DEFINEFUNC2(char *, ERR_error_string, unsigned long a, a, char *b, b, return nullptr, return) +DEFINEFUNC3(void, ERR_error_string_n, unsigned long e, e, char *b, b, size_t len, len, return, DUMMYARG) +DEFINEFUNC(unsigned long, ERR_get_error, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(EVP_CIPHER_CTX *, EVP_CIPHER_CTX_new, void, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, EVP_CIPHER_CTX_free, EVP_CIPHER_CTX *a, a, return, DUMMYARG) +DEFINEFUNC4(int, EVP_CIPHER_CTX_ctrl, EVP_CIPHER_CTX *ctx, ctx, int type, type, int arg, arg, void *ptr, ptr, return 0, return) +DEFINEFUNC2(int, EVP_CIPHER_CTX_set_key_length, EVP_CIPHER_CTX *ctx, ctx, int keylen, keylen, return 0, return) +DEFINEFUNC5(int, EVP_CipherInit, EVP_CIPHER_CTX *ctx, ctx, const EVP_CIPHER *type, type, const unsigned char *key, key, const unsigned char *iv, iv, int enc, enc, return 0, return) +DEFINEFUNC6(int, EVP_CipherInit_ex, EVP_CIPHER_CTX *ctx, ctx, const EVP_CIPHER *cipher, cipher, ENGINE *impl, impl, const unsigned char *key, key, const unsigned char *iv, iv, int enc, enc, return 0, return) +DEFINEFUNC5(int, EVP_CipherUpdate, EVP_CIPHER_CTX *ctx, ctx, unsigned char *out, out, int *outl, outl, const unsigned char *in, in, int inl, inl, return 0, return) +DEFINEFUNC3(int, EVP_CipherFinal, EVP_CIPHER_CTX *ctx, ctx, unsigned char *out, out, int *outl, outl, return 0, return) +DEFINEFUNC(const EVP_MD *, EVP_get_digestbyname, const char *name, name, return nullptr, return) +#ifndef OPENSSL_NO_DES +DEFINEFUNC(const EVP_CIPHER *, EVP_des_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_des_ede3_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +#endif +#ifndef OPENSSL_NO_RC2 +DEFINEFUNC(const EVP_CIPHER *, EVP_rc2_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +#endif +#ifndef OPENSSL_NO_AES +DEFINEFUNC(const EVP_CIPHER *, EVP_aes_128_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_aes_192_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_aes_256_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +#endif +DEFINEFUNC(const EVP_MD *, EVP_sha1, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, EVP_PKEY_free, EVP_PKEY *a, a, return, DUMMYARG) +DEFINEFUNC(EVP_PKEY *, EVP_PKEY_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(int, EVP_PKEY_type, int a, a, return NID_undef, return) +DEFINEFUNC2(int, i2d_X509, X509 *a, a, unsigned char **b, b, return -1, return) +DEFINEFUNC(const char *, OBJ_nid2sn, int a, a, return nullptr, return) +DEFINEFUNC(const char *, OBJ_nid2ln, int a, a, return nullptr, return) +DEFINEFUNC(int, OBJ_sn2nid, const char *s, s, return 0, return) +DEFINEFUNC(int, OBJ_ln2nid, const char *s, s, return 0, return) +DEFINEFUNC3(int, i2t_ASN1_OBJECT, char *a, a, int b, b, ASN1_OBJECT *c, c, return -1, return) +DEFINEFUNC4(int, OBJ_obj2txt, char *a, a, int b, b, ASN1_OBJECT *c, c, int d, d, return -1, return) +DEFINEFUNC(int, OBJ_obj2nid, const ASN1_OBJECT *a, a, return NID_undef, return) +DEFINEFUNC4(EVP_PKEY *, PEM_read_bio_PrivateKey, BIO *a, a, EVP_PKEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) + +DEFINEFUNC7(int, PEM_write_bio_PrivateKey, BIO *a, a, EVP_PKEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_PrivateKey_traditional, BIO *a, a, EVP_PKEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +DEFINEFUNC4(EVP_PKEY *, PEM_read_bio_PUBKEY, BIO *a, a, EVP_PKEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC2(int, PEM_write_bio_PUBKEY, BIO *a, a, EVP_PKEY *b, b, return 0, return) +DEFINEFUNC2(void, RAND_seed, const void *a, a, int b, b, return, DUMMYARG) +DEFINEFUNC(int, RAND_status, void, DUMMYARG, return -1, return) +DEFINEFUNC2(int, RAND_bytes, unsigned char *b, b, int n, n, return 0, return) +DEFINEFUNC(int, SSL_accept, SSL *a, a, return -1, return) +DEFINEFUNC(int, SSL_clear, SSL *a, a, return -1, return) +DEFINEFUNC3(char *, SSL_CIPHER_description, const SSL_CIPHER *a, a, char *b, b, int c, c, return nullptr, return) +DEFINEFUNC2(int, SSL_CIPHER_get_bits, const SSL_CIPHER *a, a, int *b, b, return 0, return) +DEFINEFUNC(BIO *, SSL_get_rbio, const SSL *s, s, return nullptr, return) +DEFINEFUNC(int, SSL_connect, SSL *a, a, return -1, return) +DEFINEFUNC(int, SSL_CTX_check_private_key, const SSL_CTX *a, a, return -1, return) +DEFINEFUNC4(long, SSL_CTX_ctrl, SSL_CTX *a, a, int b, b, long c, c, void *d, d, return -1, return) +DEFINEFUNC(void, SSL_CTX_free, SSL_CTX *a, a, return, DUMMYARG) +DEFINEFUNC(SSL_CTX *, SSL_CTX_new, const SSL_METHOD *a, a, return nullptr, return) +DEFINEFUNC2(int, SSL_CTX_set_cipher_list, SSL_CTX *a, a, const char *b, b, return -1, return) +DEFINEFUNC3(long, SSL_CTX_callback_ctrl, SSL_CTX *ctx, ctx, int dst, dst, GenericCallbackType cb, cb, return 0, return) +DEFINEFUNC(int, SSL_CTX_set_default_verify_paths, SSL_CTX *a, a, return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_verify, SSL_CTX *a, a, int b, b, int (*c)(int, X509_STORE_CTX *), c, return, DUMMYARG) +DEFINEFUNC2(void, SSL_CTX_set_verify_depth, SSL_CTX *a, a, int b, b, return, DUMMYARG) +DEFINEFUNC2(int, SSL_CTX_use_certificate, SSL_CTX *a, a, X509 *b, b, return -1, return) +DEFINEFUNC3(int, SSL_CTX_use_certificate_file, SSL_CTX *a, a, const char *b, b, int c, c, return -1, return) +DEFINEFUNC2(int, SSL_CTX_use_PrivateKey, SSL_CTX *a, a, EVP_PKEY *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 nullptr, return) +DEFINEFUNC(SSL_CONF_CTX *, SSL_CONF_CTX_new, DUMMYARG, DUMMYARG, return nullptr, 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); +DEFINEFUNC(void, SSL_free, SSL *a, a, return, DUMMYARG) +DEFINEFUNC(STACK_OF(SSL_CIPHER) *, SSL_get_ciphers, const SSL *a, a, return nullptr, return) +DEFINEFUNC(const SSL_CIPHER *, SSL_get_current_cipher, SSL *a, a, return nullptr, return) +DEFINEFUNC(int, SSL_version, const SSL *a, a, return 0, return) +DEFINEFUNC2(int, SSL_get_error, SSL *a, a, int b, b, return -1, return) +DEFINEFUNC(STACK_OF(X509) *, SSL_get_peer_cert_chain, SSL *a, a, return nullptr, return) + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +DEFINEFUNC(X509 *, SSL_get1_peer_certificate, SSL *a, a, return nullptr, return) +DEFINEFUNC(int, EVP_PKEY_get_bits, const EVP_PKEY *pkey, pkey, return -1, return) +DEFINEFUNC(int, EVP_PKEY_get_base_id, const EVP_PKEY *pkey, pkey, return -1, return) +#else +DEFINEFUNC(X509 *, SSL_get_peer_certificate, SSL *a, a, return nullptr, return) +DEFINEFUNC(int, EVP_PKEY_base_id, EVP_PKEY *a, a, return NID_undef, return) +#endif // OPENSSL_VERSION_MAJOR >= 3 + +DEFINEFUNC(long, SSL_get_verify_result, const SSL *a, a, return -1, return) +DEFINEFUNC(SSL *, SSL_new, SSL_CTX *a, a, return nullptr, return) +DEFINEFUNC(SSL_CTX *, SSL_get_SSL_CTX, SSL *a, a, return nullptr, return) +DEFINEFUNC4(long, SSL_ctrl, SSL *a, a, int cmd, cmd, long larg, larg, void *parg, parg, return -1, return) +DEFINEFUNC3(int, SSL_read, SSL *a, a, void *b, b, int c, c, return -1, return) +DEFINEFUNC3(void, SSL_set_bio, SSL *a, a, BIO *b, b, BIO *c, c, return, DUMMYARG) +DEFINEFUNC(void, SSL_set_accept_state, SSL *a, a, return, DUMMYARG) +DEFINEFUNC(void, SSL_set_connect_state, SSL *a, a, return, DUMMYARG) +DEFINEFUNC(int, SSL_shutdown, SSL *a, a, return -1, return) +DEFINEFUNC(int, SSL_in_init, const SSL *a, a, return 0, return) +DEFINEFUNC(int, SSL_get_shutdown, const SSL *ssl, ssl, return 0, return) +DEFINEFUNC2(int, SSL_set_session, SSL* to, to, SSL_SESSION *session, session, return -1, return) +DEFINEFUNC(void, SSL_SESSION_free, SSL_SESSION *ses, ses, return, DUMMYARG) +DEFINEFUNC(SSL_SESSION*, SSL_get1_session, SSL *ssl, ssl, return nullptr, return) +DEFINEFUNC(SSL_SESSION*, SSL_get_session, const SSL *ssl, ssl, return nullptr, return) +DEFINEFUNC3(int, SSL_set_ex_data, SSL *ssl, ssl, int idx, idx, void *arg, arg, return 0, return) +DEFINEFUNC2(void *, SSL_get_ex_data, const SSL *ssl, ssl, int idx, idx, return nullptr, return) + +#ifndef OPENSSL_NO_PSK +DEFINEFUNC2(void, SSL_set_psk_client_callback, SSL* ssl, ssl, q_psk_client_callback_t callback, callback, return, DUMMYARG) +DEFINEFUNC2(void, SSL_set_psk_server_callback, SSL* ssl, ssl, q_psk_server_callback_t callback, callback, return, DUMMYARG) +DEFINEFUNC2(int, SSL_CTX_use_psk_identity_hint, SSL_CTX* ctx, ctx, const char *hint, hint, return 0, return) +#endif // !OPENSSL_NO_PSK + +DEFINEFUNC3(int, SSL_write, SSL *a, a, const void *b, b, int c, c, return -1, return) +DEFINEFUNC2(int, X509_cmp, X509 *a, a, X509 *b, b, return -1, return) +DEFINEFUNC4(int, X509_digest, const X509 *x509, x509, const EVP_MD *type, type, unsigned char *md, md, unsigned int *len, len, return -1, return) +DEFINEFUNC(X509 *, X509_dup, X509 *a, a, return nullptr, return) +DEFINEFUNC2(void, X509_print, BIO *a, a, X509 *b, b, return, DUMMYARG); +DEFINEFUNC(ASN1_OBJECT *, X509_EXTENSION_get_object, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void, X509_free, X509 *a, a, return, DUMMYARG) +//Q_AUTOTEST_EXPORT ASN1_TIME *q_X509_gmtime_adj(ASN1_TIME *s, long adj); +DEFINEFUNC2(ASN1_TIME *, X509_gmtime_adj, ASN1_TIME *s, s, long adj, adj, return nullptr, return) +DEFINEFUNC(void, ASN1_TIME_free, ASN1_TIME *t, t, return, DUMMYARG) +DEFINEFUNC2(X509_EXTENSION *, X509_get_ext, X509 *a, a, int b, b, return nullptr, return) +DEFINEFUNC(int, X509_get_ext_count, X509 *a, a, return 0, return) +DEFINEFUNC4(void *, X509_get_ext_d2i, X509 *a, a, int b, b, int *c, c, int *d, d, return nullptr, return) +DEFINEFUNC(const X509V3_EXT_METHOD *, X509V3_EXT_get, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void *, X509V3_EXT_d2i, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(int, X509_EXTENSION_get_critical, X509_EXTENSION *a, a, return 0, return) +DEFINEFUNC(ASN1_OCTET_STRING *, X509_EXTENSION_get_data, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void, BASIC_CONSTRAINTS_free, BASIC_CONSTRAINTS *a, a, return, DUMMYARG) +DEFINEFUNC(void, AUTHORITY_KEYID_free, AUTHORITY_KEYID *a, a, return, DUMMYARG) +DEFINEFUNC(void, GENERAL_NAME_free, GENERAL_NAME *a, a, return, DUMMYARG) +DEFINEFUNC2(int, ASN1_STRING_print, BIO *a, a, const ASN1_STRING *b, b, return 0, return) +DEFINEFUNC2(int, X509_check_issued, X509 *a, a, X509 *b, b, return -1, return) +DEFINEFUNC(X509_NAME *, X509_get_issuer_name, X509 *a, a, return nullptr, return) +DEFINEFUNC(X509_NAME *, X509_get_subject_name, X509 *a, a, return nullptr, return) +DEFINEFUNC(ASN1_INTEGER *, X509_get_serialNumber, X509 *a, a, return nullptr, return) +DEFINEFUNC(int, X509_verify_cert, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(int, X509_NAME_entry_count, X509_NAME *a, a, return 0, return) +DEFINEFUNC2(X509_NAME_ENTRY *, X509_NAME_get_entry, X509_NAME *a, a, int b, b, return nullptr, return) +DEFINEFUNC(ASN1_STRING *, X509_NAME_ENTRY_get_data, X509_NAME_ENTRY *a, a, return nullptr, return) +DEFINEFUNC(ASN1_OBJECT *, X509_NAME_ENTRY_get_object, X509_NAME_ENTRY *a, a, return nullptr, return) +DEFINEFUNC(EVP_PKEY *, X509_PUBKEY_get, X509_PUBKEY *a, a, return nullptr, return) +DEFINEFUNC(void, X509_STORE_free, X509_STORE *a, a, return, DUMMYARG) +DEFINEFUNC(X509_STORE *, X509_STORE_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(int, X509_STORE_add_cert, X509_STORE *a, a, X509 *b, b, return 0, return) +DEFINEFUNC(void, X509_STORE_CTX_free, X509_STORE_CTX *a, a, return, DUMMYARG) +DEFINEFUNC4(int, X509_STORE_CTX_init, X509_STORE_CTX *a, a, X509_STORE *b, b, X509 *c, c, STACK_OF(X509) *d, d, return -1, return) +DEFINEFUNC2(int, X509_STORE_CTX_set_purpose, X509_STORE_CTX *a, a, int b, b, return -1, return) +DEFINEFUNC(int, X509_STORE_CTX_get_error, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(int, X509_STORE_CTX_get_error_depth, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(X509 *, X509_STORE_CTX_get_current_cert, X509_STORE_CTX *a, a, return nullptr, return) +DEFINEFUNC(X509_STORE *, X509_STORE_CTX_get0_store, X509_STORE_CTX *ctx, ctx, return nullptr, return) +DEFINEFUNC(X509_STORE_CTX *, X509_STORE_CTX_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(void *, X509_STORE_CTX_get_ex_data, X509_STORE_CTX *ctx, ctx, int idx, idx, return nullptr, return) +DEFINEFUNC(int, SSL_get_ex_data_X509_STORE_CTX_idx, DUMMYARG, DUMMYARG, return -1, return) + +#if OPENSSL_VERSION_MAJOR < 3 +DEFINEFUNC3(int, SSL_CTX_load_verify_locations, SSL_CTX *ctx, ctx, const char *CAfile, CAfile, const char *CApath, CApath, return 0, return) +#else +DEFINEFUNC2(int, SSL_CTX_load_verify_dir, SSL_CTX *ctx, ctx, const char *CApath, CApath, return 0, return) +#endif // OPENSSL_VERSION_MAJOR + +DEFINEFUNC2(int, i2d_SSL_SESSION, SSL_SESSION *in, in, unsigned char **pp, pp, return 0, return) +DEFINEFUNC3(SSL_SESSION *, d2i_SSL_SESSION, SSL_SESSION **a, a, const unsigned char **pp, pp, long length, length, return nullptr, return) + +#ifndef OPENSSL_NO_NEXTPROTONEG +DEFINEFUNC6(int, SSL_select_next_proto, unsigned char **out, out, unsigned char *outlen, outlen, + const unsigned char *in, in, unsigned int inlen, inlen, + const unsigned char *client, client, unsigned int client_len, client_len, + return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_next_proto_select_cb, SSL_CTX *s, s, + int (*cb) (SSL *ssl, unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), cb, + void *arg, arg, return, DUMMYARG) +DEFINEFUNC3(void, SSL_get0_next_proto_negotiated, const SSL *s, s, + const unsigned char **data, data, unsigned *len, len, return, DUMMYARG) +DEFINEFUNC3(int, SSL_set_alpn_protos, SSL *s, s, const unsigned char *protos, protos, + unsigned protos_len, protos_len, return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_alpn_select_cb, SSL_CTX *s, s, + int (*cb) (SSL *ssl, const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), cb, + void *arg, arg, return, DUMMYARG) +DEFINEFUNC3(void, SSL_get0_alpn_selected, const SSL *s, s, const unsigned char **data, data, + unsigned *len, len, return, DUMMYARG) +#endif // !OPENSSL_NO_NEXTPROTONEG + +// DTLS: +#if QT_CONFIG(dtls) +DEFINEFUNC2(void, SSL_CTX_set_cookie_generate_cb, SSL_CTX *ctx, ctx, CookieGenerateCallback cb, cb, return, DUMMYARG) +DEFINEFUNC2(void, SSL_CTX_set_cookie_verify_cb, SSL_CTX *ctx, ctx, CookieVerifyCallback cb, cb, return, DUMMYARG) +DEFINEFUNC(const SSL_METHOD *, DTLS_server_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const SSL_METHOD *, DTLS_client_method, DUMMYARG, DUMMYARG, return nullptr, return) +#endif // dtls +DEFINEFUNC2(void, BIO_set_flags, BIO *b, b, int flags, flags, return, DUMMYARG) +DEFINEFUNC2(void, BIO_clear_flags, BIO *b, b, int flags, flags, return, DUMMYARG) +DEFINEFUNC2(void *, BIO_get_ex_data, BIO *b, b, int idx, idx, return nullptr, return) +DEFINEFUNC3(int, BIO_set_ex_data, BIO *b, b, int idx, idx, void *data, data, return -1, return) + +DEFINEFUNC3(void *, CRYPTO_malloc, size_t num, num, const char *file, file, int line, line, return nullptr, return) + +#ifndef OPENSSL_NO_DEPRECATED_3_0 +DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, DH_free, DH *dh, dh, return, DUMMYARG) +DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return) +DEFINEFUNC4(void, DH_get0_pqg, const DH *dh, dh, const BIGNUM **p, p, const BIGNUM **q, q, const BIGNUM **g, g, return, DUMMYARG) + +DEFINEFUNC3(DH *, d2i_DHparams, DH**a, a, const unsigned char **pp, pp, long length, length, return nullptr, return) +DEFINEFUNC2(int, i2d_DHparams, DH *a, a, unsigned char **p, p, return -1, return) + +DEFINEFUNC4(DH *, PEM_read_bio_DHparams, BIO *a, a, DH **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +#endif +DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM *ret, ret, return nullptr, return) + + +#ifndef OPENSSL_NO_EC +DEFINEFUNC2(size_t, EC_get_builtin_curves, EC_builtin_curve * r, r, size_t nitems, nitems, return 0, return) +DEFINEFUNC(int, EC_curve_nist2nid, const char *name, name, return 0, return) +#endif // OPENSSL_NO_EC + +DEFINEFUNC5(int, PKCS12_parse, PKCS12 *p12, p12, const char *pass, pass, EVP_PKEY **pkey, pkey, \ + X509 **cert, cert, STACK_OF(X509) **ca, ca, return 1, return); +DEFINEFUNC2(PKCS12 *, d2i_PKCS12_bio, BIO *bio, bio, PKCS12 **pkcs12, pkcs12, return nullptr, return); +DEFINEFUNC(void, PKCS12_free, PKCS12 *pkcs12, pkcs12, return, DUMMYARG) + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + +DEFINEFUNC4(DSA *, PEM_read_bio_DSA_PUBKEY, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(RSA *, PEM_read_bio_RSA_PUBKEY, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(DSA *, PEM_read_bio_DSAPrivateKey, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(RSA *, PEM_read_bio_RSAPrivateKey, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) + +DEFINEFUNC2(int, PEM_write_bio_DSA_PUBKEY, BIO *a, a, DSA *b, b, return 0, return) +DEFINEFUNC2(int, PEM_write_bio_RSA_PUBKEY, BIO *a, a, RSA *b, b, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_DSAPrivateKey, BIO *a, a, DSA *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_RSAPrivateKey, BIO *a, a, RSA *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) + +DEFINEFUNC2(int, SSL_CTX_use_RSAPrivateKey, SSL_CTX *a, a, RSA *b, b, return -1, return) + +DEFINEFUNC(DSA *, DSA_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, DSA_free, DSA *a, a, return, DUMMYARG) + +DEFINEFUNC(RSA *, RSA_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, RSA_free, RSA *a, a, return, DUMMYARG) + +DEFINEFUNC(int, RSA_bits, RSA *a, a, return 0, return) +DEFINEFUNC(int, DSA_bits, DSA *a, a, return 0, return) +DEFINEFUNC(int, DH_bits, DH *dh, dh, return 0, return) + +DEFINEFUNC(DSA *, EVP_PKEY_get1_DSA, EVP_PKEY *a, a, return nullptr, return) +DEFINEFUNC(RSA *, EVP_PKEY_get1_RSA, EVP_PKEY *a, a, return nullptr, return) +DEFINEFUNC(DH *, EVP_PKEY_get1_DH, EVP_PKEY *a, a, return nullptr, return) + +DEFINEFUNC2(int, EVP_PKEY_cmp, const EVP_PKEY *a, a, const EVP_PKEY *b, b, return -1, return) +DEFINEFUNC3(int, EVP_PKEY_assign, EVP_PKEY *a, a, int b, b, void *r, r, return -1, return) + +DEFINEFUNC2(int, EVP_PKEY_set1_RSA, EVP_PKEY *a, a, RSA *b, b, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_DSA, EVP_PKEY *a, a, DSA *b, b, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_DH, EVP_PKEY *a, a, DH *b, b, return -1, return) + +#ifndef OPENSSL_NO_EC + +DEFINEFUNC4(EC_KEY *, PEM_read_bio_EC_PUBKEY, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(EC_KEY *, PEM_read_bio_ECPrivateKey, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) + +DEFINEFUNC2(int, PEM_write_bio_EC_PUBKEY, BIO *a, a, EC_KEY *b, b, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_ECPrivateKey, BIO *a, a, EC_KEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) + +DEFINEFUNC(const EC_GROUP*, EC_KEY_get0_group, const EC_KEY* k, k, return nullptr, return) +DEFINEFUNC(int, EC_GROUP_get_degree, const EC_GROUP* g, g, return 0, return) + +DEFINEFUNC2(int, EVP_PKEY_set1_EC_KEY, EVP_PKEY *a, a, EC_KEY *b, b, return -1, return) +DEFINEFUNC(EC_KEY *, EVP_PKEY_get1_EC_KEY, EVP_PKEY *a, a, return nullptr, return) + +DEFINEFUNC(EC_KEY *, EC_KEY_dup, const EC_KEY *ec, ec, return nullptr, return) +DEFINEFUNC(EC_KEY *, EC_KEY_new_by_curve_name, int nid, nid, return nullptr, return) +DEFINEFUNC(void, EC_KEY_free, EC_KEY *ecdh, ecdh, return, DUMMYARG) + +#endif // OPENSSL_NO_EC + + + +#endif + +#define RESOLVEFUNC(func) \ + if (!(_q_##func = _q_PTR_##func(libs.ssl->resolve(#func))) \ + && !(_q_##func = _q_PTR_##func(libs.crypto->resolve(#func)))) \ + qsslSocketCannotResolveSymbolWarning(#func); + +#if !defined QT_LINKED_OPENSSL + +#if !QT_CONFIG(library) +bool q_resolveOpenSslSymbols() +{ + qCWarning(lcTlsBackend, "QSslSocket: unable to resolve symbols. Qt is configured without the " + "'library' feature, which means runtime resolving of libraries won't work."); + qCWarning(lcTlsBackend, "Either compile Qt statically or with support for runtime resolving " + "of libraries."); + return false; +} +#else + +# ifdef Q_OS_UNIX +struct NumericallyLess +{ + typedef bool result_type; + result_type operator()(QStringView lhs, QStringView rhs) const + { + bool ok = false; + int b = 0; + int a = lhs.toInt(&ok); + if (ok) + b = rhs.toInt(&ok); + if (ok) { + // both toInt succeeded + return a < b; + } else { + // compare as strings; + return lhs < rhs; + } + } +}; + +struct LibGreaterThan +{ + typedef bool result_type; + result_type operator()(QStringView lhs, QStringView rhs) const + { + const auto lhsparts = lhs.split(u'.'); + const auto rhsparts = rhs.split(u'.'); + Q_ASSERT(lhsparts.size() > 1 && rhsparts.size() > 1); + + // note: checking rhs < lhs, the same as lhs > rhs + return std::lexicographical_compare(rhsparts.begin() + 1, rhsparts.end(), + lhsparts.begin() + 1, lhsparts.end(), + NumericallyLess()); + } +}; + +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +static int dlIterateCallback(struct dl_phdr_info *info, size_t size, void *data) +{ + if (size < sizeof (info->dlpi_addr) + sizeof (info->dlpi_name)) + return 1; + QDuplicateTracker<QString> *paths = (QDuplicateTracker<QString> *)data; + QString path = QString::fromLocal8Bit(info->dlpi_name); + if (!path.isEmpty()) { + QFileInfo fi(path); + path = fi.absolutePath(); + if (!path.isEmpty()) + (void)paths->hasSeen(std::move(path)); + } + return 0; +} +#endif + +static QStringList libraryPathList() +{ + QStringList paths; +# ifdef Q_OS_DARWIN + paths = QString::fromLatin1(qgetenv("DYLD_LIBRARY_PATH")).split(u':', Qt::SkipEmptyParts); + + // search in .app/Contents/Frameworks + UInt32 packageType; + CFBundleGetPackageInfo(CFBundleGetMainBundle(), &packageType, nullptr); + if (packageType == FOUR_CHAR_CODE('APPL')) { + QUrl bundleUrl = QUrl::fromCFURL(QCFType<CFURLRef>(CFBundleCopyBundleURL(CFBundleGetMainBundle()))); + QUrl frameworksUrl = QUrl::fromCFURL(QCFType<CFURLRef>(CFBundleCopyPrivateFrameworksURL(CFBundleGetMainBundle()))); + paths << bundleUrl.resolved(frameworksUrl).path(); + } +# else + paths = QString::fromLatin1(qgetenv("LD_LIBRARY_PATH")).split(u':', Qt::SkipEmptyParts); +# endif + paths << "/lib"_L1 << "/usr/lib"_L1 << "/usr/local/lib"_L1; + paths << "/lib64"_L1 << "/usr/lib64"_L1 << "/usr/local/lib64"_L1; + paths << "/lib32"_L1 << "/usr/lib32"_L1 << "/usr/local/lib32"_L1; + +#if defined(Q_OS_ANDROID) + paths << "/system/lib"_L1; +#elif defined(Q_OS_LINUX) + // discover paths of already loaded libraries + QDuplicateTracker<QString> loadedPaths; + dl_iterate_phdr(dlIterateCallback, &loadedPaths); + std::move(loadedPaths).appendTo(paths); +#endif + + return paths; +} + +Q_NEVER_INLINE +static QStringList findAllLibs(QLatin1StringView filter) +{ + const QStringList paths = libraryPathList(); + QStringList found; + const QStringList filters((QString(filter))); + + for (const QString &path : paths) { + QDir dir(path); + QStringList entryList = dir.entryList(filters, QDir::Files); + + std::sort(entryList.begin(), entryList.end(), LibGreaterThan()); + for (const QString &entry : std::as_const(entryList)) + found << path + u'/' + entry; + } + + return found; +} + +static QStringList findAllLibSsl() +{ + return findAllLibs("libssl.*"_L1); +} + +static QStringList findAllLibCrypto() +{ + return findAllLibs("libcrypto.*"_L1); +} +# endif + +#if (OPENSSL_VERSION_NUMBER >> 28) < 3 +#define QT_OPENSSL_VERSION "1_1" +#elif OPENSSL_VERSION_MAJOR == 3 // Starting with 3.0 this define is available +#define QT_OPENSSL_VERSION "3" +#endif // > 3 intentionally left undefined + +#ifdef Q_OS_WIN + +struct LoadedOpenSsl { + std::unique_ptr<QSystemLibrary> ssl, crypto; +}; + +static bool tryToLoadOpenSslWin32Library(QLatin1StringView ssleay32LibName, QLatin1StringView libeay32LibName, LoadedOpenSsl &result) +{ + auto ssleay32 = std::make_unique<QSystemLibrary>(ssleay32LibName); + if (!ssleay32->load(false)) { + return FALSE; + } + + auto libeay32 = std::make_unique<QSystemLibrary>(libeay32LibName); + if (!libeay32->load(false)) { + return FALSE; + } + + result.ssl = std::move(ssleay32); + result.crypto = std::move(libeay32); + return TRUE; +} + +static LoadedOpenSsl loadOpenSsl() +{ + LoadedOpenSsl result; + + // With OpenSSL 1.1 the names have changed to libssl-1_1 and libcrypto-1_1 for builds using + // MSVC and GCC. For 3.0 the version suffix changed again, to just '3'. + // For non-x86 builds, an architecture suffix is also appended. + +#if defined(Q_PROCESSOR_X86_64) +#define QT_SSL_SUFFIX "-x64" +#elif defined(Q_PROCESSOR_ARM_64) +#define QT_SSL_SUFFIX "-arm64" +#elif defined(Q_PROCESSOR_ARM_32) +#define QT_SSL_SUFFIX "-arm" +#else +#define QT_SSL_SUFFIX +#endif + + tryToLoadOpenSslWin32Library("libssl-" QT_OPENSSL_VERSION QT_SSL_SUFFIX ""_L1, + "libcrypto-" QT_OPENSSL_VERSION QT_SSL_SUFFIX ""_L1, result); + +#undef QT_SSL_SUFFIX + return result; +} +#else // !Q_OS_WIN: + +struct LoadedOpenSsl { + std::unique_ptr<QLibrary> ssl, crypto; +}; + +static LoadedOpenSsl loadOpenSsl() +{ + LoadedOpenSsl result = { std::make_unique<QLibrary>(), std::make_unique<QLibrary>() }; + +# if defined(Q_OS_UNIX) + QLibrary * const libssl = result.ssl.get(); + QLibrary * const libcrypto = result.crypto.get(); + + // Try to find the libssl library on the system. + // + // Up until Qt 4.3, this only searched for the "ssl" library at version -1, that + // is, libssl.so on most Unix systems. However, the .so file isn't present in + // user installations because it's considered a development file. + // + // The right thing to do is to load the library at the major version we know how + // to work with: the SHLIB_VERSION_NUMBER version (macro defined in opensslv.h) + // + // However, OpenSSL is a well-known case of binary-compatibility breakage. To + // avoid such problems, many system integrators and Linux distributions change + // the soname of the binary, letting the full version number be the soname. So + // we'll find libssl.so.0.9.7, libssl.so.0.9.8, etc. in the system. For that + // reason, we will search a few common paths (see findAllLibSsl() above) in hopes + // we find one that works. + // + // If that fails, for OpenSSL 1.0 we also try some fallbacks -- look up + // libssl.so with a hardcoded soname. The reason is QTBUG-68156: the binary + // builds of Qt happen (at the time of this writing) on RHEL machines, + // which change SHLIB_VERSION_NUMBER to a non-portable string. When running + // those binaries on the target systems, this code won't pick up + // libssl.so.MODIFIED_SHLIB_VERSION_NUMBER because it doesn't exist there. + // Given that the only 1.0 supported release (at the time of this writing) + // is 1.0.2, with soname "1.0.0", give that a try too. Note that we mandate + // OpenSSL >= 1.0.0 with a configure-time check, and OpenSSL has kept binary + // compatibility between 1.0.0 and 1.0.2. + // + // It is important, however, to try the canonical name and the unversioned name + // without going through the loop. By not specifying a path, we let the system + // dlopen(3) function determine it for us. This will include any DT_RUNPATH or + // DT_RPATH tags on our library header as well as other system-specific search + // paths. See the man page for dlopen(3) on your system for more information. + +#ifdef Q_OS_OPENBSD + libcrypto->setLoadHints(QLibrary::ExportExternalSymbolsHint); +#endif + +#if !defined(Q_OS_QNX) // on QNX, the libs are always libssl.so and libcrypto.so + +#if defined(OPENSSL_SHLIB_VERSION) + // OpenSSL v.3 does not have SLIB_VERSION_NUMBER but has OPENSSL_SHLIB_VERSION. + // The comment about OPENSSL_SHLIB_VERSION in opensslv.h is a bit troublesome: + // "This is defined in free form." + auto shlibVersion = QString("%1"_L1).arg(OPENSSL_SHLIB_VERSION); + libssl->setFileNameAndVersion("ssl"_L1, shlibVersion); + libcrypto->setFileNameAndVersion("crypto"_L1, shlibVersion); +#elif defined(SHLIB_VERSION_NUMBER) + // first attempt: the canonical name is libssl.so.<SHLIB_VERSION_NUMBER> + libssl->setFileNameAndVersion("ssl"_L1, SHLIB_VERSION_NUMBER ""_L1); + libcrypto->setFileNameAndVersion("crypto"_L1, SHLIB_VERSION_NUMBER ""_L1); +#endif // OPENSSL_SHLIB_VERSION + + if (libcrypto->load() && libssl->load()) { + // libssl.so.<SHLIB_VERSION_NUMBER> and libcrypto.so.<SHLIB_VERSION_NUMBER> found + return result; + } else { + libssl->unload(); + libcrypto->unload(); + } +#endif // !defined(Q_OS_QNX) + +#ifndef Q_OS_DARWIN + // second attempt: find the development files libssl.so and libcrypto.so + // + // disabled on macOS/iOS: + // macOS's /usr/lib/libssl.dylib, /usr/lib/libcrypto.dylib will be picked up in the third + // attempt, _after_ <bundle>/Contents/Frameworks has been searched. + // iOS does not ship a system libssl.dylib, libcrypto.dylib in the first place. +# if defined(Q_OS_ANDROID) + // OpenSSL 1.1.x must be suffixed otherwise it will use the system libcrypto.so libssl.so which on API-21 are OpenSSL 1.0 not 1.1 + auto openSSLSuffix = [](const QByteArray &defaultSuffix = {}) { + auto suffix = qgetenv("ANDROID_OPENSSL_SUFFIX"); + if (suffix.isEmpty()) + return defaultSuffix; + return suffix; + }; + + static QString suffix = QString::fromLatin1(openSSLSuffix("_" QT_OPENSSL_VERSION)); + + libssl->setFileNameAndVersion("ssl"_L1 + suffix, -1); + libcrypto->setFileNameAndVersion("crypto"_L1 + suffix, -1); +# else + libssl->setFileNameAndVersion("ssl"_L1, -1); + libcrypto->setFileNameAndVersion("crypto"_L1, -1); +# endif + if (libcrypto->load() && libssl->load()) { + // libssl.so.0 and libcrypto.so.0 found + return result; + } else { + libssl->unload(); + libcrypto->unload(); + } +#endif + + // third attempt: loop on the most common library paths and find libssl + const QStringList sslList = findAllLibSsl(); + const QStringList cryptoList = findAllLibCrypto(); + + for (const QString &crypto : cryptoList) { +#ifdef Q_OS_DARWIN + // Clients should not load the unversioned libcrypto dylib as it does not have a stable ABI + if (crypto.endsWith("libcrypto.dylib")) + continue; +#endif + libcrypto->setFileNameAndVersion(crypto, -1); + if (libcrypto->load()) { + QFileInfo fi(crypto); + QString version = fi.completeSuffix(); + + for (const QString &ssl : sslList) { + if (!ssl.endsWith(version)) + continue; + + libssl->setFileNameAndVersion(ssl, -1); + + if (libssl->load()) { + // libssl.so.x and libcrypto.so.x found + return result; + } else { + libssl->unload(); + } + } + } + libcrypto->unload(); + } + + // failed to load anything + result = {}; + return result; + +# else + // not implemented for this platform yet + return result; +# endif +} +#endif + +bool q_resolveOpenSslSymbols() +{ + static bool symbolsResolved = []() { + LoadedOpenSsl libs = loadOpenSsl(); + if (!libs.ssl || !libs.crypto) { + qCWarning(lcTlsBackend, "Failed to load libssl/libcrypto."); + return false; + } + + RESOLVEFUNC(OPENSSL_init_ssl) + RESOLVEFUNC(OPENSSL_init_crypto) + RESOLVEFUNC(ASN1_STRING_get0_data) + RESOLVEFUNC(EVP_CIPHER_CTX_reset) + RESOLVEFUNC(AUTHORITY_INFO_ACCESS_free) + RESOLVEFUNC(EVP_PKEY_up_ref) + RESOLVEFUNC(EVP_PKEY_CTX_new) + RESOLVEFUNC(EVP_PKEY_param_check) + RESOLVEFUNC(EVP_PKEY_CTX_free) + RESOLVEFUNC(OPENSSL_sk_new_null) + RESOLVEFUNC(OPENSSL_sk_push) + RESOLVEFUNC(OPENSSL_sk_free) + RESOLVEFUNC(OPENSSL_sk_num) + RESOLVEFUNC(OPENSSL_sk_pop_free) + RESOLVEFUNC(OPENSSL_sk_value) + RESOLVEFUNC(SSL_CTX_set_options) + RESOLVEFUNC(SSL_set_info_callback) + RESOLVEFUNC(SSL_alert_type_string) + RESOLVEFUNC(SSL_alert_desc_string_long) + RESOLVEFUNC(SSL_CTX_get_security_level) + RESOLVEFUNC(SSL_CTX_set_security_level) +#ifdef TLS1_3_VERSION + RESOLVEFUNC(SSL_CTX_set_ciphersuites) + RESOLVEFUNC(SSL_set_psk_use_session_callback) + RESOLVEFUNC(SSL_CTX_sess_set_new_cb) + RESOLVEFUNC(SSL_SESSION_is_resumable) +#endif // TLS 1.3 or OpenSSL > 1.1.1 + + RESOLVEFUNC(SSL_get_client_random) + RESOLVEFUNC(SSL_SESSION_get_master_key) + RESOLVEFUNC(SSL_session_reused) + RESOLVEFUNC(SSL_get_session) + RESOLVEFUNC(SSL_set_options) + RESOLVEFUNC(CRYPTO_get_ex_new_index) + RESOLVEFUNC(TLS_method) + RESOLVEFUNC(TLS_client_method) + RESOLVEFUNC(TLS_server_method) + RESOLVEFUNC(X509_up_ref) + RESOLVEFUNC(X509_STORE_CTX_get0_chain) + RESOLVEFUNC(X509_getm_notBefore) + RESOLVEFUNC(X509_getm_notAfter) + RESOLVEFUNC(ASN1_item_free) + RESOLVEFUNC(X509V3_conf_free) + RESOLVEFUNC(X509_get_version) + RESOLVEFUNC(X509_get_pubkey) + RESOLVEFUNC(X509_STORE_set_verify_cb) + RESOLVEFUNC(X509_STORE_set_ex_data) + RESOLVEFUNC(X509_STORE_get_ex_data) + RESOLVEFUNC(CRYPTO_free) + RESOLVEFUNC(CRYPTO_memcmp) + RESOLVEFUNC(OpenSSL_version_num) + RESOLVEFUNC(OpenSSL_version) + + if (!_q_OpenSSL_version || !_q_OpenSSL_version_num) { + // Apparently, we were built with OpenSSL 1.1 enabled but are now using + // a wrong library. + qCWarning(lcTlsBackend, "Incompatible version of OpenSSL"); + return false; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 + if (q_OpenSSL_version_num() < 0x30000000) { + qCWarning(lcTlsBackend, "Incompatible version of OpenSSL (built with OpenSSL >= 3.x, runtime version is < 3.x)"); + return false; + } +#else + if (q_OpenSSL_version_num() >= 0x30000000) { + qCWarning(lcTlsBackend, "Incompatible version of OpenSSL (built with OpenSSL 1.x, runtime version is >= 3.x)"); + return false; + } +#endif // OPENSSL_VERSION_NUMBER + + RESOLVEFUNC(SSL_SESSION_get_ticket_lifetime_hint) + +#if QT_CONFIG(dtls) + RESOLVEFUNC(DTLSv1_listen) + RESOLVEFUNC(BIO_ADDR_new) + RESOLVEFUNC(BIO_ADDR_free) + RESOLVEFUNC(BIO_meth_new) + RESOLVEFUNC(BIO_meth_free) + RESOLVEFUNC(BIO_meth_set_write) + RESOLVEFUNC(BIO_meth_set_read) + RESOLVEFUNC(BIO_meth_set_puts) + RESOLVEFUNC(BIO_meth_set_ctrl) + RESOLVEFUNC(BIO_meth_set_create) + RESOLVEFUNC(BIO_meth_set_destroy) +#endif // dtls + +#if QT_CONFIG(ocsp) + RESOLVEFUNC(OCSP_SINGLERESP_get0_id) + RESOLVEFUNC(d2i_OCSP_RESPONSE) + RESOLVEFUNC(OCSP_RESPONSE_free) + RESOLVEFUNC(OCSP_response_status) + RESOLVEFUNC(OCSP_response_get1_basic) + RESOLVEFUNC(OCSP_BASICRESP_free) + RESOLVEFUNC(OCSP_basic_verify) + RESOLVEFUNC(OCSP_resp_count) + RESOLVEFUNC(OCSP_resp_get0) + RESOLVEFUNC(OCSP_single_get0_status) + RESOLVEFUNC(OCSP_check_validity) + RESOLVEFUNC(OCSP_cert_to_id) + RESOLVEFUNC(OCSP_id_get0_info) + RESOLVEFUNC(OCSP_resp_get0_certs) + RESOLVEFUNC(OCSP_basic_sign) + RESOLVEFUNC(OCSP_response_create) + RESOLVEFUNC(i2d_OCSP_RESPONSE) + RESOLVEFUNC(OCSP_basic_add1_status) + RESOLVEFUNC(OCSP_BASICRESP_new) + RESOLVEFUNC(OCSP_CERTID_free) + RESOLVEFUNC(OCSP_cert_to_id) + RESOLVEFUNC(OCSP_id_cmp) +#endif // ocsp + + RESOLVEFUNC(BIO_set_data) + RESOLVEFUNC(BIO_get_data) + RESOLVEFUNC(BIO_set_init) + RESOLVEFUNC(BIO_get_shutdown) + RESOLVEFUNC(BIO_set_shutdown) + RESOLVEFUNC(ASN1_INTEGER_get) + RESOLVEFUNC(ASN1_INTEGER_cmp) + RESOLVEFUNC(ASN1_STRING_length) + RESOLVEFUNC(ASN1_STRING_to_UTF8) + RESOLVEFUNC(ASN1_TIME_to_tm) + RESOLVEFUNC(BIO_ctrl) + RESOLVEFUNC(BIO_free) + RESOLVEFUNC(BIO_new) + RESOLVEFUNC(BIO_new_mem_buf) + RESOLVEFUNC(BIO_read) + RESOLVEFUNC(BIO_s_mem) + RESOLVEFUNC(BIO_write) + RESOLVEFUNC(BIO_set_flags) + RESOLVEFUNC(BIO_clear_flags) + RESOLVEFUNC(BIO_set_ex_data) + RESOLVEFUNC(BIO_get_ex_data) + RESOLVEFUNC(BN_num_bits) + RESOLVEFUNC(BN_is_word) + RESOLVEFUNC(BN_mod_word) + RESOLVEFUNC(ERR_error_string) + RESOLVEFUNC(ERR_error_string_n) + RESOLVEFUNC(ERR_get_error) + RESOLVEFUNC(EVP_CIPHER_CTX_new) + RESOLVEFUNC(EVP_CIPHER_CTX_free) + RESOLVEFUNC(EVP_CIPHER_CTX_ctrl) + RESOLVEFUNC(EVP_CIPHER_CTX_set_key_length) + RESOLVEFUNC(EVP_CipherInit) + RESOLVEFUNC(EVP_CipherInit_ex) + RESOLVEFUNC(EVP_CipherUpdate) + RESOLVEFUNC(EVP_CipherFinal) + RESOLVEFUNC(EVP_get_digestbyname) +#ifndef OPENSSL_NO_DES + RESOLVEFUNC(EVP_des_cbc) + RESOLVEFUNC(EVP_des_ede3_cbc) +#endif +#ifndef OPENSSL_NO_RC2 + RESOLVEFUNC(EVP_rc2_cbc) +#endif +#ifndef OPENSSL_NO_AES + RESOLVEFUNC(EVP_aes_128_cbc) + RESOLVEFUNC(EVP_aes_192_cbc) + RESOLVEFUNC(EVP_aes_256_cbc) +#endif + RESOLVEFUNC(EVP_sha1) + RESOLVEFUNC(EVP_PKEY_free) + RESOLVEFUNC(EVP_PKEY_new) + RESOLVEFUNC(EVP_PKEY_type) + RESOLVEFUNC(OBJ_nid2sn) + RESOLVEFUNC(OBJ_nid2ln) + RESOLVEFUNC(OBJ_sn2nid) + RESOLVEFUNC(OBJ_ln2nid) + RESOLVEFUNC(i2t_ASN1_OBJECT) + RESOLVEFUNC(OBJ_obj2txt) + RESOLVEFUNC(OBJ_obj2nid) + RESOLVEFUNC(PEM_read_bio_PrivateKey) + RESOLVEFUNC(PEM_write_bio_PrivateKey) + RESOLVEFUNC(PEM_write_bio_PrivateKey_traditional) + RESOLVEFUNC(PEM_read_bio_PUBKEY) + RESOLVEFUNC(PEM_write_bio_PUBKEY) + RESOLVEFUNC(RAND_seed) + RESOLVEFUNC(RAND_status) + RESOLVEFUNC(RAND_bytes) + RESOLVEFUNC(SSL_CIPHER_description) + RESOLVEFUNC(SSL_CIPHER_get_bits) + RESOLVEFUNC(SSL_get_rbio) + RESOLVEFUNC(SSL_CTX_check_private_key) + RESOLVEFUNC(SSL_CTX_ctrl) + RESOLVEFUNC(SSL_CTX_free) + RESOLVEFUNC(SSL_CTX_new) + RESOLVEFUNC(SSL_CTX_set_cipher_list) + RESOLVEFUNC(SSL_CTX_callback_ctrl) + RESOLVEFUNC(SSL_CTX_set_default_verify_paths) + RESOLVEFUNC(SSL_CTX_set_verify) + RESOLVEFUNC(SSL_CTX_set_verify_depth) + RESOLVEFUNC(SSL_CTX_use_certificate) + RESOLVEFUNC(SSL_CTX_use_certificate_file) + RESOLVEFUNC(SSL_CTX_use_PrivateKey) + RESOLVEFUNC(SSL_CTX_use_PrivateKey_file) + RESOLVEFUNC(SSL_CTX_get_cert_store); + 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); + RESOLVEFUNC(SSL_accept) + RESOLVEFUNC(SSL_clear) + RESOLVEFUNC(SSL_connect) + RESOLVEFUNC(SSL_free) + RESOLVEFUNC(SSL_get_ciphers) + RESOLVEFUNC(SSL_get_current_cipher) + RESOLVEFUNC(SSL_version) + RESOLVEFUNC(SSL_get_error) + RESOLVEFUNC(SSL_get_peer_cert_chain) + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + RESOLVEFUNC(SSL_get1_peer_certificate) + RESOLVEFUNC(EVP_PKEY_get_bits) + RESOLVEFUNC(EVP_PKEY_get_base_id) +#else + RESOLVEFUNC(SSL_get_peer_certificate) + RESOLVEFUNC(EVP_PKEY_base_id) +#endif // OPENSSL_VERSION_MAJOR >= 3 + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + RESOLVEFUNC(DH_new) + RESOLVEFUNC(DH_free) + RESOLVEFUNC(DH_check) + RESOLVEFUNC(DH_get0_pqg) + + RESOLVEFUNC(d2i_DHparams) + RESOLVEFUNC(i2d_DHparams) + + RESOLVEFUNC(PEM_read_bio_DHparams) + + RESOLVEFUNC(EVP_PKEY_assign) + RESOLVEFUNC(EVP_PKEY_cmp) + + RESOLVEFUNC(EVP_PKEY_set1_RSA) + RESOLVEFUNC(EVP_PKEY_set1_DSA) + RESOLVEFUNC(EVP_PKEY_set1_DH) + + RESOLVEFUNC(EVP_PKEY_get1_DSA) + RESOLVEFUNC(EVP_PKEY_get1_RSA) + RESOLVEFUNC(EVP_PKEY_get1_DH) + + RESOLVEFUNC(PEM_read_bio_DSA_PUBKEY) + RESOLVEFUNC(PEM_read_bio_RSA_PUBKEY) + RESOLVEFUNC(PEM_read_bio_DSAPrivateKey) + RESOLVEFUNC(PEM_read_bio_RSAPrivateKey) + + RESOLVEFUNC(PEM_write_bio_DSA_PUBKEY) + RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY) + RESOLVEFUNC(PEM_write_bio_DSAPrivateKey) + RESOLVEFUNC(PEM_write_bio_RSAPrivateKey) + RESOLVEFUNC(SSL_CTX_use_RSAPrivateKey) + + RESOLVEFUNC(DSA_new) + RESOLVEFUNC(DSA_free) + + RESOLVEFUNC(RSA_new) + RESOLVEFUNC(RSA_free) + + RESOLVEFUNC(DH_bits) + RESOLVEFUNC(DSA_bits) + RESOLVEFUNC(RSA_bits) + +#ifndef OPENSSL_NO_EC + + RESOLVEFUNC(EVP_PKEY_set1_EC_KEY) + RESOLVEFUNC(EVP_PKEY_get1_EC_KEY) + RESOLVEFUNC(PEM_read_bio_EC_PUBKEY) + RESOLVEFUNC(PEM_read_bio_ECPrivateKey) + RESOLVEFUNC(PEM_write_bio_EC_PUBKEY) + RESOLVEFUNC(PEM_write_bio_ECPrivateKey) + RESOLVEFUNC(EC_KEY_get0_group) + RESOLVEFUNC(EC_GROUP_get_degree) + RESOLVEFUNC(EC_KEY_dup) + RESOLVEFUNC(EC_KEY_new_by_curve_name) + RESOLVEFUNC(EC_KEY_free) + +#endif // OPENSSL_NO_EC + +#endif // OPENSSL_NO_DEPRECATED_3_0 + + RESOLVEFUNC(SSL_get_verify_result) + RESOLVEFUNC(SSL_new) + RESOLVEFUNC(SSL_get_SSL_CTX) + RESOLVEFUNC(SSL_ctrl) + RESOLVEFUNC(SSL_read) + RESOLVEFUNC(SSL_set_accept_state) + RESOLVEFUNC(SSL_set_bio) + RESOLVEFUNC(SSL_set_connect_state) + RESOLVEFUNC(SSL_shutdown) + RESOLVEFUNC(SSL_in_init) + RESOLVEFUNC(SSL_get_shutdown) + RESOLVEFUNC(SSL_set_session) + RESOLVEFUNC(SSL_SESSION_free) + RESOLVEFUNC(SSL_get1_session) + RESOLVEFUNC(SSL_get_session) + RESOLVEFUNC(SSL_set_ex_data) + RESOLVEFUNC(SSL_get_ex_data) + RESOLVEFUNC(SSL_get_ex_data_X509_STORE_CTX_idx) + +#ifndef OPENSSL_NO_PSK + RESOLVEFUNC(SSL_set_psk_client_callback) + RESOLVEFUNC(SSL_set_psk_server_callback) + RESOLVEFUNC(SSL_CTX_use_psk_identity_hint) +#endif // !OPENSSL_NO_PSK + + RESOLVEFUNC(SSL_write) + RESOLVEFUNC(X509_NAME_entry_count) + RESOLVEFUNC(X509_NAME_get_entry) + RESOLVEFUNC(X509_NAME_ENTRY_get_data) + RESOLVEFUNC(X509_NAME_ENTRY_get_object) + RESOLVEFUNC(X509_PUBKEY_get) + RESOLVEFUNC(X509_STORE_free) + RESOLVEFUNC(X509_STORE_new) + RESOLVEFUNC(X509_STORE_add_cert) + RESOLVEFUNC(X509_STORE_CTX_free) + RESOLVEFUNC(X509_STORE_CTX_init) + RESOLVEFUNC(X509_STORE_CTX_new) + RESOLVEFUNC(X509_STORE_CTX_set_purpose) + RESOLVEFUNC(X509_STORE_CTX_get_error) + RESOLVEFUNC(X509_STORE_CTX_get_error_depth) + RESOLVEFUNC(X509_STORE_CTX_get_current_cert) + RESOLVEFUNC(X509_STORE_CTX_get0_store) + RESOLVEFUNC(X509_cmp) + RESOLVEFUNC(X509_STORE_CTX_get_ex_data) + RESOLVEFUNC(X509_dup) + RESOLVEFUNC(X509_print) + RESOLVEFUNC(X509_digest) + RESOLVEFUNC(X509_EXTENSION_get_object) + RESOLVEFUNC(X509_free) + RESOLVEFUNC(X509_gmtime_adj) + RESOLVEFUNC(ASN1_TIME_free) + RESOLVEFUNC(X509_get_ext) + RESOLVEFUNC(X509_get_ext_count) + RESOLVEFUNC(X509_get_ext_d2i) + RESOLVEFUNC(X509V3_EXT_get) + RESOLVEFUNC(X509V3_EXT_d2i) + RESOLVEFUNC(X509_EXTENSION_get_critical) + RESOLVEFUNC(X509_EXTENSION_get_data) + RESOLVEFUNC(BASIC_CONSTRAINTS_free) + RESOLVEFUNC(AUTHORITY_KEYID_free) + RESOLVEFUNC(GENERAL_NAME_free) + RESOLVEFUNC(ASN1_STRING_print) + RESOLVEFUNC(X509_check_issued) + RESOLVEFUNC(X509_get_issuer_name) + RESOLVEFUNC(X509_get_subject_name) + RESOLVEFUNC(X509_get_serialNumber) + RESOLVEFUNC(X509_verify_cert) + RESOLVEFUNC(d2i_X509) + RESOLVEFUNC(i2d_X509) +#if OPENSSL_VERSION_MAJOR < 3 + RESOLVEFUNC(SSL_CTX_load_verify_locations) +#else + RESOLVEFUNC(SSL_CTX_load_verify_dir) +#endif // OPENSSL_VERSION_MAJOR + RESOLVEFUNC(i2d_SSL_SESSION) + RESOLVEFUNC(d2i_SSL_SESSION) + +#ifndef OPENSSL_NO_NEXTPROTONEG + RESOLVEFUNC(SSL_select_next_proto) + RESOLVEFUNC(SSL_CTX_set_next_proto_select_cb) + RESOLVEFUNC(SSL_get0_next_proto_negotiated) + RESOLVEFUNC(SSL_set_alpn_protos) + RESOLVEFUNC(SSL_CTX_set_alpn_select_cb) + RESOLVEFUNC(SSL_get0_alpn_selected) +#endif // !OPENSSL_NO_NEXTPROTONEG + +#if QT_CONFIG(dtls) + RESOLVEFUNC(SSL_CTX_set_cookie_generate_cb) + RESOLVEFUNC(SSL_CTX_set_cookie_verify_cb) + RESOLVEFUNC(DTLS_server_method) + RESOLVEFUNC(DTLS_client_method) +#endif // dtls + + RESOLVEFUNC(CRYPTO_malloc) + RESOLVEFUNC(BN_bin2bn) + +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(EC_get_builtin_curves) +#endif // OPENSSL_NO_EC + + RESOLVEFUNC(PKCS12_parse) + RESOLVEFUNC(d2i_PKCS12_bio) + RESOLVEFUNC(PKCS12_free) + return true; + }(); + + return symbolsResolved; +} +#endif // QT_CONFIG(library) + +#else // !defined QT_LINKED_OPENSSL + +bool q_resolveOpenSslSymbols() +{ +#ifdef QT_NO_OPENSSL + return false; +#endif + return true; +} +#endif // !defined QT_LINKED_OPENSSL + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h b/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h new file mode 100644 index 0000000000..a93c110b3f --- /dev/null +++ b/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h @@ -0,0 +1,765 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2014 BlackBerry Limited. All rights reserved. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#ifndef QSSLSOCKET_OPENSSL_SYMBOLS_P_H +#define QSSLSOCKET_OPENSSL_SYMBOLS_P_H + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include "qopenssl_p.h" + +#include <QtCore/qglobal.h> + +#if QT_CONFIG(ocsp) +#include <QtNetwork/private/qocsp_p.h> +#endif + +QT_BEGIN_NAMESPACE + +#define DUMMYARG + +#if !defined QT_LINKED_OPENSSL +// **************** Shared declarations ****************** +// ret func(arg) + +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a); \ + } + +// ret func(arg1, arg2) +# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func);\ + err; \ + } \ + funcret _q_##func(a, b); \ + } + +// ret func(arg1, arg2, arg3) +# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c); \ + } + +// ret func(arg1, arg2, arg3, arg4) +# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg5) +# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6) +# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7) +# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f, g); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) +# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f, g, h, i); \ + } +// **************** Shared declarations ****************** + +#else // !defined QT_LINKED_OPENSSL + +// **************** Static declarations ****************** + +// ret func(arg) +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + ret q_##func(arg) { funcret func(a); } + +// ret func(arg1, arg2) +# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ + ret q_##func(arg1, arg2) { funcret func(a, b); } + +// ret func(arg1, arg2, arg3) +# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ + ret q_##func(arg1, arg2, arg3) { funcret func(a, b, c); } + +// ret func(arg1, arg2, arg3, arg4) +# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4) { funcret func(a, b, c, d); } + +// ret func(arg1, arg2, arg3, arg4, arg5) +# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5) { funcret func(a, b, c, d, e); } + +// ret func(arg1, arg2, arg3, arg4, arg6) +# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { funcret func(a, b, c, d, e, f); } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7) +# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { funcret func(a, b, c, d, e, f, g); } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) +# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { funcret func(a, b, c, d, e, f, g, h, i); } + +// **************** Static declarations ****************** + +#endif // !defined QT_LINKED_OPENSSL +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +typedef uint64_t qssloptions; +#else +typedef unsigned long qssloptions; +#endif +// TODO: the following lines previously were a part of 1.1 - specific header. +// To reduce the amount of the change, I'm directly copying and pasting the +// content of the header here. Later, can be better sorted/split into groups, +// depending on the functionality. + +const unsigned char * q_ASN1_STRING_get0_data(const ASN1_STRING *x); + +BIO *q_BIO_new(const BIO_METHOD *a); +const BIO_METHOD *q_BIO_s_mem(); + +void q_AUTHORITY_INFO_ACCESS_free(AUTHORITY_INFO_ACCESS *a); +int q_EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *c); +int q_EVP_PKEY_up_ref(EVP_PKEY *a); +EVP_PKEY_CTX *q_EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); +void q_EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); +int q_EVP_PKEY_param_check(EVP_PKEY_CTX *ctx); +int q_OPENSSL_sk_num(OPENSSL_STACK *a); +void q_OPENSSL_sk_pop_free(OPENSSL_STACK *a, void (*b)(void *)); +OPENSSL_STACK *q_OPENSSL_sk_new_null(); +void q_OPENSSL_sk_push(OPENSSL_STACK *st, void *data); +void q_OPENSSL_sk_free(OPENSSL_STACK *a); +void * q_OPENSSL_sk_value(OPENSSL_STACK *a, int b); +int q_SSL_session_reused(SSL *a); +qssloptions q_SSL_CTX_set_options(SSL_CTX *ctx, qssloptions op); +int q_OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); +size_t q_SSL_get_client_random(SSL *a, unsigned char *out, size_t outlen); +size_t q_SSL_SESSION_get_master_key(const SSL_SESSION *session, unsigned char *out, size_t outlen); +int q_CRYPTO_get_ex_new_index(int class_index, long argl, void *argp, CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); +const SSL_METHOD *q_TLS_method(); +const SSL_METHOD *q_TLS_client_method(); +const SSL_METHOD *q_TLS_server_method(); +ASN1_TIME *q_X509_getm_notBefore(X509 *a); +ASN1_TIME *q_X509_getm_notAfter(X509 *a); +void q_ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it); +void q_X509V3_conf_free(CONF_VALUE *val); + +void q_X509_up_ref(X509 *a); +long q_X509_get_version(X509 *a); +EVP_PKEY *q_X509_get_pubkey(X509 *a); +void q_X509_STORE_set_verify_cb(X509_STORE *ctx, X509_STORE_CTX_verify_cb verify_cb); +int q_X509_STORE_set_ex_data(X509_STORE *ctx, int idx, void *data); +void *q_X509_STORE_get_ex_data(X509_STORE *r, int idx); +STACK_OF(X509) *q_X509_STORE_CTX_get0_chain(X509_STORE_CTX *ctx); + +# define q_SSL_load_error_strings() q_OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS \ + | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL) + +#define q_SKM_sk_num(st) q_OPENSSL_sk_num((OPENSSL_STACK *)st) +#define q_SKM_sk_value(type, st,i) (type *)q_OPENSSL_sk_value((OPENSSL_STACK *)st, i) + +#define q_OPENSSL_add_all_algorithms_conf() q_OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS \ + | OPENSSL_INIT_LOAD_CONFIG, NULL) +#define q_OPENSSL_add_all_algorithms_noconf() q_OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL) + +int q_OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); + +long q_OpenSSL_version_num(); +const char *q_OpenSSL_version(int type); + +unsigned long q_SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *session); +unsigned long q_SSL_set_options(SSL *s, unsigned long op); + +#ifdef TLS1_3_VERSION +int q_SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); + +// The functions below do not really have to be ifdefed like this, but for now +// they only used in TLS 1.3 handshake (and probably future versions). +// Plus, 'is resumalbe' is OpenSSL 1.1.1-only (and again we need it for +// TLS 1.3-specific session management). + +extern "C" +{ +using NewSessionCallback = int (*)(SSL *, SSL_SESSION *); +} + +void q_SSL_CTX_sess_set_new_cb(SSL_CTX *ctx, NewSessionCallback cb); +int q_SSL_SESSION_is_resumable(const SSL_SESSION *s); + +#define q_SSL_CTX_set_session_cache_mode(ctx,m) \ + q_SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SESS_CACHE_MODE,m,NULL) + +#endif + +#if QT_CONFIG(dtls) +// Functions and types required for DTLS support: +extern "C" +{ + +typedef int (*CookieVerifyCallback)(SSL *, const unsigned char *, unsigned); +typedef int (*DgramWriteCallback) (BIO *, const char *, int); +typedef int (*DgramReadCallback) (BIO *, char *, int); +typedef int (*DgramPutsCallback) (BIO *, const char *); +typedef long (*DgramCtrlCallback) (BIO *, int, long, void *); +typedef int (*DgramCreateCallback) (BIO *); +typedef int (*DgramDestroyCallback) (BIO *); + +} + +int q_DTLSv1_listen(SSL *s, BIO_ADDR *client); +BIO_ADDR *q_BIO_ADDR_new(); +void q_BIO_ADDR_free(BIO_ADDR *ap); + +// API we need for a custom dgram BIO: + +BIO_METHOD *q_BIO_meth_new(int type, const char *name); +void q_BIO_meth_free(BIO_METHOD *biom); +int q_BIO_meth_set_write(BIO_METHOD *biom, DgramWriteCallback); +int q_BIO_meth_set_read(BIO_METHOD *biom, DgramReadCallback); +int q_BIO_meth_set_puts(BIO_METHOD *biom, DgramPutsCallback); +int q_BIO_meth_set_ctrl(BIO_METHOD *biom, DgramCtrlCallback); +int q_BIO_meth_set_create(BIO_METHOD *biom, DgramCreateCallback); +int q_BIO_meth_set_destroy(BIO_METHOD *biom, DgramDestroyCallback); + +#endif // dtls + +void q_BIO_set_data(BIO *a, void *ptr); +void *q_BIO_get_data(BIO *a); +void q_BIO_set_init(BIO *a, int init); +int q_BIO_get_shutdown(BIO *a); +void q_BIO_set_shutdown(BIO *a, int shut); + +#if QT_CONFIG(ocsp) +const OCSP_CERTID *q_OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP *x); +#endif // ocsp + +#define q_SSL_CTX_set_min_proto_version(ctx, version) \ + q_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, version, nullptr) + +#define q_SSL_CTX_set_max_proto_version(ctx, version) \ + q_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MAX_PROTO_VERSION, version, nullptr) + +extern "C" { +typedef int (*q_SSL_psk_use_session_cb_func_t)(SSL *, const EVP_MD *, const unsigned char **, size_t *, + SSL_SESSION **); +} +void q_SSL_set_psk_use_session_callback(SSL *s, q_SSL_psk_use_session_cb_func_t); +// Here the content of the 1.1 header ends. + +bool q_resolveOpenSslSymbols(); +long q_ASN1_INTEGER_get(ASN1_INTEGER *a); +int q_ASN1_INTEGER_cmp(const ASN1_INTEGER *x, const ASN1_INTEGER *y); +int q_ASN1_STRING_length(ASN1_STRING *a); +int q_ASN1_STRING_to_UTF8(unsigned char **a, ASN1_STRING *b); +int q_ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm); +long q_BIO_ctrl(BIO *a, int b, long c, void *d); +int q_BIO_free(BIO *a); +BIO *q_BIO_new_mem_buf(void *a, int b); +int q_BIO_read(BIO *a, void *b, int c); +int q_BIO_write(BIO *a, const void *b, int c); +int q_BN_num_bits(const BIGNUM *a); +int q_BN_is_word(BIGNUM *a, BN_ULONG w); +BN_ULONG q_BN_mod_word(const BIGNUM *a, BN_ULONG w); + +X509 *q_d2i_X509(X509 **a, const unsigned char **b, long c); +char *q_ERR_error_string(unsigned long a, char *b); +void q_ERR_error_string_n(unsigned long e, char *buf, size_t len); +unsigned long q_ERR_get_error(); +EVP_CIPHER_CTX *q_EVP_CIPHER_CTX_new(); +void q_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *a); +int q_EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr); +int q_EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen); +int q_EVP_CipherInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, const unsigned char *key, const unsigned char *iv, int enc); +int q_EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv, int enc); +int q_EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl); +int q_EVP_CipherFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); +const EVP_MD *q_EVP_get_digestbyname(const char *name); + +#ifndef OPENSSL_NO_DES +const EVP_CIPHER *q_EVP_des_cbc(); +const EVP_CIPHER *q_EVP_des_ede3_cbc(); +#endif // OPENSSL_NO_DES + +#ifndef OPENSSL_NO_RC2 +const EVP_CIPHER *q_EVP_rc2_cbc(); +#endif // OPENSSL_NO_RC2 + +#ifndef OPENSSL_NO_AES +const EVP_CIPHER *q_EVP_aes_128_cbc(); +const EVP_CIPHER *q_EVP_aes_192_cbc(); +const EVP_CIPHER *q_EVP_aes_256_cbc(); +#endif // OPENSSL_NO_AES + +const EVP_MD *q_EVP_sha1(); + +void q_EVP_PKEY_free(EVP_PKEY *a); +int q_EVP_PKEY_type(int a); +EVP_PKEY *q_EVP_PKEY_new(); +int q_i2d_X509(X509 *a, unsigned char **b); +const char *q_OBJ_nid2sn(int a); +const char *q_OBJ_nid2ln(int a); +int q_OBJ_sn2nid(const char *s); +int q_OBJ_ln2nid(const char *s); +int q_i2t_ASN1_OBJECT(char *buf, int buf_len, ASN1_OBJECT *obj); +int q_OBJ_obj2txt(char *buf, int buf_len, ASN1_OBJECT *obj, int no_name); +int q_OBJ_obj2nid(const ASN1_OBJECT *a); +#define q_EVP_get_digestbynid(a) q_EVP_get_digestbyname(q_OBJ_nid2sn(a)) +EVP_PKEY *q_PEM_read_bio_PrivateKey(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d); + +int q_PEM_write_bio_PrivateKey(BIO *a, EVP_PKEY *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +int q_PEM_write_bio_PrivateKey_traditional(BIO *a, EVP_PKEY *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +EVP_PKEY *q_PEM_read_bio_PUBKEY(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d); +int q_PEM_write_bio_PUBKEY(BIO *a, EVP_PKEY *b); + +void q_RAND_seed(const void *a, int b); +int q_RAND_status(); +int q_RAND_bytes(unsigned char *b, int n); +int q_SSL_accept(SSL *a); +int q_SSL_clear(SSL *a); +char *q_SSL_CIPHER_description(const SSL_CIPHER *a, char *b, int c); +int q_SSL_CIPHER_get_bits(const SSL_CIPHER *a, int *b); +BIO *q_SSL_get_rbio(const SSL *s); +int q_SSL_connect(SSL *a); +int q_SSL_CTX_check_private_key(const SSL_CTX *a); +long q_SSL_CTX_ctrl(SSL_CTX *a, int b, long c, void *d); +void q_SSL_CTX_free(SSL_CTX *a); +SSL_CTX *q_SSL_CTX_new(const SSL_METHOD *a); +int q_SSL_CTX_set_cipher_list(SSL_CTX *a, const char *b); +int q_SSL_CTX_set_default_verify_paths(SSL_CTX *a); +void q_SSL_CTX_set_verify(SSL_CTX *a, int b, int (*c)(int, X509_STORE_CTX *)); +void q_SSL_CTX_set_verify_depth(SSL_CTX *a, int b); +extern "C" { +typedef void (*GenericCallbackType)(); +} +long q_SSL_CTX_callback_ctrl(SSL_CTX *, int, GenericCallbackType); +int q_SSL_CTX_use_certificate(SSL_CTX *a, X509 *b); +int q_SSL_CTX_use_certificate_file(SSL_CTX *a, const char *b, int c); +int q_SSL_CTX_use_PrivateKey(SSL_CTX *a, EVP_PKEY *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); +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); +void q_SSL_free(SSL *a); +STACK_OF(SSL_CIPHER) *q_SSL_get_ciphers(const SSL *a); +const SSL_CIPHER *q_SSL_get_current_cipher(SSL *a); +int q_SSL_version(const SSL *a); +int q_SSL_get_error(SSL *a, int b); +STACK_OF(X509) *q_SSL_get_peer_cert_chain(SSL *a); +long q_SSL_get_verify_result(const SSL *a); +SSL *q_SSL_new(SSL_CTX *a); +SSL_CTX *q_SSL_get_SSL_CTX(SSL *a); +long q_SSL_ctrl(SSL *ssl,int cmd, long larg, void *parg); +int q_SSL_read(SSL *a, void *b, int c); +void q_SSL_set_bio(SSL *a, BIO *b, BIO *c); +void q_SSL_set_accept_state(SSL *a); +void q_SSL_set_connect_state(SSL *a); +int q_SSL_shutdown(SSL *a); +int q_SSL_in_init(const SSL *s); +int q_SSL_get_shutdown(const SSL *ssl); +int q_SSL_set_session(SSL *to, SSL_SESSION *session); +void q_SSL_SESSION_free(SSL_SESSION *ses); +SSL_SESSION *q_SSL_get1_session(SSL *ssl); +SSL_SESSION *q_SSL_get_session(const SSL *ssl); +int q_SSL_set_ex_data(SSL *ssl, int idx, void *arg); +void *q_SSL_get_ex_data(const SSL *ssl, int idx); +#ifndef OPENSSL_NO_PSK +typedef unsigned int (*q_psk_client_callback_t)(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); +void q_SSL_set_psk_client_callback(SSL *ssl, q_psk_client_callback_t callback); +typedef unsigned int (*q_psk_server_callback_t)(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len); +void q_SSL_set_psk_server_callback(SSL *ssl, q_psk_server_callback_t callback); +int q_SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *hint); +#endif // !OPENSSL_NO_PSK +int q_SSL_write(SSL *a, const void *b, int c); +int q_X509_cmp(X509 *a, X509 *b); +X509 *q_X509_dup(X509 *a); +void q_X509_print(BIO *a, X509*b); +int q_X509_digest(const X509 *x509, const EVP_MD *type, unsigned char *md, unsigned int *len); +ASN1_OBJECT *q_X509_EXTENSION_get_object(X509_EXTENSION *a); +void q_X509_free(X509 *a); +ASN1_TIME *q_X509_gmtime_adj(ASN1_TIME *s, long adj); +void q_ASN1_TIME_free(ASN1_TIME *t); +X509_EXTENSION *q_X509_get_ext(X509 *a, int b); +int q_X509_get_ext_count(X509 *a); +void *q_X509_get_ext_d2i(X509 *a, int b, int *c, int *d); +const X509V3_EXT_METHOD *q_X509V3_EXT_get(X509_EXTENSION *a); +void *q_X509V3_EXT_d2i(X509_EXTENSION *a); +int q_X509_EXTENSION_get_critical(X509_EXTENSION *a); +ASN1_OCTET_STRING *q_X509_EXTENSION_get_data(X509_EXTENSION *a); +void q_BASIC_CONSTRAINTS_free(BASIC_CONSTRAINTS *a); +void q_AUTHORITY_KEYID_free(AUTHORITY_KEYID *a); +int q_ASN1_STRING_print(BIO *a, const ASN1_STRING *b); +int q_X509_check_issued(X509 *a, X509 *b); +X509_NAME *q_X509_get_issuer_name(X509 *a); +X509_NAME *q_X509_get_subject_name(X509 *a); +ASN1_INTEGER *q_X509_get_serialNumber(X509 *a); +int q_X509_verify_cert(X509_STORE_CTX *ctx); +int q_X509_NAME_entry_count(X509_NAME *a); +X509_NAME_ENTRY *q_X509_NAME_get_entry(X509_NAME *a,int b); +ASN1_STRING *q_X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *a); +ASN1_OBJECT *q_X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *a); +EVP_PKEY *q_X509_PUBKEY_get(X509_PUBKEY *a); +void q_X509_STORE_free(X509_STORE *store); +X509_STORE *q_X509_STORE_new(); +int q_X509_STORE_add_cert(X509_STORE *ctx, X509 *x); +void q_X509_STORE_CTX_free(X509_STORE_CTX *storeCtx); +int q_X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, + X509 *x509, STACK_OF(X509) *chain); +X509_STORE_CTX *q_X509_STORE_CTX_new(); +int q_X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose); +int q_X509_STORE_CTX_get_error(X509_STORE_CTX *ctx); +int q_X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx); +X509 *q_X509_STORE_CTX_get_current_cert(X509_STORE_CTX *ctx); +X509_STORE *q_X509_STORE_CTX_get0_store(X509_STORE_CTX *ctx); + +// Diffie-Hellman support +#ifndef OPENSSL_NO_DEPRECATED_3_0 +DH *q_DH_new(); +void q_DH_free(DH *dh); +int q_DH_check(DH *dh, int *codes); +void q_DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); + +DH *q_d2i_DHparams(DH **a, const unsigned char **pp, long length); +int q_i2d_DHparams(DH *a, unsigned char **p); + +DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d); +#endif // OPENSSL_NO_DEPRECATED_3_0 + +BIGNUM *q_BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); +#define q_SSL_CTX_set_tmp_dh(ctx, dh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_DH, 0, (char *)dh) +#define q_SSL_CTX_set_dh_auto(ctx, onoff) q_SSL_CTX_ctrl(ctx,SSL_CTRL_SET_DH_AUTO,onoff,NULL) + +#ifndef OPENSSL_NO_EC +// EC Diffie-Hellman support +#define q_SSL_CTX_set_tmp_ecdh(ctx, ecdh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_ECDH, 0, (char *)ecdh) + +// EC curves management +size_t q_EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems); +int q_EC_curve_nist2nid(const char *name); +#endif // OPENSSL_NO_EC + +#define q_SSL_get_server_tmp_key(ssl, key) q_SSL_ctrl((ssl), SSL_CTRL_GET_SERVER_TMP_KEY, 0, (char *)key) + +// PKCS#12 support +int q_PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca); +PKCS12 *q_d2i_PKCS12_bio(BIO *bio, PKCS12 **pkcs12); +void q_PKCS12_free(PKCS12 *pkcs12); + +#define q_BIO_get_mem_data(b, pp) (int)q_BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp) +#define q_BIO_pending(b) (int)q_BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL) +#define q_SSL_CTX_set_mode(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_MODE,(op),NULL) +#define q_sk_GENERAL_NAME_num(st) q_SKM_sk_num((st)) +#define q_sk_GENERAL_NAME_value(st, i) q_SKM_sk_value(GENERAL_NAME, (st), (i)) + +void q_GENERAL_NAME_free(GENERAL_NAME *a); + +#define q_sk_X509_num(st) q_SKM_sk_num((st)) +#define q_sk_X509_value(st, i) q_SKM_sk_value(X509, (st), (i)) +#define q_sk_SSL_CIPHER_num(st) q_SKM_sk_num((st)) +#define q_sk_SSL_CIPHER_value(st, i) q_SKM_sk_value(SSL_CIPHER, (st), (i)) +#define q_SSL_CTX_add_extra_chain_cert(ctx,x509) \ + q_SSL_CTX_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) +#define q_OpenSSL_add_all_algorithms() q_OPENSSL_add_all_algorithms_conf() + +#if OPENSSL_VERSION_MAJOR < 3 +int q_SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath); +#else +int q_SSL_CTX_load_verify_dir(SSL_CTX *ctx, const char *CApath); +#endif // OPENSSL_VERSION_MAJOR + +int q_i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp); +SSL_SESSION *q_d2i_SSL_SESSION(SSL_SESSION **a, const unsigned char **pp, long length); + +#ifndef OPENSSL_NO_NEXTPROTONEG +int q_SSL_select_next_proto(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const unsigned char *client, unsigned int client_len); +void q_SSL_CTX_set_next_proto_select_cb(SSL_CTX *s, + int (*cb) (SSL *ssl, unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), + void *arg); +void q_SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data, + unsigned *len); +int q_SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos, + unsigned protos_len); +void q_SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, + int (*cb) (SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg), void *arg); +void q_SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, + unsigned *len); +#endif // !OPENSSL_NO_NEXTPROTONEG + + +#if QT_CONFIG(dtls) + +extern "C" +{ +typedef int (*CookieGenerateCallback)(SSL *, unsigned char *, unsigned *); +} + +void q_SSL_CTX_set_cookie_generate_cb(SSL_CTX *ctx, CookieGenerateCallback cb); +void q_SSL_CTX_set_cookie_verify_cb(SSL_CTX *ctx, CookieVerifyCallback cb); +const SSL_METHOD *q_DTLS_server_method(); +const SSL_METHOD *q_DTLS_client_method(); + +#endif // dtls + +void *q_X509_STORE_CTX_get_ex_data(X509_STORE_CTX *ctx, int idx); +int q_SSL_get_ex_data_X509_STORE_CTX_idx(); + +#if QT_CONFIG(dtls) +#define q_DTLS_set_link_mtu(ssl, mtu) q_SSL_ctrl((ssl), DTLS_CTRL_SET_LINK_MTU, (mtu), nullptr) +#define q_DTLSv1_get_timeout(ssl, arg) q_SSL_ctrl(ssl, DTLS_CTRL_GET_TIMEOUT, 0, arg) +#define q_DTLSv1_handle_timeout(ssl) q_SSL_ctrl(ssl, DTLS_CTRL_HANDLE_TIMEOUT, 0, nullptr) +#endif // dtls + +void q_BIO_set_flags(BIO *b, int flags); +void q_BIO_clear_flags(BIO *b, int flags); +void *q_BIO_get_ex_data(BIO *b, int idx); +int q_BIO_set_ex_data(BIO *b, int idx, void *data); + +#define q_BIO_set_retry_read(b) q_BIO_set_flags(b, (BIO_FLAGS_READ|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_set_retry_write(b) q_BIO_set_flags(b, (BIO_FLAGS_WRITE|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_clear_retry_flags(b) q_BIO_clear_flags(b, (BIO_FLAGS_RWS|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_set_app_data(s,arg) q_BIO_set_ex_data(s,0,arg) +#define q_BIO_get_app_data(s) q_BIO_get_ex_data(s,0) + +#define q_SSL_set_tlsext_status_type(ssl, type) \ + q_SSL_ctrl((ssl), SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE, (type), nullptr) + +#if QT_CONFIG(ocsp) + +OCSP_RESPONSE *q_d2i_OCSP_RESPONSE(OCSP_RESPONSE **a, const unsigned char **in, long len); +int q_i2d_OCSP_RESPONSE(OCSP_RESPONSE *r, unsigned char **ppout); +OCSP_RESPONSE *q_OCSP_response_create(int status, OCSP_BASICRESP *bs); +void q_OCSP_RESPONSE_free(OCSP_RESPONSE *rs); +int q_OCSP_response_status(OCSP_RESPONSE *resp); +OCSP_BASICRESP *q_OCSP_response_get1_basic(OCSP_RESPONSE *resp); +OCSP_SINGLERESP *q_OCSP_basic_add1_status(OCSP_BASICRESP *rsp, OCSP_CERTID *cid, + int status, int reason, ASN1_TIME *revtime, + ASN1_TIME *thisupd, ASN1_TIME *nextupd); +int q_OCSP_basic_sign(OCSP_BASICRESP *brsp, X509 *signer, EVP_PKEY *key, const EVP_MD *dgst, + STACK_OF(X509) *certs, unsigned long flags); +OCSP_BASICRESP *q_OCSP_BASICRESP_new(); +void q_OCSP_BASICRESP_free(OCSP_BASICRESP *bs); +int q_OCSP_basic_verify(OCSP_BASICRESP *bs, STACK_OF(X509) *certs, X509_STORE *st, unsigned long flags); +int q_OCSP_resp_count(OCSP_BASICRESP *bs); +OCSP_SINGLERESP *q_OCSP_resp_get0(OCSP_BASICRESP *bs, int idx); +int q_OCSP_single_get0_status(OCSP_SINGLERESP *single, int *reason, ASN1_GENERALIZEDTIME **revtime, + ASN1_GENERALIZEDTIME **thisupd, ASN1_GENERALIZEDTIME **nextupd); +int q_OCSP_check_validity(ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd, long nsec, long maxsec); +int q_OCSP_id_get0_info(ASN1_OCTET_STRING **piNameHash, ASN1_OBJECT **pmd, ASN1_OCTET_STRING **pikeyHash, + ASN1_INTEGER **pserial, OCSP_CERTID *cid); + +const STACK_OF(X509) *q_OCSP_resp_get0_certs(const OCSP_BASICRESP *bs); +OCSP_CERTID *q_OCSP_cert_to_id(const EVP_MD *dgst, X509 *subject, X509 *issuer); +void q_OCSP_CERTID_free(OCSP_CERTID *cid); +int q_OCSP_id_cmp(OCSP_CERTID *a, OCSP_CERTID *b); + +#define q_SSL_get_tlsext_status_ocsp_resp(ssl, arg) \ + q_SSL_ctrl(ssl, SSL_CTRL_GET_TLSEXT_STATUS_REQ_OCSP_RESP, 0, arg) + +#define q_SSL_CTX_set_tlsext_status_cb(ssl, cb) \ + q_SSL_CTX_callback_ctrl(ssl, SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB, GenericCallbackType(cb)) + +# define q_SSL_set_tlsext_status_ocsp_resp(ssl, arg, arglen) \ + q_SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP, arglen, arg) + +#endif // ocsp + + +void *q_CRYPTO_malloc(size_t num, const char *file, int line); +#define q_OPENSSL_malloc(num) q_CRYPTO_malloc(num, "", 0) +void q_CRYPTO_free(void *str, const char *file, int line); +# define q_OPENSSL_free(addr) q_CRYPTO_free(addr, "", 0) +int q_CRYPTO_memcmp(const void * in_a, const void * in_b, size_t len); + +void q_SSL_set_info_callback(SSL *ssl, void (*cb) (const SSL *ssl, int type, int val)); +const char *q_SSL_alert_type_string(int value); +const char *q_SSL_alert_desc_string_long(int value); + +int q_SSL_CTX_get_security_level(const SSL_CTX *ctx); +void q_SSL_CTX_set_security_level(SSL_CTX *ctx, int level); + +// Here we have the ones that make difference between OpenSSL pre/post v3: +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +X509 *q_SSL_get1_peer_certificate(SSL *a); +#define q_SSL_get_peer_certificate q_SSL_get1_peer_certificate +int q_EVP_PKEY_get_bits(const EVP_PKEY *pkey); +int q_EVP_PKEY_get_base_id(const EVP_PKEY *pkey); +#define q_EVP_PKEY_base_id q_EVP_PKEY_get_base_id +#else +X509 *q_SSL_get_peer_certificate(SSL *a); +int q_EVP_PKEY_base_id(EVP_PKEY *a); +#endif // OPENSSL_VERSION_MAJOR >= 3 + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + +DSA *q_DSA_new(); +void q_DSA_free(DSA *a); + +RSA *q_RSA_new(); +void q_RSA_free(RSA *a); + +#ifndef OPENSSL_NO_EC + +EC_KEY *q_EC_KEY_dup(const EC_KEY *src); +EC_KEY *q_EC_KEY_new_by_curve_name(int nid); +void q_EC_KEY_free(EC_KEY *ecdh); + +#endif // OPENSSL_NO_EC + +int q_SSL_CTX_use_RSAPrivateKey(SSL_CTX *a, RSA *b); + +DSA *q_PEM_read_bio_DSA_PUBKEY(BIO *a, DSA **b, pem_password_cb *c, void *d); +RSA *q_PEM_read_bio_RSA_PUBKEY(BIO *a, RSA **b, pem_password_cb *c, void *d); + +DSA *q_PEM_read_bio_DSAPrivateKey(BIO *a, DSA **b, pem_password_cb *c, void *d); +RSA *q_PEM_read_bio_RSAPrivateKey(BIO *a, RSA **b, pem_password_cb *c, void *d); + +int q_PEM_write_bio_DSA_PUBKEY(BIO *a, DSA *b); +int q_PEM_write_bio_RSA_PUBKEY(BIO *a, RSA *b); + +int q_PEM_write_bio_DSAPrivateKey(BIO *a, DSA *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +int q_PEM_write_bio_RSAPrivateKey(BIO *a, RSA *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); + +RSA *q_EVP_PKEY_get1_RSA(EVP_PKEY *a); +DSA *q_EVP_PKEY_get1_DSA(EVP_PKEY *a); +DH *q_EVP_PKEY_get1_DH(EVP_PKEY *a); + +int q_EVP_PKEY_set1_RSA(EVP_PKEY *a, RSA *b); +int q_EVP_PKEY_set1_DSA(EVP_PKEY *a, DSA *b); +int q_EVP_PKEY_set1_DH(EVP_PKEY *a, DH *b); + +int q_DH_bits(DH *dh); +int q_RSA_bits(RSA *a); +int q_DSA_bits(DSA *a); + +int q_EVP_PKEY_assign(EVP_PKEY *a, int b, void *r); +int q_EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b); + +#ifndef OPENSSL_NO_EC + +EC_KEY *q_PEM_read_bio_EC_PUBKEY(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); +EC_KEY *q_PEM_read_bio_ECPrivateKey(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); + +int q_PEM_write_bio_ECPrivateKey(BIO *a, EC_KEY *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +int q_PEM_write_bio_EC_PUBKEY(BIO *a, EC_KEY *b); + +EC_KEY *q_EVP_PKEY_get1_EC_KEY(EVP_PKEY *a); +int q_EVP_PKEY_set1_EC_KEY(EVP_PKEY *a, EC_KEY *b); + +const EC_GROUP* q_EC_KEY_get0_group(const EC_KEY* k); +int q_EC_GROUP_get_degree(const EC_GROUP* g); + +#define q_EVP_PKEY_assign_RSA(pkey,rsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\ + (char *)(rsa)) +#define q_EVP_PKEY_assign_DSA(pkey,dsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_DSA,\ + (char *)(dsa)) + + +#endif // OPENSSL_NO_EC + +#endif // OPENSSL_NO_DEPRECATED_3_0 + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tls/openssl/qtls_openssl.cpp b/src/plugins/tls/openssl/qtls_openssl.cpp new file mode 100644 index 0000000000..57d09a649b --- /dev/null +++ b/src/plugins/tls/openssl/qtls_openssl.cpp @@ -0,0 +1,1860 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsslsocket_openssl_symbols_p.h" +#include "qx509_openssl_p.h" +#include "qtls_openssl_p.h" + +#ifdef Q_OS_WIN +#include "qwindowscarootfetcher_p.h" +#endif + +#include <QtNetwork/private/qsslpresharedkeyauthenticator_p.h> +#include <QtNetwork/private/qsslcertificate_p.h> +#include <QtNetwork/private/qocspresponse_p.h> +#include <QtNetwork/private/qsslsocket_p.h> + +#include <QtNetwork/qsslpresharedkeyauthenticator.h> + +#include <QtCore/qscopedvaluerollback.h> +#include <QtCore/qscopeguard.h> + +#include <algorithm> +#include <cstring> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + +QSsl::AlertLevel tlsAlertLevel(int value) +{ + using QSsl::AlertLevel; + + if (const char *typeString = q_SSL_alert_type_string(value)) { + // Documented to return 'W' for warning, 'F' for fatal, + // 'U' for unknown. + switch (typeString[0]) { + case 'W': + return AlertLevel::Warning; + case 'F': + return AlertLevel::Fatal; + default:; + } + } + + return AlertLevel::Unknown; +} + +QString tlsAlertDescription(int value) +{ + QString description = QLatin1StringView(q_SSL_alert_desc_string_long(value)); + if (!description.size()) + description = "no description provided"_L1; + return description; +} + +QSsl::AlertType tlsAlertType(int value) +{ + // In case for some reason openssl gives us a value, + // which is not in our enum actually, we leave it to + // an application to handle (supposedly they have + // if or switch-statements). + return QSsl::AlertType(value & 0xff); +} + +#ifdef Q_OS_WIN + +QSslCertificate findCertificateToFetch(const QList<QSslError> &tlsErrors, bool checkAIA) +{ + QSslCertificate certToFetch; + + for (const auto &tlsError : tlsErrors) { + switch (tlsError.error()) { + case QSslError::UnableToGetLocalIssuerCertificate: // site presented intermediate cert, but root is unknown + case QSslError::SelfSignedCertificateInChain: // site presented a complete chain, but root is unknown + certToFetch = tlsError.certificate(); + break; + case QSslError::SelfSignedCertificate: + case QSslError::CertificateBlacklisted: + //With these errors, we know it will be untrusted so save time by not asking windows + return QSslCertificate{}; + default: +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << tlsError.errorString(); +#endif + //TODO - this part is strange. + break; + } + } + + if (checkAIA) { + const auto extensions = certToFetch.extensions(); + for (const auto &ext : extensions) { + if (ext.oid() == u"1.3.6.1.5.5.7.1.1") // See RFC 4325 + return certToFetch; + } + //The only reason we check this extensions is because an application set trusted + //CA certificates explicitly, thus technically disabling CA fetch. So, if it's + //the case and an intermediate certificate is missing, and no extensions is + //present on the leaf certificate - we fail the handshake immediately. + return QSslCertificate{}; + } + + return certToFetch; +} + +#endif // Q_OS_WIN + +} // unnamed namespace + +namespace QTlsPrivate { + +extern "C" { + +int q_X509Callback(int ok, X509_STORE_CTX *ctx) +{ + if (!ok) { + // Store the error and at which depth the error was detected. + + using ErrorListPtr = QList<QSslErrorEntry> *; + ErrorListPtr errors = nullptr; + + // Error list is attached to either 'SSL' or 'X509_STORE'. + if (X509_STORE *store = q_X509_STORE_CTX_get0_store(ctx)) // We try store first: + errors = ErrorListPtr(q_X509_STORE_get_ex_data(store, 0)); + + if (!errors) { + // Not found on store? Try SSL and its external data then. According to the OpenSSL's + // documentation: + // + // "Whenever a X509_STORE_CTX object is created for the verification of the + // peer's certificate during a handshake, a pointer to the SSL object is + // stored into the X509_STORE_CTX object to identify the connection affected. + // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be + // used with the correct index." + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::errorOffsetInExData; + if (SSL *ssl = static_cast<SSL *>(q_X509_STORE_CTX_get_ex_data( + ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()))) { + + // We may be in a renegotiation, check if we are inside a call to SSL_read: + const auto tlsOffset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::socketOffsetInExData; + auto tls = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, tlsOffset)); + Q_ASSERT(tls); + if (tls->isInSslRead()) { + // We are in a renegotiation, make a note of this for later. + // We'll check that the certificate is the same as the one we got during + // the initial handshake + tls->setRenegotiated(true); + return 1; + } + + errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset)); + } + } + + if (!errors) { + qCWarning(lcTlsBackend, "Neither X509_STORE, nor SSL contains error list, handshake failure"); + return 0; + } + + errors->append(X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); + } + // Always return OK to allow verification to continue. We handle the + // errors gracefully after collecting all errors, after verification has + // completed. + return 1; +} + +int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx) +{ + // Passed to SSL_CTX_set_verify() + // https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_verify.html + // Returns 0 to abort verification, 1 to continue. + + // This is a new, experimental verification callback, reporting + // errors immediately and returning 0 or 1 depending on an application + // either ignoring or not ignoring verification errors as they come. + if (!ctx) { + qCWarning(lcTlsBackend, "Invalid store context (nullptr)"); + return 0; + } + + if (!ok) { + // "Whenever a X509_STORE_CTX object is created for the verification of the + // peer's certificate during a handshake, a pointer to the SSL object is + // stored into the X509_STORE_CTX object to identify the connection affected. + // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be + // used with the correct index." + SSL *ssl = static_cast<SSL *>(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx())); + if (!ssl) { + qCWarning(lcTlsBackend, "No external data (SSL) found in X509 store object"); + return 0; + } + + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::socketOffsetInExData; + auto crypto = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, offset)); + if (!crypto) { + qCWarning(lcTlsBackend, "No external data (TlsCryptographOpenSSL) found in SSL object"); + return 0; + } + + return crypto->emitErrorFromCallback(ctx); + } + return 1; +} + +#ifndef OPENSSL_NO_PSK +static unsigned q_ssl_psk_client_callback(SSL *ssl, const char *hint, char *identity, unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len) +{ + auto *tls = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + return tls->pskClientTlsCallback(hint, identity, max_identity_len, psk, max_psk_len); +} + +static unsigned int q_ssl_psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, + unsigned int max_psk_len) +{ + auto *tls = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + Q_ASSERT(tls); + return tls->pskServerTlsCallback(identity, psk, max_psk_len); +} + +#ifdef TLS1_3_VERSION +static unsigned q_ssl_psk_restore_client(SSL *ssl, const char *hint, char *identity, unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len) +{ + Q_UNUSED(hint); + Q_UNUSED(identity); + Q_UNUSED(max_identity_len); + Q_UNUSED(psk); + Q_UNUSED(max_psk_len); + +#ifdef QT_DEBUG + auto tls = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + Q_ASSERT(tls); + Q_ASSERT(tls->d); + Q_ASSERT(tls->d->tlsMode() == QSslSocket::SslClientMode); +#endif + unsigned retVal = 0; + + // Let developers opt-in to having the normal PSK callback get called for TLS 1.3 + // PSK (which works differently in a few ways, and is called at the start of every connection). + // When they do opt-in we just call the old callback from here. + if (qEnvironmentVariableIsSet("QT_USE_TLS_1_3_PSK")) + retVal = q_ssl_psk_client_callback(ssl, hint, identity, max_identity_len, psk, max_psk_len); + + q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); + + return retVal; +} + +static int q_ssl_psk_use_session_callback(SSL *ssl, const EVP_MD *md, const unsigned char **id, + size_t *idlen, SSL_SESSION **sess) +{ + Q_UNUSED(md); + Q_UNUSED(id); + Q_UNUSED(idlen); + Q_UNUSED(sess); + +#ifdef QT_DEBUG + auto *tls = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + Q_ASSERT(tls); + Q_ASSERT(tls->d); + Q_ASSERT(tls->d->tlsMode() == QSslSocket::SslClientMode); +#endif + + // Temporarily rebind the psk because it will be called next. The function will restore it. + q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_restore_client); + + return 1; // need to return 1 or else "the connection setup fails." +} + +int q_ssl_sess_set_new_cb(SSL *ssl, SSL_SESSION *session) +{ + if (!ssl) { + qCWarning(lcTlsBackend, "Invalid SSL (nullptr)"); + return 0; + } + if (!session) { + qCWarning(lcTlsBackend, "Invalid SSL_SESSION (nullptr)"); + return 0; + } + + auto *tls = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + Q_ASSERT(tls); + return tls->handleNewSessionTicket(ssl); +} +#endif // TLS1_3_VERSION + +#endif // !OPENSSL_NO_PSK + +#if QT_CONFIG(ocsp) + +int qt_OCSP_status_server_callback(SSL *ssl, void *ocspRequest) +{ + Q_UNUSED(ocspRequest); + if (!ssl) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + auto crypto = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + if (!crypto) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + Q_ASSERT(crypto->d); + Q_ASSERT(crypto->d->tlsMode() == QSslSocket::SslServerMode); + const QByteArray &response = crypto->ocspResponseDer; + Q_ASSERT(response.size()); + + unsigned char *derCopy = static_cast<unsigned char *>(q_OPENSSL_malloc(size_t(response.size()))); + if (!derCopy) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + std::copy(response.data(), response.data() + response.size(), derCopy); + // We don't check the return value: internally OpenSSL simply assigns the + // pointer (it assumes it now owns this memory btw!) and the length. + q_SSL_set_tlsext_status_ocsp_resp(ssl, derCopy, response.size()); + + return SSL_TLSEXT_ERR_OK; +} + +#endif // ocsp + +void qt_AlertInfoCallback(const SSL *connection, int from, int value) +{ + // Passed to SSL_set_info_callback() + // https://www.openssl.org/docs/man1.1.1/man3/SSL_set_info_callback.html + + if (!connection) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend, "Invalid 'connection' parameter (nullptr)"); +#endif // QSSLSOCKET_DEBUG + return; + } + + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::socketOffsetInExData; + auto crypto = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(connection, offset)); + if (!crypto) { + // SSL_set_ex_data can fail: +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend, "No external data (socket backend) found for parameter 'connection'"); +#endif // QSSLSOCKET_DEBUG + return; + } + + if (!(from & SSL_CB_ALERT)) { + // We only want to know about alerts (at least for now). + return; + } + + if (from & SSL_CB_WRITE) + crypto->alertMessageSent(value); + else + crypto->alertMessageReceived(value); +} + +} // extern "C" + +#if QT_CONFIG(ocsp) +namespace { + +QSslError::SslError qt_OCSP_response_status_to_SslError(long code) +{ + switch (code) { + case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST: + return QSslError::OcspMalformedRequest; + case OCSP_RESPONSE_STATUS_INTERNALERROR: + return QSslError::OcspInternalError; + case OCSP_RESPONSE_STATUS_TRYLATER: + return QSslError::OcspTryLater; + case OCSP_RESPONSE_STATUS_SIGREQUIRED: + return QSslError::OcspSigRequred; + case OCSP_RESPONSE_STATUS_UNAUTHORIZED: + return QSslError::OcspUnauthorized; + case OCSP_RESPONSE_STATUS_SUCCESSFUL: + default: + return {}; + } + Q_UNREACHABLE(); +} + +QOcspRevocationReason qt_OCSP_revocation_reason(int reason) +{ + switch (reason) { + case OCSP_REVOKED_STATUS_NOSTATUS: + return QOcspRevocationReason::None; + case OCSP_REVOKED_STATUS_UNSPECIFIED: + return QOcspRevocationReason::Unspecified; + case OCSP_REVOKED_STATUS_KEYCOMPROMISE: + return QOcspRevocationReason::KeyCompromise; + case OCSP_REVOKED_STATUS_CACOMPROMISE: + return QOcspRevocationReason::CACompromise; + case OCSP_REVOKED_STATUS_AFFILIATIONCHANGED: + return QOcspRevocationReason::AffiliationChanged; + case OCSP_REVOKED_STATUS_SUPERSEDED: + return QOcspRevocationReason::Superseded; + case OCSP_REVOKED_STATUS_CESSATIONOFOPERATION: + return QOcspRevocationReason::CessationOfOperation; + case OCSP_REVOKED_STATUS_CERTIFICATEHOLD: + return QOcspRevocationReason::CertificateHold; + case OCSP_REVOKED_STATUS_REMOVEFROMCRL: + return QOcspRevocationReason::RemoveFromCRL; + default: + return QOcspRevocationReason::None; + } + + Q_UNREACHABLE(); +} + +bool qt_OCSP_certificate_match(OCSP_SINGLERESP *singleResponse, X509 *peerCert, X509 *issuer) +{ + // OCSP_basic_verify does verify that the responder is legit, the response is + // correctly signed, CertID is correct. But it does not know which certificate + // we were presented with by our peer, so it does not check if it's a response + // for our peer's certificate. + Q_ASSERT(singleResponse && peerCert && issuer); + + const OCSP_CERTID *certId = q_OCSP_SINGLERESP_get0_id(singleResponse); // Does not increment refcount. + if (!certId) { + qCWarning(lcTlsBackend, "A SingleResponse without CertID"); + return false; + } + + ASN1_OBJECT *md = nullptr; + ASN1_INTEGER *reportedSerialNumber = nullptr; + const int result = q_OCSP_id_get0_info(nullptr, &md, nullptr, &reportedSerialNumber, const_cast<OCSP_CERTID *>(certId)); + if (result != 1 || !md || !reportedSerialNumber) { + qCWarning(lcTlsBackend, "Failed to extract a hash and serial number from CertID structure"); + return false; + } + + if (!q_X509_get_serialNumber(peerCert)) { + // Is this possible at all? But we have to check this, + // ASN1_INTEGER_cmp (called from OCSP_id_cmp) dereferences + // without any checks at all. + qCWarning(lcTlsBackend, "No serial number in peer's ceritificate"); + return false; + } + + const int nid = q_OBJ_obj2nid(md); + if (nid == NID_undef) { + qCWarning(lcTlsBackend, "Unknown hash algorithm in CertID"); + return false; + } + + const EVP_MD *digest = q_EVP_get_digestbynid(nid); // Does not increment refcount. + if (!digest) { + qCWarning(lcTlsBackend) << "No digest for nid" << nid; + return false; + } + + OCSP_CERTID *recreatedId = q_OCSP_cert_to_id(digest, peerCert, issuer); + if (!recreatedId) { + qCWarning(lcTlsBackend, "Failed to re-create CertID"); + return false; + } + const QSharedPointer<OCSP_CERTID> guard(recreatedId, q_OCSP_CERTID_free); + + if (q_OCSP_id_cmp(const_cast<OCSP_CERTID *>(certId), recreatedId)) { + qCDebug(lcTlsBackend, "Certificate ID mismatch"); + return false; + } + // Bingo! + return true; +} + +} // unnamed namespace +#endif // ocsp + +TlsCryptographOpenSSL::~TlsCryptographOpenSSL() +{ + destroySslContext(); +} + +void TlsCryptographOpenSSL::init(QSslSocket *qObj, QSslSocketPrivate *dObj) +{ + Q_ASSERT(qObj); + Q_ASSERT(dObj); + q = qObj; + d = dObj; + + ocspResponses.clear(); + ocspResponseDer.clear(); + + systemOrSslErrorDetected = false; + handshakeInterrupted = false; + + fetchAuthorityInformation = false; + caToFetch.reset(); +} + +void TlsCryptographOpenSSL::checkSettingSslContext(std::shared_ptr<QSslContext> tlsContext) +{ + if (!sslContextPointer) + sslContextPointer = std::move(tlsContext); +} + +std::shared_ptr<QSslContext> TlsCryptographOpenSSL::sslContext() const +{ + return sslContextPointer; +} + +QList<QSslError> TlsCryptographOpenSSL::tlsErrors() const +{ + return sslErrors; +} + +void TlsCryptographOpenSSL::startClientEncryption() +{ + if (!initSslContext()) { + Q_ASSERT(d); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to init SSL Context: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } + + // Start connecting. This will place outgoing data in the BIO, so we + // follow up with calling transmit(). + startHandshake(); + transmit(); +} + +void TlsCryptographOpenSSL::startServerEncryption() +{ + if (!initSslContext()) { + Q_ASSERT(d); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to init SSL Context: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } + + // Start connecting. This will place outgoing data in the BIO, so we + // follow up with calling transmit(). + startHandshake(); + transmit(); +} + +bool TlsCryptographOpenSSL::startHandshake() +{ + // Check if the connection has been established. Get all errors from the + // verification stage. + Q_ASSERT(q); + Q_ASSERT(d); + + using ScopedBool = QScopedValueRollback<bool>; + + if (inSetAndEmitError) + return false; + + const auto mode = d->tlsMode(); + + pendingFatalAlert = false; + errorsReportedFromCallback = false; + QList<QSslErrorEntry> lastErrors; + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + errorOffsetInExData, &lastErrors); + + // SSL_set_ex_data can fail, but see the callback's code - we handle this there. + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + socketOffsetInExData, this); + q_SSL_set_info_callback(ssl, qt_AlertInfoCallback); + + int result = (mode == QSslSocket::SslClientMode) ? q_SSL_connect(ssl) : q_SSL_accept(ssl); + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + errorOffsetInExData, nullptr); + // Note, unlike errors as external data on SSL object, we do not unset + // a callback/ex-data if alert notifications are enabled: an alert can + // arrive after the handshake, for example, this happens when the server + // does not find a ClientCert or does not like it. + + if (!lastErrors.isEmpty() || errorsReportedFromCallback) + storePeerCertificates(); + + // storePeerCertificate() if called above - would update the + // configuration with peer's certificates. + auto configuration = q->sslConfiguration(); + if (!errorsReportedFromCallback) { + const auto &peerCertificateChain = configuration.peerCertificateChain(); + for (const auto ¤tError : std::as_const(lastErrors)) { + emit q->peerVerifyError(QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(currentError.code, + peerCertificateChain.value(currentError.depth))); + if (q->state() != QAbstractSocket::ConnectedState) + break; + } + } + + errorList << lastErrors; + + // Connection aborted during handshake phase. + if (q->state() != QAbstractSocket::ConnectedState) + return false; + + // Check if we're encrypted or not. + if (result <= 0) { + switch (q_SSL_get_error(ssl, result)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // The handshake is not yet complete. + break; + default: + QString errorString = QTlsBackendOpenSSL::msgErrorsDuringHandshake(); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::startHandshake: error!" << errorString; +#endif + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, errorString); + if (pendingFatalAlert) { + trySendFatalAlert(); + pendingFatalAlert = false; + } + } + q->abort(); + } + return false; + } + + // store peer certificate chain + storePeerCertificates(); + + // Start translating errors. + QList<QSslError> errors; + + // Note, the storePeerCerificates() probably updated the configuration at this point. + configuration = q->sslConfiguration(); + // Check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer) + const auto &peerCertificateChain = configuration.peerCertificateChain(); + for (const QSslCertificate &cert : peerCertificateChain) { + if (QSslCertificatePrivate::isBlacklisted(cert)) { + QSslError error(QSslError::CertificateBlacklisted, cert); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + const bool doVerifyPeer = configuration.peerVerifyMode() == QSslSocket::VerifyPeer + || (configuration.peerVerifyMode() == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + +#if QT_CONFIG(ocsp) + // For now it's always QSslSocket::SslClientMode - initSslContext() will bail out early, + // if it's enabled in QSslSocket::SslServerMode. This can change. + if (!configuration.peerCertificate().isNull() && configuration.ocspStaplingEnabled() && doVerifyPeer) { + if (!checkOcspStatus()) { + if (ocspErrors.isEmpty()) { + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, ocspErrorDescription); + } + q->abort(); + return false; + } + + for (const QSslError &error : ocspErrors) { + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } +#endif // ocsp + + // Check the peer certificate itself. First try the subject's common name + // (CN) as a wildcard, then try all alternate subject name DNS entries the + // same way. + if (!configuration.peerCertificate().isNull()) { + // but only if we're a client connecting to a server + // if we're the server, don't check CN + const auto verificationPeerName = d->verificationName(); + if (mode == QSslSocket::SslClientMode) { + QString peerName = (verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); + + if (!isMatchingHostname(configuration.peerCertificate(), peerName)) { + // No matches in common names or alternate names. + QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate()); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } else { + // No peer certificate presented. Report as error if the socket + // expected one. + if (doVerifyPeer) { + QSslError error(QSslError::NoPeerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + // Translate errors from the error list into QSslErrors. + errors.reserve(errors.size() + errorList.size()); + for (const auto &error : std::as_const(errorList)) + errors << X509CertificateOpenSSL::openSSLErrorToQSslError(error.code, peerCertificateChain.value(error.depth)); + + if (!errors.isEmpty()) { + sslErrors = errors; +#ifdef Q_OS_WIN + const bool fetchEnabled = QSslSocketPrivate::rootCertOnDemandLoadingSupported() + && d->isRootsOnDemandAllowed(); + // !fetchEnabled is a special case scenario, when we potentially have a missing + // intermediate certificate and a recoverable chain, but on demand cert loading + // was disabled by setCaCertificates call. For this scenario we check if "Authority + // Information Access" is present - wincrypt can deal with such certificates. + QSslCertificate certToFetch; + if (doVerifyPeer && !d->verifyErrorsHaveBeenIgnored()) + certToFetch = findCertificateToFetch(sslErrors, !fetchEnabled); + + //Skip this if not using system CAs, or if the SSL errors are configured in advance to be ignorable + if (!certToFetch.isNull()) { + fetchAuthorityInformation = !fetchEnabled; + //Windows desktop versions starting from vista ship with minimal set of roots and download on demand + //from the windows update server CA roots that are trusted by MS. It also can fetch a missing intermediate + //in case "Authority Information Access" extension is present. + // + //However, this is only transparent if using WinINET - we have to trigger it + //ourselves. + fetchCaRootForCert(certToFetch); + return false; + } +#endif // Q_OS_WIN + if (!checkSslErrors()) + return false; + // A slot, attached to sslErrors signal can call + // abort/close/disconnetFromHost/etc; no need to + // continue handshake then. + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } else { + sslErrors.clear(); + } + + continueHandshake(); + return true; +} + +void TlsCryptographOpenSSL::enableHandshakeContinuation() +{ + handshakeInterrupted = false; +} + +void TlsCryptographOpenSSL::cancelCAFetch() +{ + fetchAuthorityInformation = false; + caToFetch.reset(); +} + +void TlsCryptographOpenSSL::continueHandshake() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const auto mode = d->tlsMode(); + + // if we have a max read buffer size, reset the plain socket's to match + if (const auto maxSize = d->maxReadBufferSize()) + plainSocket->setReadBufferSize(maxSize); + + if (q_SSL_session_reused(ssl)) + QTlsBackend::setPeerSessionShared(d, true); + +#ifdef QT_DECRYPT_SSL_TRAFFIC + if (q_SSL_get_session(ssl)) { + size_t master_key_len = q_SSL_SESSION_get_master_key(q_SSL_get_session(ssl), nullptr, 0); + size_t client_random_len = q_SSL_get_client_random(ssl, nullptr, 0); + QByteArray masterKey(int(master_key_len), Qt::Uninitialized); // Will not overflow + QByteArray clientRandom(int(client_random_len), Qt::Uninitialized); // Will not overflow + + q_SSL_SESSION_get_master_key(q_SSL_get_session(ssl), + reinterpret_cast<unsigned char*>(masterKey.data()), + masterKey.size()); + q_SSL_get_client_random(ssl, reinterpret_cast<unsigned char *>(clientRandom.data()), + clientRandom.size()); + + QByteArray debugLineClientRandom("CLIENT_RANDOM "); + debugLineClientRandom.append(clientRandom.toHex().toUpper()); + debugLineClientRandom.append(" "); + debugLineClientRandom.append(masterKey.toHex().toUpper()); + debugLineClientRandom.append("\n"); + + QString sslKeyFile = QDir::tempPath() + "/qt-ssl-keys"_L1; + QFile file(sslKeyFile); + if (!file.open(QIODevice::Append)) + qCWarning(lcTlsBackend) << "could not open file" << sslKeyFile << "for appending"; + if (!file.write(debugLineClientRandom)) + qCWarning(lcTlsBackend) << "could not write to file" << sslKeyFile; + file.close(); + } else { + qCWarning(lcTlsBackend, "could not decrypt SSL traffic"); + } +#endif // QT_DECRYPT_SSL_TRAFFIC + + const auto &configuration = q->sslConfiguration(); + // Cache this SSL session inside the QSslContext + if (!(configuration.testSslOption(QSsl::SslOptionDisableSessionSharing))) { + if (!sslContextPointer->cacheSession(ssl)) { + sslContextPointer.reset(); // we could not cache the session + } else { + // Cache the session for permanent usage as well + if (!(configuration.testSslOption(QSsl::SslOptionDisableSessionPersistence))) { + if (!sslContextPointer->sessionASN1().isEmpty()) + QTlsBackend::setSessionAsn1(d, sslContextPointer->sessionASN1()); + QTlsBackend::setSessionLifetimeHint(d, sslContextPointer->sessionTicketLifeTimeHint()); + } + } + } + +#if !defined(OPENSSL_NO_NEXTPROTONEG) + + QTlsBackend::setAlpnStatus(d, sslContextPointer->npnContext().status); + if (sslContextPointer->npnContext().status == QSslConfiguration::NextProtocolNegotiationUnsupported) { + // we could not agree -> be conservative and use HTTP/1.1 + // T.P.: I have to admit, this is a really strange notion of 'conservative', + // given the protocol-neutral nature of ALPN/NPN. + QTlsBackend::setNegotiatedProtocol(d, QByteArrayLiteral("http/1.1")); + } else { + const unsigned char *proto = nullptr; + unsigned int proto_len = 0; + + q_SSL_get0_alpn_selected(ssl, &proto, &proto_len); + if (proto_len && mode == QSslSocket::SslClientMode) { + // Client does not have a callback that sets it ... + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNegotiated); + } + + if (!proto_len) { // Test if NPN was more lucky ... + q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len); + } + + if (proto_len) + QTlsBackend::setNegotiatedProtocol(d, QByteArray(reinterpret_cast<const char *>(proto), proto_len)); + else + QTlsBackend::setNegotiatedProtocol(d,{}); + } +#endif // !defined(OPENSSL_NO_NEXTPROTONEG) + + if (mode == QSslSocket::SslClientMode) { + EVP_PKEY *key; + if (q_SSL_get_server_tmp_key(ssl, &key)) + QTlsBackend::setEphemeralKey(d, QSslKey(key, QSsl::PublicKey)); + } + + d->setEncrypted(true); + emit q->encrypted(); + if (d->isAutoStartingHandshake() && d->isPendingClose()) { + d->setPendingClose(false); + q->disconnectFromHost(); + } +} + +void TlsCryptographOpenSSL::transmit() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + using ScopedBool = QScopedValueRollback<bool>; + + if (inSetAndEmitError) + return; + + // If we don't have any SSL context, don't bother transmitting. + if (!ssl) + return; + + auto &writeBuffer = d->tlsWriteBuffer(); + auto &buffer = d->tlsBuffer(); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + bool &emittedBytesWritten = d->tlsEmittedBytesWritten(); + + bool transmitting; + do { + transmitting = false; + + // If the connection is secure, we can transfer data from the write + // buffer (in plain text) to the write BIO through SSL_write. + if (q->isEncrypted() && !writeBuffer.isEmpty()) { + qint64 totalBytesWritten = 0; + int nextDataBlockSize; + while ((nextDataBlockSize = writeBuffer.nextDataBlockSize()) > 0) { + int writtenBytes = q_SSL_write(ssl, writeBuffer.readPointer(), nextDataBlockSize); + if (writtenBytes <= 0) { + int error = q_SSL_get_error(ssl, writtenBytes); + //write can result in a want_write_error - not an error - continue transmitting + if (error == SSL_ERROR_WANT_WRITE) { + transmitting = true; + break; + } else if (error == SSL_ERROR_WANT_READ) { + //write can result in a want_read error, possibly due to renegotiation - not an error - stop transmitting + transmitting = false; + break; + } else { + // ### Better error handling. + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to write data: %1").arg( + QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } + } +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encrypted" << writtenBytes << "bytes"; +#endif + writeBuffer.free(writtenBytes); + totalBytesWritten += writtenBytes; + + if (writtenBytes < nextDataBlockSize) { + // break out of the writing loop and try again after we had read + transmitting = true; + break; + } + } + + if (totalBytesWritten > 0) { + // Don't emit bytesWritten() recursively. + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(totalBytesWritten); + emittedBytesWritten = false; + } + emit q->channelBytesWritten(0, totalBytesWritten); + } + } + + // Check if we've got any data to be written to the socket. + QVarLengthArray<char, 4096> data; + int pendingBytes; + while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0 + && plainSocket->openMode() != QIODevice::NotOpen) { + // Read encrypted data from the write BIO into a buffer. + data.resize(pendingBytes); + int encryptedBytesRead = q_BIO_read(writeBio, data.data(), pendingBytes); + + // Write encrypted data from the buffer to the socket. + qint64 actualWritten = plainSocket->write(data.constData(), encryptedBytesRead); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: wrote" << encryptedBytesRead + << "encrypted bytes to the socket" << actualWritten << "actual."; +#endif + if (actualWritten < 0) { + //plain socket write fails if it was in the pending close state. + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, plainSocket->error(), plainSocket->errorString()); + return; + } + transmitting = true; + } + + // Check if we've got any data to be read from the socket. + if (!q->isEncrypted() || !d->maxReadBufferSize() || buffer.size() < d->maxReadBufferSize()) + while ((pendingBytes = plainSocket->bytesAvailable()) > 0) { + // Read encrypted data from the socket into a buffer. + data.resize(pendingBytes); + // just peek() here because q_BIO_write could write less data than expected + int encryptedBytesRead = plainSocket->peek(data.data(), pendingBytes); + +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: read" << encryptedBytesRead << "encrypted bytes from the socket"; +#endif + // Write encrypted data from the buffer into the read BIO. + int writtenToBio = q_BIO_write(readBio, data.constData(), encryptedBytesRead); + + // Throw away the results. + if (writtenToBio > 0) { + plainSocket->skip(writtenToBio); + } else { + // ### Better error handling. + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to decrypt data: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } + + transmitting = true; + } + + // If the connection isn't secured yet, this is the time to retry the + // connect / accept. + if (!q->isEncrypted()) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: testing encryption"; +#endif + if (startHandshake()) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encryption established"; +#endif + d->setEncrypted(true); + transmitting = true; + } else if (plainSocket->state() != QAbstractSocket::ConnectedState) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: connection lost"; +#endif + break; + } else if (d->isPaused()) { + // just wait until the user continues + return; + } else { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encryption not done yet"; +#endif + } + } + + // If the request is small and the remote host closes the transmission + // after sending, there's a chance that startHandshake() will already + // have triggered a shutdown. + if (!ssl) + continue; + + // We always read everything from the SSL decryption buffers, even if + // we have a readBufferMaxSize. There's no point in leaving data there + // just so that readBuffer.size() == readBufferMaxSize. + int readBytes = 0; + const int bytesToRead = 4096; + do { + if (q->readChannelCount() == 0) { + // The read buffer is deallocated, don't try resize or write to it. + break; + } + // Don't use SSL_pending(). It's very unreliable. + inSslRead = true; + readBytes = q_SSL_read(ssl, buffer.reserve(bytesToRead), bytesToRead); + inSslRead = false; + if (renegotiated) { + renegotiated = false; + X509 *x509 = q_SSL_get_peer_certificate(ssl); + const auto peerCertificate = + QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + // Fail the renegotiate if the certificate has changed, else: continue. + if (peerCertificate != q->peerCertificate()) { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit( + d, QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr( + "TLS certificate unexpectedly changed during renegotiation!")); + q->abort(); + return; + } + } + if (readBytes > 0) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: decrypted" << readBytes << "bytes"; +#endif + buffer.chop(bytesToRead - readBytes); + + if (bool *readyReadEmittedPointer = d->readyReadPointer()) + *readyReadEmittedPointer = true; + emit q->readyRead(); + emit q->channelReadyRead(0); + transmitting = true; + continue; + } + buffer.chop(bytesToRead); + + // Error. + switch (q_SSL_get_error(ssl, readBytes)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // Out of data. + break; + case SSL_ERROR_ZERO_RETURN: + // The remote host closed the connection. +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: remote disconnect"; +#endif + shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); + } + return; + case SSL_ERROR_SYSCALL: // some IO error + case SSL_ERROR_SSL: // error in the SSL library + // we do not know exactly what the error is, nor whether we can recover from it, + // so just return to prevent an endless loop in the outer "while" statement + systemOrSslErrorDetected = true; + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error while reading: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + } + return; + default: + // SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: can only happen with a + // BIO_s_connect() or BIO_s_accept(), which we do not call. + // SSL_ERROR_WANT_X509_LOOKUP: can only happen with a + // SSL_CTX_set_client_cert_cb(), which we do not call. + // So this default case should never be triggered. + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error while reading: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + } + break; + } + } while (ssl && readBytes > 0); + } while (ssl && transmitting); +} + +void TlsCryptographOpenSSL::disconnectFromHost() +{ + if (ssl) { + if (!shutdown && !q_SSL_in_init(ssl) && !systemOrSslErrorDetected) { + if (q_SSL_shutdown(ssl) != 1) { + // Some error may be queued, clear it. + QTlsBackendOpenSSL::clearErrorQueue(); + } + shutdown = true; + transmit(); + } + } + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + plainSocket->disconnectFromHost(); +} + +void TlsCryptographOpenSSL::disconnected() +{ + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + d->setEncrypted(false); + + if (plainSocket->bytesAvailable() <= 0) { + destroySslContext(); + } else { + // Move all bytes into the plain buffer. + const qint64 tmpReadBufferMaxSize = d->maxReadBufferSize(); + // Reset temporarily, so the plain socket buffer is completely drained: + d->setMaxReadBufferSize(0); + transmit(); + d->setMaxReadBufferSize(tmpReadBufferMaxSize); + } + //if there is still buffered data in the plain socket, don't destroy the ssl context yet. + //it will be destroyed when the socket is deleted. +} + +QSslCipher TlsCryptographOpenSSL::sessionCipher() const +{ + if (!ssl) + return {}; + + const SSL_CIPHER *sessionCipher = q_SSL_get_current_cipher(ssl); + return sessionCipher ? QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(sessionCipher) : QSslCipher{}; +} + +QSsl::SslProtocol TlsCryptographOpenSSL::sessionProtocol() const +{ + if (!ssl) + return QSsl::UnknownProtocol; + + const int ver = q_SSL_version(ssl); + switch (ver) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + case 0x301: + return QSsl::TlsV1_0; + case 0x302: + return QSsl::TlsV1_1; +QT_WARNING_POP + case 0x303: + return QSsl::TlsV1_2; + case 0x304: + return QSsl::TlsV1_3; + } + + return QSsl::UnknownProtocol; +} + +QList<QOcspResponse> TlsCryptographOpenSSL::ocsps() const +{ + return ocspResponses; +} + +bool TlsCryptographOpenSSL::checkSslErrors() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + if (sslErrors.isEmpty()) + return true; + + emit q->sslErrors(sslErrors); + + const auto vfyMode = q->peerVerifyMode(); + const auto mode = d->tlsMode(); + + bool doVerifyPeer = vfyMode == QSslSocket::VerifyPeer || (vfyMode == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); + // check whether we need to emit an SSL handshake error + if (doVerifyPeer && doEmitSslError) { + if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); + } else { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString()); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + plainSocket->disconnectFromHost(); + } + return false; + } + return true; +} + +int TlsCryptographOpenSSL::handleNewSessionTicket(SSL *connection) +{ + // If we return 1, this means we own the session, but we don't. + // 0 would tell OpenSSL to deref (but they still have it in the + // internal cache). + Q_ASSERT(connection); + + Q_ASSERT(q); + Q_ASSERT(d); + + if (q->sslConfiguration().testSslOption(QSsl::SslOptionDisableSessionPersistence)) { + // We silently ignore, do nothing, remove from cache. + return 0; + } + + SSL_SESSION *currentSession = q_SSL_get_session(connection); + if (!currentSession) { + qCWarning(lcTlsBackend, + "New session ticket callback, the session is invalid (nullptr)"); + return 0; + } + + if (q_SSL_version(connection) < 0x304) { + // We only rely on this mechanics with TLS >= 1.3 + return 0; + } + +#ifdef TLS1_3_VERSION + if (!q_SSL_SESSION_is_resumable(currentSession)) { + qCDebug(lcTlsBackend, "New session ticket, but the session is non-resumable"); + return 0; + } +#endif // TLS1_3_VERSION + + const int sessionSize = q_i2d_SSL_SESSION(currentSession, nullptr); + if (sessionSize <= 0) { + qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); + return 0; + } + + // We have somewhat perverse naming, it's not a ticket, it's a session. + QByteArray sessionTicket(sessionSize, 0); + auto data = reinterpret_cast<unsigned char *>(sessionTicket.data()); + if (!q_i2d_SSL_SESSION(currentSession, &data)) { + qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); + return 0; + } + + QTlsBackend::setSessionAsn1(d, sessionTicket); + QTlsBackend::setSessionLifetimeHint(d, q_SSL_SESSION_get_ticket_lifetime_hint(currentSession)); + + emit q->newSessionTicketReceived(); + return 0; +} + +void TlsCryptographOpenSSL::alertMessageSent(int value) +{ + Q_ASSERT(q); + Q_ASSERT(d); + + const auto level = tlsAlertLevel(value); + if (level == QSsl::AlertLevel::Fatal && !q->isEncrypted()) { + // Note, this logic is handshake-time only: + pendingFatalAlert = true; + } + + emit q->alertSent(level, tlsAlertType(value), tlsAlertDescription(value)); + +} + +void TlsCryptographOpenSSL::alertMessageReceived(int value) +{ + Q_ASSERT(q); + + emit q->alertReceived(tlsAlertLevel(value), tlsAlertType(value), tlsAlertDescription(value)); +} + +int TlsCryptographOpenSSL::emitErrorFromCallback(X509_STORE_CTX *ctx) +{ + // Returns 0 to abort verification, 1 to continue despite error (as + // OpenSSL expects from the verification callback). + Q_ASSERT(q); + Q_ASSERT(ctx); + + using ScopedBool = QScopedValueRollback<bool>; + // While we are not setting, we are emitting and in general - + // we want to prevent accidental recursive startHandshake() + // calls: + const ScopedBool bg(inSetAndEmitError, true); + + X509 *x509 = q_X509_STORE_CTX_get_current_cert(ctx); + if (!x509) { + qCWarning(lcTlsBackend, "Could not obtain the certificate (that failed to verify)"); + return 0; + } + + const QSslCertificate certificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + const auto errorAndDepth = QTlsPrivate::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx); + const QSslError tlsError = QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(errorAndDepth.code, certificate); + + errorsReportedFromCallback = true; + handshakeInterrupted = true; + emit q->handshakeInterruptedOnError(tlsError); + + // Conveniently so, we also can access 'lastErrors' external data set + // in startHandshake, we store it for the case an application later + // wants to check errors (ignored or not): + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::errorOffsetInExData; + if (auto errorList = static_cast<QList<QSslErrorEntry> *>(q_SSL_get_ex_data(ssl, offset))) + errorList->append(errorAndDepth); + + // An application is expected to ignore this error (by calling ignoreSslErrors) + // in its directly connected slot: + return !handshakeInterrupted; +} + +void TlsCryptographOpenSSL::trySendFatalAlert() +{ + Q_ASSERT(pendingFatalAlert); + Q_ASSERT(d); + + auto *plainSocket = d->plainTcpSocket(); + + pendingFatalAlert = false; + QVarLengthArray<char, 4096> data; + int pendingBytes = 0; + while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0 + && plainSocket->openMode() != QIODevice::NotOpen) { + // Read encrypted data from the write BIO into a buffer. + data.resize(pendingBytes); + const int bioReadBytes = q_BIO_read(writeBio, data.data(), pendingBytes); + + // Write encrypted data from the buffer to the socket. + qint64 actualWritten = plainSocket->write(data.constData(), bioReadBytes); + if (actualWritten < 0) + return; + plainSocket->flush(); + } +} + +bool TlsCryptographOpenSSL::initSslContext() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + // If no external context was set (e.g. by QHttpNetworkConnection) we will + // create a new one. + const auto mode = d->tlsMode(); + const auto configuration = q->sslConfiguration(); + if (!sslContextPointer) + sslContextPointer = QSslContext::sharedFromConfiguration(mode, configuration, d->isRootsOnDemandAllowed()); + + if (sslContextPointer->error() != QSslError::NoError) { + setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError, sslContextPointer->errorString()); + sslContextPointer.reset(); + return false; + } + + // Create and initialize SSL session + if (!(ssl = sslContextPointer->createSsl())) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error creating SSL session, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return false; + } + + if (configuration.protocol() != QSsl::UnknownProtocol && mode == QSslSocket::SslClientMode) { + const auto verificationPeerName = d->verificationName(); + // Set server hostname on TLS extension. RFC4366 section 3.1 requires it in ACE format. + QString tlsHostName = verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName; + if (tlsHostName.isEmpty()) + tlsHostName = d->tlsHostName(); + QByteArray ace = QUrl::toAce(tlsHostName); + // only send the SNI header if the URL is valid and not an IP + if (!ace.isEmpty() + && !QHostAddress().setAddress(tlsHostName) + && !(configuration.testSslOption(QSsl::SslOptionDisableServerNameIndication))) { + // We don't send the trailing dot from the host header if present see + // https://tools.ietf.org/html/rfc6066#section-3 + if (ace.endsWith('.')) + ace.chop(1); + if (!q_SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, ace.data())) + qCWarning(lcTlsBackend, "could not set SSL_CTRL_SET_TLSEXT_HOSTNAME, Server Name Indication disabled"); + } + } + + // Clear the session. + errorList.clear(); + + // Initialize memory BIOs for encryption and decryption. + readBio = q_BIO_new(q_BIO_s_mem()); + writeBio = q_BIO_new(q_BIO_s_mem()); + if (!readBio || !writeBio) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error creating SSL session: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + if (readBio) + q_BIO_free(readBio); + if (writeBio) + q_BIO_free(writeBio); + return false; + } + + // Assign the bios. + q_SSL_set_bio(ssl, readBio, writeBio); + + if (mode == QSslSocket::SslClientMode) + q_SSL_set_connect_state(ssl); + else + q_SSL_set_accept_state(ssl); + + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData, this); + +#ifndef OPENSSL_NO_PSK + // Set the client callback for PSK + if (mode == QSslSocket::SslClientMode) + q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); + else if (mode == QSslSocket::SslServerMode) + q_SSL_set_psk_server_callback(ssl, &q_ssl_psk_server_callback); + +#if OPENSSL_VERSION_NUMBER >= 0x10101006L + // Set the client callback for TLSv1.3 PSK + if (mode == QSslSocket::SslClientMode + && QSslSocket::sslLibraryBuildVersionNumber() >= 0x10101006L) { + q_SSL_set_psk_use_session_callback(ssl, &q_ssl_psk_use_session_callback); + } +#endif // openssl version >= 0x10101006L + +#endif // OPENSSL_NO_PSK + +#if QT_CONFIG(ocsp) + if (configuration.ocspStaplingEnabled()) { + if (mode == QSslSocket::SslServerMode) { + setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Server-side QSslSocket does not support OCSP stapling")); + return false; + } + if (q_SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp) != 1) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Failed to enable OCSP stapling")); + return false; + } + } + + ocspResponseDer.clear(); + const auto backendConfig = configuration.backendConfiguration(); + auto responsePos = backendConfig.find("Qt-OCSP-response"); + if (responsePos != backendConfig.end()) { + // This is our private, undocumented 'API' we use for the auto-testing of + // OCSP-stapling. It must be a der-encoded OCSP response, presumably set + // by tst_QOcsp. + const QVariant data(responsePos.value()); + if (data.canConvert<QByteArray>()) + ocspResponseDer = data.toByteArray(); + } + + if (ocspResponseDer.size()) { + if (mode != QSslSocket::SslServerMode) { + setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Client-side sockets do not send OCSP responses")); + return false; + } + } +#endif // ocsp + + return true; +} + +void TlsCryptographOpenSSL::destroySslContext() +{ + if (ssl) { + if (!q_SSL_in_init(ssl) && !systemOrSslErrorDetected) { + // We do not send a shutdown alert here. Just mark the session as + // resumable for qhttpnetworkconnection's "optimization", otherwise + // OpenSSL won't start a session resumption. + if (q_SSL_shutdown(ssl) != 1) { + // Some error may be queued, clear it. + const auto errors = QTlsBackendOpenSSL::getErrorsFromOpenSsl(); + Q_UNUSED(errors); + } + } + q_SSL_free(ssl); + ssl = nullptr; + } + sslContextPointer.reset(); +} + +void TlsCryptographOpenSSL::storePeerCertificates() +{ + Q_ASSERT(d); + + // Store the peer certificate and chain. For clients, the peer certificate + // chain includes the peer certificate; for servers, it doesn't. Both the + // peer certificate and the chain may be empty if the peer didn't present + // any certificate. + X509 *x509 = q_SSL_get_peer_certificate(ssl); + + const auto peerCertificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + QTlsBackend::storePeerCertificate(d, peerCertificate); + q_X509_free(x509); + auto peerCertificateChain = q->peerCertificateChain(); + if (peerCertificateChain.isEmpty()) { + peerCertificateChain = QTlsPrivate::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(q_SSL_get_peer_cert_chain(ssl)); + if (!peerCertificate.isNull() && d->tlsMode() == QSslSocket::SslServerMode) + peerCertificateChain.prepend(peerCertificate); + QTlsBackend::storePeerCertificateChain(d, peerCertificateChain); + } +} + +#if QT_CONFIG(ocsp) + +bool TlsCryptographOpenSSL::checkOcspStatus() +{ + Q_ASSERT(ssl); + Q_ASSERT(d); + + const auto &configuration = q->sslConfiguration(); + Q_ASSERT(d->tlsMode() == QSslSocket::SslClientMode); // See initSslContext() for SslServerMode + Q_ASSERT(configuration.peerVerifyMode() != QSslSocket::VerifyNone); + + const auto clearErrorQueue = qScopeGuard([] { + QTlsBackendOpenSSL::logAndClearErrorQueue(); + }); + + ocspResponses.clear(); + ocspErrorDescription.clear(); + ocspErrors.clear(); + + const unsigned char *responseData = nullptr; + const long responseLength = q_SSL_get_tlsext_status_ocsp_resp(ssl, &responseData); + if (responseLength <= 0 || !responseData) { + ocspErrors.push_back(QSslError(QSslError::OcspNoResponseFound)); + return false; + } + + OCSP_RESPONSE *response = q_d2i_OCSP_RESPONSE(nullptr, &responseData, responseLength); + if (!response) { + // Treat this as a fatal SslHandshakeError. + ocspErrorDescription = QSslSocket::tr("Failed to decode OCSP response"); + return false; + } + const QSharedPointer<OCSP_RESPONSE> responseGuard(response, q_OCSP_RESPONSE_free); + + const int ocspStatus = q_OCSP_response_status(response); + if (ocspStatus != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + // It's not a definitive response, it's an error message (not signed by the responder). + ocspErrors.push_back(QSslError(qt_OCSP_response_status_to_SslError(ocspStatus))); + return false; + } + + OCSP_BASICRESP *basicResponse = q_OCSP_response_get1_basic(response); + if (!basicResponse) { + // SslHandshakeError. + ocspErrorDescription = QSslSocket::tr("Failed to extract basic OCSP response"); + return false; + } + const QSharedPointer<OCSP_BASICRESP> basicResponseGuard(basicResponse, q_OCSP_BASICRESP_free); + + SSL_CTX *ctx = q_SSL_get_SSL_CTX(ssl); // Does not increment refcount. + Q_ASSERT(ctx); + X509_STORE *store = q_SSL_CTX_get_cert_store(ctx); // Does not increment refcount. + if (!store) { + // SslHandshakeError. + ocspErrorDescription = QSslSocket::tr("No certificate verification store, cannot verify OCSP response"); + return false; + } + + STACK_OF(X509) *peerChain = q_SSL_get_peer_cert_chain(ssl); // Does not increment refcount. + X509 *peerX509 = q_SSL_get_peer_certificate(ssl); + Q_ASSERT(peerChain || peerX509); + const QSharedPointer<X509> peerX509Guard(peerX509, q_X509_free); + // OCSP_basic_verify with 0 as verificationFlags: + // + // 0) Tries to find the OCSP responder's certificate in either peerChain + // or basicResponse->certs. If not found, verification fails. + // 1) It checks the signature using the responder's public key. + // 2) Then it tries to validate the responder's cert (building a chain + // etc.) + // 3) It checks CertID in response. + // 4) Ensures the responder is authorized to sign the status respond. + // + // Note, OpenSSL prior to 1.0.2b would only use bs->certs to + // verify the responder's chain (see their commit 4ba9a4265bd). + // Working this around - is too much fuss for ancient versions we + // are dropping quite soon anyway. + const unsigned long verificationFlags = 0; + const int success = q_OCSP_basic_verify(basicResponse, peerChain, store, verificationFlags); + if (success <= 0) + ocspErrors.push_back(QSslError(QSslError::OcspResponseCannotBeTrusted)); + + if (q_OCSP_resp_count(basicResponse) != 1) { + ocspErrors.push_back(QSslError(QSslError::OcspMalformedResponse)); + return false; + } + + OCSP_SINGLERESP *singleResponse = q_OCSP_resp_get0(basicResponse, 0); + if (!singleResponse) { + ocspErrors.clear(); + // A fatal problem -> SslHandshakeError. + ocspErrorDescription = QSslSocket::tr("Failed to decode a SingleResponse from OCSP status response"); + return false; + } + + // Let's make sure the response is for the correct certificate - we + // can re-create this CertID using our peer's certificate and its + // issuer's public key. + ocspResponses.push_back(QOcspResponse()); + QOcspResponsePrivate *dResponse = ocspResponses.back().d.data(); + dResponse->subjectCert = configuration.peerCertificate(); + bool matchFound = false; + if (dResponse->subjectCert.isSelfSigned()) { + dResponse->signerCert = configuration.peerCertificate(); + matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, peerX509); + } else { + const STACK_OF(X509) *certs = q_SSL_get_peer_cert_chain(ssl); + if (!certs) // Oh, what a cataclysm! Last try: + certs = q_OCSP_resp_get0_certs(basicResponse); + if (certs) { + // It could be the first certificate in 'certs' is our peer's + // certificate. Since it was not captured by the 'self-signed' branch + // above, the CertID will not match and we'll just iterate on to the + // next certificate. So we start from 0, not 1. + for (int i = 0, e = q_sk_X509_num(certs); i < e; ++i) { + X509 *issuer = q_sk_X509_value(certs, i); + matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, issuer); + if (matchFound) { + if (q_X509_check_issued(issuer, peerX509) == X509_V_OK) { + dResponse->signerCert = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(issuer); + break; + } + matchFound = false; + } + } + } + } + + if (!matchFound) { + dResponse->signerCert.clear(); + ocspErrors.push_back({QSslError::OcspResponseCertIdUnknown, configuration.peerCertificate()}); + } + + // Check if the response is valid time-wise: + ASN1_GENERALIZEDTIME *revTime = nullptr; + ASN1_GENERALIZEDTIME *thisUpdate = nullptr; + ASN1_GENERALIZEDTIME *nextUpdate = nullptr; + int reason; + const int certStatus = q_OCSP_single_get0_status(singleResponse, &reason, &revTime, &thisUpdate, &nextUpdate); + if (!thisUpdate) { + // This is unexpected, treat as SslHandshakeError, OCSP_check_validity assumes this pointer + // to be != nullptr. + ocspErrors.clear(); + ocspResponses.clear(); + ocspErrorDescription = QSslSocket::tr("Failed to extract 'this update time' from the SingleResponse"); + return false; + } + + // OCSP_check_validity(this, next, nsec, maxsec) does this check: + // this <= now <= next. They allow some freedom to account + // for delays/time inaccuracy. + // this > now + nsec ? -> NOT_YET_VALID + // if maxsec >= 0: + // now - maxsec > this ? -> TOO_OLD + // now - nsec > next ? -> EXPIRED + // next < this ? -> NEXT_BEFORE_THIS + // OK. + if (!q_OCSP_check_validity(thisUpdate, nextUpdate, 60, -1)) + ocspErrors.push_back({QSslError::OcspResponseExpired, configuration.peerCertificate()}); + + // And finally, the status: + switch (certStatus) { + case V_OCSP_CERTSTATUS_GOOD: + // This certificate was not found among the revoked ones. + dResponse->certificateStatus = QOcspCertificateStatus::Good; + break; + case V_OCSP_CERTSTATUS_REVOKED: + dResponse->certificateStatus = QOcspCertificateStatus::Revoked; + dResponse->revocationReason = qt_OCSP_revocation_reason(reason); + ocspErrors.push_back({QSslError::CertificateRevoked, configuration.peerCertificate()}); + break; + case V_OCSP_CERTSTATUS_UNKNOWN: + dResponse->certificateStatus = QOcspCertificateStatus::Unknown; + ocspErrors.push_back({QSslError::OcspStatusUnknown, configuration.peerCertificate()}); + } + + return !ocspErrors.size(); +} + +#endif // QT_CONFIG(ocsp) + + +unsigned TlsCryptographOpenSSL::pskClientTlsCallback(const char *hint, char *identity, + unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len) +{ + Q_ASSERT(q); + + QSslPreSharedKeyAuthenticator authenticator; + // Fill in some read-only fields (for the user) + const int hintLength = hint ? int(std::strlen(hint)) : 0; + QTlsBackend::setupClientPskAuth(&authenticator, hint, hintLength, max_identity_len, max_psk_len); + // Let the client provide the remaining bits... + emit q->preSharedKeyAuthenticationRequired(&authenticator); + + // No PSK set? Return now to make the handshake fail + if (authenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int identityLength = qMin(authenticator.identity().size(), authenticator.maximumIdentityLength()); + std::memcpy(identity, authenticator.identity().constData(), identityLength); + identity[identityLength] = 0; + + const int pskLength = qMin(authenticator.preSharedKey().size(), authenticator.maximumPreSharedKeyLength()); + std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); + return pskLength; +} + +unsigned TlsCryptographOpenSSL::pskServerTlsCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len) +{ + Q_ASSERT(q); + + QSslPreSharedKeyAuthenticator authenticator; + + // Fill in some read-only fields (for the user) + QTlsBackend::setupServerPskAuth(&authenticator, identity, q->sslConfiguration().preSharedKeyIdentityHint(), + max_psk_len); + emit q->preSharedKeyAuthenticationRequired(&authenticator); + + // No PSK set? Return now to make the handshake fail + if (authenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int pskLength = qMin(authenticator.preSharedKey().size(), authenticator.maximumPreSharedKeyLength()); + std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); + return pskLength; +} + +bool TlsCryptographOpenSSL::isInSslRead() const +{ + return inSslRead; +} + +void TlsCryptographOpenSSL::setRenegotiated(bool renegotiated) +{ + this->renegotiated = renegotiated; +} + +#ifdef Q_OS_WIN + +void TlsCryptographOpenSSL::fetchCaRootForCert(const QSslCertificate &cert) +{ + Q_ASSERT(d); + Q_ASSERT(q); + + //The root certificate is downloaded from windows update, which blocks for 15 seconds in the worst case + //so the request is done in a worker thread. + QList<QSslCertificate> customRoots; + if (fetchAuthorityInformation) + customRoots = q->sslConfiguration().caCertificates(); + + //Remember we are fetching and what we are fetching: + caToFetch = cert; + + QWindowsCaRootFetcher *fetcher = new QWindowsCaRootFetcher(cert, d->tlsMode(), customRoots, + q->peerVerifyName()); + connect(fetcher, &QWindowsCaRootFetcher::finished, this, &TlsCryptographOpenSSL::caRootLoaded, + Qt::QueuedConnection); + QMetaObject::invokeMethod(fetcher, "start", Qt::QueuedConnection); + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); +} + +void TlsCryptographOpenSSL::caRootLoaded(QSslCertificate cert, QSslCertificate trustedRoot) +{ + if (caToFetch != cert) { + //Ooops, something from the previous connection attempt, ignore! + return; + } + + Q_ASSERT(d); + Q_ASSERT(q); + + //Done, fetched already: + caToFetch.reset(); + + if (fetchAuthorityInformation) { + if (!q->sslConfiguration().caCertificates().contains(trustedRoot)) + trustedRoot = QSslCertificate{}; + fetchAuthorityInformation = false; + } + + if (!trustedRoot.isNull() && !trustedRoot.isBlacklisted()) { + if (QSslSocketPrivate::rootCertOnDemandLoadingSupported()) { + //Add the new root cert to default cert list for use by future sockets + auto defaultConfig = QSslConfiguration::defaultConfiguration(); + defaultConfig.addCaCertificate(trustedRoot); + QSslConfiguration::setDefaultConfiguration(defaultConfig); + } + //Add the new root cert to this socket for future connections + QTlsBackend::addTustedRoot(d, trustedRoot); + //Remove the broken chain ssl errors (as chain is verified by windows) + for (int i=sslErrors.count() - 1; i >= 0; --i) { + if (sslErrors.at(i).certificate() == cert) { + switch (sslErrors.at(i).error()) { + case QSslError::UnableToGetLocalIssuerCertificate: + case QSslError::CertificateUntrusted: + case QSslError::UnableToVerifyFirstCertificate: + case QSslError::SelfSignedCertificateInChain: + // error can be ignored if OS says the chain is trusted + sslErrors.removeAt(i); + break; + default: + // error cannot be ignored + break; + } + } + } + } + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + // Continue with remaining errors + if (plainSocket) + plainSocket->resume(); + d->setPaused(false); + if (checkSslErrors() && ssl) { + bool willClose = (d->isAutoStartingHandshake() && d->isPendingClose()); + continueHandshake(); + if (!willClose) + transmit(); + } +} + +#endif // Q_OS_WIN + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qtls_openssl_p.h b/src/plugins/tls/openssl/qtls_openssl_p.h new file mode 100644 index 0000000000..65d21a395b --- /dev/null +++ b/src/plugins/tls/openssl/qtls_openssl_p.h @@ -0,0 +1,140 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTLS_OPENSSL_P_H +#define QTLS_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include "qtlsbackend_openssl_p.h" +#include "qsslcontext_openssl_p.h" +#include "qopenssl_p.h" + +#include <QtNetwork/qsslcertificate.h> +#include <QtNetwork/qocspresponse.h> + +#include <QtCore/qsharedpointer.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class TlsCryptographOpenSSL : public TlsCryptograph +{ +public: + enum ExDataOffset { + errorOffsetInExData = 1, + socketOffsetInExData = 2 + }; + + ~TlsCryptographOpenSSL(); + + void init(QSslSocket *qObj, QSslSocketPrivate *dObj) override; + void checkSettingSslContext(std::shared_ptr<QSslContext> tlsContext) override; + std::shared_ptr<QSslContext> sslContext() const override; + + QList<QSslError> tlsErrors() const override; + + void startClientEncryption() override; + void startServerEncryption() override; + bool startHandshake(); + void enableHandshakeContinuation() override; + void cancelCAFetch() override; + void continueHandshake() override; + void transmit() override; + void disconnectFromHost() override; + void disconnected() override; + QSslCipher sessionCipher() const override; + QSsl::SslProtocol sessionProtocol() const override; + QList<QOcspResponse> ocsps() const override; + + bool checkSslErrors(); + int handleNewSessionTicket(SSL *connection); + + void alertMessageSent(int encoded); + void alertMessageReceived(int encoded); + + int emitErrorFromCallback(X509_STORE_CTX *ctx); + void trySendFatalAlert(); + +#if QT_CONFIG(ocsp) + bool checkOcspStatus(); +#endif + + QSslSocket *q = nullptr; + QSslSocketPrivate *d = nullptr; + + void storePeerCertificates(); + + unsigned pskClientTlsCallback(const char *hint, char *identity, unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len); + unsigned pskServerTlsCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len); + + bool isInSslRead() const; + void setRenegotiated(bool renegotiated); + +#ifdef Q_OS_WIN + void fetchCaRootForCert(const QSslCertificate &cert); + void caRootLoaded(QSslCertificate certificate, QSslCertificate trustedRoot); +#endif + + QByteArray ocspResponseDer; +private: + // TLSTODO: names were preserved, to make comparison + // easier (see qsslsocket_openssl.cpp, while it exists). + bool initSslContext(); + void destroySslContext(); + + std::shared_ptr<QSslContext> sslContextPointer; + SSL *ssl = nullptr; // TLSTODO: RAII. + + QList<QSslErrorEntry> errorList; + QList<QSslError> sslErrors; + + BIO *readBio = nullptr; + BIO *writeBio = nullptr; + + QList<QOcspResponse> ocspResponses; + + // This description will go to setErrorAndEmit(SslHandshakeError, ocspErrorDescription) + QString ocspErrorDescription; + // These will go to sslErrors() + QList<QSslError> ocspErrors; + + bool systemOrSslErrorDetected = false; + bool handshakeInterrupted = false; + + bool fetchAuthorityInformation = false; + std::optional<QSslCertificate> caToFetch; + + bool inSetAndEmitError = false; + bool pendingFatalAlert = false; + bool errorsReportedFromCallback = false; + + bool shutdown = false; + + bool inSslRead = false; + bool renegotiated = false; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLS_OPENSSL_P_H + diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp new file mode 100644 index 0000000000..d73515724b --- /dev/null +++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp @@ -0,0 +1,611 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsslsocket_openssl_symbols_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qtlskey_openssl_p.h" +#include "qx509_openssl_p.h" +#include "qtls_openssl_p.h" + +#if QT_CONFIG(dtls) +#include "qdtls_openssl_p.h" +#endif // QT_CONFIG(dtls) + +#include <QtNetwork/private/qsslcipher_p.h> + +#include <QtNetwork/qsslcipher.h> +#include <QtNetwork/qssl.h> + +#include <QtCore/qdir.h> +#include <QtCore/qdirlisting.h> +#include <QtCore/qlist.h> +#include <QtCore/qmutex.h> +#include <QtCore/qscopeguard.h> +#include <QtCore/qset.h> + +#include "qopenssl_p.h" + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) +constexpr auto DefaultWarningLevel = QtCriticalMsg; +#else +constexpr auto DefaultWarningLevel = QtDebugMsg; +#endif + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl", DefaultWarningLevel); + +static void q_loadCiphersForConnection(SSL *connection, QList<QSslCipher> &ciphers, + QList<QSslCipher> &defaultCiphers) +{ + Q_ASSERT(connection); + + STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(connection); + for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) { + if (SSL_CIPHER *cipher = q_sk_SSL_CIPHER_value(supportedCiphers, i)) { + const auto ciph = QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(cipher); + if (!ciph.isNull()) { + // Unconditionally exclude ADH and AECDH ciphers since they offer no MITM protection + if (!ciph.name().toLower().startsWith("adh"_L1) && + !ciph.name().toLower().startsWith("exp-adh"_L1) && + !ciph.name().toLower().startsWith("aecdh"_L1)) { + ciphers << ciph; + + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; + } + } + } + } +} + +int QTlsBackendOpenSSL::s_indexForSSLExtraData = -1; + +QString QTlsBackendOpenSSL::getErrorsFromOpenSsl() +{ + QString errorString; + char buf[256] = {}; // OpenSSL docs claim both 120 and 256; use the larger. + unsigned long errNum; + while ((errNum = q_ERR_get_error())) { + if (!errorString.isEmpty()) + errorString.append(", "_L1); + q_ERR_error_string_n(errNum, buf, sizeof buf); + errorString.append(QLatin1StringView(buf)); // error is ascii according to man ERR_error_string + } + return errorString; +} + +void QTlsBackendOpenSSL::logAndClearErrorQueue() +{ + const auto errors = getErrorsFromOpenSsl(); + if (errors.size()) + qCWarning(lcTlsBackend) << "Discarding errors:" << errors; +} + +void QTlsBackendOpenSSL::clearErrorQueue() +{ + while (q_ERR_get_error()) + ; +} + +bool QTlsBackendOpenSSL::ensureLibraryLoaded() +{ + static bool libraryLoaded = []() { + if (!q_resolveOpenSslSymbols()) + return false; + + // Initialize OpenSSL. + if (q_OPENSSL_init_ssl(0, nullptr) != 1) + return false; + + if (q_OpenSSL_version_num() < 0x10101000L) { + qCWarning(lcTlsBackend, "QSslSocket: OpenSSL >= 1.1.1 is required; %s was found instead", q_OpenSSL_version(OPENSSL_VERSION)); + return false; + } + + q_SSL_load_error_strings(); + q_OpenSSL_add_all_algorithms(); + + s_indexForSSLExtraData = q_CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, 0L, nullptr, nullptr, + nullptr, nullptr); + + // Initialize OpenSSL's random seed. + if (!q_RAND_status()) { + qWarning("Random number generator not seeded, disabling SSL support"); + return false; + } + + return true; + }(); + + return libraryLoaded; +} + +QString QTlsBackendOpenSSL::backendName() const +{ + return builtinBackendNames[nameIndexOpenSSL]; +} + +bool QTlsBackendOpenSSL::isValid() const +{ + return ensureLibraryLoaded(); +} + +long QTlsBackendOpenSSL::tlsLibraryVersionNumber() const +{ + return q_OpenSSL_version_num(); +} + +QString QTlsBackendOpenSSL::tlsLibraryVersionString() const +{ + const char *versionString = q_OpenSSL_version(OPENSSL_VERSION); + if (!versionString) + return QString(); + + return QString::fromLatin1(versionString); +} + +long QTlsBackendOpenSSL::tlsLibraryBuildVersionNumber() const +{ + return OPENSSL_VERSION_NUMBER; +} + +QString QTlsBackendOpenSSL::tlsLibraryBuildVersionString() const +{ + // Using QStringLiteral to store the version string as unicode and + // avoid false positives from Google searching the playstore for old + // SSL versions. See QTBUG-46265 + return QStringLiteral(OPENSSL_VERSION_TEXT); +} + +void QTlsBackendOpenSSL::ensureInitialized() const +{ + // Old qsslsocket_openssl calls supportsSsl() (which means + // library found and symbols resolved, this already assured + // by the fact we end up in this function (isValid() returned + // true for the backend, see its code). The qsslsocket_openssl + // proceedes with loading certificate, ciphers and elliptic + // curves. + ensureCiphersAndCertsLoaded(); +} + +void QTlsBackendOpenSSL::ensureCiphersAndCertsLoaded() const +{ + Q_CONSTINIT static bool initializationStarted = false; + Q_CONSTINIT static QAtomicInt initialized = Q_BASIC_ATOMIC_INITIALIZER(0); + Q_CONSTINIT static QRecursiveMutex initMutex; + + if (initialized.loadAcquire()) + return; + + const QMutexLocker locker(&initMutex); + + if (initializationStarted || initialized.loadAcquire()) + return; + + // Indicate that the initialization has already started in the current + // thread in case of recursive calls. The atomic variable cannot be used + // for this because it is checked without holding the init mutex. + initializationStarted = true; + + auto guard = qScopeGuard([] { initialized.storeRelease(1); }); + + resetDefaultCiphers(); + resetDefaultEllipticCurves(); + +#if QT_CONFIG(library) + //load symbols needed to receive certificates from system store +#if defined(Q_OS_QNX) + QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); +#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) + // check whether we can enable on-demand root-cert loading (i.e. check whether the sym links are there) + const QList<QByteArray> dirs = QSslSocketPrivate::unixRootCertDirectories(); + const QStringList symLinkFilter{ + u"[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[0-9]"_s}; + for (const auto &dir : dirs) { + QDirListing dirList(QString::fromLatin1(dir), symLinkFilter, QDir::Files); + if (dirList.cbegin() != dirList.cend()) { // Not empty + QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); + break; + } + } +#endif +#endif // QT_CONFIG(library) + // if on-demand loading was not enabled, load the certs now + if (!QSslSocketPrivate::rootCertOnDemandLoadingSupported()) + setDefaultCaCertificates(systemCaCertificates()); +#ifdef Q_OS_WIN + //Enabled for fetching additional root certs from windows update on windows. + //This flag is set false by setDefaultCaCertificates() indicating the app uses + //its own cert bundle rather than the system one. + //Same logic that disables the unix on demand cert loading. + //Unlike unix, we do preload the certificates from the cert store. + QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); +#endif +} + +void QTlsBackendOpenSSL::resetDefaultCiphers() +{ + SSL_CTX *myCtx = q_SSL_CTX_new(q_TLS_client_method()); + // Note, we assert, not just silently return/bail out early: + // this should never happen and problems with OpenSSL's initialization + // must be caught before this (see supportsSsl()). + Q_ASSERT(myCtx); + SSL *mySsl = q_SSL_new(myCtx); + Q_ASSERT(mySsl); + + QList<QSslCipher> ciphers; + QList<QSslCipher> defaultCiphers; + + q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers); + + q_SSL_CTX_free(myCtx); + q_SSL_free(mySsl); + + setDefaultSupportedCiphers(ciphers); + setDefaultCiphers(defaultCiphers); + +#if QT_CONFIG(dtls) + ciphers.clear(); + defaultCiphers.clear(); + myCtx = q_SSL_CTX_new(q_DTLS_client_method()); + if (myCtx) { + mySsl = q_SSL_new(myCtx); + if (mySsl) { + q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers); + setDefaultDtlsCiphers(defaultCiphers); + q_SSL_free(mySsl); + } + q_SSL_CTX_free(myCtx); + } +#endif // dtls +} + +QList<QSsl::SslProtocol> QTlsBackendOpenSSL::supportedProtocols() const +{ + QList<QSsl::SslProtocol> protocols; + + protocols << QSsl::AnyProtocol; + protocols << QSsl::SecureProtocols; +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + protocols << QSsl::TlsV1_0; + protocols << QSsl::TlsV1_0OrLater; + protocols << QSsl::TlsV1_1; + protocols << QSsl::TlsV1_1OrLater; +QT_WARNING_POP + protocols << QSsl::TlsV1_2; + protocols << QSsl::TlsV1_2OrLater; + +#ifdef TLS1_3_VERSION + protocols << QSsl::TlsV1_3; + protocols << QSsl::TlsV1_3OrLater; +#endif // TLS1_3_VERSION + +#if QT_CONFIG(dtls) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + protocols << QSsl::DtlsV1_0; + protocols << QSsl::DtlsV1_0OrLater; +QT_WARNING_POP + protocols << QSsl::DtlsV1_2; + protocols << QSsl::DtlsV1_2OrLater; +#endif // dtls + + return protocols; +} + +QList<QSsl::SupportedFeature> QTlsBackendOpenSSL::supportedFeatures() const +{ + QList<QSsl::SupportedFeature> features; + + features << QSsl::SupportedFeature::CertificateVerification; + +#if !defined(OPENSSL_NO_TLSEXT) + features << QSsl::SupportedFeature::ClientSideAlpn; + features << QSsl::SupportedFeature::ServerSideAlpn; +#endif // !OPENSSL_NO_TLSEXT + + features << QSsl::SupportedFeature::Ocsp; + features << QSsl::SupportedFeature::Psk; + features << QSsl::SupportedFeature::SessionTicket; + features << QSsl::SupportedFeature::Alerts; + + return features; +} + +QList<QSsl::ImplementedClass> QTlsBackendOpenSSL::implementedClasses() const +{ + QList<QSsl::ImplementedClass> classes; + + classes << QSsl::ImplementedClass::Key; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Socket; +#if QT_CONFIG(dtls) + classes << QSsl::ImplementedClass::Dtls; + classes << QSsl::ImplementedClass::DtlsCookie; +#endif + classes << QSsl::ImplementedClass::EllipticCurve; + classes << QSsl::ImplementedClass::DiffieHellman; + + return classes; +} + +QTlsPrivate::TlsKey *QTlsBackendOpenSSL::createKey() const +{ + return new QTlsPrivate::TlsKeyOpenSSL; +} + +QTlsPrivate::X509Certificate *QTlsBackendOpenSSL::createCertificate() const +{ + return new QTlsPrivate::X509CertificateOpenSSL; +} + +namespace QTlsPrivate { + +#ifdef Q_OS_ANDROID +QList<QByteArray> fetchSslCertificateData(); +#endif + +QList<QSslCertificate> systemCaCertificates(); + +#ifndef Q_OS_DARWIN +QList<QSslCertificate> systemCaCertificates() +{ +#ifdef QSSLSOCKET_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + QList<QSslCertificate> systemCerts; +#if defined(Q_OS_WIN) + HCERTSTORE hSystemStore; + hSystemStore = + CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, + CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT"); + if (hSystemStore) { + PCCERT_CONTEXT pc = nullptr; + while (1) { + pc = CertFindCertificateInStore(hSystemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, pc); + if (!pc) + break; + QByteArray der(reinterpret_cast<const char *>(pc->pbCertEncoded), + static_cast<int>(pc->cbCertEncoded)); + QSslCertificate cert(der, QSsl::Der); + systemCerts.append(cert); + } + CertCloseStore(hSystemStore, 0); + } +#elif defined(Q_OS_ANDROID) + const QList<QByteArray> certData = fetchSslCertificateData(); + for (auto certDatum : certData) + systemCerts.append(QSslCertificate::fromData(certDatum, QSsl::Der)); +#elif defined(Q_OS_UNIX) + { + const QList<QByteArray> directories = QSslSocketPrivate::unixRootCertDirectories(); + QSet<QString> certFiles = { + QStringLiteral("/etc/pki/tls/certs/ca-bundle.crt"), // Fedora, Mandriva + QStringLiteral("/usr/local/share/certs/ca-root-nss.crt") // FreeBSD's ca_root_nss + }; + QDir currentDir; + currentDir.setNameFilters(QStringList{QStringLiteral("*.pem"), QStringLiteral("*.crt")}); + for (const auto &directory : directories) { + currentDir.setPath(QLatin1StringView(directory)); + for (const auto &dirEntry : QDirListing(currentDir)) { + // use canonical path here to not load the same certificate twice if symlinked + certFiles.insert(dirEntry.canonicalFilePath()); + } + } + for (const QString& file : std::as_const(certFiles)) + systemCerts.append(QSslCertificate::fromPath(file, QSsl::Pem)); + } +#endif // platform +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "systemCaCertificates retrieval time " << timer.elapsed() << "ms"; + qCDebug(lcTlsBackend) << "imported " << systemCerts.count() << " certificates"; +#endif + + return systemCerts; +} +#endif // !Q_OS_DARWIN +} // namespace QTlsPrivate + +QList<QSslCertificate> QTlsBackendOpenSSL::systemCaCertificates() const +{ + return QTlsPrivate::systemCaCertificates(); +} + +QTlsPrivate::DtlsCookieVerifier *QTlsBackendOpenSSL::createDtlsCookieVerifier() const +{ +#if QT_CONFIG(dtls) + return new QDtlsClientVerifierOpenSSL; +#else + qCWarning(lcTlsBackend, "Feature 'dtls' is disabled, cannot verify DTLS cookies"); + return nullptr; +#endif // QT_CONFIG(dtls) +} + +QTlsPrivate::TlsCryptograph *QTlsBackendOpenSSL::createTlsCryptograph() const +{ + return new QTlsPrivate::TlsCryptographOpenSSL; +} + +QTlsPrivate::DtlsCryptograph *QTlsBackendOpenSSL::createDtlsCryptograph(QDtls *q, int mode) const +{ +#if QT_CONFIG(dtls) + return new QDtlsPrivateOpenSSL(q, QSslSocket::SslMode(mode)); +#else + Q_UNUSED(q); + Q_UNUSED(mode); + qCWarning(lcTlsBackend, "Feature 'dtls' is disabled, cannot encrypt UDP datagrams"); + return nullptr; +#endif // QT_CONFIG(dtls) +} + +QTlsPrivate::X509ChainVerifyPtr QTlsBackendOpenSSL::X509Verifier() const +{ + return QTlsPrivate::X509CertificateOpenSSL::verify; +} + +QTlsPrivate::X509PemReaderPtr QTlsBackendOpenSSL::X509PemReader() const +{ + return QTlsPrivate::X509CertificateOpenSSL::certificatesFromPem; +} + +QTlsPrivate::X509DerReaderPtr QTlsBackendOpenSSL::X509DerReader() const +{ + return QTlsPrivate::X509CertificateOpenSSL::certificatesFromDer; +} + +QTlsPrivate::X509Pkcs12ReaderPtr QTlsBackendOpenSSL::X509Pkcs12Reader() const +{ + return QTlsPrivate::X509CertificateOpenSSL::importPkcs12; +} + +QList<int> QTlsBackendOpenSSL::ellipticCurvesIds() const +{ + QList<int> ids; + +#ifndef OPENSSL_NO_EC + const size_t curveCount = q_EC_get_builtin_curves(nullptr, 0); + QVarLengthArray<EC_builtin_curve> builtinCurves(static_cast<int>(curveCount)); + + if (q_EC_get_builtin_curves(builtinCurves.data(), curveCount) == curveCount) { + ids.reserve(curveCount); + for (const auto &ec : builtinCurves) + ids.push_back(ec.nid); + } +#endif // OPENSSL_NO_EC + + return ids; +} + + int QTlsBackendOpenSSL::curveIdFromShortName(const QString &name) const + { + int nid = 0; + if (name.isEmpty()) + return nid; + + ensureInitialized(); // TLSTODO: check if it's needed! +#ifndef OPENSSL_NO_EC + const QByteArray curveNameLatin1 = name.toLatin1(); + nid = q_OBJ_sn2nid(curveNameLatin1.data()); + + if (nid == 0) + nid = q_EC_curve_nist2nid(curveNameLatin1.data()); +#endif // !OPENSSL_NO_EC + + return nid; + } + + int QTlsBackendOpenSSL::curveIdFromLongName(const QString &name) const + { + int nid = 0; + if (name.isEmpty()) + return nid; + + ensureInitialized(); + +#ifndef OPENSSL_NO_EC + const QByteArray curveNameLatin1 = name.toLatin1(); + nid = q_OBJ_ln2nid(curveNameLatin1.data()); +#endif + + return nid; + } + + QString QTlsBackendOpenSSL::shortNameForId(int id) const + { + QString result; + +#ifndef OPENSSL_NO_EC + if (id != 0) + result = QString::fromLatin1(q_OBJ_nid2sn(id)); +#endif + + return result; + } + +QString QTlsBackendOpenSSL::longNameForId(int id) const +{ + QString result; + +#ifndef OPENSSL_NO_EC + if (id != 0) + result = QString::fromLatin1(q_OBJ_nid2ln(id)); +#endif + + return result; +} + +// NIDs of named curves allowed in TLS as per RFCs 4492 and 7027, +// see also https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 +static const int tlsNamedCurveNIDs[] = { + // RFC 4492 + NID_sect163k1, + NID_sect163r1, + NID_sect163r2, + NID_sect193r1, + NID_sect193r2, + NID_sect233k1, + NID_sect233r1, + NID_sect239k1, + NID_sect283k1, + NID_sect283r1, + NID_sect409k1, + NID_sect409r1, + NID_sect571k1, + NID_sect571r1, + + NID_secp160k1, + NID_secp160r1, + NID_secp160r2, + NID_secp192k1, + NID_X9_62_prime192v1, // secp192r1 + NID_secp224k1, + NID_secp224r1, + NID_secp256k1, + NID_X9_62_prime256v1, // secp256r1 + NID_secp384r1, + NID_secp521r1, + + // RFC 7027 + NID_brainpoolP256r1, + NID_brainpoolP384r1, + NID_brainpoolP512r1 +}; + +const size_t tlsNamedCurveNIDCount = sizeof(tlsNamedCurveNIDs) / sizeof(tlsNamedCurveNIDs[0]); + +bool QTlsBackendOpenSSL::isTlsNamedCurve(int id) const +{ + const int *const tlsNamedCurveNIDsEnd = tlsNamedCurveNIDs + tlsNamedCurveNIDCount; + return std::find(tlsNamedCurveNIDs, tlsNamedCurveNIDsEnd, id) != tlsNamedCurveNIDsEnd; +} + +QString QTlsBackendOpenSSL::msgErrorsDuringHandshake() +{ + return QSslSocket::tr("Error during SSL handshake: %1").arg(getErrorsFromOpenSsl()); +} + +QSslCipher QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher) +{ + Q_ASSERT(cipher); + char buf [256] = {}; + const QString desc = QString::fromLatin1(q_SSL_CIPHER_description(cipher, buf, sizeof(buf))); + int supportedBits = 0; + const int bits = q_SSL_CIPHER_get_bits(cipher, &supportedBits); + return createCiphersuite(desc, bits, supportedBits); +} + +void QTlsBackendOpenSSL::forceAutotestSecurityLevel() +{ + QSslContext::forceAutoTestSecurityLevel(); +} + +QT_END_NAMESPACE + +#include "moc_qtlsbackend_openssl_p.cpp" diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl_p.h b/src/plugins/tls/openssl/qtlsbackend_openssl_p.h new file mode 100644 index 0000000000..b9f1f95df0 --- /dev/null +++ b/src/plugins/tls/openssl/qtlsbackend_openssl_p.h @@ -0,0 +1,104 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTLSBACKEND_OPENSSL_P_H +#define QTLSBACKEND_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtNetwork/qssldiffiehellmanparameters.h> +#include <QtNetwork/qsslcertificate.h> + +#include <QtNetwork/private/qtlsbackend_p.h> + +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> + +#include <openssl/ssl.h> + +QT_BEGIN_NAMESPACE + +class QTlsBackendOpenSSL final : public QTlsBackend +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QTlsBackend_iid) + Q_INTERFACES(QTlsBackend) + +public: + + static QString getErrorsFromOpenSsl(); + static void logAndClearErrorQueue(); + static void clearErrorQueue(); + + // Index used in SSL_get_ex_data to get the matching TlsCryptographerOpenSSL: + static int s_indexForSSLExtraData; + + static QString msgErrorsDuringHandshake(); + static QSslCipher qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher); +private: + static bool ensureLibraryLoaded(); + QString backendName() const override; + bool isValid() const override; + long tlsLibraryVersionNumber() const override; + QString tlsLibraryVersionString() const override; + long tlsLibraryBuildVersionNumber() const override; + QString tlsLibraryBuildVersionString() const override; + + void ensureInitialized() const override; + void ensureCiphersAndCertsLoaded() const; + static void resetDefaultCiphers(); + + QList<QSsl::SslProtocol> supportedProtocols() const override; + QList<QSsl::SupportedFeature> supportedFeatures() const override; + QList<QSsl::ImplementedClass> implementedClasses() const override; + + // QSslKey: + QTlsPrivate::TlsKey *createKey() const override; + + // QSslCertificate: + QTlsPrivate::X509Certificate *createCertificate() const override; + QList<QSslCertificate> systemCaCertificates() const override; + + QTlsPrivate::TlsCryptograph *createTlsCryptograph() const override; + QTlsPrivate::DtlsCookieVerifier *createDtlsCookieVerifier() const override; + QTlsPrivate::DtlsCryptograph *createDtlsCryptograph(QDtls *q, int mode) const override; + + QTlsPrivate::X509ChainVerifyPtr X509Verifier() const override; + QTlsPrivate::X509PemReaderPtr X509PemReader() const override; + QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override; + + // Elliptic curves: + QList<int> ellipticCurvesIds() const override; + int curveIdFromShortName(const QString &name) const override; + int curveIdFromLongName(const QString &name) const override; + QString shortNameForId(int cid) const override; + QString longNameForId(int cid) const override; + bool isTlsNamedCurve(int cid) const override; + + // DH parameters: + using DHParams = QSslDiffieHellmanParameters; + int dhParametersFromDer(const QByteArray &derData, QByteArray *data) const override; + int dhParametersFromPem(const QByteArray &pemData, QByteArray *data) const override; + + void forceAutotestSecurityLevel() override; +}; + +Q_DECLARE_LOGGING_CATEGORY(lcTlsBackend) + +QT_END_NAMESPACE + +#endif // QTLSBACKEND_OPENSSL_P_H + + diff --git a/src/plugins/tls/openssl/qtlskey_openssl.cpp b/src/plugins/tls/openssl/qtlskey_openssl.cpp new file mode 100644 index 0000000000..294fc2ffcd --- /dev/null +++ b/src/plugins/tls/openssl/qtlskey_openssl.cpp @@ -0,0 +1,541 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsslsocket_openssl_symbols_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qtlskey_openssl_p.h" + +#include <QtNetwork/private/qsslkey_p.h> + +#include <QtNetwork/qsslsocket.h> + +#include <QtCore/qscopeguard.h> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +void TlsKeyOpenSSL::decodeDer(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &der, + const QByteArray &passPhrase, bool deepClear) +{ + if (der.isEmpty()) + return; + + keyType = type; + keyAlgorithm = algorithm; + + QMap<QByteArray, QByteArray> headers; + const auto pem = pemFromDer(der, headers); + + decodePem(type, algorithm, pem, passPhrase, deepClear); +} + +void TlsKeyOpenSSL::decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, + const QByteArray &passPhrase, bool deepClear) +{ + if (pem.isEmpty()) + return; + + keyType = type; + keyAlgorithm = algorithm; + + clear(deepClear); + + BIO *bio = q_BIO_new_mem_buf(const_cast<char *>(pem.data()), pem.size()); + if (!bio) + return; + + const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); + + void *phrase = const_cast<char *>(passPhrase.data()); + +#ifdef OPENSSL_NO_DEPRECATED_3_0 + if (type == QSsl::PublicKey) + genericKey = q_PEM_read_bio_PUBKEY(bio, nullptr, nullptr, phrase); + else + genericKey = q_PEM_read_bio_PrivateKey(bio, nullptr, nullptr, phrase); + keyIsNull = !genericKey; + if (keyIsNull) + QTlsBackendOpenSSL::logAndClearErrorQueue(); +#else + + if (algorithm == QSsl::Rsa) { + RSA *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_RSA_PUBKEY(bio, &rsa, nullptr, phrase) + : q_PEM_read_bio_RSAPrivateKey(bio, &rsa, nullptr, phrase); + if (rsa && rsa == result) + keyIsNull = false; + } else if (algorithm == QSsl::Dsa) { + DSA *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_DSA_PUBKEY(bio, &dsa, nullptr, phrase) + : q_PEM_read_bio_DSAPrivateKey(bio, &dsa, nullptr, phrase); + if (dsa && dsa == result) + keyIsNull = false; + } else if (algorithm == QSsl::Dh) { + EVP_PKEY *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_PUBKEY(bio, nullptr, nullptr, phrase) + : q_PEM_read_bio_PrivateKey(bio, nullptr, nullptr, phrase); + if (result) + dh = q_EVP_PKEY_get1_DH(result); + if (dh) + keyIsNull = false; + q_EVP_PKEY_free(result); +#ifndef OPENSSL_NO_EC + } else if (algorithm == QSsl::Ec) { + EC_KEY *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_EC_PUBKEY(bio, &ec, nullptr, phrase) + : q_PEM_read_bio_ECPrivateKey(bio, &ec, nullptr, phrase); + if (ec && ec == result) + keyIsNull = false; +#endif // OPENSSL_NO_EC + } + +#endif // OPENSSL_NO_DEPRECATED_3_0 +} + +QByteArray TlsKeyOpenSSL::derFromPem(const QByteArray &pem, QMap<QByteArray, QByteArray> *headers) const +{ + QByteArray header = pemHeader(); + QByteArray footer = pemFooter(); + + QByteArray der(pem); + + int headerIndex = der.indexOf(header); + int footerIndex = der.indexOf(footer, headerIndex + header.size()); + if (type() != QSsl::PublicKey) { + if (headerIndex == -1 || footerIndex == -1) { + header = pkcs8Header(true); + footer = pkcs8Footer(true); + headerIndex = der.indexOf(header); + footerIndex = der.indexOf(footer, headerIndex + header.size()); + } + if (headerIndex == -1 || footerIndex == -1) { + header = pkcs8Header(false); + footer = pkcs8Footer(false); + headerIndex = der.indexOf(header); + footerIndex = der.indexOf(footer, headerIndex + header.size()); + } + } + if (headerIndex == -1 || footerIndex == -1) + return QByteArray(); + + der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); + + if (der.contains("Proc-Type:")) { + // taken from QHttpNetworkReplyPrivate::parseHeader + int i = 0; + while (i < der.size()) { + int j = der.indexOf(':', i); // field-name + if (j == -1) + break; + const QByteArray field = der.mid(i, j - i).trimmed(); + j++; + // any number of LWS is allowed before and after the value + QByteArray value; + do { + i = der.indexOf('\n', j); + if (i == -1) + break; + if (!value.isEmpty()) + value += ' '; + // check if we have CRLF or only LF + bool hasCR = (i && der[i-1] == '\r'); + int length = i -(hasCR ? 1: 0) - j; + value += der.mid(j, length).trimmed(); + j = ++i; + } while (i < der.size() && (der.at(i) == ' ' || der.at(i) == '\t')); + if (i == -1) + break; // something is wrong + + headers->insert(field, value); + } + der = der.mid(i); + } + + return QByteArray::fromBase64(der); // ignores newlines +} + +void TlsKeyOpenSSL::clear(bool deep) +{ + keyIsNull = true; + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + if (algorithm() == QSsl::Rsa && rsa) { + if (deep) + q_RSA_free(rsa); + rsa = nullptr; + } + if (algorithm() == QSsl::Dsa && dsa) { + if (deep) + q_DSA_free(dsa); + dsa = nullptr; + } + if (algorithm() == QSsl::Dh && dh) { + if (deep) + q_DH_free(dh); + dh = nullptr; + } +#ifndef OPENSSL_NO_EC + if (algorithm() == QSsl::Ec && ec) { + if (deep) + q_EC_KEY_free(ec); + ec = nullptr; + } +#endif +#endif // OPENSSL_NO_DEPRECATED_3_0 + + if (algorithm() == QSsl::Opaque && opaque) { + if (deep) + q_EVP_PKEY_free(opaque); + opaque = nullptr; + } + + if (genericKey) { + // None of the above cleared it. genericKey is either + // initialised by PEM read operation, or from X509, and + // we are the owners and not sharing. So we free it. + q_EVP_PKEY_free(genericKey); + genericKey = nullptr; + } +} + +Qt::HANDLE TlsKeyOpenSSL::handle() const +{ + if (keyAlgorithm == QSsl::Opaque) + return Qt::HANDLE(opaque); + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + switch (keyAlgorithm) { + case QSsl::Rsa: + return Qt::HANDLE(rsa); + case QSsl::Dsa: + return Qt::HANDLE(dsa); + case QSsl::Dh: + return Qt::HANDLE(dh); +#ifndef OPENSSL_NO_EC + case QSsl::Ec: + return Qt::HANDLE(ec); +#endif + default: + return Qt::HANDLE(nullptr); + } +#else + qCWarning(lcTlsBackend, + "This version of OpenSSL disabled direct manipulation with RSA/DSA/DH/EC_KEY structures, consider using QSsl::Opaque instead."); + return Qt::HANDLE(genericKey); +#endif +} + +int TlsKeyOpenSSL::length() const +{ + if (isNull() || algorithm() == QSsl::Opaque) + return -1; + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + switch (algorithm()) { + case QSsl::Rsa: + return q_RSA_bits(rsa); + case QSsl::Dsa: + return q_DSA_bits(dsa); + case QSsl::Dh: + return q_DH_bits(dh); +#ifndef OPENSSL_NO_EC + case QSsl::Ec: + return q_EC_GROUP_get_degree(q_EC_KEY_get0_group(ec)); +#endif + default: + return -1; + } +#else // OPENSSL_NO_DEPRECATED_3_0 + Q_ASSERT(genericKey); + return q_EVP_PKEY_get_bits(genericKey); +#endif // OPENSSL_NO_DEPRECATED_3_0 +} + +QByteArray TlsKeyOpenSSL::toPem(const QByteArray &passPhrase) const +{ + if (!QSslSocket::supportsSsl() || isNull() || algorithm() == QSsl::Opaque) + return {}; + + const EVP_CIPHER *cipher = nullptr; + if (type() == QSsl::PrivateKey && !passPhrase.isEmpty()) { +#ifndef OPENSSL_NO_DES + cipher = q_EVP_des_ede3_cbc(); +#else + return {}; +#endif + } + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) + return {}; + + const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + +#define write_pubkey(alg, key) q_PEM_write_bio_##alg##_PUBKEY(bio, key) +#define write_privatekey(alg, key) \ + q_PEM_write_bio_##alg##PrivateKey(bio, key, cipher, (uchar *)passPhrase.data(), \ + passPhrase.size(), nullptr, nullptr) + +#else + +#define write_pubkey(alg, key) q_PEM_write_bio_PUBKEY(bio, genericKey) +#define write_privatekey(alg, key) \ + q_PEM_write_bio_PrivateKey_traditional(bio, genericKey, cipher, (uchar *)passPhrase.data(), passPhrase.size(), nullptr, nullptr) + +#endif // OPENSSL_NO_DEPRECATED_3_0 + + bool fail = false; + if (algorithm() == QSsl::Rsa) { + if (type() == QSsl::PublicKey) { + if (!write_pubkey(RSA, rsa)) + fail = true; + } else if (!write_privatekey(RSA, rsa)) { + fail = true; + } + } else if (algorithm() == QSsl::Dsa) { + if (type() == QSsl::PublicKey) { + if (!write_pubkey(DSA, dsa)) + fail = true; + } else if (!write_privatekey(DSA, dsa)) { + fail = true; + } + } else if (algorithm() == QSsl::Dh) { +#ifdef OPENSSL_NO_DEPRECATED_3_0 + EVP_PKEY *result = genericKey; +#else + EVP_PKEY *result = q_EVP_PKEY_new(); + const auto guard = qScopeGuard([result]{if (result) q_EVP_PKEY_free(result);}); + if (!result || !q_EVP_PKEY_set1_DH(result, dh)) { + fail = true; + } else +#endif + if (type() == QSsl::PublicKey) { + if (!q_PEM_write_bio_PUBKEY(bio, result)) + fail = true; + } else if (!q_PEM_write_bio_PrivateKey(bio, result, cipher, (uchar *)passPhrase.data(), + passPhrase.size(), nullptr, nullptr)) { + fail = true; + } +#ifndef OPENSSL_NO_EC + } else if (algorithm() == QSsl::Ec) { + if (type() == QSsl::PublicKey) { + if (!write_pubkey(EC, ec)) + fail = true; + } else { + if (!write_privatekey(EC, ec)) + fail = true; + } +#endif + } else { + fail = true; + } + + QByteArray pem; + if (!fail) { + char *data = nullptr; + const long size = q_BIO_get_mem_data(bio, &data); + if (size > 0 && data) + pem = QByteArray(data, size); + } else { + QTlsBackendOpenSSL::logAndClearErrorQueue(); + } + + return pem; +} + +void TlsKeyOpenSSL::fromHandle(Qt::HANDLE handle, QSsl::KeyType expectedType) +{ + EVP_PKEY *evpKey = reinterpret_cast<EVP_PKEY *>(handle); + if (!evpKey || !fromEVP_PKEY(evpKey)) { + opaque = evpKey; + keyAlgorithm = QSsl::Opaque; + } else { + q_EVP_PKEY_free(evpKey); + } + + keyType = expectedType; + keyIsNull = !opaque; +} + +bool TlsKeyOpenSSL::fromEVP_PKEY(EVP_PKEY *pkey) +{ + if (!pkey) + return false; + +#ifndef OPENSSL_NO_DEPRECATED_3_0 +#define get_key(key, alg) key = q_EVP_PKEY_get1_##alg(pkey) +#else +#define get_key(key, alg) q_EVP_PKEY_up_ref(pkey); genericKey = pkey; +#endif + + switch (q_EVP_PKEY_type(q_EVP_PKEY_base_id(pkey))) { + case EVP_PKEY_RSA: + keyIsNull = false; + keyAlgorithm = QSsl::Rsa; + keyType = QSsl::PrivateKey; + get_key(rsa, RSA); + return true; + case EVP_PKEY_DSA: + keyIsNull = false; + keyAlgorithm = QSsl::Dsa; + keyType = QSsl::PrivateKey; + get_key(dsa, DSA); + return true; + case EVP_PKEY_DH: + keyIsNull = false; + keyAlgorithm = QSsl::Dh; + keyType = QSsl::PrivateKey; + get_key(dh, DH); + return true; +#ifndef OPENSSL_NO_EC + case EVP_PKEY_EC: + keyIsNull = false; + keyAlgorithm = QSsl::Ec; + keyType = QSsl::PrivateKey; + get_key(ec, EC_KEY); + return true; +#endif + default:; + // Unknown key type. This could be handled as opaque, but then + // we'd eventually leak memory since we wouldn't be able to free + // the underlying EVP_PKEY structure. For now, we won't support + // this. + } + + return false; +} + +QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv, bool enc) +{ + const EVP_CIPHER *type = nullptr; + int i = 0, len = 0; + + switch (cipher) { + case Cipher::DesCbc: +#ifndef OPENSSL_NO_DES + type = q_EVP_des_cbc(); +#endif + break; + case Cipher::DesEde3Cbc: +#ifndef OPENSSL_NO_DES + type = q_EVP_des_ede3_cbc(); +#endif + break; + case Cipher::Rc2Cbc: +#ifndef OPENSSL_NO_RC2 + type = q_EVP_rc2_cbc(); +#endif + break; + case Cipher::Aes128Cbc: + type = q_EVP_aes_128_cbc(); + break; + case Cipher::Aes192Cbc: + type = q_EVP_aes_192_cbc(); + break; + case Cipher::Aes256Cbc: + type = q_EVP_aes_256_cbc(); + break; + } + + if (type == nullptr) + return {}; + + QByteArray output; + output.resize(data.size() + EVP_MAX_BLOCK_LENGTH); + + EVP_CIPHER_CTX *ctx = q_EVP_CIPHER_CTX_new(); + q_EVP_CIPHER_CTX_reset(ctx); + if (q_EVP_CipherInit(ctx, type, nullptr, nullptr, enc) != 1) { + q_EVP_CIPHER_CTX_free(ctx); + QTlsBackendOpenSSL::logAndClearErrorQueue(); + return {}; + } + + q_EVP_CIPHER_CTX_set_key_length(ctx, key.size()); + if (cipher == Cipher::Rc2Cbc) + q_EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, 8 * key.size(), nullptr); + + q_EVP_CipherInit_ex(ctx, nullptr, nullptr, + reinterpret_cast<const unsigned char *>(key.constData()), + reinterpret_cast<const unsigned char *>(iv.constData()), + enc); + q_EVP_CipherUpdate(ctx, + reinterpret_cast<unsigned char *>(output.data()), &len, + reinterpret_cast<const unsigned char *>(data.constData()), data.size()); + q_EVP_CipherFinal(ctx, + reinterpret_cast<unsigned char *>(output.data()) + len, &i); + len += i; + + q_EVP_CIPHER_CTX_reset(ctx); + q_EVP_CIPHER_CTX_free(ctx); + + return output.left(len); +} + +QByteArray TlsKeyOpenSSL::decrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const +{ + return doCrypt(cipher, data, key, iv, false); +} + +QByteArray TlsKeyOpenSSL::encrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const +{ + return doCrypt(cipher, data, key, iv, true); +} + +TlsKeyOpenSSL *TlsKeyOpenSSL::publicKeyFromX509(X509 *x) +{ + TlsKeyOpenSSL *tlsKey = new TlsKeyOpenSSL; + std::unique_ptr<TlsKeyOpenSSL> keyRaii(tlsKey); + + tlsKey->keyType = QSsl::PublicKey; + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + +#define get_pubkey(keyName, alg) tlsKey->keyName = q_EVP_PKEY_get1_##alg(pkey) + +#else + +#define get_pubkey(a, b) tlsKey->genericKey = pkey + +#endif + + EVP_PKEY *pkey = q_X509_get_pubkey(x); + Q_ASSERT(pkey); + const int keyType = q_EVP_PKEY_type(q_EVP_PKEY_base_id(pkey)); + + if (keyType == EVP_PKEY_RSA) { + get_pubkey(rsa, RSA); + tlsKey->keyAlgorithm = QSsl::Rsa; + tlsKey->keyIsNull = false; + } else if (keyType == EVP_PKEY_DSA) { + get_pubkey(dsa, DSA); + tlsKey->keyAlgorithm = QSsl::Dsa; + tlsKey->keyIsNull = false; +#ifndef OPENSSL_NO_EC + } else if (keyType == EVP_PKEY_EC) { + get_pubkey(ec, EC_KEY); + tlsKey->keyAlgorithm = QSsl::Ec; + tlsKey->keyIsNull = false; +#endif + } else if (keyType == EVP_PKEY_DH) { + // DH unsupported (key is null) + } else { + // error? (key is null) + } + +#ifndef OPENSSL_NO_DEPRECATED_3_0 + q_EVP_PKEY_free(pkey); +#endif + + return keyRaii.release(); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qtlskey_openssl_p.h b/src/plugins/tls/openssl/qtlskey_openssl_p.h new file mode 100644 index 0000000000..4ee16ffc29 --- /dev/null +++ b/src/plugins/tls/openssl/qtlskey_openssl_p.h @@ -0,0 +1,100 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTLSKEY_OPENSSL_H +#define QTLSKEY_OPENSSL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include "../shared/qtlskey_base_p.h" + +#include <QtNetwork/private/qtlsbackend_p.h> +#include <QtNetwork/private/qsslkey_p.h> + +#include <QtNetwork/qssl.h> + +#include <QtCore/qbytearray.h> +#include <QtCore/qglobal.h> + +#include <openssl/rsa.h> +#include <openssl/dsa.h> +#include <openssl/dh.h> + +#ifdef OPENSSL_NO_DEPRECATED_3_0 +typedef struct evp_pkey_st EVP_PKEY; +typedef struct dsa_st DSA; +typedef struct rsa_st RSA; +typedef struct dh_st DH; +typedef struct ec_key_st EC_KEY; +#endif // OPENSSL_NO_DEPRECATED_3_0 + +QT_BEGIN_NAMESPACE + +QT_REQUIRE_CONFIG(ssl); + +namespace QTlsPrivate { + +class TlsKeyOpenSSL final : public TlsKeyBase +{ +public: + TlsKeyOpenSSL() + : opaque(nullptr) + { + clear(false); + } + ~TlsKeyOpenSSL() + { + clear(true); + } + + void decodeDer(KeyType type, KeyAlgorithm algorithm, const QByteArray &der, + const QByteArray &passPhrase, bool deepClear) override; + void decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, + const QByteArray &passPhrase, bool deepClear) override; + + QByteArray toPem(const QByteArray &passPhrase) const override; + QByteArray derFromPem(const QByteArray &pem, QMap<QByteArray, QByteArray> *headers) const override; + + void fromHandle(Qt::HANDLE opaque, KeyType expectedType) override; + + void clear(bool deep) override; + Qt::HANDLE handle() const override; + int length() const override; + + QByteArray decrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const override; + QByteArray encrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const override; + + static TlsKeyOpenSSL *publicKeyFromX509(X509 *x); + + union { + EVP_PKEY *opaque; + RSA *rsa; + DSA *dsa; + DH *dh; +#ifndef OPENSSL_NO_EC + EC_KEY *ec; +#endif + EVP_PKEY *genericKey; + }; + + bool fromEVP_PKEY(EVP_PKEY *pkey); +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_OPENSSL_H diff --git a/src/plugins/tls/openssl/qwindowscarootfetcher.cpp b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp new file mode 100644 index 0000000000..a18aae0b71 --- /dev/null +++ b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp @@ -0,0 +1,249 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwindowscarootfetcher_p.h" +#include "qx509_openssl_p.h" +#include "qopenssl_p.h" + +#include <QtCore/QThread> +#include <QtGlobal> + +#include <QtCore/qscopeguard.h> + +#ifdef QSSLSOCKET_DEBUG +#include <QtNetwork/private/qtlsbackend_p.h> // for debug categories +#include <QtCore/QElapsedTimer> +#endif + +QT_BEGIN_NAMESPACE + +class QWindowsCaRootFetcherThread : public QThread +{ +public: + QWindowsCaRootFetcherThread() + { + qRegisterMetaType<QSslCertificate>(); + setObjectName(QStringLiteral("QWindowsCaRootFetcher")); + start(); + } + ~QWindowsCaRootFetcherThread() + { + quit(); + wait(15500); // worst case, a running request can block for 15 seconds + } +}; + +Q_GLOBAL_STATIC(QWindowsCaRootFetcherThread, windowsCaRootFetcherThread); + +namespace { + +const QList<QSslCertificate> buildVerifiedChain(const QList<QSslCertificate> &caCertificates, + PCCERT_CHAIN_CONTEXT chainContext, + const QString &peerVerifyName) +{ + // We ended up here because OpenSSL verification failed to + // build a chain, with intermediate certificate missing + // but "Authority Information Access" extension present. + // Also, apparently the normal CA fetching path was disabled + // by setting custom CA certificates. We convert wincrypt's + // structures in QSslCertificate and give OpenSSL the second + // chance to verify the now (apparently) complete chain. + // In addition, wincrypt gives us a benefit of some checks + // we don't have in OpenSSL back-end. + Q_ASSERT(chainContext); + + if (!chainContext->cChain) + return {}; + + QList<QSslCertificate> verifiedChain; + + CERT_SIMPLE_CHAIN *chain = chainContext->rgpChain[chainContext->cChain - 1]; + if (!chain) + return {}; + + if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN) + return {}; // No need to mess with OpenSSL (the chain is still incomplete). + + for (DWORD i = 0; i < chain->cElement; ++i) { + CERT_CHAIN_ELEMENT *element = chain->rgpElement[i]; + QSslCertificate cert(QByteArray(reinterpret_cast<const char*>(element->pCertContext->pbCertEncoded), + int(element->pCertContext->cbCertEncoded)), QSsl::Der); + + if (cert.isBlacklisted()) + return {}; + + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) // Good to know! + return {}; + + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID) + return {}; + + verifiedChain.append(cert); + } + + // We rely on OpenSSL's ability to find other problems. + const auto tlsErrors = QTlsPrivate::X509CertificateOpenSSL::verify(caCertificates, verifiedChain, peerVerifyName); + if (tlsErrors.size()) + verifiedChain.clear(); + + return verifiedChain; +} + +} // unnamed namespace + +QWindowsCaRootFetcher::QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode, + const QList<QSslCertificate> &caCertificates, const QString &hostName) + : cert(certificate), mode(sslMode), explicitlyTrustedCAs(caCertificates), peerVerifyName(hostName) +{ + moveToThread(windowsCaRootFetcherThread()); +} + +QWindowsCaRootFetcher::~QWindowsCaRootFetcher() +{ +} + +void QWindowsCaRootFetcher::start() +{ + QByteArray der = cert.toDer(); + PCCERT_CONTEXT wincert = CertCreateCertificateContext(X509_ASN_ENCODING, (const BYTE *)der.constData(), der.length()); + if (!wincert) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "QWindowsCaRootFetcher failed to convert certificate to windows form"); +#endif + emit finished(cert, QSslCertificate()); + deleteLater(); + return; + } + + CERT_CHAIN_PARA parameters; + memset(¶meters, 0, sizeof(parameters)); + parameters.cbSize = sizeof(parameters); + // set key usage constraint + parameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; + parameters.RequestedUsage.Usage.cUsageIdentifier = 1; + LPSTR oid = (LPSTR)(mode == QSslSocket::SslClientMode ? szOID_PKIX_KP_SERVER_AUTH : szOID_PKIX_KP_CLIENT_AUTH); + parameters.RequestedUsage.Usage.rgpszUsageIdentifier = &oid; + +#ifdef QSSLSOCKET_DEBUG + QElapsedTimer stopwatch; + stopwatch.start(); +#endif + PCCERT_CHAIN_CONTEXT chain; + auto additionalStore = createAdditionalStore(); + BOOL result = CertGetCertificateChain( + nullptr, //default engine + wincert, + nullptr, //current date/time + additionalStore.get(), //default store (nullptr) or CAs an application trusts + ¶meters, + 0, //default dwFlags + nullptr, //reserved + &chain); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl) << "QWindowsCaRootFetcher" << stopwatch.elapsed() << "ms to get chain"; +#endif + + QSslCertificate trustedRoot; + if (result) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl) << "QWindowsCaRootFetcher - examining windows chains"; + if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR) + qCDebug(lcSsl) << " - TRUSTED"; + else + qCDebug(lcSsl) << " - NOT TRUSTED" << chain->TrustStatus.dwErrorStatus; + if (chain->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) + qCDebug(lcSsl) << " - SELF SIGNED"; + qCDebug(lcSsl) << "QWindowsCaRootFetcher - dumping simple chains"; + for (unsigned int i = 0; i < chain->cChain; i++) { + if (chain->rgpChain[i]->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR) + qCDebug(lcSsl) << " - TRUSTED SIMPLE CHAIN" << i; + else + qCDebug(lcSsl) << " - UNTRUSTED SIMPLE CHAIN" << i << "reason:" << chain->rgpChain[i]->TrustStatus.dwErrorStatus; + for (unsigned int j = 0; j < chain->rgpChain[i]->cElement; j++) { + QSslCertificate foundCert(QByteArray((const char *)chain->rgpChain[i]->rgpElement[j]->pCertContext->pbCertEncoded + , chain->rgpChain[i]->rgpElement[j]->pCertContext->cbCertEncoded), QSsl::Der); + qCDebug(lcSsl) << " - " << foundCert; + } + } + qCDebug(lcSsl) << " - and" << chain->cLowerQualityChainContext << "low quality chains"; //expect 0, we haven't asked for them +#endif + + //based on http://msdn.microsoft.com/en-us/library/windows/desktop/aa377182%28v=vs.85%29.aspx + //about the final chain rgpChain[cChain-1] which must begin with a trusted root to be valid + if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR + && chain->cChain > 0) { + const PCERT_SIMPLE_CHAIN finalChain = chain->rgpChain[chain->cChain - 1]; + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa377544%28v=vs.85%29.aspx + // rgpElement[0] is the end certificate chain element. rgpElement[cElement-1] is the self-signed "root" certificate element. + if (finalChain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR + && finalChain->cElement > 0) { + trustedRoot = QSslCertificate(QByteArray((const char *)finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->pbCertEncoded + , finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->cbCertEncoded), QSsl::Der); + } + } else if (explicitlyTrustedCAs.size()) { + // Setting custom CA in configuration, and those CAs are not trusted by MS. +#if QT_CONFIG(openssl) + const auto verifiedChain = buildVerifiedChain(explicitlyTrustedCAs, chain, peerVerifyName); + if (verifiedChain.size()) + trustedRoot = verifiedChain.last(); +#else + //It's only OpenSSL code-path that can trigger such a fetch. + Q_UNREACHABLE(); +#endif + } + CertFreeCertificateChain(chain); + } + CertFreeCertificateContext(wincert); + + emit finished(cert, trustedRoot); + deleteLater(); +} + +QHCertStorePointer QWindowsCaRootFetcher::createAdditionalStore() const +{ + QHCertStorePointer customStore; + if (explicitlyTrustedCAs.isEmpty()) + return customStore; + + if (HCERTSTORE rawPtr = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr)) { + customStore.reset(rawPtr); + + unsigned rootsAdded = 0; + for (const QSslCertificate &caCert : explicitlyTrustedCAs) { + const auto der = caCert.toDer(); + PCCERT_CONTEXT winCert = CertCreateCertificateContext(X509_ASN_ENCODING, + reinterpret_cast<const BYTE *>(der.data()), + DWORD(der.length())); + if (!winCert) { +#if defined(QSSLSOCKET_DEBUG) + qCWarning(lcSsl) << "CA fetcher, failed to convert QSslCertificate" + << "to the native representation"; +#endif // QSSLSOCKET_DEBUG + continue; + } + const auto deleter = qScopeGuard([winCert](){ + CertFreeCertificateContext(winCert); + }); + if (CertAddCertificateContextToStore(customStore.get(), winCert, CERT_STORE_ADD_ALWAYS, nullptr)) + ++rootsAdded; +#if defined(QSSLSOCKET_DEBUG) + else //Why assert? With flags we're using and winCert check - should not happen! + Q_ASSERT("CertAddCertificateContextToStore() failed"); +#endif // QSSLSOCKET_DEBUG + } + if (!rootsAdded) //Useless store, no cert was added. + customStore.reset(); +#if defined(QSSLSOCKET_DEBUG) + } else { + + qCWarning(lcSsl) << "CA fetcher, failed to create a custom" + << "store for explicitly trusted CA certificate"; +#endif // QSSLSOCKET_DEBUG + } + + return customStore; +} + +QT_END_NAMESPACE + +#include "moc_qwindowscarootfetcher_p.cpp" diff --git a/src/plugins/tls/openssl/qwindowscarootfetcher_p.h b/src/plugins/tls/openssl/qwindowscarootfetcher_p.h new file mode 100644 index 0000000000..715fd19945 --- /dev/null +++ b/src/plugins/tls/openssl/qwindowscarootfetcher_p.h @@ -0,0 +1,59 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWINDOWSCAROOTFETCHER_P_H +#define QWINDOWSCAROOTFETCHER_P_H + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include <QtNetwork/qsslcertificate.h> +#include <QtNetwork/qsslsocket.h> + +#include <QtCore/QtGlobal> +#include <QtCore/QObject> + +#include "../shared/qwincrypt_p.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +class QWindowsCaRootFetcher : public QObject +{ + Q_OBJECT +public: + QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode, + const QList<QSslCertificate> &caCertificates = {}, + const QString &hostName = {}); + ~QWindowsCaRootFetcher(); +public slots: + void start(); +signals: + void finished(QSslCertificate brokenChain, QSslCertificate caroot); +private: + QHCertStorePointer createAdditionalStore() const; + + QSslCertificate cert; + QSslSocket::SslMode mode; + // In case the application set CA certificates in the configuration, + // in the past we did not load missing certs. But this disables + // recoverable case when a certificate has Authority Information Access + // extension. So we try to fetch in this scenario also, but in case + // explicitly trusted root was not in a system store, we'll do + // additional checks, thus we need 'peerVerifyName': + QList<QSslCertificate> explicitlyTrustedCAs; + QString peerVerifyName; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSCAROOTFETCHER_P_H diff --git a/src/plugins/tls/openssl/qx509_openssl.cpp b/src/plugins/tls/openssl/qx509_openssl.cpp new file mode 100644 index 0000000000..0cd3749f88 --- /dev/null +++ b/src/plugins/tls/openssl/qx509_openssl.cpp @@ -0,0 +1,947 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qsslsocket_openssl_symbols_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qtlskey_openssl_p.h" +#include "qx509_openssl_p.h" +#include "qtls_openssl_p.h" + +#include <QtNetwork/private/qsslcertificate_p.h> + +#include <QtNetwork/qsslsocket.h> +#include <QtNetwork/qhostaddress.h> + +#include <QtCore/qendian.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qhash.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qscopeguard.h> +#include <QtCore/qtimezone.h> +#include <QtCore/qvarlengtharray.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace QTlsPrivate { + +namespace { + +QByteArray asn1ObjectId(ASN1_OBJECT *object) +{ + if (!object) + return {}; + + char buf[80] = {}; // The openssl docs a buffer length of 80 should be more than enough + q_OBJ_obj2txt(buf, sizeof(buf), object, 1); // the 1 says always use the oid not the long name + + return QByteArray(buf); +} + +QByteArray asn1ObjectName(ASN1_OBJECT *object) +{ + if (!object) + return {}; + + const int nid = q_OBJ_obj2nid(object); + if (nid != NID_undef) + return QByteArray(q_OBJ_nid2sn(nid)); + + return asn1ObjectId(object); +} + +QMultiMap<QByteArray, QString> mapFromX509Name(X509_NAME *name) +{ + if (!name) + return {}; + + QMultiMap<QByteArray, QString> info; + for (int i = 0; i < q_X509_NAME_entry_count(name); ++i) { + X509_NAME_ENTRY *e = q_X509_NAME_get_entry(name, i); + + QByteArray name = asn1ObjectName(q_X509_NAME_ENTRY_get_object(e)); + unsigned char *data = nullptr; + int size = q_ASN1_STRING_to_UTF8(&data, q_X509_NAME_ENTRY_get_data(e)); + info.insert(name, QString::fromUtf8((char*)data, size)); + q_CRYPTO_free(data, nullptr, 0); + } + + return info; +} + +QDateTime dateTimeFromASN1(const ASN1_TIME *aTime) +{ + QDateTime result; + tm lTime; + + if (q_ASN1_TIME_to_tm(aTime, &lTime)) { + QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday); + QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec); + result = QDateTime(resDate, resTime, QTimeZone::UTC); + } + + return result; +} + + +#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" +#define ENDCERTSTRING "-----END CERTIFICATE-----" + +QByteArray x509ToQByteArray(X509 *x509, QSsl::EncodingFormat format) +{ + Q_ASSERT(x509); + + // Use i2d_X509 to convert the X509 to an array. + const int length = q_i2d_X509(x509, nullptr); + if (length <= 0) { + QTlsBackendOpenSSL::logAndClearErrorQueue(); + return {}; + } + + QByteArray array; + array.resize(length); + + char *data = array.data(); + char **dataP = &data; + unsigned char **dataPu = (unsigned char **)dataP; + if (q_i2d_X509(x509, dataPu) < 0) + return QByteArray(); + + if (format == QSsl::Der) + return array; + + // Convert to Base64 - wrap at 64 characters. + array = array.toBase64(); + QByteArray tmp; + for (int i = 0; i <= array.size() - 64; i += 64) { + tmp += QByteArray::fromRawData(array.data() + i, 64); + tmp += '\n'; + } + if (int remainder = array.size() % 64) { + tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); + tmp += '\n'; + } + + return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n"; +} + +QString x509ToText(X509 *x509) +{ + Q_ASSERT(x509); + + QByteArray result; + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) + return QString(); + const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); + + q_X509_print(bio, x509); + + QVarLengthArray<char, 16384> data; + int count = q_BIO_read(bio, data.data(), 16384); + if ( count > 0 ) + result = QByteArray( data.data(), count ); + + return QString::fromLatin1(result); +} + +QVariant x509UnknownExtensionToValue(X509_EXTENSION *ext) +{ + // Get the extension specific method object if available + // we cast away the const-ness here because some versions of openssl + // don't use const for the parameters in the functions pointers stored + // in the object. + Q_ASSERT(ext); + + X509V3_EXT_METHOD *meth = const_cast<X509V3_EXT_METHOD *>(q_X509V3_EXT_get(ext)); + if (!meth) { + ASN1_OCTET_STRING *value = q_X509_EXTENSION_get_data(ext); + Q_ASSERT(value); + QByteArray result( reinterpret_cast<const char *>(q_ASN1_STRING_get0_data(value)), + q_ASN1_STRING_length(value)); + return result; + } + + void *ext_internal = q_X509V3_EXT_d2i(ext); + if (!ext_internal) + return {}; + + const auto extCleaner = qScopeGuard([meth, ext_internal]{ + Q_ASSERT(ext_internal && meth); + + if (meth->it) + q_ASN1_item_free(static_cast<ASN1_VALUE *>(ext_internal), ASN1_ITEM_ptr(meth->it)); + else if (meth->ext_free) + meth->ext_free(ext_internal); + else + qCWarning(lcTlsBackend, "No method to free an unknown extension, a potential memory leak?"); + }); + + // If this extension can be converted + if (meth->i2v) { + STACK_OF(CONF_VALUE) *val = meth->i2v(meth, ext_internal, nullptr); + const auto stackCleaner = qScopeGuard([val]{ + if (val) + q_OPENSSL_sk_pop_free((OPENSSL_STACK *)val, (void(*)(void*))q_X509V3_conf_free); + }); + + QVariantMap map; + QVariantList list; + bool isMap = false; + + for (int j = 0; j < q_SKM_sk_num(val); j++) { + CONF_VALUE *nval = q_SKM_sk_value(CONF_VALUE, val, j); + if (nval->name && nval->value) { + isMap = true; + map[QString::fromUtf8(nval->name)] = QString::fromUtf8(nval->value); + } else if (nval->name) { + list << QString::fromUtf8(nval->name); + } else if (nval->value) { + list << QString::fromUtf8(nval->value); + } + } + + if (isMap) + return map; + else + return list; + } else if (meth->i2s) { + const char *hexString = meth->i2s(meth, ext_internal); + QVariant result(hexString ? QString::fromUtf8(hexString) : QString{}); + q_OPENSSL_free((void *)hexString); + return result; + } else if (meth->i2r) { + QByteArray result; + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) + return result; + + meth->i2r(meth, ext_internal, bio, 0); + + char *bio_buffer; + long bio_size = q_BIO_get_mem_data(bio, &bio_buffer); + result = QByteArray(bio_buffer, bio_size); + + q_BIO_free(bio); + return result; + } + + return QVariant(); +} + +/* + * Convert extensions to a variant. The naming of the keys of the map are + * taken from RFC 5280, however we decided the capitalisation in the RFC + * was too silly for the real world. + */ +QVariant x509ExtensionToValue(X509_EXTENSION *ext) +{ + ASN1_OBJECT *obj = q_X509_EXTENSION_get_object(ext); + int nid = q_OBJ_obj2nid(obj); + + // We cast away the const-ness here because some versions of openssl + // don't use const for the parameters in the functions pointers stored + // in the object. + X509V3_EXT_METHOD *meth = const_cast<X509V3_EXT_METHOD *>(q_X509V3_EXT_get(ext)); + + void *ext_internal = nullptr; // The value, returned by X509V3_EXT_d2i. + const auto extCleaner = qScopeGuard([meth, &ext_internal]() { + if (!meth || !ext_internal) + return; + + if (meth->it) + q_ASN1_item_free(static_cast<ASN1_VALUE *>(ext_internal), ASN1_ITEM_ptr(meth->it)); + else if (meth->ext_free) + meth->ext_free(ext_internal); + else + qWarning(lcTlsBackend, "Cannot free an extension, a potential memory leak?"); + }); + + const char * hexString = nullptr; // The value returned by meth->i2s. + const auto hexStringCleaner = qScopeGuard([&hexString](){ + if (hexString) + q_OPENSSL_free((void*)hexString); + }); + + switch (nid) { + case NID_basic_constraints: + { + BASIC_CONSTRAINTS *basic = reinterpret_cast<BASIC_CONSTRAINTS *>(q_X509V3_EXT_d2i(ext)); + if (!basic) + return {}; + QVariantMap result; + result["ca"_L1] = basic->ca ? true : false; + if (basic->pathlen) + result["pathLenConstraint"_L1] = (qlonglong)q_ASN1_INTEGER_get(basic->pathlen); + + q_BASIC_CONSTRAINTS_free(basic); + return result; + } + break; + case NID_info_access: + { + AUTHORITY_INFO_ACCESS *info = reinterpret_cast<AUTHORITY_INFO_ACCESS *>(q_X509V3_EXT_d2i(ext)); + if (!info) + return {}; + QVariantMap result; + for (int i=0; i < q_SKM_sk_num(info); i++) { + ACCESS_DESCRIPTION *ad = q_SKM_sk_value(ACCESS_DESCRIPTION, info, i); + + GENERAL_NAME *name = ad->location; + if (name->type == GEN_URI) { + int len = q_ASN1_STRING_length(name->d.uniformResourceIdentifier); + if (len < 0 || len >= 8192) { + // broken name + continue; + } + + const char *uriStr = reinterpret_cast<const char *>(q_ASN1_STRING_get0_data(name->d.uniformResourceIdentifier)); + const QString uri = QString::fromUtf8(uriStr, len); + + result[QString::fromUtf8(asn1ObjectName(ad->method))] = uri; + } else { + qCWarning(lcTlsBackend) << "Strange location type" << name->type; + } + } + + q_AUTHORITY_INFO_ACCESS_free(info); + return result; + } + break; + case NID_subject_key_identifier: + { + ext_internal = q_X509V3_EXT_d2i(ext); + if (!ext_internal) + return {}; + + hexString = meth->i2s(meth, ext_internal); + return QVariant(QString::fromUtf8(hexString)); + } + break; + case NID_authority_key_identifier: + { + AUTHORITY_KEYID *auth_key = reinterpret_cast<AUTHORITY_KEYID *>(q_X509V3_EXT_d2i(ext)); + if (!auth_key) + return {}; + QVariantMap result; + + // keyid + if (auth_key->keyid) { + QByteArray keyid(reinterpret_cast<const char *>(auth_key->keyid->data), + auth_key->keyid->length); + result["keyid"_L1] = keyid.toHex(); + } + + // issuer + // TODO: GENERAL_NAMES + + // serial + if (auth_key->serial) + result["serial"_L1] = (qlonglong)q_ASN1_INTEGER_get(auth_key->serial); + + q_AUTHORITY_KEYID_free(auth_key); + return result; + } + break; + } + + return {}; +} + +} // Unnamed namespace + +extern "C" int qt_X509Callback(int ok, X509_STORE_CTX *ctx) +{ + if (!ok) { + // Store the error and at which depth the error was detected. + using ErrorListPtr = QList<QSslErrorEntry> *; + ErrorListPtr errors = nullptr; + + // Error list is attached to either 'SSL' or 'X509_STORE'. + if (X509_STORE *store = q_X509_STORE_CTX_get0_store(ctx)) // We try store first: + errors = ErrorListPtr(q_X509_STORE_get_ex_data(store, 0)); + + if (!errors) { + // Not found on store? Try SSL and its external data then. According to the OpenSSL's + // documentation: + // + // "Whenever a X509_STORE_CTX object is created for the verification of the + // peer's certificate during a handshake, a pointer to the SSL object is + // stored into the X509_STORE_CTX object to identify the connection affected. + // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be + // used with the correct index." + + // TLSTODO: verification callback has to change as soon as TlsCryptographer is in place. + // This is a temporary solution for now to ease the transition. + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::errorOffsetInExData; + if (SSL *ssl = static_cast<SSL *>(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()))) + errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset)); + } + + if (!errors) { + qCWarning(lcTlsBackend, "Neither X509_STORE, nor SSL contains error list, verification failed"); + return 0; + } + + errors->append(X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); + } + // Always return OK to allow verification to continue. We handle the + // errors gracefully after collecting all errors, after verification has + // completed. + return 1; +} + +X509CertificateOpenSSL::X509CertificateOpenSSL() = default; + +X509CertificateOpenSSL::~X509CertificateOpenSSL() +{ + if (x509) + q_X509_free(x509); +} + +bool X509CertificateOpenSSL::isEqual(const X509Certificate &rhs) const +{ + //TLSTODO: to make it safe I'll check the backend type later. + const auto &other = static_cast<const X509CertificateOpenSSL &>(rhs); + if (x509 && other.x509) { + const int ret = q_X509_cmp(x509, other.x509); + if (ret >= -1 && ret <= 1) + return ret == 0; + QTlsBackendOpenSSL::logAndClearErrorQueue(); + } + + return false; +} + +bool X509CertificateOpenSSL::isSelfSigned() const +{ + if (!x509) + return false; + + return q_X509_check_issued(x509, x509) == X509_V_OK; +} + +QMultiMap<QSsl::AlternativeNameEntryType, QString> +X509CertificateOpenSSL::subjectAlternativeNames() const +{ + QMultiMap<QSsl::AlternativeNameEntryType, QString> result; + + if (!x509) + return result; + + auto *altNames = static_cast<STACK_OF(GENERAL_NAME) *>(q_X509_get_ext_d2i(x509, NID_subject_alt_name, + nullptr, nullptr)); + if (!altNames) + return result; + + auto altName = [](ASN1_IA5STRING *ia5, int len) { + const char *altNameStr = reinterpret_cast<const char *>(q_ASN1_STRING_get0_data(ia5)); + return QString::fromLatin1(altNameStr, len); + }; + + for (int i = 0; i < q_sk_GENERAL_NAME_num(altNames); ++i) { + const GENERAL_NAME *genName = q_sk_GENERAL_NAME_value(altNames, i); + if (genName->type != GEN_DNS && genName->type != GEN_EMAIL && genName->type != GEN_IPADD) + continue; + + const int len = q_ASN1_STRING_length(genName->d.ia5); + if (len < 0 || len >= 8192) { + // broken name + continue; + } + + switch (genName->type) { + case GEN_DNS: + result.insert(QSsl::DnsEntry, altName(genName->d.ia5, len)); + break; + case GEN_EMAIL: + result.insert(QSsl::EmailEntry, altName(genName->d.ia5, len)); + break; + case GEN_IPADD: { + QHostAddress ipAddress; + switch (len) { + case 4: // IPv4 + ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast<quint32 *>(genName->d.iPAddress->data))); + break; + case 16: // IPv6 + ipAddress = QHostAddress(reinterpret_cast<quint8 *>(genName->d.iPAddress->data)); + break; + default: // Unknown IP address format + break; + } + if (!ipAddress.isNull()) + result.insert(QSsl::IpAddressEntry, ipAddress.toString()); + break; + } + default: + break; + } + } + + q_OPENSSL_sk_pop_free((OPENSSL_STACK*)altNames, reinterpret_cast<void(*)(void*)>(q_GENERAL_NAME_free)); + + return result; +} + +TlsKey *X509CertificateOpenSSL::publicKey() const +{ + if (!x509) + return {}; + + return TlsKeyOpenSSL::publicKeyFromX509(x509); +} + +QByteArray X509CertificateOpenSSL::toPem() const +{ + if (!x509) + return {}; + + return x509ToQByteArray(x509, QSsl::Pem); +} + +QByteArray X509CertificateOpenSSL::toDer() const +{ + if (!x509) + return {}; + + return x509ToQByteArray(x509, QSsl::Der); + +} +QString X509CertificateOpenSSL::toText() const +{ + if (!x509) + return {}; + + return x509ToText(x509); +} + +Qt::HANDLE X509CertificateOpenSSL::handle() const +{ + return Qt::HANDLE(x509); +} + +size_t X509CertificateOpenSSL::hash(size_t seed) const noexcept +{ + if (x509) { + const EVP_MD *sha1 = q_EVP_sha1(); + unsigned int len = 0; + unsigned char md[EVP_MAX_MD_SIZE]; + q_X509_digest(x509, sha1, md, &len); + return qHashBits(md, len, seed); + } + + return seed; +} + +QSslCertificate X509CertificateOpenSSL::certificateFromX509(X509 *x509) +{ + QSslCertificate certificate; + + auto *backend = QTlsBackend::backend<X509CertificateOpenSSL>(certificate); + if (!backend || !x509) + return certificate; + + ASN1_TIME *nbef = q_X509_getm_notBefore(x509); + if (nbef) + backend->notValidBefore = dateTimeFromASN1(nbef); + + ASN1_TIME *naft = q_X509_getm_notAfter(x509); + if (naft) + backend->notValidAfter = dateTimeFromASN1(naft); + + backend->null = false; + backend->x509 = q_X509_dup(x509); + + backend->issuerInfoEntries = mapFromX509Name(q_X509_get_issuer_name(x509)); + backend->subjectInfoEntries = mapFromX509Name(q_X509_get_subject_name(x509)); + backend->versionString = QByteArray::number(qlonglong(q_X509_get_version(x509)) + 1); + + if (ASN1_INTEGER *serialNumber = q_X509_get_serialNumber(x509)) { + QByteArray hexString; + hexString.reserve(serialNumber->length * 3); + for (int a = 0; a < serialNumber->length; ++a) { + hexString += QByteArray::number(serialNumber->data[a], 16).rightJustified(2, '0'); + hexString += ':'; + } + hexString.chop(1); + backend->serialNumberString = hexString; + } + + backend->parseExtensions(); + + return certificate; +} + +QList<QSslCertificate> X509CertificateOpenSSL::stackOfX509ToQSslCertificates(STACK_OF(X509) *x509) +{ + if (!x509) + return {}; + + QList<QSslCertificate> certificates; + for (int i = 0; i < q_sk_X509_num(x509); ++i) { + if (X509 *entry = q_sk_X509_value(x509, i)) + certificates << certificateFromX509(entry); + } + + return certificates; +} + +QSslErrorEntry X509CertificateOpenSSL::errorEntryFromStoreContext(X509_STORE_CTX *ctx) +{ + Q_ASSERT(ctx); + + return {q_X509_STORE_CTX_get_error(ctx), q_X509_STORE_CTX_get_error_depth(ctx)}; +} + +QList<QSslError> X509CertificateOpenSSL::verify(const QList<QSslCertificate> &chain, + const QString &hostName) +{ + // This was previously QSslSocketPrivate::verify(). + auto roots = QSslConfiguration::defaultConfiguration().caCertificates(); +#ifndef Q_OS_WIN + // On Windows, system CA certificates are already set as default ones. + // No need to add them again (and again) and also, if the default configuration + // has its own set of CAs, this probably should not be amended by the ones + // from the 'ROOT' store, since it's not what an application chose to trust. + if (QSslSocketPrivate::rootCertOnDemandLoadingSupported()) + roots.append(QSslSocketPrivate::systemCaCertificates()); +#endif // Q_OS_WIN + return verify(roots, chain, hostName); +} + +QList<QSslError> X509CertificateOpenSSL::verify(const QList<QSslCertificate> &caCertificates, + const QList<QSslCertificate> &certificateChain, + const QString &hostName) +{ + // This was previously QSslSocketPrivate::verify(). + if (certificateChain.size() <= 0) + return {QSslError(QSslError::UnspecifiedError)}; + + QList<QSslError> errors; + X509_STORE *certStore = q_X509_STORE_new(); + if (!certStore) { + qCWarning(lcTlsBackend) << "Unable to create certificate store"; + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + const std::unique_ptr<X509_STORE, decltype(&q_X509_STORE_free)> storeGuard(certStore, q_X509_STORE_free); + + const QDateTime now = QDateTime::currentDateTimeUtc(); + 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: QSslContext::sharedFromConfiguration() + if (caCertificate.expiryDate() >= now) { + q_X509_STORE_add_cert(certStore, reinterpret_cast<X509 *>(caCertificate.handle())); + } + } + + QList<QSslErrorEntry> lastErrors; + if (!q_X509_STORE_set_ex_data(certStore, 0, &lastErrors)) { + qCWarning(lcTlsBackend) << "Unable to attach external data (error list) to a store"; + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + // Register a custom callback to get all verification errors. + q_X509_STORE_set_verify_cb(certStore, qt_X509Callback); + + // Build the chain of intermediate certificates + STACK_OF(X509) *intermediates = nullptr; + if (certificateChain.size() > 1) { + intermediates = (STACK_OF(X509) *) q_OPENSSL_sk_new_null(); + + if (!intermediates) { + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + bool first = true; + for (const QSslCertificate &cert : certificateChain) { + if (first) { + first = false; + continue; + } + + q_OPENSSL_sk_push((OPENSSL_STACK *)intermediates, reinterpret_cast<X509 *>(cert.handle())); + } + } + + X509_STORE_CTX *storeContext = q_X509_STORE_CTX_new(); + if (!storeContext) { + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + std::unique_ptr<X509_STORE_CTX, decltype(&q_X509_STORE_CTX_free)> ctxGuard(storeContext, q_X509_STORE_CTX_free); + + if (!q_X509_STORE_CTX_init(storeContext, certStore, reinterpret_cast<X509 *>(certificateChain[0].handle()), intermediates)) { + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + // Now we can actually perform the verification of the chain we have built. + // We ignore the result of this function since we process errors via the + // callback. + (void) q_X509_verify_cert(storeContext); + ctxGuard.reset(); + q_OPENSSL_sk_free((OPENSSL_STACK *)intermediates); + + // Now process the errors + + if (certificateChain[0].isBlacklisted()) + errors << QSslError(QSslError::CertificateBlacklisted, certificateChain[0]); + + // Check the certificate name against the hostname if one was specified + if (!hostName.isEmpty() && !TlsCryptograph::isMatchingHostname(certificateChain[0], hostName)) { + // No matches in common names or alternate names. + QSslError error(QSslError::HostNameMismatch, certificateChain[0]); + errors << error; + } + + // Translate errors from the error list into QSslErrors. + errors.reserve(errors.size() + lastErrors.size()); + for (const auto &error : std::as_const(lastErrors)) + errors << openSSLErrorToQSslError(error.code, certificateChain.value(error.depth)); + + return errors; +} + +QList<QSslCertificate> X509CertificateOpenSSL::certificatesFromPem(const QByteArray &pem, int count) +{ + QList<QSslCertificate> certificates; + + int offset = 0; + while (count == -1 || certificates.size() < count) { + int startPos = pem.indexOf(BEGINCERTSTRING, offset); + if (startPos == -1) + break; + startPos += sizeof(BEGINCERTSTRING) - 1; + if (!matchLineFeed(pem, &startPos)) + break; + + int endPos = pem.indexOf(ENDCERTSTRING, startPos); + if (endPos == -1) + break; + + offset = endPos + sizeof(ENDCERTSTRING) - 1; + if (offset < pem.size() && !matchLineFeed(pem, &offset)) + break; + + QByteArray decoded = QByteArray::fromBase64( + QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); + const unsigned char *data = (const unsigned char *)decoded.data(); + + if (X509 *x509 = q_d2i_X509(nullptr, &data, decoded.size())) { + certificates << certificateFromX509(x509); + q_X509_free(x509); + } + } + + return certificates; +} + +QList<QSslCertificate> X509CertificateOpenSSL::certificatesFromDer(const QByteArray &der, int count) +{ + QList<QSslCertificate> certificates; + + const unsigned char *data = (const unsigned char *)der.data(); + int size = der.size(); + + while (size > 0 && (count == -1 || certificates.size() < count)) { + if (X509 *x509 = q_d2i_X509(nullptr, &data, size)) { + certificates << certificateFromX509(x509); + q_X509_free(x509); + } else { + break; + } + size -= ((const char *)data - der.data()); + } + + return certificates; +} + +bool X509CertificateOpenSSL::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList<QSslCertificate> *caCertificates, + const QByteArray &passPhrase) +{ + // These are required + Q_ASSERT(device); + Q_ASSERT(key); + Q_ASSERT(cert); + + // Read the file into a BIO + QByteArray pkcs12data = device->readAll(); + if (pkcs12data.size() == 0) + return false; + + BIO *bio = q_BIO_new_mem_buf(const_cast<char *>(pkcs12data.constData()), pkcs12data.size()); + if (!bio) { + qCWarning(lcTlsBackend, "BIO_new_mem_buf returned null"); + return false; + } + const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); + + // Create the PKCS#12 object + PKCS12 *p12 = q_d2i_PKCS12_bio(bio, nullptr); + if (!p12) { + qCWarning(lcTlsBackend, "Unable to read PKCS#12 structure, %s", + q_ERR_error_string(q_ERR_get_error(), nullptr)); + return false; + } + const auto p12Raii = qScopeGuard([p12]{q_PKCS12_free(p12);}); + + // Extract the data + EVP_PKEY *pkey = nullptr; + X509 *x509 = nullptr; + STACK_OF(X509) *ca = nullptr; + + if (!q_PKCS12_parse(p12, passPhrase.constData(), &pkey, &x509, &ca)) { + qCWarning(lcTlsBackend, "Unable to parse PKCS#12 structure, %s", + q_ERR_error_string(q_ERR_get_error(), nullptr)); + return false; + } + + const auto x509Raii = qScopeGuard([x509]{q_X509_free(x509);}); + const auto keyRaii = qScopeGuard([pkey]{q_EVP_PKEY_free(pkey);}); + const auto caRaii = qScopeGuard([ca] { + q_OPENSSL_sk_pop_free(reinterpret_cast<OPENSSL_STACK *>(ca), + reinterpret_cast<void (*)(void *)>(q_X509_free)); + }); + + // Convert to Qt types + auto *tlsKey = QTlsBackend::backend<TlsKeyOpenSSL>(*key); + if (!tlsKey || !tlsKey->fromEVP_PKEY(pkey)) { + qCWarning(lcTlsBackend, "Unable to convert private key"); + return false; + } + + *cert = certificateFromX509(x509); + + if (caCertificates) + *caCertificates = stackOfX509ToQSslCertificates(ca); + + return true; +} + +QSslError X509CertificateOpenSSL::openSSLErrorToQSslError(int errorCode, const QSslCertificate &cert) +{ + QSslError error; + switch (errorCode) { + case X509_V_OK: + // X509_V_OK is also reported if the peer had no certificate. + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + error = QSslError(QSslError::UnableToGetIssuerCertificate, cert); break; + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + error = QSslError(QSslError::UnableToDecryptCertificateSignature, cert); break; + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + error = QSslError(QSslError::UnableToDecodeIssuerPublicKey, cert); break; + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + error = QSslError(QSslError::CertificateSignatureFailed, cert); break; + case X509_V_ERR_CERT_NOT_YET_VALID: + error = QSslError(QSslError::CertificateNotYetValid, cert); break; + case X509_V_ERR_CERT_HAS_EXPIRED: + error = QSslError(QSslError::CertificateExpired, cert); break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + error = QSslError(QSslError::InvalidNotBeforeField, cert); break; + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + error = QSslError(QSslError::InvalidNotAfterField, cert); break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + error = QSslError(QSslError::SelfSignedCertificate, cert); break; + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + error = QSslError(QSslError::SelfSignedCertificateInChain, cert); break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + error = QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert); break; + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + error = QSslError(QSslError::UnableToVerifyFirstCertificate, cert); break; + case X509_V_ERR_CERT_REVOKED: + error = QSslError(QSslError::CertificateRevoked, cert); break; + case X509_V_ERR_INVALID_CA: + error = QSslError(QSslError::InvalidCaCertificate, cert); break; + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + error = QSslError(QSslError::PathLengthExceeded, cert); break; + case X509_V_ERR_INVALID_PURPOSE: + error = QSslError(QSslError::InvalidPurpose, cert); break; + case X509_V_ERR_CERT_UNTRUSTED: + error = QSslError(QSslError::CertificateUntrusted, cert); break; + case X509_V_ERR_CERT_REJECTED: + error = QSslError(QSslError::CertificateRejected, cert); break; + default: + error = QSslError(QSslError::UnspecifiedError, cert); break; + } + return error; +} + +void X509CertificateOpenSSL::parseExtensions() +{ + extensions.clear(); + + if (!x509) + return; + + int count = q_X509_get_ext_count(x509); + if (count <= 0) + return; + + extensions.reserve(count); + + for (int i = 0; i < count; i++) { + X509_EXTENSION *ext = q_X509_get_ext(x509, i); + if (!ext) { + qCWarning(lcTlsBackend) << "Invalid (nullptr) extension at index" << i; + continue; + } + + extensions << convertExtension(ext); + } + + // Converting an extension may result in an error(s), clean them up: + QTlsBackendOpenSSL::clearErrorQueue(); +} + +X509CertificateBase::X509CertificateExtension X509CertificateOpenSSL::convertExtension(X509_EXTENSION *ext) +{ + Q_ASSERT(ext); + + X509CertificateExtension result; + + ASN1_OBJECT *obj = q_X509_EXTENSION_get_object(ext); + if (!obj) + return result; + + result.oid = QString::fromUtf8(asn1ObjectId(obj)); + result.name = QString::fromUtf8(asn1ObjectName(obj)); + + result.critical = bool(q_X509_EXTENSION_get_critical(ext)); + + // Lets see if we have custom support for this one + QVariant extensionValue = x509ExtensionToValue(ext); + if (extensionValue.isValid()) { + result.value = extensionValue; + result.supported = true; + return result; + } + + extensionValue = x509UnknownExtensionToValue(ext); + if (extensionValue.isValid()) + result.value = extensionValue; + + result.supported = false; + + return result; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qx509_openssl_p.h b/src/plugins/tls/openssl/qx509_openssl_p.h new file mode 100644 index 0000000000..3b2e06f343 --- /dev/null +++ b/src/plugins/tls/openssl/qx509_openssl_p.h @@ -0,0 +1,88 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QX509_OPENSSL_P_H +#define QX509_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> + +#include "../shared/qx509_base_p.h" + +#include <QtNetwork/private/qtlsbackend_p.h> + +#include <QtCore/qvariant.h> +#include <QtCore/qglobal.h> +#include <QtCore/qstring.h> + +#include "qopenssl_p.h" + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class X509CertificateOpenSSL final : public X509CertificateBase +{ +public: + X509CertificateOpenSSL(); + ~X509CertificateOpenSSL(); + + // TLSTODO: in future may become movable/copyable (ref-counted based + // OpenSSL's X509 implementation). + + bool isEqual(const X509Certificate &rhs) const override; + bool isSelfSigned() const override; + QMultiMap<QSsl::AlternativeNameEntryType, QString> subjectAlternativeNames() const override; + TlsKey *publicKey() const override; + + QByteArray toPem() const override; + QByteArray toDer() const override; + QString toText() const override; + Qt::HANDLE handle() const override; + + size_t hash(size_t seed) const noexcept override; + + static QSslCertificate certificateFromX509(X509 *x); + static QList<QSslCertificate> stackOfX509ToQSslCertificates(STACK_OF(X509) *x509); + static QSslErrorEntry errorEntryFromStoreContext(X509_STORE_CTX *ctx); + + static QList<QSslError> verify(const QList<QSslCertificate> &chain, const QString &hostName); + static QList<QSslError> verify(const QList<QSslCertificate> &caCertificates, + const QList<QSslCertificate> &certificateChain, + const QString &hostName); + + static QList<QSslCertificate> certificatesFromPem(const QByteArray &pem, int count); + static QList<QSslCertificate> certificatesFromDer(const QByteArray &der, int count); + static bool importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList<QSslCertificate> *caCertificates, + const QByteArray &passPhrase); + + static QSslError openSSLErrorToQSslError(int errorCode, const QSslCertificate &cert); +private: + void parseExtensions(); + static X509CertificateExtension convertExtension(X509_EXTENSION *ext); + + X509 *x509 = nullptr; + + Q_DISABLE_COPY_MOVE(X509CertificateOpenSSL) +}; + +extern "C" int qt_X509Callback(int ok, X509_STORE_CTX *ctx); + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509_OPENSSL_P_H |