diff options
author | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2021-03-04 19:20:18 +0100 |
---|---|---|
committer | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2021-03-17 16:25:37 +0100 |
commit | b477d823ada32f81457044c292af4099a6099cea (patch) | |
tree | 3beda1f80c21f79672215ea5d37a4414d62e41a3 /src/network/ssl | |
parent | fac23d695f9b9f9b172467eaa7f94102dac4dc25 (diff) |
Convert QSslSocket(Backend)Private into plugin
All backend-specific code is now separated and removed
from QSslSocket(Private) code. The original code is mostly
preserved to avoid (as much as possible) regressions (and
to simplify code-review).
Fixes: QTBUG-91173
Task-number: QTBUG-65922
Change-Id: I3ac4ba35d952162c8d6dc62d747cbd62dca0ef78
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
(cherry picked from commit 9391ba55149336c395b866b24dc9b844334d50da)
Diffstat (limited to 'src/network/ssl')
40 files changed, 3239 insertions, 2462 deletions
diff --git a/src/network/ssl/qdtls_openssl.cpp b/src/network/ssl/qdtls_openssl.cpp index a60a6cb53c..374839458c 100644 --- a/src/network/ssl/qdtls_openssl.cpp +++ b/src/network/ssl/qdtls_openssl.cpp @@ -44,7 +44,6 @@ #include "qsslpresharedkeyauthenticator_p.h" #include "qsslsocket_openssl_symbols_p.h" -#include "qsslsocket_openssl_p.h" #include "qsslcertificate_p.h" #include "qdtls_openssl_p.h" #include "qx509_openssl_p.h" @@ -198,7 +197,7 @@ extern "C" int q_generate_cookie_callback(SSL *ssl, unsigned char *dst, return 0; } - void *generic = q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData); + void *generic = q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData); if (!generic) { qCWarning(lcSsl, "SSL_get_ex_data returned nullptr, cannot generate cookie"); return 0; @@ -252,7 +251,7 @@ extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx) return 0; } - void *generic = q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData); + void *generic = q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData); if (!generic) { qCWarning(lcSsl, "SSL_get_ex_data returned nullptr, handshake failure"); return 0; @@ -273,7 +272,7 @@ extern "C" unsigned q_PSK_client_callback(SSL *ssl, const char *hint, char *iden unsigned max_psk_len) { auto *dtls = static_cast<dtlsopenssl::DtlsState *>(q_SSL_get_ex_data(ssl, - QSslSocketBackendPrivate::s_indexForSSLExtraData)); + QTlsBackendOpenSSL::s_indexForSSLExtraData)); if (!dtls) return 0; @@ -285,7 +284,7 @@ extern "C" unsigned q_PSK_server_callback(SSL *ssl, const char *identity, unsign unsigned max_psk_len) { auto *dtls = static_cast<dtlsopenssl::DtlsState *>(q_SSL_get_ex_data(ssl, - QSslSocketBackendPrivate::s_indexForSSLExtraData)); + QTlsBackendOpenSSL::s_indexForSSLExtraData)); if (!dtls) return 0; @@ -693,7 +692,7 @@ bool DtlsState::initCtxAndConnection(QDtlsBasePrivate *dtlsBase) } const int set = q_SSL_set_ex_data(newConnection.data(), - QSslSocketBackendPrivate::s_indexForSSLExtraData, + QTlsBackendOpenSSL::s_indexForSSLExtraData, this); if (set != 1 && configurationCopy->peerVerifyMode != QSslSocket::VerifyNone) { @@ -815,7 +814,7 @@ bool QDtlsClientVerifierOpenSSL::verifyClient(QUdpSocket *socket, const QByteArr 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, QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + setDtlsError(QDtlsError::TlsFatalError, QTlsBackendOpenSSL::getErrorsFromOpenSsl()); return false; } @@ -1031,7 +1030,7 @@ bool QDtlsPrivateOpenSSL::continueHandshake(QUdpSocket *socket, const QByteArray default: storePeerCertificates(); setDtlsError(QDtlsError::TlsFatalError, - QSslSocketBackendPrivate::msgErrorsDuringHandshake()); + QTlsBackendOpenSSL::msgErrorsDuringHandshake()); dtls.reset(); handshakeState = QDtls::HandshakeNotStarted; return false; @@ -1192,7 +1191,7 @@ qint64 QDtlsPrivateOpenSSL::writeDatagramEncrypted(QUdpSocket *socket, // 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(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + QString description(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); if (socket->error() != QAbstractSocket::UnknownSocketError && description.isEmpty()) { setDtlsError(QDtlsError::UnderlyingSocketError, socket->errorString()); } else { @@ -1258,7 +1257,7 @@ QByteArray QDtlsPrivateOpenSSL::decryptDatagram(QUdpSocket *socket, const QByteA default: setDtlsError(QDtlsError::TlsNonFatalError, QDtls::tr("Error while reading: %1") - .arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl())); + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); return dgram; } } @@ -1276,15 +1275,10 @@ unsigned QDtlsPrivateOpenSSL::pskClientCallback(const char *hint, char *identity if (hint) { identityHint.clear(); identityHint.append(hint); - // From the original code in QSslSocket: - // "it's NULL terminated, but do not include the NULL" == this fromRawData(ptr/size). - authenticator.d->identityHint = QByteArray::fromRawData(identityHint.constData(), - int(std::strlen(hint))); } - authenticator.d->maximumIdentityLength = int(max_identity_len) - 1; // needs to be NULL terminated - authenticator.d->maximumPreSharedKeyLength = int(max_psk_len); - + QTlsBackend::setupClientPskAuth(&authenticator, hint ? identityHint.constData() : nullptr, + hint ? std::strlen(hint) : 0, max_identity_len, max_psk_len); pskAuthenticator.swap(authenticator); } @@ -1314,11 +1308,8 @@ unsigned QDtlsPrivateOpenSSL::pskServerCallback(const char *identity, unsigned c { QSslPreSharedKeyAuthenticator authenticator; // Fill in some read-only fields (for the user) - authenticator.d->identityHint = dtlsConfiguration.preSharedKeyIdentityHint; - authenticator.d->identity = identity; - authenticator.d->maximumIdentityLength = 0; // user cannot set an identity - authenticator.d->maximumPreSharedKeyLength = int(max_psk_len); - + QTlsBackend::setupServerPskAuth(&authenticator, identity, dtlsConfiguration.preSharedKeyIdentityHint, + max_psk_len); pskAuthenticator.swap(authenticator); } @@ -1367,7 +1358,7 @@ bool QDtlsPrivateOpenSSL::verifyPeer() name = dtls.udpSocket->peerName(); } - if (!QSslSocketPrivate::isMatchingHostname(dtlsConfiguration.peerCertificate, name)) + if (!QTlsPrivate::TlsCryptograph::isMatchingHostname(dtlsConfiguration.peerCertificate, name)) errors << QSslError(QSslError::HostNameMismatch, dtlsConfiguration.peerCertificate); } @@ -1418,9 +1409,10 @@ void QDtlsPrivateOpenSSL::fetchNegotiatedParameters() { Q_ASSERT(dtls.tlsConnection.data()); - const SSL_CIPHER *cipher = q_SSL_get_current_cipher(dtls.tlsConnection.data()); - sessionCipher = cipher ? QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(cipher) - : QSslCipher(); + 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?). diff --git a/src/network/ssl/qdtls_openssl_p.h b/src/network/ssl/qdtls_openssl_p.h index 4f10a49b93..6e9e59cbd2 100644 --- a/src/network/ssl/qdtls_openssl_p.h +++ b/src/network/ssl/qdtls_openssl_p.h @@ -47,11 +47,12 @@ #include <openssl/ossl_typ.h> #include "qtlsbackend_openssl_p.h" +#include "qtls_openssl_p.h" #include "qdtls_base_p.h" #include "qdtls_p.h" #include <private/qsslcontext_openssl_p.h> -#include <private/qsslsocket_openssl_p.h> +#include <private/qopenssl_p.h> #include <QtNetwork/qsslpresharedkeyauthenticator.h> #include <QtNetwork/qhostaddress.h> @@ -185,7 +186,6 @@ private: 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, diff --git a/src/network/ssl/qocspresponse.h b/src/network/ssl/qocspresponse.h index 8f184924c7..2bceadf86e 100644 --- a/src/network/ssl/qocspresponse.h +++ b/src/network/ssl/qocspresponse.h @@ -72,6 +72,10 @@ enum class QOcspRevocationReason RemoveFromCRL }; +namespace QTlsPrivate { +class TlsCryptographOpenSSL; +} + class QOcspResponse; Q_NETWORK_EXPORT size_t qHash(const QOcspResponse &response, size_t seed = 0) noexcept; @@ -99,7 +103,7 @@ public: private: bool isEqual(const QOcspResponse &other) const; - friend class QSslSocketBackendPrivate; + friend class QTlsPrivate::TlsCryptographOpenSSL; friend bool operator==(const QOcspResponse &lhs, const QOcspResponse &rhs) { return lhs.isEqual(rhs); } friend bool operator!=(const QOcspResponse &lhs, const QOcspResponse &rhs) diff --git a/src/network/ssl/qopenssl.cpp b/src/network/ssl/qopenssl.cpp new file mode 100644 index 0000000000..1453b4d881 --- /dev/null +++ b/src/network/ssl/qopenssl.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2014 Governikus GmbH & Co. KG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** 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 "qtlsbackend_openssl_p.h" +#include "qopenssl_p.h" + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QTlsBackendOpenSSL, backendOpenSsl) + +void QSslSocketPrivate::registerAdHocFactory() +{ + // TLSTODO: this is a temporary solution, waiting for + // backends to move to ... plugins. + if (!backendOpenSsl()) + qCWarning(lcSsl, "Failed to create backend factory"); +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qopenssl_p.h index 1e79468958..fa3efb2c28 100644 --- a/src/network/ssl/qsslsocket_openssl_p.h +++ b/src/network/ssl/qopenssl_p.h @@ -67,10 +67,9 @@ // #include <QtNetwork/private/qtnetworkglobal_p.h> -#include "qsslsocket_p.h" -#include <QtCore/qlist.h> -#include <QtCore/qstring.h> +#include "qsslsocket_p.h" +#include "qsslcipher.h" #ifdef Q_OS_WIN #include <qt_windows.h> @@ -82,6 +81,8 @@ #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> @@ -100,92 +101,17 @@ #include <openssl/rsa.h> #include <openssl/crypto.h> #include <openssl/tls1.h> - -#if QT_CONFIG(opensslv11) #include <openssl/dh.h> -#endif QT_BEGIN_NAMESPACE struct QSslErrorEntry { - int code; - int depth; + int code = 0; + int depth = 0; }; Q_DECLARE_TYPEINFO(QSslErrorEntry, Q_PRIMITIVE_TYPE); -class QSslSocketBackendPrivate : public QSslSocketPrivate -{ - Q_DECLARE_PUBLIC(QSslSocket) -public: - QSslSocketBackendPrivate(); - virtual ~QSslSocketBackendPrivate(); - - // SSL context - bool initSslContext(); - void destroySslContext(); - SSL *ssl; - BIO *readBio; - BIO *writeBio; - SSL_SESSION *session; - QList<QSslErrorEntry> errorList; - static int s_indexForSSLExtraData; // index used in SSL_get_ex_data to get the matching QSslSocketBackendPrivate - enum ExDataOffset { - errorOffsetInExData = 1, - socketOffsetInExData = 2 - }; - - bool inSetAndEmitError = false; - - // Platform specific functions - void startClientEncryption() override; - void startServerEncryption() override; - void transmit() override; - bool startHandshake(); - void disconnectFromHost() override; - void disconnected() override; - QSslCipher sessionCipher() const override; - QSsl::SslProtocol sessionProtocol() const override; - void continueHandshake() override; - bool checkSslErrors(); - void storePeerCertificates(); - int handleNewSessionTicket(SSL *context); - unsigned int tlsPskClientCallback(const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); - unsigned int tlsPskServerCallback(const char *identity, unsigned char *psk, unsigned int max_psk_len); -#ifdef Q_OS_WIN - void fetchCaRootForCert(const QSslCertificate &cert); - void _q_caRootLoaded(QSslCertificate,QSslCertificate) override; -#endif - -#if QT_CONFIG(ocsp) - bool checkOcspStatus(); -#endif - - void alertMessageSent(int encoded); - void alertMessageReceived(int encoded); - - int emitErrorFromCallback(X509_STORE_CTX *ctx); - void trySendFatalAlert(); - - bool pendingFatalAlert = false; - bool errorsReportedFromCallback = false; - - // This decription will go to setErrorAndEmit(SslHandshakeError, ocspErrorDescription) - QString ocspErrorDescription; - // These will go to sslErrors() - QList<QSslError> ocspErrors; - QByteArray ocspResponseDer; - - Q_AUTOTEST_EXPORT static long setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions); - static QSslCipher QSslCipher_from_SSL_CIPHER(const SSL_CIPHER *cipher); - static QList<QSslError> verify(const QList<QSslCertificate> &certificateChain, const QString &hostName); - static QList<QSslError> verify(const QList<QSslCertificate> &cas, const QList<QSslCertificate> &certificateChain, - const QString &hostName); - static QString getErrorsFromOpenSsl(); - static void logAndClearErrorQueue(); - static QString msgErrorsDuringHandshake(); -}; - QT_END_NAMESPACE #endif diff --git a/src/network/ssl/qsslcertificate.h b/src/network/ssl/qsslcertificate.h index 371bf6264a..e8349d502b 100644 --- a/src/network/ssl/qsslcertificate.h +++ b/src/network/ssl/qsslcertificate.h @@ -153,8 +153,6 @@ public: private: QExplicitlySharedDataPointer<QSslCertificatePrivate> d; - friend class QSslCertificatePrivate; - friend class QSslSocketBackendPrivate; friend class QTlsBackend; friend Q_NETWORK_EXPORT size_t qHash(const QSslCertificate &key, size_t seed) noexcept; diff --git a/src/network/ssl/qsslcertificate_p.h b/src/network/ssl/qsslcertificate_p.h index e7f5efe4ed..95d58dcf49 100644 --- a/src/network/ssl/qsslcertificate_p.h +++ b/src/network/ssl/qsslcertificate_p.h @@ -71,10 +71,8 @@ public: ~QSslCertificatePrivate(); QList<QSslCertificateExtension> extensions() const; - static bool isBlacklisted(const QSslCertificate &certificate); - static QByteArray subjectInfoToString(QSslCertificate::SubjectInfo info); - - friend class QSslSocketBackendPrivate; + Q_NETWORK_PRIVATE_EXPORT static bool isBlacklisted(const QSslCertificate &certificate); + Q_NETWORK_PRIVATE_EXPORT static QByteArray subjectInfoToString(QSslCertificate::SubjectInfo info); QAtomicInt ref; std::unique_ptr<QTlsPrivate::X509Certificate> backend; diff --git a/src/network/ssl/qsslcipher.h b/src/network/ssl/qsslcipher.h index 05692aca16..bc54b1cf91 100644 --- a/src/network/ssl/qsslcipher.h +++ b/src/network/ssl/qsslcipher.h @@ -85,7 +85,7 @@ public: private: // ### Qt 7: make implicitly shared std::unique_ptr<QSslCipherPrivate> d; - friend class QSslSocketBackendPrivate; + friend class QTlsBackend; }; Q_DECLARE_SHARED(QSslCipher) diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h index 116920950e..716ea8ed7f 100644 --- a/src/network/ssl/qsslconfiguration.h +++ b/src/network/ssl/qsslconfiguration.h @@ -201,7 +201,6 @@ public: private: friend class QSslSocket; friend class QSslConfigurationPrivate; - friend class QSslSocketBackendPrivate; friend class QSslContext; friend class QDtlsBasePrivate; friend class dtlsopenssl::DtlsState; diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h index 3d416b42d7..9a70fda653 100644 --- a/src/network/ssl/qsslconfiguration_p.h +++ b/src/network/ssl/qsslconfiguration_p.h @@ -79,7 +79,7 @@ QT_BEGIN_NAMESPACE -class QSslConfigurationPrivate: public QSharedData +class Q_NETWORK_PRIVATE_EXPORT QSslConfigurationPrivate: public QSharedData { public: QSslConfigurationPrivate() @@ -114,11 +114,11 @@ public: bool allowRootCertOnDemandLoading; bool peerSessionShared; - Q_AUTOTEST_EXPORT static bool peerSessionWasShared(const QSslConfiguration &configuration); + static bool peerSessionWasShared(const QSslConfiguration &configuration); QSsl::SslOptions sslOptions; - Q_AUTOTEST_EXPORT static const QSsl::SslOptions defaultSslOptions; + static const QSsl::SslOptions defaultSslOptions; QList<QSslEllipticCurve> ellipticCurves; diff --git a/src/network/ssl/qsslcontext_openssl.cpp b/src/network/ssl/qsslcontext_openssl.cpp index a8d92bd80d..de7ef15d10 100644 --- a/src/network/ssl/qsslcontext_openssl.cpp +++ b/src/network/ssl/qsslcontext_openssl.cpp @@ -43,12 +43,13 @@ #include <QtNetwork/qsslsocket.h> #include <QtNetwork/qssldiffiehellmanparameters.h> +#include "private/qopenssl_p.h" #include "private/qssl_p.h" #include "private/qsslsocket_p.h" #include "private/qsslcontext_openssl_p.h" -#include "private/qsslsocket_openssl_p.h" #include "private/qsslsocket_openssl_symbols_p.h" #include "private/qssldiffiehellmanparameters_p.h" +#include "private/qtlsbackend_openssl_p.h" #include <vector> @@ -61,10 +62,17 @@ Q_NETWORK_EXPORT void qt_ForceTlsSecurityLevel() *forceSecurityLevel() = true; } -// defined in qsslsocket_openssl.cpp: -extern int q_X509Callback(int ok, X509_STORE_CTX *ctx); +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); -extern QString getErrorsFromOpenSsl(); + +#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: @@ -82,9 +90,6 @@ extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie, extern "C" int q_ssl_sess_set_new_cb(SSL *context, SSL_SESSION *session); #endif // TLS1_3_VERSION -// Defined in qsslsocket.cpp -QList<QSslCipher> q_getDefaultDtlsCiphers(); - static inline QString msgErrorSettingBackendConfig(const QString &why) { return QSslSocket::tr("Error when setting the OpenSSL configuration (%1)").arg(why); @@ -95,6 +100,56 @@ static inline QString msgErrorSettingEllipticCurves(const QString &why) return QSslSocket::tr("Error when setting the elliptic curves (%1)").arg(why); } +long QSslContext::setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions) +{ + long options; + switch (protocol) { + case QSsl::SecureProtocols: + 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; + 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), @@ -130,6 +185,12 @@ QSharedPointer<QSslContext> QSslContext::sharedFromConfiguration(QSslSocket::Ssl return sslContext; } +QSharedPointer<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, @@ -335,7 +396,7 @@ init_context: } sslContext->errorStr = QSslSocket::tr("Error creating SSL context (%1)").arg( - unsupportedProtocol ? QSslSocket::tr("unsupported protocol") : QSslSocketBackendPrivate::getErrorsFromOpenSsl() + unsupportedProtocol ? QSslSocket::tr("unsupported protocol") : QTlsBackendOpenSSL::getErrorsFromOpenSsl() ); sslContext->errorCode = QSslError::UnspecifiedError; return; @@ -438,7 +499,7 @@ init_context: } // Enable bug workarounds. - long options = QSslSocketBackendPrivate::setupOpenSslOptions(configuration.protocol(), configuration.d->sslOptions); + const long options = setupOpenSslOptions(configuration.protocol(), configuration.d->sslOptions); q_SSL_CTX_set_options(sslContext->ctx, options); // Tell OpenSSL to release memory early @@ -464,13 +525,13 @@ init_context: // Initialize ciphers QList<QSslCipher> ciphers = sslContext->sslConfiguration.ciphers(); if (ciphers.isEmpty()) - ciphers = isDtls ? q_getDefaultDtlsCiphers() : QSslSocketPrivate::defaultCiphers(); + 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(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); sslContext->errorCode = QSslError::UnspecifiedError; return; } @@ -480,7 +541,7 @@ init_context: #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(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); sslContext->errorCode = QSslError::UnspecifiedError; return; } @@ -513,7 +574,7 @@ init_context: } } - if (QSslSocketPrivate::s_loadRootCertsOnDemand && allowRootCertOnDemandLoading) { + 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; @@ -529,7 +590,7 @@ init_context: } #endif // OPENSSL_VERSION_MAJOR if (success != 1) { - const auto qtErrors = QSslSocketBackendPrivate::getErrorsFromOpenSsl(); + const auto qtErrors = QTlsBackendOpenSSL::getErrorsFromOpenSsl(); qCWarning(lcSsl) << "An error encountered while to set root certificates location:" << qtErrors; } @@ -545,7 +606,7 @@ init_context: // 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(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + sslContext->errorStr = QSslSocket::tr("Error loading local certificate, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); sslContext->errorCode = QSslError::UnspecifiedError; return; } @@ -572,14 +633,14 @@ init_context: 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(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + 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(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + sslContext->errorStr = QSslSocket::tr("Private key does not certify public key, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); sslContext->errorCode = QSslError::UnspecifiedError; return; } @@ -605,10 +666,10 @@ init_context: #if QT_CONFIG(dtls) isDtls ? dtlscallbacks::q_X509DtlsCallback : #endif // dtls - q_X509Callback; + QTlsPrivate::q_X509Callback; if (!isDtls && configuration.handshakeMustInterruptOnError()) - verificationCallback = q_X509CallbackDirect; + verificationCallback = QTlsPrivate::q_X509CallbackDirect; auto verificationMode = SSL_VERIFY_PEER; if (!isDtls && sslContext->sslConfiguration.missingCertificateIsFatal()) @@ -680,7 +741,7 @@ init_context: 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(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + sslContext->errorStr = msgErrorSettingEllipticCurves(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); sslContext->errorCode = QSslError::UnspecifiedError; return; } @@ -690,10 +751,6 @@ init_context: applyBackendConfig(sslContext); } -#if QT_CONFIG(ocsp) -extern "C" int qt_OCSP_status_server_callback(SSL *ssl, void *); // Defined in qsslsocket_openssl.cpp. -#endif // ocsp -// static void QSslContext::applyBackendConfig(QSslContext *sslContext) { const QMap<QByteArray, QVariant> &conf = sslContext->sslConfiguration.backendConfiguration(); @@ -706,7 +763,7 @@ void QSslContext::applyBackendConfig(QSslContext *sslContext) // 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, qt_OCSP_status_server_callback); + q_SSL_CTX_set_tlsext_status_cb(sslContext->ctx, QTlsPrivate::qt_OCSP_status_server_callback); if (conf.size() == 1) return; } diff --git a/src/network/ssl/qsslcontext_openssl_p.h b/src/network/ssl/qsslcontext_openssl_p.h index 5385c42240..4b343e7842 100644 --- a/src/network/ssl/qsslcontext_openssl_p.h +++ b/src/network/ssl/qsslcontext_openssl_p.h @@ -73,6 +73,9 @@ public: bool allowRootCertOnDemandLoading); static QSharedPointer<QSslContext> sharedFromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading); + static QSharedPointer<QSslContext> sharedFromPrivateConfiguration(QSslSocket::SslMode mode, QSslConfigurationPrivate *privConfiguration, + bool allowRootCertOnDemandLoading); + static long setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions); QSslError::SslError error() const; QString errorString() const; diff --git a/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp b/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp index 01dca50720..8e0458de4e 100644 --- a/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp +++ b/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp @@ -69,22 +69,22 @@ int q_DH_check(DH *dh, int *status) EVP_PKEY *key = q_EVP_PKEY_new(); if (!key) { qCWarning(lcSsl, "EVP_PKEY_new failed"); - QSslSocketBackendPrivate::logAndClearErrorQueue(); + QTlsBackendOpenSSL::logAndClearErrorQueue(); return 0; } const auto keyDeleter = qScopeGuard([key](){ q_EVP_PKEY_free(key); }); if (!q_EVP_PKEY_set1_DH(key, dh)) { - qCWarning(lcSsl, "EVP_PKEY_set1_DH failed"); - QSslSocketBackendPrivate::logAndClearErrorQueue(); + qCWarning(lcTlsBackend, "EVP_PKEY_set1_DH failed"); + QTlsBackendOpenSSL::logAndClearErrorQueue(); return 0; } EVP_PKEY_CTX *keyCtx = q_EVP_PKEY_CTX_new(key, nullptr); if (!keyCtx) { - qCWarning(lcSsl, "EVP_PKEY_CTX_new failed"); - QSslSocketBackendPrivate::logAndClearErrorQueue(); + qCWarning(lcTlsBackend, "EVP_PKEY_CTX_new failed"); + QTlsBackendOpenSSL::logAndClearErrorQueue(); return 0; } const auto ctxDeleter = qScopeGuard([keyCtx]{ @@ -92,7 +92,7 @@ int q_DH_check(DH *dh, int *status) }); const int result = q_EVP_PKEY_param_check(keyCtx); - QSslSocketBackendPrivate::logAndClearErrorQueue(); + QTlsBackendOpenSSL::logAndClearErrorQueue(); // Note: unlike DH_check, we cannot obtain the 'status', // if the 'result' is 0 (actually the result is 1 only // if this 'status' was 0). We could probably check the diff --git a/src/network/ssl/qsslellipticcurve.h b/src/network/ssl/qsslellipticcurve.h index 2906891fdc..c8ead24433 100644 --- a/src/network/ssl/qsslellipticcurve.h +++ b/src/network/ssl/qsslellipticcurve.h @@ -82,7 +82,6 @@ private: friend class QSslContext; friend class QSslSocketPrivate; - friend class QSslSocketBackendPrivate; }; Q_DECLARE_TYPEINFO(QSslEllipticCurve, Q_PRIMITIVE_TYPE); diff --git a/src/network/ssl/qsslkey.h b/src/network/ssl/qsslkey.h index a6e930bad0..d9df2686e6 100644 --- a/src/network/ssl/qsslkey.h +++ b/src/network/ssl/qsslkey.h @@ -94,8 +94,6 @@ public: private: QExplicitlySharedDataPointer<QSslKeyPrivate> d; - friend class QSslCertificate; - friend class QSslSocketBackendPrivate; friend class QTlsBackend; }; diff --git a/src/network/ssl/qsslpresharedkeyauthenticator.h b/src/network/ssl/qsslpresharedkeyauthenticator.h index 7b703d5020..41112e3e43 100644 --- a/src/network/ssl/qsslpresharedkeyauthenticator.h +++ b/src/network/ssl/qsslpresharedkeyauthenticator.h @@ -50,7 +50,6 @@ QT_REQUIRE_CONFIG(ssl); QT_BEGIN_NAMESPACE class QSslPreSharedKeyAuthenticatorPrivate; - class QSslPreSharedKeyAuthenticator { public: @@ -76,8 +75,7 @@ public: private: Q_NETWORK_EXPORT bool isEqual(const QSslPreSharedKeyAuthenticator &other) const; - friend class QSslSocketBackendPrivate; - friend class QDtlsPrivateOpenSSL; + friend class QTlsBackend; friend bool operator==(const QSslPreSharedKeyAuthenticator &lhs, const QSslPreSharedKeyAuthenticator &rhs) { return lhs.isEqual(rhs); } diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 9a02f439ab..91f8cb4528 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -386,16 +386,8 @@ #include "qsslcipher.h" #include "qocspresponse.h" #include "qtlsbackend_p.h" -#ifndef QT_NO_OPENSSL -#include "qsslsocket_openssl_p.h" -#endif -#ifdef QT_SECURETRANSPORT -#include "qsslsocket_mac_p.h" -#endif -#if QT_CONFIG(schannel) -#include "qsslsocket_schannel_p.h" -#endif #include "qsslconfiguration_p.h" +#include "qsslsocket_p.h" #include <QtCore/qdebug.h> #include <QtCore/qdir.h> @@ -433,7 +425,7 @@ Q_GLOBAL_STATIC(QSslSocketGlobalData, globalData) set to the one returned by the static method defaultCiphers(). */ QSslSocket::QSslSocket(QObject *parent) - : QTcpSocket(*new QSslSocketBackendPrivate, parent) + : QTcpSocket(*new QSslSocketPrivate, parent) { Q_D(QSslSocket); #ifdef QSSLSOCKET_DEBUG @@ -903,7 +895,8 @@ void QSslSocket::close() // On Windows, CertGetCertificateChain is probably still doing its // job, if the socket is re-used, we want to ignore its reported // root CA. - d->caToFetch = QSslCertificate{}; + if (auto *backend = d->backend.get()) + backend->cancelCAFetch(); if (!d->abortCalled && (encryptedBytesToWrite() || !d->writeBuffer.isEmpty())) flush(); @@ -1210,7 +1203,9 @@ QSsl::SslProtocol QSslSocket::sessionProtocol() const QList<QOcspResponse> QSslSocket::ocspResponses() const { Q_D(const QSslSocket); - return d->ocspResponses; + if (const auto *backend = d->backend.get()) + return backend->ocsps(); + return {}; } /*! @@ -1485,7 +1480,9 @@ bool QSslSocket::waitForDisconnected(int msecs) QList<QSslError> QSslSocket::sslHandshakeErrors() const { Q_D(const QSslSocket); - return d->sslErrors; + if (const auto *backend = d->backend.get()) + return backend->tlsErrors(); + return {}; } /*! @@ -1502,12 +1499,14 @@ bool QSslSocket::supportsSsl() \since 5.0 Returns the version number of the SSL library in use. Note that this is the version of the library in use at run-time not compile - time. If no SSL support is available then this will return an - undefined value. + time. If no SSL support is available then this will return -1. */ long QSslSocket::sslLibraryVersionNumber() { - return QSslSocketPrivate::sslLibraryVersionNumber(); + if (const auto *tlsBackend = QSslSocketPrivate::tlsBackendInUse()) + return tlsBackend->tlsLibraryVersionNumber(); + + return -1; } /*! @@ -1518,20 +1517,23 @@ long QSslSocket::sslLibraryVersionNumber() */ QString QSslSocket::sslLibraryVersionString() { - return QSslSocketPrivate::sslLibraryVersionString(); + if (const auto *tlsBackend = QSslSocketPrivate::tlsBackendInUse()) + return tlsBackend->tlsLibraryVersionString(); + return {}; } /*! \since 5.4 Returns the version number of the SSL library in use at compile - time. If no SSL support is available then this will return an - undefined value. + time. If no SSL support is available then this will return -1. \sa sslLibraryVersionNumber() */ long QSslSocket::sslLibraryBuildVersionNumber() { - return QSslSocketPrivate::sslLibraryBuildVersionNumber(); + if (const auto *tlsBackend = QSslSocketPrivate::tlsBackendInUse()) + return tlsBackend->tlsLibraryBuildVersionNumber(); + return -1; } /*! @@ -1544,7 +1546,10 @@ long QSslSocket::sslLibraryBuildVersionNumber() */ QString QSslSocket::sslLibraryBuildVersionString() { - return QSslSocketPrivate::sslLibraryBuildVersionString(); + if (const auto *tlsBackend = QSslSocketPrivate::tlsBackendInUse()) + return tlsBackend->tlsLibraryBuildVersionString(); + + return {}; } /*! @@ -1857,7 +1862,8 @@ void QSslSocket::ignoreSslErrors(const QList<QSslError> &errors) void QSslSocket::continueInterruptedHandshake() { Q_D(QSslSocket); - d->handshakeInterrupted = false; + if (auto *backend = d->backend.get()) + backend->enableHandshakeContinuation(); } /*! @@ -1914,7 +1920,8 @@ void QSslSocket::disconnectFromHost() } // Make sure we don't process any signal from the CA fetcher // (Windows): - d->caToFetch = QSslCertificate{}; + if (auto *backend = d->backend.get()) + backend->cancelCAFetch(); // Perhaps emit closing() if (d->state != ClosingState) { @@ -1982,6 +1989,8 @@ qint64 QSslSocket::writeData(const char *data, qint64 len) return len; } +bool QSslSocketPrivate::s_loadRootCertsOnDemand = false; + /*! \internal */ @@ -1990,7 +1999,6 @@ QSslSocketPrivate::QSslSocketPrivate() , mode(QSslSocket::UnencryptedMode) , autoStartHandshake(false) , connectionEncrypted(false) - , shutdown(false) , ignoreAllSslErrors(false) , readyReadEmittedPointer(nullptr) , allowRootCertOnDemandLoading(true) @@ -1999,6 +2007,17 @@ QSslSocketPrivate::QSslSocketPrivate() , flushTriggered(false) { QSslConfigurationPrivate::deepCopyDefaultConfiguration(&configuration); + + const auto *tlsBackend = tlsBackendInUse(); + if (!tlsBackend) { + qCWarning(lcSsl, "No TLS backend is available"); + return; + } + backend.reset(tlsBackend->createTlsCryptograph()); + if (!backend.get()) { + qCWarning(lcSsl) << "The backend named" << tlsBackend->backendName() + << "does not support TLS"; + } } /*! @@ -2011,28 +2030,54 @@ QSslSocketPrivate::~QSslSocketPrivate() /*! \internal */ +bool QSslSocketPrivate::supportsSsl() +{ + if (const auto *tlsBackend = tlsBackendInUse()) + return tlsBackend->implementedClasses().contains(QSsl::ImplementedClass::Socket); + return false; +} + +/*! + \internal + + Declared static in QSslSocketPrivate, makes sure the SSL libraries have + been initialized. +*/ +void QSslSocketPrivate::ensureInitialized() +{ + if (!supportsSsl()) + return; + + const auto *tlsBackend = tlsBackendInUse(); + Q_ASSERT(tlsBackend); + tlsBackend->ensureInitialized(); +} + +/*! + \internal +*/ void QSslSocketPrivate::init() { + // TLSTODO: delete those data members. mode = QSslSocket::UnencryptedMode; autoStartHandshake = false; connectionEncrypted = false; ignoreAllSslErrors = false; - shutdown = false; abortCalled = false; pendingClose = false; flushTriggered = false; - ocspResponses.clear(); - systemOrSslErrorDetected = false; - // we don't want to clear the ignoreErrorsList, so - // that it is possible setting it before connecting -// ignoreErrorsList.clear(); + // We don't want to clear the ignoreErrorsList, so + // that it is possible setting it before connecting. buffer.clear(); writeBuffer.clear(); configuration.peerCertificate.clear(); configuration.peerCertificateChain.clear(); - fetchAuthorityInformation = false; - caToFetch = QSslCertificate{}; + + if (backend.get()) { + Q_ASSERT(q_ptr); + backend->init(static_cast<QSslSocket *>(q_ptr), this); + } } /*! @@ -2103,7 +2148,35 @@ void QSslSocketPrivate::setDefaultSupportedCiphers(const QList<QSslCipher> &ciph /*! \internal */ -void q_setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers) +void QSslSocketPrivate::resetDefaultEllipticCurves() +{ + const auto *tlsBackend = tlsBackendInUse(); + if (!tlsBackend) + return; + + auto ids = tlsBackend->ellipticCurvesIds(); + if (!ids.size()) + return; + + QList<QSslEllipticCurve> curves; + curves.reserve(ids.size()); + for (int id : ids) { + QSslEllipticCurve curve; + curve.id = id; + curves.append(curve); + } + + // Set the list of supported ECs, but not the list + // of *default* ECs. OpenSSL doesn't like forcing an EC for the wrong + // ciphersuite, so don't try it -- leave the empty list to mean + // "the implementation will choose the most suitable one". + setDefaultSupportedEllipticCurves(curves); +} + +/*! + \internal +*/ +void QSslSocketPrivate::setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers) { QMutexLocker locker(&globalData()->mutex); globalData()->dtlsConfig.detach(); @@ -2113,7 +2186,7 @@ void q_setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers) /*! \internal */ -QList<QSslCipher> q_getDefaultDtlsCiphers() +QList<QSslCipher> QSslSocketPrivate::defaultDtlsCiphers() { QSslSocketPrivate::ensureInitialized(); QMutexLocker locker(&globalData()->mutex); @@ -2360,6 +2433,11 @@ bool QSslSocketPrivate::isPaused() const return paused; } +void QSslSocketPrivate::setPaused(bool p) +{ + paused = p; +} + bool QSslSocketPrivate::bind(const QHostAddress &address, quint16 port, QAbstractSocket::BindMode mode) { // this function is called from QAbstractSocket::bind @@ -2590,6 +2668,7 @@ void QSslSocketPrivate::_q_resumeImplementation() if (verifyErrorsHaveBeenIgnored()) { continueHandshake(); } else { + const auto sslErrors = backend->tlsErrors(); Q_ASSERT(!sslErrors.isEmpty()); setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString()); plainSocket->disconnectFromHost(); @@ -2604,11 +2683,14 @@ void QSslSocketPrivate::_q_resumeImplementation() */ bool QSslSocketPrivate::verifyErrorsHaveBeenIgnored() { + Q_ASSERT(backend.get()); + bool doEmitSslError; if (!ignoreErrorsList.empty()) { // check whether the errors we got are all in the list of expected errors // (applies only if the method QSslSocket::ignoreSslErrors(const QList<QSslError> &errors) // was called) + const auto &sslErrors = backend->tlsErrors(); doEmitSslError = false; for (int a = 0; a < sslErrors.count(); a++) { if (!ignoreErrorsList.contains(sslErrors.at(a))) { @@ -2628,6 +2710,91 @@ bool QSslSocketPrivate::verifyErrorsHaveBeenIgnored() /*! \internal */ +bool QSslSocketPrivate::isAutoStartingHandshake() const +{ + return autoStartHandshake; +} + +/*! + \internal +*/ +bool QSslSocketPrivate::isPendingClose() const +{ + return pendingClose; +} + +/*! + \internal +*/ +void QSslSocketPrivate::setPendingClose(bool pc) +{ + pendingClose = pc; +} + +/*! + \internal +*/ +qint64 QSslSocketPrivate::maxReadBufferSize() const +{ + return readBufferMaxSize; +} + +/*! + \internal +*/ +void QSslSocketPrivate::setMaxReadBufferSize(qint64 maxSize) +{ + readBufferMaxSize = maxSize; +} + +/*! + \internal +*/ +void QSslSocketPrivate::setEncrypted(bool enc) +{ + connectionEncrypted = enc; +} + +/*! + \internal +*/ +QIODevicePrivate::QRingBufferRef &QSslSocketPrivate::tlsWriteBuffer() +{ + return writeBuffer; +} + +/*! + \internal +*/ +QIODevicePrivate::QRingBufferRef &QSslSocketPrivate::tlsBuffer() +{ + return buffer; +} + +/*! + \internal +*/ +bool &QSslSocketPrivate::tlsEmittedBytesWritten() +{ + return emittedBytesWritten; +} + +/*! + \internal +*/ +bool *QSslSocketPrivate::readyReadPointer() +{ + return readyReadEmittedPointer; +} + +bool QSslSocketPrivate::hasUndecryptedData() const +{ + return backend.get() && backend->hasUndecryptedData(); +} + +/*! + \internal +*/ qint64 QSslSocketPrivate::peek(char *data, qint64 maxSize) { if (mode == QSslSocket::UnencryptedMode && !autoStartHandshake) { @@ -2643,9 +2810,9 @@ qint64 QSslSocketPrivate::peek(char *data, qint64 maxSize) if (r2 < 0) return (r > 0 ? r : r2); return r + r2; - } else { - return -1; } + + return -1; } else { //encrypted mode - the socket engine will read and decrypt data into the QIODevice buffer return QTcpSocketPrivate::peek(data, maxSize); @@ -2668,8 +2835,8 @@ QByteArray QSslSocketPrivate::peek(qint64 maxSize) //peek at data in the plain socket if (plainSocket) return ret + plainSocket->peek(maxSize - ret.length()); - else - return QByteArray(); + + return QByteArray(); } else { //encrypted mode - the socket engine will read and decrypt data into the QIODevice buffer return QTcpSocketPrivate::peek(maxSize); @@ -2711,6 +2878,82 @@ bool QSslSocketPrivate::flush() /*! \internal */ +void QSslSocketPrivate::startClientEncryption() +{ + if (backend.get()) + backend->startClientEncryption(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::startServerEncryption() +{ + if (backend.get()) + backend->startServerEncryption(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::transmit() +{ + if (backend.get()) + backend->transmit(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::disconnectFromHost() +{ + if (backend.get()) + backend->disconnectFromHost(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::disconnected() +{ + if (backend.get()) + backend->disconnected(); +} + +/*! + \internal +*/ +QSslCipher QSslSocketPrivate::sessionCipher() const +{ + if (backend.get()) + return backend->sessionCipher(); + + return {}; +} + +/*! + \internal +*/ +QSsl::SslProtocol QSslSocketPrivate::sessionProtocol() const +{ + if (backend.get()) + return backend->sessionProtocol(); + + return QSsl::UnknownProtocol; +} + +/*! + \internal +*/ +void QSslSocketPrivate::continueHandshake() +{ + if (backend.get()) + backend->continueHandshake(); +} + +/*! + \internal +*/ bool QSslSocketPrivate::rootCertOnDemandLoadingSupported() { return s_loadRootCertsOnDemand; @@ -2719,6 +2962,14 @@ bool QSslSocketPrivate::rootCertOnDemandLoadingSupported() /*! \internal */ +void QSslSocketPrivate::setRootCertOnDemandLoadingSupported(bool supported) +{ + s_loadRootCertsOnDemand = supported; +} + +/*! + \internal +*/ QList<QByteArray> QSslSocketPrivate::unixRootCertDirectories() { return QList<QByteArray>() << "/etc/ssl/certs/" // (K)ubuntu, OpenSUSE, Mandriva ... @@ -2735,10 +2986,13 @@ QList<QByteArray> QSslSocketPrivate::unixRootCertDirectories() /*! \internal */ -void QSslSocketPrivate::checkSettingSslContext(QSslSocket* socket, QSharedPointer<QSslContext> sslContext) +void QSslSocketPrivate::checkSettingSslContext(QSslSocket* socket, QSharedPointer<QSslContext> tlsContext) { - if (socket->d_func()->sslContextPointer.isNull()) - socket->d_func()->sslContextPointer = sslContext; + if (!socket) + return; + + if (auto *backend = socket->d_func()->backend.get()) + backend->checkSettingSslContext(tlsContext); } /*! @@ -2746,7 +3000,13 @@ void QSslSocketPrivate::checkSettingSslContext(QSslSocket* socket, QSharedPointe */ QSharedPointer<QSslContext> QSslSocketPrivate::sslContext(QSslSocket *socket) { - return (socket) ? socket->d_func()->sslContextPointer : QSharedPointer<QSslContext>(); + if (!socket) + return {}; + + if (const auto *backend = socket->d_func()->backend.get()) + return backend->sslContext(); + + return {}; } bool QSslSocketPrivate::isMatchingHostname(const QSslCertificate &cert, const QString &peerName) @@ -2852,6 +3112,61 @@ QTlsBackend *QSslSocketPrivate::tlsBackendInUse() return tlsBackend = QTlsBackend::findBackend(activeBackendName); } +/*! + \internal +*/ +QSslSocket::SslMode QSslSocketPrivate::tlsMode() const +{ + return mode; +} + +/*! + \internal +*/ +QSslConfigurationPrivate &QSslSocketPrivate::privateConfiguration() +{ + return configuration; +} + +/*! + \internal +*/ +bool QSslSocketPrivate::isRootsOnDemandAllowed() const +{ + return allowRootCertOnDemandLoading; +} + +/*! + \internal +*/ +QString QSslSocketPrivate::verificationName() const +{ + return verificationPeerName; +} + +/*! + \internal +*/ +QString QSslSocketPrivate::tlsHostName() const +{ + return hostName; +} + +QTcpSocket *QSslSocketPrivate::plainTcpSocket() const +{ + return plainSocket; +} + +/*! + \internal +*/ +QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates() +{ + if (const auto *tlsBackend = tlsBackendInUse()) + return tlsBackend->systemCaCertificates(); + return {}; +} + QT_END_NAMESPACE #include "moc_qsslsocket.cpp" diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h index 8841929eec..1737c93164 100644 --- a/src/network/ssl/qsslsocket.h +++ b/src/network/ssl/qsslsocket.h @@ -201,6 +201,7 @@ protected: private: Q_DECLARE_PRIVATE(QSslSocket) Q_DISABLE_COPY_MOVE(QSslSocket) + Q_PRIVATE_SLOT(d_func(), void _q_connectedSlot()) Q_PRIVATE_SLOT(d_func(), void _q_hostFoundSlot()) Q_PRIVATE_SLOT(d_func(), void _q_disconnectedSlot()) @@ -214,10 +215,6 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_flushWriteBuffer()) Q_PRIVATE_SLOT(d_func(), void _q_flushReadBuffer()) Q_PRIVATE_SLOT(d_func(), void _q_resumeImplementation()) -#if defined(Q_OS_WIN) && !QT_CONFIG(schannel) - Q_PRIVATE_SLOT(d_func(), void _q_caRootLoaded(QSslCertificate,QSslCertificate)) -#endif - friend class QSslSocketBackendPrivate; }; #endif // QT_NO_SSL diff --git a/src/network/ssl/qsslsocket_mac_shared.cpp b/src/network/ssl/qsslsocket_mac_shared.cpp index c2ade1c702..837ac4a4f6 100644 --- a/src/network/ssl/qsslsocket_mac_shared.cpp +++ b/src/network/ssl/qsslsocket_mac_shared.cpp @@ -38,30 +38,22 @@ ** ****************************************************************************/ -//#define QSSLSOCKET_DEBUG -//#define QT_DECRYPT_SSL_TRAFFIC +#include "qsslcertificate.h" -#include "qssl_p.h" -#include "qsslsocket.h" -#include "qsslsocket_p.h" +#include <QtCore/qglobal.h> -#ifndef QT_NO_OPENSSL -# include "qsslsocket_openssl_p.h" -# include "qsslsocket_openssl_symbols_p.h" -#endif +#ifdef Q_OS_MACOS -#include "qsslcertificate_p.h" +#include "qtlsbackend_p.h" -#ifdef Q_OS_DARWIN -# include <private/qcore_mac_p.h> -#endif +#include <private/qcore_mac_p.h> #include <QtCore/qdebug.h> -#ifdef Q_OS_MACOS -# include <Security/Security.h> -#endif +#include <CoreFoundation/CFArray.h> +#include <Security/Security.h> +#endif // Q_OS_MACOS QT_BEGIN_NAMESPACE @@ -114,18 +106,17 @@ bool isCaCertificateTrusted(SecCertificateRef cfCert, int domain) } } } else { - qCWarning(lcSsl, "Error receiving trust for a CA certificate"); + qCWarning(lcTlsBackend, "Error receiving trust for a CA certificate"); } return false; } -} // anon namespace +} // unnamed namespace #endif // Q_OS_MACOS -QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates() +namespace QTlsPrivate { +QList<QSslCertificate> systemCaCertificates() { - ensureInitialized(); - QList<QSslCertificate> systemCerts; // SecTrustSettingsCopyCertificates is not defined on iOS. #ifdef Q_OS_MACOS @@ -152,5 +143,6 @@ QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates() #endif return systemCerts; } +} // namespace QTlsPrivate QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_android.cpp b/src/network/ssl/qsslsocket_openssl_android.cpp index 8d0c9a69ee..22884cb819 100644 --- a/src/network/ssl/qsslsocket_openssl_android.cpp +++ b/src/network/ssl/qsslsocket_openssl_android.cpp @@ -52,13 +52,15 @@ ** ****************************************************************************/ -#include "qsslsocket_openssl_p.h" +#include "qsslsocket_p.h" #include <QtCore/QJniEnvironment> #include <QtCore/QJniObject> QT_BEGIN_NAMESPACE -QList<QByteArray> QSslSocketPrivate::fetchSslCertificateData() +namespace QTlsPrivate { + +QList<QByteArray> fetchSslCertificateData() { QList<QByteArray> certificateData; @@ -86,4 +88,6 @@ QList<QByteArray> QSslSocketPrivate::fetchSslCertificateData() return certificateData; } +} // namespace QTlsPrivate + QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h index 443faf068f..f733c64fe3 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -69,7 +69,7 @@ // #include <QtNetwork/private/qtnetworkglobal_p.h> -#include "qsslsocket_openssl_p.h" +#include "qopenssl_p.h" #include <QtCore/qglobal.h> #if QT_CONFIG(ocsp) diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h index 1d6881d012..6a03a23dc6 100644 --- a/src/network/ssl/qsslsocket_p.h +++ b/src/network/ssl/qsslsocket_p.h @@ -55,59 +55,25 @@ // #include <QtNetwork/private/qtnetworkglobal_p.h> + #include <private/qtcpsocket_p.h> -#include "qsslkey.h" -#include "qsslconfiguration_p.h" + #include "qocspresponse.h" -#ifndef QT_NO_OPENSSL -#include <private/qsslcontext_openssl_p.h> -#else -class QSslContext; -#endif +#include "qsslconfiguration_p.h" +#include "qsslkey.h" +#include "qtlsbackend_p.h" #include <QtCore/qlist.h> -#include <QtCore/qstringlist.h> #include <QtCore/qmutex.h> - -#include <private/qringbuffer_p.h> - -#if defined(Q_OS_MAC) -#include <Security/SecCertificate.h> -#include <CoreFoundation/CFArray.h> -#elif defined(Q_OS_WIN) -#include <QtCore/qt_windows.h> -#include <memory> -#include <wincrypt.h> -#ifndef HCRYPTPROV_LEGACY -#define HCRYPTPROV_LEGACY HCRYPTPROV -#endif // !HCRYPTPROV_LEGACY -#endif // Q_OS_WIN +#include <QtCore/qstringlist.h> #include <memory> QT_BEGIN_NAMESPACE -#if defined(Q_OS_MACOS) - typedef CFDataRef (*PtrSecCertificateCopyData)(SecCertificateRef); - typedef OSStatus (*PtrSecTrustSettingsCopyCertificates)(int, CFArrayRef*); - typedef OSStatus (*PtrSecTrustCopyAnchorCertificates)(CFArrayRef*); -#endif - -#if defined(Q_OS_WIN) - -// Those are needed by both OpenSSL and Schannel back-ends on Windows: -struct QHCertStoreDeleter { - void operator()(HCERTSTORE store) - { - CertCloseStore(store, 0); - } -}; - -using QHCertStorePointer = std::unique_ptr<void, QHCertStoreDeleter>; - -#endif // Q_OS_WIN - +class QSslContext; class QTlsBackend; + class QSslSocketPrivate : public QTcpSocketPrivate { Q_DECLARE_PUBLIC(QSslSocket) @@ -122,14 +88,11 @@ public: QSslSocket::SslMode mode; bool autoStartHandshake; bool connectionEncrypted; - bool shutdown; bool ignoreAllSslErrors; QList<QSslError> ignoreErrorsList; bool* readyReadEmittedPointer; QSslConfigurationPrivate configuration; - QList<QSslError> sslErrors; - QSharedPointer<QSslContext> sslContextPointer; // if set, this hostname is used for certificate validation instead of the hostname // that was used for connecting to. @@ -140,16 +103,14 @@ public: static bool s_loadRootCertsOnDemand; static bool supportsSsl(); - static long sslLibraryVersionNumber(); - static QString sslLibraryVersionString(); - static long sslLibraryBuildVersionNumber(); - static QString sslLibraryBuildVersionString(); static void ensureInitialized(); + static QList<QSslCipher> defaultCiphers(); + static QList<QSslCipher> defaultDtlsCiphers(); static QList<QSslCipher> supportedCiphers(); static void setDefaultCiphers(const QList<QSslCipher> &ciphers); + static void setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers); static void setDefaultSupportedCiphers(const QList<QSslCipher> &ciphers); - static void resetDefaultCiphers(); static QList<QSslEllipticCurve> supportedEllipticCurves(); static void setDefaultSupportedEllipticCurves(const QList<QSslEllipticCurve> &curves); @@ -160,19 +121,19 @@ public: static void setDefaultCaCertificates(const QList<QSslCertificate> &certs); static void addDefaultCaCertificate(const QSslCertificate &cert); static void addDefaultCaCertificates(const QList<QSslCertificate> &certs); - Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QSslCertificate &cert, - const QString &peerName); + Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QSslCertificate &cert, const QString &peerName); Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QString &cn, const QString &hostname); // The socket itself, including private slots. - QTcpSocket *plainSocket; + QTcpSocket *plainSocket = nullptr; void createPlainSocket(QIODevice::OpenMode openMode); - static void pauseSocketNotifiers(QSslSocket*); - static void resumeSocketNotifiers(QSslSocket*); + Q_NETWORK_EXPORT static void pauseSocketNotifiers(QSslSocket*); + Q_NETWORK_EXPORT static void resumeSocketNotifiers(QSslSocket*); // ### The 2 methods below should be made member methods once the QSslContext class is made public static void checkSettingSslContext(QSslSocket*, QSharedPointer<QSslContext>); static QSharedPointer<QSslContext> sslContext(QSslSocket *socket); - bool isPaused() const; + Q_NETWORK_EXPORT bool isPaused() const; + Q_NETWORK_EXPORT void setPaused(bool p); bool bind(const QHostAddress &address, quint16, QAbstractSocket::BindMode) override; void _q_connectedSlot(); void _q_hostFoundSlot(); @@ -187,62 +148,59 @@ public: void _q_flushWriteBuffer(); void _q_flushReadBuffer(); void _q_resumeImplementation(); -#if defined(Q_OS_WIN) && !QT_CONFIG(schannel) - virtual void _q_caRootLoaded(QSslCertificate,QSslCertificate) = 0; -#endif - static QList<QByteArray> unixRootCertDirectories(); // used also by QSslContext + Q_NETWORK_PRIVATE_EXPORT static QList<QByteArray> unixRootCertDirectories(); // used also by QSslContext - virtual qint64 peek(char *data, qint64 maxSize) override; - virtual QByteArray peek(qint64 maxSize) override; + qint64 peek(char *data, qint64 maxSize) override; + QByteArray peek(qint64 maxSize) override; bool flush() override; - // Platform specific functions - virtual void startClientEncryption() = 0; - virtual void startServerEncryption() = 0; - virtual void transmit() = 0; - virtual void disconnectFromHost() = 0; - virtual void disconnected() = 0; - virtual QSslCipher sessionCipher() const = 0; - virtual QSsl::SslProtocol sessionProtocol() const = 0; - virtual void continueHandshake() = 0; + void startClientEncryption(); + void startServerEncryption(); + void transmit(); + void disconnectFromHost(); + void disconnected(); + QSslCipher sessionCipher() const; + QSsl::SslProtocol sessionProtocol() const; + void continueHandshake(); - Q_AUTOTEST_EXPORT static bool rootCertOnDemandLoadingSupported(); + Q_NETWORK_PRIVATE_EXPORT static bool rootCertOnDemandLoadingSupported(); + Q_NETWORK_PRIVATE_EXPORT static void setRootCertOnDemandLoadingSupported(bool supported); static QTlsBackend *tlsBackendInUse(); static void registerAdHocFactory(); -private: - static bool ensureLibraryLoaded(); - static void ensureCiphersAndCertsLoaded(); -#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) - static QList<QByteArray> fetchSslCertificateData(); -#endif - - static bool s_libraryLoaded; - static bool s_loadedCiphersAndCerts; + // Needed by TlsCryptograph: + Q_NETWORK_PRIVATE_EXPORT QSslSocket::SslMode tlsMode() const; + Q_NETWORK_PRIVATE_EXPORT QSslConfigurationPrivate &privateConfiguration(); + Q_NETWORK_PRIVATE_EXPORT bool isRootsOnDemandAllowed() const; + Q_NETWORK_PRIVATE_EXPORT QString verificationName() const; + Q_NETWORK_PRIVATE_EXPORT QString tlsHostName() const; + Q_NETWORK_PRIVATE_EXPORT QTcpSocket *plainTcpSocket() const; + Q_NETWORK_PRIVATE_EXPORT bool verifyErrorsHaveBeenIgnored(); + Q_NETWORK_PRIVATE_EXPORT bool isAutoStartingHandshake() const; + Q_NETWORK_PRIVATE_EXPORT bool isPendingClose() const; + Q_NETWORK_PRIVATE_EXPORT void setPendingClose(bool pc); + Q_NETWORK_PRIVATE_EXPORT qint64 maxReadBufferSize() const; + Q_NETWORK_PRIVATE_EXPORT void setMaxReadBufferSize(qint64 maxSize); + Q_NETWORK_PRIVATE_EXPORT void setEncrypted(bool enc); + Q_NETWORK_PRIVATE_EXPORT QRingBufferRef &tlsWriteBuffer(); + Q_NETWORK_PRIVATE_EXPORT QRingBufferRef &tlsBuffer(); + Q_NETWORK_PRIVATE_EXPORT bool &tlsEmittedBytesWritten(); + Q_NETWORK_PRIVATE_EXPORT bool *readyReadPointer(); protected: - bool verifyErrorsHaveBeenIgnored(); - // Only implemented/useful in Schannel for now - virtual bool hasUndecryptedData() { return false; }; + + bool hasUndecryptedData() const; bool paused; bool flushTriggered; - bool systemOrSslErrorDetected = false; - QList<QOcspResponse> ocspResponses; - bool handshakeInterrupted = false; - bool fetchAuthorityInformation = false; - QSslCertificate caToFetch; static inline QMutex backendMutex; static inline QString activeBackendName; static inline QTlsBackend *tlsBackend = nullptr; -}; -#if QT_CONFIG(securetransport) || QT_CONFIG(schannel) -// Implemented in qsslsocket_qt.cpp -QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key, const QString &passPhrase); -#endif + std::unique_ptr<QTlsPrivate::TlsCryptograph> backend; +}; QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qtls_openssl.cpp index 160d8cc1d9..5149b4ce38 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qtls_openssl.cpp @@ -1,7 +1,6 @@ /**************************************************************************** ** ** Copyright (C) 2021 The Qt Company Ltd. -** Copyright (C) 2014 Governikus GmbH & Co. KG ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -38,73 +37,27 @@ ** ****************************************************************************/ -/**************************************************************************** -** -** 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. -** -****************************************************************************/ - -//#define QSSLSOCKET_DEBUG - -#include "qssl_p.h" -#include "qsslsocket_openssl_p.h" +#include "qsslpresharedkeyauthenticator_p.h" +#include "qsslpresharedkeyauthenticator.h" #include "qsslsocket_openssl_symbols_p.h" -#include "qsslsocket.h" #include "qsslcertificate_p.h" -#include "qsslcipher_p.h" -#include "qsslkey_p.h" -#include "qsslellipticcurve.h" -#include "qsslpresharedkeyauthenticator.h" -#include "qsslpresharedkeyauthenticator_p.h" -#include "qtlsbackend_openssl_p.h" -#include "qocspresponse_p.h" -#include "qsslkey.h" -#include "qtlsbackend_openssl_p.h" -#include "qtlskey_openssl_p.h" #include "qx509_openssl_p.h" +#include "qocspresponse_p.h" +#include "qtls_openssl_p.h" +#include "qsslsocket_p.h" #ifdef Q_OS_WIN #include "qwindowscarootfetcher_p.h" #endif -#include <QtCore/qdatetime.h> -#include <QtCore/qdebug.h> -#include <QtCore/qdir.h> -#include <QtCore/qdiriterator.h> -#include <QtCore/qelapsedtimer.h> -#include <QtCore/qfile.h> -#include <QtCore/qfileinfo.h> -#include <QtCore/qmutex.h> -#include <QtCore/qthread.h> -#include <QtCore/qurl.h> -#include <QtCore/qvarlengtharray.h> #include <QtCore/qscopedvaluerollback.h> #include <QtCore/qscopeguard.h> -#include <QtCore/qlibrary.h> -#include <QtCore/qoperatingsystemversion.h> - -#if QT_CONFIG(ocsp) -#include "qocsp_p.h" -#endif #include <algorithm> -#include <memory> - -#include <string.h> +#include <cstring> QT_BEGIN_NAMESPACE -Q_GLOBAL_STATIC(QTlsBackendOpenSSL, backend) - namespace { QSsl::AlertLevel tlsAlertLevel(int value) @@ -161,7 +114,7 @@ QSslCertificate findCertificateToFetch(const QList<QSslError> &tlsErrors, bool c return QSslCertificate{}; default: #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << tlsError.errorString(); + qCDebug(lcTlsBackend) << tlsError.errorString(); #endif //TODO - this part is strange. break; @@ -186,44 +139,50 @@ QSslCertificate findCertificateToFetch(const QList<QSslError> &tlsErrors, bool c #endif // Q_OS_WIN -} // Unnamed namespace +} // unnamed namespace -extern "C" -{ +namespace QTlsPrivate { -void qt_AlertInfoCallback(const SSL *connection, int from, int value) +extern "C" { + +int q_X509Callback(int ok, X509_STORE_CTX *ctx) { - // Passed to SSL_set_info_callback() - // https://www.openssl.org/docs/man1.1.1/man3/SSL_set_info_callback.html + if (!ok) { + // Store the error and at which depth the error was detected. - if (!connection) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl, "Invalid 'connection' parameter (nullptr)"); -#endif // QSSLSOCKET_DEBUG - return; - } + using ErrorListPtr = QList<QSslErrorEntry> *; + ErrorListPtr errors = nullptr; - const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData - + QSslSocketBackendPrivate::socketOffsetInExData; - auto privateSocket = - static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(connection, offset)); - if (!privateSocket) { - // SSL_set_ex_data can fail: -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl, "No external data (socket backend) found for parameter 'connection'"); -#endif // QSSLSOCKET_DEBUG - return; - } + // 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 (!(from & SSL_CB_ALERT)) { - // We only want to know about alerts (at least for now). - return; - } + 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()))) + errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset)); + } - if (from & SSL_CB_WRITE) - privateSocket->alertMessageSent(value); - else - privateSocket->alertMessageReceived(value); + 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) @@ -248,80 +207,42 @@ int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx) // 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(lcSsl, "No external data (SSL) found in X509 store object"); + qCWarning(lcTlsBackend, "No external data (SSL) found in X509 store object"); return 0; } - const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData - + QSslSocketBackendPrivate::socketOffsetInExData; - auto privateSocket = static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, offset)); - if (!privateSocket) { - qCWarning(lcSsl, "No external data (QSslSocketBackendPrivate) found in SSL object"); + 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 privateSocket->emitErrorFromCallback(ctx); + return crypto->emitErrorFromCallback(ctx); } return 1; } -} // extern "C" - -Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex) - -bool QSslSocketPrivate::s_libraryLoaded = false; -bool QSslSocketPrivate::s_loadedCiphersAndCerts = false; -bool QSslSocketPrivate::s_loadRootCertsOnDemand = false; -int QSslSocketBackendPrivate::s_indexForSSLExtraData = -1; - -QString QSslSocketBackendPrivate::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(QLatin1String(", ")); - q_ERR_error_string_n(errNum, buf, sizeof buf); - errorString.append(QString::fromLatin1(buf)); // error is ascii according to man ERR_error_string - } - return errorString; -} - -void QSslSocketBackendPrivate::logAndClearErrorQueue() -{ - const auto errors = getErrorsFromOpenSsl(); - if (errors.size()) - qCWarning(lcSsl) << "Discarding errors:" << errors; -} - -extern "C" { - #ifndef OPENSSL_NO_PSK -static unsigned int q_ssl_psk_client_callback(SSL *ssl, - const char *hint, - char *identity, unsigned int max_identity_len, - unsigned char *psk, unsigned int max_psk_len) +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) { - QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData)); - Q_ASSERT(d); - return d->tlsPskClientCallback(hint, identity, max_identity_len, psk, 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) +static unsigned int q_ssl_psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, + unsigned int max_psk_len) { - QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData)); - Q_ASSERT(d); - return d->tlsPskServerCallback(identity, psk, 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 int q_ssl_psk_restore_client(SSL *ssl, - const char *hint, - char *identity, unsigned int max_identity_len, - unsigned char *psk, unsigned int max_psk_len) +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); @@ -330,9 +251,10 @@ static unsigned int q_ssl_psk_restore_client(SSL *ssl, Q_UNUSED(max_psk_len); #ifdef QT_DEBUG - QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData)); - Q_ASSERT(d); - Q_ASSERT(d->mode == QSslSocket::SslClientMode); + 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 q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); @@ -349,9 +271,10 @@ static int q_ssl_psk_use_session_callback(SSL *ssl, const EVP_MD *md, const unsi Q_UNUSED(sess); #ifdef QT_DEBUG - QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData)); - Q_ASSERT(d); - Q_ASSERT(d->mode == QSslSocket::SslClientMode); + 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. @@ -363,17 +286,17 @@ static int q_ssl_psk_use_session_callback(SSL *ssl, const EVP_MD *md, const unsi int q_ssl_sess_set_new_cb(SSL *ssl, SSL_SESSION *session) { if (!ssl) { - qCWarning(lcSsl, "Invalid SSL (nullptr)"); + qCWarning(lcTlsBackend, "Invalid SSL (nullptr)"); return 0; } if (!session) { - qCWarning(lcSsl, "Invalid SSL_SESSION (nullptr)"); + qCWarning(lcTlsBackend, "Invalid SSL_SESSION (nullptr)"); return 0; } - auto socketPrivate = static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, - QSslSocketBackendPrivate::s_indexForSSLExtraData)); - return socketPrivate->handleNewSessionTicket(ssl); + 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 @@ -387,12 +310,13 @@ int qt_OCSP_status_server_callback(SSL *ssl, void *ocspRequest) if (!ssl) return SSL_TLSEXT_ERR_ALERT_FATAL; - auto d = static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData)); - if (!d) + auto crypto = static_cast<TlsCryptographOpenSSL *>(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + if (!crypto) return SSL_TLSEXT_ERR_ALERT_FATAL; - Q_ASSERT(d->mode == QSslSocket::SslServerMode); - const QByteArray &response = d->ocspResponseDer; + 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()))); @@ -409,62 +333,44 @@ int qt_OCSP_status_server_callback(SSL *ssl, void *ocspRequest) #endif // ocsp -} // extern "C" - -QSslSocketBackendPrivate::QSslSocketBackendPrivate() - : ssl(nullptr), - readBio(nullptr), - writeBio(nullptr), - session(nullptr) +void qt_AlertInfoCallback(const SSL *connection, int from, int value) { - // Calls SSL_library_init(). - ensureInitialized(); -} + // Passed to SSL_set_info_callback() + // https://www.openssl.org/docs/man1.1.1/man3/SSL_set_info_callback.html -QSslSocketBackendPrivate::~QSslSocketBackendPrivate() -{ - destroySslContext(); -} + if (!connection) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend, "Invalid 'connection' parameter (nullptr)"); +#endif // QSSLSOCKET_DEBUG + return; + } -QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(const SSL_CIPHER *cipher) -{ - QSslCipher ciph; - - char buf [256]; - QString descriptionOneLine = QString::fromLatin1(q_SSL_CIPHER_description(cipher, buf, sizeof(buf))); - - const auto descriptionList = QStringView{descriptionOneLine}.split(QLatin1Char(' '), Qt::SkipEmptyParts); - if (descriptionList.size() > 5) { - // ### crude code. - ciph.d->isNull = false; - ciph.d->name = descriptionList.at(0).toString(); - - QString protoString = descriptionList.at(1).toString(); - ciph.d->protocolString = protoString; - ciph.d->protocol = QSsl::UnknownProtocol; - if (protoString == QLatin1String("TLSv1")) - ciph.d->protocol = QSsl::TlsV1_0; - else if (protoString == QLatin1String("TLSv1.1")) - ciph.d->protocol = QSsl::TlsV1_1; - else if (protoString == QLatin1String("TLSv1.2")) - ciph.d->protocol = QSsl::TlsV1_2; - else if (protoString == QLatin1String("TLSv1.3")) - ciph.d->protocol = QSsl::TlsV1_3; - - if (descriptionList.at(2).startsWith(QLatin1String("Kx="))) - ciph.d->keyExchangeMethod = descriptionList.at(2).mid(3).toString(); - if (descriptionList.at(3).startsWith(QLatin1String("Au="))) - ciph.d->authenticationMethod = descriptionList.at(3).mid(3).toString(); - if (descriptionList.at(4).startsWith(QLatin1String("Enc="))) - ciph.d->encryptionMethod = descriptionList.at(4).mid(4).toString(); - ciph.d->exportable = (descriptionList.size() > 6 && descriptionList.at(6) == QLatin1String("export")); - - ciph.d->bits = q_SSL_CIPHER_get_bits(cipher, &ciph.d->supportedBits); - } - return ciph; + 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) { @@ -571,540 +477,392 @@ bool qt_OCSP_certificate_match(OCSP_SINGLERESP *singleResponse, X509 *peerCert, return true; } +} // unnamed namespace #endif // ocsp -int q_X509Callback(int ok, X509_STORE_CTX *ctx) +TlsCryptographOpenSSL::~TlsCryptographOpenSSL() { - if (!ok) { - // Store the error and at which depth the error was detected. - - using ErrorListPtr = QList<QSslErrorEntry> *; - ErrorListPtr errors = nullptr; + destroySslContext(); +} - // 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)); +void TlsCryptographOpenSSL::init(QSslSocket *qObj, QSslSocketPrivate *dObj) +{ + Q_ASSERT(qObj); + Q_ASSERT(dObj); + q = qObj; + d = dObj; - 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 = QSslSocketBackendPrivate::s_indexForSSLExtraData - + QSslSocketBackendPrivate::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)); - } + ocspResponses.clear(); + ocspResponseDer.clear(); - if (!errors) { - qCWarning(lcSsl, "Neither X509_STORE, nor SSL contains error list, handshake failure"); - return 0; - } + systemOrSslErrorDetected = false; + handshakeInterrupted = false; - errors->append(QTlsPrivate::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; + fetchAuthorityInformation = false; + caToFetch = QSslCertificate{}; } -static void q_loadCiphersForConnection(SSL *connection, QList<QSslCipher> &ciphers, - QList<QSslCipher> &defaultCiphers) +void TlsCryptographOpenSSL::checkSettingSslContext(QSharedPointer<QSslContext> tlsContext) { - Q_ASSERT(connection); + if (sslContextPointer.isNull()) + sslContextPointer = tlsContext; +} - 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)) { - QSslCipher ciph = QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(cipher); - if (!ciph.isNull()) { - // Unconditionally exclude ADH and AECDH ciphers since they offer no MITM protection - if (!ciph.name().toLower().startsWith(QLatin1String("adh")) && - !ciph.name().toLower().startsWith(QLatin1String("exp-adh")) && - !ciph.name().toLower().startsWith(QLatin1String("aecdh"))) { - ciphers << ciph; - - if (ciph.usedBits() >= 128) - defaultCiphers << ciph; - } - } - } - } +QSharedPointer<QSslContext> TlsCryptographOpenSSL::sslContext() const +{ + return sslContextPointer; } -// Defined in qsslsocket.cpp -void q_setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers); +QList<QSslError> TlsCryptographOpenSSL::tlsErrors() const +{ + return sslErrors; +} -long QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions) +void TlsCryptographOpenSSL::startClientEncryption() { - long options; - switch (protocol) { - case QSsl::SecureProtocols: - 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; - 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; + if (!initSslContext()) { + Q_ASSERT(d); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to init SSL Context: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; } - // 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 + // Start connecting. This will place outgoing data in the BIO, so we + // follow up with calling transmit(). + startHandshake(); + transmit(); +} - if (!(sslOptions & QSsl::SslOptionDisableServerCipherPreference)) - options |= SSL_OP_CIPHER_SERVER_PREFERENCE; +void TlsCryptographOpenSSL::startServerEncryption() +{ + if (!initSslContext()) { + Q_ASSERT(d); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to init SSL Context: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } - return options; + // Start connecting. This will place outgoing data in the BIO, so we + // follow up with calling transmit(). + startHandshake(); + transmit(); } -bool QSslSocketBackendPrivate::initSslContext() +bool TlsCryptographOpenSSL::startHandshake() { - Q_Q(QSslSocket); + // Check if the connection has been established. Get all errors from the + // verification stage. + Q_ASSERT(q); + Q_ASSERT(d); - // If no external context was set (e.g. by QHttpNetworkConnection) we will - // create a default context - if (!sslContextPointer) { - // create a deep copy of our configuration - QSslConfigurationPrivate *configurationCopy = new QSslConfigurationPrivate(configuration); - configurationCopy->ref.storeRelaxed(0); // the QSslConfiguration constructor refs up - sslContextPointer = QSslContext::sharedFromConfiguration(mode, configurationCopy, allowRootCertOnDemandLoading); - } + using ScopedBool = QScopedValueRollback<bool>; - if (sslContextPointer->error() != QSslError::NoError) { - setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, sslContextPointer->errorString()); - sslContextPointer.clear(); // deletes the QSslContext + if (inSetAndEmitError) return false; - } - // Create and initialize SSL session - if (!(ssl = sslContextPointer->createSsl())) { - // ### Bad error code - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error creating SSL session, %1").arg(getErrorsFromOpenSsl())); - return false; - } + const auto mode = d->tlsMode(); + auto &configuration = d->privateConfiguration(); - if (configuration.protocol != QSsl::UnknownProtocol && mode == QSslSocket::SslClientMode) { - // 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 = hostName; - 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.sslOptions & 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(lcSsl, "could not set SSL_CTRL_SET_TLSEXT_HOSTNAME, Server Name Indication disabled"); - } - } + pendingFatalAlert = false; + errorsReportedFromCallback = false; + QList<QSslErrorEntry> lastErrors; + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + errorOffsetInExData, &lastErrors); - // Clear the session. - errorList.clear(); + // 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); - // 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(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error creating SSL session: %1").arg(getErrorsFromOpenSsl())); - if (readBio) - q_BIO_free(readBio); - if (writeBio) - q_BIO_free(writeBio); - return false; - } + 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. - // Assign the bios. - q_SSL_set_bio(ssl, readBio, writeBio); + if (!lastErrors.isEmpty() || errorsReportedFromCallback) + storePeerCertificates(); - if (mode == QSslSocket::SslClientMode) - q_SSL_set_connect_state(ssl); - else - q_SSL_set_accept_state(ssl); + if (!errorsReportedFromCallback) { + for (const auto ¤tError : qAsConst(lastErrors)) { + emit q->peerVerifyError(QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(currentError.code, + configuration.peerCertificateChain.value(currentError.depth))); + if (q->state() != QAbstractSocket::ConnectedState) + break; + } + } - q_SSL_set_ex_data(ssl, s_indexForSSLExtraData, this); + errorList << lastErrors; -#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); + // Connection aborted during handshake phase. + if (q->state() != QAbstractSocket::ConnectedState) + return false; -#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); + // 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); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, errorString); + if (pendingFatalAlert) { + trySendFatalAlert(); + pendingFatalAlert = false; + } + } + q->abort(); + } + return false; } -#endif // openssl version >= 0x10101006L -#endif // OPENSSL_NO_PSK + // store peer certificate chain + storePeerCertificates(); + // Start translating errors. + QList<QSslError> errors; -#if QT_CONFIG(ocsp) - if (configuration.ocspStaplingEnabled) { - if (mode == QSslSocket::SslServerMode) { - setErrorAndEmit(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(QAbstractSocket::SslInternalError, - QSslSocket::tr("Failed to enable OCSP stapling")); - return false; + // check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer) + for (const QSslCertificate &cert : qAsConst(configuration.peerCertificateChain)) { + if (QSslCertificatePrivate::isBlacklisted(cert)) { + QSslError error(QSslError::CertificateBlacklisted, cert); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; } } - ocspResponseDer.clear(); - auto responsePos = configuration.backendConfig.find("Qt-OCSP-response"); - if (responsePos != configuration.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(); - } + const bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer + || (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); - if (ocspResponseDer.size()) { - if (mode != QSslSocket::SslServerMode) { - setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, - QSslSocket::tr("Client-side sockets do not send OCSP responses")); - return false; +#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); + d->setErrorAndEmit(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 - return true; -} + // 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); -void QSslSocketBackendPrivate::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 = getErrorsFromOpenSsl(); - Q_UNUSED(errors); + 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; } } - q_SSL_free(ssl); - ssl = nullptr; + } 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; + } } - sslContextPointer.clear(); -} - -/*! - \internal - - Does the minimum amount of initialization to determine whether SSL - is supported or not. -*/ - -bool QSslSocketPrivate::supportsSsl() -{ - return ensureLibraryLoaded(); -} + // Translate errors from the error list into QSslErrors. + errors.reserve(errors.size() + errorList.size()); + for (const auto &error : qAsConst(errorList)) + errors << X509CertificateOpenSSL::openSSLErrorToQSslError(error.code, configuration.peerCertificateChain.value(error.depth)); -/*! - \internal + 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); - Returns the version number of the SSL library in use. Note that - this is the version of the library in use at run-time, not compile - time. -*/ -long QSslSocketPrivate::sslLibraryVersionNumber() -{ - if (!supportsSsl()) - return 0; + //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(); + } - return q_OpenSSL_version_num(); + continueHandshake(); + return true; } -/*! - \internal - - Returns the version string of the SSL library in use. Note that - this is the version of the library in use at run-time, not compile - time. If no SSL support is available then this will return an empty value. -*/ -QString QSslSocketPrivate::sslLibraryVersionString() +void TlsCryptographOpenSSL::enableHandshakeContinuation() { - if (!supportsSsl()) - return QString(); - - const char *versionString = q_OpenSSL_version(OPENSSL_VERSION); - if (!versionString) - return QString(); - - return QString::fromLatin1(versionString); + handshakeInterrupted = false; } -/*! - \internal - - Declared static in QSslSocketPrivate, makes sure the SSL libraries have - been initialized. -*/ -void QSslSocketPrivate::ensureInitialized() +void TlsCryptographOpenSSL::cancelCAFetch() { - if (!supportsSsl()) - return; - - ensureCiphersAndCertsLoaded(); + fetchAuthorityInformation = false; + caToFetch = QSslCertificate{}; } -/*! - \internal - - Returns the version number of the SSL library in use at compile - time. -*/ -long QSslSocketPrivate::sslLibraryBuildVersionNumber() +void TlsCryptographOpenSSL::continueHandshake() { - return OPENSSL_VERSION_NUMBER; -} + Q_ASSERT(q); + Q_ASSERT(d); -/*! - \internal + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); - Returns the version string of the SSL library in use at compile - time. -*/ -QString QSslSocketPrivate::sslLibraryBuildVersionString() -{ - // 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); -} + auto &configuration = d->privateConfiguration(); + const auto mode = d->tlsMode(); -/*! - \internal + // if we have a max read buffer size, reset the plain socket's to match + if (const auto maxSize = d->maxReadBufferSize()) + plainSocket->setReadBufferSize(maxSize); - Declared static in QSslSocketPrivate, backend-dependent loading of - application-wide global ciphers. -*/ -void QSslSocketPrivate::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); - q_setDefaultDtlsCiphers(defaultCiphers); - q_SSL_free(mySsl); - } - q_SSL_CTX_free(myCtx); - } -#endif // dtls -} + if (q_SSL_session_reused(ssl)) + configuration.peerSessionShared = true; -void QSslSocketPrivate::resetDefaultEllipticCurves() -{ - // TLSTODO: this function to be be merged into qsslsocket.cpp - const auto *tlsBackend = tlsBackendInUse(); - if (!tlsBackend) - return; +#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 - auto ids = tlsBackend->ellipticCurvesIds(); - if (!ids.size()) - return; + 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()); - QList<QSslEllipticCurve> curves; - curves.reserve(ids.size()); - for (int id : ids) { - QSslEllipticCurve curve; - curve.id = id; - curves.append(curve); - } + QByteArray debugLineClientRandom("CLIENT_RANDOM "); + debugLineClientRandom.append(clientRandom.toHex().toUpper()); + debugLineClientRandom.append(" "); + debugLineClientRandom.append(masterKey.toHex().toUpper()); + debugLineClientRandom.append("\n"); - // set the list of supported ECs, but not the list - // of *default* ECs. OpenSSL doesn't like forcing an EC for the wrong - // ciphersuite, so don't try it -- leave the empty list to mean - // "the implementation will choose the most suitable one". - setDefaultSupportedEllipticCurves(curves); -} + QString sslKeyFile = QDir::tempPath() + QLatin1String("/qt-ssl-keys"); + QFile file(sslKeyFile); + if (!file.open(QIODevice::Append)) + qCWarning(lcSsl) << "could not open file" << sslKeyFile << "for appending"; + if (!file.write(debugLineClientRandom)) + qCWarning(lcSsl) << "could not write to file" << sslKeyFile; + file.close(); + } else { + qCWarning(lcTlsBackend, "could not decrypt SSL traffic"); + } +#endif // QT_DECRYPT_SSL_TRAFFIC -#ifndef Q_OS_DARWIN // Apple implementation in qsslsocket_mac_shared.cpp -QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates() -{ - ensureInitialized(); -#ifdef QSSLSOCKET_DEBUG - QElapsedTimer timer; - timer.start(); -#endif - QList<QSslCertificate> systemCerts; -#if defined(Q_OS_WIN) - HCERTSTORE hSystemStore; - hSystemStore = CertOpenSystemStoreW(0, 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_UNIX) - QSet<QString> certFiles; - QDir currentDir; - QStringList nameFilters; - QList<QByteArray> directories; - QSsl::EncodingFormat platformEncodingFormat; -# ifndef Q_OS_ANDROID - directories = unixRootCertDirectories(); - nameFilters << QLatin1String("*.pem") << QLatin1String("*.crt"); - platformEncodingFormat = QSsl::Pem; -# else - // Q_OS_ANDROID - QByteArray ministroPath = qgetenv("MINISTRO_SSL_CERTS_PATH"); // Set by Ministro - directories << ministroPath; - nameFilters << QLatin1String("*.der"); - platformEncodingFormat = QSsl::Der; -# ifndef Q_OS_ANDROID_EMBEDDED - if (ministroPath.isEmpty()) { - QList<QByteArray> certificateData = fetchSslCertificateData(); - for (int i = 0; i < certificateData.size(); ++i) { - systemCerts.append(QSslCertificate::fromData(certificateData.at(i), QSsl::Der)); - } - } else -# endif //Q_OS_ANDROID_EMBEDDED -# endif //Q_OS_ANDROID - { - currentDir.setNameFilters(nameFilters); - for (int a = 0; a < directories.count(); a++) { - currentDir.setPath(QLatin1String(directories.at(a))); - QDirIterator it(currentDir); - while (it.hasNext()) { - it.next(); - // use canonical path here to not load the same certificate twice if symlinked - certFiles.insert(it.fileInfo().canonicalFilePath()); + // Cache this SSL session inside the QSslContext + if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionSharing)) { + if (!sslContextPointer->cacheSession(ssl)) { + sslContextPointer.clear(); // we could not cache the session + } else { + // Cache the session for permanent usage as well + if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionPersistence)) { + if (!sslContextPointer->sessionASN1().isEmpty()) + configuration.sslSession = sslContextPointer->sessionASN1(); + configuration.sslSessionTicketLifeTimeHint = sslContextPointer->sessionTicketLifeTimeHint(); } } - for (const QString& file : qAsConst(certFiles)) - systemCerts.append(QSslCertificate::fromPath(file, platformEncodingFormat)); -# ifndef Q_OS_ANDROID - systemCerts.append(QSslCertificate::fromPath(QLatin1String("/etc/pki/tls/certs/ca-bundle.crt"), QSsl::Pem)); // Fedora, Mandriva - systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/local/share/certs/ca-root-nss.crt"), QSsl::Pem)); // FreeBSD's ca_root_nss -# endif } -#endif -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "systemCaCertificates retrieval time " << timer.elapsed() << "ms"; - qCDebug(lcSsl) << "imported " << systemCerts.count() << " certificates"; -#endif - return systemCerts; -} -#endif // Q_OS_DARWIN +#if !defined(OPENSSL_NO_NEXTPROTONEG) -void QSslSocketBackendPrivate::startClientEncryption() -{ - if (!initSslContext()) { - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Unable to init SSL Context: %1").arg(getErrorsFromOpenSsl())); - return; - } + configuration.nextProtocolNegotiationStatus = sslContextPointer->npnContext().status; + if (sslContextPointer->npnContext().status == QSslConfiguration::NextProtocolNegotiationUnsupported) { + // we could not agree -> be conservative and use HTTP/1.1 + configuration.nextNegotiatedProtocol = QByteArrayLiteral("http/1.1"); + } else { + const unsigned char *proto = nullptr; + unsigned int proto_len = 0; - // Start connecting. This will place outgoing data in the BIO, so we - // follow up with calling transmit(). - startHandshake(); - transmit(); -} + q_SSL_get0_alpn_selected(ssl, &proto, &proto_len); + if (proto_len && mode == QSslSocket::SslClientMode) { + // Client does not have a callback that sets it ... + configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNegotiated; + } -void QSslSocketBackendPrivate::startServerEncryption() -{ - if (!initSslContext()) { - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Unable to init SSL Context: %1").arg(getErrorsFromOpenSsl())); - return; + if (!proto_len) { // Test if NPN was more lucky ... + q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len); + } + + if (proto_len) + configuration.nextNegotiatedProtocol = QByteArray(reinterpret_cast<const char *>(proto), proto_len); + else + configuration.nextNegotiatedProtocol.clear(); } +#endif // !defined(OPENSSL_NO_NEXTPROTONEG) - // Start connecting. This will place outgoing data in the BIO, so we - // follow up with calling transmit(). - startHandshake(); - transmit(); -} + if (mode == QSslSocket::SslClientMode) { + EVP_PKEY *key; + if (q_SSL_get_server_tmp_key(ssl, &key)) + configuration.ephemeralServerKey = QSslKey(key, QSsl::PublicKey); + } -/*! - \internal + d->setEncrypted(true); + emit q->encrypted(); + if (d->isAutoStartingHandshake() && d->isPendingClose()) { + d->setPendingClose(false); + q->disconnectFromHost(); + } +} - Transmits encrypted data between the BIOs and the socket. -*/ -void QSslSocketBackendPrivate::transmit() +void TlsCryptographOpenSSL::transmit() { - Q_Q(QSslSocket); + Q_ASSERT(q); + Q_ASSERT(d); using ScopedBool = QScopedValueRollback<bool>; @@ -1115,13 +873,19 @@ void QSslSocketBackendPrivate::transmit() 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 (connectionEncrypted && !writeBuffer.isEmpty()) { + if (q->isEncrypted() && !writeBuffer.isEmpty()) { qint64 totalBytesWritten = 0; int nextDataBlockSize; while ((nextDataBlockSize = writeBuffer.nextDataBlockSize()) > 0) { @@ -1139,14 +903,14 @@ void QSslSocketBackendPrivate::transmit() } else { // ### Better error handling. const ScopedBool bg(inSetAndEmitError, true); - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Unable to write data: %1").arg( - getErrorsFromOpenSsl())); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to write data: %1").arg( + QTlsBackendOpenSSL::getErrorsFromOpenSsl())); return; } } #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: encrypted" << writtenBytes << "bytes"; + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encrypted" << writtenBytes << "bytes"; #endif writeBuffer.free(writtenBytes); totalBytesWritten += writtenBytes; @@ -1181,19 +945,20 @@ void QSslSocketBackendPrivate::transmit() // Write encrypted data from the buffer to the socket. qint64 actualWritten = plainSocket->write(data.constData(), encryptedBytesRead); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: wrote" << encryptedBytesRead << "encrypted bytes to the socket" << actualWritten << "actual."; + 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(plainSocket->error(), plainSocket->errorString()); + d->setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); return; } transmitting = true; } // Check if we've got any data to be read from the socket. - if (!connectionEncrypted || !readBufferMaxSize || buffer.size() < readBufferMaxSize) + 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); @@ -1201,7 +966,7 @@ void QSslSocketBackendPrivate::transmit() int encryptedBytesRead = plainSocket->peek(data.data(), pendingBytes); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: read" << encryptedBytesRead << "encrypted bytes from the socket"; + 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); @@ -1212,9 +977,9 @@ void QSslSocketBackendPrivate::transmit() } else { // ### Better error handling. const ScopedBool bg(inSetAndEmitError, true); - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Unable to decrypt data: %1").arg( - getErrorsFromOpenSsl())); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to decrypt data: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); return; } @@ -1223,27 +988,27 @@ void QSslSocketBackendPrivate::transmit() // If the connection isn't secured yet, this is the time to retry the // connect / accept. - if (!connectionEncrypted) { + if (!q->isEncrypted()) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: testing encryption"; + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: testing encryption"; #endif if (startHandshake()) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: encryption established"; + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encryption established"; #endif - connectionEncrypted = true; + d->setEncrypted(true); transmitting = true; } else if (plainSocket->state() != QAbstractSocket::ConnectedState) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: connection lost"; + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: connection lost"; #endif break; - } else if (paused) { + } else if (d->isPaused()) { // just wait until the user continues return; } else { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: encryption not done yet"; + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encryption not done yet"; #endif } } @@ -1260,7 +1025,7 @@ void QSslSocketBackendPrivate::transmit() int readBytes = 0; const int bytesToRead = 4096; do { - if (readChannelCount == 0) { + if (q->readChannelCount() == 0) { // The read buffer is deallocated, don't try resize or write to it. break; } @@ -1268,11 +1033,11 @@ void QSslSocketBackendPrivate::transmit() readBytes = q_SSL_read(ssl, buffer.reserve(bytesToRead), bytesToRead); if (readBytes > 0) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: decrypted" << readBytes << "bytes"; + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: decrypted" << readBytes << "bytes"; #endif buffer.chop(bytesToRead - readBytes); - if (readyReadEmittedPointer) + if (bool *readyReadEmittedPointer = d->readyReadPointer()) *readyReadEmittedPointer = true; emit q->readyRead(); emit q->channelReadyRead(0); @@ -1290,13 +1055,13 @@ void QSslSocketBackendPrivate::transmit() case SSL_ERROR_ZERO_RETURN: // The remote host closed the connection. #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: remote disconnect"; + 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(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); + d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); } return; case SSL_ERROR_SYSCALL: // some IO error @@ -1306,8 +1071,9 @@ void QSslSocketBackendPrivate::transmit() systemOrSslErrorDetected = true; { const ScopedBool bg(inSetAndEmitError, true); - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl())); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Error while reading: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); } return; default: @@ -1318,8 +1084,9 @@ void QSslSocketBackendPrivate::transmit() // So this default case should never be triggered. { const ScopedBool bg(inSetAndEmitError, true); - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl())); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Error while reading: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); } break; } @@ -1327,229 +1094,118 @@ void QSslSocketBackendPrivate::transmit() } while (ssl && transmitting); } -QString QSslSocketBackendPrivate::msgErrorsDuringHandshake() +void TlsCryptographOpenSSL::disconnectFromHost() { - return QSslSocket::tr("Error during SSL handshake: %1") - .arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); + 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(); } -bool QSslSocketBackendPrivate::startHandshake() +void TlsCryptographOpenSSL::disconnected() { - Q_Q(QSslSocket); - - // Check if the connection has been established. Get all errors from the - // verification stage. - - using ScopedBool = QScopedValueRollback<bool>; - - if (inSetAndEmitError) - return false; - - pendingFatalAlert = false; - errorsReportedFromCallback = false; - QList<QSslErrorEntry> lastErrors; - q_SSL_set_ex_data(ssl, 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, 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, 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(); + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); - if (!errorsReportedFromCallback) { - for (const auto ¤tError : qAsConst(lastErrors)) { - emit q->peerVerifyError(QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(currentError.code, - configuration.peerCertificateChain.value(currentError.depth))); - if (q->state() != QAbstractSocket::ConnectedState) - break; - } + 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. +} - 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 = QSslSocketBackendPrivate::msgErrorsDuringHandshake(); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QSslSocketBackendPrivate::startHandshake: error!" << errorString; -#endif - { - const ScopedBool bg(inSetAndEmitError, true); - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, errorString); - if (pendingFatalAlert) { - trySendFatalAlert(); - pendingFatalAlert = false; - } - } - q->abort(); - } - return false; - } +QSslCipher TlsCryptographOpenSSL::sessionCipher() const +{ + if (!ssl) + return {}; - // store peer certificate chain - storePeerCertificates(); + const SSL_CIPHER *sessionCipher = q_SSL_get_current_cipher(ssl); + return sessionCipher ? QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(sessionCipher) : QSslCipher{}; +} - // Start translating errors. - QList<QSslError> errors; +QSsl::SslProtocol TlsCryptographOpenSSL::sessionProtocol() const +{ + if (!ssl) + return QSsl::UnknownProtocol; - // check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer) - for (const QSslCertificate &cert : qAsConst(configuration.peerCertificateChain)) { - if (QSslCertificatePrivate::isBlacklisted(cert)) { - QSslError error(QSslError::CertificateBlacklisted, cert); - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } + const int ver = q_SSL_version(ssl); + switch (ver) { + case 0x301: + return QSsl::TlsV1_0; + case 0x302: + return QSsl::TlsV1_1; + case 0x303: + return QSsl::TlsV1_2; + case 0x304: + return QSsl::TlsV1_3; } - 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(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 + return QSsl::UnknownProtocol; +} - // 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 - if (mode == QSslSocket::SslClientMode) { - QString peerName = (verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); +QList<QOcspResponse> TlsCryptographOpenSSL::ocsps() const +{ + return ocspResponses; +} - 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; - } - } +bool TlsCryptographOpenSSL::checkSslErrors() +{ + Q_ASSERT(q); + Q_ASSERT(d); - // Translate errors from the error list into QSslErrors. - errors.reserve(errors.size() + errorList.size()); - for (const auto &error : qAsConst(errorList)) - errors << QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(error.code, configuration.peerCertificateChain.value(error.depth)); + if (sslErrors.isEmpty()) + return true; - if (!errors.isEmpty()) { - sslErrors = errors; + emit q->sslErrors(sslErrors); -#ifdef Q_OS_WIN - const bool fetchEnabled = s_loadRootCertsOnDemand - && allowRootCertOnDemandLoading; - // !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 && !verifyErrorsHaveBeenIgnored()) - certToFetch = findCertificateToFetch(sslErrors, !fetchEnabled); + auto &configuration = d->privateConfiguration(); + const auto mode = d->tlsMode(); - //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; + bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer + || (configuration.peerVerifyMode == 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 { + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString()); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + plainSocket->disconnectFromHost(); } -#endif - 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(); + return false; } - - continueHandshake(); return true; } -void QSslSocketBackendPrivate::storePeerCertificates() -{ - // 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); - configuration.peerCertificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); - q_X509_free(x509); - if (configuration.peerCertificateChain.isEmpty()) { - configuration.peerCertificateChain = QTlsPrivate::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(q_SSL_get_peer_cert_chain(ssl)); - if (!configuration.peerCertificate.isNull() && mode == QSslSocket::SslServerMode) - configuration.peerCertificateChain.prepend(configuration.peerCertificate); - } -} - -int QSslSocketBackendPrivate::handleNewSessionTicket(SSL *connection) +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_Q(QSslSocket); - Q_ASSERT(connection); + Q_ASSERT(q); if (q->sslConfiguration().testSslOption(QSsl::SslOptionDisableSessionPersistence)) { // We silently ignore, do nothing, remove from cache. @@ -1558,7 +1214,7 @@ int QSslSocketBackendPrivate::handleNewSessionTicket(SSL *connection) SSL_SESSION *currentSession = q_SSL_get_session(connection); if (!currentSession) { - qCWarning(lcSsl, + qCWarning(lcTlsBackend, "New session ticket callback, the session is invalid (nullptr)"); return 0; } @@ -1570,14 +1226,14 @@ int QSslSocketBackendPrivate::handleNewSessionTicket(SSL *connection) #ifdef TLS1_3_VERSION if (!q_SSL_SESSION_is_resumable(currentSession)) { - qCDebug(lcSsl, "New session ticket, but the session is non-resumable"); + 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(lcSsl, "could not store persistent version of SSL session"); + qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); return 0; } @@ -1585,10 +1241,11 @@ int QSslSocketBackendPrivate::handleNewSessionTicket(SSL *connection) QByteArray sessionTicket(sessionSize, 0); auto data = reinterpret_cast<unsigned char *>(sessionTicket.data()); if (!q_i2d_SSL_SESSION(currentSession, &data)) { - qCWarning(lcSsl, "could not store persistent version of SSL session"); + qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); return 0; } + auto &configuration = d->privateConfiguration(); configuration.sslSession = sessionTicket; configuration.sslSessionTicketLifeTimeHint = int(q_SSL_SESSION_get_ticket_lifetime_hint(currentSession)); @@ -1596,181 +1253,273 @@ int QSslSocketBackendPrivate::handleNewSessionTicket(SSL *connection) return 0; } -bool QSslSocketBackendPrivate::checkSslErrors() +void TlsCryptographOpenSSL::alertMessageSent(int value) { - Q_Q(QSslSocket); - if (sslErrors.isEmpty()) - return true; - - emit q->sslErrors(sslErrors); + Q_ASSERT(q); + Q_ASSERT(d); - bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer - || (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer - && mode == QSslSocket::SslClientMode); - bool doEmitSslError = !verifyErrorsHaveBeenIgnored(); - // check whether we need to emit an SSL handshake error - if (doVerifyPeer && doEmitSslError) { - if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { - pauseSocketNotifiers(q); - paused = true; - } else { - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString()); - plainSocket->disconnectFromHost(); - } - return false; + const auto level = tlsAlertLevel(value); + if (level == QSsl::AlertLevel::Fatal && !q->isEncrypted()) { + // Note, this logic is handshake-time only: + pendingFatalAlert = true; } - return true; + + emit q->alertSent(level, tlsAlertType(value), tlsAlertDescription(value)); + } -unsigned int QSslSocketBackendPrivate::tlsPskClientCallback(const char *hint, - char *identity, unsigned int max_identity_len, - unsigned char *psk, unsigned int max_psk_len) +void TlsCryptographOpenSSL::alertMessageReceived(int value) { - QSslPreSharedKeyAuthenticator authenticator; + Q_ASSERT(q); - // Fill in some read-only fields (for the user) - if (hint) - authenticator.d->identityHint = QByteArray::fromRawData(hint, int(::strlen(hint))); // it's NUL terminated, but do not include the NUL + emit q->alertReceived(tlsAlertLevel(value), tlsAlertType(value), tlsAlertDescription(value)); +} - authenticator.d->maximumIdentityLength = int(max_identity_len) - 1; // needs to be NUL terminated - authenticator.d->maximumPreSharedKeyLength = int(max_psk_len); +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); - // Let the client provide the remaining bits... - Q_Q(QSslSocket); - emit q->preSharedKeyAuthenticationRequired(&authenticator); + 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); - // No PSK set? Return now to make the handshake fail - if (authenticator.preSharedKey().isEmpty()) + 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; + } - // Copy data back into OpenSSL - const int identityLength = qMin(authenticator.identity().length(), authenticator.maximumIdentityLength()); - ::memcpy(identity, authenticator.identity().constData(), identityLength); - identity[identityLength] = 0; + const QSslCertificate certificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + const auto errorAndDepth = QTlsPrivate::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx); + const QSslError tlsError = QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(errorAndDepth.code, certificate); - const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); - ::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); - return pskLength; + 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; } -unsigned int QSslSocketBackendPrivate::tlsPskServerCallback(const char *identity, - unsigned char *psk, unsigned int max_psk_len) +void TlsCryptographOpenSSL::trySendFatalAlert() { - QSslPreSharedKeyAuthenticator authenticator; - - // Fill in some read-only fields (for the user) - authenticator.d->identityHint = configuration.preSharedKeyIdentityHint; - authenticator.d->identity = identity; - authenticator.d->maximumIdentityLength = 0; // user cannot set an identity - authenticator.d->maximumPreSharedKeyLength = int(max_psk_len); + Q_ASSERT(pendingFatalAlert); + Q_ASSERT(d); - // Let the client provide the remaining bits... - Q_Q(QSslSocket); - emit q->preSharedKeyAuthenticationRequired(&authenticator); + auto *plainSocket = d->plainTcpSocket(); - // No PSK set? Return now to make the handshake fail - if (authenticator.preSharedKey().isEmpty()) - return 0; + 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); - // Copy data back into OpenSSL - const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); - ::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); - return pskLength; + // Write encrypted data from the buffer to the socket. + qint64 actualWritten = plainSocket->write(data.constData(), bioReadBytes); + if (actualWritten < 0) + return; + plainSocket->flush(); + } } -#ifdef Q_OS_WIN - -void QSslSocketBackendPrivate::fetchCaRootForCert(const QSslCertificate &cert) +bool TlsCryptographOpenSSL::initSslContext() { - Q_Q(QSslSocket); - //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 = configuration.caCertificates; + Q_ASSERT(q); + Q_ASSERT(d); - //Remember we are fetching and what we are fetching: - caToFetch = cert; + // If no external context was set (e.g. by QHttpNetworkConnection) we will + // create a default context + auto &configuration = d->privateConfiguration(); + const auto mode = d->tlsMode(); - QWindowsCaRootFetcher *fetcher = new QWindowsCaRootFetcher(cert, mode, customRoots, q->peerVerifyName()); - QObjectPrivate::connect(fetcher, &QWindowsCaRootFetcher::finished, - this, &QSslSocketBackendPrivate::_q_caRootLoaded, - Qt::QueuedConnection); - QMetaObject::invokeMethod(fetcher, "start", Qt::QueuedConnection); - pauseSocketNotifiers(q); - paused = true; -} + if (!sslContextPointer) { + // create a deep copy of our configuration + QSslConfigurationPrivate *configurationCopy = new QSslConfigurationPrivate(configuration); + configurationCopy->ref.storeRelaxed(0); // the QSslConfiguration constructor refs up + sslContextPointer = QSslContext::sharedFromPrivateConfiguration(mode, configurationCopy, + d->isRootsOnDemandAllowed()); + } -//This is the callback from QWindowsCaRootFetcher, trustedRoot will be invalid (default constructed) if it failed. -void QSslSocketBackendPrivate::_q_caRootLoaded(QSslCertificate cert, QSslCertificate trustedRoot) -{ - if (caToFetch != cert) { - //Ooops, something from the previous connection attempt, ignore! - return; + if (sslContextPointer->error() != QSslError::NoError) { + d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, sslContextPointer->errorString()); + sslContextPointer.clear(); // deletes the QSslContext + return false; } - //Done, fetched already: - caToFetch = QSslCertificate{}; + // Create and initialize SSL session + if (!(ssl = sslContextPointer->createSsl())) { + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Error creating SSL session, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return false; + } - if (fetchAuthorityInformation) { - if (!configuration.caCertificates.contains(trustedRoot)) - trustedRoot = QSslCertificate{}; - fetchAuthorityInformation = 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.sslOptions & 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"); + } } - if (!trustedRoot.isNull() && !trustedRoot.isBlacklisted()) { - if (s_loadRootCertsOnDemand) { - //Add the new root cert to default cert list for use by future sockets - auto defaultConfig = QSslConfiguration::defaultConfiguration(); - defaultConfig.addCaCertificate(trustedRoot); - QSslConfiguration::setDefaultConfiguration(defaultConfig); + // 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) { + d->setErrorAndEmit(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) { + d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Server-side QSslSocket does not support OCSP stapling")); + return false; } - //Add the new root cert to this socket for future connections - if (!configuration.caCertificates.contains(trustedRoot)) - configuration.caCertificates += 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; - } - } + if (q_SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp) != 1) { + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Failed to enable OCSP stapling")); + return false; } } - // Continue with remaining errors - if (plainSocket) - plainSocket->resume(); - paused = false; - if (checkSslErrors() && ssl) { - bool willClose = (autoStartHandshake && pendingClose); - continueHandshake(); - if (!willClose) - transmit(); + ocspResponseDer.clear(); + auto responsePos = configuration.backendConfig.find("Qt-OCSP-response"); + if (responsePos != configuration.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) { + d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Client-side sockets do not send OCSP responses")); + return false; + } } +#endif // ocsp + + return true; } -#endif +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.clear(); +} + +void TlsCryptographOpenSSL::storePeerCertificates() +{ + Q_ASSERT(d); + auto &configuration = d->privateConfiguration(); + // 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); + configuration.peerCertificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + q_X509_free(x509); + if (configuration.peerCertificateChain.isEmpty()) { + configuration.peerCertificateChain = QTlsPrivate::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(q_SSL_get_peer_cert_chain(ssl)); + if (!configuration.peerCertificate.isNull() && d->tlsMode() == QSslSocket::SslServerMode) + configuration.peerCertificateChain.prepend(configuration.peerCertificate); + } +} #if QT_CONFIG(ocsp) -bool QSslSocketBackendPrivate::checkOcspStatus() +bool TlsCryptographOpenSSL::checkOcspStatus() { Q_ASSERT(ssl); - Q_ASSERT(mode == QSslSocket::SslClientMode); // See initSslContext() for SslServerMode + Q_ASSERT(d); + + auto &configuration = d->privateConfiguration(); + Q_ASSERT(d->tlsMode() == QSslSocket::SslClientMode); // See initSslContext() for SslServerMode Q_ASSERT(configuration.peerVerifyMode != QSslSocket::VerifyNone); const auto clearErrorQueue = qScopeGuard([] { - logAndClearErrorQueue(); + QTlsBackendOpenSSL::logAndClearErrorQueue(); }); ocspResponses.clear(); @@ -1936,343 +1685,148 @@ bool QSslSocketBackendPrivate::checkOcspStatus() return !ocspErrors.size(); } -#endif // ocsp - -void QSslSocketBackendPrivate::alertMessageSent(int value) -{ - Q_Q(QSslSocket); +#endif // QT_CONFIG(ocsp) - const auto level = tlsAlertLevel(value); - if (level == QSsl::AlertLevel::Fatal && !connectionEncrypted) { - // Note, this logic is handshake-time only: - pendingFatalAlert = true; - } - emit q->alertSent(level, tlsAlertType(value), tlsAlertDescription(value)); -} - -void QSslSocketBackendPrivate::alertMessageReceived(int value) -{ - Q_Q(QSslSocket); - - emit q->alertReceived(tlsAlertLevel(value), tlsAlertType(value), tlsAlertDescription(value)); -} - -int QSslSocketBackendPrivate::emitErrorFromCallback(X509_STORE_CTX *ctx) +unsigned TlsCryptographOpenSSL::pskClientTlsCallback(const char *hint, char *identity, + unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len) { - // Returns 0 to abort verification, 1 to continue despite error (as - // OpenSSL expects from the verification callback). - Q_Q(QSslSocket); - - Q_ASSERT(ctx); + Q_ASSERT(q); - 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); + 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); - X509 *x509 = q_X509_STORE_CTX_get_current_cert(ctx); - if (!x509) { - qCWarning(lcSsl, "Could not obtain the certificate (that failed to verify)"); + // No PSK set? Return now to make the handshake fail + if (authenticator.preSharedKey().isEmpty()) 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 = QSslSocketBackendPrivate::s_indexForSSLExtraData - + QSslSocketBackendPrivate::errorOffsetInExData; - if (auto errorList = static_cast<QList<QSslErrorEntry> *>(q_SSL_get_ex_data(ssl, offset))) - errorList->append(errorAndDepth); + // Copy data back into OpenSSL + const int identityLength = qMin(authenticator.identity().length(), authenticator.maximumIdentityLength()); + std::memcpy(identity, authenticator.identity().constData(), identityLength); + identity[identityLength] = 0; - // An application is expected to ignore this error (by calling ignoreSslErrors) - // in its directly connected slot: - return !handshakeInterrupted; + const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); + std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); + return pskLength; } -void QSslSocketBackendPrivate::trySendFatalAlert() +unsigned TlsCryptographOpenSSL::pskServerTlsCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len) { - Q_ASSERT(pendingFatalAlert); + Q_ASSERT(q); - 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); + QSslPreSharedKeyAuthenticator authenticator; - // Write encrypted data from the buffer to the socket. - qint64 actualWritten = plainSocket->write(data.constData(), bioReadBytes); - if (actualWritten < 0) - return; - plainSocket->flush(); - } -} + // Fill in some read-only fields (for the user) + QTlsBackend::setupServerPskAuth(&authenticator, identity, d->privateConfiguration().preSharedKeyIdentityHint, + max_psk_len); + emit q->preSharedKeyAuthenticationRequired(&authenticator); -void QSslSocketBackendPrivate::disconnectFromHost() -{ - if (ssl) { - if (!shutdown && !q_SSL_in_init(ssl) && !systemOrSslErrorDetected) { - if (q_SSL_shutdown(ssl) != 1) { - // Some error may be queued, clear it. - const auto errors = getErrorsFromOpenSsl(); - Q_UNUSED(errors); - } - shutdown = true; - transmit(); - } - } - plainSocket->disconnectFromHost(); -} + // No PSK set? Return now to make the handshake fail + if (authenticator.preSharedKey().isEmpty()) + return 0; -void QSslSocketBackendPrivate::disconnected() -{ - if (plainSocket->bytesAvailable() <= 0) - destroySslContext(); - else { - // Move all bytes into the plain buffer - qint64 tmpReadBufferMaxSize = readBufferMaxSize; - readBufferMaxSize = 0; // reset temporarily so the plain socket buffer is completely drained - transmit(); - readBufferMaxSize = 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. + // Copy data back into OpenSSL + const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); + std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); + return pskLength; } -QSslCipher QSslSocketBackendPrivate::sessionCipher() const -{ - if (!ssl) - return QSslCipher(); - - const SSL_CIPHER *sessionCipher = q_SSL_get_current_cipher(ssl); - return sessionCipher ? QSslCipher_from_SSL_CIPHER(sessionCipher) : QSslCipher(); -} +#ifdef Q_OS_WIN -QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const +void TlsCryptographOpenSSL::fetchCaRootForCert(const QSslCertificate &cert) { - if (!ssl) - return QSsl::UnknownProtocol; - int ver = q_SSL_version(ssl); + Q_ASSERT(d); + Q_ASSERT(q); - switch (ver) { - case 0x301: - return QSsl::TlsV1_0; - case 0x302: - return QSsl::TlsV1_1; - case 0x303: - return QSsl::TlsV1_2; - case 0x304: - return QSsl::TlsV1_3; - } + //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 = d->privateConfiguration().caCertificates; - return QSsl::UnknownProtocol; -} + //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 QSslSocketBackendPrivate::continueHandshake() +void TlsCryptographOpenSSL::caRootLoaded(QSslCertificate cert, QSslCertificate trustedRoot) { - Q_Q(QSslSocket); - // if we have a max read buffer size, reset the plain socket's to match - if (readBufferMaxSize) - plainSocket->setReadBufferSize(readBufferMaxSize); - - if (q_SSL_session_reused(ssl)) - configuration.peerSessionShared = 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), 0, 0); - size_t client_random_len = q_SSL_get_client_random(ssl, 0, 0); - QByteArray masterKey(int(master_key_len), 0); // Will not overflow - QByteArray clientRandom(int(client_random_len), 0); // 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() + QLatin1String("/qt-ssl-keys"); - QFile file(sslKeyFile); - if (!file.open(QIODevice::Append)) - qCWarning(lcSsl) << "could not open file" << sslKeyFile << "for appending"; - if (!file.write(debugLineClientRandom)) - qCWarning(lcSsl) << "could not write to file" << sslKeyFile; - file.close(); - } else { - qCWarning(lcSsl, "could not decrypt SSL traffic"); - } -#endif - - // Cache this SSL session inside the QSslContext - if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionSharing)) { - if (!sslContextPointer->cacheSession(ssl)) { - sslContextPointer.clear(); // we could not cache the session - } else { - // Cache the session for permanent usage as well - if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionPersistence)) { - if (!sslContextPointer->sessionASN1().isEmpty()) - configuration.sslSession = sslContextPointer->sessionASN1(); - configuration.sslSessionTicketLifeTimeHint = sslContextPointer->sessionTicketLifeTimeHint(); - } - } + if (caToFetch != cert) { + //Ooops, something from the previous connection attempt, ignore! + return; } -#if !defined(OPENSSL_NO_NEXTPROTONEG) - - configuration.nextProtocolNegotiationStatus = sslContextPointer->npnContext().status; - if (sslContextPointer->npnContext().status == QSslConfiguration::NextProtocolNegotiationUnsupported) { - // we could not agree -> be conservative and use HTTP/1.1 - configuration.nextNegotiatedProtocol = 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 ... - configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNegotiated; - } - - if (!proto_len) { // Test if NPN was more lucky ... - q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len); - } - - if (proto_len) - configuration.nextNegotiatedProtocol = QByteArray(reinterpret_cast<const char *>(proto), proto_len); - else - configuration.nextNegotiatedProtocol.clear(); - } -#endif // !defined(OPENSSL_NO_NEXTPROTONEG) + Q_ASSERT(d); + Q_ASSERT(q); - if (mode == QSslSocket::SslClientMode) { - EVP_PKEY *key; - if (q_SSL_get_server_tmp_key(ssl, &key)) - configuration.ephemeralServerKey = QSslKey(key, QSsl::PublicKey); - } + auto &configuration = d->privateConfiguration(); + //Done, fetched already: + caToFetch = QSslCertificate{}; - connectionEncrypted = true; - emit q->encrypted(); - if (autoStartHandshake && pendingClose) { - pendingClose = false; - q->disconnectFromHost(); + if (fetchAuthorityInformation) { + if (!configuration.caCertificates.contains(trustedRoot)) + trustedRoot = QSslCertificate{}; + fetchAuthorityInformation = false; } -} -bool QSslSocketPrivate::ensureLibraryLoaded() -{ - if (!q_resolveOpenSslSymbols()) - return false; - - const QMutexLocker locker(qt_opensslInitMutex()); - - if (!s_libraryLoaded) { - // Initialize OpenSSL. - if (q_OPENSSL_init_ssl(0, nullptr) != 1) - return false; - - if (q_OpenSSL_version_num() < 0x10101000L) { - qCWarning(lcSsl, "QSslSocket: OpenSSL >= 1.1.1 is required; %s was found instead", q_OpenSSL_version(OPENSSL_VERSION)); - return 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); } - - q_SSL_load_error_strings(); - q_OpenSSL_add_all_algorithms(); - - QSslSocketBackendPrivate::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; + //Add the new root cert to this socket for future connections + if (!configuration.caCertificates.contains(trustedRoot)) + configuration.caCertificates += 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; + } + } } - - s_libraryLoaded = true; } - return true; -} -void QSslSocketPrivate::ensureCiphersAndCertsLoaded() -{ - const QMutexLocker locker(qt_opensslInitMutex()); - - if (s_loadedCiphersAndCerts) - return; - s_loadedCiphersAndCerts = true; - - resetDefaultCiphers(); - resetDefaultEllipticCurves(); - -#if QT_CONFIG(library) - //load symbols needed to receive certificates from system store -#if defined(Q_OS_QNX) - s_loadRootCertsOnDemand = 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) - QList<QByteArray> dirs = unixRootCertDirectories(); - QStringList symLinkFilter; - symLinkFilter << QLatin1String("[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]"); - for (int a = 0; a < dirs.count(); ++a) { - QDirIterator iterator(QLatin1String(dirs.at(a)), symLinkFilter, QDir::Files); - if (iterator.hasNext()) { - s_loadRootCertsOnDemand = true; - 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 -#endif // QT_CONFIG(library) - // if on-demand loading was not enabled, load the certs now - if (!s_loadRootCertsOnDemand) - 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. - s_loadRootCertsOnDemand = true; -#endif -} - -QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &certificateChain, - const QString &hostName) -{ - return QTlsPrivate::X509CertificateOpenSSL::verify(certificateChain, hostName); } -QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &caCertificates, - const QList<QSslCertificate> &certificateChain, - const QString &hostName) -{ - return QTlsPrivate::X509CertificateOpenSSL::verify(caCertificates, certificateChain, hostName); -} +#endif // Q_OS_WIN -void QSslSocketPrivate::registerAdHocFactory() -{ - // TLSTODO: this is a temporary solution, waiting for - // backends to move to ... plugins. - if (!backend()) - qCWarning(lcSsl, "Failed to create backend factory"); -} +} // namespace QTlsPrivate QT_END_NAMESPACE diff --git a/src/network/ssl/qtls_openssl_p.h b/src/network/ssl/qtls_openssl_p.h new file mode 100644 index 0000000000..1def93831d --- /dev/null +++ b/src/network/ssl/qtls_openssl_p.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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 <private/qtnetworkglobal_p.h> + +#include "qtlsbackend_openssl_p.h" +#include "qsslcontext_openssl_p.h" +#include "qsslcertificate.h" +#include "qocspresponse.h" +#include "qopenssl_p.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(QSharedPointer<QSslContext> tlsContext) override; + QSharedPointer<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); + +#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(); + + QSharedPointer<QSslContext> sslContextPointer; + SSL *ssl = nullptr; // TLSTODO: RAII. + + QList<QSslErrorEntry> errorList; + QList<QSslError> sslErrors; + + BIO *readBio = nullptr; + BIO *writeBio = nullptr; + + QList<QOcspResponse> ocspResponses; + + // This decription 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; + QSslCertificate caToFetch; + + bool inSetAndEmitError = false; + bool pendingFatalAlert = false; + bool errorsReportedFromCallback = false; + + bool shutdown = false; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLS_OPENSSL_P_H + diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qtls_schannel.cpp index debcc42084..760572a94f 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qtls_schannel.cpp @@ -41,7 +41,7 @@ #include "qssl_p.h" #include "qsslsocket.h" -#include "qsslsocket_schannel_p.h" +#include "qtls_schannel_p.h" #include "qsslcertificate.h" #include "qsslcertificateextension.h" #include "qsslcertificate_p.h" @@ -161,10 +161,99 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.schannel"); +// Defined in qsslsocket_qt.cpp. +QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key, + const QString &passPhrase); + +namespace QTlsPrivate { + +QList<QSslCipher> defaultCiphers() +{ + // Previously the code was in QSslSocketBackendPrivate. + QList<QSslCipher> ciphers; + // @temp (I hope), stolen from qsslsocket_winrt.cpp + const QString protocolStrings[] = { QStringLiteral("TLSv1"), QStringLiteral("TLSv1.1"), + QStringLiteral("TLSv1.2"), QStringLiteral("TLSv1.3") }; + const QSsl::SslProtocol protocols[] = { QSsl::TlsV1_0, QSsl::TlsV1_1, + QSsl::TlsV1_2, QSsl::TlsV1_3 }; + const int size = ARRAYSIZE(protocols); + static_assert(size == ARRAYSIZE(protocolStrings)); + ciphers.reserve(size); + for (int i = 0; i < size; ++i) { + const QSslCipher cipher = QTlsBackend::createCipher(QStringLiteral("Schannel"), + protocols[i], protocolStrings[i]); + + ciphers.append(cipher); + } + + return ciphers; + +} + +} // namespace QTlsPrivate + namespace { bool supportsTls13(); } +bool QSchannelBackend::s_loadedCiphersAndCerts = false; +Q_GLOBAL_STATIC(QRecursiveMutex, qt_schannel_mutex) + +long QSchannelBackend::tlsLibraryVersionNumber() const +{ + const auto os = QOperatingSystemVersion::current(); + return (os.majorVersion() << 24) | ((os.minorVersion() & 0xFF) << 16) | (os.microVersion() & 0xFFFF); +} + +QString QSchannelBackend::tlsLibraryVersionString() const +{ + const auto os = QOperatingSystemVersion::current(); + return QString::fromLatin1("Secure Channel, %1 %2.%3.%4") + .arg(os.name(), + QString::number(os.majorVersion()), + QString::number(os.minorVersion()), + QString::number(os.microVersion())); +} + +long QSchannelBackend::tlsLibraryBuildVersionNumber() const +{ + return tlsLibraryVersionNumber(); +} + +QString QSchannelBackend::tlsLibraryBuildVersionString() const +{ + const auto os = QOperatingSystemVersion::current(); + return QString::fromLatin1("%1.%2.%3") + .arg(QString::number(os.majorVersion()), + QString::number(os.minorVersion()), + QString::number(os.microVersion())); +} + +void QSchannelBackend::ensureInitialized() const +{ + ensureInitializedImplementation(); +} + +void QSchannelBackend::ensureInitializedImplementation() +{ + const QMutexLocker<QRecursiveMutex> locker(qt_schannel_mutex); + if (s_loadedCiphersAndCerts) + return; + s_loadedCiphersAndCerts = true; + + setDefaultCaCertificates(systemCaCertificatesImplementation()); + // setDefaultCaCertificates sets it to false, re-enable it: + QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); + + resetDefaultCiphers(); +} + +void QSchannelBackend::resetDefaultCiphers() +{ + setDefaultSupportedCiphers(QTlsPrivate::defaultCiphers()); + setDefaultCiphers(QTlsPrivate::defaultCiphers()); +} + QString QSchannelBackend::backendName() const { return builtinBackendNames[nameIndexSchannel]; @@ -222,6 +311,32 @@ QTlsPrivate::X509Certificate *QSchannelBackend::createCertificate() const return new QTlsPrivate::X509CertificateSchannel; } +QList<QSslCertificate> QSchannelBackend::systemCaCertificates() const +{ + return systemCaCertificatesImplementation(); +} + +QTlsPrivate::TlsCryptograph *QSchannelBackend::createTlsCryptograph() const +{ + return new QTlsPrivate::TlsCryptographSchannel; +} + +QList<QSslCertificate> QSchannelBackend::systemCaCertificatesImplementation() +{ + // Similar to non-Darwin version found in qtlsbackend_openssl.cpp, + // QTlsPrivate::systemCaCertificates function. + QList<QSslCertificate> systemCerts; + auto hSystemStore = QHCertStorePointer(CertOpenSystemStore(0, L"ROOT")); + if (hSystemStore) { + PCCERT_CONTEXT pc = nullptr; + while ((pc = CertFindCertificateInStore(hSystemStore.get(), X509_ASN_ENCODING, 0, + CERT_FIND_ANY, nullptr, pc))) { + systemCerts.append(QTlsPrivate::X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(pc)); + } + } + return systemCerts; +} + QTlsPrivate::X509PemReaderPtr QSchannelBackend::X509PemReader() const { return QTlsPrivate::X509CertificateGeneric::certificatesFromPem; @@ -232,7 +347,7 @@ QTlsPrivate::X509DerReaderPtr QSchannelBackend::X509DerReader() const return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; } -Q_GLOBAL_STATIC(QSchannelBackend, backend) +Q_GLOBAL_STATIC(QSchannelBackend, backendSchannel) namespace { @@ -588,93 +703,17 @@ qint64 checkIncompleteData(const SecBuffer &secBuffer) } // anonymous namespace -bool QSslSocketPrivate::s_loadRootCertsOnDemand = true; -bool QSslSocketPrivate::s_loadedCiphersAndCerts = false; -Q_GLOBAL_STATIC(QRecursiveMutex, qt_schannel_mutex) - -void QSslSocketPrivate::ensureInitialized() -{ - const QMutexLocker<QRecursiveMutex> locker(qt_schannel_mutex); - if (s_loadedCiphersAndCerts) - return; - s_loadedCiphersAndCerts = true; - - setDefaultCaCertificates(systemCaCertificates()); - s_loadRootCertsOnDemand = true; // setDefaultCaCertificates sets it to false, re-enable it. - - resetDefaultCiphers(); -} -void QSslSocketPrivate::resetDefaultCiphers() -{ - setDefaultSupportedCiphers(QSslSocketBackendPrivate::defaultCiphers()); - setDefaultCiphers(QSslSocketBackendPrivate::defaultCiphers()); -} +namespace QTlsPrivate { -void QSslSocketPrivate::resetDefaultEllipticCurves() -{ - Q_UNIMPLEMENTED(); -} - -bool QSslSocketPrivate::supportsSsl() -{ - return true; -} - -QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates() -{ - // Copied from qsslsocket_openssl.cpp's systemCaCertificates function. - QList<QSslCertificate> systemCerts; - auto hSystemStore = QHCertStorePointer(CertOpenSystemStore(0, L"ROOT")); - if (hSystemStore) { - PCCERT_CONTEXT pc = nullptr; - while ((pc = CertFindCertificateInStore(hSystemStore.get(), X509_ASN_ENCODING, 0, - CERT_FIND_ANY, nullptr, pc))) { - systemCerts.append(QTlsPrivate::X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(pc)); - } - } - return systemCerts; -} - -long QSslSocketPrivate::sslLibraryVersionNumber() -{ - const auto os = QOperatingSystemVersion::current(); - return (os.majorVersion() << 24) | ((os.minorVersion() & 0xFF) << 16) | (os.microVersion() & 0xFFFF); -} - -QString QSslSocketPrivate::sslLibraryVersionString() -{ - const auto os = QOperatingSystemVersion::current(); - return QString::fromLatin1("Secure Channel, %1 %2.%3.%4") - .arg(os.name(), - QString::number(os.majorVersion()), - QString::number(os.minorVersion()), - QString::number(os.microVersion())); -} - -long QSslSocketPrivate::sslLibraryBuildVersionNumber() -{ - // There is no separate build version - return sslLibraryVersionNumber(); -} - -QString QSslSocketPrivate::sslLibraryBuildVersionString() -{ - const auto os = QOperatingSystemVersion::current(); - return QString::fromLatin1("%1.%2.%3") - .arg(QString::number(os.majorVersion()), - QString::number(os.minorVersion()), - QString::number(os.microVersion())); -} - -QSslSocketBackendPrivate::QSslSocketBackendPrivate() +TlsCryptographSchannel::TlsCryptographSchannel() { SecInvalidateHandle(&credentialHandle); SecInvalidateHandle(&contextHandle); - ensureInitialized(); + QSchannelBackend::ensureInitializedImplementation(); } -QSslSocketBackendPrivate::~QSslSocketBackendPrivate() +TlsCryptographSchannel::~TlsCryptographSchannel() { closeCertificateStores(); deallocateContext(); @@ -682,29 +721,51 @@ QSslSocketBackendPrivate::~QSslSocketBackendPrivate() CertFreeCertificateContext(localCertContext); } -bool QSslSocketBackendPrivate::sendToken(void *token, unsigned long tokenLength, bool emitError) +void TlsCryptographSchannel::init(QSslSocket *qObj, QSslSocketPrivate *dObj) +{ + Q_ASSERT(qObj); + Q_ASSERT(dObj); + + q = qObj; + d = dObj; + + reset(); +} + +bool TlsCryptographSchannel::sendToken(void *token, unsigned long tokenLength, bool emitError) { if (tokenLength == 0) return true; + + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + const qint64 written = plainSocket->write(static_cast<const char *>(token), tokenLength); if (written != qint64(tokenLength)) { // Failed to write/buffer everything or an error occurred if (emitError) - setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); + d->setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); return false; } return true; } -QString QSslSocketBackendPrivate::targetName() const +QString TlsCryptographSchannel::targetName() const { // Used for SNI extension - return verificationPeerName.isEmpty() ? q_func()->peerName() : verificationPeerName; + Q_ASSERT(q); + Q_ASSERT(d); + + const auto verificationPeerName = d->verificationName(); + return verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName; } -ULONG QSslSocketBackendPrivate::getContextRequirements() +ULONG TlsCryptographSchannel::getContextRequirements() { - const bool isClient = mode == QSslSocket::SslClientMode; + Q_ASSERT(d); + + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; ULONG req = 0; req |= ISC_REQ_ALLOCATE_MEMORY; // Allocate memory for buffers automatically @@ -716,7 +777,7 @@ ULONG QSslSocketBackendPrivate::getContextRequirements() if (isClient) { req |= ISC_REQ_MANUAL_CRED_VALIDATION; // Manually validate certificate } else { - switch (configuration.peerVerifyMode) { + switch (d->privateConfiguration().peerVerifyMode) { case QSslSocket::PeerVerifyMode::VerifyNone: // There doesn't seem to be a way to ask for an optional client cert :-( case QSslSocket::PeerVerifyMode::AutoVerifyPeer: @@ -731,15 +792,18 @@ ULONG QSslSocketBackendPrivate::getContextRequirements() return req; } -bool QSslSocketBackendPrivate::acquireCredentialsHandle() +bool TlsCryptographSchannel::acquireCredentialsHandle() { + Q_ASSERT(d); + const auto &configuration = d->privateConfiguration(); + Q_ASSERT(schannelState == SchannelState::InitializeHandshake); - const bool isClient = mode == QSslSocket::SslClientMode; + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; const DWORD protocols = toSchannelProtocol(configuration.protocol); if (protocols == DWORD(-1)) { - setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, - QSslSocket::tr("Invalid protocol chosen")); + d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Invalid protocol chosen")); return false; } @@ -776,7 +840,7 @@ bool QSslSocketBackendPrivate::acquireCredentialsHandle() const QString message = isClient ? QSslSocket::tr("The certificate provided cannot be used for a client.") : QSslSocket::tr("The certificate provided cannot be used for a server."); - setErrorAndEmit(QAbstractSocket::SocketError::SslInvalidUserDataError, message); + d->setErrorAndEmit(QAbstractSocket::SocketError::SslInvalidUserDataError, message); return false; } Q_ASSERT(chainContext->cChain == 1); @@ -865,13 +929,13 @@ bool QSslSocketBackendPrivate::acquireCredentialsHandle() } if (status != SEC_E_OK) { - setErrorAndEmit(QAbstractSocket::SslInternalError, schannelErrorToString(status)); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, schannelErrorToString(status)); return false; } return true; } -void QSslSocketBackendPrivate::deallocateContext() +void TlsCryptographSchannel::deallocateContext() { if (SecIsValidHandle(&contextHandle)) { DeleteSecurityContext(&contextHandle); @@ -879,7 +943,7 @@ void QSslSocketBackendPrivate::deallocateContext() } } -void QSslSocketBackendPrivate::freeCredentialsHandle() +void TlsCryptographSchannel::freeCredentialsHandle() { if (SecIsValidHandle(&credentialHandle)) { FreeCredentialsHandle(&credentialHandle); @@ -887,18 +951,21 @@ void QSslSocketBackendPrivate::freeCredentialsHandle() } } -void QSslSocketBackendPrivate::closeCertificateStores() +void TlsCryptographSchannel::closeCertificateStores() { localCertificateStore.reset(); peerCertificateStore.reset(); caCertificateStore.reset(); } -bool QSslSocketBackendPrivate::createContext() +bool TlsCryptographSchannel::createContext() { + Q_ASSERT(d); + auto &configuration = d->privateConfiguration(); + Q_ASSERT(SecIsValidHandle(&credentialHandle)); Q_ASSERT(schannelState == SchannelState::InitializeHandshake); - Q_ASSERT(mode == QSslSocket::SslClientMode); + Q_ASSERT(d->tlsMode() == QSslSocket::SslClientMode); ULONG contextReq = getContextRequirements(); SecBuffer outBuffers[3]; @@ -951,8 +1018,8 @@ bool QSslSocketBackendPrivate::createContext() // This is the first call to InitializeSecurityContext, so theoretically "CONTINUE_NEEDED" // should be the only non-error return-code here. if (status != SEC_I_CONTINUE_NEEDED) { - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error creating SSL context (%1)").arg(schannelErrorToString(status))); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Error creating SSL context (%1)").arg(schannelErrorToString(status))); return false; } @@ -962,11 +1029,15 @@ bool QSslSocketBackendPrivate::createContext() return true; } -bool QSslSocketBackendPrivate::acceptContext() +bool TlsCryptographSchannel::acceptContext() { + Q_ASSERT(d); + auto &configuration = d->privateConfiguration(); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(SecIsValidHandle(&credentialHandle)); Q_ASSERT(schannelState == SchannelState::InitializeHandshake); - Q_ASSERT(mode == QSslSocket::SslServerMode); + Q_ASSERT(d->tlsMode() == QSslSocket::SslServerMode); ULONG contextReq = getContextRequirements(); if (missingData > plainSocket->bytesAvailable()) @@ -1043,8 +1114,8 @@ bool QSslSocketBackendPrivate::acceptContext() } if (status != SEC_I_CONTINUE_NEEDED) { - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Error creating SSL context (%1)").arg(schannelErrorToString(status))); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Error creating SSL context (%1)").arg(schannelErrorToString(status))); return false; } if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) @@ -1053,11 +1124,15 @@ bool QSslSocketBackendPrivate::acceptContext() return true; } -bool QSslSocketBackendPrivate::performHandshake() +bool TlsCryptographSchannel::performHandshake() { + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + if (plainSocket->state() == QAbstractSocket::UnconnectedState) { - setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); + d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); return false; } Q_ASSERT(SecIsValidHandle(&credentialHandle)); @@ -1157,8 +1232,8 @@ bool QSslSocketBackendPrivate::performHandshake() case SEC_I_INCOMPLETE_CREDENTIALS: // Schannel takes care of picking certificate to send (other than the one we can specify), // so if we get here then that means we don't have a certificate the server accepts. - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Server did not accept any certificate we could present.")); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Server did not accept any certificate we could present.")); return false; case SEC_I_CONTEXT_EXPIRED: // "The message sender has finished using the connection and has initiated a shutdown." @@ -1167,8 +1242,8 @@ bool QSslSocketBackendPrivate::performHandshake() return false; } if (!shutdown) { // we did not initiate this - setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); + d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); } return true; case SEC_E_INCOMPLETE_MESSAGE: @@ -1176,8 +1251,8 @@ bool QSslSocketBackendPrivate::performHandshake() missingData = checkIncompleteData(outBuffers[0]); return true; case SEC_E_ALGORITHM_MISMATCH: - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Algorithm mismatch")); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Algorithm mismatch")); shutdown = true; // skip sending the "Shutdown" alert return false; } @@ -1185,20 +1260,23 @@ bool QSslSocketBackendPrivate::performHandshake() // Note: We can get here if the connection is using TLS 1.2 and the server certificate uses // MD5, which is not allowed in Schannel. This causes an "invalid token" error during handshake. // (If you came here investigating an error: md5 is insecure, update your certificate) - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Handshake failed: %1").arg(schannelErrorToString(status))); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Handshake failed: %1").arg(schannelErrorToString(status))); return false; } -bool QSslSocketBackendPrivate::verifyHandshake() +bool TlsCryptographSchannel::verifyHandshake() { - Q_Q(QSslSocket); + Q_ASSERT(d); + Q_ASSERT(q); + auto &configuration = d->privateConfiguration(); + sslErrors.clear(); - const bool isClient = mode == QSslSocket::SslClientMode; + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; #define CHECK_STATUS(status) \ if (status != SEC_E_OK) { \ - setErrorAndEmit(QAbstractSocket::SslInternalError, \ + d->setErrorAndEmit(QAbstractSocket::SslInternalError, \ QSslSocket::tr("Failed to query the TLS context: %1") \ .arg(schannelErrorToString(status))); \ return false; \ @@ -1207,8 +1285,8 @@ bool QSslSocketBackendPrivate::verifyHandshake() // Everything is set up, now make sure there's nothing wrong and query some attributes... if (!matchesContextRequirements(contextAttributes, getContextRequirements(), configuration.peerVerifyMode, isClient)) { - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Did not get the required attributes for the connection.")); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Did not get the required attributes for the connection.")); return false; } @@ -1235,7 +1313,7 @@ bool QSslSocketBackendPrivate::verifyHandshake() QByteArray negotiatedProto = QByteArray((const char *)alpn.ProtocolId, alpn.ProtocolIdSize); if (!configuration.nextAllowedProtocols.contains(negotiatedProto)) { - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QSslSocket::tr("Unwanted protocol was negotiated")); return false; } @@ -1285,20 +1363,22 @@ bool QSslSocketBackendPrivate::verifyHandshake() if (certificateContext && !verifyCertContext(certificateContext)) return false; - if (!checkSslErrors() || state != QAbstractSocket::ConnectedState) { + if (!checkSslErrors() || q->state() != QAbstractSocket::ConnectedState) { #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << __func__ << "was unsuccessful. Paused:" << paused; #endif // If we're paused then checkSslErrors returned false, but it's not an error - return paused && state == QAbstractSocket::ConnectedState; + return d->isPaused() && q->state() == QAbstractSocket::ConnectedState; } schannelState = SchannelState::Done; return true; } -bool QSslSocketBackendPrivate::renegotiate() +bool TlsCryptographSchannel::renegotiate() { + Q_ASSERT(d); + SecBuffer outBuffers[3]; outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); @@ -1318,7 +1398,7 @@ bool QSslSocketBackendPrivate::renegotiate() ULONG contextReq = getContextRequirements(); TimeStamp expiry; SECURITY_STATUS status; - if (mode == QSslSocket::SslClientMode) { + if (d->tlsMode() == QSslSocket::SslClientMode) { status = InitializeSecurityContext(&credentialHandle, // phCredential &contextHandle, // phContext const_reinterpret_cast<SEC_WCHAR *>(targetName().utf16()), // pszTargetName @@ -1352,7 +1432,7 @@ bool QSslSocketBackendPrivate::renegotiate() schannelState = SchannelState::PerformHandshake; return true; } - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QSslSocket::tr("Renegotiation was unsuccessful: %1").arg(schannelErrorToString(status))); return false; } @@ -1361,8 +1441,10 @@ bool QSslSocketBackendPrivate::renegotiate() \internal reset the state in preparation for reuse of socket */ -void QSslSocketBackendPrivate::reset() +void TlsCryptographSchannel::reset() { + Q_ASSERT(d); + closeCertificateStores(); // certificate stores could've changed deallocateContext(); freeCredentialsHandle(); // in case we already had one (@future: session resumption requires re-use) @@ -1377,34 +1459,42 @@ void QSslSocketBackendPrivate::reset() intermediateBuffer.clear(); schannelState = SchannelState::InitializeHandshake; - connectionEncrypted = false; + + d->setEncrypted(false); shutdown = false; renegotiating = false; missingData = 0; } -void QSslSocketBackendPrivate::startClientEncryption() +void TlsCryptographSchannel::startClientEncryption() { - if (connectionEncrypted) + Q_ASSERT(q); + + if (q->isEncrypted()) return; // let's not mess up the connection... reset(); continueHandshake(); } -void QSslSocketBackendPrivate::startServerEncryption() +void TlsCryptographSchannel::startServerEncryption() { - if (connectionEncrypted) + Q_ASSERT(q); + + if (q->isEncrypted()) return; // let's not mess up the connection... reset(); continueHandshake(); } -void QSslSocketBackendPrivate::transmit() +void TlsCryptographSchannel::transmit() { - Q_Q(QSslSocket); + Q_ASSERT(q); + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); - if (mode == QSslSocket::UnencryptedMode) + if (d->tlsMode() == QSslSocket::UnencryptedMode) return; // This function should not have been called // Can happen if called through QSslSocket::abort->QSslSocket::close->QSslSocket::flush->here @@ -1416,7 +1506,9 @@ void QSslSocketBackendPrivate::transmit() return; } - if (connectionEncrypted) { // encrypt data in writeBuffer and write it to plainSocket + auto &writeBuffer = d->tlsWriteBuffer(); + auto &buffer = d->tlsBuffer(); + if (q->isEncrypted()) { // encrypt data in writeBuffer and write it to plainSocket qint64 totalBytesWritten = 0; qint64 writeBufferSize; while ((writeBufferSize = writeBuffer.size()) > 0) { @@ -1444,7 +1536,7 @@ void QSslSocketBackendPrivate::transmit() }; auto status = EncryptMessage(&contextHandle, 0, &message, 0); if (status != SEC_E_OK) { - setErrorAndEmit(QAbstractSocket::SslInternalError, + d->setErrorAndEmit(QAbstractSocket::SslInternalError, QSslSocket::tr("Schannel failed to encrypt data: %1") .arg(schannelErrorToString(status))); return; @@ -1461,13 +1553,14 @@ void QSslSocketBackendPrivate::transmit() if (bytesWritten >= 0) { totalBytesWritten += bytesWritten; } else { - setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); + d->setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); return; } } if (totalBytesWritten > 0) { // Don't emit bytesWritten() recursively. + bool &emittedBytesWritten = d->tlsEmittedBytesWritten(); if (!emittedBytesWritten) { emittedBytesWritten = true; emit q->bytesWritten(totalBytesWritten); @@ -1477,9 +1570,10 @@ void QSslSocketBackendPrivate::transmit() } } - if (connectionEncrypted) { // Decrypt data from remote + if (q->isEncrypted()) { // Decrypt data from remote int totalRead = 0; bool hadIncompleteData = false; + const auto readBufferMaxSize = d->maxReadBufferSize(); while (!readBufferMaxSize || buffer.size() < readBufferMaxSize) { if (missingData > plainSocket->bytesAvailable() && (!readBufferMaxSize || readBufferMaxSize >= missingData)) { @@ -1560,7 +1654,7 @@ void QSslSocketBackendPrivate::transmit() // The message has been altered, disconnect now. shutdown = true; // skips sending the shutdown alert disconnectFromHost(); - setErrorAndEmit(QAbstractSocket::SslInternalError, + d->setErrorAndEmit(QAbstractSocket::SslInternalError, schannelErrorToString(status)); break; } else if (status == SEC_E_OUT_OF_SEQUENCE) { @@ -1569,13 +1663,13 @@ void QSslSocketBackendPrivate::transmit() // while SEC_E_MESSAGE_ALTERED is for stream-oriented ones (what we use). shutdown = true; // skips sending the shutdown alert disconnectFromHost(); - setErrorAndEmit(QAbstractSocket::SslInternalError, + d->setErrorAndEmit(QAbstractSocket::SslInternalError, schannelErrorToString(status)); break; } else if (status == SEC_I_CONTEXT_EXPIRED) { // 'remote' has initiated a shutdown disconnectFromHost(); - setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, + d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, schannelErrorToString(status)); break; } else if (status == SEC_I_RENEGOTIATE) { @@ -1593,7 +1687,7 @@ void QSslSocketBackendPrivate::transmit() } if (totalRead) { - if (readyReadEmittedPointer) + if (bool *readyReadEmittedPointer = d->readyReadPointer()) *readyReadEmittedPointer = true; emit q->readyRead(); emit q->channelReadyRead(0); @@ -1601,9 +1695,11 @@ void QSslSocketBackendPrivate::transmit() } } -void QSslSocketBackendPrivate::sendShutdown() +void TlsCryptographSchannel::sendShutdown() { - const bool isClient = mode == QSslSocket::SslClientMode; + Q_ASSERT(d); + + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; DWORD shutdownToken = SCHANNEL_SHUTDOWN; SecBuffer buffer = createSecBuffer(&shutdownToken, sizeof(DWORD), SECBUFFER_TOKEN); SecBufferDesc token{ @@ -1678,18 +1774,23 @@ void QSslSocketBackendPrivate::sendShutdown() } } -void QSslSocketBackendPrivate::disconnectFromHost() +void TlsCryptographSchannel::disconnectFromHost() { + Q_ASSERT(q); + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + if (SecIsValidHandle(&contextHandle)) { if (!shutdown) { shutdown = true; if (plainSocket->state() != QAbstractSocket::UnconnectedState) { - if (connectionEncrypted) { + if (q->isEncrypted()) { // Read as much as possible because this is likely our last chance - qint64 tempMax = readBufferMaxSize; - readBufferMaxSize = 0; + qint64 tempMax = d->maxReadBufferSize(); + d->setMaxReadBufferSize(0); transmit(); - readBufferMaxSize = tempMax; + d->setMaxReadBufferSize(tempMax); sendShutdown(); } } @@ -1699,32 +1800,40 @@ void QSslSocketBackendPrivate::disconnectFromHost() plainSocket->disconnectFromHost(); } -void QSslSocketBackendPrivate::disconnected() +void TlsCryptographSchannel::disconnected() { + Q_ASSERT(d); + shutdown = true; - connectionEncrypted = false; + d->setEncrypted(false); deallocateContext(); freeCredentialsHandle(); } -QSslCipher QSslSocketBackendPrivate::sessionCipher() const +QSslCipher TlsCryptographSchannel::sessionCipher() const { - if (!connectionEncrypted) + Q_ASSERT(q); + + if (!q->isEncrypted()) return QSslCipher(); return QSslCipher(QStringLiteral("Schannel"), sessionProtocol()); } -QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const +QSsl::SslProtocol TlsCryptographSchannel::sessionProtocol() const { - if (!connectionEncrypted) + if (!q->isEncrypted()) return QSsl::SslProtocol::UnknownProtocol; return toQtSslProtocol(connectionInfo.dwProtocol); } -void QSslSocketBackendPrivate::continueHandshake() +void TlsCryptographSchannel::continueHandshake() { - Q_Q(QSslSocket); - const bool isServer = mode == QSslSocket::SslServerMode; + Q_ASSERT(q); + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const bool isServer = d->tlsMode() == QSslSocket::SslServerMode; switch (schannelState) { case SchannelState::InitializeHandshake: if (!SecIsValidHandle(&credentialHandle) && !acquireCredentialsHandle()) { @@ -1762,13 +1871,13 @@ void QSslSocketBackendPrivate::continueHandshake() Q_FALLTHROUGH(); case SchannelState::Done: // connectionEncrypted is already true if we come here from a renegotiation - if (!connectionEncrypted) { - connectionEncrypted = true; // all is done + if (!q->isEncrypted()) { + d->setEncrypted(true); // all is done emit q->encrypted(); } renegotiating = false; - if (pendingClose) { - pendingClose = false; + if (d->isPendingClose()) { + d->setPendingClose(false); disconnectFromHost(); } else { transmit(); @@ -1785,75 +1894,37 @@ void QSslSocketBackendPrivate::continueHandshake() } } -QList<QSslCipher> QSslSocketBackendPrivate::defaultCiphers() +QList<QSslError> TlsCryptographSchannel::tlsErrors() const { - QList<QSslCipher> ciphers; - // @temp (I hope), stolen from qsslsocket_winrt.cpp - const QString protocolStrings[] = { QStringLiteral("TLSv1"), QStringLiteral("TLSv1.1"), - QStringLiteral("TLSv1.2"), QStringLiteral("TLSv1.3") }; - const QSsl::SslProtocol protocols[] = { QSsl::TlsV1_0, QSsl::TlsV1_1, - QSsl::TlsV1_2, QSsl::TlsV1_3 }; - const int size = ARRAYSIZE(protocols); - static_assert(size == ARRAYSIZE(protocolStrings)); - ciphers.reserve(size); - for (int i = 0; i < size; ++i) { - QSslCipher cipher; - cipher.d->isNull = false; - cipher.d->name = QStringLiteral("Schannel"); - cipher.d->protocol = protocols[i]; - cipher.d->protocolString = protocolStrings[i]; - ciphers.append(cipher); - } - - return ciphers; -} - -QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &certificateChain, - const QString &hostName) -{ - Q_UNUSED(certificateChain); - Q_UNUSED(hostName); - - Q_UNIMPLEMENTED(); - return {}; // @future implement(?) -} - -bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, - QList<QSslCertificate> *caCertificates, - const QByteArray &passPhrase) -{ - Q_UNUSED(device); - Q_UNUSED(key); - Q_UNUSED(cert); - Q_UNUSED(caCertificates); - Q_UNUSED(passPhrase); - // @future: can load into its own certificate store (encountered problems extracting key). - Q_UNIMPLEMENTED(); - return false; + return sslErrors; } /* Copied from qsslsocket_mac.cpp, which was copied from qsslsocket_openssl.cpp */ -bool QSslSocketBackendPrivate::checkSslErrors() +bool TlsCryptographSchannel::checkSslErrors() { if (sslErrors.isEmpty()) return true; - Q_Q(QSslSocket); + + Q_ASSERT(q); + Q_ASSERT(d); + const auto &configuration = d->privateConfiguration(); + auto *plainSocket = d->plainTcpSocket(); emit q->sslErrors(sslErrors); const bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer || (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer - && mode == QSslSocket::SslClientMode); - const bool doEmitSslError = !verifyErrorsHaveBeenIgnored(); + && d->tlsMode() == QSslSocket::SslClientMode); + const bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); // check whether we need to emit an SSL handshake error if (doVerifyPeer && doEmitSslError) { if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { - pauseSocketNotifiers(q); - paused = true; + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); } else { - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString()); plainSocket->disconnectFromHost(); } @@ -1863,9 +1934,12 @@ bool QSslSocketBackendPrivate::checkSslErrors() return true; } -void QSslSocketBackendPrivate::initializeCertificateStores() +void TlsCryptographSchannel::initializeCertificateStores() { //// helper function which turns a chain into a certificate store + Q_ASSERT(d); + const auto &configuration = d->privateConfiguration(); + auto createStoreFromCertificateChain = [](const QList<QSslCertificate> certChain, const QSslKey &privateKey) { const wchar_t *passphrase = L""; // Need to embed the private key in the certificate @@ -1880,7 +1954,7 @@ void QSslSocketBackendPrivate::initializeCertificateStores() if (!configuration.localCertificateChain.isEmpty()) { if (configuration.privateKey.isNull()) { - setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, + d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, QSslSocket::tr("Cannot provide a certificate with no key")); return; } @@ -1898,12 +1972,14 @@ void QSslSocketBackendPrivate::initializeCertificateStores() } } -bool QSslSocketBackendPrivate::verifyCertContext(CERT_CONTEXT *certContext) +bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) { Q_ASSERT(certContext); - Q_Q(QSslSocket); + Q_ASSERT(q); + Q_ASSERT(d); + auto &configuration = d->privateConfiguration(); - const bool isClient = mode == QSslSocket::SslClientMode; + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; // Create a collection of stores so we can pass in multiple stores as additional locations to // search for the certificate chain @@ -2187,14 +2263,15 @@ bool QSslSocketBackendPrivate::verifyCertContext(CERT_CONTEXT *certContext) // @Note: Somewhat copied from qsslsocket_mac.cpp const bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer || (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer - && mode == QSslSocket::SslClientMode); + && d->tlsMode() == 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. if (!configuration.peerCertificate.isNull()) { // but only if we're a client connecting to a server // if we're the server, don't check CN - if (mode == QSslSocket::SslClientMode) { + if (d->tlsMode() == QSslSocket::SslClientMode) { + const auto verificationPeerName = d->verificationName(); const QString peerName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); if (!isMatchingHostname(configuration.peerCertificate, peerName)) { // No matches in common names or alternate names. @@ -2218,17 +2295,20 @@ bool QSslSocketBackendPrivate::verifyCertContext(CERT_CONTEXT *certContext) return true; } -bool QSslSocketBackendPrivate::rootCertOnDemandLoadingAllowed() +bool TlsCryptographSchannel::rootCertOnDemandLoadingAllowed() { - return allowRootCertOnDemandLoading && s_loadRootCertsOnDemand; + Q_ASSERT(d); + return d->isRootsOnDemandAllowed() && QSslSocketPrivate::rootCertOnDemandLoadingSupported(); } +} // namespace QTlsPrivate + void QSslSocketPrivate::registerAdHocFactory() { // TLSTODO: this is a temporary solution, waiting for // backends to move to ... plugins. - if (!backend()) - qCWarning(lcSsl, "Failed to create backend factory"); + if (!backendSchannel()) + qCWarning(lcTlsBackend, "Failed to create backend factory"); } QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_schannel_p.h b/src/network/ssl/qtls_schannel_p.h index 30f5a9695b..e4ac15206c 100644 --- a/src/network/ssl/qsslsocket_schannel_p.h +++ b/src/network/ssl/qtls_schannel_p.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QSSLSOCKET_SCHANNEL_P_H -#define QSSLSOCKET_SCHANNEL_P_H +#ifndef QTLS_SCHANNEL_P_H +#define QTLS_SCHANNEL_P_H // // W A R N I N G @@ -51,12 +51,17 @@ // We mean it. // +#include <QtNetwork/private/qtnetworkglobal_p.h> + QT_REQUIRE_CONFIG(schannel); -#include <QtNetwork/private/qtnetworkglobal_p.h> +#include <QtCore/qt_windows.h> +#include "qtlsbackend_schannel_p.h" #include "qsslsocket_p.h" +#include "qwincrypt_p.h" + #define SECURITY_WIN32 #define SCHANNEL_USE_BLACKLISTS 1 #include <Winternl.h> // needed for UNICODE defines @@ -67,15 +72,17 @@ QT_REQUIRE_CONFIG(schannel); QT_BEGIN_NAMESPACE -class QSslSocketBackendPrivate final : public QSslSocketPrivate +namespace QTlsPrivate { + +class TlsCryptographSchannel final : public TlsCryptograph { - Q_DISABLE_COPY_MOVE(QSslSocketBackendPrivate) - Q_DECLARE_PUBLIC(QSslSocket) + Q_DISABLE_COPY_MOVE(TlsCryptographSchannel) public: - QSslSocketBackendPrivate(); - ~QSslSocketBackendPrivate(); + TlsCryptographSchannel(); + ~TlsCryptographSchannel(); + + void init(QSslSocket *q, QSslSocketPrivate *d) override; - // Platform specific functions void startClientEncryption() override; void startServerEncryption() override; void transmit() override; @@ -84,12 +91,7 @@ public: QSslCipher sessionCipher() const override; QSsl::SslProtocol sessionProtocol() const override; void continueHandshake() override; - - static QList<QSslCipher> defaultCiphers(); - static QList<QSslError> verify(const QList<QSslCertificate> &certificateChain, - const QString &hostName); - static bool importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, - QList<QSslCertificate> *caCertificates, const QByteArray &passPhrase); + QList<QSslError> tlsErrors() const override; private: enum class SchannelState { @@ -123,7 +125,10 @@ private: bool rootCertOnDemandLoadingAllowed(); - bool hasUndecryptedData() override { return intermediateBuffer.size() > 0; } + bool hasUndecryptedData() const override { return intermediateBuffer.size() > 0; } + + QSslSocket *q = nullptr; + QSslSocketPrivate *d = nullptr; SecPkgContext_ConnectionInfo connectionInfo = {}; SecPkgContext_StreamSizes streamSizes = {}; @@ -143,8 +148,12 @@ private: qint64 missingData = 0; bool renegotiating = false; + bool shutdown = false; + QList<QSslError> sslErrors; }; +} // namespace QTlsPrivate + QT_END_NAMESPACE -#endif // QSSLSOCKET_SCHANNEL_P_H +#endif // QTLS_SCHANNEL_P_H diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qtls_st.cpp index 9bcec5f7b3..602f1ef409 100644 --- a/src/network/ssl/qsslsocket_mac.cpp +++ b/src/network/ssl/qtls_st.cpp @@ -41,7 +41,7 @@ #include "qsslsocket.h" #include "qssl_p.h" -#include "qsslsocket_mac_p.h" +#include "qtls_st_p.h" #include "qasn1element_p.h" #include "qsslcertificate_p.h" #include "qtlsbackend_st_p.h" @@ -75,7 +75,16 @@ QT_BEGIN_NAMESPACE -Q_GLOBAL_STATIC(QSecureTransportBackend, backend) +Q_GLOBAL_STATIC(QSecureTransportBackend, backendSecureTransport) + +// Defined in qsslsocket_qt.cpp. +QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key, + const QString &passPhrase); + +namespace QTlsPrivate { + +// Defined in qtlsbackend_st.cpp +QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher); namespace { @@ -101,7 +110,7 @@ EphemeralSecKeychain::EphemeralSecKeychain() { const auto uuid = QUuid::createUuid(); if (uuid.isNull()) { - qCWarning(lcSsl) << "Failed to create a unique keychain name"; + qCWarning(lcTlsBackend) << "Failed to create a unique keychain name"; return; } @@ -128,14 +137,14 @@ EphemeralSecKeychain::EphemeralSecKeychain() const auto ok = CFStringGetFileSystemRepresentation(cfName, &posixPath[0], CFIndex(posixPath.size())); if (!ok) { - qCWarning(lcSsl) << "Failed to create a unique keychain name from" - << "QDir::tempPath()"; + qCWarning(lcTlsBackend) << "Failed to create a unique keychain name from" + << "QDir::tempPath()"; return; } std::vector<uint8_t> passUtf8(256); if (SecRandomCopyBytes(kSecRandomDefault, passUtf8.size(), &passUtf8[0])) { - qCWarning(lcSsl) << "SecRandomCopyBytes: failed to create a key"; + qCWarning(lcTlsBackend) << "SecRandomCopyBytes: failed to create a key"; return; } @@ -143,7 +152,7 @@ EphemeralSecKeychain::EphemeralSecKeychain() &passUtf8[0], FALSE, nullptr, &keychain); if (status != errSecSuccess || !keychain) { - qCWarning(lcSsl) << "SecKeychainCreate: failed to create a custom keychain"; + qCWarning(lcTlsBackend) << "SecKeychainCreate: failed to create a custom keychain"; if (keychain) { SecKeychainDelete(keychain); CFRelease(keychain); @@ -158,13 +167,13 @@ EphemeralSecKeychain::EphemeralSecKeychain() // == false, set interval to INT_MAX to never lock ... settings.lockInterval = INT_MAX; if (SecKeychainSetSettings(keychain, &settings) != errSecSuccess) - qCWarning(lcSsl) << "SecKeychainSettings: failed to disable lock on sleep"; + qCWarning(lcTlsBackend) << "SecKeychainSettings: failed to disable lock on sleep"; } #ifdef QSSLSOCKET_DEBUG if (keychain) { - qCDebug(lcSsl) << "Custom keychain with name" << keychainName << "was created" - << "successfully"; + qCDebug(lcTlsBackend) << "Custom keychain with name" << keychainName << "was created" + << "successfully"; } #endif } @@ -180,9 +189,16 @@ EphemeralSecKeychain::~EphemeralSecKeychain() #endif // Q_OS_MACOS +void qt_releaseSecureTransportContext(SSLContextRef context) +{ + if (context) + CFRelease(context); +} + } // unnamed namespace -static SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) +// To be also used by qtlsbackend_st.cpp (thus not in unnamed namespace). +SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) { const bool isServer = mode == QSslSocket::SslServerMode; const SSLProtocolSide side = isServer ? kSSLServerSide : kSSLClientSide; @@ -193,12 +209,6 @@ static SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) return context; } -static void qt_releaseSecureTransportContext(SSLContextRef context) -{ - if (context) - CFRelease(context); -} - QSecureTransportContext::QSecureTransportContext(SSLContextRef c) : context(c) { @@ -220,15 +230,6 @@ void QSecureTransportContext::reset(SSLContextRef newContext) context = newContext; } -Q_GLOBAL_STATIC(QRecursiveMutex, qt_securetransport_mutex) - -//#define QSSLSOCKET_DEBUG - -bool QSslSocketPrivate::s_libraryLoaded = false; -bool QSslSocketPrivate::s_loadedCiphersAndCerts = false; -bool QSslSocketPrivate::s_loadRootCertsOnDemand = false; - - #if !defined(QT_PLATFORM_UIKIT) // dhparam is only used on macOS. (see the SSLSetDiffieHellmanParams call below) static const uint8_t dhparam[] = "\x30\x82\x01\x08\x02\x82\x01\x01\x00\x97\xea\xd0\x46\xf7\xae\xa7\x76\x80" @@ -248,14 +249,15 @@ static const uint8_t dhparam[] = "\x90\x0b\x35\x64\xff\xd9\xe3\xac\xf2\xf2\xeb\x3a\x63\x02\x01\x02"; #endif -OSStatus QSslSocketBackendPrivate::ReadCallback(QSslSocketBackendPrivate *socket, - char *data, size_t *dataLength) +OSStatus TlsCryptographSecureTransport::ReadCallback(TlsCryptographSecureTransport *socket, + char *data, size_t *dataLength) { Q_ASSERT(socket); Q_ASSERT(data); Q_ASSERT(dataLength); - QTcpSocket *plainSocket = socket->plainSocket; + Q_ASSERT(socket->d); + QTcpSocket *plainSocket = socket->d->plainTcpSocket(); Q_ASSERT(plainSocket); if (socket->isHandshakeComplete()) { @@ -279,7 +281,7 @@ OSStatus QSslSocketBackendPrivate::ReadCallback(QSslSocketBackendPrivate *socket const qint64 bytes = plainSocket->read(data, *dataLength); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "read" << bytes; + qCDebug(lcTlsBackend) << plainSocket << "read" << bytes; #endif if (bytes < 0) { *dataLength = 0; @@ -292,19 +294,20 @@ OSStatus QSslSocketBackendPrivate::ReadCallback(QSslSocketBackendPrivate *socket return err; } -OSStatus QSslSocketBackendPrivate::WriteCallback(QSslSocketBackendPrivate *socket, - const char *data, size_t *dataLength) +OSStatus TlsCryptographSecureTransport::WriteCallback(TlsCryptographSecureTransport *socket, + const char *data, size_t *dataLength) { Q_ASSERT(socket); Q_ASSERT(data); Q_ASSERT(dataLength); - QTcpSocket *plainSocket = socket->plainSocket; + Q_ASSERT(socket->d); + QTcpSocket *plainSocket = socket->d->plainTcpSocket(); Q_ASSERT(plainSocket); const qint64 bytes = plainSocket->write(data, *dataLength); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "write" << bytes; + qCDebug(lcTlsBackend) << plainSocket << "write" << bytes; #endif if (bytes < 0) { *dataLength = 0; @@ -317,109 +320,42 @@ OSStatus QSslSocketBackendPrivate::WriteCallback(QSslSocketBackendPrivate *socke return err; } -void QSslSocketPrivate::ensureInitialized() -{ - const QMutexLocker locker(qt_securetransport_mutex()); - if (s_loadedCiphersAndCerts) - return; - - // We have to set it before setDefaultSupportedCiphers, - // since this function can trigger static (global)'s initialization - // and as a result - recursive ensureInitialized call - // from QSslCertificatePrivate's ctor. - s_loadedCiphersAndCerts = true; - - const QSecureTransportContext context(qt_createSecureTransportContext(QSslSocket::SslClientMode)); - if (context) { - QList<QSslCipher> ciphers; - QList<QSslCipher> defaultCiphers; - - size_t numCiphers = 0; - // Fails only if any of parameters is null. - SSLGetNumberSupportedCiphers(context, &numCiphers); - QList<SSLCipherSuite> cfCiphers(numCiphers); - // Fails only if any of parameter is null or number of ciphers is wrong. - SSLGetSupportedCiphers(context, cfCiphers.data(), &numCiphers); - - for (size_t i = 0; i < size_t(cfCiphers.size()); ++i) { - const QSslCipher ciph(QSslSocketBackendPrivate::QSslCipher_from_SSLCipherSuite(cfCiphers.at(i))); - if (!ciph.isNull()) { - ciphers << ciph; - if (ciph.usedBits() >= 128) - defaultCiphers << ciph; - } - } - - setDefaultSupportedCiphers(ciphers); - setDefaultCiphers(defaultCiphers); - - if (!s_loadRootCertsOnDemand) - setDefaultCaCertificates(systemCaCertificates()); - } else { - s_loadedCiphersAndCerts = false; - } - -} - -long QSslSocketPrivate::sslLibraryVersionNumber() -{ - return 0; -} - -QString QSslSocketPrivate::sslLibraryVersionString() -{ - return QLatin1String("Secure Transport, ") + QSysInfo::prettyProductName(); -} - -long QSslSocketPrivate::sslLibraryBuildVersionNumber() -{ - return 0; -} - -QString QSslSocketPrivate::sslLibraryBuildVersionString() -{ - return sslLibraryVersionString(); -} - -bool QSslSocketPrivate::supportsSsl() -{ - return true; -} - -void QSslSocketPrivate::resetDefaultCiphers() +TlsCryptographSecureTransport::TlsCryptographSecureTransport() + : context(nullptr) { - Q_UNIMPLEMENTED(); } -void QSslSocketPrivate::resetDefaultEllipticCurves() +TlsCryptographSecureTransport::~TlsCryptographSecureTransport() { - // No public API for this (?). - Q_UNIMPLEMENTED(); + destroySslContext(); } -QSslSocketBackendPrivate::QSslSocketBackendPrivate() - : context(nullptr) +void TlsCryptographSecureTransport::init(QSslSocket *qObj, QSslSocketPrivate *dObj) { -} + Q_ASSERT(qObj); + Q_ASSERT(dObj); + q = qObj; + d = dObj; -QSslSocketBackendPrivate::~QSslSocketBackendPrivate() -{ - destroySslContext(); + renegotiating = false; + shutdown = false; } -void QSslSocketBackendPrivate::continueHandshake() +void TlsCryptographSecureTransport::continueHandshake() { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "connection encrypted"; + Q_ASSERT(q); + Q_ASSERT(d); + d->setEncrypted(true); +#ifdef QSSLSOCKET_DEBU + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "connection encrypted"; #endif - Q_Q(QSslSocket); - connectionEncrypted = true; #if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13_4, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) // Unlike OpenSSL, Secure Transport does not allow to negotiate protocols via // a callback during handshake. We can only set our list of preferred protocols // (and send it during handshake) and then receive what our peer has sent to us. // And here we can finally try to find a match (if any). + auto &configuration = d->privateConfiguration(); if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { const auto &requestedProtocols = configuration.nextAllowedProtocols; if (const int requestedCount = requestedProtocols.size()) { @@ -454,32 +390,34 @@ void QSslSocketBackendPrivate::continueHandshake() if (!renegotiating) emit q->encrypted(); - if (autoStartHandshake && pendingClose) { - pendingClose = false; + if (d->isAutoStartingHandshake() && d->isPendingClose()) { + d->setPendingClose(false); q->disconnectFromHost(); } } -void QSslSocketBackendPrivate::disconnected() +void TlsCryptographSecureTransport::disconnected() { - if (plainSocket->bytesAvailable() <= 0) + Q_ASSERT(d && d->plainTcpSocket()); + if (d->plainTcpSocket()->bytesAvailable() <= 0) destroySslContext(); // 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. } -void QSslSocketBackendPrivate::disconnectFromHost() +void TlsCryptographSecureTransport::disconnectFromHost() { + Q_ASSERT(d && d->plainTcpSocket()); if (context) { if (!shutdown) { SSLClose(context); shutdown = true; } } - plainSocket->disconnectFromHost(); + d->plainTcpSocket()->disconnectFromHost(); } -QSslCipher QSslSocketBackendPrivate::sessionCipher() const +QSslCipher TlsCryptographSecureTransport::sessionCipher() const { SSLCipherSuite cipher = 0; if (context && SSLGetNegotiatedCipher(context, &cipher) == errSecSuccess) @@ -488,7 +426,7 @@ QSslCipher QSslSocketBackendPrivate::sessionCipher() const return QSslCipher(); } -QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const +QSsl::SslProtocol TlsCryptographSecureTransport::sessionProtocol() const { if (!context) return QSsl::UnknownProtocol; @@ -496,7 +434,7 @@ QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const SSLProtocol protocol = kSSLProtocolUnknown; const OSStatus err = SSLGetNegotiatedProtocolVersion(context, &protocol); if (err != errSecSuccess) { - qCWarning(lcSsl) << "SSLGetNegotiatedProtocolVersion failed:" << err; + qCWarning(lcTlsBackend) << "SSLGetNegotiatedProtocolVersion failed:" << err; return QSsl::UnknownProtocol; } @@ -514,35 +452,37 @@ QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const } } -void QSslSocketBackendPrivate::startClientEncryption() +void TlsCryptographSecureTransport::startClientEncryption() { if (!initSslContext()) { + Q_ASSERT(d); // Error description/code were set, 'error' emitted // by initSslContext, but OpenSSL socket also sets error // emits a signal twice, so ... - setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); return; } startHandshake(); } -void QSslSocketBackendPrivate::startServerEncryption() +void TlsCryptographSecureTransport::startServerEncryption() { if (!initSslContext()) { // Error description/code were set, 'error' emitted // by initSslContext, but OpenSSL socket also sets error // emits a signal twice, so ... - setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); return; } startHandshake(); } -void QSslSocketBackendPrivate::transmit() +void TlsCryptographSecureTransport::transmit() { - Q_Q(QSslSocket); + Q_ASSERT(q); + Q_ASSERT(d); // If we don't have any SSL context, don't bother transmitting. // Edit: if SSL session closed, don't bother either. @@ -552,6 +492,7 @@ void QSslSocketBackendPrivate::transmit() if (!isHandshakeComplete()) startHandshake(); + auto &writeBuffer = d->tlsWriteBuffer(); if (isHandshakeComplete() && !writeBuffer.isEmpty()) { qint64 totalBytesWritten = 0; while (writeBuffer.nextDataBlockSize() > 0 && context) { @@ -559,11 +500,11 @@ void QSslSocketBackendPrivate::transmit() size_t writtenBytes = 0; const OSStatus err = SSLWrite(context, writeBuffer.readPointer(), nextDataBlockSize, &writtenBytes); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "SSLWrite returned" << err; + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "SSLWrite returned" << err; #endif if (err != errSecSuccess && err != errSSLWouldBlock) { - setErrorAndEmit(QAbstractSocket::SslInternalError, - QStringLiteral("SSLWrite failed: %1").arg(err)); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QStringLiteral("SSLWrite failed: %1").arg(err)); break; } @@ -578,6 +519,7 @@ void QSslSocketBackendPrivate::transmit() if (totalBytesWritten > 0) { // Don't emit bytesWritten() recursively. + auto &emittedBytesWritten = d->tlsEmittedBytesWritten(); if (!emittedBytesWritten) { emittedBytesWritten = true; emit q->bytesWritten(totalBytesWritten); @@ -587,6 +529,8 @@ void QSslSocketBackendPrivate::transmit() } } + auto &buffer = d->tlsBuffer(); + const auto readBufferMaxSize = d->maxReadBufferSize(); if (isHandshakeComplete()) { QVarLengthArray<char, 4096> data; while (context && (!readBufferMaxSize || buffer.size() < readBufferMaxSize)) { @@ -594,16 +538,16 @@ void QSslSocketBackendPrivate::transmit() data.resize(4096); const OSStatus err = SSLRead(context, data.data(), data.size(), &readBytes); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "SSLRead returned" << err; + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "SSLRead returned" << err; #endif if (err == errSSLClosedGraceful) { shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves - setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); + d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); break; } else if (err != errSecSuccess && err != errSSLWouldBlock) { - setErrorAndEmit(QAbstractSocket::SslInternalError, - QStringLiteral("SSLRead failed: %1").arg(err)); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QStringLiteral("SSLRead failed: %1").arg(err)); break; } @@ -614,7 +558,7 @@ void QSslSocketBackendPrivate::transmit() if (readBytes) { buffer.append(data.constData(), readBytes); - if (readyReadEmittedPointer) + if (bool *readyReadEmittedPointer = d->readyReadPointer()) *readyReadEmittedPointer = true; emit q->readyRead(); emit q->channelReadyRead(0); @@ -626,357 +570,129 @@ void QSslSocketBackendPrivate::transmit() } } - -QList<QSslError> (QSslSocketBackendPrivate::verify)(QList<QSslCertificate> certificateChain, const QString &hostName) +SSLCipherSuite TlsCryptographSecureTransport::SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph) { - Q_UNIMPLEMENTED(); - Q_UNUSED(certificateChain); - Q_UNUSED(hostName); - - QList<QSslError> errors; - errors << QSslError(QSslError::UnspecifiedError); - - return errors; -} - -bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device, - QSslKey *key, QSslCertificate *cert, - QList<QSslCertificate> *caCertificates, - const QByteArray &passPhrase) -{ - Q_UNIMPLEMENTED(); - Q_UNUSED(device); - Q_UNUSED(key); - Q_UNUSED(cert); - Q_UNUSED(caCertificates); - Q_UNUSED(passPhrase); - return false; -} - -QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher) -{ - QSslCipher ciph; - switch (cipher) { - // Sorted as in CipherSuite.h (and groupped by their RFC) - // TLS addenda using AES, per RFC 3268 - case TLS_RSA_WITH_AES_128_CBC_SHA: - ciph.d->name = QLatin1String("AES128-SHA"); - break; - case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: - ciph.d->name = QLatin1String("DHE-RSA-AES128-SHA"); - break; - case TLS_RSA_WITH_AES_256_CBC_SHA: - ciph.d->name = QLatin1String("AES256-SHA"); - break; - case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: - ciph.d->name = QLatin1String("DHE-RSA-AES256-SHA"); - break; - - // ECDSA addenda, RFC 4492 - case TLS_ECDH_ECDSA_WITH_NULL_SHA: - ciph.d->name = QLatin1String("ECDH-ECDSA-NULL-SHA"); - break; - case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: - ciph.d->name = QLatin1String("ECDH-ECDSA-RC4-SHA"); - break; - case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: - ciph.d->name = QLatin1String("ECDH-ECDSA-DES-CBC3-SHA"); - break; - case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: - ciph.d->name = QLatin1String("ECDH-ECDSA-AES128-SHA"); - break; - case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: - ciph.d->name = QLatin1String("ECDH-ECDSA-AES256-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_NULL_SHA: - ciph.d->name = QLatin1String("ECDHE-ECDSA-NULL-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: - ciph.d->name = QLatin1String("ECDHE-ECDSA-RC4-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: - ciph.d->name = QLatin1String("ECDHE-ECDSA-DES-CBC3-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: - ciph.d->name = QLatin1String("ECDHE-ECDSA-AES128-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: - ciph.d->name = QLatin1String("ECDHE-ECDSA-AES256-SHA"); - break; - case TLS_ECDH_RSA_WITH_NULL_SHA: - ciph.d->name = QLatin1String("ECDH-RSA-NULL-SHA"); - break; - case TLS_ECDH_RSA_WITH_RC4_128_SHA: - ciph.d->name = QLatin1String("ECDH-RSA-RC4-SHA"); - break; - case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: - ciph.d->name = QLatin1String("ECDH-RSA-DES-CBC3-SHA"); - break; - case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: - ciph.d->name = QLatin1String("ECDH-RSA-AES128-SHA"); - break; - case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: - ciph.d->name = QLatin1String("ECDH-RSA-AES256-SHA"); - break; - case TLS_ECDHE_RSA_WITH_NULL_SHA: - ciph.d->name = QLatin1String("ECDHE-RSA-NULL-SHA"); - break; - case TLS_ECDHE_RSA_WITH_RC4_128_SHA: - ciph.d->name = QLatin1String("ECDHE-RSA-RC4-SHA"); - break; - case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: - ciph.d->name = QLatin1String("ECDHE-RSA-DES-CBC3-SHA"); - break; - case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: - ciph.d->name = QLatin1String("ECDHE-RSA-AES128-SHA"); - break; - case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: - ciph.d->name = QLatin1String("ECDHE-RSA-AES256-SHA"); - break; - - // TLS 1.2 addenda, RFC 5246 - case TLS_RSA_WITH_3DES_EDE_CBC_SHA: - ciph.d->name = QLatin1String("DES-CBC3-SHA"); - break; - case TLS_RSA_WITH_AES_128_CBC_SHA256: - ciph.d->name = QLatin1String("AES128-SHA256"); - break; - case TLS_RSA_WITH_AES_256_CBC_SHA256: - ciph.d->name = QLatin1String("AES256-SHA256"); - break; - case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: - ciph.d->name = QLatin1String("DHE-RSA-DES-CBC3-SHA"); - break; - case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: - ciph.d->name = QLatin1String("DHE-RSA-AES128-SHA256"); - break; - case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: - ciph.d->name = QLatin1String("DHE-RSA-AES256-SHA256"); - break; - - // Addendum from RFC 4279, TLS PSK - // all missing atm. - - // RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption - // all missing atm. - - // Addenda from rfc 5288 AES Galois Counter Mode (CGM) Cipher Suites for TLS - case TLS_RSA_WITH_AES_256_GCM_SHA384: - ciph.d->name = QLatin1String("AES256-GCM-SHA384"); - break; - - // RFC 5487 - PSK with SHA-256/384 and AES GCM - // all missing atm. - - // Addenda from rfc 5289 Elliptic Curve Cipher Suites with HMAC SHA-256/384 - case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: - ciph.d->name = QLatin1String("ECDHE-ECDSA-AES128-SHA256"); - break; - case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: - ciph.d->name = QLatin1String("ECDHE-ECDSA-AES256-SHA384"); - break; - case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: - ciph.d->name = QLatin1String("ECDH-ECDSA-AES128-SHA256"); - break; - case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: - ciph.d->name = QLatin1String("ECDH-ECDSA-AES256-SHA384"); - break; - case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: - ciph.d->name = QLatin1String("ECDHE-RSA-AES128-SHA256"); - break; - case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: - ciph.d->name = QLatin1String("ECDHE-RSA-AES256-SHA384"); - break; - case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: - ciph.d->name = QLatin1String("ECDH-RSA-AES128-SHA256"); - break; - case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: - ciph.d->name = QLatin1String("ECDH-RSA-AES256-SHA384"); - break; - - // Addenda from rfc 5289 Elliptic Curve Cipher Suites - // with SHA-256/384 and AES Galois Counter Mode (GCM) - case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: - ciph.d->name = QLatin1String("ECDHE-RSA-AES256-GCM-SHA384"); - break; - - default: - return ciph; - } - ciph.d->isNull = false; - - // protocol - ciph.d->protocol = QSsl::TlsV1_2; - ciph.d->protocolString = QLatin1String("TLSv1.2"); - - const auto bits = QStringView{ciph.d->name}.split(QLatin1Char('-')); - if (bits.size() >= 2) { - if (bits.size() == 2 || bits.size() == 3) { - ciph.d->keyExchangeMethod = QLatin1String("RSA"); - } else if (bits.front() == QLatin1String("DH") || bits.front() == QLatin1String("DHE")) { - ciph.d->keyExchangeMethod = QLatin1String("DH"); - } else if (bits.front() == QLatin1String("ECDH") || bits.front() == QLatin1String("ECDHE")) { - ciph.d->keyExchangeMethod = QLatin1String("ECDH"); - } else { - qCWarning(lcSsl) << "Unknown Kx" << ciph.d->name; - } - - if (bits.size() == 2 || bits.size() == 3) { - ciph.d->authenticationMethod = QLatin1String("RSA"); - } else if (ciph.d->name.contains(QLatin1String("-ECDSA-"))) { - ciph.d->authenticationMethod = QLatin1String("ECDSA"); - } else if (ciph.d->name.contains(QLatin1String("-RSA-"))) { - ciph.d->authenticationMethod = QLatin1String("RSA"); - } else { - qCWarning(lcSsl) << "Unknown Au" << ciph.d->name; - } - - if (ciph.d->name.contains(QLatin1String("RC4-"))) { - ciph.d->encryptionMethod = QLatin1String("RC4(128)"); - ciph.d->bits = 128; - ciph.d->supportedBits = 128; - } else if (ciph.d->name.contains(QLatin1String("DES-CBC3-"))) { - ciph.d->encryptionMethod = QLatin1String("3DES(168)"); - ciph.d->bits = 168; - ciph.d->supportedBits = 168; - } else if (ciph.d->name.contains(QLatin1String("AES128-"))) { - ciph.d->encryptionMethod = QLatin1String("AES(128)"); - ciph.d->bits = 128; - ciph.d->supportedBits = 128; - } else if (ciph.d->name.contains(QLatin1String("AES256-GCM"))) { - ciph.d->encryptionMethod = QLatin1String("AESGCM(256)"); - ciph.d->bits = 256; - ciph.d->supportedBits = 256; - } else if (ciph.d->name.contains(QLatin1String("AES256-"))) { - ciph.d->encryptionMethod = QLatin1String("AES(256)"); - ciph.d->bits = 256; - ciph.d->supportedBits = 256; - } else if (ciph.d->name.contains(QLatin1String("NULL-"))) { - ciph.d->encryptionMethod = QLatin1String("NULL"); - } else { - qCWarning(lcSsl) << "Unknown Enc" << ciph.d->name; - } - } - return ciph; -} -SSLCipherSuite QSslSocketBackendPrivate::SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph) -{ - if (ciph.d->name == QLatin1String("AES128-SHA")) + if (ciph.name() == QLatin1String("AES128-SHA")) return TLS_RSA_WITH_AES_128_CBC_SHA; - if (ciph.d->name == QLatin1String("DHE-RSA-AES128-SHA")) + if (ciph.name() == QLatin1String("DHE-RSA-AES128-SHA")) return TLS_DHE_RSA_WITH_AES_128_CBC_SHA; - if (ciph.d->name == QLatin1String("AES256-SHA")) + if (ciph.name() == QLatin1String("AES256-SHA")) return TLS_RSA_WITH_AES_256_CBC_SHA; - if (ciph.d->name == QLatin1String("DHE-RSA-AES256-SHA")) + if (ciph.name() == QLatin1String("DHE-RSA-AES256-SHA")) return TLS_DHE_RSA_WITH_AES_256_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-NULL-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-NULL-SHA")) return TLS_ECDH_ECDSA_WITH_NULL_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-RC4-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-RC4-SHA")) return TLS_ECDH_ECDSA_WITH_RC4_128_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) return TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-AES128-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA")) return TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-AES256-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA")) return TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-RC4-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-RC4-SHA")) return TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) return TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-AES128-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA")) return TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-AES256-SHA")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA")) return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-NULL-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-NULL-SHA")) return TLS_ECDH_RSA_WITH_NULL_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-RC4-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-RC4-SHA")) return TLS_ECDH_RSA_WITH_RC4_128_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) return TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-AES128-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-AES128-SHA")) return TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-AES256-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-AES256-SHA")) return TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-RC4-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-RC4-SHA")) return TLS_ECDHE_RSA_WITH_RC4_128_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) return TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-AES128-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-AES128-SHA")) return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; - if (ciph.d->name == QLatin1String("ECDH-RSA-AES256-SHA")) + if (ciph.name() == QLatin1String("ECDH-RSA-AES256-SHA")) return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; - if (ciph.d->name == QLatin1String("DES-CBC3-SHA")) + if (ciph.name() == QLatin1String("DES-CBC3-SHA")) return TLS_RSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.d->name == QLatin1String("AES128-SHA256")) + if (ciph.name() == QLatin1String("AES128-SHA256")) return TLS_RSA_WITH_AES_128_CBC_SHA256; - if (ciph.d->name == QLatin1String("AES256-SHA256")) + if (ciph.name() == QLatin1String("AES256-SHA256")) return TLS_RSA_WITH_AES_256_CBC_SHA256; - if (ciph.d->name == QLatin1String("DHE-RSA-DES-CBC3-SHA")) + if (ciph.name() == QLatin1String("DHE-RSA-DES-CBC3-SHA")) return TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.d->name == QLatin1String("DHE-RSA-AES128-SHA256")) + if (ciph.name() == QLatin1String("DHE-RSA-AES128-SHA256")) return TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; - if (ciph.d->name == QLatin1String("DHE-RSA-AES256-SHA256")) + if (ciph.name() == QLatin1String("DHE-RSA-AES256-SHA256")) return TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; - if (ciph.d->name == QLatin1String("AES256-GCM-SHA384")) + if (ciph.name() == QLatin1String("AES256-GCM-SHA384")) return TLS_RSA_WITH_AES_256_GCM_SHA384; - if (ciph.d->name == QLatin1String("ECDHE-ECDSA-AES128-SHA256")) + if (ciph.name() == QLatin1String("ECDHE-ECDSA-AES128-SHA256")) return TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; - if (ciph.d->name == QLatin1String("ECDHE-ECDSA-AES256-SHA384")) + if (ciph.name() == QLatin1String("ECDHE-ECDSA-AES256-SHA384")) return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-AES128-SHA256")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA256")) return TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; - if (ciph.d->name == QLatin1String("ECDH-ECDSA-AES256-SHA384")) + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA384")) return TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; - if (ciph.d->name == QLatin1String("ECDHE-RSA-AES128-SHA256")) + if (ciph.name() == QLatin1String("ECDHE-RSA-AES128-SHA256")) return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; - if (ciph.d->name == QLatin1String("ECDHE-RSA-AES256-SHA384")) + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-SHA384")) return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; - if (ciph.d->name == QLatin1String("ECDHE-RSA-AES256-SHA384")) + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-SHA384")) return TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; - if (ciph.d->name == QLatin1String("ECDHE-RSA-AES256-GCM-SHA384")) + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-GCM-SHA384")) return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; return 0; } -bool QSslSocketBackendPrivate::initSslContext() +bool TlsCryptographSecureTransport::initSslContext() { - Q_Q(QSslSocket); + Q_ASSERT(q); + Q_ASSERT(d); Q_ASSERT_X(!context, Q_FUNC_INFO, "invalid socket state, context is not null"); + auto *plainSocket = d->plainTcpSocket(); Q_ASSERT(plainSocket); + const auto mode = d->tlsMode(); + context.reset(qt_createSecureTransportContext(mode)); if (!context) { - setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLCreateContext failed")); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLCreateContext failed")); return false; } const OSStatus err = SSLSetIOFuncs(context, - reinterpret_cast<SSLReadFunc>(&QSslSocketBackendPrivate::ReadCallback), - reinterpret_cast<SSLWriteFunc>(&QSslSocketBackendPrivate::WriteCallback)); + reinterpret_cast<SSLReadFunc>(&TlsCryptographSecureTransport::ReadCallback), + reinterpret_cast<SSLWriteFunc>(&TlsCryptographSecureTransport::WriteCallback)); if (err != errSecSuccess) { destroySslContext(); - setErrorAndEmit(QAbstractSocket::SslInternalError, - QStringLiteral("SSLSetIOFuncs failed: %1").arg(err)); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QStringLiteral("SSLSetIOFuncs failed: %1").arg(err)); return false; } SSLSetConnection(context, this); + auto &configuration = d->privateConfiguration(); if (mode == QSslSocket::SslServerMode && !configuration.localCertificateChain.isEmpty()) { QString errorDescription; QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; if (!setSessionCertificate(errorDescription, errorCode)) { destroySslContext(); - setErrorAndEmit(errorCode, errorDescription); + d->setErrorAndEmit(errorCode, errorDescription); return false; } } if (!setSessionProtocol()) { destroySslContext(); - setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Failed to set protocol version")); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Failed to set protocol version")); return false; } @@ -987,8 +703,8 @@ bool QSslSocketBackendPrivate::initSslContext() if (cfNames) { for (const QByteArray &name : protocolNames) { if (name.size() > 255) { - qCWarning(lcSsl) << "TLS ALPN extension" << name - << "is too long and will be ignored."; + qCWarning(lcTlsBackend) << "TLS ALPN extension" << name + << "is too long and will be ignored."; continue; } else if (name.isEmpty()) { continue; @@ -1002,19 +718,20 @@ bool QSslSocketBackendPrivate::initSslContext() // failed, and handle this non-TLS error, we do not handle // the result of this call as an error: if (SSLSetALPNProtocols(context, cfNames) != errSecSuccess) - qCWarning(lcSsl) << "SSLSetALPNProtocols failed - too long protocol names?"; + qCWarning(lcTlsBackend) << "SSLSetALPNProtocols failed - too long protocol names?"; } } else { - qCWarning(lcSsl) << "failed to allocate ALPN names array"; + qCWarning(lcTlsBackend) << "failed to allocate ALPN names array"; } } #endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE if (mode == QSslSocket::SslClientMode) { // enable Server Name Indication (SNI) + const auto verificationPeerName = d->verificationName(); QString tlsHostName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); if (tlsHostName.isEmpty()) - tlsHostName = hostName; + tlsHostName = d->tlsHostName(); const QByteArray ace(QUrl::toAce(tlsHostName)); SSLSetPeerDomainName(context, ace.data(), ace.size()); @@ -1025,8 +742,8 @@ bool QSslSocketBackendPrivate::initSslContext() if (err != errSecSuccess) { destroySslContext(); - setErrorAndEmit(QSslSocket::SslInternalError, - QStringLiteral("SSLSetSessionOption failed: %1").arg(err)); + d->setErrorAndEmit(QSslSocket::SslInternalError, + QStringLiteral("SSLSetSessionOption failed: %1").arg(err)); return false; } // @@ -1042,8 +759,8 @@ bool QSslSocketBackendPrivate::initSslContext() if (err != errSecSuccess) { destroySslContext(); - setErrorAndEmit(QAbstractSocket::SslInternalError, - QStringLiteral("failed to set SSL context option in server mode: %1").arg(err)); + d->setErrorAndEmit(QAbstractSocket::SslInternalError, + QStringLiteral("failed to set SSL context option in server mode: %1").arg(err)); return false; } } @@ -1055,32 +772,40 @@ bool QSslSocketBackendPrivate::initSslContext() if (configuration.ciphers.size() > 0) { QVector<SSLCipherSuite> cfCiphers; for (const QSslCipher &cipher : configuration.ciphers) { - if (auto sslCipher = QSslSocketBackendPrivate::SSLCipherSuite_from_QSslCipher(cipher)) + if (auto sslCipher = TlsCryptographSecureTransport::SSLCipherSuite_from_QSslCipher(cipher)) cfCiphers << sslCipher; } if (cfCiphers.size() == 0) { - qCWarning(lcSsl) << "failed to add any of the requested ciphers from the configuration"; + qCWarning(lcTlsBackend) << "failed to add any of the requested ciphers from the configuration"; return false; } OSStatus err = SSLSetEnabledCiphers(context, cfCiphers.data(), cfCiphers.size()); if (err != errSecSuccess) { - qCWarning(lcSsl) << "failed to set the ciphers from the configuration"; + qCWarning(lcTlsBackend) << "failed to set the ciphers from the configuration"; return false; } } return true; } -void QSslSocketBackendPrivate::destroySslContext() +void TlsCryptographSecureTransport::destroySslContext() { context.reset(nullptr); } -bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, QAbstractSocket::SocketError &errorCode) +bool TlsCryptographSecureTransport::setSessionCertificate(QString &errorDescription, QAbstractSocket::SocketError &errorCode) { Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); + Q_ASSERT(d); + const auto &configuration = d->privateConfiguration(); + +#ifdef QSSLSOCKET_DEBUG + auto *plainSocket = d->plainTcpSocket(); +#endif + QSslCertificate localCertificate; + if (!configuration.localCertificateChain.isEmpty()) localCertificate = configuration.localCertificateChain.at(0); @@ -1118,8 +843,8 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, OSStatus err = SecPKCS12Import(pkcs12, options, &items); if (err != errSecSuccess) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl) << plainSocket - << QStringLiteral("SecPKCS12Import failed: %1").arg(err); + qCWarning(lcTlsBackend) << plainSocket + << QStringLiteral("SecPKCS12Import failed: %1").arg(err); #endif errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("SecPKCS12Import failed: %1").arg(err); @@ -1128,7 +853,7 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, if (!CFArrayGetCount(items)) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl) << plainSocket << "SecPKCS12Import returned no items"; + qCWarning(lcTlsBackend) << plainSocket << "SecPKCS12Import returned no items"; #endif errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("SecPKCS12Import returned no items"); @@ -1139,7 +864,7 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(import, kSecImportItemIdentity); if (!identity) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl) << plainSocket << "SecPKCS12Import returned no identity"; + qCWarning(lcTlsBackend) << plainSocket << "SecPKCS12Import returned no identity"; #endif errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("SecPKCS12Import returned no identity"); @@ -1164,8 +889,8 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, err = SSLSetCertificate(context, certs); if (err != errSecSuccess) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl) << plainSocket - << QStringLiteral("Cannot set certificate and key: %1").arg(err); + qCWarning(lcTlsBackend) + << plainSocket << QStringLiteral("Cannot set certificate and key: %1").arg(err); #endif errorCode = QAbstractSocket::SslInvalidUserDataError; errorDescription = QStringLiteral("Cannot set certificate and key: %1").arg(err); @@ -1176,18 +901,20 @@ bool QSslSocketBackendPrivate::setSessionCertificate(QString &errorDescription, return true; } -bool QSslSocketBackendPrivate::setSessionProtocol() +bool TlsCryptographSecureTransport::setSessionProtocol() { Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); - + Q_ASSERT(d); // SecureTransport has kTLSProtocol13 constant and also, kTLSProtocolMaxSupported. // Calling SSLSetProtocolVersionMax/Min with any of these two constants results // in errInvalidParam and a failure to set the protocol version. This means // no TLS 1.3 on macOS and iOS. + const auto &configuration = d->privateConfiguration(); + auto *plainSocket = d->plainTcpSocket(); switch (configuration.protocol) { case QSsl::TlsV1_3: case QSsl::TlsV1_3OrLater: - qCWarning(lcSsl) << plainSocket << "SecureTransport does not support TLS 1.3"; + qCWarning(lcTlsBackend) << plainSocket << "SecureTransport does not support TLS 1.3"; return false; default:; } @@ -1196,53 +923,53 @@ bool QSslSocketBackendPrivate::setSessionProtocol() if (configuration.protocol == QSsl::TlsV1_0) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.0"; + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.0"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol1); } else if (configuration.protocol == QSsl::TlsV1_1) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.1"; + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.1"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol11); if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol11); } else if (configuration.protocol == QSsl::TlsV1_2) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.2"; + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol12); if (err == errSecSuccess) err = SSLSetProtocolVersionMax(context, kTLSProtocol12); } else if (configuration.protocol == QSsl::AnyProtocol) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : any"; + qCDebug(lcTlsBackend) << plainSocket << "requesting : any"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); } else if (configuration.protocol == QSsl::SecureProtocols) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2"; + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); } else if (configuration.protocol == QSsl::TlsV1_0OrLater) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2"; + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol1); } else if (configuration.protocol == QSsl::TlsV1_1OrLater) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.1 - TLSv1.2"; + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.1 - TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol11); } else if (configuration.protocol == QSsl::TlsV1_2OrLater) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "requesting : TLSv1.2"; + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.2"; #endif err = SSLSetProtocolVersionMin(context, kTLSProtocol12); } else { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "no protocol version found in the configuration"; + qCDebug(lcTlsBackend) << plainSocket << "no protocol version found in the configuration"; #endif return false; } @@ -1250,17 +977,22 @@ bool QSslSocketBackendPrivate::setSessionProtocol() return err == errSecSuccess; } -bool QSslSocketBackendPrivate::canIgnoreTrustVerificationFailure() const +bool TlsCryptographSecureTransport::canIgnoreTrustVerificationFailure() const { + Q_ASSERT(d); + const auto &configuration = d->privateConfiguration(); const QSslSocket::PeerVerifyMode verifyMode = configuration.peerVerifyMode; - return mode == QSslSocket::SslServerMode + return d->tlsMode() == QSslSocket::SslServerMode && (verifyMode == QSslSocket::QueryPeer || verifyMode == QSslSocket::AutoVerifyPeer || verifyMode == QSslSocket::VerifyNone); } -bool QSslSocketBackendPrivate::verifySessionProtocol() const +bool TlsCryptographSecureTransport::verifySessionProtocol() const { + Q_ASSERT(d); + + const auto &configuration = d->privateConfiguration(); bool protocolOk = false; if (configuration.protocol == QSsl::AnyProtocol) protocolOk = true; @@ -1280,14 +1012,19 @@ bool QSslSocketBackendPrivate::verifySessionProtocol() const return protocolOk; } -bool QSslSocketBackendPrivate::verifyPeerTrust() +bool TlsCryptographSecureTransport::verifyPeerTrust() { - Q_Q(QSslSocket); + Q_ASSERT(q); + Q_ASSERT(d); + auto &configuration = d->privateConfiguration(); + const auto mode = d->tlsMode(); const QSslSocket::PeerVerifyMode verifyMode = configuration.peerVerifyMode; const bool canIgnoreVerify = canIgnoreTrustVerificationFailure(); Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); + + auto *plainSocket = d->plainTcpSocket(); Q_ASSERT(plainSocket); QCFType<SecTrustRef> trust; @@ -1295,8 +1032,8 @@ bool QSslSocketBackendPrivate::verifyPeerTrust() // !trust - SSLCopyPeerTrust can return errSecSuccess but null trust. if (err != errSecSuccess || !trust) { if (!canIgnoreVerify) { - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QStringLiteral("Failed to obtain peer trust: %1").arg(err)); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("Failed to obtain peer trust: %1").arg(err)); plainSocket->disconnectFromHost(); return false; } else { @@ -1317,8 +1054,8 @@ bool QSslSocketBackendPrivate::verifyPeerTrust() if (err != errSecSuccess) { // We can not ignore this, it's not even about trust verification // probably ... - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QStringLiteral("SecTrustEvaluate failed: %1").arg(err)); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("SecTrustEvaluate failed: %1").arg(err)); plainSocket->disconnectFromHost(); return false; } @@ -1349,13 +1086,14 @@ bool QSslSocketBackendPrivate::verifyPeerTrust() const bool doVerifyPeer = verifyMode == QSslSocket::VerifyPeer || (verifyMode == QSslSocket::AutoVerifyPeer - && mode == QSslSocket::SslClientMode); + && d->tlsMode() == 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. 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 QString verificationPeerName = d->verificationName(); if (mode == QSslSocket::SslClientMode) { const QString peerName(verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); if (!isMatchingHostname(configuration.peerCertificate, peerName) && !canIgnoreVerify) { @@ -1386,7 +1124,7 @@ bool QSslSocketBackendPrivate::verifyPeerTrust() if (QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, certData)) CFArrayAppendValue(certArray, secRef); else - qCWarning(lcSsl, "Failed to create SecCertificate from QSslCertificate"); + qCWarning(lcTlsBackend, "Failed to create SecCertificate from QSslCertificate"); } SecTrustSetAnchorCertificates(trust, certArray); @@ -1451,27 +1189,31 @@ bool QSslSocketBackendPrivate::verifyPeerTrust() /* Copied verbatim from qsslsocket_openssl.cpp */ -bool QSslSocketBackendPrivate::checkSslErrors() +bool TlsCryptographSecureTransport::checkSslErrors() { - Q_Q(QSslSocket); if (sslErrors.isEmpty()) return true; - emit q->sslErrors(sslErrors); + Q_ASSERT(q); + Q_ASSERT(d); + emit q->sslErrors(sslErrors); + const auto mode = d->tlsMode(); + const auto &configuration = d->privateConfiguration(); const bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer || (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer && mode == QSslSocket::SslClientMode); - const bool doEmitSslError = !verifyErrorsHaveBeenIgnored(); + const bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); // check whether we need to emit an SSL handshake error if (doVerifyPeer && doEmitSslError) { if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { - pauseSocketNotifiers(q); - paused = true; + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); } else { - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - sslErrors.constFirst().errorString()); - plainSocket->disconnectFromHost(); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + sslErrors.constFirst().errorString()); + Q_ASSERT(d->plainTcpSocket()); + d->plainTcpSocket()->disconnectFromHost(); } return false; } @@ -1479,14 +1221,19 @@ bool QSslSocketBackendPrivate::checkSslErrors() return true; } -bool QSslSocketBackendPrivate::startHandshake() +bool TlsCryptographSecureTransport::startHandshake() { Q_ASSERT(context); - Q_Q(QSslSocket); + Q_ASSERT(q); + Q_ASSERT(d); + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + const auto mode = d->tlsMode(); OSStatus err = SSLHandshake(context); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << plainSocket << "SSLHandhake returned" << err; + qCDebug(lcTlsBackend) << plainSocket << "SSLHandhake returned" << err; #endif if (err == errSSLWouldBlock) { @@ -1507,7 +1254,7 @@ bool QSslSocketBackendPrivate::startHandshake() // setSessionCertificate does not fail if we have no certificate. // Failure means a real error (invalid certificate, no private key, etc). if (!setSessionCertificate(errorDescription, errorCode)) { - setErrorAndEmit(errorCode, errorDescription); + d->setErrorAndEmit(errorCode, errorDescription); renegotiating = false; return false; } else { @@ -1524,15 +1271,15 @@ bool QSslSocketBackendPrivate::startHandshake() } renegotiating = false; - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QStringLiteral("SSLHandshake failed: %1").arg(err)); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("SSLHandshake failed: %1").arg(err)); plainSocket->disconnectFromHost(); return false; } // Connection aborted during handshake phase. if (q->state() != QAbstractSocket::ConnectedState) { - qCDebug(lcSsl) << "connection aborted"; + qCDebug(lcTlsBackend) << "connection aborted"; renegotiating = false; return false; } @@ -1540,7 +1287,7 @@ bool QSslSocketBackendPrivate::startHandshake() // check protocol version ourselves, as Secure Transport does not enforce // the requested min / max versions. if (!verifySessionProtocol()) { - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Protocol version mismatch")); + d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Protocol version mismatch")); plainSocket->disconnectFromHost(); renegotiating = false; return false; @@ -1556,12 +1303,25 @@ bool QSslSocketBackendPrivate::startHandshake() } } +bool TlsCryptographSecureTransport::isHandshakeComplete() const +{ + Q_ASSERT(q); + return q->isEncrypted() && !renegotiating; +} + +QList<QSslError> TlsCryptographSecureTransport::tlsErrors() const +{ + return sslErrors; +} + +} // namespace QTlsPrivate + void QSslSocketPrivate::registerAdHocFactory() { // TLSTODO: this is a temporary solution, waiting for // backends to move to ... plugins. - if (!backend()) - qCWarning(lcSsl, "Failed to create backend factory"); + if (!backendSecureTransport()) + qCWarning(lcTlsBackend, "Failed to create backend factory"); } QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_mac_p.h b/src/network/ssl/qtls_st_p.h index dcd4f4de72..42c0ad622f 100644 --- a/src/network/ssl/qsslsocket_mac_p.h +++ b/src/network/ssl/qtls_st_p.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QSSLSOCKET_MAC_P_H -#define QSSLSOCKET_MAC_P_H +#ifndef QTLS_ST_P_H +#define QTLS_ST_P_H // // W A R N I N G @@ -52,6 +52,10 @@ // #include <QtNetwork/private/qtnetworkglobal_p.h> + +#include "qtlsbackend_st_p.h" + +#include <QtCore/qobject.h> #include <QtCore/qstring.h> #include <QtCore/qglobal.h> #include <QtCore/qlist.h> @@ -64,6 +68,8 @@ QT_BEGIN_NAMESPACE +namespace QTlsPrivate { + class QSecureTransportContext { public: @@ -78,14 +84,13 @@ private: Q_DISABLE_COPY_MOVE(QSecureTransportContext) }; -class QSslSocketBackendPrivate : public QSslSocketPrivate +class TlsCryptographSecureTransport : public TlsCryptograph { - Q_DECLARE_PUBLIC(QSslSocket) public: - QSslSocketBackendPrivate(); - virtual ~QSslSocketBackendPrivate(); + TlsCryptographSecureTransport(); + ~TlsCryptographSecureTransport() override; - // Final-overriders (QSslSocketPrivate): + void init(QSslSocket *qObj, QSslSocketPrivate *dObj) override; void continueHandshake() override; void disconnected() override; void disconnectFromHost() override; @@ -94,17 +99,9 @@ public: void startClientEncryption() override; void startServerEncryption() override; void transmit() override; + QList<QSslError> tlsErrors() const override; - static QList<QSslError> verify(QList<QSslCertificate> certificateChain, - const QString &hostName); - - static bool importPkcs12(QIODevice *device, - QSslKey *key, QSslCertificate *cert, - QList<QSslCertificate> *caCertificates, - const QByteArray &passPhrase); - - static QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher); - static SSLCipherSuite SSLCipherSuite_from_QSslCipher(const QSslCipher &cipher); + SSLCipherSuite SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph); private: // SSL context management/properties: @@ -121,18 +118,24 @@ private: bool checkSslErrors(); bool startHandshake(); - bool isHandshakeComplete() const {return connectionEncrypted && !renegotiating;} + bool isHandshakeComplete() const; // IO callbacks: - static OSStatus ReadCallback(QSslSocketBackendPrivate *socket, char *data, size_t *dataLength); - static OSStatus WriteCallback(QSslSocketBackendPrivate *plainSocket, const char *data, size_t *dataLength); + static OSStatus ReadCallback(TlsCryptographSecureTransport *socket, char *data, size_t *dataLength); + static OSStatus WriteCallback(TlsCryptographSecureTransport *plainSocket, const char *data, size_t *dataLength); QSecureTransportContext context; bool renegotiating = false; + QSslSocket *q = nullptr; + QSslSocketPrivate *d = nullptr; + bool shutdown = false; + QList<QSslError> sslErrors; - Q_DISABLE_COPY_MOVE(QSslSocketBackendPrivate) + Q_DISABLE_COPY_MOVE(TlsCryptographSecureTransport) }; +} // namespace QTlsPrivate + QT_END_NAMESPACE -#endif +#endif // QTLS_ST_P_H diff --git a/src/network/ssl/qtlsbackend.cpp b/src/network/ssl/qtlsbackend.cpp index cdf4f6b55f..1d4cf2844f 100644 --- a/src/network/ssl/qtlsbackend.cpp +++ b/src/network/ssl/qtlsbackend.cpp @@ -40,7 +40,10 @@ #include "qtlsbackend_p.h" #if QT_CONFIG(ssl) +#include "qsslpresharedkeyauthenticator_p.h" +#include "qsslpresharedkeyauthenticator.h" #include "qsslsocket_p.h" +#include "qsslcipher_p.h" #include "qsslkey_p.h" #include "qsslkey.h" #else @@ -199,6 +202,50 @@ TlsKey *X509Certificate::publicKey() const return nullptr; } +#if QT_CONFIG(ssl) + +TlsCryptograph::~TlsCryptograph() = default; + +void TlsCryptograph::checkSettingSslContext(QSharedPointer<QSslContext> tlsContext) +{ + Q_UNUSED(tlsContext); +} + +QSharedPointer<QSslContext> TlsCryptograph::sslContext() const +{ + return {}; +} + +void TlsCryptograph::enableHandshakeContinuation() +{ +} + +void TlsCryptograph::cancelCAFetch() +{ +} + +bool TlsCryptograph::hasUndecryptedData() const +{ + return false; +} + +QList<QOcspResponse> TlsCryptograph::ocsps() const +{ + return {}; +} + +bool TlsCryptograph::isMatchingHostname(const QSslCertificate &cert, const QString &peerName) +{ + return QSslSocketPrivate::isMatchingHostname(cert, peerName); +} + +bool TlsCryptograph::isMatchingHostname(const QString &cn, const QString &hostname) +{ + return QSslSocketPrivate::isMatchingHostname(cn, hostname); +} + +#endif // QT_CONFIG(ssl) + #if QT_CONFIG(dtls) DtlsBase::~DtlsBase() = default; #endif // QT_CONFIG(dtls) @@ -228,6 +275,30 @@ bool QTlsBackend::isValid() const return true; } +long QTlsBackend::tlsLibraryVersionNumber() const +{ + return 0; +} + +QString QTlsBackend::tlsLibraryVersionString() const +{ + return {}; +} + +long QTlsBackend::tlsLibraryBuildVersionNumber() const +{ + return 0; +} + +QString QTlsBackend::tlsLibraryBuildVersionString() const +{ + return {}; +} + +void QTlsBackend::ensureInitialized() const +{ +} + QString QTlsBackend::backendName() const { return QStringLiteral("dummyTLS"); @@ -248,6 +319,12 @@ QTlsPrivate::X509Certificate *QTlsBackend::createCertificate() const return nullptr; } +QList<QSslCertificate> QTlsBackend::systemCaCertificates() const +{ + REPORT_MISSING_SUPPORT("does not provide system CA certificates"); + return {}; +} + QTlsPrivate::TlsCryptograph *QTlsBackend::createTlsCryptograph() const { REPORT_MISSING_SUPPORT("does not support QSslSocket"); @@ -441,4 +518,189 @@ void QTlsBackend::resetBackend(QSslKey &key, QTlsPrivate::TlsKey *keyBackend) #endif // QT_CONFIG(ssl) } +void QTlsBackend::setupClientPskAuth(QSslPreSharedKeyAuthenticator *auth, const char *hint, + int hintLength, unsigned maxIdentityLen, unsigned maxPskLen) +{ + Q_ASSERT(auth); +#if QT_CONFIG(ssl) + if (hint) + auth->d->identityHint = QByteArray::fromRawData(hint, hintLength); // it's NUL terminated, but do not include the NUL + + auth->d->maximumIdentityLength = int(maxIdentityLen) - 1; // needs to be NUL terminated + auth->d->maximumPreSharedKeyLength = int(maxPskLen); +#else + Q_UNUSED(auth); + Q_UNUSED(hint); + Q_UNUSED(hintLength); + Q_UNUSED(maxIdentityLen); + Q_UNUSED(maxPskLen); +#endif +} + +void QTlsBackend::setupServerPskAuth(QSslPreSharedKeyAuthenticator *auth, const char *identity, + const QByteArray &identityHint, unsigned int maxPskLen) +{ +#if QT_CONFIG(ssl) + Q_ASSERT(auth); + auth->d->identityHint = identityHint; + auth->d->identity = identity; + auth->d->maximumIdentityLength = 0; // user cannot set an identity + auth->d->maximumPreSharedKeyLength = int(maxPskLen); +#else + Q_UNUSED(auth); + Q_UNUSED(identity); + Q_UNUSED(identityHint); + Q_UNUSED(maxPskLen); +#endif +} + +#if QT_CONFIG(ssl) +QSslCipher QTlsBackend::createCiphersuite(const QString &descriptionOneLine, int bits, int supportedBits) +{ + QSslCipher ciph; + + const auto descriptionList = QStringView{descriptionOneLine}.split(QLatin1Char(' '), Qt::SkipEmptyParts); + if (descriptionList.size() > 5) { + ciph.d->isNull = false; + ciph.d->name = descriptionList.at(0).toString(); + + QString protoString = descriptionList.at(1).toString(); + ciph.d->protocolString = protoString; + ciph.d->protocol = QSsl::UnknownProtocol; + if (protoString == QLatin1String("TLSv1")) + ciph.d->protocol = QSsl::TlsV1_0; + else if (protoString == QLatin1String("TLSv1.1")) + ciph.d->protocol = QSsl::TlsV1_1; + else if (protoString == QLatin1String("TLSv1.2")) + ciph.d->protocol = QSsl::TlsV1_2; + else if (protoString == QLatin1String("TLSv1.3")) + ciph.d->protocol = QSsl::TlsV1_3; + + if (descriptionList.at(2).startsWith(QLatin1String("Kx="))) + ciph.d->keyExchangeMethod = descriptionList.at(2).mid(3).toString(); + if (descriptionList.at(3).startsWith(QLatin1String("Au="))) + ciph.d->authenticationMethod = descriptionList.at(3).mid(3).toString(); + if (descriptionList.at(4).startsWith(QLatin1String("Enc="))) + ciph.d->encryptionMethod = descriptionList.at(4).mid(4).toString(); + ciph.d->exportable = (descriptionList.size() > 6 && descriptionList.at(6) == QLatin1String("export")); + + ciph.d->bits = bits; + ciph.d->supportedBits = supportedBits; + } + + return ciph; +} + +QSslCipher QTlsBackend::createCiphersuite(const QString &suiteName, QSsl::SslProtocol protocol, + const QString &protocolString) +{ + QSslCipher ciph; + + if (!suiteName.size()) + return ciph; + + ciph.d->isNull = false; + ciph.d->name = suiteName; + ciph.d->protocol = protocol; + ciph.d->protocolString = protocolString; + + const auto bits = QStringView{ciph.d->name}.split(QLatin1Char('-')); + if (bits.size() >= 2) { + if (bits.size() == 2 || bits.size() == 3) + ciph.d->keyExchangeMethod = QLatin1String("RSA"); + else if (bits.front() == QLatin1String("DH") || bits.front() == QLatin1String("DHE")) + ciph.d->keyExchangeMethod = QLatin1String("DH"); + else if (bits.front() == QLatin1String("ECDH") || bits.front() == QLatin1String("ECDHE")) + ciph.d->keyExchangeMethod = QLatin1String("ECDH"); + else + qCWarning(lcSsl) << "Unknown Kx" << ciph.d->name; + + if (bits.size() == 2 || bits.size() == 3) + ciph.d->authenticationMethod = QLatin1String("RSA"); + else if (ciph.d->name.contains(QLatin1String("-ECDSA-"))) + ciph.d->authenticationMethod = QLatin1String("ECDSA"); + else if (ciph.d->name.contains(QLatin1String("-RSA-"))) + ciph.d->authenticationMethod = QLatin1String("RSA"); + else + qCWarning(lcSsl) << "Unknown Au" << ciph.d->name; + + if (ciph.d->name.contains(QLatin1String("RC4-"))) { + ciph.d->encryptionMethod = QLatin1String("RC4(128)"); + ciph.d->bits = 128; + ciph.d->supportedBits = 128; + } else if (ciph.d->name.contains(QLatin1String("DES-CBC3-"))) { + ciph.d->encryptionMethod = QLatin1String("3DES(168)"); + ciph.d->bits = 168; + ciph.d->supportedBits = 168; + } else if (ciph.d->name.contains(QLatin1String("AES128-"))) { + ciph.d->encryptionMethod = QLatin1String("AES(128)"); + ciph.d->bits = 128; + ciph.d->supportedBits = 128; + } else if (ciph.d->name.contains(QLatin1String("AES256-GCM"))) { + ciph.d->encryptionMethod = QLatin1String("AESGCM(256)"); + ciph.d->bits = 256; + ciph.d->supportedBits = 256; + } else if (ciph.d->name.contains(QLatin1String("AES256-"))) { + ciph.d->encryptionMethod = QLatin1String("AES(256)"); + ciph.d->bits = 256; + ciph.d->supportedBits = 256; + } else if (ciph.d->name.contains(QLatin1String("NULL-"))) { + ciph.d->encryptionMethod = QLatin1String("NULL"); + } else { + qCWarning(lcSsl) << "Unknown Enc" << ciph.d->name; + } + } + return ciph; +} + +QSslCipher QTlsBackend::createCipher(const QString &name, QSsl::SslProtocol protocol, + const QString &protocolString) +{ + // Note the name 'createCipher' (not 'ciphersuite'): we don't provide + // information about Kx, Au, bits/supported etc. + QSslCipher cipher; + cipher.d->isNull = false; + cipher.d->name = name; + cipher.d->protocol = protocol; + cipher.d->protocolString = protocolString; + return cipher; +} + +QList<QSslCipher> QTlsBackend::defaultCiphers() +{ + return QSslSocketPrivate::defaultCiphers(); +} + +QList<QSslCipher> QTlsBackend::defaultDtlsCiphers() +{ + return QSslSocketPrivate::defaultDtlsCiphers(); +} + +void QTlsBackend::setDefaultCiphers(const QList<QSslCipher> &ciphers) +{ + QSslSocketPrivate::setDefaultCiphers(ciphers); +} + +void QTlsBackend::setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers) +{ + QSslSocketPrivate::setDefaultDtlsCiphers(ciphers); +} + +void QTlsBackend::setDefaultSupportedCiphers(const QList<QSslCipher> &ciphers) +{ + QSslSocketPrivate::setDefaultSupportedCiphers(ciphers); +} + +void QTlsBackend::resetDefaultEllipticCurves() +{ + QSslSocketPrivate::resetDefaultEllipticCurves(); +} + +void QTlsBackend::setDefaultCaCertificates(const QList<QSslCertificate> &certs) +{ + QSslSocketPrivate::setDefaultCaCertificates(certs); +} + +#endif // QT_CONFIG(ssl) + QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend_openssl.cpp b/src/network/ssl/qtlsbackend_openssl.cpp index 69e3c15198..ef4aab6283 100644 --- a/src/network/ssl/qtlsbackend_openssl.cpp +++ b/src/network/ssl/qtlsbackend_openssl.cpp @@ -40,20 +40,25 @@ #include "qtlsbackend_openssl_p.h" #include "qtlskey_openssl_p.h" #include "qx509_openssl_p.h" +#include "qtls_openssl_p.h" +#include "qsslcipher_p.h" +//#include "qsslsocket_p.h" +#include "qsslcipher.h" #if QT_CONFIG(dtls) #include "qdtls_openssl_p.h" #endif // QT_CONFIG(dtls) -// TLSTODO: Later, this code (ensure initialised, etc.) -// must move from the socket to backend. -#include "qsslsocket_p.h" -// #include "qsslsocket_openssl_symbols_p.h" +#include "qopenssl_p.h" #include <qssl.h> +#include <qdir.h> +#include <qdiriterator.h> #include <qlist.h> +#include <qmutex.h> +#include <qscopeguard.h> #include <algorithm> @@ -61,6 +66,36 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl"); +Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex) + +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(QLatin1String("adh")) && + !ciph.name().toLower().startsWith(QLatin1String("exp-adh")) && + !ciph.name().toLower().startsWith(QLatin1String("aecdh"))) { + ciphers << ciph; + + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; + } + } + } + } +} + +bool QTlsBackendOpenSSL::s_libraryLoaded = false; +bool QTlsBackendOpenSSL::s_loadedCiphersAndCerts = false; +int QTlsBackendOpenSSL::s_indexForSSLExtraData = -1; + QString QTlsBackendOpenSSL::getErrorsFromOpenSsl() { QString errorString; @@ -88,6 +123,41 @@ void QTlsBackendOpenSSL::clearErrorQueue() Q_UNUSED(errs); } +bool QTlsBackendOpenSSL::ensureLibraryLoaded() +{ + if (!q_resolveOpenSslSymbols()) + return false; + + const QMutexLocker locker(qt_opensslInitMutex()); + + if (!s_libraryLoaded) { + // 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; + } + + s_libraryLoaded = true; + } + + return true; +} + QString QTlsBackendOpenSSL::backendName() const { return builtinBackendNames[nameIndexOpenSSL]; @@ -95,9 +165,124 @@ QString QTlsBackendOpenSSL::backendName() const bool QTlsBackendOpenSSL::isValid() const { - // TLSTODO: backend should do initialization, - // not socket. - return QSslSocket::supportsSsl(); + 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 +{ + const QMutexLocker locker(qt_opensslInitMutex()); + + if (s_loadedCiphersAndCerts) + return; + s_loadedCiphersAndCerts = true; + + 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) + QList<QByteArray> dirs = QSslSocketPrivate::unixRootCertDirectories(); + QStringList symLinkFilter; + symLinkFilter << QLatin1String("[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]"); + for (int a = 0; a < dirs.count(); ++a) { + QDirIterator iterator(QLatin1String(dirs.at(a)), symLinkFilter, QDir::Files); + if (iterator.hasNext()) { + 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 @@ -167,6 +352,98 @@ QTlsPrivate::X509Certificate *QTlsBackendOpenSSL::createCertificate() const return new QTlsPrivate::X509CertificateOpenSSL; } +namespace QTlsPrivate { + +// TLSTODO: remove. +#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) +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 = CertOpenSystemStoreW(0, 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_UNIX) + QSet<QString> certFiles; + QDir currentDir; + QStringList nameFilters; + QList<QByteArray> directories; + QSsl::EncodingFormat platformEncodingFormat; +# ifndef Q_OS_ANDROID + directories = QSslSocketPrivate::unixRootCertDirectories(); + nameFilters << QLatin1String("*.pem") << QLatin1String("*.crt"); + platformEncodingFormat = QSsl::Pem; +# else + // Q_OS_ANDROID + QByteArray ministroPath = qgetenv("MINISTRO_SSL_CERTS_PATH"); // Set by Ministro + directories << ministroPath; + nameFilters << QLatin1String("*.der"); + platformEncodingFormat = QSsl::Der; +# ifndef Q_OS_ANDROID_EMBEDDED + if (ministroPath.isEmpty()) { + QList<QByteArray> certificateData = fetchSslCertificateData(); + for (int i = 0; i < certificateData.size(); ++i) { + systemCerts.append(QSslCertificate::fromData(certificateData.at(i), QSsl::Der)); + } + } else +# endif //Q_OS_ANDROID_EMBEDDED +# endif //Q_OS_ANDROID + { + currentDir.setNameFilters(nameFilters); + for (int a = 0; a < directories.count(); a++) { + currentDir.setPath(QLatin1String(directories.at(a))); + QDirIterator it(currentDir); + while (it.hasNext()) { + it.next(); + // use canonical path here to not load the same certificate twice if symlinked + certFiles.insert(it.fileInfo().canonicalFilePath()); + } + } + for (const QString& file : qAsConst(certFiles)) + systemCerts.append(QSslCertificate::fromPath(file, platformEncodingFormat)); +# ifndef Q_OS_ANDROID + systemCerts.append(QSslCertificate::fromPath(QLatin1String("/etc/pki/tls/certs/ca-bundle.crt"), QSsl::Pem)); // Fedora, Mandriva + systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/local/share/certs/ca-root-nss.crt"), QSsl::Pem)); // FreeBSD's ca_root_nss +# endif + } +#endif +#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) @@ -177,6 +454,11 @@ QTlsPrivate::DtlsCookieVerifier *QTlsBackendOpenSSL::createDtlsCookieVerifier() #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) @@ -233,10 +515,7 @@ QList<int> QTlsBackendOpenSSL::ellipticCurvesIds() const if (name.isEmpty()) return nid; - // TLSTODO: check if it's needed! The fact we are here, - // means OpenSSL was loaded, symbols resolved. Is it because - // of ensureCiphers(AndCertificates)Loaded ? - QSslSocketPrivate::ensureInitialized(); + ensureInitialized(); // TLSTODO: check if it's needed! #ifndef OPENSSL_NO_EC const QByteArray curveNameLatin1 = name.toLatin1(); nid = q_OBJ_sn2nid(curveNameLatin1.data()); @@ -254,10 +533,7 @@ QList<int> QTlsBackendOpenSSL::ellipticCurvesIds() const if (name.isEmpty()) return nid; - // TLSTODO: check if it's needed! The fact we are here, - // means OpenSSL was loaded, symbols resolved. Is it because - // of ensureCiphers(AndCertificates)Loaded ? - QSslSocketPrivate::ensureInitialized(); + ensureInitialized(); #ifndef OPENSSL_NO_EC const QByteArray curveNameLatin1 = name.toLatin1(); @@ -336,4 +612,19 @@ bool QTlsBackendOpenSSL::isTlsNamedCurve(int id) const 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); +} + QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend_openssl_p.h b/src/network/ssl/qtlsbackend_openssl_p.h index 9f9a65f292..1a02bf5bed 100644 --- a/src/network/ssl/qtlsbackend_openssl_p.h +++ b/src/network/ssl/qtlsbackend_openssl_p.h @@ -54,22 +54,44 @@ #include <private/qtnetworkglobal_p.h> #include "qssldiffiehellmanparameters.h" +#include "qsslcertificate.h" #include "qtlsbackend_p.h" #include <QtCore/qglobal.h> +#include <QtCore/qlist.h> +#include <openssl/ssl.h> QT_BEGIN_NAMESPACE class QTlsBackendOpenSSL final : public QTlsBackend { public: + static QString getErrorsFromOpenSsl(); static void logAndClearErrorQueue(); static void clearErrorQueue(); + + static bool ensureLibraryLoaded(); + // Index used in SSL_get_ex_data to get the matching TlsCryptographerOpenSSL: + static bool s_libraryLoaded; + static bool s_loadedCiphersAndCerts; + static int s_indexForSSLExtraData; + + static QString msgErrorsDuringHandshake(); + static QSslCipher qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher); private: + 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; @@ -80,7 +102,9 @@ private: // 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; diff --git a/src/network/ssl/qtlsbackend_p.h b/src/network/ssl/qtlsbackend_p.h index 300d12c259..9015be7a8e 100644 --- a/src/network/ssl/qtlsbackend_p.h +++ b/src/network/ssl/qtlsbackend_p.h @@ -62,11 +62,12 @@ #endif #include <QtNetwork/qsslcertificate.h> -#include <QtNetwork/qsslerror.h> +#include <QtNetwork/qsslcipher.h> #include <QtNetwork/qsslkey.h> #include <QtNetwork/qssl.h> #include <QtCore/qloggingcategory.h> +#include <QtCore/qsharedpointer.h> #include <QtCore/qnamespace.h> #include <QtCore/qobject.h> #include <QtCore/qglobal.h> @@ -78,11 +79,17 @@ QT_BEGIN_NAMESPACE +class QSslPreSharedKeyAuthenticator; +class QSslSocketPrivate; class QHostAddress; +class QSslContext; + +class QSslSocket; class QByteArray; class QSslCipher; class QUdpSocket; class QIODevice; +class QSslError; class QSslKey; namespace QTlsPrivate { @@ -93,9 +100,8 @@ namespace QTlsPrivate { // however strange they are, for now preserved to ease the transition // (this may change in future - for example, 'decodeDer' is not just // decoding DER, it's initializing a key from DER. Note, QSslKey requires -// a real TLS library because private keys tend to be encrypted. This -// base class does not need a working TLS library. -class TlsKey { +// a real TLS library because private keys tend to be encrypted. +class Q_NETWORK_PRIVATE_EXPORT TlsKey { public: virtual ~TlsKey(); @@ -137,7 +143,7 @@ public: // An abstraction hiding OpenSSL's X509 or our generic // 'derData'-based code. -class X509Certificate +class Q_NETWORK_PRIVATE_EXPORT X509Certificate { public: virtual ~X509Certificate(); @@ -191,12 +197,43 @@ using X509Pkcs12ReaderPtr = bool (*)(QIODevice *device, QSslKey *key, QSslCertif QList<QSslCertificate> *caCertificates, const QByteArray &passPhrase); +#if QT_CONFIG(ssl) // TLS over TCP. Handshake, encryption/decryption. +class Q_NETWORK_PRIVATE_EXPORT TlsCryptograph : public QObject +{ +public: + virtual ~TlsCryptograph(); + + virtual void init(QSslSocket *q, QSslSocketPrivate *d) = 0; + virtual void checkSettingSslContext(QSharedPointer<QSslContext> tlsContext); + virtual QSharedPointer<QSslContext> sslContext() const; + + virtual QList<QSslError> tlsErrors() const = 0; + + virtual void startClientEncryption() = 0; + virtual void startServerEncryption() = 0; + virtual void continueHandshake() = 0; + virtual void enableHandshakeContinuation(); + virtual void disconnectFromHost() = 0; + virtual void disconnected() = 0; + virtual void cancelCAFetch(); + virtual QSslCipher sessionCipher() const = 0; + virtual QSsl::SslProtocol sessionProtocol() const = 0; + + virtual void transmit() = 0; + virtual bool hasUndecryptedData() const; + virtual QList<QOcspResponse> ocsps() const; + + static bool isMatchingHostname(const QSslCertificate &cert, const QString &peerName); + static bool isMatchingHostname(const QString &cn, const QString &hostname); +}; +#else class TlsCryptograph; +#endif // QT_CONFIG(ssl) #if QT_CONFIG(dtls) -class DtlsBase +class Q_NETWORK_PRIVATE_EXPORT DtlsBase { public: virtual ~DtlsBase(); @@ -217,7 +254,7 @@ public: }; // DTLS cookie: generation and verification. -class DtlsCookieVerifier : virtual public DtlsBase +class Q_NETWORK_EXPORT DtlsCookieVerifier : virtual public DtlsBase { public: virtual bool verifyClient(QUdpSocket *socket, const QByteArray &dgram, @@ -226,7 +263,7 @@ public: }; // TLS over UDP. Handshake, encryption/decryption. -class DtlsCryptograph : virtual public DtlsBase +class Q_NETWORK_PRIVATE_EXPORT DtlsCryptograph : virtual public DtlsBase { public: @@ -279,6 +316,11 @@ public: ~QTlsBackend() override; virtual bool isValid() const; + virtual long tlsLibraryVersionNumber() const; + virtual QString tlsLibraryVersionString() const; + virtual long tlsLibraryBuildVersionNumber() const; + virtual QString tlsLibraryBuildVersionString() const; + virtual void ensureInitialized() const; virtual QString backendName() const = 0; virtual QList<QSsl::SslProtocol> supportedProtocols() const = 0; @@ -289,6 +331,8 @@ public: virtual QTlsPrivate::TlsKey *createKey() const; virtual QTlsPrivate::X509Certificate *createCertificate() const; + virtual QList<QSslCertificate> systemCaCertificates() const; + // TLS and DTLS: virtual QTlsPrivate::TlsCryptograph *createTlsCryptograph() const; virtual QTlsPrivate::DtlsCryptograph *createDtlsCryptograph(class QDtls *qObject, int mode) const; @@ -338,6 +382,31 @@ public: static void resetBackend(QSslKey &key, QTlsPrivate::TlsKey *keyBackend); + static void setupClientPskAuth(QSslPreSharedKeyAuthenticator *auth, const char *hint, + int hintLength, unsigned maxIdentityLen, unsigned maxPskLen); + static void setupServerPskAuth(QSslPreSharedKeyAuthenticator *auth, const char *identity, + const QByteArray &identityHint, unsigned maxPskLen); +#if QT_CONFIG(ssl) + static QSslCipher createCiphersuite(const QString &description, int bits, int supportedBits); + static QSslCipher createCiphersuite(const QString &suiteName, QSsl::SslProtocol protocol, + const QString &protocolString); + static QSslCipher createCipher(const QString &name, QSsl::SslProtocol protocol, + const QString &protocolString); + + // Those statics are implemented using QSslSocketPrivate (which is not exported, + // unlike QTlsBackend). + static QList<QSslCipher> defaultCiphers(); + static QList<QSslCipher> defaultDtlsCiphers(); + + static void setDefaultCiphers(const QList<QSslCipher> &ciphers); + static void setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers); + static void setDefaultSupportedCiphers(const QList<QSslCipher> &ciphers); + + static void resetDefaultEllipticCurves(); + + static void setDefaultCaCertificates(const QList<QSslCertificate> &certs); +#endif // QT_CONFIG(ssl) + Q_DISABLE_COPY_MOVE(QTlsBackend) }; diff --git a/src/network/ssl/qtlsbackend_schannel_p.h b/src/network/ssl/qtlsbackend_schannel_p.h index 951c73256e..ca1cb9e621 100644 --- a/src/network/ssl/qtlsbackend_schannel_p.h +++ b/src/network/ssl/qtlsbackend_schannel_p.h @@ -62,7 +62,18 @@ QT_BEGIN_NAMESPACE class QSchannelBackend : public QTlsBackend { +public: + static void ensureInitializedImplementation(); + private: + long tlsLibraryVersionNumber() const override; + QString tlsLibraryVersionString() const override; + long tlsLibraryBuildVersionNumber() const override; + QString tlsLibraryBuildVersionString() const override; + void ensureInitialized() const override; + + static void resetDefaultCiphers(); + QString backendName() const override; QList<QSsl::SslProtocol> supportedProtocols() const override; QList<QSsl::SupportedFeature> supportedFeatures() const override; @@ -71,8 +82,15 @@ private: QTlsPrivate::TlsKey *createKey() const override; QTlsPrivate::X509Certificate *createCertificate() const override; + QTlsPrivate::TlsCryptograph * createTlsCryptograph() const override; + + QList<QSslCertificate> systemCaCertificates() const override; + static QList<QSslCertificate> systemCaCertificatesImplementation(); + QTlsPrivate::X509PemReaderPtr X509PemReader() const override; QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + + static bool s_loadedCiphersAndCerts; }; QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend_st.cpp b/src/network/ssl/qtlsbackend_st.cpp index 3bf95ec79e..7fc7692350 100644 --- a/src/network/ssl/qtlsbackend_st.cpp +++ b/src/network/ssl/qtlsbackend_st.cpp @@ -40,11 +40,234 @@ #include "qtlsbackend_st_p.h" #include "qtlskey_st_p.h" #include "qx509_st_p.h" +#include "qtls_st_p.h" + +#include <QtCore/qsysinfo.h> +#include <QtCore/qmutex.h> QT_BEGIN_NAMESPACE +Q_GLOBAL_STATIC(QRecursiveMutex, qt_securetransport_mutex) + Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.securetransport"); +namespace QTlsPrivate { + +QList<QSslCertificate> systemCaCertificates(); // defined in qsslsocket_mac_shared.cpp + +SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode); + +QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher) +{ + QString name; + switch (cipher) { + // Sorted as in CipherSuite.h (and groupped by their RFC) + // TLS addenda using AES, per RFC 3268 + case TLS_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("AES128-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("DHE-RSA-AES128-SHA"); + break; + case TLS_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("AES256-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("DHE-RSA-AES256-SHA"); + break; + + // ECDSA addenda, RFC 4492 + case TLS_ECDH_ECDSA_WITH_NULL_SHA: + name = QLatin1String("ECDH-ECDSA-NULL-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDH-ECDSA-RC4-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-DES-CBC3-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-AES128-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-AES256-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_NULL_SHA: + name = QLatin1String("ECDHE-ECDSA-NULL-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDHE-ECDSA-RC4-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-DES-CBC3-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-AES128-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-AES256-SHA"); + break; + case TLS_ECDH_RSA_WITH_NULL_SHA: + name = QLatin1String("ECDH-RSA-NULL-SHA"); + break; + case TLS_ECDH_RSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDH-RSA-RC4-SHA"); + break; + case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDH-RSA-DES-CBC3-SHA"); + break; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDH-RSA-AES128-SHA"); + break; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDH-RSA-AES256-SHA"); + break; + case TLS_ECDHE_RSA_WITH_NULL_SHA: + name = QLatin1String("ECDHE-RSA-NULL-SHA"); + break; + case TLS_ECDHE_RSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDHE-RSA-RC4-SHA"); + break; + case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDHE-RSA-DES-CBC3-SHA"); + break; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDHE-RSA-AES128-SHA"); + break; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDHE-RSA-AES256-SHA"); + break; + + // TLS 1.2 addenda, RFC 5246 + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("DES-CBC3-SHA"); + break; + case TLS_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("AES128-SHA256"); + break; + case TLS_RSA_WITH_AES_256_CBC_SHA256: + name = QLatin1String("AES256-SHA256"); + break; + case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("DHE-RSA-DES-CBC3-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("DHE-RSA-AES128-SHA256"); + break; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + name = QLatin1String("DHE-RSA-AES256-SHA256"); + break; + + // Addendum from RFC 4279, TLS PSK + // all missing atm. + + // RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption + // all missing atm. + + // Addenda from rfc 5288 AES Galois Counter Mode (CGM) Cipher Suites for TLS + case TLS_RSA_WITH_AES_256_GCM_SHA384: + name = QLatin1String("AES256-GCM-SHA384"); + break; + + // RFC 5487 - PSK with SHA-256/384 and AES GCM + // all missing atm. + + // Addenda from rfc 5289 Elliptic Curve Cipher Suites with HMAC SHA-256/384 + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDHE-ECDSA-AES128-SHA256"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDHE-ECDSA-AES256-SHA384"); + break; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDH-ECDSA-AES128-SHA256"); + break; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDH-ECDSA-AES256-SHA384"); + break; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDHE-RSA-AES128-SHA256"); + break; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDHE-RSA-AES256-SHA384"); + break; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDH-RSA-AES128-SHA256"); + break; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDH-RSA-AES256-SHA384"); + break; + + // Addenda from rfc 5289 Elliptic Curve Cipher Suites + // with SHA-256/384 and AES Galois Counter Mode (GCM) + case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + name = QLatin1String("ECDHE-RSA-AES256-GCM-SHA384"); + break; + + default: + return {}; + } + + return QTlsBackend::createCiphersuite(name, QSsl::TlsV1_2, QLatin1String("TLSv1.2")); +} + +} // namespace QTlsPrivate + +bool QSecureTransportBackend::s_loadedCiphersAndCerts = false; + +QString QSecureTransportBackend::tlsLibraryVersionString() const +{ + return QLatin1String("Secure Transport, ") + QSysInfo::prettyProductName(); +} + +QString QSecureTransportBackend::tlsLibraryBuildVersionString() const +{ + return tlsLibraryVersionString(); +} + +void QSecureTransportBackend::ensureInitialized() const +{ + const QMutexLocker locker(qt_securetransport_mutex()); + if (s_loadedCiphersAndCerts) + return; + + // We have to set it before setDefaultSupportedCiphers, + // since this function can trigger static (global)'s initialization + // and as a result - recursive ensureInitialized call + // from QSslCertificatePrivate's ctor. + s_loadedCiphersAndCerts = true; + + const QTlsPrivate::QSecureTransportContext context(QTlsPrivate::qt_createSecureTransportContext(QSslSocket::SslClientMode)); + if (context) { + QList<QSslCipher> ciphers; + QList<QSslCipher> defaultCiphers; + + size_t numCiphers = 0; + // Fails only if any of parameters is null. + SSLGetNumberSupportedCiphers(context, &numCiphers); + QList<SSLCipherSuite> cfCiphers(numCiphers); + // Fails only if any of parameter is null or number of ciphers is wrong. + SSLGetSupportedCiphers(context, cfCiphers.data(), &numCiphers); + + for (size_t i = 0; i < size_t(cfCiphers.size()); ++i) { + const QSslCipher ciph(QTlsPrivate::QSslCipher_from_SSLCipherSuite(cfCiphers.at(i))); + if (!ciph.isNull()) { + ciphers << ciph; + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; + } + } + + setDefaultSupportedCiphers(ciphers); + setDefaultCiphers(defaultCiphers); + + if (!QSslSocketPrivate::rootCertOnDemandLoadingSupported()) + setDefaultCaCertificates(systemCaCertificates()); + } else { + s_loadedCiphersAndCerts = false; + } +} + QString QSecureTransportBackend::backendName() const { return builtinBackendNames[nameIndexSecureTransport]; @@ -60,6 +283,11 @@ QTlsPrivate::X509Certificate *QSecureTransportBackend::createCertificate() const return new QTlsPrivate::X509CertificateSecureTransport; } +QList<QSslCertificate> QSecureTransportBackend::systemCaCertificates() const +{ + return QTlsPrivate::systemCaCertificates(); +} + QList<QSsl::SslProtocol> QSecureTransportBackend::supportedProtocols() const { QList<QSsl::SslProtocol> protocols; @@ -104,5 +332,10 @@ QTlsPrivate::X509DerReaderPtr QSecureTransportBackend::X509DerReader() const return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; } +QTlsPrivate::TlsCryptograph *QSecureTransportBackend::createTlsCryptograph() const +{ + return new QTlsPrivate::TlsCryptographSecureTransport; +} + QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend_st_p.h b/src/network/ssl/qtlsbackend_st_p.h index 9dea26c8d7..b0f3050674 100644 --- a/src/network/ssl/qtlsbackend_st_p.h +++ b/src/network/ssl/qtlsbackend_st_p.h @@ -63,6 +63,11 @@ QT_BEGIN_NAMESPACE class QSecureTransportBackend : public QTlsBackend { private: + + QString tlsLibraryVersionString() const override; + virtual QString tlsLibraryBuildVersionString() const override; + virtual void ensureInitialized() const override; + QString backendName() const override; QList<QSsl::SslProtocol> supportedProtocols() const override; @@ -72,8 +77,14 @@ private: QTlsPrivate::TlsKey *createKey() const override; QTlsPrivate::X509Certificate *createCertificate() const override; + QList<QSslCertificate> systemCaCertificates() const override; + QTlsPrivate::X509PemReaderPtr X509PemReader() const override; QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + + QTlsPrivate::TlsCryptograph *createTlsCryptograph() const override; + + static bool s_loadedCiphersAndCerts; }; QT_END_NAMESPACE diff --git a/src/network/ssl/qtls_utils_p.h b/src/network/ssl/qwincrypt_p.h index 514d1fefd0..2a7bd1fae2 100644 --- a/src/network/ssl/qtls_utils_p.h +++ b/src/network/ssl/qwincrypt_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -37,15 +37,15 @@ ** ****************************************************************************/ -#ifndef QTLS_UTILS_P_H -#define QTLS_UTILS_P_H +#ifndef QWINCRYPT_P_H +#define 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 +// 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. @@ -53,47 +53,29 @@ #include <QtNetwork/private/qtnetworkglobal_p.h> -#if QT_CONFIG(openssl) -#include <QtNetwork/private/qsslsocket_openssl_p.h> -#endif - -#include <QtNetwork/private/qssl_p.h> +#include <QtCore/qt_windows.h> #include <QtCore/qglobal.h> -#include <QtCore/qdebug.h> + +#include <wincrypt.h> +#ifndef HCRYPTPROV_LEGACY +#define HCRYPTPROV_LEGACY HCRYPTPROV +#endif // !HCRYPTPROV_LEGACY #include <memory> QT_BEGIN_NAMESPACE -namespace QTlslUtils -{ - -template <class NativeTlsType, void (*Deleter)(NativeTlsType *)> -void safe_delete(NativeTlsType *object) -{ - if (object) - Deleter(object); -} - -template<class NativeTlsType, int ok, int (*Deleter)(NativeTlsType *)> -void safe_delete(NativeTlsType *object) -{ - if (object) { - if (Deleter(object) != ok) { - qCWarning(lcSsl, "Failed to free a resource."); -#if QT_CONFIG(openssl) // || wolfssl later - QSslSocketBackendPrivate::logAndClearErrorQueue(); -#endif // QT_CONFIG(openssl) - } +struct QHCertStoreDeleter { + void operator()(HCERTSTORE store) + { + CertCloseStore(store, 0); } -} - -template<class NativeTlsType> -using Deleter = std::unique_ptr<NativeTlsType, void (*)(NativeTlsType *)>; +}; -} // namespace QTlsUtils +// A simple RAII type used by Schannel code and Window CA fetcher class: +using QHCertStorePointer = std::unique_ptr<void, QHCertStoreDeleter>; QT_END_NAMESPACE -#endif // QTLS_UTILS_P_H +#endif // QWINCRYPT_P_H diff --git a/src/network/ssl/qwindowscarootfetcher.cpp b/src/network/ssl/qwindowscarootfetcher.cpp index c414ca580b..b675ac8d4e 100644 --- a/src/network/ssl/qwindowscarootfetcher.cpp +++ b/src/network/ssl/qwindowscarootfetcher.cpp @@ -52,7 +52,8 @@ #include "qsslsocket_p.h" // Transitively includes Wincrypt.h #if QT_CONFIG(openssl) -#include "qsslsocket_openssl_p.h" +#include "qopenssl_p.h" +#include "qx509_openssl_p.h" #endif QT_BEGIN_NAMESPACE @@ -77,7 +78,9 @@ Q_GLOBAL_STATIC(QWindowsCaRootFetcherThread, windowsCaRootFetcherThread); #if QT_CONFIG(openssl) namespace { - +// TLSTODO: we have to ask the currently active TLS backend about verification +// support and get a function pointer. QT_CONFIG(openssl) check is becoming useless +// as soon as we have several plugins. const QList<QSslCertificate> buildVerifiedChain(const QList<QSslCertificate> &caCertificates, PCCERT_CHAIN_CONTEXT chainContext, const QString &peerVerifyName) @@ -123,7 +126,7 @@ const QList<QSslCertificate> buildVerifiedChain(const QList<QSslCertificate> &ca } // We rely on OpenSSL's ability to find other problems. - const auto tlsErrors = QSslSocketBackendPrivate::verify(caCertificates, verifiedChain, peerVerifyName); + const auto tlsErrors = QTlsPrivate::X509CertificateOpenSSL::verify(caCertificates, verifiedChain, peerVerifyName); if (tlsErrors.size()) verifiedChain.clear(); @@ -195,7 +198,7 @@ void QWindowsCaRootFetcher::start() qCDebug(lcSsl) << " - NOT TRUSTED" << chain->TrustStatus.dwErrorStatus; if (chain->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) qCDebug(lcSsl) << " - SELF SIGNED"; - qCDebug(lcSsl) << "QSslSocketBackendPrivate::fetchCaRootForCert - dumping simple chains"; + 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; diff --git a/src/network/ssl/qwindowscarootfetcher_p.h b/src/network/ssl/qwindowscarootfetcher_p.h index e98e59f0cf..ee0d07c1e0 100644 --- a/src/network/ssl/qwindowscarootfetcher_p.h +++ b/src/network/ssl/qwindowscarootfetcher_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. @@ -40,15 +40,15 @@ #ifndef QWINDOWSCAROOTFETCHER_P_H #define QWINDOWSCAROOTFETCHER_P_H +#include <QtNetwork/private/qtnetworkglobal_p.h> + #include <QtCore/QtGlobal> #include <QtCore/QObject> -#include "qsslsocket_p.h" - -#include "qsslsocket.h" #include "qsslcertificate.h" +#include "qsslsocket.h" -#include <memory> +#include "qwincrypt_p.h" // // W A R N I N G diff --git a/src/network/ssl/qx509_openssl.cpp b/src/network/ssl/qx509_openssl.cpp index 51dc25bd16..6c4a83be0b 100644 --- a/src/network/ssl/qx509_openssl.cpp +++ b/src/network/ssl/qx509_openssl.cpp @@ -43,7 +43,8 @@ #include "qx509_openssl_p.h" #include "qsslsocket_openssl_symbols_p.h" - +#include "qtlsbackend_openssl_p.h" +#include "qtls_openssl_p.h" #include "qsslsocket.h" #include <QtNetwork/qhostaddress.h> @@ -356,8 +357,8 @@ extern "C" int qt_X509Callback(int ok, X509_STORE_CTX *ctx) // 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 = QSslSocketBackendPrivate::s_indexForSSLExtraData - + QSslSocketBackendPrivate::errorOffsetInExData; + 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)); } @@ -587,7 +588,7 @@ QList<QSslError> X509CertificateOpenSSL::verify(const QList<QSslCertificate> &ch // 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::s_loadRootCertsOnDemand) + if (QSslSocketPrivate::rootCertOnDemandLoadingSupported()) roots.append(QSslSocketPrivate::systemCaCertificates()); #endif // Q_OS_WIN return verify(roots, chain, hostName); diff --git a/src/network/ssl/qx509_openssl_p.h b/src/network/ssl/qx509_openssl_p.h index ad5786d5e3..5bc5ad63f5 100644 --- a/src/network/ssl/qx509_openssl_p.h +++ b/src/network/ssl/qx509_openssl_p.h @@ -53,8 +53,7 @@ #include <private/qtnetworkglobal_p.h> -// TLSTODO: only temporary, and only because of QSslErrorEntry! -#include <private/qsslsocket_openssl_p.h> +#include <private/qopenssl_p.h> #include <private/qtlsbackend_p.h> #include <private/qx509_base_p.h> |